One continuous transformation across all nine scenes, so the page reads as a single evening instead of nine separate slides. A fixed backdrop layer travels from early evening toward past midnight as she scrolls: the corners go to plum, the lamp keeps its warmth, and dust turns over in the beam. It starts from the hour on her own device, so opening it at 1am begins deep in the night and a friend shown it the next afternoon sees a different room. Nothing leaves the device and there is no backend — it reads her clock. Deliberate boundaries: - Atmosphere only. The palette tokens are never interpolated, so an explicit light or dark choice always wins; both themes simply travel within themselves. - The scrubbed value is written to one element, not to :root. On :root it would invalidate everything inheriting it on every frame. - Dust is a masked, tiled dot pattern on one composited layer rather than N particle elements — many things moving, one transform. - Defaults to a lit room in CSS, so the no-JS tier gets a deliberate composition rather than an unlit one, and reduced motion freezes the evening at about 22:00 with the air still. Verified at 1440x900 and 390x844, both themes, and reduced motion: the value travels with scroll, the theme choice survives, no horizontal overflow, no hidden beats, no console output. This is the light system the remaining objects will be lit by; it is groundwork as much as it is a scene.
Dating
A warm open letter from Tiến Nguyễn Minh to whoever comes along, told as a scroll-driven short film. Vietnamese only.
The page was originally framed as a mock job description and CV; that conceit was dropped in favour of a plainly romantic register, so avoid reintroducing recruiting language (ứng viên, vị trí, quyền lợi, trách nhiệm, tuyển chọn) when editing copy.
Structure
Single-page site: index.html at the repo root holds the whole story, told as
scenes and navigable via anchors:
#home— cold open, the vacant position#applicant— who the candidate is#honest— saying it straight#offer— what you get#shared— what you both protect#no-test— bonus points, and the written climax#closing— the connect beat#dossier— appendix, the detail table#end— end card
Features
- Light theme: warm ivory and terracotta-rose
- Dark theme: candlelit plum and rose-gold
- First visit follows the OS colour scheme; an explicit choice is persisted
- Compact emoji theme switch
- Vietnamese-only copy
Tech
- Plain HTML, CSS, and JavaScript — no framework, no build step
- GitHub Pages (classic, deploy from branch)
Motion tiers
Motion is a three-tier progressive enhancement. Any change must keep all three
working, and no [data-reveal] element may ever be left invisible:
- Film (
html.film-ready) — GSAP + ScrollTrigger + Lenis from a CDN, inassets/story-film.js. Activates only on ≥768px, with motion allowed, and only when the CDN libraries actually arrived in time. - Reveal (
html.js-ready:not(.film-ready)) — IntersectionObserver fade-and-rise inassets/story.js. This is what phones and reduced-motion visitors get. - Static — no JS, or a stalled CDN. A watchdog in the
index.htmlhead un-hides everything after 1200ms if no engine has claimed the page, so the first paint is never wordless.
Two ordering rules in story-film.js are load-bearing and commented there: pins
are created before reveals and carry refreshPriority: 1, and reveals are
per-beat and non-reversing.
Files
assets/styles.css— tokens, layout, components (both themes)assets/story.css/story.js— tier 2 revealassets/story-film.css/story-film.js— tier 1 filmassets/story-chrome.css/story-chrome.js— progress bar + active navassets/theme-switch.js— theme toggle (keep in sync with the head bootstrap)assets/freshness-stamp.js— "cập nhật [ngày]" from the GitHub commit date
Developing
No install needed. Open index.html directly, or serve the folder:
python -m http.server 8000
Then visit http://localhost:8000/.
Deploying
Classic GitHub Pages serves the main branch root directly — pushing to main
publishes the site. .nojekyll disables Jekyll processing.