/* website_v5 page layer - motion, nav dropdowns + mobile menu, per-section fits.
   Moved out of index.html on 2026-09-02 so every page shares it. Tokens and
   components come from design-system/ (css/tokens.css, css/components.css). */
/* components.css resolves url() against /css/, so re-point the map mask at the real file */
.ds-story__band::before{-webkit-mask-image:url("/assets/world-map.svg");mask-image:url("/assets/world-map.svg")}
/* scroll-scrubbed reveal drives inline styles every frame - kill the one-shot
   transition and keep elements visible when JS is absent */
.ds-reveal{opacity:1;transform:none;transition:none;will-change:opacity,transform}
/* NO html{scroll-behavior:smooth} - even gated behind a post-load class it
   races the browser's scroll restoration and fragment re-anchor on refresh,
   and losing the race animates the reload jump ("page glides"). Smooth anchor
   scrolling is done per-click in JS instead (see the click handler below). */
/* anchor landings clear the sticky nav exactly, so a section's title sits
   at the same distance below the bar as the hero's */
section[id]{scroll-margin-top:var(--nav-h,96px)}
/* sticky nav - Figma bar is opaque off-white with the UI shadow */
.ds-nav{position:sticky;top:0;z-index:40;transition:transform var(--ds-dur-slow) var(--ds-ease)}
/* the top bar slides away on the way down and comes back on the way up (js/site.js, the catalog's behaviour);
   never while the mobile menu is open or a link in it has focus */
.ds-nav.is-up{transform:translateY(-100%)}
.ds-nav.is-open,.ds-nav:focus-within{transform:none}
/* phones: the brand ease-out moves most of the 72 px in its first beat and read as a jump; the bar slides out and back
   on a gentle in-out curve, 1.25x the slow duration (0 under reduced motion like every token) (Ron, 15 Oct) */
@media (max-width:767px){
  .ds-nav{transition:transform calc(var(--ds-dur-slow) * 1.25) cubic-bezier(.4,0,.2,1);will-change:transform}
}
/* mobile menu (nav links are display:none at <=1259px in components.css) */
.ds-menu-btn{display:none;margin-left:auto;background:none;border:1px solid var(--ds-line-2);
  border-radius:var(--ds-r-chip);padding:var(--ds-s2) var(--ds-s3);cursor:pointer;color:var(--ds-muted)}
.ds-menu-btn svg{display:block}
/* hamburger morphs to an X while the menu is open - state feedback on the
   button itself. translate runs before rotate (rightmost first) so the outer
   bars pass through the viewBox centre and cross exactly */
.ds-menu-btn path{transition:transform var(--ds-dur) var(--ds-ease),opacity var(--ds-dur) var(--ds-ease);
  transform-box:view-box;transform-origin:12px 12px}
.ds-nav.is-open .ds-menu-btn path:nth-child(1){transform:rotate(45deg) translateY(5px)}
.ds-nav.is-open .ds-menu-btn path:nth-child(2){opacity:0}
.ds-nav.is-open .ds-menu-btn path:nth-child(3){transform:rotate(-45deg) translateY(-5px)}
.ds-mobile-nav{display:none;background:var(--ds-off-white)}
/* grid rows 0fr->1fr animate the panel to content height without measuring;
   padding lives on children so the collapsed row really reaches 0 */
.ds-mobile-nav__in{overflow:hidden;min-height:0;padding-inline:var(--ds-gutter)}
.ds-mobile-nav ul{list-style:none;margin:8px 0 0;padding:0}
.ds-mobile-nav a{display:block;padding:var(--ds-s3) var(--ds-s1);color:var(--ds-ink);text-decoration:none;
  font-weight:700;border-bottom:1px solid var(--ds-line)}
/* sub-links stay ink, like the desktop dropdown - weight and indent carry the hierarchy */
.ds-mobile-nav a.sub{font-weight:400;padding-left:var(--ds-s5)}
/* .ds-mobile-nav a (0,1,1) out-specifies .ds-btn--primary (0,1,0) - without
   this the pill's label goes ink-on-purple and it inherits a link underline row */
