1. Concept
Nocturne is a fictional Michelin-adjacent tasting-menu restaurant. The brief was "dark luxury" — near-black grounds, brass and burgundy as the only two accent colors, and motion that reads as cinematic rather than playful. Every design decision traces back to one idea: a restaurant that opens when the rest of the city exhales. That gave the copy its register (quiet, declarative, a little poetic) and the art direction its lighting brief (single-source, low-key, warm highlights against near-black shadow — the way a film gaffer would light a night interior, not the flat even light of a typical restaurant menu photo).
The layout follows a classic long-form hospitality site structure — hero, philosophy, tasting menu, room gallery, reservations, private events, footer — but every section is built as its own composition rather than a repeated card grid, because a template tell is almost always repetition without variation.
2. Palette & type
| Token | Hex | Use |
|---|---|---|
| Black | #08080a | Base ground |
| Panel | #131215 | Cards, code blocks |
| Brass | #c6a664 | Primary accent — CTAs, eyebrows, rules |
| Brass Light | #e6cd9a | Hover states, emphasis |
| Burgundy | #4a1522 | Secondary accent (used sparingly, mostly in imagery) |
| Emerald | #0f2620 → #1c3f34 | Reservations section background glow |
| Cream | #f2ece0 | Primary text on dark |
Type is a two-family pairing: Fraunces (a high-contrast variable serif with a genuine italic, used at its opsz optical-size axis for display sizes) carries every headline — it has the fashion-magazine "confidence" the brief asked for without tipping into a wedding-invite script. Manrope at weight 300 carries all body copy and UI labels; it's a light, humanist grotesque that stays legible at small sizes on a dark ground, which matters because near-black-on-cream and cream-on-near-black both need real luminance contrast to hold up (checked against WCAG AA at body sizes). Letter-spacing is pulled tight on display type (letter-spacing: -0.02em) and pushed wide on all-caps labels (0.2–0.32em) — that contrast is doing a lot of the "editorial" feeling on its own.
3. Asset generation
All photography is generated through the Higgsfield MCP, model nano_banana_pro at 2K resolution, then converted from PNG to JPEG (quality 82) with macOS sips to bring the folder from ~60 MB of source PNGs down to ~7 MB of shipped JPGs. Every prompt was art-directed with real photography vocabulary — lens, lighting setup, plating style, color grade — rather than generic "nice restaurant photo" phrasing, because that specificity is what keeps the output from reading as stock art.
Each image — the dining-room hero, the three tasting-menu courses, the chef plating, the emerald booth interior, the bar pour, and the private dining room — was generated from a carefully art-directed brief specifying the lens, lighting setup, palette, composition, and mood, then iterated until it sat inside the brand. A few direction notes worth keeping: the hero was directed for depth rather than a flat wide shot, with all three accent colors keyed into one frame; the three courses were each lit from a different angle so the tasting-menu section doesn't feel like the same photo repeated; and the chef and bartender shots were directed with no visible face — it keeps a fictional restaurant honest, with no invented human likeness standing in as a real person. The full prompt library behind the set is proprietary and available to clients on request.
4. Build techniques
Plain HTML/CSS/JS, no build step, no framework. Animation runs on GSAP 3 + ScrollTrigger loaded from the jsDelivr CDN. The techniques doing the most visible work:
Curtain reveal
Every framed image (.reveal-media) sits under a solid .curtain div. On scroll-in, a ScrollTrigger-fired GSAP timeline scales the curtain to scaleY: 0 from transform-origin: top while the image itself eases from scale(1.18) down to scale(1) — a slow wipe-and-settle instead of a plain fade, which reads as far more deliberate/cinematic.
Scroll parallax
Any element flagged data-parallax gets its inner <img> tied to a scrubbed ScrollTrigger tween (yPercent: 10, scrub: 0.6) — the image drifts slower than the page scroll, the classic parallax depth cue, tuned subtle enough not to cause seasickness.
Film grain overlay
A fixed, full-viewport <div> with an inline SVG feTurbulence filter as its background-image, mix-blend-mode: overlay, opacity 0.05, and an 8-step steps() keyframe animation shifting its position — a cheap, GPU-light way to fake analog grain without a video texture or canvas loop.
Section rail (roman numerals)
A fixed side-rail lists I–VI for each section. A ScrollTrigger.create() per section toggles an .active class on the matching numeral as it crosses viewport-center — a quiet wayfinding device that also signals "this is a crafted long-form page," not a stock template.
Custom cursor
A two-part cursor (small solid dot + lagging outline ring) tracks the pointer via gsap.ticker-driven lerp easing, and grows/brightens on hover over links, dishes, and gallery tiles. Disabled entirely on touch devices and under prefers-reduced-motion.
Hero timeline
On window.load, a single GSAP timeline sequences: preloader fade → eyebrow clip-in → headline slide-up (from a fully-visible overflow:hidden parent, so the text itself appears to rise out of a mask) → subhead fade → hero image settling from an oversized crop, all offset with negative-time overlaps for a layered rather than sequential feel.
5. Layout notes
- The tasting-menu rows alternate image/text order on even children via
nth-child(even)grid-column reassignment — avoids the "every row identical" template tell. - The room gallery is a hand-set 6-column / 2-row CSS Grid with one large hero tile and three supporting tiles, not an auto-generated masonry — asymmetry read as more intentional than a uniform grid.
- Safe, real contrast: body copy on black sits at a light but legible weight (Manrope 300) with a dimmed cream (
#cfc7b8) rather than pure white, which reduces the "glowing text on black" halation while keeping contrast well above WCAG AA at the sizes used. - Everything collapses to a single column under 900px; the fixed section rail and custom cursor are hidden below that breakpoint since they're desktop-only affordances.
6. Deployment
This is a static folder — index.html, guide/index.html, and everything under assets/ — with zero build step, so it deploys as-is:
cd nocturne
netlify deploy --prod --dir=.
Or drag the nocturne/ folder directly onto Netlify's deploy UI. Because every asset path in the HTML/CSS is relative (assets/images/..., never an absolute local path), the folder works identically whether it's served from Netlify, GitHub Pages, or a plain python3 -m http.server during local development — which is exactly how this build was previewed while it was being made.