/* Phones (<= 767px). Loaded after every other sheet, so the rules here win at equal
   specificity; nothing outside the media blocks changes a wider screen, except that the
   phone-only elements stay hidden there. Tablets (768+) keep sections.css's own <= 768
   rules as they were. --um is 1px of a 390px-wide phone mock. */

.footer-acc,
.menu-nav,
.ov-short,
.what-tabs,
.sp-line,
.rotate-note { display: none; }

@media (max-width: 767px) {
  :root {
    --um: calc(100vw / 390);
    --section-y: calc(72 * var(--um)); /* vertical air of the plain (unpinned) sections */
  }

  /* ---- Heights: svh, so Safari's toolbar never covers a pinned screen. The canvas is the
     one exception: 100lvh, so the toolbar sliding away uncovers scene, not page ground
     (main.js sizes the renderer to the same lvh and ignores toolbar-only resizes). ---- */
  #canvas-container { height: 100lvh; }
  .hero-stage { height: calc(100svh + var(--hero-pin)); }
  html[data-scene="dunes"] .s-why { margin-top: calc(-100svh - var(--hero-pin) * var(--story-why-tail)); }
  .why-sticky,
  .what-sticky { height: 100svh; }
  .s-team,
  .team-panel { min-height: 100svh; }
  /* (the hero's 100svh and the pinned team panel's are set with their own rules below) */

  /* ---- Safe areas (index.html viewport-fit=cover) ---- */
  header {
    height: calc(var(--header-h) + env(safe-area-inset-top));
    padding-top: calc(14px + env(safe-area-inset-top));
  }
  /* ---- 01 Hero: "our partners" as one row -- the label on the left gutter, the logos on
     the right one -- fixed to the window's bottom: iOS moves fixed boxes with the toolbar
     in the same animation, so the row rides along instead of hanging (svh anchor) or
     jumping (env(safe-area-inset-bottom) steps once the toolbar has settled). The hero is
     pinned for as long as the row shows, and it hides with the hero copy
     (dune-story.css .story-copy-out), so fixed never outlives the hero. ---- */
  .hero-partners {
    position: fixed;
    right: var(--page-x); /* left: the desktop's own --page-x */
    bottom: 24px;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
  }
  .hero-partners > .small { flex: none; }
  /* The logos as a ticker (partner-marquee.js): it takes the rest of the row and fades out
     at both ends; the track slides by one of its two equal halves per loop */
  .hero-partners .partner-logos {
    flex: 0 1 calc(210 * var(--um)); /* the ticker window, at the right edge */
    min-width: 0;
    display: block;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
  }
  /* The logos first come in like the rest of the hero copy (the row's own reveal, .is-in),
     stand for a beat, and only then start to run: moving while they faded in read as a jerk */
  .pm-track {
    display: flex;
    width: max-content;
  }
  .partner-logos.is-in .pm-track { animation: pm-run 20s linear 0.3s infinite; }
  .pm-set {
    display: flex;
    align-items: center;
    gap: 20px;
    padding-right: 20px; /* = gap, so the two halves are exactly equal */
  }
  .pm-set > * { flex: none; }
  @keyframes pm-run { to { transform: translateX(-50%); } }
  /* The title group on the screen's vertical centre (of the 100svh hero: the visible area
     with the toolbar up, which never changes with it, so the group does not move) */
  .hero {
    height: 100svh;
    align-items: center;
    padding-bottom: 0;
  }
  .hero-content { margin-top: 60px; } /* no desktop nudge up; 30px below the centre (margin shifts a centred box by half) */

  /* ---- No curtains: why us -> what we do and the plate -> core team are plain scroll ---- */
  .page-tint { display: none; }
  .why-sticky { clip-path: none; }
  .s-why .why-sticky > * {
    transform: none;
    opacity: 1;
    will-change: auto; /* no promoted layers left over: nothing here animates on phones */
  }
  .s-why .player {
    translate: none;
    filter: none;
    transition: translate 0.6s var(--ease-out), width .45s ease, height .45s ease, border-radius .45s ease, opacity var(--dur-fast) ease;
  }
  /* team-flight.js writes --team-open inline every frame; held open here */
  .s-team { --team-open: 1 !important; }
  .s-team::before { display: none; }
  .s-team.is-fixing .team-panel,
  .s-team.team-flight.is-fixing .team-panel {
    height: 100svh;
    position: sticky;
    inset: auto;
    top: 0;
  }

  /* ---- Tap targets (44 x 44) ---- */
  .menu-btn { height: 44px; }
  /* Opening the drawer focuses the close (keyboard users land in it); iOS paints the ring
     after a tap too. No keyboard here, and VoiceOver draws its own frame. */
  .menu-close:focus-visible { outline: none; }
  .menu-close {
    width: 44px;
    height: 44px;
    margin-right: -14px; /* the cross stays where the 32px box had it */
  }

  /* ---- Small print: nothing under 12px ---- */
  .player-readout,
  .quote-client .label,
  .office-name,
  .team-person small,
  .footer-bottom { font-size: 12px; }

  /* ---- Readability over the 3D scene: the small text standing on the canvas (hero, why
     us, team, presence, contact) is darker and a step heavier. (No halo: the canvas ground
     is lit and tone-mapped, never the flat --scene-bg, so any halo showed as a pale rim.)
     The white sheet's text is on plain white and stays as it is. ---- */
  :is(.hero, .s-why, .s-team, .s-presence, .s-contact) :is(.eyebrow, .small, .team-lead, .stat .body-lg, .team-person small) {
    color: var(--ink-soft);
  }
  :is(.hero, .s-why, .s-team, .s-presence, .s-contact) :is(.eyebrow, .small) { font-weight: 500; }

  /* ---- Small print is Regular, not Light: a phone's 12-14px text in the greys (captions,
     roles, the footer's lines) and over a picture or the scene (the stats' copy, the case
     captions) reads thin at 300. Only pieces that keep their line breaks at every phone
     width (360-430) get it: .team-lead and .what-copy re-wrap when they widen, and stay
     Light. ---- */
  .caption, .caption-xl,
  .team-person small,
  .menu-clocks time,
  .s-presence .stat .body-lg,
  .project .body-lg,
  .footer-desc,
  .footer-bottom { font-weight: 400; }

  /* ---- Page progress: a 2px line along the top (ui.js --page-p) ---- */
  .sp-line {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    z-index: calc(var(--z-header) + 1);
    /* ink, then the story's blue as the star colours the hand (ui.js --sp-tint 0..1) */
    background: color-mix(in srgb, var(--blue) calc(var(--sp-tint, 0) * 100%), var(--ink));
    transform-origin: 0 50%;
    transform: scaleX(var(--page-p, 0));
    pointer-events: none;
  }

  /* ---- Menu drawer: the address and the social link as before; under them, where the
     offices used to stand, the section links in two columns (phones have no progress
     map); the offices, two abreast, pressed down onto the foot's divider. The column
     scrolls on a short screen rather than clip. ---- */
  .menu-links {
    justify-content: flex-start;
    gap: clamp(16px, 3svh, 28px);
    padding: 12px 0 0;
    overflow-y: auto;
    scrollbar-width: none;
  }
  /* The address, the social link and the section links are one group, centred in the room
     between the drawer's head and the offices (equal auto margins above the address and
     under the links); the column's gap is the least they keep from the offices */
  /* ...and the whole group sits 20px above that centre */
  .menu-email,
  .menu-social-row,
  .menu-nav {
    position: relative;
    top: -20px;
  }
  .menu-email {
    margin-top: auto;
    font-size: calc(14.2cqw + 2px); /* fills its column, as on the desktop */
  }
  .menu-nav {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 24px;
    margin: 0;
    padding: clamp(12px, 2svh, 20px) 0 0;
    border-top: 1px solid var(--hairline-soft); /* the rule the offices had here */
    margin-bottom: auto;
    list-style: none;
  }
  .menu-nav a {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: 24px; /* the type scale's 24 step (.h5 / .list floor), one above the social link's 20 */
    font-weight: 300;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--ink);
    text-decoration: none;
  }
  .menu-overlay.is-open .menu-nav li:nth-child(1) .rli { transition-delay: 0.32s; }
  .menu-overlay.is-open .menu-nav li:nth-child(2) .rli { transition-delay: 0.34s; }
  .menu-overlay.is-open .menu-nav li:nth-child(3) .rli { transition-delay: 0.36s; }
  .menu-overlay.is-open .menu-nav li:nth-child(4) .rli { transition-delay: 0.38s; }
  .menu-overlay.is-open .menu-nav li:nth-child(5) .rli { transition-delay: 0.4s; }
  /* Linkedin stands last among the section links instead, and the row under the address
     takes no room of its own. The "copied" note rises into the address's second line: the
     domain leaves up under its mask meanwhile and comes back after, the way the desktop's
     link row does (global.css .menu-social-row). */
  .menu-social { display: none; }
  .menu-social-row { height: 0; }
  .menu-social-note { top: auto; bottom: 0; }
  .menu-overlay.is-open .menu-links:has(.menu-social-row.is-copied) .menu-email-dim .rli {
    transform: translateY(-110%);
    transition: transform 0.32s var(--ease-in);
  }
  .menu-overlay.is-open .menu-links:has(.menu-social-row.has-copied:not(.is-copied)) .menu-email-dim .rli {
    transition: transform 0.55s var(--ease-out-soft) 0.34s;
  }

  /* The offices as one running line on the foot's divider (partner-marquee.js
     createListTicker): city and time pairs, fading at both ends. It runs only while the
     drawer is open. */
  .ticker-view {
    flex: none;
    overflow: hidden;
    padding-bottom: 16px;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  }
  .menu-clocks {
    display: flex;
    width: max-content;
    gap: 0 28px;
    padding: 0 28px 0 0; /* = gap: the copies' half equals the originals' */
    border-top: 0;
  }
  .menu-clocks li {
    flex: none;
    gap: 8px;
  }
  .menu-overlay.is-open .menu-clocks { animation: pm-run 26s linear 0.6s infinite; }

  /* ---- 02 Why us ---- */
  /* One phrase a little longer than the rest of the run (ui.js WHY_TAIL holds the last one) */
  /* Longer on phones: native flicks carry far, and more scroll per phrase keeps one from
     racing through the whole story (no snaps or scroll locks, by choice) */
  .s-why { height: 700vh; } /* 4 phrase slots of 140vh + the last phrase's 40vh (ui.js WHY_TAIL) */
  /* The phrase band sits a little above the geometric middle: its centre lands at ~44% */
  .why-head {
    top: calc(var(--header-h) + 48px);
    bottom: 26svh;
    /* A long phrase at this size can run past the band, over the player (the band is
       layered above it): taps must fall through to the button. Nothing here is interactive. */
    pointer-events: none;
  }
  /* The "let's talk" pill stands a little higher than the Play disc it grows out of */
  .s-why.is-final .player { translate: 0 -24px; }
  .why-line-p { font-size: calc(30 * var(--um)); }
  /* The closing phrase at the hero title's size (48px), on a tighter line than the phrases' 1.35 */
  .why-line-p.why-line-last {
    font-size: 48px;
    line-height: 1.15;
  }
  .waveform { bottom: 8svh; }
  /* The play disc: no 3D flip on phones. iOS hit-tests a preserve-3d face turned 180deg
     unreliably -- after the first flip, taps stopped reaching the button. The faces
     cross-fade flat instead, and every tap lands on the button itself. */
  .player-play { perspective: none; }
  .player-play > *,
  .player-play * { pointer-events: none; }
  .player-disc,
  .has-played .player-disc {
    transform: none;
    transform-style: flat;
  }
  .player-face {
    backface-visibility: visible;
    -webkit-backface-visibility: visible;
    transition: opacity var(--dur-base) ease;
  }
  .player-back {
    transform: none;
    opacity: 0;
  }
  .has-played .player-back { opacity: 1; }
  .has-played .player-front { opacity: 0; }

  /* ---- 04 Works ---- */
  /* The caption is there at once when its tile turns active: its lines rise out of their
     masks like the hero's. With the block's own delayed fade on top (desktop hover), the
     rise played out unseen and the text only faded in. */
  html.js .project.is-active .project-data { transition: none; }
  /* A shorter block: the title stands in the upper part of its pinned screen instead of the
     middle (less empty run before it), the first tile arrives sooner, the tiles sit closer,
     and less is left under the last one */
  /* --works-air: the same distance above the title (from the stack's block) and below the
     last tile (to the review's first line: the reviews' own top padding is part of it) */
  .s-works {
    --star-u: calc(1.3 * var(--um)); /* works-ui.js: shooting-star size unit */
    --works-air: 20svh;
  }
  .works-heading {
    align-items: start;
    padding-top: var(--works-air);
  }
  .project-list {
    padding: 54svh 24px calc(var(--works-air) - var(--section-y));
    gap: 12svh;
  }
  .project:nth-child(odd),
  .project:nth-child(even) { align-self: center; }

  /* ---- 05 Testimonials ---- */
  .s-testimonials { padding: var(--section-y) var(--page-x) calc(var(--section-y) * 4 / 3); }

  /* ---- 06 Core team: "The names that stay." is fixed to the window's bottom like the
     hero's partners, so it rides with Safari's toolbar instead of hanging above it. The
     pinned panel is 100lvh for that (the title must stay inside the panel's clip when the
     toolbar is away); the clip also hides the fixed title outside the section. A fixed
     box needs transform-free ancestors: the stage's (zero) translate is dropped. ---- */
  .s-team.team-flight .team-panel,
  .s-team.team-flight.is-fixing .team-panel { height: 100lvh; }
  .s-team .team-stage { translate: none; }
  .team-flight .team-title {
    position: fixed;
    left: var(--page-x);
    bottom: 24px;
  }

  /* ---- 07 Presence: the two pairs stand on the page's own gutters, left and right ---- */
  html[data-scene="dunes"].has-presence-story .s-presence .stat-tl,
  html[data-scene="dunes"].has-presence-story .s-presence .stat-bl {
    left: var(--page-x);
    right: auto;
    width: calc(50% - var(--page-x));
  }
  html[data-scene="dunes"].has-presence-story .s-presence .stat-tr,
  html[data-scene="dunes"].has-presence-story .s-presence .stat-br {
    left: auto;
    right: var(--page-x);
    width: calc(50% - var(--page-x) - 12px);
    align-items: flex-end;
    text-align: right;
  }
  html[data-scene="dunes"].has-presence-story .s-presence .stat .eyebrow { font-size: 12px; }
  /* The four stats pressed to the foot of the WINDOW: fixed, like the hero's partners, so
     they ride with Safari's toolbar instead of standing a toolbar's height above the
     bottom when it is away. The lower pair is 24px up, the upper pair one block
     (--stat-step) above it. The pinned screen is 100lvh with a clip for that: the clip
     keeps the fixed stats to this chapter, and lvh keeps them inside it toolbar or not. */
  html[data-scene="dunes"].has-presence-story .s-presence {
    --stat-foot: 56px;  /* the lower pair above the window's bottom */
    --stat-step: 140px; /* from one pair to the other (the blocks are 100px tall) */
  }
  /* The stats' copy in the services description's type (14 / 1.5, no tracking, ink), its
     lines balanced: left to fill the column, the left pair broke as one long line and a
     stub ("...driving scalable / digital solutions.") and read wider than the right pair.
     Two equal columns (the pairs' widths, above); the left is just wide enough for
     "scalable digital solutions." (bound by nbsp) on one line. */
  html[data-scene="dunes"].has-presence-story .s-presence .stat .body-lg {
    font-size: 14px;
    line-height: 1.5;
    letter-spacing: 0;
    color: var(--ink);
    text-wrap: balance;
  }
  html[data-scene="dunes"].has-presence-story .presence-sticky {
    height: 100lvh;
    clip-path: inset(0);
  }
  html[data-scene="dunes"].has-presence-story .s-presence .stat {
    position: fixed;
    transition: opacity 0.4s ease; /* the swap to the close (below) */
  }
  html[data-scene="dunes"].has-presence-story .s-presence .stat-bl,
  html[data-scene="dunes"].has-presence-story .s-presence .stat-br { bottom: var(--stat-foot); }
  html[data-scene="dunes"].has-presence-story .s-presence .stat-tl,
  html[data-scene="dunes"].has-presence-story .s-presence .stat-tr { bottom: calc(var(--stat-foot) + var(--stat-step)); }

  /* ---- 08 Contact ---- */
  /* The overline at the team eyebrow's size, in its short form: one line */
  .contact-overline { font-size: 12px; }
  .contact-overline .ov-long { display: none; }
  .contact-overline .ov-short { display: inline; }
  /* The title and its button pressed to the foot of the window, on the stats' own line
     (--stat-foot), riding with the toolbar: sticky to the bottom inside the 100lvh pinned
     screen. Sticky, not fixed: when the section ends the copy simply scrolls away with it
     and the footer follows -- a fixed copy stayed put while its clip slid off, which read
     as the footer climbing over it. */
  .contact-sticky {
    height: 100lvh;
    padding-bottom: 56px; /* = presence --stat-foot */
  }
  .contact-copy {
    position: sticky;
    bottom: 56px;
  }

  /* ---- 07 -> 08 on one pinned screen: the contact section is pulled up over the end of
     presence (both pinned together for the last 20% of its run); when it pins, the stats
     fade out and the close fades in where they stood (presence-story.js state.swap ->
     presence-ui.js html.contact-in), while the globe turns into the mark. ---- */
  /* The run is long on phones (460svh, 360 of scroll): a flick should not carry the reader
     from the globe through the stats into the close in one go. The stats stand from 64%
     (230svh) and the close takes over at 80% (288svh): the contact section is pulled up by
     the rest (460 - 288 = 172svh) and stays pinned 72svh with presence and 80svh after. */
  html.has-team-flight.has-presence-story .s-presence { height: 460svh; }
  html.has-team-flight.has-presence-story .s-contact {
    margin-top: -172svh;
    height: 252svh;
  }
  html.has-presence-story .s-contact .contact-sticky {
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-slow) ease;
  }
  html.contact-in.has-presence-story .s-contact .contact-sticky {
    opacity: 1;
    pointer-events: auto;
  }
  /* The stats leave up through their line masks as the close takes the screen (the hero's
     own .is-out, by hand: presence owns these reveals, so ui.js never marks them out), and
     come back down when the close lets go. */
  html.contact-in.has-presence-story .s-presence .stat .rv.is-in .rli {
    transform: translateY(-110%);
    transition: transform 0.4s var(--ease-in);
    transition-delay: calc(var(--d-in, 0s) * 0.6);
  }
  html.contact-in.has-presence-story .s-presence .stat .rv.rv-block.is-in {
    opacity: 0;
    transform: translateY(-16px);
  }
  /* The close's lines are revealed by the viewport long before they are seen (the section
     is pulled up under presence): hold them under their masks until the handover, so they
     rise then, not under the fade. */
  html.has-presence-story:not(.contact-in) .s-contact .contact-sticky .rv.is-in .rli {
    transform: translateY(110%);
    transition: none;
  }
  html.has-presence-story:not(.contact-in) .s-contact .contact-sticky .rv.rv-block.is-in {
    opacity: 0;
    transform: translateY(16px);
    transition: none;
  }

  /* ---- Footer: an ordinary block (no curtain), link columns as accordions ---- */
  /* While it is on screen the page ground is the footer's white (ui.js html.footer-in) */
  html.footer-in,
  html.footer-in body { background: var(--footer-bg); }
  .footer-layer > .s-footer {
    position: relative;
    clip-path: none;
    will-change: auto;
  }
  /* A full screen, like the other chapters; the wordmark keeps its own height (left to
     stretch, its box took the leftover room and pushed the letters off the rule). The
     section's own space-between keeps the copy and the menu up top, the wordmark at the foot. */
  .s-footer {
    gap: 40px;
    min-height: 100lvh; /* the whole screen, toolbar or not */
    padding: var(--section-y) var(--page-x) env(safe-area-inset-bottom);
  }
  .footer-mark,
  .footer-mark > .stack,
  .footer-wordmark { flex: none; }
  .footer-cols {
    flex-direction: column;
    gap: 0;
    width: 100%;
  }
  .footer-col {
    display: grid;
    grid-template-rows: auto 0fr;
    gap: 0;
    border-top: 1px solid var(--hairline);
    transition: grid-template-rows 0.4s var(--ease-out);
  }
  .footer-col:last-child { border-bottom: 1px solid var(--hairline); }
  .footer-col.is-open { grid-template-rows: auto 1fr; }
  /* Shut means out of sight, whatever the row's height resolves to (after the fold has played) */
  .footer-col > ul { transition: visibility 0s linear 0s; }
  .footer-col:not(.is-open) > ul {
    visibility: hidden;
    transition-delay: 0.4s;
  }
  .footer-col > .eyebrow { display: none; } /* the toggle carries the label here */
  .footer-col ul {
    min-height: 0;
    overflow: hidden;
    gap: 0;
  }
  .footer-col li:last-child { padding-bottom: 12px; }
  .footer-col a {
    display: flex;
    align-items: center;
    min-height: 36px; /* links 36px apart (were 44) */
  }
  .footer-acc {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 56px;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: 12px;
    font-weight: 400;
    letter-spacing: var(--track-caps);
    text-transform: uppercase;
    color: #727272;
    cursor: pointer;
  }
  /* The overline's slash (typography.css .eyebrow::before): the toggle stands in for it */
  .footer-acc::before {
    content: "/";
    margin-right: 1.3em;
    font-size: 0.82em;
  }
  /* "+", turning a quarter-and-a-half into "x" when open */
  .footer-acc-mark {
    position: relative;
    flex: none;
    margin-left: auto; /* to the right edge: the slash is a flex item too */
    width: 12px;
    height: 12px;
    transition: transform 0.35s var(--ease-out-2);
  }
  .footer-acc-mark::before,
  .footer-acc-mark::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 1px;
    background: var(--ink);
  }
  .footer-acc-mark::after { transform: rotate(90deg); }
  .footer-col.is-open .footer-acc-mark { transform: rotate(45deg); }
  /* Offices and copyright on either side of one row */
  .footer-bottom {
    flex-direction: row;
    gap: 12px;
  }
}

/* ---- Phones on their side: the story is portrait-only, a note asks to turn back ----
   A deliberate exception to WCAG 1.3.4 (Orientation), confirmed in the 2026-09-30
   accessibility pass: the pinned chapters are composed for a tall screen. Keep it unless
   the landscape phone gets a layout of its own. */
@media (orientation: landscape) and (max-height: 500px) and (hover: none) {
  .rotate-note {
    display: grid;
    place-items: center;
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-top) + 2);
    padding: 24px;
    background: var(--scene-bg);
    color: var(--ink);
    font-size: 16px;
    font-weight: 300;
    line-height: 1.4;
    text-align: center;
  }
}

@media (max-width: 767px) and (prefers-reduced-motion: reduce) {
  .footer-col,
  .footer-acc-mark { transition: none; }
  .pm-track { animation: none; }
  .menu-overlay.is-open .menu-clocks { animation: none; }
}
