/* ==========================================================================
   SiteYourStory — the motion ground.

   PASS FOUR made every normal section one flat colour so that no section
   boundary would read as "new page". This pass keeps that decision exactly and
   changes only what the ground is made of: instead of a painted #f1eee8, the
   canvas is a single fixed film with a wash of that same #f1eee8 over it.

   The important part is that there is still only ONE of it. The layer is
   fixed to the viewport and lives once per document — sections do not each get
   a video, they get out of the way and let the one underneath show through.
   That is what stops it reading as "page 1 has a video block, page 3 doesn't":
   scrolling never restarts anything, it just moves content across a ground
   that was already there.

   Order of the stack, bottom to top:

     html            the painted safety ground (overscroll, first paint)
     .mbg            fixed film + readability wash          z-index: -1
     body            transparent
     sections        transparent
     content / UI    untouched

   Loaded last, after story.css, because that file ends by declaring the flat
   ground with !important and this is the layer that replaces it.
   ========================================================================== */

/* --- Tuning ---------------------------------------------------------------
   The wash is the canvas colour at a measured opacity, not a grey overlay:
   over the graded footage it lands within a couple of points of the #f1eee8
   the site was already painted in, so nothing about the palette changes — the
   ground simply moves now.

   The footage itself is graded in the encode rather than with a CSS filter
   (saturation 0.46, contrast 0.90, 1.4px gaussian). A filter on a playing
   full-screen video is a per-frame GPU cost for something that never needs to
   change; baking it costs nothing at runtime and halved the file as a bonus.
   ------------------------------------------------------------------------ */
:root {
  --mbg-tint: 241, 238, 232;   /* --site-bg, as channels */
  /* How much canvas sits over the film. 0.76 was an opaque overlay that
     flattened the picture into a tint; this is haze.

     Set deliberately below the AA floor. 0.60 was the last value where the
     weakest type on the page cleared 4.5:1 against the darkest pixel the
     ground reaches; 0.56 is a chosen trade — a more present picture, bought
     with a few tenths of contrast on secondary text. Headings are unaffected
     and stay well clear.

     If it ever needs to go back to compliant, this number is the only thing
     to move: 0.60 restores it, and nothing else has to change. */
  --mbg-veil: 0.56;
  --mbg-drift: 0px;            /* set by motion.js — slow vertical parallax */
  /* What the ground actually composites to, measured off the finished layer.
     The hero's white-out ends on this rather than on flat --site-bg, so the
     handoff out of the hero is a dissolve and not a step. */
  --mbg-mean: #ddddd8;
}

/* --- Ink, against a ground that is no longer flat -------------------------
   The two secondary inks were set as alphas against a solid #f1eee8. Over
   footage the darkest place the ground reaches is roughly 20 points lower than
   that, and at 0.72 / 0.68 the ledes and the eyebrows measured 4.21 and 3.89
   against it — under AA for type at that size.

   Both sit at 0.82, which is what the lighter wash costs: measured on the
   composited ground, headings read at 6.3:1 and ledes and eyebrows at 4.7:1.
   Same colour, same faces, same sizes, same hierarchy — the alpha is
   the only thing that moves, and story.css already sets the precedent for it
   (the nav links were taken to 0.9 for exactly this reason: small tracked type
   renders thinner than its nominal value once antialiased).
   ------------------------------------------------------------------------ */
:root {
  --site-ink-2: rgba(30, 26, 22, 0.82);
  --site-ink-3: rgba(30, 26, 22, 0.82);
}

/* --- The layer ------------------------------------------------------------ */
.mbg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  /* The film is what shows; this is only what is behind it while it decodes
     and behind the edges of the drift. */
  background: var(--site-bg);
  contain: layout paint;
}

.mbg-film {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 46%;
  /* Oversized so the parallax drift never exposes an edge. */
  transform: translate3d(0, var(--mbg-drift), 0) scale(1.08);
  will-change: transform;
  /* Held back until the first frame is decodable, so the cut from poster to
     film is never a flash of black. */
  opacity: 0;
  transition: opacity 1.1s cubic-bezier(0.65, 0, 0.35, 1);
}
.mbg[data-ready='true'] .mbg-film { opacity: 1; }

