类型:Site模板 · 收录:2026-10-04 12:33:24 · 源码:会员可见
Build one standalone HTML file (embedded CSS + vanilla JS, no frameworks, no libraries, no build step) that recreates the PLUME landing page exactly. Black full-viewport cinematic product page for a knife. Four stacked sections. Videos cover each section edge to edge. White editorial type over the footage. Phones, tablets, and short landscape screens stack the collage into normal flow so nothing overlaps or clips.
The third section is a native CSS sticky scroll track. It is the part generators usually get wrong. Follow the SECTION 3 rules literally, including the CSS and the scroll algorithm. Do not reinvent the pin with position:fixed, scroll-snap, GSAP, or a 100vh section.
PAGE TITLE
PLUME — Light as a feather. Strong by design.
FONTS (Google Fonts, preconnect fonts.googleapis.com and fonts.gstatic.com)
https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,100..900&family=Inter:wght@300;400;500&display=swap
- Body, nav, buttons, small copy: Inter, weight 400. Fallback: -apple-system, Segoe UI, Helvetica, Arial, sans-serif. Antialiased.
- Display headlines: Archivo, weight 400, uppercase, line-height 0.94, letter-spacing -0.022em.
- Logo: Inter weight 300, letter-spacing 0.42em, uppercase, color #fff, size clamp(15px, 1.45vw, 23px).
- Advantages headlines: Archivo, font-stretch 122% on desktop (100% at max-width 900px), letter-spacing -0.018em, uppercase, line-height 1.3, white-space nowrap.
COLOR TOKENS
--ink: #ffffff
--dim: #c7c7c9
--dimmer: #8a8a8f
--bg: #000000
--blue: #4b4ce6
--pink: #ef5a7f
--pad: 3.6vw (5.2vw at max-width 900px or max-height 540px)
--grad: linear-gradient(90deg, #3c3cf0 0%, #6a4cdd 22%, #a85ab4 42%, #df5473 62%, #ec4a4a 80%, #f2602c 100%)
Gradient text uses background-clip: text, color transparent, -webkit-text-fill-color transparent. On the advantages label, the gradient span is display:inline-block so the gradient maps across the words, not the viewport.
html and body background #000. color #fff. Reset margin/padding, box-sizing border-box. Links inherit color, no underline. img and video display block, max-width 100% (full-bleed videos override max-width to none).
STICKY REQUIREMENT: do not set overflow:hidden, overflow:auto, or overflow:scroll on html, body, or any wrapper around #adv. Those properties cancel position:sticky. If horizontal clipping is required, use overflow-x: clip only. #adv itself must stay overflow: visible. The only overflow:hidden in section 3 is on the inner sticky frame .adv-stage.
SMALL COPY (.body-sm)
font-size clamp(12px, 1vw, 16px); line-height 1.55; color #c7c7c9; letter-spacing 0.002em. At ≤900px or short screens: 14px / 1.5. At max-height 540px: 13px.
BUTTONS
.pill — transparent, 1px solid rgba(255,255,255,.34), border-radius 999px, padding 0.85em 1.55em, font-size clamp(11px, 0.88vw, 14px), white, nowrap. Hover: background #fff, color #000, border #fff. Transition background, border-color, color 0.35s.
.btn-solid — background #f1f1f2, color #0b0b0b, no border, border-radius 6px, padding 1.15em 2.1em, same font size, letter-spacing 0.005em. Hover: background #fff, translateY(-2px). Transition background and transform 0.35s.
At ≤900px both pills/buttons and the menu toggle have min-height 44px. At ≤380px pill padding is 0.7em 1em and nav action gap is 8px.
NAV
Absolute, top 0, full width, z-index 40, flex, space-between, align center, padding 2.6vh var(--pad) 0.
Left: wordmark PLUME.
Center links, flex, gap 2.1vw, margin-left auto, margin-right 2.6vw. Size clamp(11px, 0.88vw, 14px), color #8a8a8f. Hover and .active: #fff. Transition color 0.3s.
Links and hrefs:
- Product → #hero (class active)
- Structure → #recipe
- Materials → #recipe
- Engineering → #adv
- Contact → #ess
Right: pill button label “Explore PLUME”.
Hamburger (.nav-toggle) hidden on desktop. 44×44 circle, 1px solid rgba(255,255,255,.34), transparent background, three 16×1.5px white bars, gap 4px. When aria-expanded true: bar 1 translateY(5.5px) rotate(45deg), bar 2 opacity 0, bar 3 translateY(-5.5px) rotate(-45deg). Transition transform and opacity 0.3s.
Mobile menu (≤900px or max-height 540px): toggle visible. Links hidden until .open. When open: absolute dropdown, top calc(100% + 10px), right aligned to pad (respect safe-area), column, align flex-end, gap 2px, min-width 180px, padding 10px 16px, background rgba(8,8,10,.88), 1px solid rgba(255,255,255,.16), radius 16px, backdrop-filter blur(16px). Link font-size 16px, padding 10px 4px. Nav padding respects safe-area insets. Click outside closes. Anchor clicks close the menu and smooth-scroll (auto scroll if prefers-reduced-motion).
REVEAL ANIMATIONS (sections 1, 2, and 4 only — section 3 does not use .panel or .rv)
.rv starts opacity 0, translateY(22px). .rv.in: opacity 1, transform none. Transition opacity and transform 0.9s cubic-bezier(.22,.61,.36,1).
Headline clip: .clip is display block, overflow hidden. Inner span starts translateY(105%), transitions transform 1.05s cubic-bezier(.19,1,.22,1) to none when .clip.in.
Nav .rv items reveal on load, staggered: first at 140ms, then +95ms each.
.panel content starts opacity 0 and visibility hidden. When the panel is the active scroll panel, add .revealed so .panel-content fades in over 0.85s ease. Then stagger every .clip, .rv, and .callout inside by index × 110ms. Callouts also stagger their inner .rv at +80ms then +90ms.
Reduced motion: those transitions become 0.01ms.
PANEL SCROLL BEHAVIOR (only #hero, #recipe, #ess — these three have class "panel")
.panel is position relative, height 100vh, overflow hidden, background #000. On the responsive breakpoint, .panel becomes height auto, min-height 100vh and 100svh, overflow visible.
Active panel rule: among .panel elements, pick the one whose top is ≤ 88px, whose bottom is still below 42% of the viewport height, and whose top is the greatest (closest to the 88px line). On enter, add .is-active and run the reveal once (WeakSet, never replay). On leave, remove .is-active.
#adv is NOT a .panel. Do not run this reveal system on it.
Hero video: loop, muted, playsinline, autoplay, preload auto. Starts opacity 0, fades to 1 over 1.6s ease once loadeddata (or if readyState ≥ 2). Plays only while ≥10% visible, pauses when not.
Recipe video: muted, playsinline, preload auto, NOT loop, no autoplay. On recipe panel enter: currentTime 0, playbackRate 2, play. On leave: pause and playbackRate 1.
Essentials video: muted, loop, playsinline, preload auto, no autoplay. Play on essentials panel enter, pause on leave.
FULL-BLEED VIDEO CSS (hero, recipe, advantages, essentials)
position absolute, inset 0, width 100%, height 100%, max-width none, object-fit cover, object-position center.
ASSETS — USE THESE EXACT URLS
Hero video (loop, fade in):
https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/0a285ef6-17e3-443b-a3e3-bf043fcf6952.mp4
Recipe video (2× on enter, restart from 0):
https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/ddcebad8-6b75-40e2-b30e-83035b3f4d26.mp4
Advantages video (plays once at 2×, then fades out; NOT scroll-scrubbed):
https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/5ce34dc5-db43-4b33-9cd0-82d9da44bf96.mp4
Essentials video (loop while section is active):
https://d2ol7oe51mr4n9.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/8f758ee6-5736-4b37-9c66-db8e59a1f154.mp4
Advantage stills (PNG, one visible at a time, cross-fade, scroll-driven after the video ends):
1 skeletonised steel body:
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260922_235551_d79382b3-319e-46ee-9be7-c434921ef3c7.png
alt: PLUME knife, skeletonised steel body
2 profile:
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260922_235605_60056fc4-aca1-4e44-8b37-84cdd87761d0.png
alt: PLUME knife in profile
3 blackened finish:
https://d8j0ntlcm91z4.cloudfront.net/user_38xzZboKViGWJOttwIXH07lWA1P/hf_20260923_002256_436731d2-c211-48a2-9793-a6c0be6d7906.png
alt: PLUME knife, blackened finish
SECTION 1 — HERO (#hero, class "hero panel")
Video layer z-index 1. Inner content z-index 10, height 100%, horizontal padding var(--pad). All hero text is position absolute.
- Headline, class display h-title, left var(--pad), top 25.5%, font-size clamp(36px, 7.4vw, 133px). Two clip lines: “Light as” / “a Feather.”
- Material lockup, class h-mats, right var(--pad), top 24.5%, text-align right, font-size clamp(13px, 1.62vw, 26px), line-height 1.42, letter-spacing -0.005em. Line 1: “Titanium core.” in #ef5a7f. Line 2: “Steel” in #4b4ce6, then “ outer Layer.” in #ef5a7f.
- Second headline, class display h-strong, right var(--pad), top 45.5%, text-align right, same display size. Two clip lines: “Strong by” / “Design”
- Model line, class body-sm h-model, left 27%, top 50.5%, max-width 24ch: Demonstrated model —<br>“Plume OnlyMetal 001”
- Copy 1, class body-sm h-copy-1, left var(--pad), top 65.5%, max-width 40ch: Designed to feel weightless, PLUME reduces mass to the minimum necessary for control. The blade moves effortlessly, leaving only precision in its wake.
- Copy 2, class body-sm h-copy-2, left 27%, top 78.5%, max-width 42ch: A titanium core forms the structural backbone of the knife, delivering exceptional rigidity and durability without adding unnecessary weight.
- Button, class btn-solid h-scroll, right var(--pad), bottom 7.5%, data-scroll="#recipe": Scroll to explore
SECTION 2 — RECIPE (#recipe, class "recipe panel")
Inner stage height 100%. Video z-index 1. Content z-index 10.
- Headline, class display r-title, left var(--pad), top 7.5%, font-size clamp(38px, 7.8vw, 142px). Clip lines: “Recipe for” / “Lightness” (Lightness is gradient text).
- Lede, class body-sm r-lede, right var(--pad), top 7.5%, max-width 37ch: Every detail is engineered to remove excess mass while preserving strength, balance, and control.
- Callout Steel, class callout co-steel, right var(--pad), top 39%, text-align right. h3 “Steel Outer Layer”. Paragraph max-width 35ch, margin-left auto: Outer steel layers provide cutting performance, edge retention, and protection against wear and corrosion.
- Callout Core, class callout co-core, left var(--pad), top 66%, text-align left. h3 “Titanium Core”. Paragraph: A lightweight titanium layer runs through the center of the blade, absorbing stress while maintaining structural stability.
Callout h3: weight 400, size clamp(16px, 2.15vw, 35px), letter-spacing -0.012em, color #efeff1, line-height 1.1, margin-bottom 0.72em.
Lead line: 1px, background rgba(255,255,255,.4), scaleX(0), transitions to scaleX(1) over 1.1s cubic-bezier(.22,.61,.36,1) when .callout.in. A 6×6 white dot sits on the line via ::after (top -2.5px).
Steel line: right 0, top calc(100% + 2.4vh), width 31vw, origin right center, dot on the left end.
Core line: left 0, top calc(100% + 2.4vh), width 26vw, origin left center, dot on the right end.
SECTION 3 — UNDENIABLE ADVANTAGES (#adv). THIS SECTION IS A STICKY SCROLL PIN.
What the user sees
The document scrolls normally until #adv reaches the top of the viewport. The frame then locks in place and stays full-screen while the user keeps scrolling. During that locked scroll: a video plays once at double speed and fades away, then three knife stills cross-fade one at a time, each with its own two-line headline and caption. When the tall section has been scrolled through, the frame unlocks and the next section arrives. Scrolling back above the section resets the video to the start and hides the stills.
DOM — this exact nesting, nothing wrapped around it
section#adv.adv is the SCROLL TRACK. It is tall. It is not sticky.
div.adv-stage is the PINNED FRAME. It is the only sticky element. It is a direct child of #adv.
Inside .adv-stage, in this order: the video, the three images, the gradient label, then the three slides. All of those live inside the sticky frame so they stay on screen together while the track scrolls.
<section class="adv" id="adv">
<div class="adv-stage">
<video id="advVideo" class="adv-video" src="ADVANTAGES_VIDEO_URL" muted playsinline preload="auto"></video>
<div class="adv-shots">
<img class="adv-shot" id="shot1" src="STILL_1" alt="PLUME knife, skeletonised steel body">
<img class="adv-shot" id="shot2" src="STILL_2" alt="PLUME knife in profile">
<img class="adv-shot" id="shot3" src="STILL_3" alt="PLUME knife, blackened finish">
</div>
<p class="adv-label"><span class="grad-text">Undeniable advantages</span></p>
<div class="adv-slide" data-slide="0">...</div>
<div class="adv-slide" data-slide="1">...</div>
<div class="adv-slide" data-slide="2">...</div>
</div>
</section>
The video tag has muted and playsinline. It does NOT have loop. It does NOT have autoplay. JS starts it.
CSS — emit this, do not paraphrase the heights or the position values
.adv {
position: relative;
height: 460vh;
background: #000;
}
.adv-stage {
position: sticky;
top: 0;
height: 100vh;
overflow: hidden;
background: #000;
}
.adv-video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
max-width: none;
object-fit: cover;
object-position: center;
z-index: 2;
transition: opacity .75s ease;
}
.adv-shots { position: absolute; inset: 0; z-index: 3; }
.adv-shot {
position: absolute;
top: 50%;
left: 50%;
width: min(78vw, 1340px);
transform: translate(-50%, -50%);
opacity: 0;
transition: opacity .7s ease;
filter: drop-shadow(0 30px 60px rgba(0,0,0,.85));
will-change: opacity, transform;
}
.adv-shot.on { opacity: 1; }
.adv-label {
position: absolute; left: 0; right: 0; top: 9.2%; z-index: 12;
text-align: center;
font-size: clamp(12px, 1.15vw, 19px);
letter-spacing: .005em;
}
.adv-label span { display: inline-block; }
.adv-head {
position: absolute; left: 0; right: 0; top: 0; height: 100%; z-index: 4;
display: flex; flex-direction: column; justify-content: center; align-items: center;
pointer-events: none;
padding: 0 1.5vw;
}
.adv-head .line {
display: flex; justify-content: center;
font-family: 'Archivo', 'Inter', Helvetica, Arial, sans-serif;
font-weight: 400;
font-stretch: 122%;
letter-spacing: -.018em;
font-size: clamp(36px, 10.8vw, 200px);
line-height: 1.3;
text-transform: uppercase;
white-space: nowrap;
}
.adv-head .line.l2 { margin-top: .1em; }
.adv-head .ch {
display: inline-block;
transform: translateY(.42em) scaleY(.6);
opacity: 0;
transition: transform .62s cubic-bezier(.19,1,.22,1), opacity .42s ease;
}
.adv-slide.on .adv-head .ch { transform: none; opacity: 1; }
.adv-copy {
position: absolute; left: 0; right: 0; bottom: 12.5%; z-index: 12;
text-align: center;
}
.adv-copy p { margin: 0 auto; max-width: 58ch; }
.adv-slide {
position: absolute; inset: 0;
opacity: 0; pointer-events: none;
transition: opacity .5s ease;
}
.adv-slide.on { opacity: 1; }
Why these two heights are both required
#adv is 460vh. That is 4.6 viewports of document scroll. .adv-stage is 100vh and position:sticky; top:0. The extra 360vh is the distance the frame stays pinned. Progress is 0 when the top of #adv hits the top of the viewport. Progress is 1 when the bottom of #adv hits the bottom of the viewport. Formula: clamp(-rect.top / (rect.height - window.innerHeight), 0, 1). If #adv is 100vh, sticky has no runway and the three stills never scrub. If .adv-stage is position:fixed, it will not release into the next section. If any parent of .adv-stage has overflow:hidden, the pin never engages.
Slide copy, split into per-letter spans on load
Each headline line is empty in HTML and has data-text. JS replaces it with one span.ch per character. A space becomes a non-breaking space. transitionDelay = character index × 26ms. Letters stay hidden until their slide has class "on".
Slide 0, data-text "Structural" and "Lightness". Caption: Material is reduced at a structural level, lowering weight without sacrificing rigidity or stability. Image: shot1.
Slide 1, data-text "Balanced" and "Geometry". Caption: Every curve and angle distributes mass evenly, ensuring control, balance, and predictable handling. Image: shot2.
Slide 2, data-text "Controlled" and "Edge". Caption: The cutting edge is tuned for precision, maintaining sharpness while avoiding unnecessary material mass. Image: shot3.
The gradient label “Undeniable advantages” stays visible the whole time the frame is on screen. It does not change per slide.
STATE MACHINE — video first, then scroll-scrubbed stills
The video is time-based. Scroll does not scrub the video. Scroll only chooses the still, and only after the video has ended.
State A, before the frame is on screen: video paused, currentTime 0, playbackRate 1, opacity 1. No slide has class "on". No image has class "on". activeSlide = -1.
State B, pinned frame is at least 35% visible (IntersectionObserver on .adv-stage, threshold 0.35): start the video once. playbackRate = 2, opacity 1, play() (if readyState < 2, wait for canplay, then play). Stills stay hidden while the video plays.
State C, video fires "ended": set video opacity to 0 (the 0.75s opacity transition fades it), set advVideoDone = true, show slide 0 and shot1 immediately.
State D, advVideoDone is true, every animation frame: read scroll progress p of #adv with the formula above. Remap it so the first 22% of the pin is the video hold:
t = clamp((p - 0.22) / 0.78, 0, 1)
if t < 0.34 show slide 0 and shot1
else if t < 0.67 show slide 1 and shot2
else show slide 2 and shot3
Only one slide and one image have class "on". Changing the index cross-fades them (opacity 0.5s on the slide, 0.7s on the image).
While a slide is active, gently move its image from scroll position inside that third:
local = t < 0.34 ? t / 0.34 : t < 0.67 ? (t - 0.34) / 0.33 : (t - 0.67) / 0.33
k = 1 + (1 - clamp(local, 0, 1)) * 0.075
deg = (local - 0.5) * 1.1
image style transform = translate(-50%, -50%) scale(k) rotate(deg)
k goes from 1.075 at the start of the band to 1.000 at the end. Rotation goes from about -0.55deg to +0.55deg. Write scale to 4 decimals and rotation to 3 decimals.
State E, the observer says the stage is NOT intersecting AND the stage's top is still below the viewport top (boundingClientRect.top > 0, meaning the user scrolled back above #adv): reset to State A. Pause, rate 1, currentTime 0, opacity 1, clear advStarted and advVideoDone, setSlide(-1). Scrolling down past #adv (top is negative) does not reset. Coming back from below keeps the ended state until the user passes above the section.
If the user scrolls through the pin before the video ends, leave the stills hidden until "ended". On "ended", jump to whichever slide t currently maps to.
Run this update and the .panel sync from one requestAnimationFrame loop. Also call it once on load.
Required JS shape for this section (keep this behavior):
const ADV_VIDEO_END = 0.22;
let advStarted = false;
let advVideoDone = false;
let activeSlide = -1;
function sectionProgress(section) {
const r = section.getBoundingClientRect();
const total = r.height - window.innerHeight;
if (total <= 0) return 0;
return Math.min(1, Math.max(0, -r.top / total));
}
function setSlide(i) {
if (i === activeSlide) return;
activeSlide = i;
slides.forEach((s, n) => s.classList.toggle('on', n === i));
shots.forEach((s, n) => s.classList.toggle('on', n === i));
}
function resetAdvVideo() {
advStarted = false;
advVideoDone = false;
advVideo.pause();
advVideo.playbackRate = 1;
advVideo.currentTime = 0;
advVideo.style.opacity = '1';
setSlide(-1);
}
function startAdvVideo() {
if (advStarted) return;
advStarted = true;
advVideoDone = false;
advVideo.style.opacity = '1';
advVideo.playbackRate = 2;
const play = () => advVideo.play().catch(() => {});
if (advVideo.readyState >= 2) play();
else advVideo.addEventListener('canplay', play, { once: true });
}
advVideo.addEventListener('ended', () => {
advVideo.style.opacity = '0';
advVideoDone = true;
setSlide(0);
});
new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) startAdvVideo();
else if (e.boundingClientRect.top > 0) resetAdvVideo();
});
}, { threshold: 0.35 }).observe(advStage);
function updateAdv() {
if (!advVideoDone) return;
const p = sectionProgress(advSection);
const t = Math.min(1, Math.max(0, (p - ADV_VIDEO_END) / (1 - ADV_VIDEO_END)));
setSlide(t < 0.34 ? 0 : t < 0.67 ? 1 : 2);
const local = t < 0.34 ? t / 0.34 : t < 0.67 ? (t - 0.34) / 0.33 : (t - 0.67) / 0.33;
const shot = shots[activeSlide];
if (!shot) return;
const clamped = Math.min(1, Math.max(0, local));
const k = 1 + (1 - clamped) * 0.075;
shot.style.transform = 'translate(-50%,-50%) scale(' + k.toFixed(4) + ') rotate(' + ((local - 0.5) * 1.1).toFixed(3) + 'deg)';
}
Mobile and short screens still use the same sticky track. Do not turn #adv into a normal stacking section. Keep height: 460vh on .adv at every breakpoint. Only the inner frame and type change:
At max-width 900px OR max-height 540px:
.adv-stage { height: 100vh; height: 100svh; }
.adv-label, .adv-copy { padding: 0 6vw; }
.adv-copy { bottom: max(8%, env(safe-area-inset-bottom)); }
.adv-head { padding: 0 5vw; }
.adv-head .line {
font-size: clamp(30px, 11vw, 78px);
font-stretch: 100%;
letter-spacing: -0.03em;
line-height: 1.12;
}
.adv-shot {
width: auto;
max-width: min(88vw, 640px);
max-height: 42vh;
height: auto;
}
At max-height 540px also:
.adv-head .line { font-size: clamp(24px, 6vw, 48px); line-height: 1.05; }
.adv-label { top: 4%; }
.adv-copy { bottom: 4%; }
.adv-shot { max-height: 34vh; max-width: min(62vw, 460px); }
SECTION 4 — ESSENTIALS (#ess, class "ess panel")
Video full bleed. Content z-index 10.
- Headline, class display e-title, left var(--pad), top 7.5%, font-size clamp(38px, 7.8vw, 142px). Clip lines: “Only what” / “Really matters” (Really matters is gradient text).
- Lede, class body-sm e-lede, right var(--pad), top 7.5%, max-width 41ch: Stripped of excess, refined to essentials. PLUME is engineered where every gram, angle, and surface serves a purpose.
- Button, class btn-solid e-cta, left 50%, translateX(-50%), bottom 8.5%: View Full Specification
RESPONSIVE — sections 1, 2, and 4 when max-width 900px OR max-height 540px
Desktop absolute collage stays on large screens only. .panel becomes height auto, min-height 100vh and 100svh, overflow visible. Recipe stage matches that min-height.
Hero, recipe, and essentials inners become flex column, align stretch, height auto, min-height 100vh/100svh, gap 0.85rem. Padding: top max(76px, safe-area-top + 60px), right/left max(pad, safe-area), bottom max(28px, safe-area-bottom).
Every absolutely positioned text block and callout in those three sections becomes position relative, top/right/bottom/left auto, text-align left, max-width 40ch.
Headlines (.h-title, .h-strong, .r-title, .e-title): max-width 100%, font-size clamp(32px, 10.2vw, 80px).
.h-mats: order -1 (first), align-self flex-end, text-align right, max-width 18ch, font-size clamp(13px, 3.4vw, 20px).
.h-strong: align-self flex-end, text-align right.
.h-model: margin-top 0.4rem.
Copy blocks and ledes: max-width 38ch.
.h-scroll: align-self flex-end, margin-top auto.
.r-lede: margin-bottom 0.4rem.
Callouts: width min(100%, 38ch). Steel: align-self flex-end, text-align right, margin-top auto. Core: align-self flex-start, margin-top 1.6rem. Steel paragraph keeps margin-left auto.
Lead lines become position relative, display block, width min(34vw, 180px), margin-top 1rem. Steel line margin-left auto. Core line margin-right auto.
Essentials CTA: align-self center, transform none, margin-top auto.
≤380px
Inner gap 0.6rem, padding-top 68px, padding-bottom 14px. Those four headlines are 32px.
max-height 540px AND min-width 700px (short landscape, hero and recipe only)
Hero inner is a 2-column grid: columns minmax(0,1.05fr) minmax(0,.95fr), rows auto auto 1fr auto auto, column-gap 4vw, align start.
- title: col 1, row 1 / span 2, align end
- mats: col 2, row 1, order 0, align start, justify end
- strong: col 2, row 2, align start, justify end
- model: col 1, row 3, align end, margin-top 0
- copy 1: col 1, row 4
- copy 2: col 1, row 5
- scroll button: col 2, row 5, align end, justify end, margin-top 0
Recipe inner: 2 columns equal, rows auto auto 1fr, column-gap 4vw.
- title col1 row1
- lede col1 row2, margin-bottom 0
- core callout col1 row3, align end, margin-top 0
- steel callout col2 row3, align end, justify end, margin-top 0
Also for this band: hero padding-top 66px, padding-bottom 0, gap 0.28rem 4vw. Title and strong font-size clamp(24px, 4.6vw, 40px). Core, steel, and scroll margin-top 0.
max-height 540px (hero, recipe, essentials)
Inner gap 0.45rem. Padding-top max(58px, safe-area-top + 48px), padding-bottom 16px. Headlines clamp(26px, 6.2vw, 52px). Model margin-top 0. Core callout margin-top 0.8rem.
prefers-reduced-motion: reduce
.rv, .clip > span, and .adv-head .ch transitions become 0.01ms. Anchor scroll uses behavior auto.
OUTPUT CHECK
One complete HTML document. Exact copy, exact CloudFront URLs, exact type, exact positions.
Section 3 check before finishing:
- #adv computed height is 460vh and it has no overflow value other than visible.
- .adv-stage computed position is sticky, top is 0, height is 100vh.
- No ancestor of .adv-stage has overflow hidden, auto, or scroll.
- The three PNGs are stacked in the same centered slot inside the sticky frame, hidden until the video ends, then cross-faded by scroll. They are not three separate page sections.
- The video plays once at 2× and fades out. Scroll does not control video currentTime.
- After the pin finishes, #ess scrolls in normally.
Do not invent a fifth section, do not swap assets, do not restyle the gradient, and do not replace Archivo or Inter.[/hidecontent]