.ds-mobile-nav a.ds-btn{margin:var(--ds-s4) 0 var(--ds-s5);color:var(--ds-on-dark);border-bottom:none}
/* ...and display:block + the link padding left the pill's label stuck top left: give it back the button box */
.ds-mobile-nav a.ds-btn{display:flex;padding:15px 18px}
/* the full bar needs ~1260px (lockup + six links + pill) - the hamburger takes
   over below that, at the same width as components.css */
@media (max-width:1259px){
  .ds-menu-btn{display:block}
  /* the Figma bar is a desktop spec: 103px of sticky chrome is an eighth of a
     phone screen. Compact bar on mobile - the --nav-h JS keeps anchor offsets
     and the menu panel in sync automatically */
  .ds-nav__in{min-height:72px;padding-block:12px}
  .ds-mobile-nav{display:grid;grid-template-rows:0fr;visibility:hidden;
    transition:grid-template-rows var(--ds-dur-slow) var(--ds-ease),visibility var(--ds-dur-slow)}
  .ds-nav.is-open .ds-mobile-nav{grid-template-rows:1fr;visibility:visible;
    border-top:1px solid var(--ds-line)}
  .ds-nav__in>.ds-btn{display:none}  /* the menu panel carries the CTA on mobile */
  /* components.css opens its own dropdown of the desktop links on .is-open; this site uses .ds-mobile-nav, so that
     second list showed over the opening panel and below it (Ron, 15 Oct: "button on menu is broken") */
  .ds-nav.is-open .ds-nav__links{display:none}
}
/* phones: every button gets 2px more on each side (Ron, 15 Oct) */
@media (max-width:767px){
  .ds-btn,.ds-mobile-nav a.ds-btn{padding-inline:20px}
  .ds-cta .ds-btn{padding-inline:26px}
}
/* phones (Ron, 15 Oct): the Zedge and GuruShots banners run the full column width, their wordmarks 20% smaller;
   the Evals illustration follows its text */
@media (max-width:767px){
  .ds-engines{margin-inline:16px}   /* more side margin around both sections: 40 px from the screen edge (Ron, 15 Oct) */
  .ds-engine__art{max-width:none}
  .ds-engine__mark{height:26.4px}
  .ds-engine__mark--zedge{height:31.2px}
  #evals .ds-split > div:first-child{order:2}
}
/* nav dropdowns - carets are G5, like every web icon (§7) */
.ds-nav__links>li{position:relative}
.ds-nav__links .has-menu>a::after{content:"";display:inline-block;margin-left:var(--ds-s2);width:7px;height:7px;
  border-right:2px solid;border-bottom:2px solid;color:var(--ds-muted);transform:rotate(45deg) translateY(-3px)}
.ds-menu{position:absolute;top:100%;left:50%;transform:translateX(-50%) translateY(-6px);padding-top:var(--ds-s2);
  visibility:hidden;opacity:0;
  transition:opacity var(--ds-dur) var(--ds-ease),transform var(--ds-dur) var(--ds-ease),visibility var(--ds-dur)}
.has-menu:hover .ds-menu,.has-menu:focus-within .ds-menu{visibility:visible;opacity:1;transform:translateX(-50%)}
.ds-nav__links a,.ds-menu__in a{transition:color var(--ds-dur) var(--ds-ease),background-color var(--ds-dur) var(--ds-ease)}
.ds-menu__in{background:var(--ds-white);border:1px solid var(--ds-line);border-radius:var(--ds-r-chip);
  box-shadow:var(--ds-sh-card);padding:8px;min-width:200px;list-style:none;margin:0}
