/* ===== AutoCare Ringerike — Pradžios preview ===== */

:root{
  --bg:#060605;
  --bg-2:#0b0b09;
  --fg:#f4efe6;
  --fg-dim:#a89f8f;
  --gold:#b3915a;
  --gold-bright:#c9a870;
  --line:rgba(244,239,230,.16);
  --ease:cubic-bezier(.22,.61,.36,1);
  --serif:'Fraunces', Georgia, serif;
  --sans:'Hanken Grotesk', -apple-system, Helvetica, Arial, sans-serif;
  --gold-rgb:201,168,112;
  --ease-boom:cubic-bezier(.16,1.4,.3,1);
  --display:'Bricolage Grotesque', 'Hanken Grotesk', -apple-system, Arial, sans-serif;
  --headline:'Bricolage Grotesque', 'Hanken Grotesk', -apple-system, Arial, sans-serif;
}

*{box-sizing:border-box; margin:0; padding:0;}
html{background:var(--bg); scroll-behavior:auto;}
body{
  background:var(--bg); color:var(--fg); font-family:var(--sans);
  -webkit-font-smoothing:antialiased; overflow-x:clip;
}
img,video{display:block; max-width:100%;}
a{color:inherit; text-decoration:none;}
button{font:inherit; color:inherit; background:none; border:none; cursor:pointer;}

/* ---------- pill / outline button (Bugatti lesson: hairline, not filled) ---------- */
.btn-pill{
  display:inline-flex; align-items:center; gap:.5rem;
  padding:.95rem 2rem; border:1px solid var(--gold); border-radius:999px;
  font:600 .78rem/1 var(--sans); letter-spacing:.14em; text-transform:uppercase; color:var(--fg);
  transition:background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
  white-space:nowrap;
}
.btn-pill:hover{background:var(--gold); color:#0a0a09; border-color:var(--gold);}

/* ---------- header ---------- */
/* z-index:220, ABOVE .mobile-nav's 200: header establishes its own stacking
   context (position:fixed + z-index), so the hamburger's own z-index:210
   only ever wins against its siblings inside the header -- against the nav
   overlay itself the whole header would lose and sit hidden underneath it,
   which is exactly why the close (X) state wasn't visible once the menu was
   open. Raising the header above the nav fixes that; brand-mark/nav-cta are
   then hidden explicitly (below) while the nav is open instead of relying
   on the header being buried to hide them. */
header.site-header{
  /* grid, not flex+space-between: with 3 items (hamburger, logo, "Kontakt"),
     space-between only centers the middle item when the two side items are
     equal width -- they aren't (44px hamburger vs a wider text link), so the
     logo used to sit visibly left of true center (Jonas noticed it didn't
     line up with the hero text below, which IS true-centered). A 3-column
     grid centers the middle column regardless of the side columns' widths. */
  position:fixed; inset:0 0 auto 0; z-index:220; height:76px;
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
  padding:0 clamp(1.25rem,4vw,3rem);
  background:linear-gradient(to bottom, rgba(6,6,5,.65), transparent);
}
.hamburger{ justify-self:start; }
.brand-mark{ justify-self:center; }
.nav-cta{ justify-self:end; }
.brand-mark{font-family:var(--serif); font-size:1.15rem; letter-spacing:.14em; text-transform:uppercase; transition:opacity .3s var(--ease);}
.nav-cta{font:600 .7rem/1 var(--sans); letter-spacing:.16em; text-transform:uppercase; transition:opacity .3s var(--ease);}
body.nav-open .brand-mark, body.nav-open .nav-cta{opacity:0; pointer-events:none;}

/* ---------- hamburger -> mobile nav (luxury slide/fade, matches hero line-clip rhythm) ---------- */
.hamburger{
  width:44px; height:44px; position:relative;
  display:flex; align-items:center; justify-content:center;
  background:none; border:none; padding:0; cursor:pointer; z-index:210;
}
/* soft circular backdrop that scales in behind the icon on open -- makes the
   close state read as a deliberate control, not just two lines meeting */
.hamburger::before{
  content:''; position:absolute; inset:0; border-radius:50%;
  background:rgba(var(--gold-rgb),.14); opacity:0; transform:scale(.6);
  transition:opacity .4s var(--ease), transform .4s var(--ease-boom);
}
.hamburger.is-open::before{opacity:1; transform:scale(1);}
.hamburger span{
  position:absolute; left:50%; top:50%; width:24px; height:1.5px; background:var(--fg);
  transform:translate(-50%,-50%) translateY(-7px);
  transition:transform .55s var(--ease-boom), opacity .3s var(--ease), width .3s var(--ease);
}
.hamburger span:nth-child(2){transform:translate(-50%,-50%);}
.hamburger span:nth-child(3){transform:translate(-50%,-50%) translateY(7px);}
.hamburger.is-open span:nth-child(1){transform:translate(-50%,-50%) rotate(45deg); width:26px;}
.hamburger.is-open span:nth-child(2){opacity:0; width:0;}
.hamburger.is-open span:nth-child(3){transform:translate(-50%,-50%) rotate(-45deg); width:26px;}

.mobile-nav{
  position:fixed; inset:0; z-index:200; background:rgba(6,6,5,.88); overflow:hidden;
  /* frosted glass, matching .dock-nav's own native-app treatment -- the
     fullscreen menu used to be flat opaque black while the bottom dock
     right below it on mobile was already blurred glass; same surface
     language on both closes that gap ("more app feeling"). */
  backdrop-filter:blur(20px) saturate(140%); -webkit-backdrop-filter:blur(20px) saturate(140%);
  display:flex; flex-direction:column; align-items:flex-start; justify-content:center;
  gap:1.1rem; padding:0 clamp(1.5rem,8vw,4rem);
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .5s var(--ease), visibility .5s var(--ease);
}
.mobile-nav.is-open{opacity:1; visibility:visible; pointer-events:auto;}
/* same warm drifting glow as .hero::after/.scrub::after, dimmer and static
   (no animation while the nav is only open briefly) so the fullscreen menu
   reads as part of the same world instead of a flat modal dropped on top */
.mobile-nav::before{
  content:''; position:absolute; inset:-20%; z-index:0; pointer-events:none;
  opacity:0; filter:blur(30px);
  background:
    radial-gradient(30% 24% at 25% 30%, rgba(var(--gold-rgb),.32), transparent 70%),
    radial-gradient(26% 30% at 78% 70%, rgba(244,239,230,.1), transparent 72%);
  transition:opacity 1s var(--ease) .1s;
}
.mobile-nav.is-open::before{opacity:1;}
.mobile-nav a{
  position:relative; z-index:1;
  display:flex; align-items:baseline; gap:.85rem;
  font-family:var(--headline); font-weight:700; font-size:clamp(1.9rem,7vw,2.7rem);
  color:var(--fg); text-decoration:none; letter-spacing:-.01em;
  opacity:0; filter:blur(6px); transform:translateY(22px) scale(.97);
  transition:opacity .6s var(--ease-boom), transform .6s var(--ease-boom), filter .6s var(--ease), color .25s var(--ease);
}
.mobile-nav a .nav-num{
  font-family:var(--display); font-weight:800; font-size:.85rem; letter-spacing:.04em;
  color:var(--gold-bright); opacity:.85;
}
.mobile-nav.is-open a{opacity:1; filter:blur(0); transform:translateY(0) scale(1);}
/* was only defined for the first 4 of these 8 links -- items 5-8
   (Privatpersoner through Kontakt) fell back to 0s delay, so they popped in
   together with item 1 instead of continuing the cascade: the menu read as
   "staggered, then everything else pops at once" (Jonas: "uneven"). Same
   .07s step, carried through all 8 so the whole list reads as one
   deliberate cascade top to bottom. */
.mobile-nav a:nth-child(1){transition-delay:.06s;}
.mobile-nav a:nth-child(2){transition-delay:.13s;}
.mobile-nav a:nth-child(3){transition-delay:.2s;}
.mobile-nav a:nth-child(4){transition-delay:.27s;}
.mobile-nav a:nth-child(5){transition-delay:.34s;}
.mobile-nav a:nth-child(6){transition-delay:.41s;}
.mobile-nav a:nth-child(7){transition-delay:.48s;}
.mobile-nav a:nth-child(8){transition-delay:.55s;}
.mobile-nav a:hover, .mobile-nav a:active{color:var(--gold-bright);}
.mobile-nav .nav-foot{
  position:absolute; z-index:1; bottom:clamp(2rem,6vh,3rem); left:clamp(1.5rem,8vw,4rem);
  font:600 .68rem/1 var(--sans); letter-spacing:.18em; text-transform:uppercase; color:var(--fg-dim);
  opacity:0; transition:opacity .6s var(--ease) .62s;
}
.mobile-nav.is-open .nav-foot{opacity:1;}
body.nav-open{overflow:hidden;}
@media (prefers-reduced-motion: reduce){
  .hamburger span, .hamburger::before, .mobile-nav, .mobile-nav::before, .mobile-nav a{transition:none;}
}

/* ---------- preloader ---------- */
#preloader{
  position:fixed; inset:0; z-index:1000; background:var(--bg);
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1.25rem;
  transition:opacity .8s var(--ease), visibility .8s var(--ease);
}
#preloader.is-hidden{opacity:0; visibility:hidden; pointer-events:none;}
.pre-mark{font-family:var(--serif); font-size:1.4rem; letter-spacing:.22em; text-transform:uppercase;}
.pre-bar{width:180px; height:1px; background:var(--line); position:relative; overflow:hidden;}
.pre-bar i{position:absolute; inset:0; width:0%; background:var(--gold); transition:width .25s linear;}