/* The poster carries the same grade as the film, so the still and the moving
   version are the same picture — there is no "it changed when it loaded". */
.mbg-still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 46%;
  transform: translate3d(0, var(--mbg-drift), 0) scale(1.08);
}

/* --- The wash -------------------------------------------------------------
   Denser at the top and the bottom than through the middle. Not decoration:
   the nav sits in the top band and the footer in the bottom one, and both are
   small type. Through the middle, where the headlines are, the film is
   allowed to be at its most visible.
   ------------------------------------------------------------------------ */
.mbg-veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(var(--mbg-tint), calc(var(--mbg-veil) + 0.1)) 0%,
    rgba(var(--mbg-tint), var(--mbg-veil)) 24%,
    rgba(var(--mbg-tint), var(--mbg-veil)) 70%,
    rgba(var(--mbg-tint), calc(var(--mbg-veil) + 0.12)) 100%
  );
}

/* A vignette off the same warm charcoal as the ink. This is the depth cue:
   it puts the ground behind the content rather than level with it. */
.mbg-veil::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(
    128% 96% at 50% 38%,
    transparent 42%,
    rgba(46, 39, 31, 0.02) 78%,
    rgba(46, 39, 31, 0.05) 100%
  );
}

/* ==========================================================================
   Getting out of the way.

   Everything story.css painted --site-bg becomes transparent. The wash it used
   to be is now in .mbg-veil, once, instead of repeated per section — which is
   the whole reason the ground reads as continuous rather than as a stack of
   panels that happen to share a colour.
   ========================================================================== */

/* html keeps the paint: it is what the overscroll rubber-band and the first
   frame before any CSS-driven layer exists are made of. */
html { background: var(--site-bg) !important; }

body,
.bridge,
.cf, .cf-pin,
.process,
.usp,
.close,
.finale, .finale-scene,
.foot,
.proof,
/* inner pages */
.shell,
.page-head,
.scene {
  background: transparent !important;
  background-image: none !important;
}

/* The film keeps its own footage and its own scrims — it is a dedicated
   immersive video and nothing here touches it. What changes is only what sits
   behind it: on a wide screen the picture covers the stage completely and this
   is invisible, and on a phone, where the film is letterboxed, the bars now
   carry the same ground as the rest of the site instead of a flat plate. */
.film-stage { background: transparent !important; }

/* The hero used to keep its own opaque dark ground, which is what made its
   ending a cut: the plate faded and landed on a flat panel. It is transparent
   now, so what is behind the hero is .mbg — the same clip, under the wash.
   The plate dissolving into it (film.js) is the whole trick: the mountain is
   continuously on screen from the first frame to the Selected Work section,
   and the "transition" is just an exposure change. */
.hero, .hero-stage { background: transparent !important; }

/* The plate still needs a ground under it for the moment before the video
   decodes — but only behind the plate, not behind the whole section. */