.ds-menu__in a{display:block;padding:var(--ds-s2) var(--ds-s4);border-radius:var(--ds-r-sm);color:var(--ds-ink);text-decoration:none}
.ds-menu__in a:hover{background:var(--ds-grey);color:var(--ds-purple)}
/* the Figma frame's hero sits on the flat page background - no tint washes */
.ds-hero{position:relative;overflow:clip}
.ds-hero>.ds-container{position:relative;z-index:1}
/* hero load-in stagger. `backwards` fill (not `both`): it hides the element
   through its delay, then RELEASES opacity/transform when the run ends - a
   forwards fill would keep overriding them forever, which on the illustration
   would beat the scroll-scrubbed reveal's inline styles and kill its fade-out */
@media (prefers-reduced-motion:no-preference){
  @keyframes ds-rise{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
  .ds-hero-stagger>*{animation:ds-rise .62s var(--ds-ease) backwards}
  .ds-hero-stagger>*:nth-child(2){animation-delay:.09s}
  .ds-hero-stagger>*:nth-child(3){animation-delay:.18s}
  .ds-hero-stagger>*:nth-child(4){animation-delay:.27s}
  /* the illustration joins the choreography mid-stagger, so the eye reads
     headline first, art second - instead of the art popping in fully formed */
  .ds-hero .ds-media-wrap{animation:ds-rise .62s var(--ds-ease) .22s backwards}
}
/* FAQ chevron - drawn in CSS like the nav caret (the old U+2304 glyph is not in
   Nunito Sans and fell back to Menlo), G5 per the §7 icon rule. The span is
   empty; the translate keeps the V centred on the question's first line as it
   flips down -> up when the row opens. */
.ds-faq__q span{flex:none;width:9px;height:9px;margin:10px 4px 0 0;
  border-right:2px solid;border-bottom:2px solid;color:var(--ds-muted);
  transform:translateY(-3px) rotate(45deg);transition:transform var(--ds-dur) var(--ds-ease)}
details[open]>.ds-faq__q span{transform:translateY(3px) rotate(225deg)}
/* FAQ answer settles down from its question (keyframe runs the moment the
   details renders the answer - works in every browser) */
@media (prefers-reduced-motion:no-preference){
  @keyframes ds-faq-in{from{opacity:0;transform:translateY(-4px)}}
  details[open]>.ds-faq__a{animation:ds-faq-in var(--ds-dur-slow) var(--ds-ease)}
}
/* where ::details-content exists, the row's height animates too; elsewhere
   these rules are ignored and the fade above still carries the open */
html{interpolate-size:allow-keywords}
.ds-faq__row::details-content{opacity:0;block-size:0;overflow:clip;
  transition:content-visibility var(--ds-dur-slow) allow-discrete,
    opacity var(--ds-dur-slow) var(--ds-ease),block-size var(--ds-dur-slow) var(--ds-ease)}
.ds-faq__row[open]::details-content{opacity:1;block-size:auto}
/* press feedback: the hover lift lands back down while the pointer is down */
.ds-btn:active{transform:none}

/* Research: the links column is ONE grey r32 card - the "Links" eyebrow sits inside
   it, rows are hairline-separated (Ron, 2026-09-29). Card-width, hugging the right
   edge on desktop */
#links{width:100%;max-width:360px;background:var(--ds-grey);border-radius:var(--ds-r-xl);padding:var(--ds-s5) var(--ds-s6)}
#links .ds-linklist{margin-top:var(--ds-s2);gap:0}
#links .ds-linklist li+li{border-top:1px solid var(--ds-line)}
#links .ds-linklist a{background:none;border-radius:0;padding:var(--ds-s4) 0;box-shadow:none;transform:none;color:var(--ds-blue-deep)}
#links .ds-linklist a:hover{box-shadow:none;transform:none;color:var(--ds-purple)}   /* the site link hover; no underline - it would run under the source too */
/* labels read as links in the site link blue (Ron, 2026-09-29: "blue - not purple"); note blue-deep is 4.32:1 on G1, just under AA 4.5 at 18px */
@media (min-width:901px){#links{justify-self:end}}

/* Contact: the one "prefer to talk" line under the lead - off-white page, so
   the blue-deep links pass AA here (Ron, 2026-09-29) */
.ds-contact__alt{margin-top:var(--ds-s6);color:var(--ds-body)}
/* Turnstile: reserve the widget's 65px so the button does not jump when it renders;
   .ds-field's flex beats [hidden], so restate it (no site key = no box) */
.ds-captcha{min-height:65px;margin-inline:min(0px,(100% - 300px) / 2)}   /* under a 300 content box (phones <388) the widget's 300 minimum borrows the card padding, centred, instead of scrolling the page */
.ds-captcha[hidden]{display:none}

/* Hero, film format (2026-10-14): copy in one compact row, the silent loop below it at full container
   width, and the "See how it works" film card in the loop's corner. Text never sits on the loop
   (brand: no looping motion behind text). Mobile swaps to the 4:5 cut via <source media>. */
.ds-hero--film .ds-hero__top{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--ds-s8);align-items:end}
.ds-hero--film .ds-display{max-width:none}
.ds-hero--film .ds-lead{max-width:44ch}
.ds-hero--film .ds-actions{margin-top:var(--ds-s5)}
.ds-hero__media{display:block;position:relative;margin-top:var(--ds-s7)}
.ds-hero__loop{display:block;width:100%;height:auto;aspect-ratio:2/1;object-fit:cover;border-radius:var(--ds-r-hero);background:var(--ds-off-white)}
/* the card sits under the loop, right-aligned: inside the loop it covered the timeline panel's right end */
.ds-hero__film{position:relative;margin:var(--ds-s4) 0 0 auto;width:fit-content;display:flex;align-items:center;gap:var(--ds-s3);
  padding:6px 18px 6px 6px;border:0;border-radius:var(--ds-r-lg);background:var(--ds-white);box-shadow:var(--ds-sh-card);
  font:700 var(--ds-fs-body-lg)/1.2 var(--ds-font);color:var(--ds-ink);cursor:pointer;
  transition:transform var(--ds-dur) var(--ds-ease),box-shadow var(--ds-dur) var(--ds-ease)}
.ds-hero__film:hover{transform:translateY(-1px);box-shadow:var(--ds-sh-lg)}
.ds-hero__film:focus-visible{outline:2px solid var(--ds-purple);outline-offset:3px}
.ds-hero__thumb{position:relative;display:block;width:96px;height:54px;border-radius:var(--ds-r-chip);overflow:hidden;background:var(--ds-navy)}
.ds-hero__thumb img{display:block;width:100%;height:100%;object-fit:cover}
.ds-hero__thumb::after{content:"";position:absolute;left:50%;top:50%;width:28px;height:28px;margin:-14px 0 0 -14px;border-radius:50%;
  background:var(--ds-purple) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28'%3E%3Cpath d='M11 9.5v9l7.5-4.5z' fill='%23fff'/%3E%3C/svg%3E") center/100% no-repeat}
/* the close button sits above the film: inside it, it covered the film's step track */
.ds-filmbox{width:min(1240px,calc(100vw - 32px));padding:56px 0 0;border:0;background:transparent;overflow:visible}
.ds-filmbox::backdrop{background:rgba(12,26,50,.72)}
.ds-filmbox video{display:block;width:100%;height:auto;aspect-ratio:16/9;border-radius:var(--ds-r-xl);background:var(--ds-navy)}
.ds-filmbox__close{position:absolute;right:0;top:0;z-index:1;width:44px;height:44px;border:0;border-radius:50%;
  background:var(--ds-white);color:var(--ds-ink);font:400 28px/1 var(--ds-font);cursor:pointer}
.ds-filmbox__close:focus-visible{outline:2px solid var(--ds-purple);outline-offset:2px}
@media (max-width:1024px){.ds-hero--film .ds-hero__top{grid-template-columns:minmax(0,1fr);gap:var(--ds-s5);align-items:start}}
@media (max-width:767px){
  .ds-hero__loop{aspect-ratio:4/5;border-radius:var(--ds-r-xl)}
  .ds-hero__film{margin-top:var(--ds-s4);width:100%}
}

/* Hero, background-video version (2026-10-14, after runway.com): the loop fills an inset rounded panel and
   the unchanged hero copy sits on it in white at the lower left. The navy scrim (dark is navy, never black)
   keeps the copy at AA over any frame; the loop holds on its poster under reduced motion. */
.ds-hero--bg{padding:var(--ds-s4) 0 var(--ds-s8)}
.ds-hero__panel{position:relative;width:min(var(--ds-container-wide),calc(100% - 48px));margin-inline:auto;
  height:clamp(560px,calc(100svh - 120px),820px);border-radius:var(--ds-r-hero);overflow:hidden;background:var(--ds-navy)}
.ds-hero__bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.ds-hero__panel::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(90deg,rgba(12,26,50,.86) 0%,rgba(12,26,50,.62) 42%,rgba(12,26,50,0) 70%),
             linear-gradient(0deg,rgba(12,26,50,.72) 0%,rgba(12,26,50,0) 60%)}   /* deeper scrim (Ron, 15 Oct: C1) */
.ds-hero__copy{position:absolute;z-index:1;left:clamp(24px,5vw,64px);bottom:clamp(24px,5vw,64px);max-width:600px}
.ds-hero__copy .ds-eyebrow{color:rgba(255,255,255,.82)}
.ds-hero__copy .ds-display{color:var(--ds-on-dark);max-width:none}
.ds-hero__copy .ds-lead{color:var(--ds-on-dark);max-width:40ch;margin-top:var(--ds-s4)}
.ds-hero__copy .ds-actions{margin-top:var(--ds-s6)}
.ds-hero__copy .ds-btn--primary:focus-visible,.ds-hero__copy .ds-btn--secondary:focus-visible{outline-color:var(--ds-white)}
@media (max-width:767px){
  .ds-hero__panel{width:calc(100% - 32px);height:auto;aspect-ratio:9/16;max-height:calc(100svh - 96px);min-height:600px;border-radius:var(--ds-r-xl)}
  .ds-hero__panel::after{background:linear-gradient(0deg,rgba(12,26,50,.92) 0%,rgba(12,26,50,.8) 50%,rgba(12,26,50,.25) 80%,rgba(12,26,50,0) 92%)}
  .ds-hero__copy{left:20px;right:20px;bottom:24px}
}

/* Hero V6 (2026-10-15, Figma 70:255): the frame holds the panel and the Watch the video play circle, which sits in the
   panel's top right at every width. Now playing (component 23:310, story rail since 15 Oct) is a
   six-part rail along the bottom of the panel with the shot's title and an arrow to its collection above it (no icon; Ron, 15 Oct) in 14/400
   (the caption rung, added for it; Ron, 15 Oct), right aligned at every width (clear of the copy). No box; the
   panel's bottom scrim carries the contrast. */
.ds-hero__frame{position:relative;width:min(var(--ds-container-wide),calc(100% - 48px));margin-inline:auto}
.ds-hero__frame .ds-hero__panel{width:100%}
.ds-hero__frame .ds-hero__copy{bottom:80px}   /* the rail runs under it with 24 px of air above and below (Ron, 15 Oct) */
.ds-np{position:absolute;z-index:2;left:32px;right:32px;bottom:24px;display:grid;gap:8px;color:var(--ds-on-dark);
  text-shadow:0 1px 8px rgba(12,26,50,.55);pointer-events:none}   /* only the link takes clicks */
.ds-np__bar{order:2;display:flex;gap:var(--ds-s1)}
.ds-np__bar span{flex:1;height:3px;border-radius:2px;background:rgba(251,251,251,.28);overflow:hidden}
.ds-np__bar i{display:block;height:100%;background:var(--ds-on-dark);transform:scaleX(0);transform-origin:0 50%}
/* the whole title row is the link to the shot's collection (Ron, 15 Oct); 12 px of padding, cancelled by the margin,
   makes it a 44 px target without moving anything */
.ds-np__row{justify-self:end;display:flex;align-items:center;gap:6px;max-width:100%;padding:12px 8px;margin:-12px -8px;
  color:var(--ds-on-dark);text-decoration:none;pointer-events:auto;border-radius:var(--ds-r-sm)}
.ds-np__title{min-width:0;font:400 var(--ds-fs-caption)/20px var(--ds-font);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ds-np__arrow{flex:none;font:400 var(--ds-fs-caption)/1 var(--ds-font);transition:transform var(--ds-dur) var(--ds-ease)}
.ds-np__link:hover .ds-np__arrow{transform:translateX(3px)}
.ds-np__link:focus-visible{outline:2px solid var(--ds-white);outline-offset:-6px}
.ds-vpill{position:absolute;z-index:2;top:32px;right:32px;display:grid;place-items:center;width:56px;height:56px;border-radius:50%;
  background:rgba(16,32,60,.42);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  transition:transform var(--ds-dur) var(--ds-ease),background var(--ds-dur) var(--ds-ease)}   /* a play circle (Ron, 15 Oct) */
.ds-vpill:hover{transform:scale(1.06);background:rgba(16,32,60,.62)}
.ds-vpill:focus-visible{outline:2px solid var(--ds-white);outline-offset:3px}
.ds-vpill__play{width:22px;height:22px;margin-left:3px;   /* optical centre of the triangle */
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22'%3E%3Cpath d='M5 2.6v16.8L19 11z' fill='%23FBFBFB'/%3E%3C/svg%3E") center/contain no-repeat}
@media (max-width:767px){
  .ds-hero__frame{width:calc(100% - 32px)}
  /* the 9:16 card (capped by the screen, 600 floor) plus 10 px (Ron, 15 Oct) */
  .ds-hero__frame .ds-hero__panel{aspect-ratio:auto;height:calc(min((100vw - 32px) * 16 / 9, 100svh - 96px) + 10px);max-height:none;min-height:610px}
  .ds-nav__logo{height:25px}   /* 21 px + 20% on phones (Ron, 15 Oct; was the 19 px floor + 10%) */
  .ds-np{left:16px;right:16px}
  .ds-vpill{top:16px;right:16px;width:48px;height:48px}   /* top right on phones too (Ron, 15 Oct) */
  .ds-vpill__play{width:18px;height:18px}
}

/* Product video: native <dialog> over a navy scrim (dark is navy, never black), the Evals film pattern; the close
   button sits above the video, Book a call and Contact us under it. display only while [open] */
.ds-film{width:min(1100px,100% - 2 * var(--ds-s4));max-width:none;padding:0;border:0;background:none;overflow:visible}
.ds-film[open]{display:flex;flex-direction:column;align-items:flex-end;gap:var(--ds-s3)}
.ds-film::backdrop{background:rgba(16,32,60,.88)}   /* --ds-navy; ::backdrop does not inherit custom properties everywhere */
.ds-film video{display:block;width:100%;aspect-ratio:16/9;border-radius:var(--ds-r-md);background:var(--ds-navy-deep)}
.ds-film__close{display:grid;place-items:center;width:44px;height:44px;padding:0;border:0;border-radius:50%;
  background:var(--ds-white);color:var(--ds-ink);cursor:pointer;transition:color var(--ds-dur) var(--ds-ease)}
.ds-film__close svg{width:28px;height:28px}
.ds-film__close:hover{color:var(--ds-purple)}
.ds-film__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--ds-s3)}
.ds-film__actions .ds-btn:focus-visible{outline-color:var(--ds-white)}