/* ---------- hero ---------- */
.hero{position:relative; height:100svh; overflow:hidden; background:var(--bg);}
.hero video{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
/* slow continuous zoom breathe on the desktop hero -- keeps it feeling alive
   once the clip ends and holds on its last frame (Jonas: "make it breathe...
   enough to notice its alive"). Scale never dips below 1 so cover never
   reveals an edge. */
@keyframes heroBreathe{
  0%,100%{ transform:scale(1); }
  50%{ transform:scale(1.09); }
}
.hero-video, .hero-video-mobile{ animation:heroBreathe 5.5s ease-in-out infinite; }
/* mobile: the video fades out into this still during its hold phase (see
   setupMobileHeroVideo in app.js) -- wrapped in its own div so the breathe
   animation's transform doesn't fight the GSAP scroll-scrub transform that
   already runs on .hero-still itself; the two compose instead of conflict. */
.hero-still-wrap{ position:absolute; inset:0; animation:heroBreathe 5.5s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce){
  .hero-video, .hero-video-mobile, .hero-still-wrap{ animation:none; }
}
/* Mobile stills (hero + the three scrub sections): these are true 9:16 portrait
   source images (AI-generated off the same reference car/showroom, not cropped from
   the 16:9 desktop footage), so plain object-fit:cover is all that's needed -- no
   letterbox math required. will-change:transform because setupMobileStillMotion()
   (app.js) applies a slow scroll-tied scale for a subtle "still isn't dead" push-in;
   safe to scale UP from cover's resting state since that only crops in further, never
   reveals an edge. */
.hero-still{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:none; will-change:transform;}
/* still-scrim: real photos, not a dark motion-blurred loop -- a caption can land
   on a bright/busy patch (chrome, wet highlights) with nothing guaranteeing
   contrast. One gradient div per section, matched to whichever edge that
   section's .beat sits on, painted above the still but below the text
   (z-index 2 vs .beat's 7). Global now -- every platform shows a real photo. */
.still-scrim{ position:absolute; inset:0; z-index:2; pointer-events:none; }
.still-scrim.scrim-bottom{ background:linear-gradient(to top, rgba(6,6,5,.82) 0%, rgba(6,6,5,.4) 30%, rgba(6,6,5,0) 60%); }
.still-scrim.scrim-top{ background:linear-gradient(to bottom, rgba(6,6,5,.78) 0%, rgba(6,6,5,.35) 28%, rgba(6,6,5,0) 56%); }
/* shown on every platform now -- desktop's canvas frame-sequence is gone, both
   platforms show one static shot per scrub section (picked by <picture>'s own
   media query in the HTML) with the same scroll-scrubbed captions over it. */
.section-still{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; will-change:transform;}
/* mobile-only entrance clip (dark-to-light reveal) -- sits above .hero-still,
   autoplays once and holds on its own last frame (lights lit) -- no more
   fade-to-still/replay cycle. Never shown on desktop. */
.hero-video-mobile{position:absolute; inset:0; z-index:1; width:100%; height:100%; object-fit:cover; display:none;}
/* mobile-only dissolve-from-black per scrub section, see setupStaticSection() */
.mobile-reveal-cover{position:absolute; inset:0; z-index:6; background:var(--bg); opacity:1; pointer-events:none; display:block;}
.hero .scrim{
  position:absolute; inset:0;
  background:linear-gradient(to bottom, rgba(6,6,5,.6) 0%, rgba(6,6,5,.05) 26%, rgba(6,6,5,.05) 62%, rgba(6,6,5,.78) 100%);
}
.hero-content{
  /* text pinned to the top, CTA pinned to the bottom, car left unobstructed
     in the middle -- reads as a premium/automotive hero layout rather than
     one text block stacked over the footage (Jonas: "car is in focus...
     feels much more luxurious"). */
  position:relative; z-index:2; height:100%;
  display:flex; flex-direction:column; justify-content:space-between; align-items:center;
  text-align:center;
  padding:clamp(3.5rem,11vh,6.5rem) clamp(1.25rem,4vw,3rem) clamp(3.5rem,10vh,6rem);
  max-width:620px; margin:0 auto;
}
.hero-text-top{ display:flex; flex-direction:column; align-items:center; }
.hero-title{font-family:var(--display); font-weight:900; font-size:clamp(2.2rem,4vw,3.4rem); line-height:1.08; letter-spacing:-.02em; margin-bottom:1.25rem;}
/* cycling typewriter headline (Jonas ref: cinematic-site-components/typewriter.html)
   -- each phrase types in, holds, deletes, and the next one cycles in; the
   final word of whatever's currently typed always reads in the brand gold,
   never the whole line, so the emphasis moves with the phrase instead of
   sitting static on one line like the old 4-line reveal did. Driven by
   setupHeroTypewriter() in app.js.
   ROOT CAUSE of the old "line jumps to next row" bug: .line-clip span{
   display:block} (further down, for the entrance-reveal system) matches
   ANY span inside .line-clip -- including the dynamically-injected
   .type-word and .type-cursor spans this typewriter creates. A block-level
   element sitting inside inline nowrap text always forces a line break
   immediately before AND after itself, no matter what white-space says --
   that's why the last word (and the cursor right after it) kept landing on
   their own row. Fixed by giving .type-word and .type-cursor their own
   higher-priority display overrides below, so they win that cascade too
   (the same fix .hero-type itself already had). white-space stays
   !important for the same reason: an easy target for future span-inside-
   .line-clip rules to accidentally re-break.
   font-size is a FIXED clamp (not computed per-phrase at runtime -- that
   was tried three ways and each broke differently), hand-verified via
   canvas text measurement to fit the longest of the four phrases, in this
   exact font/weight, at every viewport width from 320px to 2560px, with
   margin to spare. Now that the real display:block bug above is fixed,
   this can run close to full headline size instead of the tiny defensive
   size used while the wrap bug was still misdiagnosed. */
.hero-type{
  white-space:nowrap !important; display:inline-block !important;
  font-size:clamp(.95rem,4.4vw,2.4rem); letter-spacing:-.01em;
}
.hero-type .type-word{ color:var(--gold-bright); display:inline !important; }
.type-cursor{ display:inline-block !important; width:2px; height:.85em; margin-left:.05em; background:var(--gold-bright); vertical-align:-.05em; animation:typeBlink .85s step-end infinite; }
@keyframes typeBlink{ 50%{ opacity:0; } }
.sr-only{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
.line-clip{display:block; overflow:hidden;}
.line-clip span{display:block; will-change:transform;}
.hero-sub{font-size:.9rem; color:var(--fg-dim); max-width:30ch; margin:0 auto; line-height:1.5;}
/* hero's own CTA reads bigger/bolder than the pill used elsewhere (split
   cards) -- it's the one action on the whole hero, so it gets to be the
   most prominent element on screen besides the car itself. */
.hero-content .btn-pill--hero{
  padding:1.05rem 2.4rem; font-size:.82rem; letter-spacing:.15em;
  animation:heroBtnPulse 3.2s ease-in-out infinite;
}
.hero-content .btn-pill--hero:hover{ animation-play-state:paused; }
@keyframes heroBtnPulse{
  0%,100%{ box-shadow:0 0 0 0 rgba(var(--gold-rgb),.32); }
  50%{ box-shadow:0 0 22px 7px rgba(var(--gold-rgb),.32); }
}
/* subtle ambient life in the headline block itself -- once the video settles
   on its last frame this keeps the hero from reading as a dead still. Applied
   to the wrapper, not the individual spans, so it never fights the entrance
   transition those already run (see .hero-content .line-clip span below). */
@keyframes heroAmbientFloat{
  0%,100%{ transform:translateY(0) scale(1); opacity:1; }
  50%{ transform:translateY(-13px) scale(1.015); opacity:.94; }
}
.hero-text-top{ animation:heroAmbientFloat 5s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce){
  .hero-content .btn-pill--hero{ animation:none; }
  .hero-text-top{ animation:none; }
  .type-cursor{ animation:none; opacity:0; }
}
/* desktop: back to centered and grouped together in the vertical middle
   (Jonas: "center the hero and make it smaller, button a bit higher in
   center") -- smaller title, text+CTA grouped with a gap instead of
   stretched via space-between, so the button sits close under the text
   instead of pinned to the very bottom edge. */
@media (min-width:761px){
  .hero-content{
    align-items:center; text-align:center; justify-content:space-between;
    gap:2rem; max-width:560px; margin:0 auto;
    padding-top:clamp(6rem,14vh,8rem);
    padding-bottom:clamp(7rem,16vh,9.5rem);
  }
  .hero-text-top{ align-items:center; }
  .hero-title{ font-size:clamp(1.9rem,2.6vw,2.6rem); }
  .hero-sub{ margin:0 auto; }
}

/* hero entrance — plain CSS transition driven by one class swap (see
   setupHeroEntrance in app.js). Deliberately NOT GSAP: a .from() tween on
   this exact markup has twice frozen at its hidden state in production. */
.hero-content .hero-sub,
.hero-content .btn-pill,
.hero-content .line-clip span{
  opacity:0; filter:blur(8px); transform:translateY(34px) scale(.97);
  transition:opacity 1.1s var(--ease), transform 1.1s var(--ease-boom), filter 1.1s var(--ease);
}
.hero-content .line-clip:nth-child(1) span{transition-delay:.22s;}
.hero-content .line-clip:nth-child(2) span{transition-delay:.32s;}
.hero-content .line-clip:nth-child(3) span{transition-delay:.42s;}
.hero-content .line-clip:nth-child(4) span{transition-delay:.52s;}
.hero-content .hero-sub{transition-delay:.68s;}
.hero-content .btn-pill{transition-delay:.8s;}
.hero-content.is-ready .hero-sub,
.hero-content.is-ready .btn-pill,
.hero-content.is-ready .line-clip span{
  opacity:1; filter:blur(0); transform:translateY(0) scale(1);
}
@media (prefers-reduced-motion: reduce){
  .hero-content .hero-sub, .hero-content .btn-pill, .hero-content .line-clip span{
    transition:none; opacity:1; transform:none;
  }
}
.scroll-cue{position:absolute; left:50%; bottom:2rem; transform:translateX(-50%); z-index:2;}
.scroll-cue i{display:block; width:1px; height:46px; background:var(--line); position:relative; overflow:hidden;}
.scroll-cue i::after{content:''; position:absolute; top:-100%; left:0; right:0; height:100%; background:var(--gold); animation:cueDrop 2.2s ease-in-out infinite;}
@keyframes cueDrop{0%{top:-100%;} 60%{top:100%;} 100%{top:100%;}}

/* ---------- pinned scrub sections ---------- */
.scrub{position:relative; height:100svh; overflow:hidden; background:var(--bg);}
.scrub canvas{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;}
.scrub .beat{
  /* above .mobile-reveal-cover's z-index:6 -- a caption can now start
     fading in while the section is still mostly covered/black without
     ever looking muddied through the cover, since it's never blended
     THROUGH it, just drawn on top of whatever is beneath (black, or the
     image once revealed). That's what lets a beat come in earlier on the
     scroll on mobile. */
  position:absolute; left:clamp(1.25rem,4vw,3rem); bottom:clamp(3rem,10vh,6rem); z-index:7;
  font-family:var(--headline); font-weight:800; font-size:clamp(2.4rem,6.5vw,5.2rem); color:var(--fg);
  max-width:16ch; line-height:1.18; letter-spacing:-.02em;
  text-shadow:0 2px 18px rgba(0,0,0,.55);
  filter:blur(10px); opacity:0; transform:scale(.9) translateY(22px);
  transition:filter .9s var(--ease), opacity .6s var(--ease), transform .9s var(--ease-boom);
}
.scrub .beat.is-visible{filter:blur(0); opacity:1; transform:scale(1) translateY(0);}

/* seam-cover: a plain fade-to-black dissolve between scrub sections --
   replaces the earlier curtain wipe. Simpler, and reads as one continuous
   piece of film rather than a series of "effects". */
/* Starts fully OPAQUE (hidden/black) at rest -- a section must not be
   visible before the visitor actually scrolls into it. JS only starts
   driving this element's opacity once its own ScrollTrigger becomes
   active; leaving the CSS default at 0 meant the canvas underneath (which
   draws its first frame immediately on load, by design) was visible the
   whole time the visitor was still scrolling through earlier sections,
   then popped to black the instant this section's scroll range began --
   a flash instead of a fade. Starting at 1 and letting JS fade it DOWN as
   the visitor scrolls in fixes both: hidden the whole approach, then a
   real fade from black. */
.seam-cover{
  position:absolute; inset:0; z-index:4; background:var(--bg); opacity:1;
  pointer-events:none; will-change:opacity;
}

/* mist-veil: only used on the very last scrub section (section3b), which no
   longer dissolves to black on exit. Instead of a hard cut into the content
   sections below, a soft atmospheric haze rises from the bottom as the
   visitor finishes scrolling -- reads as fog/cloud, not a curtain, and it
   sits BELOW the beat text (z-index 2 vs 3) so the headline stays legible
   the whole time it's held. JS drives its opacity over the same window the
   old cover used, so this is a like-for-like swap: dissolve -> haze. */
.mist-veil{
  position:absolute; inset:0; z-index:2; opacity:0; pointer-events:none;
  will-change:opacity;
  /* actual haze, not a flat dark fade -- the gradient colors are lighter/
     warmer than the surrounding footage so they read as visible atmosphere
     (mist/cloud) instead of just disappearing into the matching --bg. The
     radial blobs are pulled back from the very bottom edge (transparent by
     ~85% of their own reach) so the last ~15-18% of the section is
     GUARANTEED flat var(--bg) with no residual tint -- that flat band is
     what actually kills the seam line, not the haze itself. The mirrored
     recipe at the top of .why-strip below leaves the same flat band there,
     so both sides of the boundary render the literal same solid color. */
  background:
    radial-gradient(58% 34% at 22% 52%, rgba(var(--gold-rgb),.30) 0%, transparent 88%),
    radial-gradient(50% 30% at 76% 58%, rgba(200,196,188,.22) 0%, transparent 88%),
    linear-gradient(to top, var(--bg) 0%, rgba(11,11,9,.5) 55%, transparent 88%);
  filter:blur(16px);
}

/* ---------- content sections (one-shot reveal on scroll-into-view) ---------- */
[data-motion]:not([data-motion="text"]){opacity:0; filter:blur(8px); transform:translateY(28px);}
[data-motion]:not([data-motion="text"])[data-motion-state="visible"]{
  opacity:1; filter:blur(0); transform:translateY(0);
  transition:opacity 1s var(--ease), filter 1s var(--ease), transform 1s var(--ease);
}
[data-motion-order="1"]{transition-delay:.08s;}
[data-motion-order="2"]{transition-delay:.18s;}
[data-motion-order="3"]{transition-delay:.28s;}

section.content{padding:clamp(5rem,12vh,8rem) clamp(1.25rem,4vw,3rem); position:relative;}
/* the marquee band already reads as a section break on its own (borders + bg-2 tint) --
   stacking the standard full top padding on top of that on the section right after it
   doubled the black gap and read as broken/empty space rather than intentional breathing
   room. Halve just this one seam's top padding. */
.marquee-section + section.content{ padding-top:clamp(2.5rem,6vh,4rem); }
.section-inner{max-width:1180px; margin:0 auto;}

/* echoes .mist-veil's haze (same tones, mirrored) at the top of the section
   that follows the last scrub -- makes the atmosphere read as one continuous
   thing crossing the section boundary, rather than a hard cut into flat bg */
.why-strip{ position:relative; }
.why-strip::after{
  content:''; position:absolute; top:0; left:0; right:0; height:50%; z-index:0;
  pointer-events:none;
  /* mirrors .mist-veil: radials pulled back so the top ~15-18% of THIS
     element is flat var(--bg) too -- same guaranteed-flat band meeting the
     one at the bottom of the scrub section above means the boundary itself
     is one continuous solid color, and the haze blooms a little further
     down/up from it rather than exactly on the seam. */
  background:
    radial-gradient(56% 40% at 22% 58%, rgba(var(--gold-rgb),.20) 0%, transparent 88%),
    radial-gradient(48% 36% at 76% 62%, rgba(200,196,188,.14) 0%, transparent 88%),
    linear-gradient(to bottom, var(--bg) 0%, transparent 100%);
  filter:blur(22px);
}

/* big cinematic statement -- bold grotesk, not the delicate serif, so it
   carries the same weight as the hero title instead of reading as a soft
   corporate mission line */
.why-strip h2{
  font-family:var(--headline); font-weight:800; letter-spacing:-.01em;
  font-size:clamp(2.1rem,5vw,3.6rem); line-height:1.05; max-width:16ch; margin-bottom:3.5rem;
}

/* editorial numbered rows instead of a 3-up feature grid -- full-width,
   stacked, each row a beat, mirroring the hente/vask/lever structure of the
   scrub sections above so the page recaps its own story instead of
   repeating it as a generic "benefits" tile row */
.process-list{ border-top:1px solid var(--line); }
.process-row{
  display:grid; grid-template-columns:80px 220px 1fr; align-items:baseline;
  gap:1.5rem; padding:2.25rem 0; border-bottom:1px solid var(--line);
  transition:padding-left .4s var(--ease);
}
.process-row:hover{ padding-left:.75rem; }
.process-num{ font:800 1.05rem/1 var(--display); color:var(--gold-bright); letter-spacing:.02em; }
.process-row h3{ font:700 1.4rem/1 var(--headline); letter-spacing:-.01em; color:var(--fg); }
.process-row p{ color:var(--fg-dim); line-height:1.55; font-size:.95rem; max-width:38ch; }

/* closing line folds the trust facts into one signed statement instead of a
   row of badge chips -- the accent clause reuses the site's own refrain
   (hero + final beat) so the page closes on a line it already said */
.process-signature{
  margin-top:2.75rem; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.15rem,2.2vw,1.5rem); color:var(--fg-dim); line-height:1.5;
}
.process-signature .accent{ color:var(--fg); font-weight:500; }

.split-grid{display:grid; grid-template-columns:1fr 1fr; gap:1.5rem;}

/* ---------- new homepage sections (Bestilling / Om oss / FAQ) -- filling out
   the client's original 8-page nav (Hjem, Tjenester, Privatpersoner,
   Bedrifter, Bestilling, Om oss, Kontakt, FAQ) as homepage snapshot sections
   for this v1 single-page build, rather than 8 separate pages. Reuses the
   existing .why-strip h2 / .process-list / .process-signature components
   wherever possible instead of inventing new ones. ---------- */
.section-statement{
  font-family:var(--headline); font-weight:800; letter-spacing:-.01em;
  font-size:clamp(2.1rem,5vw,3.6rem); line-height:1.05; max-width:16ch; margin-bottom:3.5rem;
}
.section-lede{ color:var(--fg-dim); font-size:1.05rem; line-height:1.65; max-width:56ch; }

/* ---------- Bestilling: wide sticky card stack (ported from the reference
   component library's "Sticky Card Stack" module), replacing the old 3-up
   spotlight-border grid -- Jonas: the 3-across grid read as "snapped to a
   grid, like AI"; wanted these much wider and the section itself more
   editorial rather than boxed into the site's standard 1180px column.
   Mechanic: each card position:sticky's near the top of the viewport with
   a staggered offset (clears the 76px fixed header), so as you scroll the
   next card slides up and physically covers the last one -- GSAP scales
   and fades each card down slightly right as the next one arrives
   (setupStickyCards in app.js), reinforcing the "cards being laid down"
   feel instead of a flat swap. Restrained on colour per the Bugatti-
   reference lesson already applied site-wide -- no rainbow card palette,
   just the same --bg-2 surface with the gold accent intensifying 01->03 to
   read as "getting closer to done" instead of decoration for its own sake. */
.section-inner--wide{ max-width:1440px; }
/* #bestilling needs sticky children (.stack-card below) to actually pin to
   the viewport as the page scrolls. section.content further down sets
   overflow:hidden on every .content section purely to clip its own ::before
   ambient glow blob -- but overflow:hidden on ANY ancestor between a sticky
   element and the real scrolling viewport silences position:sticky entirely
   (confirmed live: without this, the cards just scrolled past like normal
   flow content instead of stacking). ID beats the type+class selector, so
   this override wins without !important. Pulled the glow's own bleed margin
   in to compensate, so removing the clip here doesn't let it wash into the
   sections above/below. */
#bestilling{ overflow:visible; }
/* inset:0 (not the -6%/-4% bleed every other .content::before glow uses) --
   any negative inset here becomes real, scrollable page overflow the moment
   this section can't clip its own contents (needed above for sticky), and
   at mobile widths that showed up exactly as reported: extra blank space
   you could scroll sideways into. (Tried adding overflow-x:clip here as a
   belt-and-suspenders backstop -- reverted: per the CSS overflow spec, an
   element with overflow-x set to anything other than visible silently
   forces overflow-y from visible to auto, which would turn this section
   back into its own scroll container and break the sticky cards all over
   again. inset:0 removes the actual overflow instead, so no backstop is
   needed.) */
#bestilling::before{ inset:0; }
.stack-cards{ margin-bottom:2.5rem; }
.stack-card{
  position:sticky;
  display:grid; grid-template-columns:auto 1fr; align-items:end; gap:clamp(1.5rem,4vw,3.5rem);
  background:var(--bg-2); border:1px solid var(--line); border-radius:20px;
  padding:clamp(2.5rem,5vw,4.5rem) clamp(2rem,4.5vw,3.75rem);
  min-height:clamp(280px,42vh,420px);
  margin-bottom:1.5rem;
  box-shadow:0 30px 70px rgba(0,0,0,.4);
}
/* Pure CSS sticky -- the reference module's own core effect needs no JS at
   all (a GSAP scale/fade tied to the next card's scroll position was tried
   here first, but ScrollTrigger reads a sticky element's live rect, which
   stops updating the instant it's pinned -- caused the fade to stall
   mid-transition and two cards' text to ghost through each other at once).
   A wide (60px) step between each card's `top` gives the previous card a
   generous, clearly-legible peek strip above the next one instead of a
   sliver, and opaque .stack-card backgrounds do the rest: each card simply
   covers the last as its own sticky point is reached. */
.stack-card:nth-child(1){ top:96px; z-index:1; }
.stack-card:nth-child(2){ top:156px; z-index:2; border-color:rgba(var(--gold-rgb),.28); }
.stack-card:nth-child(3){ top:216px; z-index:3; border-color:rgba(var(--gold-rgb),.5); margin-bottom:0; }
.stack-card-num{
  font:800 clamp(2.4rem,6vw,4.2rem)/1 var(--display); letter-spacing:-.01em;
  color:rgba(244,239,230,.12);
}
.stack-card:nth-child(2) .stack-card-num{ color:rgba(var(--gold-rgb),.35); }
.stack-card:nth-child(3) .stack-card-num{ color:rgba(var(--gold-rgb),.6); }
.stack-card-body h3{
  font:700 clamp(1.4rem,2.6vw,2rem)/1.1 var(--headline); letter-spacing:-.01em;
  color:var(--fg); margin-bottom:.6rem;
}
.stack-card-body p{ color:var(--fg-dim); font-size:clamp(.95rem,1.3vw,1.1rem); line-height:1.6; max-width:52ch; }
@media (max-width:760px){
  .stack-card{
    grid-template-columns:1fr; align-items:start; gap:1rem;
    padding:1.75rem 1.5rem; min-height:0;
  }
  .stack-card:nth-child(1){ top:84px; }
  .stack-card:nth-child(2){ top:104px; }
  .stack-card:nth-child(3){ top:124px; }
  .stack-card-num{ font-size:1.8rem; }
}

/* ---------- Bestilling: a real form. No backend yet, so submit composes a
   mailto: with the fields folded in (see setupBookingForm in app.js) -- a
   genuine form the visitor fills in, not a fake placeholder link. ---------- */
.booking-form{ display:flex; flex-direction:column; gap:1.25rem; max-width:640px; margin:0 auto; }
.form-row{ display:grid; grid-template-columns:1fr 1fr; gap:1.25rem; }
.booking-form label{
  display:flex; flex-direction:column; gap:.5rem; font-size:.72rem;
  letter-spacing:.1em; text-transform:uppercase; color:var(--fg-dim);
}
.booking-form input, .booking-form select, .booking-form textarea{
  font:400 .95rem/1.4 var(--sans); color:var(--fg); background:transparent;
  border:1px solid var(--line); border-radius:8px; padding:.85rem 1rem;
  transition:border-color .25s var(--ease);
}
.booking-form input:focus, .booking-form select:focus, .booking-form textarea:focus{
  outline:none; border-color:var(--gold);
}
.booking-form select{ appearance:none; }
.booking-form textarea{ resize:vertical; min-height:80px; }
.form-full{ grid-column:1/-1; }
@media (max-width:600px){ .form-row{ grid-template-columns:1fr; } }

/* ---------- Bestilling: inline "thank you" state, swapped in over the form
   itself on submit (setupBookingForm in app.js) instead of a page reload or
   a plain browser alert -- booking is the one action-oriented moment on an
   otherwise editorial page, so it gets its own deliberate, on-brand
   confirmation rather than defaulting to whatever the browser gives you.
   Both states live in the same fixed-position wrapper so the swap doesn't
   jump the page's scroll position. ---------- */
.booking-form-wrap{ position:relative; max-width:640px; margin:0 auto; }
/* A straight swap, not an absolute-positioned cross-fade: overlapping them
   needs the wrapper to hold a fixed height for both states at once, and
   with the form and a short success message being very different heights,
   that either clips one of them or leaves a reserved gap. Simplest robust
   fix -- .booking-form is display:none while .is-success is set (JS also
   clears the success panel's own `hidden` attribute at the same time,
   since [hidden] forces display:none and no opacity transition can
   override that) -- and the success panel still gets its own fade/blur-in
   so the swap doesn't feel like a hard cut. */
.booking-form-wrap.is-success .booking-form{ display:none; }
.booking-success{
  text-align:center; padding:clamp(2.5rem,6vh,3.5rem) clamp(1.5rem,4vw,2.5rem);
  border:1px solid rgba(var(--gold-rgb),.35); border-radius:20px; background:var(--bg-2);
  opacity:0; filter:blur(6px); transform:translateY(10px);
}
.booking-form-wrap.is-success .booking-success{
  opacity:1; filter:blur(0); transform:translateY(0);
  transition:opacity .5s var(--ease) .1s, filter .5s var(--ease) .1s, transform .5s var(--ease) .1s;
}
.booking-success-icon{
  display:flex; align-items:center; justify-content:center; margin:0 auto 1.25rem;
  width:52px; height:52px; border-radius:999px; color:var(--gold-bright);
  border:1px solid rgba(var(--gold-rgb),.4); background:rgba(var(--gold-rgb),.08);
}
.booking-success-icon svg{ width:24px; height:24px; }
.booking-success h3{ font:700 1.4rem/1.2 var(--headline); letter-spacing:-.01em; color:var(--fg); margin-bottom:.75rem; }
.booking-success p{ color:var(--fg-dim); font-size:.95rem; line-height:1.6; max-width:42ch; margin:0 auto 1.75rem; }
.booking-success-mailto{
  display:inline-block; font:600 .82rem/1 var(--sans); color:var(--gold-bright);
  text-decoration:underline; text-underline-offset:3px; text-decoration-color:rgba(var(--gold-rgb),.5);
  margin:-1rem auto 1.5rem; transition:text-decoration-color .25s var(--ease);
}
.booking-success-mailto:hover{ text-decoration-color:var(--gold-bright); }
.booking-success-reset{
  font:600 .72rem/1 var(--sans); letter-spacing:.12em; text-transform:uppercase;
  color:var(--fg-dim); background:none; border:0; padding:.5rem; cursor:pointer;
  transition:color .25s var(--ease);
}
.booking-success-reset:hover{ color:var(--gold-bright); }

/* ---------- kinetic marquee: ambient scrolling service band between
   Bestilling and the Privat/Bedrift split (ported from the reference
   library's "Kinetic Marquee" module) -- a spec-band device real
   automotive sites use, gives the page a moment of pure ambient motion. ---------- */
.marquee-section{
  overflow:hidden; padding:2.25rem 0; background:var(--bg-2);
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  /* fade the band's own edges to transparent -- the thing that reads as
     "premium" instead of a hard-cut ticker tape (Jonas: marquee looked
     cheap/broken -- this plus the wider 6-word loop below fixes it). */
  -webkit-mask-image:linear-gradient(to right, transparent 0, #000 8%, #000 92%, transparent 100%);
  mask-image:linear-gradient(to right, transparent 0, #000 8%, #000 92%, transparent 100%);
}
.marquee-row{
  display:flex; white-space:nowrap; will-change:transform; width:max-content;
  /* 36s, not the old 28s -- the giant-display type below is a much wider
     band per loop than the old small uppercase label text was, and the
     @keyframes below travels 50% of the row's own (now much larger) width
     regardless of font-size, so the old duration read as noticeably faster
     once the text got bigger. Slowed to keep the same calm, ambient pace. */
  animation:marqueeScroll 30s linear infinite;
}
@keyframes marqueeScroll{ from{ transform:translateX(0); } to{ transform:translateX(-25%); } }
@media (prefers-reduced-motion:reduce){ .marquee-row{ animation:none; } }
/* Jonas ref: cinematic-site-components/kinetic-marquee.html, "giant display"
   style, dialed back to a discreet size per Jonas (the first pass was too
   big) -- still the bold display-font treatment instead of the original
   tiny uppercase label, just modest instead of dominant. Kept the reliable
   CSS-only @keyframes engine above rather than the reference's scroll-
   velocity-reactive JS loop -- see the animation comment for why. */
.marquee-item{
  display:inline-flex; align-items:center; padding:0 2rem; flex-shrink:0;
  font:700 clamp(1rem,2.4vw,1.5rem)/1 var(--display); letter-spacing:-.01em; text-transform:uppercase; color:var(--fg);
}
.marquee-dot{ padding:0 .1rem; color:var(--gold-bright); }
.marquee-dot svg{ width:10px; height:10px; }

/* ---------- Om oss: odometer-style stat row (ported from the reference
   library's "Odometer Counter" module) -- reuses facts already stated
   elsewhere on the page (insured transport, 2-minute booking, etc.)
   rather than inventing new claims. ---------- */
.stat-row{ display:grid; grid-template-columns:repeat(3,1fr); gap:1rem; margin:2.75rem 0 2.5rem; }
.stat-item{ text-align:center; padding:1.75rem 1rem; border:1px solid var(--line); border-radius:14px; }
.stat-num{ display:flex; align-items:center; justify-content:center; font-family:var(--display); font-weight:800; font-size:clamp(1.8rem,4.5vw,2.6rem); color:var(--gold-bright); letter-spacing:-.02em; }
.stat-odo{ display:inline-flex; height:1.1em; overflow:hidden; }
.odo-digit{ display:inline-block; overflow:hidden; height:1.1em; }
.odo-strip{ display:flex; flex-direction:column; }
.odo-strip span{ display:block; height:1.1em; line-height:1.1; }
.stat-suffix{ margin-left:.15em; }
.stat-label{ margin-top:.55rem; color:var(--fg-dim); font-size:.82rem; }
@media (max-width:600px){ .stat-row{ grid-template-columns:1fr; } }

/* ---------- FAQ: click-to-expand accordion (ported from the reference
   library's accordion mechanic, simplified to plain text rows) instead of
   a static Q&A list -- distinct interaction from every other section. ---------- */
.faq-list{ border-top:1px solid var(--line); max-width:760px; margin:0 auto; }
.faq-item{ border-bottom:1px solid var(--line); }
.faq-q{
  width:100%; display:flex; align-items:center; justify-content:space-between; gap:1rem;
  padding:1.5rem 0; text-align:left; font:700 1.1rem/1.4 var(--headline); letter-spacing:-.01em; color:var(--fg);
}
.faq-plus{ position:relative; width:16px; height:16px; flex:none; }
.faq-plus::before, .faq-plus::after{
  content:''; position:absolute; background:var(--gold-bright); transition:transform .3s var(--ease);
}
.faq-plus::before{ top:50%; left:0; width:100%; height:1px; margin-top:-.5px; }
.faq-plus::after{ left:50%; top:0; height:100%; width:1px; margin-left:-.5px; }
.faq-item.is-open .faq-plus::after{ transform:rotate(90deg); opacity:0; }
.faq-a{ display:grid; grid-template-rows:0fr; transition:grid-template-rows .4s var(--ease); }
.faq-a > p{ overflow:hidden; min-height:0; color:var(--fg-dim); font-size:.92rem; line-height:1.6; }
.faq-item.is-open .faq-a{ grid-template-rows:1fr; }
.faq-a > p{ padding-bottom:0; }
.faq-item.is-open .faq-a > p{ padding-bottom:1.5rem; }

/* ---------- mobile-only persistent bottom dock (ported from the reference
   library's "macOS Dock Nav" module, stripped of its cursor-magnify
   interaction since touch has no hover) -- gives the phone experience a
   native-app quick-actions bar alongside the fullscreen hamburger menu.
   Desktop keeps just the header; this never shows there. ---------- */
.dock-nav{
  position:fixed; left:50%; bottom:1.1rem; transform:translateX(-50%); z-index:150;
  display:flex; align-items:center; gap:.35rem; padding:.5rem .55rem; border-radius:999px;
  background:rgba(11,11,9,.72); backdrop-filter:blur(16px) saturate(140%);
  border:1px solid rgba(244,239,230,.12);
  transition:transform .4s var(--ease), opacity .4s var(--ease);
}
.dock-nav.is-hidden{ transform:translateX(-50%) translateY(140%); opacity:0; }
.dock-item{
  width:46px; height:46px; border-radius:999px; display:flex; align-items:center; justify-content:center;
  color:var(--fg-dim); transition:color .25s var(--ease), background .25s var(--ease), transform .15s var(--ease);
}
.dock-item svg{ width:20px; height:20px; }
.dock-item:active{ transform:scale(.88); }
.dock-item--cta{ background:var(--gold); color:#0a0a09; }
@media (min-width:761px){ .dock-nav{ display:none; } }
@media (prefers-reduced-motion: reduce){ .dock-nav{ transition:none; } }
.split-card{
  position:relative; overflow:hidden; isolation:isolate;
  min-height:440px; padding:0;
  display:flex; flex-direction:column; justify-content:flex-end;
  background-color:var(--bg-2); background-size:cover; background-position:center;
  box-shadow:inset 0 0 0 1px rgba(244,239,230,.06);
  transition:transform .55s var(--ease), box-shadow .55s var(--ease);
}
.split-card::before{
  content:""; position:absolute; inset:0; z-index:0;
  background:linear-gradient(180deg, rgba(6,6,5,.15) 0%, rgba(6,6,5,.6) 58%, rgba(6,6,5,.97) 100%);
}
.split-card::after{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  box-shadow:inset 0 0 60px 10px rgba(0,0,0,.45);
}
.split-tag{
  position:absolute; top:1.75rem; left:1.75rem; z-index:1;
  font:800 .85rem/1 var(--display); color:var(--gold-bright); letter-spacing:.02em;
}
.split-body{position:relative; z-index:1; padding:2.5rem; display:flex; flex-direction:column; gap:1rem;}
.split-card h3{font-family:var(--headline); font-weight:700; font-size:1.85rem; color:var(--fg);}
.split-card p{color:var(--fg-dim); font-size:.92rem; line-height:1.55; max-width:32ch;}
.split-card:hover{transform:translateY(-6px); box-shadow:inset 0 0 0 1px rgba(var(--gold-rgb),.45);}
@media (hover:none){ .split-card:hover{transform:none;} }

.cta-close{text-align:center; padding:clamp(6rem,16vh,10rem) 1.5rem;}
.cta-close h2{
  font-family:var(--headline); font-weight:800; letter-spacing:-.01em;
  font-size:clamp(2.4rem,6vw,4.2rem); line-height:1.05; max-width:16ch; margin:0 auto 2.75rem;
}

/* editorial text-link close, not another pill button -- the pill is used
   everywhere else on the page (nav, cards), so the one moment meant to feel
   like a considered full-stop gets its own, quieter treatment: a plain bold
   line with an animated underline and a arrow that nudges on hover */
.btn-cta-final{
  position:relative; display:inline-flex; align-items:center; gap:.6rem;
  font:700 1.15rem/1 var(--display); letter-spacing:-.01em; color:var(--fg);
  text-decoration:none; padding-bottom:.35rem;
}
.btn-cta-final::after{
  content:''; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:var(--gold-bright); transform:scaleX(1); transform-origin:left;
  transition:transform .45s var(--ease);
}
.btn-cta-final .arrow{
  color:var(--gold-bright); font-weight:400; display:inline-block;
  transition:transform .45s var(--ease);
}
.btn-cta-final:hover .arrow{ transform:translateX(5px); }
.btn-cta-final:hover::after{ transform:scaleX(.4); transform-origin:right; }
#magnetic-cta{position:relative;}

footer.site-footer{
  border-top:1px solid var(--line); padding:2.5rem clamp(1.25rem,4vw,3rem);
  display:flex; justify-content:space-between; align-items:center; color:var(--fg-dim); font-size:.8rem;
}

/* ---------- custom cursor (desktop, fine pointer only) ---------- */
.cursor-dot,.cursor-ring{position:fixed; top:0; left:0; pointer-events:none; z-index:9999; border-radius:50%; transform:translate(-50%,-50%);}
.cursor-dot{width:6px; height:6px; background:var(--gold); }
.cursor-ring{width:32px; height:32px; border:1px solid var(--line); transition:transform .12s linear;}
@media (hover:none), (pointer:coarse){ .cursor-dot,.cursor-ring{display:none;} }

/* ---------- mobile ---------- */
@media (max-width: 760px){
  /* hero + section2/3a/3b: swap the ambient loop / scroll-scrubbed frame sequence
     for a single still on mobile -- video stays a desktop-only thing. Videos/canvases
     never even load below this breakpoint (app.js). seam-cover/mist-veil only ever get
     driven by the desktop pinned-scroll ScrollTrigger, so without it they'd sit at
     their default opacity (seam-cover defaults to opaque) and blank the section --
     hide both here. */
  .hero-video{ display:none; }
  .hero-still{ display:block; }
  .hero-video-mobile{ display:block; }

  /* still-scrim: the mobile stills are real photos now, not a dark motion-blurred
     loop -- a caption can land on a bright/busy patch (chrome, wet highlights) with
     nothing guaranteeing contrast. One gradient div per section, matched to whichever
     edge that section's .beat sits on, painted above the still but below the text
     (z-index 2 vs .beat's 3) so legibility never depends on what the shot happens to
     contain. */
  /* JS drives .beat's opacity/filter/transform directly, every scroll frame
     (setupStaticSection) -- a CSS transition racing to catch up with values
     that change that often is exactly what reads as "text doesn't match the
     scroll", so it's switched off here; desktop keeps its own transition,
     driven by class toggles instead, untouched. */
  .scrub .beat{ transition:none; }

  .process-row{ grid-template-columns:44px 1fr; row-gap:.5rem; }
  .process-row p{ grid-column:2; }
  .split-grid{grid-template-columns:1fr;}
  .split-card{min-height:340px;}

  /* beat-2b/2c collision fix: stack section2's three captions in their own
     top / middle / bottom bands instead of left/right pairing them in the
     same bottom band, which could overlap in the middle on a narrow screen. */
  #beat-2b{ bottom:auto; top:44%; left:clamp(1.25rem,4vw,3rem); right:auto; text-align:left; }
  #beat-2c{ left:clamp(1.25rem,4vw,3rem); right:auto; text-align:left; }
}

/* persistent per-section chrome -- unlike .beat these never fade out, so
   the section always has *something* alive on screen, not just footage */
.scrub-index{
  position:absolute; top:clamp(96px,16vh,150px); left:clamp(1.25rem,4vw,3rem); z-index:3;
  display:flex; align-items:baseline; gap:.55rem;
}
.scrub-index .num{font:800 .85rem/1 var(--display); color:var(--gold-bright); letter-spacing:.02em;}
.scrub-index .label{font:600 .66rem/1 var(--sans); letter-spacing:.18em; text-transform:uppercase; color:var(--fg-dim);}

.scrub-progress{
  position:absolute; top:50%; right:clamp(1.1rem,3vw,2.25rem); z-index:3;
  width:2px; height:110px; transform:translateY(-50%);
  background:rgba(var(--gold-rgb),.16); border-radius:2px; overflow:hidden;
}
.scrub-progress i{
  position:absolute; left:0; bottom:0; width:100%; height:0%;
  background:var(--gold-bright); display:block;
}
@media (max-width:760px){
  .scrub-index{ top:clamp(80px,13vh,110px); }
  .scrub-progress{ display:none; }
}

/* beat position varies per section — don't repeat the same layout device */
#scrub-3a .beat{ left:50%; right:auto; bottom:auto; top:16%; transform:translateX(-50%); text-align:center; max-width:18ch; }
/* beat-2a sits higher than the default bottom anchor so it and beat-2b
   (which stays at the default bottom position) can both be on screen
   together without overlapping, now that they hold as a pair. Was a bare
   top:14% -- of the SECTION's own height, not the viewport -- which put it
   only ~12px below .scrub-index's own anchor once both are measured out in
   real pixels on a typical phone, so a caption at this font-size ran
   straight into the "01 Henting" label above it (Jonas: "01 henting
   collapses a bit with vi henter bilen"). A vh-based clamp, same technique
   already used for this element on desktop below, gives it real fixed
   clearance instead of a percentage that happened to land too close. */
#beat-2a{ bottom:auto; top:clamp(150px,22vh,190px); }
#scrub-3b .beat{ left:auto; right:clamp(1.25rem,4vw,3rem); text-align:right; }
#scrub-3b #beat-3b0{ bottom:auto; top:16%; }
/* section2's third, closing caption -- mirrors beat-3b's right alignment
   so it reads as a distinct beat from beat-2b sharing the same bottom band. */
#beat-2c{ left:auto; right:clamp(1.25rem,4vw,3rem); text-align:right; }
/* section3a now holds two captions like section2 does: the new opener
   (beat-3a0) keeps #scrub-3a .beat's default top:16% centered slot, and
   the original beat-3a moves to a matching centered slot near the bottom
   so the two never overlap. */
#scrub-3a #beat-3a{ top:auto; bottom:16%; left:clamp(1.25rem,4vw,3rem); right:auto; transform:none; text-align:left; }

@media (min-width:761px){
  /* desktop: the shared .beat font-size clamp above scales with vw and
     was ballooning on wide screens -- large enough to wrap a longer
     caption (like beat-2b) onto 3 lines and run into .scrub-index's
     "01 Henting" label pinned near the same top edge. Desktop has
     plenty of width to work with, so it doesn't need to grow anywhere
     near as aggressively as it did for narrow phone screens. */
  .scrub .beat{ font-size:clamp(2.1rem,3.6vw,3.6rem); max-width:22ch; transition:none; }
  /* beat-2a's top:14% (above) was tuned against a phone's short
     viewport height -- on a tall desktop window that same 14% lands
     right where .scrub-index sits, hence the collision. Desktop gets
     its own fixed clearance instead of a percentage of viewport height. */
  #beat-2a{ top:clamp(180px,24vh,260px); }
}

/* ---------- seam blend — every section dissolves fully to bg right at its own
   top/bottom edge, so adjacent sections' different photos read as one
   continuous space instead of a hard cut (Jonas: "make the connection
   seamless"). Sits above the image, above each section's own directional
   scrim, below the caption text. ---------- */
.hero::before, .scrub::before{
  content:''; position:absolute; inset:0; z-index:3; pointer-events:none;
  background:linear-gradient(to bottom,
    var(--bg) 0%, rgba(6,6,5,0) 8%,
    rgba(6,6,5,0) 92%, var(--bg) 100%);
}
/* ---------- ambient "living" light — soft drifting warm blobs, screen-blended ---------- */
.hero::after, .scrub::after{
  content:''; position:absolute; inset:-15%; z-index:1; pointer-events:none;
  opacity:.22; filter:blur(4px); will-change:transform;
  background:
    radial-gradient(28% 22% at 22% 30%, rgba(var(--gold-rgb),.5), transparent 70%),
    radial-gradient(24% 30% at 78% 68%, rgba(244,239,230,.22), transparent 72%);
  animation:driftBlob 26s ease-in-out infinite alternate;
}
.scrub::after{ animation-duration:32s; animation-delay:-6s; }
#scrub-3a::after{ animation-delay:-14s; }
#scrub-3b::after{ animation-delay:-22s; }
@keyframes driftBlob{
  0%{ transform:translate(-3%,-2%) scale(1); }
  50%{ transform:translate(3%,4%) scale(1.14); }
  100%{ transform:translate(-2%,3%) scale(1.05); }
}

section.content{ position:relative; overflow:hidden; }
section.content::before{
  content:''; position:absolute; inset:-20% -10%; z-index:0; pointer-events:none;
  opacity:.22; filter:blur(6px); will-change:transform;
  background:
    radial-gradient(30% 40% at 15% 20%, rgba(var(--gold-rgb),.4), transparent 70%),
    radial-gradient(26% 34% at 85% 80%, rgba(179,145,90,.3), transparent 72%);
  animation:driftBlob 34s ease-in-out infinite alternate;
}
section.content > *{ position:relative; z-index:1; }
/* the generic child rule above would otherwise outrank .mobile-reveal-cover's
   own z-index:6 (higher selector specificity wins) and let this section's
   content paint on top of its own cover -- reasserted here so the fade-to-
   black actually hides the content sections the same way it hides the
   image sections. */
section.content > .mobile-reveal-cover{ z-index:6; }

/* ---------- film grain — whole page, low-opacity texture for an expensive, filmic feel ----------
   Previously this rendered as plain colored static laid on top with normal
   blending -- at 5% opacity, animated every 250ms, that read as visible
   speckled "black pixels" over every dark area of the site (hero, canvas,
   content sections), which is everywhere here. Two fixes: desaturate the
   turbulence to pure luminance noise (a feColorMatrix saturate=0, so no
   colored speckle) and blend it with mix-blend-mode:overlay so it modulates
   the existing image instead of sitting on top as its own visible layer --
   that's what makes a grain overlay read as "film" instead of "dirty
   sensor". Also slowed the shift so it doesn't flicker. */
.grain{
  position:fixed; inset:-10%; z-index:500; pointer-events:none; opacity:.05;
  mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  animation:grainShift 2.4s steps(6) infinite;
  will-change:transform;
}
@keyframes grainShift{
  0%{transform:translate(0,0);} 25%{transform:translate(-1%,1%);}
  50%{transform:translate(1%,-1%);} 75%{transform:translate(-1%,-1%);} 100%{transform:translate(0,0);}
}

/* ---------- boom flash — a quick light pulse fired the instant a new beat lands ---------- */
.light-flash{
  position:fixed; inset:0; z-index:600; pointer-events:none; opacity:0;
  background:radial-gradient(circle at var(--flash-x,50%) var(--flash-y,42%), rgba(255,246,224,.5), rgba(var(--gold-rgb),.16) 28%, transparent 56%);
  transform:scale(var(--flash-scale,1));
  will-change:opacity;
}
.light-flash.is-active{ animation:flashPulse 1.1s var(--ease); }
@keyframes flashPulse{ 0%{opacity:0;} 20%{opacity:.4;} 100%{opacity:0;} }

@media (max-width: 760px){
  .grain{ display:none; }
  .hero::after, .scrub::after, section.content::before{ animation:none; opacity:.16; }
}
@media (prefers-reduced-motion: reduce){
  .grain, .hero::after, .scrub::after, section.content::before{ animation:none; }
  .light-flash{ display:none; }
}