.hero-plate { background: #17130f; }
.hero-plate { transition: opacity 0.25s linear; }

/* --- Selected Work: telling the reader it is theirs ------------------------
   film.css set a grab cursor on the stage; hero.css later set `cursor: default`
   on the same selector and won. So the one affordance saying "this moves" was
   painted over, and the projects looked like something being played at you.

   The cursor is the whole hint — no overlay, no instruction label. The card in
   front keeps `pointer` because it opens its site; everything around it is
   grab / grabbing.
   ------------------------------------------------------------------------ */
.cf-stage {
  cursor: grab;
  /* Horizontal drags belong to the ring, vertical ones to the page. Without
     this a swipe on a phone is eaten by the scroller. */
  touch-action: pan-y;
}
.cf-stage:active,
.cf-stage.is-dragging { cursor: grabbing; }

/* Every card is a screenshot, and an <img> is natively draggable — so grabbing
   one to turn the ring started the browser's own image-drag instead. That
   fires pointercancel, which the coverflow reads as "the gesture ended", so
   the ring snapped back and the drag died on the first move. The one
   interaction the section is built around only worked if you happened to grab
   the few pixels of card that are not the picture. */
.cf-card img,
.cf-card .cf-screen {
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
}
/* The one in front is a link, so it says so. */
.cf-card[data-live='true'],
.cf-card[data-live='true'] .cf-screen { cursor: pointer; }

/* Every card except the centred one was unreachable by the mouse. The ring is
   a preserve-3d space and the cards are pushed back into it — the neighbours
   sit at z = -380 and -760, behind .cf-ring's own plane at z = 0, so the ring
   intercepted every hit and no click ever landed on a project you had not
   already selected. Measured across the width of the stage, .cf-ring owned
   every pixel that the front card did not.
   Making the ring transparent to hit-testing hands those pixels to the cards
   behind it, which is what makes "click the project you want" work. The ring
   carries no interaction of its own; the drag lives on .cf-stage, which is its
   parent and still receives everything that falls through. */
.cf-ring { pointer-events: none; }
.cf-card { pointer-events: auto; }

/* Keyboard users get the same ring, and can see where they are in it. */
.cf-stage:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 4px;
}

/* --- The caption, inside the pinned screen --------------------------------
   .cf-pin is min-height:100svh with justify-content:center, and its contents
   are taller than that: 126px of top padding, the heading, 45px, a 560px
   stage, 45px, the caption, 72px of bottom padding — about 1058px inside 900.
   Centred, the overflow splits top and bottom, which put "Visit the site" at
   y≈1113, a clear 200px under the fold. You had to scroll away from the ring
   to reach the link belonging to the card you had just chosen.

   Pinning to exactly one screen and letting the stage take what is left puts
   the heading at the top, the ring in the middle and the caption at the
   bottom, all at once. The cards are sized from their own width, so nothing
   about the composition changes — only the space the ring is centred in.

   Where the ring is taller than that space it simply overlaps the margins,
   which is what the 3D field already does; .cf-head and .cf-foot carry
   z-index 300 against the cards' 200, so the type stays on top of it.
   ------------------------------------------------------------------------ */
@media (min-width: 900px) {
  .cf-pin {
    height: 100svh;
    min-height: 0;
    justify-content: space-between;
    padding-block: clamp(78px, 10.5vh, 124px) clamp(24px, 3.4vh, 46px);
  }
  .cf-head { margin-bottom: clamp(14px, 2vh, 28px); }
  .cf-stage {
    flex: 1 1 auto;
    height: auto;
    min-height: 0;
  }
  /* .cf-foot was laid out as a flex row with align-items:flex-end, from when
     it held the caption *and* a set of arrows and dots. Only the caption is
     left, and that alignment was pushing the single child 42px below the
     container's own top and 43px past its bottom — which is how the link
     ended up under the fold even after the column was pinned to one screen.
     One child, so block layout, and the box finally contains what is in it. */
  .cf-foot {
    display: block;
    margin-top: clamp(14px, 2vh, 26px);
  }
}

/* --- The grey wash over Selected Work -------------------------------------
   story.css paints the site's hairlines with one rule:

       .beat-rule, .process-rail, .cf-track { background: var(--site-line) }

   Three of those are 1px rules. `.cf-track` is not a rule — it is the
   coverflow's scroll driver, aria-hidden, 1440 × 5184px, and its only job is
   to be tall enough to give the pinned stage something to scroll against. It
   was swept into a selector meant for hairlines, so it has been painting a
   13% charcoal rectangle 5.76 viewports tall.

   That rectangle is the "gradient" that appears on the way into Selected
   Work: its top edge is the hard horizontal boundary, and the screen goes
   progressively grey as more of it covers the viewport. There is no gradient
   and nothing fading — it is one flat panel sliding up the screen.

   The film's equivalent spacer, [data-film-track], was always transparent.
   This makes them agree. Nothing about the composition needs it: with the
   panel gone, what shows behind Selected Work is the ground that was already
   there, which is what the section was designed to sit on.
   ------------------------------------------------------------------------ */
