This commit is contained in:
krahets
2026-11-04 01:07:29 +08:00
parent b1daaffc33
commit 1bbfde55c8
293 changed files with 2446 additions and 3262 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -0,0 +1,273 @@
/* Shared by the Zensical and legacy landing pages. */
:root {
--home-universe-bg: #171717;
}
/* Keep the landing header and static/animated scene on the same opaque surface. */
.md-header.md-header--landing {
background-color: var(--home-universe-bg);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
.home-div.home-universe-bg {
--hero-starfield-bg-color: var(--home-universe-bg);
background-color: var(--home-universe-bg);
position: relative;
display: flex;
flex-direction: column;
height: auto;
min-height: calc(min(100svh, 120vw) - 2.4rem);
margin-top: 0;
padding: 0 0 1rem;
overflow: hidden;
}
/* Keep the map below the header and let captions grow in their own space.
Absolute positioning clipped the map and overlaid its bottom labels on short screens. */
.home-div.home-universe-bg > .hero-div {
flex: none;
max-width: 100%;
height: auto;
aspect-ratio: 4 / 3;
margin: 0 auto;
}
.home-div.home-universe-bg > .heading-div {
position: relative;
left: auto;
bottom: auto;
transform: none;
padding: 0 .75rem 2rem;
}
.home-div.home-universe-bg > .heading-div p {
text-wrap: balance;
}
/* Reserve room for the heading and actions on ordinary desktop screens.
Short landscape screens keep a readable map and scroll instead of shrinking it. */
@media (min-width: 901px) and (min-height: 640px) {
.home-div.home-universe-bg > .hero-div {
max-width: min(100%, calc((100svh - 16rem) * 4 / 3));
}
}
.hero-universe .starfield {
pointer-events: none;
background-image:
linear-gradient(var(--home-universe-bg) 72px, transparent 200px),
radial-gradient(ellipse at 25% 30%, rgba(85, 174, 166, 0.055), transparent 55%),
radial-gradient(ellipse at 80% 60%, rgba(130, 144, 187, 0.045), transparent 50%);
}
.hero-universe .hero-stars {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* Drift is independent of hover; selection never changes an object's size. */
.hero-universe .hero-div > a > img,
.hero-universe .hero-div > a > span {
translate: var(--hero-x, 0px) var(--hero-y, 0px);
}
.hero-universe .hero-div > a > img {
rotate: var(--hero-angle, 0deg);
transition: transform 240ms ease, filter 240ms ease;
}
.hero-universe .hero-ground,
.hero-universe .hero-links {
translate: var(--hero-ground-x, 0px) var(--hero-ground-y, 0px);
pointer-events: none;
}
.hero-universe .hero-links {
opacity: .72;
transition: filter 300ms ease;
}
.hero-universe .hero-network-active > .hero-links {
filter: brightness(1.16) drop-shadow(0 0 2px rgba(126, 207, 196, 0.22));
}
.hero-universe .hero-div > a.hero-selected > img {
transform: none;
filter: brightness(1.04) drop-shadow(0 0 3px rgba(147, 213, 204, 0.18));
}
.hero-universe .hero-div > a.hero-selected > span {
color: var(--md-typeset-btn-color);
text-decoration: underline;
text-underline-offset: 0.2em;
}
/* The positioned children, rather than the zero-size inline anchor, show focus. */
.hero-universe .hero-div > a:focus-visible {
outline: none;
}
.hero-universe .hero-div > a:focus-visible > span {
outline: 2px solid var(--md-typeset-btn-color);
outline-offset: 5px;
border-radius: 2px;
}
.hero-universe .hero-motion-toggle {
position: absolute;
bottom: 0.65rem;
right: 0.65rem;
z-index: 4;
display: grid;
place-items: center;
width: 44px;
height: 44px;
padding: 11px;
border: 1px solid rgba(197, 216, 213, 0.25);
border-radius: 50%;
background: rgba(30, 30, 30, 0.7);
color: #bbceca;
cursor: pointer;
transition: border-color 200ms ease, color 200ms ease;
}
.hero-universe .hero-motion-toggle:hover,
.hero-universe .hero-motion-toggle:focus-visible {
color: #e1f3ef;
border-color: var(--md-typeset-btn-color);
outline: 2px solid var(--md-typeset-btn-color);
outline-offset: 3px;
}
.hero-motion-toggle svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 1.7;
stroke-linecap: round;
stroke-linejoin: round;
}
.hero-motion-toggle .hero-play-icon,
.hero-motion-toggle[data-paused="true"] .hero-pause-icon,
.hero-universe .hero-motion-toggle[hidden] {
display: none;
}
.hero-motion-toggle[data-paused="true"] .hero-play-icon {
display: block;
}
@media (prefers-reduced-motion: reduce) {
.hero-universe .hero-div > a > img,
.hero-universe .hero-div > a > span,
.hero-universe .hero-ground,
.hero-universe .hero-links {
translate: none;
rotate: none;
transition: none;
}
.hero-universe .hero-div > a:hover > img,
.hero-universe .hero-div > a.hero-selected > img {
transform: none;
}
}
/* The PNG boxes remain the native link hit targets and responsive layout anchors. */
.hero-universe .hero-div > a > img.hero-replaced {
opacity: 0;
}
.hero-universe .hero-objects-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.hero-universe .hero-objects-canvas[hidden] {
display: none;
}
/* Original art is a complete fallback; labels and hit areas remain native HTML. */
.hero-universe .hero-div > img.hero-replaced { opacity: 0; }
.hero-universe > .hero-objects-canvas { z-index: 0; }
/* Immediate preference response also covers an already-paused animation clock. */
@media (prefers-reduced-motion: reduce) {
.hero-universe .hero-objects-canvas,
.hero-universe .hero-motion-toggle { display: none; }
.hero-universe .hero-div img.hero-replaced { opacity: 1; }
.hero-three-active .hero-stars { visibility: visible; }
}
/* Long localized labels need space beside the new volumetric silhouettes. */
.hero-three-active :is(.hero-div--en, .hero-div--ru) > a[href$="chapter_greedy/"] > span {
margin-left: -4%;
}
.hero-three-active :is(.hero-div--en, .hero-div--ru, .hero-div--ja) > a[href$="chapter_stack_and_queue/"] > span {
margin-left: 4%;
}
/* Long translated chapter names use readable native links on narrow screens. */
@media screen and (min-width: 901px) {
/* Keep the original planet silhouettes; move long labels into the open gaps. */
.hero-universe :is(.hero-div--en, .hero-div--ru, .hero-div--ja, .hero-div--ko) > a[href$="chapter_introduction/"] > span {
margin-top: .6rem;
}
.hero-universe .hero-div--en > a[href$="chapter_divide_and_conquer/"] > span {
margin-left: -4.5%;
}
}
@media screen and (max-width: 900px) {
.home-div.home-universe-bg > :is(.hero-div--en, .hero-div--ru, .hero-div--ja) { aspect-ratio: auto; }
.hero-universe .hero-div--ko > a[href$="chapter_introduction/"] > span {
left: 48% !important;
top: 95% !important;
}
.hero-universe .hero-div--ko > a[href$="chapter_divide_and_conquer/"] > span {
margin-left: -2%;
}
.home-div:has(> :is(.hero-div--en, .hero-div--ru, .hero-div--ja)) { display: block; height: auto !important; margin-top: 0 !important; padding: .5rem 0 1rem !important; }
.home-div > :is(.hero-div--en, .hero-div--ru, .hero-div--ja) { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .35rem .5rem; width: calc(100% - 1.5rem); height: auto; margin: .5rem auto 1rem; }
.home-div > :is(.hero-div--en, .hero-div--ru, .hero-div--ja) > img { display: none; }
.home-div > :is(.hero-div--en, .hero-div--ru, .hero-div--ja) > a { display: flex; align-items: center; gap: .4rem; min-width: 0; min-height: 48px; padding: .3rem .15rem; text-align: left; }
.home-div > :is(.hero-div--en, .hero-div--ru, .hero-div--ja) > a > img { position: static; flex-shrink: 0; width: 32px !important; height: 32px !important; object-fit: contain; }
.home-div > :is(.hero-div--en, .hero-div--ru, .hero-div--ja) > a > span { position: static; min-width: 0; transform: none; white-space: normal; font-size: 13px; line-height: 1.3; }
.home-div > :is(.hero-div--en, .hero-div--ru, .hero-div--ja) + .heading-div { position: relative; left: auto; bottom: auto; transform: none; padding: 0 .75rem; }
}
/* Restore the animated, hand-drawn glass from the accepted motion study. */
.hero-universe .hero-div > a > .hero-hourglass {
position: absolute;
width: auto;
aspect-ratio: 113 / 178;
overflow: visible;
pointer-events: none;
translate: var(--hero-x, 0px) var(--hero-y, 0px);
rotate: var(--hero-angle, 0deg);
transition: transform 240ms ease, filter 240ms ease;
}
.hero-universe .hero-div > a.hero-selected > .hero-hourglass {
transform: none;
filter: brightness(1.04) drop-shadow(0 0 3px rgba(147, 213, 204, 0.18));
}
@media (prefers-reduced-motion: reduce) {
.hero-universe .hero-hourglass { display: none !important; }
}
/* Override legacy :hover scaling, including the still-visible original hit targets. */
.hero-universe .hero-div > a:hover > img { transform: none; filter: none; }
.hero-universe .hero-div > a.hero-selected > img { filter: brightness(1.04); }
/* Intrinsic dimensions reserve space without overriding responsive media height. */
.home-div .profile-img { height: auto; }
.home-div video.intro-image { height: auto; }

View File

@@ -0,0 +1 @@
export const textureCatalog = {"array_and_linkedlist":{"full":"../../shared/hero-textures/c51fcf1c87fef527/array_and_linkedlist-albedo.webp"},"backtracking":{"full":"../../shared/hero-textures/a86caaaa56f4d3a6/backtracking-albedo.webp"},"divide_and_conquer":{"full":"../../shared/hero-textures/08f0d9f214bf029a/divide_and_conquer-albedo.webp"},"dynamic_programming":{"full":"../../shared/hero-textures/0e401884672532cc/dynamic_programming-albedo.webp"},"graph":{"full":"../../shared/hero-textures/233e11ffcea7bce9/graph-albedo.webp"},"greedy":{"full":"../../shared/hero-textures/246b0162bfe79563/greedy-albedo.webp"},"hashing":{"full":"../../shared/hero-textures/8da4ad1ec85383c1/hashing-albedo.webp"},"heap":{"full":"../../shared/hero-textures/59c46d0a9d8edbb9/heap-albedo.webp"},"moon":{"full":"../../shared/hero-textures/c2403c9a216e8976/moon-albedo.webp"},"searching":{"full":"../../shared/hero-textures/bd71ba70ed7710ec/searching-albedo.webp"},"sorting":{"full":"../../shared/hero-textures/3607226f27fc2cda/sorting-albedo.webp"},"stack_and_queue":{"full":"../../shared/hero-textures/04bb3c644a0ca4e0/stack_and_queue-albedo.webp"},"tree":{"full":"../../shared/hero-textures/9d2bc4e5556a0ecb/tree-albedo.webp"}};

View File

@@ -0,0 +1,85 @@
// A layered vector illustration; no physics engine or independent animation loop.
export const HOURGLASS_CYCLE_SECONDS = 28;
let instance = 0;
export function createHourglass(items) {
const item = items.find(item => item.image.dataset.heroAsset === "chapter_computational_complexity");
if (!item) return null;
const id = `hero-sand-${++instance}`;
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 113 178");
svg.setAttribute("aria-hidden", "true");
svg.classList.add("hero-hourglass");
svg.style.cssText = item.image.style.cssText;
svg.innerHTML = `
<defs>
<clipPath id="${id}-top"><path d="M16 25 Q21 62 56.5 85 Q92 62 97 25Z"/></clipPath>
<clipPath id="${id}-bottom"><path d="M56.5 93 Q22 116 16 153 Q56 167 97 153 Q91 116 56.5 93Z"/></clipPath>
<linearGradient id="${id}-glass" x1="0" x2="1"><stop stop-color="#193d4b"/><stop offset=".5" stop-color="#204e5d"/><stop offset="1" stop-color="#102b3c"/></linearGradient>
<linearGradient id="${id}-sand" x1="0" y1="0" x2="1" y2=".5"><stop stop-color="#fae6a3"/><stop offset=".5" stop-color="#d8c37d"/><stop offset="1" stop-color="#b89b65"/></linearGradient>
</defs>
<g class="hourglass-body">
<path d="M9 19 C12 56 30 77 52 89 C28 105 13 128 9 157 Q56 177 104 157 C100 126 84 103 61 89 C86 73 100 49 104 19Z" fill="url(#${id}-glass)" stroke="#d8d39d" stroke-width="5"/>
<path d="M15 26 C24 65 41 77 56 85 C78 69 91 49 97 26 M56 94 C34 113 23 132 16 153 M63 95 C84 118 91 138 97 152" fill="none" stroke="#5c9c9d" stroke-width="3"/>
<g class="hourglass-sand">
<g clip-path="url(#${id}-top)"><path class="hourglass-upper" fill="url(#${id}-sand)"/><path class="hourglass-surface" fill="none" stroke="#fff0b5" stroke-width="2"/></g>
<g clip-path="url(#${id}-bottom)"><path class="hourglass-lower" fill="url(#${id}-sand)"/><path class="hourglass-pile-shade" fill="#ad9363" opacity=".55"/></g>
<g class="hourglass-stream" fill="#f6df9b"><path class="hourglass-thread" fill="none" stroke="#efd391" stroke-width="1"/>${Array.from({length:8},(_,i)=>`<circle class="hourglass-grain" r="${i%3===0?0.9:0.55}"/>`).join("")}</g>
</g>
<path d="M18 31 Q22 48 33 60 M37 65 L44 72 M85 42 Q80 59 68 70 M25 129 L21 144 M32 114 L28 121 M87 130 L91 146" fill="none" stroke="#f6efc3" stroke-width="2.5" stroke-linecap="round"/>
<path d="M12 26 Q20 67 50 86 M62 95 Q86 119 100 153" fill="none" stroke="#f8e7ae" stroke-width="1.5"/>
<ellipse cx="56.5" cy="18" rx="49" ry="13" fill="#d5cc92" stroke="#f5e7ad" stroke-width="3"/>
<ellipse cx="56.5" cy="17" rx="39" ry="8" fill="#244c5a" stroke="#90aaa0" stroke-width="2"/>
<path d="M24 18 Q55 9 90 17 M33 21 Q58 17 81 20" fill="none" stroke="#f3ebbb" stroke-width="2" stroke-linecap="round"/>
<path d="M8 153 Q56 174 105 153 L104 163 Q57 184 9 163Z" fill="#c8bf89" stroke="#f0e2a5" stroke-width="3"/>
<path d="M15 158 Q56 173 99 159" fill="none" stroke="#568d8a" stroke-width="3"/>
</g>`;
item.image.insertAdjacentElement("afterend", svg);
const body = svg.querySelector(".hourglass-body");
const sand = svg.querySelector(".hourglass-sand");
const upper = svg.querySelector(".hourglass-upper");
const surface = svg.querySelector(".hourglass-surface");
const lower = svg.querySelector(".hourglass-lower");
const shade = svg.querySelector(".hourglass-pile-shade");
const stream = svg.querySelector(".hourglass-stream");
const thread = svg.querySelector(".hourglass-thread");
const grains = [...svg.querySelectorAll(".hourglass-grain")];
const smooth = x => x*x*(3-2*x);
return {
render(time, enabled) {
svg.style.display = enabled ? "" : "none";
item.image.classList.toggle("hero-replaced", enabled);
if (!enabled) return;
// Slow the entire choreography, including falling grains and the turn.
time *= 14 / HOURGLASS_CYCLE_SECONDS;
const phase = time % 14;
const turns = Math.floor(time / 14);
const progress = Math.min(phase / 11, 1);
const flip = smooth(Math.min(1, Math.max(0, (phase-11.6)/1.6)));
// Keep turning in the same direction; the frame never snaps back at a loop boundary.
body.setAttribute("transform", `rotate(${((turns+flip)*180).toFixed(2)} 56.5 89)`);
sand.setAttribute("transform", `rotate(${turns*180} 56.5 89)`);
const level = 38 + 47*(1-Math.pow(1-progress, 0.45));
const notch = (1-progress)*5;
upper.setAttribute("d", `M8 ${level} Q39 ${level-1} 56.5 ${level+notch} Q75 ${level-1} 105 ${level} V89 H8Z`);
upper.setAttribute("opacity", progress>0.995?"0":"1");
surface.setAttribute("d", `M17 ${level} Q38 ${level-1} 56.5 ${level+notch} Q78 ${level-1} 97 ${level}`);
surface.setAttribute("opacity", progress>0.995?"0":".8");
const pile = Math.sqrt(progress);
// Sand settles toward the neck after inversion, before the next pour begins.
const settle = smooth(Math.max(0, Math.min(1, (phase-13.2)/0.8)));
const peak = 156-pile*43-settle*24;
const base = 160-settle*20;
const radius = pile*45+settle*5;
lower.setAttribute("d", `M${56.5-radius} ${base} Q40 ${peak+14} 56.5 ${peak} Q70 ${peak+14} ${56.5+radius} ${base} Q75 ${base+settle} 56.5 ${base-settle*5} Q39 ${base+settle} ${56.5-radius} ${base}Z`);
shade.setAttribute("d", `M56.5 ${peak} Q70 ${peak+14} ${56.5+radius} ${base} H68Z`);
stream.setAttribute("opacity", phase<10.95?".9":"0");
thread.setAttribute("d", `M56.5 84 Q${56.5+Math.sin(time*8)*0.5} 101 56.5 ${peak}`);
grains.forEach((grain,i)=>{
const travel=(time*1.5+i/8)%1;
grain.setAttribute("cx", (56.5+Math.sin(i*2.4+time*5)*0.75).toFixed(2));
grain.setAttribute("cy", (86+travel*(peak-86)).toFixed(2));
});
},
dispose() { svg.remove(); item.image.classList.remove("hero-replaced"); },
};
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,61 @@
import * as THREE from "./vendor/three/three.module.min.js";
import { planets, createPlanet, loadPlanetPaint, disposePlanetPaint } from "./hero-surfaces.js";
// The illustration supplies composition; each globe is independently lit and rotates fully.
export function createPlanets(map, items, onRestore) {
const targets=planets.map(d=>({...d,item:items.find(i=>i.image.dataset.heroAsset === 'chapter_'+d.name)})).filter(d=>d.item);
const hero=map.closest('.home-div');
const renderer=new THREE.WebGLRenderer({alpha:true,antialias:true,powerPreference:'low-power'});
renderer.setClearColor(0x000000,0);renderer.domElement.className='hero-objects-canvas';
renderer.domElement.setAttribute('aria-hidden','true');
renderer.debug.onShaderError=(gl,program,vertex,fragment)=>{throw new Error(`Hero shader: ${gl.getShaderInfoLog(vertex)} ${gl.getShaderInfoLog(fragment)}`);};
const scene=new THREE.Scene(),camera=new THREE.OrthographicCamera(-1,1,1,-1,.1,5000);
camera.position.z=2000;
map.insertBefore(renderer.domElement,map.querySelector(':scope > a'));
let width=0,height=0,lost=false,disposed=false,ready=false,lastTime=0;
const showOriginal=show=>{
renderer.domElement.hidden=show;hero.classList.toggle('hero-three-active',!show);
targets.forEach(({item})=>item.image.classList.toggle('hero-replaced',!show));
};
function resize(){
width=map.clientWidth;height=map.clientHeight;
renderer.setPixelRatio(Math.min(devicePixelRatio||1,2));
renderer.setSize(Math.max(width,1),Math.max(height,1),false);
camera.left=-width/2;camera.right=width/2;camera.top=height/2;camera.bottom=-height/2;camera.updateProjectionMatrix();
for(const target of targets){const i=target.item.image;target.rect={x:i.offsetLeft,y:i.offsetTop,width:i.offsetWidth,height:i.offsetHeight};
}
}
function render(time,enabled,pointer={x:0,y:0}){
if(disposed)return;
if(!enabled||lost||!ready||!width||!height){showOriginal(true);return;}
const emphasisBlend=time===lastTime?1:1-Math.exp(-Math.min(.1,Math.abs(time-lastTime))*10);lastTime=time;
for(const target of targets){
const {item,rect,root,sphere,material}=target,selected=item.anchor.classList.contains('hero-selected');
root.scale.setScalar(rect.height/Number(item.image.dataset.sourceHeight || item.image.naturalHeight));
root.position.set(rect.x+rect.width/2+item.x-width/2,height/2-rect.y-rect.height/2-item.y,0);
root.rotation.set(pointer.y*.035,pointer.x*.045,-item.angle*Math.PI/180);
sphere.rotation.y=time*Math.PI*2/target.period;
if(target.moon)target.moon.rotation.y=sphere.rotation.y*2;
material.uniforms.emphasis.value+=((selected?1:0)-material.uniforms.emphasis.value)*emphasisBlend;
}
renderer.render(scene,camera);showOriginal(false);
Object.assign(renderer.domElement.dataset,{drawCalls:String(renderer.info.render.calls),triangles:String(renderer.info.render.triangles),
art:'reference-authored-albedo',network:'original-brushwork',rotation:'continuous-360'});
}
const onLost=event=>{event.preventDefault();lost=true;showOriginal(true);};
const onRestored=()=>{lost=false;resize();onRestore();};
renderer.domElement.addEventListener('webglcontextlost',onLost);renderer.domElement.addEventListener('webglcontextrestored',onRestored);
showOriginal(true);resize();
const readyPromise=Promise.allSettled(targets.map(async t=>{await t.item.image.decode();const map=await loadPlanetPaint(t);if(disposed){disposePlanetPaint(map);return;}t.paintMap=map;})).then(results=>{
const failure=results.find(r=>r.status==='rejected');if(failure)throw failure.reason;
if(disposed)return;
for(const target of targets){Object.assign(target,createPlanet(target.item.image,target,target.paintMap));scene.add(target.root);}
ready=true;resize();onRestore();
}).catch(error=>{if(!disposed){targets.forEach(t=>{disposePlanetPaint(t.paintMap);t.paintMap=null;});showOriginal(true);console.warn('Original planets retained: paint unavailable.',error);}});
return {resize,render,ready:readyPromise,dispose(){
disposed=true;showOriginal(true);
renderer.domElement.removeEventListener('webglcontextlost',onLost);renderer.domElement.removeEventListener('webglcontextrestored',onRestored);
const geometries=new Set(),materials=new Set();scene.traverse(o=>{if(o.geometry)geometries.add(o.geometry);if(o.material)materials.add(o.material);});
geometries.forEach(g=>g.dispose());materials.forEach(m=>m.dispose());targets.forEach(t=>disposePlanetPaint(t.paintMap));renderer.dispose();renderer.forceContextLoss();renderer.domElement.remove();
}};
}

View File

@@ -0,0 +1,195 @@
import { textureCatalog } from './hero-assets.js';
import { paintSeams } from './hero-paint-seams.js';
/* Reference-led painted sphere maps and antialiased three-dimensional rings. */
import * as T from './vendor/three/three.module.min.js';
// Palette order: deep ink, secondary pigment, body, accent, paper highlight.
// tilt sets the shared polar axis. belt traces a reference latitude in the paint
// (top-down y at 9 evenly spaced x positions); beltLevel is its target latitude.
// Monotone UV rectification aligns the painted flow over the entire revolution.
// Shapes describe the original illustration's visual vocabulary, not its pixels.
export const planets = [
{name:'array_and_linkedlist',paintWindow:[.1,.62],period:76,tilt:0.59,beltLevel:0.5,belt:[0.5,0.5175,0.59,0.63,0.63,0.58,0.55,0.5025,0.5],colors:['#554f6d','#99748b','#d9bd83','#f3dca0','#fff0bd'],circle:[237,166,130],ring:[237,166,277,51,176],ringStyle:0},
{name:'stack_and_queue',period:69,tilt:0.12,beltLevel:0.5,belt:[0.5,0.52,0.6,0.59,0.56,0.5,0.43,0.47,0.5],colors:['#263f56','#438c97','#e4b044','#4fa4a4','#f6ce64']},
{name:'hashing',paintWindow:[.04,.96],period:63,tilt:0.69,beltLevel:0.5,belt:[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],colors:['#294459','#a56870','#4a9e9b','#efad48','#f8d687']},
{name:'tree',period:83,tilt:0.28,beltLevel:0.5,belt:[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],colors:['#264458','#aa866e','#188e84','#cfcc94','#f1d99b']},
{name:'heap',period:55,tilt:-0.55,beltLevel:0.5,belt:[0.5,0.4625,0.47,0.57,0.67,0.75,0.65,0.5,0.5],colors:['#273e52','#ad7c99','#b4c67e','#ddab98','#e9d8a0']},
{name:'graph',period:89,tilt:0.27,beltLevel:0.5,belt:[0.5,0.535,0.61,0.58,0.54,0.5,0.47,0.485,0.5],colors:['#464464','#786587','#be8d9d','#e3a493','#f2ce9b'],circle:[270,136,135],ring:[270,136,280,9,146],ringStyle:1},
{name:'searching',period:58,tilt:0.12,beltLevel:0.5,belt:[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],colors:['#4b526d','#a7788a','#63a5a0','#a6b28c','#eddb9e']},
{name:'sorting',period:52,tilt:-0.5,beltLevel:0.5,belt:[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],colors:['#3e3d57','#b57191','#eaa9ae','#f6df98','#fff0b5']},
{name:'divide_and_conquer',period:94,tilt:0.58,beltLevel:0.5,belt:[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],colors:['#494a63','#b6777e','#e5a129','#f3c45c','#ffe6a1'],circle:[156,130,117],ring:[203,134,240,60,164],ringStyle:2,moon:[307,50,33]},
{name:'backtracking',period:81,tilt:-0.08,beltLevel:0.5,belt:[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],colors:['#36475c','#727887','#eead30','#c58c4d','#f7d27b'],circle:[166,115,115],ring:[173,159,173,70,126],ringStyle:3},
{name:'dynamic_programming',period:67,tilt:0.2,beltLevel:0.5,belt:[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5],colors:['#36394f','#8c4b6d','#d36959','#ed9d46','#f5cb6e']},
{name:'greedy',period:72,tilt:-0.57,beltLevel:0.5,belt:[0.5,0.4825,0.51,0.62,0.67,0.67,0.61,0.51,0.5],colors:['#234c60','#32878d','#72b1a7','#b9bd84','#e8ba66'],circle:[135,85,83],ring:[133,101,149,61,111],ringStyle:4},
];
const vertex=`varying vec3 p;varying vec3 n;
void main(){p=position;n=normalize(normalMatrix*normal);
gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);}`;
async function loadMap(file) {
const map=await new T.TextureLoader().loadAsync(new URL(file,import.meta.url).href);
map.colorSpace=T.SRGBColorSpace;map.wrapS=T.RepeatWrapping;
map.minFilter=T.LinearMipmapLinearFilter;map.magFilter=T.LinearFilter;map.anisotropy=4;
return map;
}
export async function loadPlanetPaint(definition) {
const surface=await loadMap(textureCatalog[definition.name].full);
try {
return {surface,moon:definition.moon?await loadMap(textureCatalog.moon.full):null};
} catch(error) {surface.dispose();throw error;}
}
export function disposePlanetPaint(paint) {
paint?.surface.dispose();paint?.moon?.dispose();
}
function surface(definition,paintMap) {
const seam=paintSeams[definition.name];
return new T.ShaderMaterial({uniforms:{pigments:{value:definition.colors.map(c=>new T.Color(c))},paintMap:{value:paintMap},paintWindow:{value:new T.Vector2(...seam.window)},join:{value:seam.join},flowLeft:{value:seam.left},flowRight:{value:seam.right},belt:{value:definition.belt||Array(9).fill(.5)},beltLevel:{value:definition.beltLevel||.5},emphasis:{value:0}},vertexShader:vertex,
fragmentShader:`varying vec3 p;varying vec3 n;uniform vec3 pigments[5];uniform float emphasis;uniform vec2 paintWindow;uniform float join[65];uniform float flowLeft[33];uniform float flowRight[33];uniform float belt[9];uniform float beltLevel;uniform sampler2D paintMap;
// Cubic paint-space guide with its analytic slope. Keeping derivatives explicit
// avoids the atan seam selecting a blurry mip level during a full revolution.
vec2 guide(float x){
float pos=clamp(x,0.,1.)*8.;int i=min(int(floor(pos)),7);float t=pos-float(i);
float a=belt[max(i-1,0)],b=belt[i],c=belt[i+1],d=belt[min(i+2,8)];
float A=-a+3.*b-3.*c+d,B=2.*a-5.*b+4.*c-d,C=-a+c;
float v=.5*(2.*b+C*t+B*t*t+A*t*t*t);
float slope=4.*(C+2.*B*t+3.*A*t*t);
return vec2(clamp(v,.04,.96),slope);
}
vec3 paint(float x,float y,vec2 dx,vec2 dy){
vec2 g=guide(x);
// An odds-ratio warp maps the traced contour to one latitude. It is smooth,
// strictly monotone and fixes both poles: no latitude clipping or mirrored paint.
float k=(1.-g.x)*beltLevel/(g.x*(1.-beltLevel));
float den=1.-y+k*y;
float v=k*y/den;
float dvdx=-v*(1.-v)*g.y/(g.x*(1.-g.x));
float dvdy=k/(den*den);
return textureGrad(paintMap,vec2(x,v),vec2(dx.x,dvdx*dx.x+dvdy*dx.y),vec2(dy.x,dvdx*dy.x+dvdy*dy.y)).rgb;
}
vec2 edgeFlow(float y,bool left){
float row=clamp(y,0.,1.)*32.;int i=min(int(floor(row)),31);
float a=left?flowLeft[i]:flowRight[i],b=left?flowLeft[i+1]:flowRight[i+1];
return vec2(mix(a,b,row-float(i)),32.*(b-a));
}
vec3 joinedPaint(float x,float y,vec2 dx,vec2 dy,float longitude,bool left){
vec2 flow=edgeFlow(y,left);
float t=clamp((abs(longitude)-.012)/.168,0.,1.);
float strength=1.-t*t*(3.-2.*t);
float slope=-sign(longitude)*6.*t*(1.-t)/.168;
float v=mix(y,flow.x,strength),dvdv=mix(1.,flow.y,strength);
float dvdu=(flow.x-y)*slope/(paintWindow.y-paintWindow.x);
return paint(x,v,vec2(dx.x,dvdu*dx.x+dvdv*dx.y),vec2(dy.x,dvdu*dy.x+dvdv*dy.y));
}
void main(){vec3 q=normalize(p);
// A pigment-matched join follows the paint rather than a vertical fade strip.
// Both samples stay inside the authored texture; no wrapped/clamped guide tails.
float u=atan(q.x,q.z)/6.28318530718+.5;
float y=asin(clamp(q.y,-1.,1.))/3.14159265359+.5;
float span=paintWindow.y-paintWindow.x;
float x=paintWindow.x+span*u;
// Explicit angular derivatives keep mip selection continuous at atan's wrap.
float denom=6.28318530718*max(dot(q.xz,q.xz),.0001);
vec2 gradX=vec2(span*(q.z*dFdx(q.x)-q.x*dFdx(q.z))/denom,dFdx(y));
vec2 gradY=vec2(span*(q.z*dFdy(q.x)-q.x*dFdy(q.z))/denom,dFdy(y));
vec3 ink;
float signedU=u>.5?u-1.:u;
if(abs(signedU)<.18){
float row=clamp(y,0.,1.)*64.;int i=min(int(floor(row)),63);
float center=mix(join[i],join[i+1],row-float(i));
float slope=64.*(join[i+1]-join[i]);
float distance=signedU-center;
float width=max(.003,abs(gradX.x/span-slope*dFdx(y))+abs(gradY.x/span-slope*dFdy(y)));
if(distance < -width)ink=joinedPaint(paintWindow.y+span*signedU,y,gradX,gradY,signedU,true);
else if(distance > width)ink=joinedPaint(paintWindow.x+span*signedU,y,gradX,gradY,signedU,false);
else{
vec3 left=joinedPaint(paintWindow.y+span*signedU,y,gradX,gradY,signedU,true);
vec3 right=joinedPaint(paintWindow.x+span*signedU,y,gradX,gradY,signedU,false);
ink=mix(left,right,smoothstep(-width,width,distance));
}
}else ink=paint(x,y,gradX,gradY);
// Fixed, restrained illumination preserves the illustration's pigment values.
vec3 normal=normalize(n);float light=dot(normal,normalize(vec3(-.60,.80,1.2)));
float shade=.72+.28*smoothstep(-.10,.86,light);
vec3 color=ink*shade;
float rim=1.-smoothstep(.015,.10,normal.z);
color=mix(color,pigments[4],rim*(.18+emphasis*.18));
color*=1.+emphasis*.04;
gl_FragColor=vec4(color,1.);
#include <colorspace_fragment>
}`});
}
export function createPlanet(image,d,paint) {
const w=Number(image.dataset?.sourceWidth || image.naturalWidth),h=Number(image.dataset?.sourceHeight || image.naturalHeight),[cx,cy,r]=d.circle||[w/2,h/2,Math.min(w,h)/2];
const root=new T.Group(),axis=new T.Group(),sphere=new T.Mesh(new T.SphereGeometry(1,64,40),surface(d,paint.surface));
// Local Y is the polar axis for both the globe and its ring plane. A ring's
// apparent minor/major ratio fixes the view elevation, not a separate tilt.
const pitch=d.ring?Math.asin(d.ring[3]/d.ring[2]):.08;
axis.rotation.set(pitch,0,d.tilt,'ZXY');axis.position.set(cx-w/2,h/2-cy,0);
sphere.scale.setScalar(r);axis.add(sphere);root.add(axis);
let ring=null;
if(d.ring){
const [,y,outer,,inner]=d.ring;
ring=new T.Mesh(new T.RingGeometry(inner,outer,256),new T.ShaderMaterial({side:T.DoubleSide,transparent:true,depthWrite:false,
uniforms:{inner:{value:inner},outer:{value:outer},style:{value:d.ringStyle},ink:{value:new T.Color(d.ringStyle===2?'#c3d3a7':d.ringStyle===4?'#66bcb3':d.ringStyle===3?'#f5b632':d.colors[d.ringStyle===1?2:4])},dark:{value:new T.Color(d.ringStyle===0?'#bca178':d.ringStyle===2?'#986e63':d.ringStyle===4?'#e5b66a':d.ringStyle===3?'#686c80':d.colors[1])}},vertexShader:vertex,
fragmentShader:`varying vec3 p;uniform float inner;uniform float outer;uniform float style;uniform vec3 ink;uniform vec3 dark;
// Integrate a stroke over its pixel footprint. Unresolved rings become faint
// continuous coverage, rather than hard discard dots that shimmer in motion.
float stroke(float t,float center,float halfWidth,float pixel){
return clamp((t-center+halfWidth)/pixel+.5,0.,1.)-clamp((t-center-halfWidth)/pixel+.5,0.,1.);
}
void main(){float t=(length(p.xy)-inner)/(outer-inner);
float pixel=max(fwidth(t),.0005);float alpha=1.;
vec3 color=ink;
if(style==0.){
// Broad cream ring: a small number of painterly layers, not a sine comb.
color=mix(ink,dark,.22*stroke(t,.18,.06,pixel));
color=mix(color,dark,.18*stroke(t,.45,.025,pixel));
color=mix(color,dark,.12*stroke(t,.66,.04,pixel));
color=mix(color,dark,.30*stroke(t,.90,.015,pixel));
alpha=1.-.82*stroke(t,.77,.018,pixel);
}else if(style==1.){
color=mix(dark,ink,.45+.35*smoothstep(.15,.85,t));
}else if(style==2.){
float first=stroke(t,.12,.027,pixel),middle=stroke(t,.46,.020,pixel),last=stroke(t,.86,.024,pixel);
alpha=max(first,max(middle,last));color=mix(ink,dark,.35*last);
}else{
// Individually drawn, tapered sweeps. The radial curves stay in the
// equatorial plane; angular variation gives the original ink character.
float a=atan(p.y,p.x),right=cos(a),front=-sin(a);
if(style==3.){
float warm=smoothstep(-.12,.55,right);
float taper=smoothstep(-.96,-.5,cos(a+.45));
float main=stroke(t,.48+.035*sin(a+.5),(.035+.095*warm)*taper,pixel);
float inside=stroke(t,.14+.03*sin(a),.065*smoothstep(-.4,.65,cos(a+.75)),pixel);
float outside=stroke(t,.88+.025*cos(a),.023*smoothstep(-.3,.2,front)*smoothstep(-.95,-.6,right),pixel);
alpha=max(main,max(inside,outside));color=mix(dark,ink,warm);
}else{
float main=stroke(t,.30+.045*sin(a+.4),.067*(.7+.3*cos(a-.5)),pixel);
float innerArc=stroke(t,.075+.025*sin(a),.028*smoothstep(-.95,-.35,cos(a-.8)),pixel);
float outerArc=stroke(t,.64+.035*cos(a+.4),.040*smoothstep(-.95,-.45,cos(a+1.8)),pixel);
float gold=stroke(t,.92+.02*sin(a),.027*smoothstep(-.3,.6,cos(a+.4)),pixel);
alpha=max(max(main,innerArc),max(outerArc,gold));
color=mix(ink,dark,gold/max(alpha,.001));
}
}
alpha*=smoothstep(0.,pixel,t)*(1.-smoothstep(1.-pixel,1.,t));
if(alpha<.001)discard;
color*=.90+.10*smoothstep(-outer*.4,outer*.4,p.y);
gl_FragColor=vec4(color,alpha);
#include <colorspace_fragment>
}`}));
ring.rotation.x=-Math.PI/2;
// Low-set bands in the reference are translated along the same polar axis.
ring.position.y=(cy-y)/Math.cos(pitch);axis.add(ring);
}
let moon=null;
if(d.moon){
const [x,y,radius]=d.moon;moon=new T.Mesh(new T.SphereGeometry(radius,32,24),surface({...d,name:'moon',belt:Array(9).fill(.5),beltLevel:.5,colors:['#35475a','#b77c94','#659e98','#d9bb87','#eed6ac']},paint.moon));
moon.position.set(x-w/2,h/2-y,80);root.add(moon);
}
return {root,axis,sphere,ring,moon,material:sphere.material};
}

View File

@@ -0,0 +1,44 @@
/* Below-fold demos load near the viewport and never animate in reduced motion. */
(() => {
const reduced = matchMedia('(prefers-reduced-motion: reduce)');
let observer;
const videos = [...document.querySelectorAll('[data-home-video]')];
const images = [...document.querySelectorAll('[data-home-src]')];
const visible = new Set();
function reveal(element) {
if (element.dataset.homeSrc && !element.getAttribute('src')) element.src = element.dataset.homeSrc;
if (element.dataset.homePoster && !element.getAttribute('poster')) element.poster = element.dataset.homePoster;
}
function load(video) {
if (!video.getAttribute('src')) {
video.src = video.dataset.homeVideo;
video.load();
}
}
function update() {
for (const video of videos) {
if (document.hidden || reduced.matches || !visible.has(video)) video.pause();
else { load(video); video.play().catch(() => {}); }
}
}
function start() {
observer?.disconnect();
if (!('IntersectionObserver' in window)) { [...images, ...videos].forEach(reveal); return; }
observer = new IntersectionObserver(entries => {
for (const entry of entries) {
const element = entry.target;
if (entry.isIntersecting) reveal(element);
if (element.tagName === 'VIDEO') {
if (entry.isIntersecting) visible.add(element); else visible.delete(element);
} else if (entry.isIntersecting) observer.unobserve(element);
}
update();
}, {rootMargin: '120px 0px'});
[...images, ...videos].forEach(element => observer.observe(element));
}
document.addEventListener('visibilitychange', update);
reduced.addEventListener('change', update);
addEventListener('pagehide', () => {observer?.disconnect();visible.clear();update();});
addEventListener('pageshow', start);
start();
})();

View File

@@ -0,0 +1,321 @@
/* Homepage illustration motion. Loaded only by the landing-page build hook. */
(function (root) {
"use strict";
let dispose = () => {};
const moduleBase = new URL(".", document.currentScript.src);
function setup(config = {}) {
dispose();
const container = config.container || document.querySelector(".starfield");
const hero = container?.closest(".home-div");
const map = hero?.querySelector(".hero-div");
if (!map) return;
const reduced = matchMedia("(prefers-reduced-motion: reduce)");
const finePointer = matchMedia("(hover: hover) and (pointer: fine)");
const mobile = matchMedia("(max-width: 900px)");
const listeners = [];
const on = (target, type, fn) => {
target.addEventListener(type, fn);
listeners.push(() => target.removeEventListener(type, fn));
};
const items = Array.from(map.querySelectorAll(":scope > a")).map((anchor, i) => ({
anchor,
image: anchor.querySelector("img"),
label: anchor.querySelector("span"),
originalLabelMargin: anchor.querySelector("span")?.style.marginLeft || "",
depth: 0.55 + (i % 4) * 0.15,
phase: i * 2.39996,
speed: 0.55 + (i % 5) * 0.055,
astronaut: anchor.querySelector('img[data-hero-asset="astronaut"]') !== null,
x: 0,
y: 0,
angle: 0,
})).filter(item => item.image);
const ground = map.querySelector(':scope > img[data-hero-asset="ground"]');
const links = map.querySelector(':scope > img[data-hero-asset="links"]');
ground?.classList.add("hero-ground");
links?.classList.add("hero-links");
hero.classList.add("hero-universe");
const canvas = document.createElement("canvas");
canvas.className = "hero-stars";
canvas.setAttribute("aria-hidden", "true");
container.appendChild(canvas);
// Illustration interactions also work if Canvas is unavailable.
let ctx = null;
try { ctx = canvas.getContext("2d"); } catch (_) { /* Static background remains. */ }
const lang = (document.documentElement.lang || "zh").toLowerCase();
const labels = lang.startsWith("en") ? ["Pause animation", "Play animation"]
: lang.startsWith("ja") ? ["アニメーションを一時停止", "アニメーションを再生"]
: lang.startsWith("ko") ? ["애니메이션 일시 정지", "애니메이션 재생"]
: lang.startsWith("ru") ? ["Приостановить анимацию", "Воспроизвести анимацию"]
: /hant|tw/.test(lang) ? ["暫停動畫", "播放動畫"] : ["暂停动画", "播放动画"];
const toggle = document.createElement("button");
toggle.type = "button";
toggle.className = "hero-motion-toggle";
toggle.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path class="hero-pause-icon" d="M8 6v12M16 6v12"/><path class="hero-play-icon" d="m9 6 9 6-9 6Z"/></svg>';
hero.appendChild(toggle);
let width = 1, height = 1, scale = 1, stars = [];
let pointer = { x: 0, y: 0 }, target = { x: 0, y: 0 };
let hovered = null, focused = null;
let visible = false, suspended = false, destroyed = false;
let raf = 0, last = 0, time = 0, paused = false;
let planets = null, hourglass = null, objectsRequested = false;
try { paused = sessionStorage.getItem("hello-algo:hero-paused") === "true"; } catch (_) { /* Storage is optional. */ }
const staticMotion = () => reduced.matches || paused;
const listLayout = () => mobile.matches && ["hero-div--en", "hero-div--ru", "hero-div--ja"].some(name => map.classList.contains(name));
const canRun = () => !destroyed && visible && !suspended && !document.hidden && !staticMotion();
const wrap = (n, size) => ((n % size) + size) % size;
function renderObjects() {
const enabled = !reduced.matches && !listLayout();
if (planets) {
try { planets.render(time, enabled, pointer); }
catch (error) {
planets.dispose();
planets = null;
console.warn("Planet rendering unavailable; keeping the original illustrations.", error);
}
}
hourglass?.render(time, enabled);
}
function loadObjects() {
if (objectsRequested || destroyed || !visible || reduced.matches || listLayout()) return;
objectsRequested = true;
import(new URL("hero-planets.js", moduleBase).href).then(module => {
if (destroyed) return;
planets = module.createPlanets(map, items, updatePlayback);
renderObjects();
resize();
}).catch(error => console.warn("3D enhancement unavailable; keeping the original planets.", error));
import(new URL("hero-hourglass.js", moduleBase).href).then(module => {
if (destroyed) return;
hourglass = module.createHourglass(items);
renderObjects();
}).catch(error => console.warn("Hourglass enhancement unavailable; keeping the original illustration.", error));
}
function drawStars() {
if (!ctx) return;
ctx.clearRect(0, 0, width, height);
for (const star of stars) {
const drift = time * star.depth * 0.7;
const x = wrap(star.x * width + drift + pointer.x * star.depth * 10, width + 16) - 8;
const y = wrap(star.y * height + Math.sin(time * 0.08 + star.phase) * 2 + pointer.y * star.depth * 7, height + 16) - 8;
const twinkle = 0.78 + Math.sin(time * star.speed + star.phase) * 0.22;
ctx.globalAlpha = star.opacity * twinkle;
ctx.fillStyle = star.color;
ctx.beginPath();
ctx.arc(x, y, star.radius, 0, Math.PI * 2);
ctx.fill();
if (star.radius > 1.2) {
ctx.globalAlpha *= 0.12;
ctx.beginPath();
ctx.arc(x, y, star.radius * 3, 0, Math.PI * 2);
ctx.fill();
}
}
ctx.globalAlpha = 1;
}
function moveIllustrations(dt) {
const mix = 1 - Math.exp(-dt * 5);
pointer.x += (target.x - pointer.x) * mix;
pointer.y += (target.y - pointer.y) * mix;
const still = staticMotion() || listLayout();
const central = item => item.astronaut || item.image.dataset.heroAsset === 'chapter_computational_complexity';
const centralSelected = items.some(item => central(item) && (item.anchor === hovered || item.anchor === focused));
for (const item of items) {
if (central(item)) {
if (!still && centralSelected) continue;
item.x = still ? 0 : pointer.x * 2 * scale;
item.y = still ? 0 : (pointer.y * 1.5 - Math.sin(time * .45) * 1.2) * scale;
item.angle = 0;
item.anchor.style.setProperty('--hero-x', `${item.x.toFixed(2)}px`);
item.anchor.style.setProperty('--hero-y', `${item.y.toFixed(2)}px`);
item.anchor.style.setProperty('--hero-angle', '0deg');
continue;
}
// Freeze the selected object so the pointer/keyboard target stays easy to use.
if (!still && (item.anchor === hovered || item.anchor === focused)) continue;
const amplitude = (item.astronaut ? 5 : 2.6) * scale;
const x = (pointer.x * 7 * item.depth + Math.sin(time * item.speed * 0.7 + item.phase) * 1.4) * scale;
const y = pointer.y * 5 * item.depth * scale + Math.sin(time * item.speed + item.phase) * amplitude;
const angle = Math.sin(time * item.speed * 0.65 + item.phase) * (item.astronaut ? 0.65 : 0.35);
item.x = still ? 0 : item.x + (x - item.x) * mix;
item.y = still ? 0 : item.y + (y - item.y) * mix;
item.angle = still ? 0 : item.angle + (angle - item.angle) * mix;
item.anchor.style.setProperty("--hero-x", `${item.x.toFixed(2)}px`);
item.anchor.style.setProperty("--hero-y", `${item.y.toFixed(2)}px`);
item.anchor.style.setProperty("--hero-angle", `${item.angle.toFixed(3)}deg`);
}
map.style.setProperty("--hero-ground-x", `${(still ? 0 : pointer.x * 2 * scale).toFixed(2)}px`);
map.style.setProperty("--hero-ground-y", `${(still ? 0 : pointer.y * 1.5 * scale).toFixed(2)}px`);
}
function frame(now) {
raf = 0;
if (!hero.isConnected) { dispose(); return; }
// A media preference may change before its queued change event is delivered.
// Apply its static state before this frame stops scheduling the next one.
if (reduced.matches && !hero.classList.contains('hero-motion-paused')) {
updatePlayback();
return;
}
if (!canRun()) return;
// Cap touch devices at 30 fps. Motion uses elapsed time, not frame count.
if (last && now - last < (finePointer.matches ? 14 : 30)) {
raf = requestAnimationFrame(frame);
return;
}
const dt = last ? Math.min((now - last) / 1000, 0.05) : 0;
last = now;
time += dt;
moveIllustrations(dt);
drawStars();
renderObjects();
raf = requestAnimationFrame(frame);
}
function updatePlayback() {
cancelAnimationFrame(raf);
raf = 0;
last = 0;
toggle.hidden = reduced.matches;
toggle.setAttribute("aria-label", labels[paused ? 1 : 0]);
toggle.title = labels[paused ? 1 : 0];
toggle.dataset.paused = String(paused);
hero.classList.toggle("hero-motion-paused", staticMotion());
if (reduced.matches) {
pointer = { x: 0, y: 0 };
target = { x: 0, y: 0 };
moveIllustrations(0);
}
drawStars();
renderObjects();
loadObjects();
if (canRun()) raf = requestAnimationFrame(frame);
}
function resize() {
width = Math.max(container.clientWidth, 1);
height = Math.max(container.clientHeight, 1);
scale = Math.min(1, map.clientWidth / 1000);
const dpr = Math.min(window.devicePixelRatio || 1, finePointer.matches ? 1.5 : 1);
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(height * dpr);
ctx?.setTransform(dpr, 0, 0, dpr, 0, 0);
// Deterministic positions avoid a new constellation on every resize.
let seed = 73;
const random = () => { seed = (seed * 1664525 + 1013904223) >>> 0; return seed / 4294967296; };
const count = Math.min(finePointer.matches ? 300 : 130, Math.max(65, Math.round(width * height / 4200)));
stars = Array.from({ length: count }, (_, i) => ({
x: random(), y: random(), phase: random() * Math.PI * 2,
depth: [0.25, 0.6, 1][i % 3],
radius: (0.4 + random() * 0.55) * [0.7, 1, 1.5][i % 3],
opacity: 0.2 + random() * 0.45,
speed: 0.35 + random() * 0.45,
color: ["#d0dedc", "#c5d2e8", "#e3d5bf"][i % 3],
}));
if (!paused || reduced.matches || listLayout()) moveIllustrations(0);
drawStars();
planets?.resize();
renderObjects();
// A page can have no horizontal scroll while absolutely positioned text is clipped.
// Leave room for the small pointer displacement on both sides of the actual viewport.
const heroBounds = hero.getBoundingClientRect();
const left = Math.max(0, heroBounds.left) + 12;
const right = Math.min(document.documentElement.clientWidth, heroBounds.right) - 12;
for (const item of items) {
if (!item.label) continue;
item.label.style.marginLeft = item.originalLabelMargin;
if (listLayout()) continue;
const bounds = item.label.getBoundingClientRect();
const nudge = Math.max(left - bounds.left, Math.min(0, right - bounds.right));
const base = parseFloat(getComputedStyle(item.label).marginLeft) || 0;
if (Math.abs(nudge) > .1) item.label.style.marginLeft = `${base + nudge}px`;
}
loadObjects();
}
function updateSelection() {
const selected = focused || hovered;
for (const item of items) item.anchor.classList.toggle("hero-selected", item.anchor === selected);
map.classList.toggle("hero-network-active", Boolean(selected));
if (!canRun()) renderObjects();
}
on(hero, "pointermove", event => {
if (!finePointer.matches || event.pointerType === "touch" || staticMotion()) return;
const bounds = hero.getBoundingClientRect();
target.x = Math.max(-1, Math.min(1, (event.clientX - bounds.left) / bounds.width * 2 - 1));
target.y = Math.max(-1, Math.min(1, (event.clientY - bounds.top) / bounds.height * 2 - 1));
});
on(hero, "pointerleave", () => { target = { x: 0, y: 0 }; hovered = null; updateSelection(); });
for (const item of items) {
on(item.anchor, "pointerenter", event => {
if (event.pointerType === "touch" || !finePointer.matches) return;
hovered = item.anchor;
updateSelection();
});
on(item.anchor, "pointerleave", () => { if (hovered === item.anchor) hovered = null; updateSelection(); });
on(item.anchor, "focus", () => { focused = item.anchor; updateSelection(); });
on(item.anchor, "blur", () => { focused = null; updateSelection(); });
}
on(toggle, "click", () => {
paused = !paused;
try { sessionStorage.setItem("hello-algo:hero-paused", String(paused)); } catch (_) { /* Storage is optional. */ }
updatePlayback();
});
on(reduced, "change", updatePlayback);
on(finePointer, "change", () => { target = { x: 0, y: 0 }; resize(); });
on(mobile, "change", resize);
on(document, "visibilitychange", updatePlayback);
on(window, "pagehide", () => { suspended = true; updatePlayback(); });
on(window, "pageshow", () => { suspended = false; updatePlayback(); });
const resizeObserver = new ResizeObserver(resize);
resizeObserver.observe(hero);
resizeObserver.observe(map);
const intersectionObserver = new IntersectionObserver(entries => {
visible = entries[0].isIntersecting;
updatePlayback();
});
intersectionObserver.observe(hero);
dispose = () => {
destroyed = true;
cancelAnimationFrame(raf);
resizeObserver.disconnect();
intersectionObserver.disconnect();
listeners.forEach(remove => remove());
planets?.dispose();
hourglass?.dispose();
canvas.remove();
toggle.remove();
hero.classList.remove("hero-universe", "hero-motion-paused");
map.classList.remove("hero-network-active");
map.style.removeProperty("--hero-ground-x");
map.style.removeProperty("--hero-ground-y");
ground?.classList.remove("hero-ground");
links?.classList.remove("hero-links");
for (const { anchor, label, originalLabelMargin } of items) {
if (label) label.style.marginLeft = originalLabelMargin;
anchor.classList.remove("hero-selected");
["--hero-x", "--hero-y", "--hero-angle"].forEach(name => anchor.style.removeProperty(name));
}
};
resize();
updatePlayback();
}
// Retain the build hook's entry point; all motion shares one cancellable loop.
root.Starfield = { setup, cleanup: () => dispose() };
})(window);

View File

@@ -0,0 +1 @@
export * from '../../../../shared/vendor/three/459267a54e856023/three.core.min.js';

View File

@@ -0,0 +1 @@
export * from '../../../../shared/vendor/three/459267a54e856023/three.module.min.js';

View File

@@ -0,0 +1,505 @@
{
"version": "ad8bb1cc4c28091c",
"assets": {
"ground": {
"width": 881,
"height": 380,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/567306c070a6a7e2/ground.webp"
},
"links": {
"width": 1562,
"height": 1134,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/3e50f38a325c1cb5/links.webp"
},
"astronaut": {
"width": 515,
"height": 672,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/8e5eb3b3e3ebd9f7/astronaut.webp"
},
"chapter_computational_complexity": {
"width": 113,
"height": 178,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/2fb425426d1f8d86/chapter_computational_complexity.webp"
},
"chapter_array_and_linkedlist": {
"width": 468,
"height": 319,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/5cbbdf7be555d3ef/chapter_array_and_linkedlist.webp"
},
"chapter_stack_and_queue": {
"width": 204,
"height": 204,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/e2667767cf7481bd/chapter_stack_and_queue.webp"
},
"chapter_hashing": {
"width": 218,
"height": 218,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/b108d70a537714cb/chapter_hashing.webp"
},
"chapter_tree": {
"width": 280,
"height": 281,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/8138bb0acecedec4/chapter_tree.webp"
},
"chapter_heap": {
"width": 151,
"height": 152,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/7586281e6691807a/chapter_heap.webp"
},
"chapter_graph": {
"width": 542,
"height": 272,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/175a7f2e7c3cf5f0/chapter_graph.webp"
},
"chapter_searching": {
"width": 216,
"height": 216,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/ebfe2b953dbb9d90/chapter_searching.webp"
},
"chapter_sorting": {
"width": 138,
"height": 137,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/9d4be68b2680967c/chapter_sorting.webp"
},
"chapter_divide_and_conquer": {
"width": 407,
"height": 269,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/6f5766ca9385cd2e/chapter_divide_and_conquer.webp"
},
"chapter_backtracking": {
"width": 345,
"height": 249,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/176675e685febf11/chapter_backtracking.webp"
},
"chapter_dynamic_programming": {
"width": 267,
"height": 268,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/06264b1ffd05b08e/chapter_dynamic_programming.webp"
},
"chapter_greedy": {
"width": 265,
"height": 202,
"loading": "visible-layout",
"kind": "hero",
"path": "../shared/images/2ce6caaac951c40b/chapter_greedy.webp"
},
"cover_render": {
"width": 535,
"height": 720,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/dc5be783cea3a262/cover_render.webp"
},
"web_mac_iphone": {
"width": 1598,
"height": 965,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/5b64cf7672ec5ec6/web_mac_iphone.webp"
},
"pdf_ipad": {
"width": 812,
"height": 960,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/66baa2f9bf3abec7/pdf_ipad.webp"
},
"animation": {
"path": "../shared/media/6c70de2b2bf4eef7/animation.mp4",
"poster": "../shared/images/9e5f39bf33afc70f/animation-poster.webp",
"width": 1600,
"height": 1080
},
"running_code": {
"path": "../shared/media/bb169a96ca8316da/running_code.mp4",
"poster": "../shared/images/f33a13e8331245ae/running_code-poster.webp",
"width": 1600,
"height": 1082
},
"comment": {
"path": "../shared/media/62aa0b8d351c63a7/comment.mp4",
"poster": "../shared/images/f9ac69536a7582f5/comment-poster.webp",
"width": 1600,
"height": 1080
},
"avatar_yudongjin": {
"width": 512,
"height": 512,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/02348410d40f7665/avatar_yudongjin.webp"
},
"avatar_coderonion": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/db606f27971eae34/avatar_coderonion.webp"
},
"avatar_curtishd": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/feedb0e547ea31fc/avatar_curtishd.webp"
},
"avatar_Gonglja": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/cdcf023b032b8d30/avatar_Gonglja.webp"
},
"avatar_gvenusleo": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/464e2d351b9263ec/avatar_gvenusleo.webp"
},
"avatar_hpstory": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/7b6478acdab6b845/avatar_hpstory.webp"
},
"avatar_justin-tse": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/208ee5aae490eefa/avatar_justin-tse.webp"
},
"avatar_khoaxuantu": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/9d3f1a21ff51de78/avatar_khoaxuantu.webp"
},
"avatar_krahets": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/e321f9a3ac2739e0/avatar_krahets.webp"
},
"avatar_night-cruise": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/960b489efa22abd4/avatar_night-cruise.jpg"
},
"avatar_nuomi1": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/7316a9eb4429b259/avatar_nuomi1.webp"
},
"avatar_Reanon": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/dcc15aa68cc793c7/avatar_Reanon.webp"
},
"avatar_rongyi": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/5dc6c4b1d8bfe0ac/avatar_rongyi.webp"
},
"avatar_Shyam-Chen": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/a635319ad4eaf2e9/avatar_Shyam-Chen.webp"
},
"avatar_Dr-XYZ": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/b5b4c188d159778c/avatar_Dr-XYZ.webp"
},
"en_animation": {
"path": "../shared/media/99db238804bedf45/en_animation.mp4",
"poster": "../shared/images/325eedad7e336423/en_animation-poster.webp",
"width": 1280,
"height": 864
},
"en_running_code": {
"path": "../shared/media/8a8d9283cb37b664/en_running_code.mp4",
"poster": "../shared/images/615e2bcba0901fda/en_running_code-poster.webp",
"width": 1280,
"height": 864
},
"en_comment": {
"path": "../shared/media/ea699aff8bc08558/en_comment.mp4",
"poster": "../shared/images/ef67d25d9c7ff1b5/en_comment-poster.webp",
"width": 1280,
"height": 864
},
"avatar_shevkun": {
"width": 384,
"height": 384,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/91019a79d7ac4441/avatar_shevkun.webp"
},
"avatar_yuyanhuang": {
"width": 512,
"height": 512,
"loading": "lazy",
"kind": "image",
"path": "../shared/images/f2c06730dacd8659/avatar_yuyanhuang.webp"
}
},
"objects": [
{
"id": "ground",
"style": "position: absolute; width: auto; height: 26.445%; left: 28.211%; top: 54.145%;",
"href": null,
"label_style": null
},
{
"id": "links",
"style": "position: absolute; width: auto; height: 78.751%; left: 10.545%; top: 7.326%;",
"href": null,
"label_style": null
},
{
"id": "astronaut",
"style": "height: 46.673%; left: 35.413%; top: 24.343%;",
"href": "chapter_introduction/",
"label_style": "left: 52.244%; top: 20.919%;"
},
{
"id": "chapter_computational_complexity",
"style": "height: 12.347%; left: 36.267%; top: 37.653%;",
"href": "chapter_computational_complexity/",
"label_style": "left: 39.244%; top: 33.919%;"
},
{
"id": "chapter_array_and_linkedlist",
"style": "height: 22.242%; left: 73.242%; top: 52.481%;",
"href": "chapter_array_and_linkedlist/",
"label_style": "left: 90.897%; top: 76.259%;"
},
{
"id": "chapter_stack_and_queue",
"style": "height: 14.302%; left: 62.646%; top: 77.875%;",
"href": "chapter_stack_and_queue/",
"label_style": "left: 77.571%; top: 91.25%;"
},
{
"id": "chapter_hashing",
"style": "height: 15.266%; left: 63.281%; top: 27.933%;",
"href": "chapter_hashing/",
"label_style": "left: 68.862%; top: 46.292%;"
},
{
"id": "chapter_tree",
"style": "height: 19.615%; left: 80.137%; top: 26.678%;",
"href": "chapter_tree/",
"label_style": "left: 96.159%; top: 44.8%;"
},
{
"id": "chapter_heap",
"style": "height: 10.566%; left: 77.226%; top: 11.559%;",
"href": "chapter_heap/",
"label_style": "left: 88.103%; top: 15.422%;"
},
{
"id": "chapter_graph",
"style": "height: 16.112%; left: 51.854%; top: 5.575%;",
"href": "chapter_graph/",
"label_style": "left: 71.195%; top: 6.503%;"
},
{
"id": "chapter_searching",
"style": "height: 15.149%; left: 18.185%; top: 16.404%;",
"href": "chapter_searching/",
"label_style": "left: 14.556%; top: 20.876%;"
},
{
"id": "chapter_sorting",
"style": "height: 9.574%; left: 25.409%; top: 40.747%;",
"href": "chapter_sorting/",
"label_style": "left: 28.805%; top: 53.808%;"
},
{
"id": "chapter_divide_and_conquer",
"style": "height: 18.681%; left: 32.721%; top: 4.816%;",
"href": "chapter_divide_and_conquer/",
"label_style": "left: 31.42%; top: 8.679%;"
},
{
"id": "chapter_backtracking",
"style": "height: 17.338%; left: 4.875%; top: 32.925%;",
"href": "chapter_backtracking/",
"label_style": "left: 4.742%; top: 50.113%;"
},
{
"id": "chapter_dynamic_programming",
"style": "height: 15.47%; left: 9.406%; top: 57.472%;",
"href": "chapter_dynamic_programming/",
"label_style": "left: 8.561%; top: 75.351%;"
},
{
"id": "chapter_greedy",
"style": "height: 14.127%; left: 23.132%; top: 75.803%;",
"href": "chapter_greedy/",
"label_style": "left: 21.619%; top: 86.85%;"
}
],
"shared": [
"shared/hero-textures/04bb3c644a0ca4e0/stack_and_queue-albedo.webp",
"shared/hero-textures/08f0d9f214bf029a/divide_and_conquer-albedo.webp",
"shared/hero-textures/0e401884672532cc/dynamic_programming-albedo.webp",
"shared/hero-textures/233e11ffcea7bce9/graph-albedo.webp",
"shared/hero-textures/246b0162bfe79563/greedy-albedo.webp",
"shared/hero-textures/3607226f27fc2cda/sorting-albedo.webp",
"shared/hero-textures/59c46d0a9d8edbb9/heap-albedo.webp",
"shared/hero-textures/8da4ad1ec85383c1/hashing-albedo.webp",
"shared/hero-textures/9d2bc4e5556a0ecb/tree-albedo.webp",
"shared/hero-textures/a86caaaa56f4d3a6/backtracking-albedo.webp",
"shared/hero-textures/bd71ba70ed7710ec/searching-albedo.webp",
"shared/hero-textures/c2403c9a216e8976/moon-albedo.webp",
"shared/hero-textures/c51fcf1c87fef527/array_and_linkedlist-albedo.webp",
"shared/images/02348410d40f7665/avatar_yudongjin.webp",
"shared/images/06264b1ffd05b08e/chapter_dynamic_programming.webp",
"shared/images/175a7f2e7c3cf5f0/chapter_graph.webp",
"shared/images/176675e685febf11/chapter_backtracking.webp",
"shared/images/208ee5aae490eefa/avatar_justin-tse.webp",
"shared/images/2ce6caaac951c40b/chapter_greedy.webp",
"shared/images/2fb425426d1f8d86/chapter_computational_complexity.webp",
"shared/images/325eedad7e336423/en_animation-poster.webp",
"shared/images/3e50f38a325c1cb5/links.webp",
"shared/images/464e2d351b9263ec/avatar_gvenusleo.webp",
"shared/images/567306c070a6a7e2/ground.webp",
"shared/images/5b64cf7672ec5ec6/web_mac_iphone.webp",
"shared/images/5cbbdf7be555d3ef/chapter_array_and_linkedlist.webp",
"shared/images/5dc6c4b1d8bfe0ac/avatar_rongyi.webp",
"shared/images/615e2bcba0901fda/en_running_code-poster.webp",
"shared/images/66baa2f9bf3abec7/pdf_ipad.webp",
"shared/images/6f5766ca9385cd2e/chapter_divide_and_conquer.webp",
"shared/images/7316a9eb4429b259/avatar_nuomi1.webp",
"shared/images/7586281e6691807a/chapter_heap.webp",
"shared/images/7b6478acdab6b845/avatar_hpstory.webp",
"shared/images/8138bb0acecedec4/chapter_tree.webp",
"shared/images/8e5eb3b3e3ebd9f7/astronaut.webp",
"shared/images/91019a79d7ac4441/avatar_shevkun.webp",
"shared/images/960b489efa22abd4/avatar_night-cruise.jpg",
"shared/images/9d3f1a21ff51de78/avatar_khoaxuantu.webp",
"shared/images/9d4be68b2680967c/chapter_sorting.webp",
"shared/images/9e5f39bf33afc70f/animation-poster.webp",
"shared/images/a635319ad4eaf2e9/avatar_Shyam-Chen.webp",
"shared/images/b108d70a537714cb/chapter_hashing.webp",
"shared/images/b5b4c188d159778c/avatar_Dr-XYZ.webp",
"shared/images/cdcf023b032b8d30/avatar_Gonglja.webp",
"shared/images/db606f27971eae34/avatar_coderonion.webp",
"shared/images/dc5be783cea3a262/cover_render.webp",
"shared/images/dcc15aa68cc793c7/avatar_Reanon.webp",
"shared/images/e2667767cf7481bd/chapter_stack_and_queue.webp",
"shared/images/e321f9a3ac2739e0/avatar_krahets.webp",
"shared/images/ebfe2b953dbb9d90/chapter_searching.webp",
"shared/images/ef67d25d9c7ff1b5/en_comment-poster.webp",
"shared/images/f2c06730dacd8659/avatar_yuyanhuang.webp",
"shared/images/f33a13e8331245ae/running_code-poster.webp",
"shared/images/f9ac69536a7582f5/comment-poster.webp",
"shared/images/feedb0e547ea31fc/avatar_curtishd.webp",
"shared/media/62aa0b8d351c63a7/comment.mp4",
"shared/media/6c70de2b2bf4eef7/animation.mp4",
"shared/media/8a8d9283cb37b664/en_running_code.mp4",
"shared/media/99db238804bedf45/en_animation.mp4",
"shared/media/bb169a96ca8316da/running_code.mp4",
"shared/media/ea699aff8bc08558/en_comment.mp4",
"shared/vendor/three/459267a54e856023/LICENSE",
"shared/vendor/three/459267a54e856023/package.json",
"shared/vendor/three/459267a54e856023/three.core.min.js",
"shared/vendor/three/459267a54e856023/three.module.min.js"
],
"encoding": {
"hero_quality": 75,
"image_quality": 75,
"texture_quality": 75,
"poster_quality": 75
},
"textures": {
"array_and_linkedlist": {
"full": "../../shared/hero-textures/c51fcf1c87fef527/array_and_linkedlist-albedo.webp"
},
"backtracking": {
"full": "../../shared/hero-textures/a86caaaa56f4d3a6/backtracking-albedo.webp"
},
"divide_and_conquer": {
"full": "../../shared/hero-textures/08f0d9f214bf029a/divide_and_conquer-albedo.webp"
},
"dynamic_programming": {
"full": "../../shared/hero-textures/0e401884672532cc/dynamic_programming-albedo.webp"
},
"graph": {
"full": "../../shared/hero-textures/233e11ffcea7bce9/graph-albedo.webp"
},
"greedy": {
"full": "../../shared/hero-textures/246b0162bfe79563/greedy-albedo.webp"
},
"hashing": {
"full": "../../shared/hero-textures/8da4ad1ec85383c1/hashing-albedo.webp"
},
"heap": {
"full": "../../shared/hero-textures/59c46d0a9d8edbb9/heap-albedo.webp"
},
"moon": {
"full": "../../shared/hero-textures/c2403c9a216e8976/moon-albedo.webp"
},
"searching": {
"full": "../../shared/hero-textures/bd71ba70ed7710ec/searching-albedo.webp"
},
"sorting": {
"full": "../../shared/hero-textures/3607226f27fc2cda/sorting-albedo.webp"
},
"stack_and_queue": {
"full": "../../shared/hero-textures/04bb3c644a0ca4e0/stack_and_queue-albedo.webp"
},
"tree": {
"full": "../../shared/hero-textures/9d2bc4e5556a0ecb/tree-albedo.webp"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Some files were not shown because too many files have changed in this diff Show More