diff --git a/assets/story-film.js b/assets/story-film.js index feebc2d..484f4a5 100644 --- a/assets/story-film.js +++ b/assets/story-film.js @@ -233,7 +233,59 @@ }); } - /* ---------- 2. Scene 2 set-piece: the photograph is picked up ---------- */ + /* ---------- 2. Đêm dần xuống · the night falls ---------- */ + + // The single continuous transformation across all nine scenes, and the + // reason the page reads as one evening rather than nine slides. The room + // gets later as she scrolls, starting from the hour on HER OWN device — so + // if she opens this at 1am it is already deep night, and a friend she shows + // it to tomorrow afternoon sees a different room. Nothing leaves the device + // and there is no backend; it is just her clock. + // + // Deliberately drives ONE custom property on ONE element. Writing a scrubbed + // value onto :root would invalidate every element that inherits it, every + // frame; scoping it to .nightfall keeps the recalculation to one subtree. + // + // It must never touch the palette tokens — she may have explicitly chosen + // light or dark, and overriding that would be a bug, not a feature. + const nightfall = document.querySelector('.nightfall'); + if (nightfall) { + // Where the evening starts, by local hour. Daytime still opens softly + // lit rather than at zero, because the room has a lamp in it either way. + const hour = new Date().getHours(); + const startNight = + hour >= 1 && hour < 5 + ? 0.72 // small hours + : hour < 16 + ? 0.12 // daytime + : hour < 19 + ? 0.22 // late afternoon + : hour < 21 + ? 0.34 // early evening + : hour < 23 + ? 0.48 // night + : 0.62; // near midnight + + // Always leave headroom so the arc is felt rather than saturating early. + const endNight = Math.min(0.96, startNight + 0.46); + + gsap.fromTo( + nightfall, + { '--night': startNight }, + { + '--night': endNight, + ease: 'none', + scrollTrigger: { + trigger: document.body, + start: 'top top', + end: 'bottom bottom', + scrub: SCRUB, + }, + }, + ); + } + + /* ---------- 3. Scene 2 set-piece: the photograph is picked up ---------- */ // The one place on this page where a "turn" is physically motivated. The // hero frame establishes a desk with a letter on it; this is a photograph @@ -287,7 +339,7 @@ }); } - /* ---------- 3. Per-beat entrances (non-reversing) ---------- */ + /* ---------- 4. Per-beat entrances (non-reversing) ---------- */ // One batch per scene so each can carry its own tempo, and so a tall scene // times its lower half correctly — a single scene-level trigger never can. @@ -352,7 +404,7 @@ }); }); - /* ---------- 4. Anchor jumps travel with the film's camera ---------- */ + /* ---------- 5. Anchor jumps travel with the film's camera ---------- */ // Native `scroll-behavior: smooth` bypasses Lenis: a nav click warps the // page in ~24 frames with a completely different easing signature from @@ -379,7 +431,7 @@ }); }); - /* ---------- 5. Measurement ---------- */ + /* ---------- 6. Measurement ---------- */ // Pins change total scroll height; recompute all triggers once now and on // full load (fonts/images can shift layout after this script runs). diff --git a/assets/styles.css b/assets/styles.css index ea29d90..f576437 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -167,6 +167,85 @@ body::after { } } +/* ---------- Đêm dần xuống · the night falls ---------- + One fixed layer that makes the room get later as she scrolls. `--night` runs + 0 (early evening) → 1 (past midnight) and is written by story-film.js onto + this element only — deliberately NOT onto :root, so a per-frame scrub + invalidates one subtree instead of every element on the page. + + It is atmosphere, not theming. The palette tokens are never interpolated: the + visitor's explicit light/dark choice always wins, and both themes simply + travel — light from dusk toward evening, dark from evening toward midnight. + + The 0.35 default is the whole point of the fallback story: with no JS, or + under reduced motion, the room is simply already lit at a plausible hour + rather than sitting at "unlit". */ +.nightfall { + --night: 0.35; + position: fixed; + inset: 0; + /* Above the page wash and the candle bloom (-1), below the grain (2) and all + chrome. Transparent through the middle, so body text is never dimmed. */ + z-index: 1; + pointer-events: none; + background: + radial-gradient( + 125% 105% at 50% 42%, + transparent 46%, + rgba(18, 7, 12, calc(0.5 * var(--night))) 100% + ), + radial-gradient( + 70% 55% at 22% 88%, + rgba(255, 190, 140, calc(0.1 * var(--night))), + transparent 62% + ); +} + +/* Dust turning over in the lamp beam — the cheapest possible way to have many + small things moving at once. A tiled dot pattern rather than N elements: one + composited layer, one transform, no per-particle cost. Masked to the beam so + it reads as lit air instead of as noise over the page. */ +.nightfall__dust { + position: absolute; + inset: -25%; + display: block; + opacity: calc(0.55 * var(--night)); + background-image: + radial-gradient(circle at 20% 30%, rgba(255, 226, 200, 0.9) 0 1px, transparent 1.6px), + radial-gradient(circle at 68% 12%, rgba(255, 226, 200, 0.7) 0 1.2px, transparent 1.8px), + radial-gradient(circle at 42% 76%, rgba(255, 226, 200, 0.8) 0 1px, transparent 1.5px); + background-size: + 180px 180px, + 260px 260px, + 220px 220px; + /* Confine the motes to the lamp's throw. */ + mask-image: radial-gradient(58% 48% at 24% 84%, #000 12%, transparent 72%); + -webkit-mask-image: radial-gradient(58% 48% at 24% 84%, #000 12%, transparent 72%); + animation: dust-drift 42s linear infinite alternate; + will-change: transform; +} + +@keyframes dust-drift { + from { + transform: translate3d(-2.5%, 1.5%, 0); + } + to { + transform: translate3d(2.5%, -2%, 0); + } +} + +@media (prefers-reduced-motion: reduce) { + /* Freeze the evening at roughly 22:00 and still the air. A lit room, no + travel — nothing readable is lost. */ + .nightfall { + --night: 0.45; + } + + .nightfall__dust { + animation: none; + } +} + a { color: inherit; text-decoration-thickness: 0.08em; diff --git a/index.html b/index.html index 1d41879..f86aafd 100644 --- a/index.html +++ b/index.html @@ -121,6 +121,15 @@
+ + + Bỏ qua đến nội dung