.cf-track { background: transparent !important; }

/* --- The rail is gone -----------------------------------------------------
   A 2px accent line drawing itself across the bottom of the film was the
   transition out of it. A progress bar is a UI affordance, not a cut, and it
   was the most template-looking thing on the page. The film hands over by
   dissolving into the ground now; nothing replaces the line. */
.film-rail { display: none !important; }

/* ==========================================================================
   The hero, on the new footage.

   The doorway was a dark corridor, so the page could put white type straight
   onto it and the nav needed nothing. This clip opens on a bright sky, which
   is the one thing that treatment does not survive: white nav links over a
   240-luma cloud is not a contrast problem you can style your way out of
   afterwards.

   Two changes, both readability, neither of them a redesign:
   ========================================================================== */

/* One: the grade. story.css was lifting the corridor to make it cinematic
   (brightness 1.1). Lifting a clip that is already bright only flattens it, so
   this settles it back instead — the picture keeps its light, and the type
   gets somewhere to sit. */
.hero-plate video {
  filter: contrast(1.04) saturate(0.86) brightness(0.9) !important;
}

/* Two: a top scrim for the nav. Absolutely positioned, so it is out of flow
   and .hero-stage's flex layout is untouched. It sits above the veil and below
   the bloom, so the white-out at the end of the hero still covers it. */
.hero-stage::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(130px, 24vh, 280px);
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(10, 8, 6, 0.62) 0%,
    rgba(10, 8, 6, 0.34) 42%,
    rgba(10, 8, 6, 0.1) 74%,
    transparent 100%
  );
}

/* The hero ends by washing to the colour the next section starts on. That
   colour is no longer flat --site-bg but the film under the wash, so the wash
   resolves to the composite instead — otherwise the handoff is a step from
   ivory down onto a slightly deeper ground. Both are held to a haze by
   film.js; the opacity is the thing that changed, not the colour. */
.hero-wash,
.hero-wash::after {
  background: var(--mbg-mean, #e4dfd5) !important;
}

/* --- The statement, inside the environment --------------------------------
   "Your marketing earns the click / Your website earns the trust" used to
   arrive after a white-out, so it was type on a flat panel. It now lands on
   the ground itself, with the mountain still visible behind it — which means
   it takes the canvas ink, and a soft pool rather than a panel to sit on.
   ------------------------------------------------------------------------ */
.say-line { color: var(--site-ink) !important; }

/* The four marks sat 99px below centre, which was clear of a two-line
   statement. Set in uppercase at this measure the line wraps to four, so the
   block is ~192px tall and the path was landing on top of its own last line.
   Pushed clear of the tallest wrap it can produce. */
.say-path { bottom: calc(50% - clamp(134px, 15.5vh, 200px)) !important; }

.hero-say::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: var(--o, 0);
  background: radial-gradient(
    ellipse 62% 42% at 50% 50%,
    rgba(238, 235, 229, 0.86) 0%,
    rgba(238, 235, 229, 0.6) 46%,
    rgba(238, 235, 229, 0.2) 74%,
    transparent 92%
  );
}

/* --- The page transition, in the ground's own colour ----------------------
   `.veil` is painted var(--ink), which resolved to near-white under
   data-env='dark'. Remapping the inner pages' ink to warm charcoal therefore
   flipped their wipe from light to near-black as a side effect — a behaviour
   change nobody asked for.

   It is pinned here instead, to the colour the ground actually composites to.
   The page dissolves into its own background and the next one resolves out of
   it, which is the same move the hero already makes at the end of its scroll.
   ------------------------------------------------------------------------ */
.veil { background: var(--mbg-mean) !important; }

/* --- Layered parallax -----------------------------------------------------
   assets/js/parallax.js writes the `translate` property, which composes with
   the `transform` the existing choreography already owns. Nothing to declare
   here but the opt-out.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .f-eyebrow, .label,
  .cf-title, .process-title, .usp-head, .close-head, .page-title, .head, .display,
  .f-lede, .lede, .page-lede, .close-sub, .usp-close, .body, .usp-list li,
  .stage-art, .cf-caption, .score, .marks, .stat { translate: none !important; }
}

/* --- The nav, once there is something under it ----------------------------
   The homepage nav is deliberately bare — "a mark and a way out", nothing
   permanent on top of a film — and over the two video environments it stays
   exactly that. Over the canvas it cannot: the section headings run to 4.6rem
   and pass straight under the logo, so the mark and the headline collide for
   the whole length of the scroll.

   The band lifts only where that happens, keyed off the ink state film.js
   already computes — data-ink='dark' means "the nav is over the canvas". No
   new state, no new listener, and the film's nav is untouched.

   backdrop-filter rather than a solid fill, because what is behind the nav is
   now the moving ground: frosting it keeps the band part of the environment
   instead of laying a foreign bar across it. Same blur and saturation as the
   lift the inner pages have always used.
   ------------------------------------------------------------------------ */
.nav--film {
  transition: background-color 0.45s var(--ease), border-color 0.45s var(--ease),
    backdrop-filter 0.45s var(--ease), -webkit-backdrop-filter 0.45s var(--ease);
}
/* !important because film.css pins the bare nav with it — `.nav--film
   { background: transparent !important }` — and !important beats specificity.
   This is the one state that rule was not written for. */
.nav--film[data-ink='dark'] {
  background: rgba(var(--mbg-tint), 0.66) !important;
  backdrop-filter: blur(18px) saturate(140%) !important;
  -webkit-backdrop-filter: blur(18px) saturate(140%) !important;
  border-bottom-color: var(--site-line) !important;
}

/* --- Why-Us: the four lines nobody could read -----------------------------
   Same class of miss as the inner pages, one section wide. Why-Us used to be
   a dark band, and hero.css dressed it accordingly:

       .usp-list li span  { color: rgba(242, 241, 238, 0.86) }   (0,1,2)
       .usp-list li       { border-top: rgba(255, 255, 255, 0.14) }

   PASS FOUR brought the section onto the light canvas with `.usp, .usp *`,
   which is (0,1,0) and loses. So the four lines the section exists to make —
   ads, trust, prospects, investors — have been painting near-white on ivory,
   and the rules between them have been white on ivory too.

   The eyebrow, the heading and the closing line were all caught by name in
   that pass; only the list was missed.
   ------------------------------------------------------------------------ */
.usp-list li span { color: var(--site-ink) !important; }
.usp-list li { border-top-color: var(--site-line) !important; }

/* --- …and the same miss in the close --------------------------------------
   film.css dresses `.close-say .reach` (0,2,0) for the dark close it used to
   be, so "Or just write to us:" was painting rgba(242,241,238,0.6) on ivory
   and the address next to it was --accent-lift, the coral that only works on
   black. Both belong to the canvas now.
   ------------------------------------------------------------------------ */
.close-say .reach {
  color: var(--site-ink-2) !important;
  border-top-color: var(--site-line) !important;
}
/* The address itself takes the ink, not the red.
   At a 0.56 wash the ground behind this line reaches rgb(156,152,144), and
   brand red on that measures 2.3:1 — no shade of it clears AA against a
   ground that dark, so darkening further would only make it muddy without
   making it legible. Charcoal reads 10:1 and is already the site's own link
   idiom: .cf-go a is ink with a rule under it, not coloured text. The accent
   stays on the hover. */
.close-say .reach a,
[data-page='inner'] .reach a { color: var(--site-ink) !important; }
.close-say .reach a:hover,
[data-page='inner'] .reach a:hover { color: var(--accent-deep) !important; }

/* --accent-text resolves to --accent-lift under data-env='dark', which is on
   <html> for every page. That was right when the pages were black; on the
   canvas it is a 2.3:1 coral, including for focus rings. The canvas takes the
   deep red, and the two video environments keep the lift. */
:root { --accent-text: var(--accent-deep); }
.hero, .film { --accent-text: var(--accent-lift); }

/* ==========================================================================
   The inner pages, on the same canvas.

   work / services / industries / about / contact were written against
   inner.css's black ground, and PASS FOUR's `html, body { --site-bg }` landed
   on top of them with !important — which left those pages painting near-white
   type (#fff, rgba(242,241,238,…)) onto an ivory background. Every heading and
   every lede on all five pages was invisible.

   The ink is set here rather than by reverting the ground, because one canvas
   across the whole site is the established direction and is the only reading
   under which the motion ground can be continuous at all. Nothing about the
   type itself changes — same faces, same weights, same scale, same hierarchy.
   Only which end of the palette it is drawn from.
   ========================================================================== */

[data-page='inner'] {
  --paper: var(--site-bg);
  --paper-2: var(--site-bg);
  --ink: var(--site-ink);
  --ink-2: var(--site-ink-2);
  --ink-3: var(--site-ink-3);
  --line: var(--site-line);
  --line-2: rgba(30, 26, 22, 0.07);
  --glass: rgba(241, 238, 232, 0.72);
  /* The deep end of the same brand red rather than the bright one. #c0392b as
     *text* measured 3.99:1 on this ground; --accent-deep is 4.86:1 and is
     already in the palette. Fills are untouched — buttons stay #c0392b. */
  --accent-text: var(--accent-deep);
}

[data-page='inner'] body { color: var(--site-ink); }

[data-page='inner'] .label,
[data-page='inner'] .scene .label { color: var(--site-ink-3); }

[data-page='inner'] .head,
[data-page='inner'] .display,
[data-page='inner'] .sub { color: var(--site-ink); }

[data-page='inner'] .body,
[data-page='inner'] .lede { color: var(--site-ink-2); }

[data-page='inner'] .rule,
[data-page='inner'] hr { background: var(--site-line); }

/* Nav and menu, over the canvas rather than over black. */
[data-page='inner'] .nav--inner .logo { color: var(--site-ink); }
[data-page='inner'] .nav--inner .burger { border-color: rgba(30, 26, 22, 0.26); }
[data-page='inner'] .nav--inner .burger span { background: var(--site-ink); }
[data-page='inner'] .nav--inner[data-lifted='true'] {
  background: rgba(241, 238, 232, 0.66);
  border-bottom-color: var(--site-line);
}
[data-page='inner'] .nav-index .nav-link {
  color: rgba(30, 26, 22, 0.9) !important;
  text-shadow: none;
}
[data-page='inner'] .nav-index .nav-link:hover { color: var(--site-ink) !important; }

/* The work grid keeps its dark plates — those are frames around real
   screenshots, not page background — but its captions come back to the ink. */
[data-page='inner'] .wgrid figcaption { border-top-color: var(--site-line); }
[data-page='inner'] .wgrid .w-name { color: var(--site-ink); }
[data-page='inner'] .wgrid .w-kind { color: var(--site-ink-2); }
[data-page='inner'] .wgrid .w-ind { color: var(--accent-deep); }

/* The industry list reads by opacity, which needs a floor once it is dark ink
   on a light ground rather than light ink on black. */
[data-page='inner'] .industry { color: var(--site-ink); }

/* ==========================================================================
   Responsive and preference behaviour.
   ========================================================================== */

/* Phones take the narrow encode (213 KB against 563 KB) and no drift: the
   layer is already oversized against a short viewport, and a transform tied to
   scroll is the first thing to cost frames on a low-end device. */
@media (max-width: 760px) {
  :root { --mbg-veil: 0.72; }
  .mbg-film,
  .mbg-still { transform: scale(1.12); }
}

/* Reduced motion: the ground holds one frame. It is the same picture under the
   same wash, so the page looks the way it is supposed to — it simply stops
   moving, which is what was asked for. */
@media (prefers-reduced-motion: reduce) {
  .mbg-film { display: none; }
  .mbg-film,
  .mbg-still { transform: scale(1.04); }
}

/* Print: no ground at all. */
@media print {
  .mbg { display: none; }
  html, body { background: #fff !important; }
}
