/* =========================================================================
   uroojcreative — packaging design studio
   Zero-build stylesheet. Tokens, base, components, utilities.
   ========================================================================= */

/* -------------------------------------------------------------------------
   1. Tokens

   LIGHT IS THE DEFAULT. Dark is opt-in via [data-theme="dark"] on <html>,
   driven by the switch in the header.

   Orange stays a precious accent: cursor, selection, hairlines, one CTA, the
   rim light in WebGL. It is never used for body copy.

   Note the accent splits in two, because vivid #FF5A00 is only 3.13:1 on
   white and fails as text:
     --orange       accent for TEXT      light #C63F00 5.1:1 · dark #FF5A00 6.4:1
     --orange-fill  vivid brand fill     #FF5A00 in both, always with dark text
     --on-orange    text on that fill    #0A0A0C, 6.3:1
   ---------------------------------------------------------------------- */
:root {
  /* ground */
  --ink:        #FFFFFF;
  --ink-raise:  #F7F7F5;
  --ink-card:   #F1F1EE;
  /* The footer band. Warm rather than neutral grey: it is the one large field
     of colour on the page, and a barely-there orange tint ties it to the
     accent instead of reading as a grey box. Kept pale enough that
     --bone-faint still clears AA on it (4.81:1).

     --footer-accent is a DEEPER orange than the usual --orange. On white
     --orange is 5.1:1, but on this warmer, darker band it drops to 4.59:1 -
     technically passing and far too close to the line for 12px headings.
     The deeper cut restores it to 5.95:1 without washing the band out to
     near-white, which is what lightening the tint instead would have cost. */
  --footer-bg:      #FBF1EB;
  --footer-accent:  #A83500;
  --plate:      #FFFFFF;   /* the field cut-out product shots sit on */
  --hairline:   rgba(10, 10, 12, 0.16);
  --hairline-2: rgba(10, 10, 12, 0.08);

  /* text — every value clears WCAG AA on --ink.
     bone 19.8:1 · bone-dim 8.8:1 · bone-faint 5.4:1 */
  --bone:       #0A0A0C;
  --bone-dim:   #4A4A52;
  --bone-faint: #6A6A73;

  /* accent */
  --orange:      #C63F00;
  --orange-fill: #FF5A00;
  --on-orange:   #0A0A0C;
  --orange-lift: #E04E00;
  --orange-deep: #A83500;
  --orange-wash: rgba(255, 90, 0, 0.12);

  /* Neutral overlay tint and the frosted header plate. Both must flip with
     the theme, so they are tokens rather than literal rgba in components. */
  --veil:  rgba(10, 10, 12, 0.035);
  --glass: rgba(255, 255, 255, 0.80);

  color-scheme: light;

  /* rhythm — the gutter and the page margin are the same number, always */
  --pad:    clamp(20px, 3.2vw, 58px);
  --gap:    var(--pad);
  /* Measured off the reference: it runs 48px padding-block on every
     section (80 on the hero). Ours was up to 150, which is what made the
     page read as loose. This lands at ~55 at 1440. */
  --section: clamp(38px, 3.8vw, 58px);

  /* type

     Scale measured off the reference and applied 1:1 at desktop. Two
     families, mirroring how the reference splits the job:

       --font-display   headings   geometric sans
       --font           body / UI  Poppins

     The reference sets its headings in Area Normal (Blaze Type). The copy
     running on that site is the *trial* cut, which is licensed for comps
     only and cannot ship — so display falls to Outfit, the nearest free
     geometric. If Area is ever licensed properly (Adobe Fonts carries it),
     that is a one-token swap: put 'Area' at the head of --font-display.

     Two habits define the look and both are unusual:
       · nothing is bolder than 400 — hierarchy comes from size, not weight
       · small text tracks OPEN (+1px at 15px), large text tracks TIGHT
     Tracking is in em so it survives the fluid clamps; the px equivalents
     at the reference's own desktop sizes are noted per token. */
  --font-display: 'Outfit', 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --font:         'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;

  --track-display: -0.018em;  /* -0.9px @ 60px  — large type only */
  --track-title:    0.025em;  /* +1px   @ 40px */
  --track-h2:      -0.025em;  /* -0.8px @ 32px  — the case-study section head */
  --track-sub:      0.025em;  /* +0.5px @ 20px */
  --track-body:     0.065em;  /* +1px   @ 15px  — the signature airiness */
  --track-ui:       0.0625em; /* +1px   @ 16px */

  /* Sizes top out at the reference's desktop values. 60px is the ceiling
     for the entire site; there is no giant display tier.

     Each vw term is set so the token reaches that ceiling at a 1440px
     viewport — the common laptop — rather than drifting under it until
     1650px. Below that they scale down; above, they hold. */
  --t-display: clamp(2.125rem, 4.2vw, 3.75rem);   /* 34 → 60 */
  --t-h1:      clamp(1.875rem, 3.35vw, 3rem);     /* 30 → 48 */
  --t-title:   clamp(1.625rem, 2.8vw, 2.5rem);    /* 26 → 40 */
  --t-h2:      clamp(1.375rem, 2.25vw, 2rem);     /* 22 → 32 */
  --t-lead:    clamp(1.0625rem, 1.7vw, 1.5rem);   /* 17 → 24 */
  --t-sub:     clamp(1rem, 1.4vw, 1.25rem);       /* 16 → 20 */
  --t-ui:      1rem;                              /* 16, flat — nav, links, buttons */
  --t-body:    clamp(0.875rem, 1.05vw, 0.9375rem);/* 14 → 15 */
  --t-small:   clamp(0.8125rem, 1vw, 0.875rem);   /* 13 → 14 */
  --t-micro:   clamp(0.6875rem, 0.85vw, 0.75rem); /* 11 → 12 */

  /* leading, straight off the reference */
  --lh-display: 1.1;    /* 60/72 hero · 48/48 case-study title */
  --lh-title:   1.2;    /* 40/48 */
  --lh-lead:    1.5;    /* 24/36 */
  --lh-body:    1.6;    /* 15/24 · 16/25.6 */

  /* image bands — aspect varies per project, row height never does */
  --band:      clamp(300px, 42vw, 760px);
  --band-tall: clamp(400px, 56vw, 1000px);
  --band-low:  clamp(180px, 25vw, 470px);

  /* motion */
  --ease:     cubic-bezier(0.5, 1, 0.89, 1);
  --ease-out: cubic-bezier(0.33, 1, 0.68, 1);
  --ease-io:  cubic-bezier(0.65, 0, 0.35, 1);
  --dur:      0.6s;
  --dur-fast: 0.3s;

  --radius: 14px;
  --radius-s: 8px;
}

/* -------------------------------------------------------------------------
   1b. Dark theme
   Opt-in only. Set by the header switch, which writes data-theme on <html>
   and remembers the choice. Because every component reads from the tokens
   above, the theme only redefines tokens — no component CSS is duplicated.

   Contrast on #08080A, measured:
     #F2F1EF  17.7:1   body and display
     #A8A6A2   8.2:1   secondary
     #828079   5.1:1   labels — the floor, still clears AA
     #FF5A00   6.4:1   accent, vivid enough to use as text here
   ---------------------------------------------------------------------- */
[data-theme='dark'] {
  --ink:        #08080A;
  --ink-raise:  #101013;
  --ink-card:   #141418;
  /* Dark twin of the footer band. It lifts OFF the near-black page rather
     than going light — a pale band here would flare in a dark room and
     undo the whole point of the dark theme. Same job, opposite direction.
     The accent goes the other way too: the deep orange that works on the
     light band would disappear here, so it reverts to the bright one. */
  --footer-bg:      #121215;
  --footer-accent:  #FF7A33;
  /* Stays white: the product cut-outs are flattened onto white, so their
     field has to be white in both themes or a seam appears. On a dark page
     they read as catalogue plates, which is deliberate. */
  --plate:      #FFFFFF;
  --hairline:   rgba(255, 255, 255, 0.12);
  --hairline-2: rgba(255, 255, 255, 0.06);

  --bone:       #F2F1EF;
  --bone-dim:   #A8A6A2;
  --bone-faint: #828079;

  --orange:      #FF5A00;
  --orange-fill: #FF5A00;
  --on-orange:   #0A0A0C;
  --orange-lift: #FF8236;
  --orange-deep: #C63F00;
  --orange-wash: rgba(255, 90, 0, 0.14);

  --veil:  rgba(255, 255, 255, 0.022);
  --glass: rgba(8, 8, 10, 0.72);

  color-scheme: dark;
}

/* Repainting the whole page instantly is jarring; a short cross-fade is not.
   Scoped to the switch being used, so it never delays first paint. */
html.theme-anim,
html.theme-anim body,
html.theme-anim .header,
html.theme-anim .menu,
html.theme-anim .block__frame,
html.theme-anim .tile__frame,
html.theme-anim .media {
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease),
              border-color 0.35s var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  html.theme-anim, html.theme-anim * { transition: none !important; }
}

/* -------------------------------------------------------------------------
   2. Reset + base
   ---------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--font);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: var(--lh-body);
  letter-spacing: var(--track-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }

/* Headings take the display family and stay at 400 — the reference never
   goes bolder, and the restraint is most of the character. */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
}
h1 { font-size: var(--t-h1);    line-height: var(--lh-display); letter-spacing: var(--track-display); }
h2 { font-size: var(--t-h2);    line-height: var(--lh-title);   letter-spacing: var(--track-display); }
h3 { font-size: var(--t-sub);   line-height: 1.4;               letter-spacing: var(--track-sub); }
h4 { font-size: var(--t-body);  line-height: var(--lh-body);    letter-spacing: var(--track-sub); }

/* Nav, links and buttons all sit at 16/25.6 with open tracking. */
a, button { letter-spacing: var(--track-ui); }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, video, canvas, svg { display: block; max-width: 100%; }
img, video { height: auto; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* White on --orange is only 3.13:1; near-black on it is 6.4:1 and reads more
   like the identity anyway. */
::selection { background: var(--orange-fill); color: var(--on-orange); }

/* Orange pointer, the only chroma that follows the mouse.
   Fine pointers only — never hijack touch. */
@media (hover: hover) and (pointer: fine) {
  body {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18'%3E%3Ccircle cx='9' cy='9' r='4.5' fill='%23FF5A00'/%3E%3C/svg%3E") 9 9, auto;
  }
  /* The arrow carries a white ring so it stays visible when it lands on the
     orange-filled CTA, not just on the black ground. */
  a:hover, button:hover, .tile:hover {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28'%3E%3Ccircle cx='14' cy='14' r='11' fill='%23FF5A00' stroke='%23ffffff' stroke-width='1.5'/%3E%3Cpath d='M10 14h8m-3.2-3.4L18 14l-3.2 3.4' stroke='%23ffffff' stroke-width='1.7' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 14 14, pointer;
  }
  /* Keep the I-beam where text is actually selectable, so the decorative dot
     never hides that affordance. */
  p, li, dd, dt, blockquote, h1, h2, h3, h4, figcaption, input, textarea {
    cursor: text;
  }
  a p, a li, a h1, a h2, a h3, a h4, a figcaption,
  button p, .tile p, .tile figcaption { cursor: inherit; }
}

:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 3px;
  border-radius: 2px;
}

/* -------------------------------------------------------------------------
   3. Layout primitives
   ---------------------------------------------------------------------- */
.wrap {
  width: 100%;
  padding-inline: var(--pad);
  margin-inline: auto;
  max-width: 2100px;
}

.rule {
  height: 1px;
  border: 0;
  margin: 0;
  background: var(--hairline);
}
.rule--accent { background: linear-gradient(90deg, var(--orange), transparent 62%); }

.section { padding-block: var(--section); }

.eyebrow {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bone-faint);
  font-weight: 500;
}
.eyebrow--accent { color: var(--orange); }

.display {
  font-family: var(--font-display);
  font-size: var(--t-display);
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  font-weight: 400;
  /* optical hang so the glyph edge, not the box edge, meets the gutter.
     Shallower than before — at 60px the old -0.045em over-hung. */
  margin-left: -0.02em;
}
.display em {
  font-style: normal;
  color: var(--bone-dim);
}

.title {
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
  letter-spacing: var(--track-title);
  font-weight: 400;
}

.lead {
  font-size: var(--t-lead);
  line-height: var(--lh-lead);
  letter-spacing: 0.04em;   /* +1px @ 24px */
  color: var(--bone-dim);
  max-width: 46ch;
}

.dim { color: var(--bone-dim); }
.faint { color: var(--bone-faint); }

/* -------------------------------------------------------------------------
   4. Header
   ---------------------------------------------------------------------- */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  padding: clamp(14px, 1.5vw, 22px) var(--pad);
  transition: transform 0.45s var(--ease-io), background-color 0.3s linear,
              backdrop-filter 0.3s linear, border-color 0.3s linear;
  border-bottom: 1px solid transparent;
}
.header.is-stuck {
  background: var(--glass);
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  border-bottom-color: var(--hairline-2);
}
.header.is-hidden { transform: translateY(-102%); }

/* On a page whose hero is full-bleed footage, the header floats over a dark
   scrim before it sticks — so it must stay light-on-dark regardless of theme,
   then hand back to the theme once it gains its plate. */
.header--overlay:not(.is-stuck) {
  --bone:       #F2F1EF;
  --bone-faint: #B4B2AD;
  color: #F2F1EF;
}

/* Desktop lays the header out as three columns rather than space-between.
   With space-between the leftover width is split evenly between the gaps, so
   the nav lands halfway between the brand and the switch — not in the middle
   of the page. The brand block is ~148px and the switch ~71px, which pushed
   the nav 38px right of true centre. 1fr | auto | 1fr pins the middle column
   to the page centre no matter how wide either side grows.

   Only above 720px: below that .nav is display:none and .nav-toggle takes
   over, and plain flex already places those three correctly. */
@media (min-width: 721px) {
  .header {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
  }
  .brand  { justify-self: start; }
  .nav    { justify-self: center; }
  .themer { justify-self: end; }
}

.brand {
  display: flex;
  align-items: center;
  gap: 14px;
}
.brand__mark {
  width: clamp(34px, 3.5vw, 50px);
  height: auto;
  fill: var(--bone);
  transition: fill var(--dur-fast) var(--ease);
}
.brand:hover .brand__mark { fill: var(--orange); }
.brand__name {
  font-family: var(--font-display);
  font-size: clamp(0.9375rem, 1.25vw, 1.0625rem);   /* 15 → 17, full size by 1360px */
  font-weight: 500;
  letter-spacing: var(--track-ui);
}
.brand__name span { color: var(--bone-faint); font-weight: 400; }

/* The nav sits deliberately below body size — it is orientation, not content,
   and dropping it lets the wordmark carry the header. */
.nav { display: flex; align-items: center; gap: clamp(16px, 1.9vw, 32px); }
.nav a { font-size: clamp(0.75rem, 0.95vw, 0.8125rem); }   /* 12 → 13 */

/* ---- theme switch ------------------------------------------------------
   A track with a knob that slides. Labelled for screen readers via
   aria-label + aria-pressed on the button itself. */
.themer {
  --h: 26px;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
  padding: 9px 0;          /* pads the hit area to 44px without moving the track */
  color: var(--bone-faint);
  transition: color var(--dur-fast) var(--ease);
}
.themer:hover { color: var(--bone); }

.themer__track {
  position: relative;
  width: calc(var(--h) * 1.85);
  height: var(--h);
  border-radius: var(--h);
  border: 1px solid var(--hairline);
  background: var(--veil);
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.themer__knob {
  position: absolute;
  top: 50%;
  left: 3px;
  width: calc(var(--h) - 8px);
  height: calc(var(--h) - 8px);
  margin-top: calc((var(--h) - 8px) / -2);
  border-radius: 50%;
  background: var(--orange-fill);
  transition: transform 0.32s var(--ease-out);
}
[data-theme='dark'] .themer__knob {
  transform: translateX(calc(var(--h) * 0.85));
}
.themer__icon {
  width: 15px;
  height: 15px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}
/* Sun in light mode, moon in dark — only one is ever shown. */
.themer__icon--moon { display: none; }
[data-theme='dark'] .themer__icon--sun  { display: none; }
[data-theme='dark'] .themer__icon--moon { display: block; }

@media (max-width: 720px) {
  /* At phone widths the switch belongs inside the overlay, and initMenu moves
     it there. This hides it only while it is still a child of the header, so
     it never flashes beside the burger on the frame before the move. Once
     moved it is .menu .themer, which this selector no longer matches.

     Nothing is lost if the module fails: the switch is inert without JS
     anyway, since initTheme is what makes it do something. */
  .header > .themer { display: none; }
}

/* ---- mobile menu -------------------------------------------------------
   Four inline links plus the wordmark do not fit under ~720px, so below that
   the nav becomes a full-screen overlay. */
.nav-toggle {
  display: none;
  align-items: center;
  gap: 9px;
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bone);
  padding: 10px 4px;           /* keeps the tap target at 44px */
  min-height: 44px;
}
.nav-toggle__bars { display: grid; gap: 4px; width: 20px; }
.nav-toggle__bars i {
  display: block;
  height: 1px;
  background: currentColor;
  transition: transform var(--dur-fast) var(--ease-out), opacity var(--dur-fast) linear;
}
.menu-open .nav-toggle__bars i:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.menu-open .nav-toggle__bars i:nth-child(2) { opacity: 0; }
.menu-open .nav-toggle__bars i:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

.menu {
  position: fixed;
  inset: 0;
  z-index: 55;
  display: grid;
  align-content: center;
  gap: 6px;
  padding: var(--pad);
  background: var(--ink);
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform 0.5s var(--ease-io), visibility 0s linear 0.5s;
}
.menu-open .menu {
  transform: translateY(0);
  visibility: visible;
  transition: transform 0.5s var(--ease-io), visibility 0s linear 0s;
}
.menu a {
  font-family: var(--font-display);
  font-size: var(--t-display);
  font-weight: 400;
  letter-spacing: var(--track-display);
  line-height: 1.15;
  padding-block: 6px;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease), color var(--dur-fast) linear;
}
.menu-open .menu a { opacity: 1; transform: none; }
.menu-open .menu a:nth-child(1) { transition-delay: 0.14s; }
.menu-open .menu a:nth-child(2) { transition-delay: 0.20s; }
.menu-open .menu a:nth-child(3) { transition-delay: 0.26s; }
.menu-open .menu a:nth-child(4) { transition-delay: 0.32s; }
.menu a:hover, .menu a:focus-visible { color: var(--orange); }

/* The theme switch once initMenu has moved it in here (see reveal.js).
   It rides the same stagger as the links, one step behind the last one. */
.menu .themer {
  justify-self: start;
  margin-top: clamp(24px, 5vw, 44px);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease),
              color var(--dur-fast) var(--ease);
}
.menu-open .menu .themer {
  opacity: 1;
  transform: none;
  transition-delay: 0.38s;
}

.menu__foot {
  margin-top: clamp(28px, 6vw, 56px);
  font-size: var(--t-small);
  color: var(--bone-dim);
}
/* The switch already supplies the breathing room above the email line. */
.menu .themer + .menu__foot { margin-top: clamp(18px, 3.5vw, 30px); }

@media (max-width: 720px) {
  .nav { display: none; }
  .nav-toggle { display: flex; }
  /* The overlay owns the screen; stop the page scrolling behind it. */
  .menu-open body, body.menu-open { overflow: hidden; }
}
@media (min-width: 721px) {
  .menu { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .menu, .menu a { transition: none !important; }
}

/* The site's one interaction primitive: a 1px directional underline wipe.
   Wipes in from the left, retracts out to the right. */
.wipe { position: relative; display: inline-block; }
.wipe::before {
  content: '';
  position: absolute;
  left: 0;
  top: 100%;
  width: 100%;
  height: 1px;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform var(--dur-fast) var(--ease-out);
}
.wipe:hover::before,
.wipe[aria-current='page']::before {
  transform: scaleX(1);
  transform-origin: 0% 50%;
}

/* -------------------------------------------------------------------------
   5. Buttons — circle grows into a pill, filled orange
   ---------------------------------------------------------------------- */
.btn {
  --h: clamp(46px, 4vw, 58px);
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--h);
  padding-left: calc(var(--h) + 18px);
  padding-right: 26px;
  font-size: var(--t-ui);
  font-weight: 400;
  letter-spacing: var(--track-ui);
  isolation: isolate;
}
.btn::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  z-index: -1;
  width: var(--h);
  height: var(--h);
  border: 1px solid var(--orange);
  border-radius: calc(var(--h) / 2);
  background: transparent;
  transition: width 0.42s var(--ease-out), background-color 0.42s var(--ease-out);
}
.btn::after {
  content: '';
  position: absolute;
  left: calc(var(--h) / 2 - 5px);
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  border-right: 1.5px solid var(--orange);
  border-top: 1.5px solid var(--orange);
  transform: rotate(45deg);
  transition: border-color 0.42s var(--ease-out), transform 0.42s var(--ease-out);
}
.btn:hover::before { width: 100%; background: var(--orange-fill); }
.btn:hover::after { border-color: var(--on-orange); transform: rotate(45deg) translate(2px, -2px); }
/* Near-black on orange is 6.4:1; white would be 3.13:1 and fail AA. */
.btn:hover { color: var(--on-orange); }

.btn--ghost::before { border-color: var(--hairline); }
.btn--ghost::after { border-color: var(--bone-dim); }

/* -------------------------------------------------------------------------
   6. Hero
   The 3D canvas gets its own band. Nothing is overlaid on it — the headline
   sits below, separated by a hairline.
   ---------------------------------------------------------------------- */
.hero { padding-top: clamp(78px, 8vw, 116px); }

.stage {
  position: relative;
  width: 100%;
  height: clamp(360px, 62vh, 720px);
  border-radius: var(--radius);
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 50% 8%, var(--orange-wash), transparent 58%),
    var(--ink-raise);
  border: 1px solid var(--hairline-2);
}
.stage__canvas { width: 100%; height: 100%; display: block; }

/* Specular sweep — the "glossy" read, done in CSS so it costs nothing.
   It has to invert with the theme: screening white onto a white page does
   nothing, so on light we multiply a faint dark sweep instead. */
.stage::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(104deg,
    transparent 34%,
    rgba(10, 10, 12, 0.05) 47%,
    rgba(10, 10, 12, 0.02) 53%,
    transparent 66%);
  mix-blend-mode: multiply;
}
[data-theme='dark'] .stage::after {
  background: linear-gradient(104deg,
    transparent 34%,
    rgba(255, 255, 255, 0.055) 47%,
    rgba(255, 255, 255, 0.02) 53%,
    transparent 66%);
  mix-blend-mode: screen;
}

.stage__hint {
  position: absolute;
  left: clamp(16px, 2vw, 26px);
  bottom: clamp(14px, 1.6vw, 22px);
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bone-faint);
  pointer-events: none;
}

.hero__statement {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(22px, 3vw, 46px);
  padding-block: clamp(34px, 4.4vw, 68px);
}
@media (min-width: 900px) {
  .hero__statement { grid-template-columns: 1.35fr 1fr; align-items: end; }
}
.hero__meta { display: flex; flex-direction: column; gap: 18px; }

/* -------------------------------------------------------------------------
   6b. Film hero — edge-to-edge video, no frame, no radius.
   Deliberately outside .wrap so it ignores the page gutter entirely.
   ---------------------------------------------------------------------- */
.filmhero {
  position: relative;
  width: 100%;
  height: 100vh;
  margin: 0;
  overflow: hidden;
  background: #000;
  isolation: isolate;

  /* These sit on a dark scrim over footage, so their type stays light no
     matter which theme the rest of the page is in. Redefining the tokens
     locally means the components inside need no theme-specific rules. */
  --bone:       #F2F1EF;
  --bone-dim:   #D6D4D0;
  --bone-faint: #B4B2AD;
  --orange:     #FFC4A2;
  color: var(--bone);
}
@supports (height: 100svh) {
  .filmhero { height: 100svh; }
}

.filmhero__media,
.filmhero__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* cover, so it fills any aspect without letterboxing or borders */
  object-fit: cover;
  object-position: center;
}
.filmhero__poster { z-index: 1; transition: opacity 0.7s var(--ease); }
.filmhero.is-playing .filmhero__poster { opacity: 0; }

/* Scrim. Tuned against the actual footage, which is a bright warm yellow at
   ~0.63 relative luminance — near-white type on that raw is 1.4:1. The stack
   below lands the header band near 7:1 and the headline band near 5:1, and the
   flat tint also pulls a bright video into the near-black brand world.
   If you swap in much darker footage, ease these back. */
.filmhero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(to top, rgba(8, 8, 10, 0.88) 0%, rgba(8, 8, 10, 0) 55%),
    linear-gradient(to bottom, rgba(8, 8, 10, 0.72) 0%, rgba(8, 8, 10, 0.45) 10%, rgba(8, 8, 10, 0) 30%),
    rgba(8, 8, 10, 0.18);
}

.filmhero__inner {
  position: relative;
  z-index: 3;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: clamp(16px, 2vw, 30px);
  padding: var(--pad);
  padding-bottom: clamp(38px, 5vw, 84px);
}
.filmhero__row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gap);
  flex-wrap: wrap;
}

.filmhero__scroll {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bone-dim);
}
.filmhero__scroll i {
  display: block;
  width: 1px;
  height: 34px;
  background: linear-gradient(to bottom, var(--orange), transparent);
  animation: drop 1.9s var(--ease-io) infinite;
}
@keyframes drop {
  0%   { transform: scaleY(0); transform-origin: 0 0; opacity: 0; }
  40%  { transform: scaleY(1); transform-origin: 0 0; opacity: 1; }
  60%  { transform: scaleY(1); transform-origin: 0 100%; opacity: 1; }
  100% { transform: scaleY(0); transform-origin: 0 100%; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .filmhero__scroll i { animation: none; transform: none; opacity: 1; }
}

/* -------------------------------------------------------------------------
   6c. Holding page — a project that exists but has no assets yet.
   Deliberately austere: it should read as "not finished", not as "broken".
   ---------------------------------------------------------------------- */
.holding {
  min-height: 100vh;
  display: grid;
  align-content: center;
  gap: clamp(20px, 2.6vw, 40px);
  padding: calc(var(--pad) + clamp(60px, 8vw, 110px)) var(--pad) var(--section);
  position: relative;
  overflow: hidden;
}
@supports (height: 100svh) {
  .holding { min-height: 100svh; }
}
.holding::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(90% 70% at 12% 0%, rgba(255, 90, 0, 0.10), transparent 60%),
    repeating-linear-gradient(-45deg, var(--veil) 0 14px, transparent 14px 28px);
}
.holding > * { position: relative; }
.holding__meta {
  display: flex;
  gap: clamp(18px, 3vw, 48px);
  flex-wrap: wrap;
  padding-top: clamp(8px, 1.4vw, 18px);
  border-top: 1px solid var(--hairline-2);
  margin-top: clamp(10px, 1.6vw, 22px);
}
.holding__meta div { min-width: 130px; }
.holding__k {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bone-faint);
  margin-bottom: 6px;
}
.holding__v { font-size: var(--t-small); color: var(--bone-dim); }

/* -------------------------------------------------------------------------
   6d. Case study opening — no hero image.
   The page opens on the title, so the first thing the reader meets is the
   claim, not a picture. Title and intro sit 50/50 on desktop with the
   discipline list running full width beneath, then a rule.
   ---------------------------------------------------------------------- */
.caseopen {
  padding-top: clamp(104px, 13vw, 200px);
  padding-bottom: clamp(30px, 3.6vw, 60px);
}
.caseopen__split {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(24px, 3vw, 56px);
}
@media (min-width: 900px) {
  .caseopen__split { grid-template-columns: 1fr 1fr; align-items: start; }
}
.caseopen__intro p + p { margin-top: 1.15em; }

/* Discipline list — one inline run separated by slashes. */
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin-top: clamp(28px, 3.4vw, 56px);
  font-size: var(--t-small);
  color: var(--bone-dim);
}
.tags li:not(:last-child)::after {
  content: '/';
  margin: 0 0.7em;
  color: var(--bone-faint);
}

/* -------------------------------------------------------------------------
   6e. Case study body — bare media blocks, no captions.
   Alternates full-width and two-up so the page has rhythm without needing
   any text between the pictures.
   ---------------------------------------------------------------------- */
.blocks { display: flex; flex-direction: column; gap: clamp(14px, 1.6vw, 26px); }

.block { display: flex; flex-wrap: wrap; gap: clamp(14px, 1.6vw, 26px); }
.block--full figure { width: 100%; }
.block--two figure { width: calc(50% - clamp(14px, 1.6vw, 26px) / 2); }
@media (max-width: 760px) {
  .block--two figure { width: 100%; }
}

.block figure { margin: 0; }

/* The frame takes its proportion from the artwork it holds, set per block via
   --ar. That way nothing has to be cropped to fit an arbitrary band, and the
   two figures in a row still line up because they share one ratio. */
.block__frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ar, 16 / 9);
  border-radius: var(--radius);
  /* --plate, not --ink-card: these hold cut-out product renders flattened onto
     white, so the field behind them must be white in BOTH themes or a seam
     shows. In light the frame dissolves into the page; in dark it reads as a
     catalogue plate. */
  background: var(--plate);
  border: 1px solid var(--hairline-2);
}
.block__frame img {
  width: 100%;
  height: 100%;
  /* contain, never cover — the whole pack must be visible. Any letterboxing is
     the same white as the plate, so it simply disappears. */
  object-fit: contain;
  object-position: center;
  transition: transform 0.7s var(--ease);
}
.block figure:hover .block__frame img { transform: scale(1.02); }

/* In light mode the plate IS the page, so the outline would just draw a box
   around a product meant to be floating. */
:root:not([data-theme='dark']) .block__frame {
  border-color: transparent;
  border-radius: 0;
}

/* A quiet category marker above a block, so the range still reads by category
   without putting a caption under every picture. */
.block__label {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bone-faint);
  padding-bottom: 12px;
  margin-top: clamp(26px, 3.2vw, 52px);
  border-top: 1px solid var(--hairline-2);
  padding-top: 14px;
  display: flex;
  justify-content: space-between;
  gap: 16px;
}
.block__label span:last-child { color: var(--orange); }
.blocks > .block__label:first-child { margin-top: 0; border-top: 0; padding-top: 0; }

/* -------------------------------------------------------------------------
   6f. Related projects
   ---------------------------------------------------------------------- */
.related { display: grid; grid-template-columns: 1fr; gap: var(--gap); }
@media (min-width: 780px) {
  .related { grid-template-columns: repeat(3, 1fr); }
}
.related .tile__frame { height: clamp(200px, 24vw, 340px); }

/* -------------------------------------------------------------------------
   6g. About — word-split reveal
   Each word is <span class="w"><i>word</i></span>. The span masks, the <i>
   moves, so words rise out of nothing instead of only fading. Stagger comes
   from --i, set per word by splitWords().
   ---------------------------------------------------------------------- */
[data-split] .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* room for descenders, which the mask would otherwise clip */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}
[data-split] .w > i {
  display: inline-block;
  font-style: inherit;
  transform: translateY(105%);
  opacity: 0;
  transition:
    transform 0.85s cubic-bezier(0.16, 1, 0.3, 1) calc(var(--i) * 55ms),
    opacity   0.6s  ease                          calc(var(--i) * 55ms);
}
[data-split].is-in .w > i { transform: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  [data-split] .w > i { transform: none !important; opacity: 1 !important; transition: none !important; }
}

/* Body copy and blocks that fade up as a whole. */
[data-rise] {
  opacity: 0;
  transform: translateY(40px);
  transition: opacity 0.8s var(--ease) var(--rise-delay, 0ms),
              transform 0.8s cubic-bezier(0.16, 1, 0.3, 1) var(--rise-delay, 0ms);
}
[data-rise].is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-rise] { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* -------------------------------------------------------------------------
   6h. About — hero
   ---------------------------------------------------------------------- */
.abouthero {
  padding-top: clamp(120px, 15vw, 220px);
  padding-bottom: clamp(30px, 4vw, 60px);
  text-align: center;
}
.abouthero .display { margin-left: 0; }
.abouthero__lead {
  max-width: 62ch;
  margin: clamp(20px, 2.4vw, 34px) auto 0;
  font-size: var(--t-lead);
  color: var(--bone-dim);
}
.abouthero__cta {
  display: flex;
  gap: clamp(12px, 1.6vw, 22px);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: clamp(26px, 3vw, 44px);
}
.abouthero__banner {
  margin-top: clamp(34px, 4.6vw, 76px);
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--hairline-2);
  aspect-ratio: 2 / 1;
}
.abouthero__banner img { width: 100%; height: 100%; object-fit: cover; }

/* -------------------------------------------------------------------------
   6i. About — sticky process stepper
   The panel pins at the top while the step cards scroll past it.
   ---------------------------------------------------------------------- */
.process__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(22px, 3vw, 46px);
  align-items: stretch;
}
/* Even halves, and the same gap as the statement row above it, so the panel's
   left edge lines up with the lead paragraph and the two sides balance. */
@media (min-width: 900px) {
  .process__grid,
  #process .hero__statement { grid-template-columns: 1fr 1fr; }
}

/* Stretches to the full height of the four steps rather than pinning at a
   fixed height, so the panel and the timeline end on the same line. */
.process__panel {
  position: relative;
  display: grid;
  place-items: center;
  min-height: clamp(320px, 62vh, 620px);
  border-radius: var(--radius);
  background:
    radial-gradient(85% 70% at 50% 30%, var(--orange-wash), transparent 62%),
    var(--ink-raise);
  border: 1px solid var(--hairline-2);
  overflow: hidden;
}
/* The mark replaces the reference site's animal photo — flat, not 3D. */
.process__mark {
  width: min(50%, 420px);
  aspect-ratio: 202 / 158;
  background: var(--bone);
  -webkit-mask: url('/assets/img/brand/urooj-mark.svg') no-repeat center / contain;
  mask: url('/assets/img/brand/urooj-mark.svg') no-repeat center / contain;
  transition: background-color 0.5s var(--ease), transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
.process__panel.is-lit .process__mark { background: var(--orange-fill); }

/* Must come AFTER the .process__panel rule above, which sets display:grid —
   same specificity, so source order decides. The reference hides its image on
   phones too, leaving the rail and rings to own the layout. */
@media (max-width: 899px) {
  .process__panel { display: none; }
}

/* A vertical timeline, not cards. The reference draws one hairline down the
   whole stack and hangs a ring on each step; there is no panel, border,
   radius or fill anywhere on a row. Measured off the reference:
     row      position:relative, padding-left 56px
     rail     left 10.75px, width 0.5px, full height of the stack
     ring     22px accent disc with an 8px page-coloured centre
     label    14px / +3px tracking / uppercase / accent
     title    40px / 1.2 / weight 400, second half in the accent
     body     16px / 1.5, max-width 512px
   The rail is 1px rather than the reference's 0.5px: sub-pixel borders drop
   out entirely on non-retina displays, where 0.5px rounds to nothing. */
.process__steps {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(44px, 4.4vw, 64px);
}
.process__steps::before {
  content: '';
  position: absolute;
  left: 10.5px;              /* centres under the 22px ring */
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--orange);
}

.step {
  position: relative;
  padding-left: 56px;
}
.step__dot {
  position: absolute;
  left: 0;
  top: 2px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--orange-fill);
  /* The centre is the page ground, not white, so the ring reads as a punched
     hole in both themes rather than a light dot floating on dark. */
  box-shadow: inset 0 0 0 7px var(--ink);
}
.step__n {
  font-size: var(--t-small);
  letter-spacing: 0.214em;   /* +3px at 14px */
  text-transform: uppercase;
  color: var(--orange);
  font-variant-numeric: tabular-nums;
  margin-bottom: 12px;
}
.step__t {
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
  font-weight: 400;
  letter-spacing: normal;    /* the reference sets tracking-normal here */
  margin-bottom: 16px;
}
.step__t span { color: var(--orange); }
.step__b {
  color: var(--bone-dim);
  font-size: var(--t-ui);
  line-height: var(--lh-lead);
  letter-spacing: 0.025em;
  max-width: 512px;
}

/* -------------------------------------------------------------------------
   6j. About — global reach map
   The SVG carries no colour of its own, so it recolours with the theme.
   ---------------------------------------------------------------------- */
.reach__map {
  position: relative;
  margin-top: clamp(28px, 3.4vw, 56px);
}
.wm { width: 100%; height: auto; display: block; overflow: visible; }
.wm__land circle {
  r: 1.6;
  fill: var(--bone);
  opacity: 0.18;
}
[data-theme='dark'] .wm__land circle { opacity: 0.22; }

.wm__dot { fill: var(--orange-fill); }
.wm__ping {
  fill: none;
  stroke: var(--orange-fill);
  stroke-width: 1.5;
  transform-origin: center;
  transform-box: fill-box;
  animation: wm-ping 2.8s cubic-bezier(0.2, 0.6, 0.35, 1) infinite;
}
.wm__ping--2 { animation-delay: 1.4s; }

@keyframes wm-ping {
  0%   { transform: scale(1);   opacity: 0.9; }
  70%  { transform: scale(4.2); opacity: 0;   }
  100% { transform: scale(4.2); opacity: 0;   }
}
@media (prefers-reduced-motion: reduce) {
  .wm__ping { animation: none; opacity: 0.35; }
}

/* Labels are HTML, positioned over the SVG in percentages, so they stay
   crisp and selectable rather than being baked into the graphic. */
.wm__label {
  position: absolute;
  transform: translate(-50%, -140%);
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bone);
  white-space: nowrap;
  padding: 5px 9px;
  border-radius: 100px;
  background: var(--ink-raise);
  border: 1px solid var(--hairline);
  pointer-events: none;
}
@media (max-width: 700px) {
  .wm__label { font-size: 9px; padding: 3px 6px; letter-spacing: 0.08em; }
}

.reach__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--gap);
  margin-top: clamp(34px, 4.4vw, 70px);
  border-top: 1px solid var(--hairline-2);
  padding-top: clamp(28px, 3.4vw, 52px);
}
.reach__n {
  font-family: var(--font-display);
  font-size: var(--t-display);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--track-display);
  font-variant-numeric: tabular-nums;
}
.reach__n span { color: var(--orange); }
.reach__l { color: var(--bone-dim); font-size: var(--t-small); margin-top: 10px; max-width: 30ch; }

/* -------------------------------------------------------------------------
   6k. Contact — form, detail cards, office grid
   ---------------------------------------------------------------------- */
.contact__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(30px, 4vw, 64px);
  align-items: start;
}
@media (min-width: 920px) {
  .contact__grid { grid-template-columns: 1.15fr 0.85fr; }
}

.form { display: grid; gap: clamp(14px, 1.6vw, 20px); }
.form__row { display: grid; grid-template-columns: 1fr; gap: inherit; }
@media (min-width: 620px) { .form__row { grid-template-columns: 1fr 1fr; } }

.field { display: flex; flex-direction: column; gap: 8px; }
.field > label {
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bone-faint);
}
.field > label .req { color: var(--orange); }

.field input,
.field select,
.field textarea {
  width: 100%;
  font: inherit;
  font-size: var(--t-small);
  color: var(--bone);
  background: var(--ink-raise);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-s);
  padding: 13px 15px;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
  appearance: none;
}
.field textarea { min-height: 148px; resize: vertical; }

/* The native arrow disappears under appearance:none, so draw one. */
.field select {
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 19px) center, calc(100% - 14px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 40px;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--orange);
  background: var(--ink-card);
}
.field input::placeholder,
.field textarea::placeholder { color: var(--bone-faint); }

/* Validation styling only after a submit attempt, so the form is not red
   before the visitor has typed anything. */
.form.is-checked :invalid { border-color: var(--orange-deep); }
.field__err { font-size: var(--t-micro); color: var(--orange); display: none; }
.form.is-checked :invalid ~ .field__err { display: block; }

.form__submit { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 6px; }
.form__note { font-size: var(--t-micro); color: var(--bone-faint); }

.form__status {
  font-size: var(--t-small);
  padding: 13px 16px;
  border-radius: var(--radius-s);
  border: 1px solid var(--hairline);
  background: var(--ink-raise);
  display: none;
}
.form__status.is-shown { display: block; }
.form__status.is-ok { border-color: var(--orange); color: var(--bone); }

/* Contact detail cards beside the form */
.cards { display: grid; gap: clamp(12px, 1.4vw, 18px); }
.card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: clamp(16px, 1.8vw, 22px);
  border: 1px solid var(--hairline-2);
  border-radius: var(--radius);
  background: var(--ink-raise);
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
a.card:hover { border-color: var(--orange); background: var(--ink-card); }
.card__ico {
  flex: none;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 12px;
  background: var(--orange-wash);
  color: var(--orange);
}
.card__ico svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* The key sits ABOVE the value. Both are spans, so without display:block they
   run together on one line and every value starts at a different x — the
   labels are all different widths ("Email us" vs "Studio hours"), so the
   column reads ragged. Blocking the key is what makes the four values share
   one flush left edge. The value's margin-top is also inert until then. */
.card__k { display: block; font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone-faint); }
.card__v { display: block; font-family: var(--font-display); font-size: var(--t-sub); font-weight: 400; letter-spacing: var(--track-sub); margin-top: 3px; }

/* Offices */
.offices { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--gap); }
.office {
  padding: clamp(20px, 2.2vw, 30px);
  border: 1px solid var(--hairline-2);
  border-radius: var(--radius);
  background: var(--ink-raise);
}
.office__c { font-family: var(--font-display); font-size: var(--t-sub); font-weight: 400; letter-spacing: var(--track-sub); }
.office__a { font-size: var(--t-small); color: var(--bone-dim); margin-top: 10px; }
.office__r { font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--orange); margin-bottom: 10px; }

/* -------------------------------------------------------------------------
   6l. Portfolio grid — uniform 3:2 tiles with an overlay reveal on hover.

   Measured off the reference: 1 / 2 / 3 columns, 24px gap, tiles locked to
   3:2, image scales to just 1.02 (restrained on purpose), overlay fades in,
   and the title + arrow rise out of an overflow-hidden mask. Everything runs
   on 0.9s cubic-bezier(0.25, 1, 0.5, 1).
   ---------------------------------------------------------------------- */
.folio {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(20px, 2.2vw, 24px);
}
@media (min-width: 700px)  { .folio { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .folio { grid-template-columns: repeat(3, 1fr); } }

.folio__item { --fo-ease: cubic-bezier(0.25, 1, 0.5, 1); display: block; color: inherit; }

.folio__box {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--ink-card);
  border: 1px solid var(--hairline-2);
  aspect-ratio: 3 / 2;
}
.folio__box img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1);
  transform-origin: 50% center;
  transition: transform 0.9s var(--fo-ease);
  will-change: transform;
}
.folio__item:hover .folio__box img,
.folio__item:focus-visible .folio__box img { transform: scale(1.02); }

/* Cut-out product shots sit on the white plate and must not be cropped. */
.folio__item--plate .folio__box { background: var(--plate); }
.folio__item--plate .folio__box img { object-fit: contain; }

/* Overlay: a scrim plus the caption. Hidden until hover on fine pointers. */
.folio__overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  padding: clamp(16px, 1.8vw, 24px);
  opacity: 0;
  transition: opacity 0.9s var(--fo-ease);
}
.folio__overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(8, 8, 10, 0.88) 0%, rgba(8, 8, 10, 0.35) 45%, rgba(8, 8, 10, 0) 78%);
}
.folio__item:hover .folio__overlay,
.folio__item:focus-visible .folio__overlay { opacity: 1; }

.folio__content {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(12px, 1.6vw, 20px);
  width: 100%;
}
.folio__text { display: flex; flex-direction: column; gap: 4px; flex: 1; }

/* The mask is what makes the title rise out of nothing rather than just fade. */
.folio__line { overflow: hidden; }
.folio__title,
.folio__tags,
.folio__arrow {
  /* Always light: they sit on the dark scrim, in both themes. */
  color: #F2F1EF;
  opacity: 0;
  transform: translateY(105%);
  transition: opacity 0.9s var(--fo-ease), transform 0.9s var(--fo-ease);
}
.folio__title { font-family: var(--font-display); font-size: var(--t-sub); font-weight: 400; letter-spacing: var(--track-sub); }
.folio__tags {
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #C8C6C2;
  transition-delay: 50ms;
}
.folio__arrow {
  flex: none;
  display: grid;
  place-items: center;
  width: clamp(30px, 3vw, 38px);
  height: clamp(30px, 3vw, 38px);
  border-radius: 50%;
  background: var(--orange-fill);
  color: var(--on-orange);
  transform: translateY(140%);
  transition-delay: 80ms;
}
.folio__arrow svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.folio__item:hover .folio__title,
.folio__item:hover .folio__tags,
.folio__item:hover .folio__arrow,
.folio__item:focus-visible .folio__title,
.folio__item:focus-visible .folio__tags,
.folio__item:focus-visible .folio__arrow { opacity: 1; transform: none; }

/* A caption below the tile carries the name when the overlay cannot. */
.folio__cap { display: none; padding-top: 14px; }
.folio__cap b { display: block; font-family: var(--font-display); font-size: var(--t-sub); font-weight: 400; letter-spacing: var(--track-sub); }
.folio__cap span { font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone-faint); }

/* Touch has no hover, so the overlay would never appear. Drop it and show a
   permanent caption underneath instead — same trick the reference uses. */
@media (hover: none), (pointer: coarse) {
  .folio__overlay { display: none; }
  .folio__cap { display: block; }
}

/* Projects with no imagery yet. */
.folio__box--holding {
  display: grid;
  place-items: center;
  background:
    repeating-linear-gradient(-45deg, var(--veil) 0 12px, transparent 12px 24px),
    var(--ink-card);
}
.folio__mark {
  width: clamp(40px, 5vw, 66px);
  aspect-ratio: 202 / 158;
  background: var(--bone-faint);
  -webkit-mask: url('/assets/img/brand/urooj-mark.svg') no-repeat center / contain;
  mask: url('/assets/img/brand/urooj-mark.svg') no-repeat center / contain;
  transition: background-color var(--dur-fast) var(--ease);
}
.folio__item:hover .folio__mark { background: var(--orange); }

@media (prefers-reduced-motion: reduce) {
  .folio__box img,
  .folio__overlay,
  .folio__title, .folio__tags, .folio__arrow { transition: none !important; }
  .folio__item:hover .folio__box img { transform: none; }
}

/* -------------------------------------------------------------------------
   6m. Case study, cinematic model — pinned hero, body rides over it.

   Measured off the reference: the hero is `position: sticky; top: 0;
   height: 100vh; z-index: 0` inside a tall wrapper, and the body that follows
   is `position: relative; z-index: 10` with an OPAQUE background and rounded
   top corners. So the hero does not scroll away — the content slides up over
   it. On load the media eases down from a slight zoom.
   ---------------------------------------------------------------------- */
.csx { position: relative; }

.csx__hero {
  position: sticky;
  top: 0;
  z-index: 0;
  height: 100vh;
  overflow: hidden;
  background: #08080A;
}
@supports (height: 100svh) { .csx__hero { height: 100svh; } }

.csx__media,
.csx__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The zoom-out: starts slightly in, settles to 1. */
  transform: scale(1.14);
  animation: csx-zoom 1900ms cubic-bezier(0.16, 1, 0.3, 1) 120ms forwards;
}
.csx__poster { z-index: 1; transition: opacity 0.6s var(--ease); }
.csx.is-playing .csx__poster { opacity: 0; }

@keyframes csx-zoom { to { transform: scale(1); } }

@media (prefers-reduced-motion: reduce) {
  .csx__media, .csx__poster { animation: none; transform: none; }
}

/* Scrim so the title reads over any frame. Bottom-weighted, like the reference. */
.csx__hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /* Tuned against the actual footage. The Bio Active film has bright frames,
     and at .82/.30 the small orange eyebrow measured only 1.8:1. At .93/.55
     the same label clears AA. */
  background:
    linear-gradient(to top, rgba(8, 8, 10, 0.93) 0%, rgba(8, 8, 10, 0.55) 32%, rgba(8, 8, 10, 0) 60%),
    linear-gradient(to bottom, rgba(8, 8, 10, 0.62) 0%, rgba(8, 8, 10, 0) 24%);
}

/* Deeper scrim, for any hero where bright content lands behind the title.
   The values above were tuned against the Bio Active film. Two other cases
   broke them, and only the first is what you would guess:

     Active Plus  white product plate         eyebrow 4.23:1
     Hi-Lube      BLACK pack, but with hard
                  specular highlights on it   eyebrow 3.68:1

   So this is not about the poster being "light" overall — it is about peak
   luminance behind the text. The eyebrow fails first every time, because it
   sits highest in the title block where the scrim is thinnest, and it is 12px.
   Measure against the video frames, not just the poster: a still can pass
   while the footage it hands over to does not. */
.csx__hero--deep::after {
  background:
    linear-gradient(to top, rgba(8, 8, 10, 0.95) 0%, rgba(8, 8, 10, 0.74) 32%, rgba(8, 8, 10, 0) 66%),
    linear-gradient(to bottom, rgba(8, 8, 10, 0.62) 0%, rgba(8, 8, 10, 0) 24%);
}

.csx__titlewrap {
  position: relative;
  z-index: 3;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--pad);
  padding-bottom: clamp(48px, 7vw, 110px);
  /* Always light: it sits on the scrim in both themes. */
  --bone: #F2F1EF;
  --bone-dim: #D6D4D0;
  --bone-faint: #B4B2AD;
  --orange: #FFC4A2;
  color: var(--bone);
}

/* The body panel. Opaque + rounded top is what makes it read as rising over
   the pinned hero rather than simply following it. */
.csx__body {
  position: relative;
  z-index: 10;
  background: var(--ink);
  border-radius: clamp(20px, 3vw, 40px) clamp(20px, 3vw, 40px) 0 0;
  box-shadow: 0 -30px 60px rgba(0, 0, 0, 0.28);
}

/* Meta strip directly under the fold: industry + services. */
.csx__meta {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(20px, 2.6vw, 40px);
  padding-block: clamp(34px, 4.4vw, 68px);
  border-bottom: 1px solid var(--hairline-2);
}
@media (min-width: 780px) { .csx__meta { grid-template-columns: repeat(3, 1fr); } }
/* The meta key and the section head below are the same idea at two sizes: a
   sentence-case accent heading in the display face at weight 400, never
   uppercase micro-caps. Hierarchy is carried by size, as everywhere else on
   the site — 20px for the meta keys, 32px for the section heads. */
.csx__k {
  font-family: var(--font-display);
  font-size: var(--t-sub);          /* 16 -> 20 */
  line-height: 1.4;                 /* 20/28 */
  letter-spacing: var(--track-sub);
  font-weight: 400;
  color: var(--orange);
  margin-bottom: 12px;
}
.csx__v { font-size: var(--t-body); color: var(--bone-dim); max-width: 38ch; }

/* Services read as outlined chips rather than a comma-separated sentence, so
   the list is scannable at a glance and each service is its own object. */
.csx__tags { display: flex; flex-wrap: wrap; gap: clamp(8px, 0.9vw, 12px); }
.csx__tag {
  font-size: var(--t-small);        /* 13 -> 14 */
  line-height: 20px;
  color: var(--bone-dim);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 6px 16px;
}

/* Challenge / Approach / Process — sticky label column, scrolling prose. */
.csx__cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(24px, 3vw, 60px);
  align-items: start;
}
@media (min-width: 900px) { .csx__cols { grid-template-columns: 0.8fr 1.2fr; } }
@media (min-width: 900px) {
  .csx__sticky { position: sticky; top: clamp(96px, 11vw, 128px); }
}

/* A hairline separates the blocks, so Challenge / Approach / Process read as
   a stack of discrete sections rather than one long column of prose. */
.csx__block + .csx__block {
  margin-top: clamp(28px, 3.2vw, 48px);
  padding-top: clamp(28px, 3.2vw, 48px);
  border-top: 1px solid var(--hairline-2);
}
.csx__h {
  font-family: var(--font-display);
  font-size: var(--t-h2);           /* 22 -> 32 */
  line-height: 1;                   /* 32/32 — set solid, the heads are short */
  letter-spacing: var(--track-h2);
  font-weight: 400;
  color: var(--orange);
  margin-bottom: 16px;
}
/* Body copy sits at the same 15/24 as the rest of the site. It was running at
   --t-lead (up to 24px), which left the prose competing with the heads
   instead of sitting under them. */
.csx__p { font-size: var(--t-body); line-height: var(--lh-body); color: var(--bone-dim); max-width: 62ch; }
.csx__p + .csx__p { margin-top: 1em; }
.csx__p em { font-style: normal; color: var(--bone); }

/* Media rhythm — full-width and two-up, aspect set per block via --ar. */
.csx__media-grid { display: flex; flex-wrap: wrap; gap: clamp(14px, 1.8vw, 26px); }
.csx__media-grid > figure { margin: 0; width: 100%; }
.csx__media-grid--two > figure { width: calc(50% - clamp(14px, 1.8vw, 26px) / 2); }
@media (max-width: 760px) { .csx__media-grid--two > figure { width: 100%; } }

.csx__frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ar, 16 / 9);
  border-radius: var(--radius);
  background: var(--ink-card);
  border: 1px solid var(--hairline-2);
}
.csx__frame img { width: 100%; height: 100%; object-fit: cover; }

/* The film, under the stills. A real player, not a hero loop:
   - controls, because someone chose to watch it
   - preload="none" in the markup, so a 7-11 MB file costs NOTHING until the
     play button is pressed. The poster is all that loads.
   - the source keeps its audio, unlike the muted hero loops
   Same frame as the photographs, so it sits in the sequence rather than
   interrupting it. */
.csx__frame video { width: 100%; height: 100%; object-fit: cover; display: block; background: #08080A; }

/* Media beside prose. One row, media in one cell and a text block in the
   other, so a film or a still can be read together with the paragraph that
   explains it rather than stacked above it.

   --split sets the media column's share of the row (default 1fr of 2). Add
   .csx__split--flip to put the media on the right; the source order stays
   media-then-text either way, so the reading order is unchanged on mobile
   where the row collapses to a single column. */
/* ROW HEIGHT IS THE CONSTANT, the way it is on /work/bioactive/. Every media
   row there lands on the same height whether it holds one frame or two — a
   full-width band at 1600/848 and a two-up at 1487/1600 both resolve to the
   same number at any viewport, because the two-up aspect is the band aspect
   times (W - gap) / 2W. Only the aspect changes; the rhythm does not.

   So media rows take the full .wrap width and are NOT capped. What they must
   not do is carry prose: text has a measure, and a column of text stretched to
   1700px is unreadable. .csx__split is the one row that mixes the two, and it
   is capped for that reason alone. */
.csx__row { max-width: var(--row); margin-inline: auto; }

/* A two-up of SQUARE media at full width lands at (W - gap) / 2 tall, which is
   within a few per cent of the band height above — so square sources drop
   into this rhythm without being cropped to fit it. */
.csx__pair {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(14px, 1.8vw, 26px);
}
@media (max-width: 620px) { .csx__pair { grid-template-columns: 1fr; } }
.csx__pair > figure { margin: 0; }

.csx__split {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(22px, 3vw, 56px);
  align-items: center;
  /* Proportional, not just capped. A fixed px cap holds its size while the
     full-width rows around it shrink with the viewport, so the two drift apart
     on a smaller screen - 865 vs 837 at 1830px became 812 vs 653 at 1440px.
     The 85% term keeps the split's media within a few per cent of a two-up
     cell at every width. */
  max-width: min(var(--row, 1400px), 85%);
  margin-inline: auto;
}
/* Full .wrap width, for a LANDSCAPE image beside prose. The cap above is sized
   for square and portrait media; a landscape frame inside it comes out far
   shorter than the bands around it.

   Here the prose column is a PERCENTAGE, not the usual fixed measure, and that
   is the whole trick. Matching a band of aspect r1 to a split whose media is
   aspect r2 needs

       prose = (1 - r2/r1) - gap/W

   of the row. For the 1.644 bands and 1.130 media on the kit page that is
   0.281, and because the gap scales with the viewport the number holds: 28%
   keeps the two within 0.1% from 960px up. A fixed px measure does not - it
   eats a growing share of the row as the viewport narrows, which is what put
   the bands 12% out at 1440px.

   --row and --prose are BOTH set per page, because the right pair depends on
   the two aspects involved. The solved values in use:

     /work/active-plus-kit/  1.644 band, 1.130 media  ->  --row none, --prose 28%
     /work/active-plus/      16:9  band, 1:1   media  ->  --row 85%,  --prose 30%

   The square case cannot run full-wrap: the arithmetic wants a 44% prose
   column there, which is an 85-character line. Narrowing the row to 85% pulls
   it back to ~54 characters and still matches the band. */
.csx__split--wide { max-width: var(--row, none); }
@media (min-width: 860px) {
  /* The prose column is a MEASURE, not a fraction: 30rem caps it at ~59
     characters, and 38% keeps it from eating the row on a narrow laptop. The
     media takes everything else, which at the capped row width puts it close
     to the band height the pure-media rows run at. */
  .csx__split { grid-template-columns: minmax(0, 1fr) minmax(0, min(30rem, 38%)); }
  /* Flipping moves the figure into the second column, so the tracks swap with
     it — otherwise the media lands in the prose column and the imbalance is
     inverted rather than fixed. */
  .csx__split--flip { grid-template-columns: minmax(0, min(30rem, 38%)) minmax(0, 1fr); }
  .csx__split--flip > *:first-child { order: 2; }
}
@media (min-width: 860px) {
  .csx__split--wide { grid-template-columns: minmax(0, 1fr) minmax(0, var(--prose, 28%)); }
  .csx__split--wide.csx__split--flip { grid-template-columns: minmax(0, var(--prose, 28%)) minmax(0, 1fr); }
}
.csx__split > figure { margin: 0; }
/* No measure of its own: the column IS the measure. */
.csx__split .csx__p { max-width: none; }

/* A row of N equal cells: a contact sheet of social slides, a row of films.
   --cols is the desktop count and --cols-sm the tablet one, so a row of three
   9:16 films can stay three across where a row of 4:5 slides drops to two.
   Below 480px everything goes single column - three vertical films squeezed
   into a phone width are thumbnails, not films. */
.csx__grid {
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), 1fr);
  gap: clamp(14px, 1.8vw, 26px);
}
@media (max-width: 760px) { .csx__grid { grid-template-columns: repeat(var(--cols-sm, 2), 1fr); } }
@media (max-width: 480px) { .csx__grid { grid-template-columns: 1fr; } }
.csx__grid > figure { margin: 0; }

/* Category label inside a long media run. Frames within a category keep the
   standard 26px gap; the break BETWEEN categories is carried by this label's
   own top margin, so the grouping is legible without any grid getting
   special-cased spacing. */
.csx__cat {
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--orange);
  margin: clamp(46px, 5.2vw, 86px) 0 clamp(16px, 1.8vw, 24px);
}
.csx__cat:first-child { margin-top: 0; }

.csx__band { font-family: var(--font-display); font-size: var(--t-title); line-height: var(--lh-title); letter-spacing: var(--track-title); font-weight: 400; max-width: 26ch; }
.csx__band span { color: var(--orange); }

/* -------------------------------------------------------------------------
   7. Work index — typed rows, not a uniform grid
   ---------------------------------------------------------------------- */
/* 24px between rows AND between tiles, measured off the reference's
   portfolio grid. --gap is redefined locally rather than globally because the
   .row--* width calcs below already divide by it - override it in one place
   and the widths follow. */
.rows { --gap: clamp(16px, 1.8vw, 24px); display: flex; flex-direction: column; gap: var(--gap); }
.row { display: flex; flex-wrap: wrap; gap: var(--gap); }
.row--single .tile { width: 100%; }
.row--double .tile { width: calc(50% - var(--gap) / 2); }
.row--triple .tile { width: calc(33.333% - var(--gap) / 1.5); }

@media (max-width: 860px) {
  .row--double .tile,
  .row--triple .tile { width: 100%; }
  /* Rows keep the SAME gap as the items inside them here. Above this width a
     row is a horizontal group and a bigger gap between rows separates one
     group from the next; below it every row is a single column, so that
     grouping has nothing left to express - and a 16px gap inside a row against
     a 53px gap between rows just reads as uneven spacing down one stack. */
  .rows { gap: var(--gap); }
}

.tile { display: block; position: relative; }

.tile__frame {
  position: relative;
  height: var(--band);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--ink-card);
  border: 1px solid var(--hairline-2);
}
.row--single .tile__frame { height: var(--band-tall); }
.row--triple .tile__frame { height: var(--band-low); }

/* A tile holding a cut-out product rather than a photograph. Its images are
   flattened onto white, so the frame is white and the image is contained
   rather than cropped — otherwise the crop cuts through floating products. */
.tile--plate .tile__frame { background: var(--plate); }
.tile--plate .tile__img,
.tile--plate .tile__hover { object-fit: contain; }

.tile__img,
.tile__hover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.55s var(--ease), opacity 0.35s var(--ease);
}
.tile__hover { opacity: 0; }
.tile:hover .tile__img { transform: scale(1.035); }
.tile:hover .tile__hover { opacity: 1; transform: scale(1.035); }

/* Orange edge that wipes across the bottom of the frame on hover. */
.tile__frame::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 2px;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.45s var(--ease-out);
}
.tile:hover .tile__frame::after { transform: scaleX(1); }

.tile__cap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-top: 18px;
}
.tile__name { font-family: var(--font-display); font-size: var(--t-sub); font-weight: 400; letter-spacing: var(--track-sub); }
.tile__tag { font-size: var(--t-small); color: var(--bone-dim); }
.tile__cat { font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone-faint); white-space: nowrap; }

/* Placeholder tile for work that hasn't shipped yet. Not clickable. */
.tile--soon .tile__frame,
.tile--holding .tile__frame {
  display: grid;
  place-items: center;
  background:
    repeating-linear-gradient(-45deg, var(--veil) 0 12px, transparent 12px 24px),
    var(--ink-card);
}
.tile--soon .tile__frame::after { display: none; }
.tile--soon { pointer-events: none; }

/* A project whose page exists but has no imagery yet — clickable, and it keeps
   the orange hover wipe so it reads as a live link rather than a dead slot. */
.tile--holding .tile__frame {
  transition: background-color var(--dur-fast) var(--ease);
}
.tile--holding:hover .tile__frame { background-color: rgba(255, 90, 0, 0.05); }
.tile--holding .tile__mark {
  width: clamp(38px, 5vw, 62px);
  aspect-ratio: 202 / 158;
  background: var(--bone-faint);
  -webkit-mask: url('/assets/img/brand/urooj-mark.svg') no-repeat center / contain;
  mask: url('/assets/img/brand/urooj-mark.svg') no-repeat center / contain;
  transition: background-color var(--dur-fast) var(--ease);
}
.tile--holding:hover .tile__mark { background: var(--orange); }

/* -------------------------------------------------------------------------
   8. Services / process
   ---------------------------------------------------------------------- */
.grid-2 { display: grid; gap: clamp(28px, 4vw, 72px); }
@media (min-width: 900px) { .grid-2 { grid-template-columns: 1fr 1fr; } }

.list { border-top: 1px solid var(--hairline-2); }
.list__item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: clamp(16px, 2.4vw, 40px);
  padding-block: clamp(22px, 2.4vw, 34px);
  border-bottom: 1px solid var(--hairline-2);
  transition: background-color var(--dur-fast) var(--ease), padding-left var(--dur-fast) var(--ease);
}
.list__item:hover { background: var(--veil); padding-left: 12px; }
.list__num {
  font-size: var(--t-micro);
  color: var(--orange);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
  padding-top: 0.45em;
}
.list__title { font-family: var(--font-display); font-size: var(--t-sub); font-weight: 400; letter-spacing: var(--track-sub); margin-bottom: 6px; }
.list__body { color: var(--bone-dim); max-width: 58ch; font-size: var(--t-small); }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--gap); }
.stat__n {
  font-family: var(--font-display);
  font-size: var(--t-h1);
  font-weight: 500;
  letter-spacing: var(--track-display);
  line-height: 1;
}
.stat__l { font-size: var(--t-small); color: var(--bone-dim); margin-top: 8px; }

/* -------------------------------------------------------------------------
   8b. Landing page, reference model
   Modelled on richestbranding.com, measured off the live page: the hero head
   runs 54px/400 at 1440 and the section heads 40px, which already sit inside
   --t-display (60) and --t-title (40). Nothing here widens the type scale.

   The reference's whole organising idea is that every section ANNOUNCES
   itself the same way - a centred accent eyebrow, a centred head whose second
   half turns accent, then an optional centred lead - and only the content
   below it changes. That is .sechead, and it is the one component this file
   gained; the rest is reuse.
   ---------------------------------------------------------------------- */

/* The two-tone head. The reference colours the trailing phrase of every
   heading, which is a lot more orange than this site used to carry - but it
   lands on headings only, never body copy, so the "orange is precious" rule
   survives intact. --orange is the TEXT cut (5.1:1 light / 6.4:1 dark), not
   --orange-fill, which would fail at 3.13:1. */
.accent { color: var(--orange); }

.sechead {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: clamp(14px, 1.6vw, 20px);
  max-width: 60rem;
  margin-inline: auto;
  padding-bottom: clamp(34px, 4.4vw, 68px);
}
.sechead .lead { max-width: 58ch; }
/* .display and .title carry margin-left:-0.02em, an optical hang that is
   correct against a left gutter and visibly wrong once the line is centred -
   it drags the whole head a few px left of the axis. Cancel it here. */
.sechead .display,
.sechead .title,
.hero__head .display { margin-left: 0; }

/* A centred trailing action, the way every reference section closes. */
.sechead--after { padding: clamp(34px, 4.4vw, 68px) 0 0; }

/* ---- Hero -------------------------------------------------------------
   Two columns: the sentence on the left, a media card on the right. Measured
   off the reference at 1920 - an even `grid-cols-2`, h1 at 60px capped to a
   700px measure, and text that is CENTRED while stacked and switches to
   left-aligned the moment the two columns appear. Getting that switch wrong
   is the easy mistake: the reference looks centred at laptop widths and is
   not centred at all on a wide screen. */
.hero__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(26px, 3vw, 48px);
  align-items: center;
}
@media (min-width: 1024px) {
  .hero__grid { grid-template-columns: 1fr 1fr; }
}

.hero__head {
  display: grid;
  /* minmax(0, 1fr), NOT the implicit auto track. A grid item's automatic
     minimum size is its MIN-CONTENT, and the marquee track inside .logos is
     max-content wide (~1230px) - on an auto track that blows the column past
     the viewport, pushes the headline sideways and gives the whole document a
     horizontal scrollbar. overflow:hidden on a descendant does not save the
     ancestor; capping the track is what does. */
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  text-align: center;
  gap: clamp(14px, 1.6vw, 22px);
  max-width: 44rem;
  margin-inline: auto;
}
.hero__head .lead { max-width: 52ch; }
.hero__cta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(12px, 1.4vw, 20px);
  margin-top: clamp(4px, 0.6vw, 10px);
}
@media (min-width: 1024px) {
  .hero__head { justify-items: start; text-align: left; margin-inline: 0; }
  .hero__cta  { justify-content: flex-start; }
  /* The optical hang is correct again once the head is left-aligned. */
  .hero__head .display { margin-left: -0.02em; }
}

/* The media card. The reference fills it with three shots of ONE project on a
   brand-coloured ground, which reads as a range rather than as a stock photo. */
.herocard {
  border-radius: var(--radius);
  background: var(--ink-raise);
  border: 1px solid var(--hairline-2);
  padding: clamp(14px, 1.6vw, 22px);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(8px, 1vw, 14px);
}
.herocard figure { margin: 0; aspect-ratio: 3 / 4; border-radius: calc(var(--radius) - 5px); overflow: hidden; background: var(--plate); }
.herocard img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- Logo marquee -----------------------------------------------------
   The strip under the hero CTAs. The track holds the SAME group twice and
   slides exactly -50%, so the moment it wraps, frame one is pixel-identical
   to where it started - that is what makes the loop seamless. Duplicate the
   group in the markup or the reset will visibly jump.

   The mask fades both edges so logos enter and leave instead of being
   guillotined at the box edge. Measured off the reference: 10% / 90%. */
/* min-width:0 for the same reason - belt and braces if .logos is ever
   moved into a flex or grid parent that does not cap its tracks. */
.logos { margin-top: clamp(22px, 2.6vw, 38px); width: 100%; min-width: 0; }
@media (min-width: 1024px) { .logos { max-width: 70%; } }
.logos__mask {
  overflow: hidden;
  -webkit-mask: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  mask: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}
.logos__track {
  display: flex;
  width: max-content;
  animation: logos-slide 22s linear infinite;
}
.logos__group {
  display: flex;
  align-items: center;
  flex: none;
  gap: clamp(26px, 3vw, 46px);
  padding-right: clamp(26px, 3vw, 46px);
}
.logos__item {
  flex: none;
  display: grid;
  place-items: center;
  height: 30px;
  color: var(--bone-faint);
  font-family: var(--font-display);
  font-size: var(--t-small);
  letter-spacing: var(--track-ui);
  white-space: nowrap;
}
.logos__item img { max-height: 30px; width: auto; object-fit: contain; }
@keyframes logos-slide { to { transform: translateX(-50%); } }
/* A loop with no off switch is a accessibility failure, not a flourish. */
@media (prefers-reduced-motion: reduce) { .logos__track { animation: none; } }

/* The reference pairs two PILLS. A bare .btn starts as a circle and grows
   into a pill on hover, which is right for a lone link sitting in a text
   block and wrong for a matched pair - the two read as different components.
   .btn--pill just starts the ::before at full width; every hover mechanic is
   untouched, so there is no second button to maintain. */
.btn--pill::before { width: 100%; }
/* The pill has a border all the way round, so the label needs the same air on
   both ends; the circle form only ever needed it on the right. */
.btn--pill { padding-right: 32px; }

/* Solid primary, to pair with the outlined secondary. */
.btn--fill { color: var(--on-orange); }
.btn--fill::before { width: 100%; background: var(--orange-fill); border-color: var(--orange-fill); }
.btn--fill::after { border-color: var(--on-orange); }
.btn--fill:hover::before { background: var(--orange); border-color: var(--orange); }

/* ---- Work rows -------------------------------------------------------
   The reference's portfolio is typed rows, not a uniform grid, and the
   caption is an overlay ON the image rather than a line under it. The row
   widths already existed for .tile; .folio__item just joins them, which is
   what swaps the caption treatment for the /work/ overlay wholesale. */
.row--single .folio__item { width: 100%; }
.row--double .folio__item { width: calc(50% - var(--gap) / 2); }
.row--triple .folio__item { width: calc(33.333% - var(--gap) / 1.5); }
@media (max-width: 860px) {
  .row--double .folio__item,
  .row--triple .folio__item { width: 100%; }
}
/* A full-width row at 3/2 is far too tall, so the aspect is a variable the
   row sets rather than a constant baked into the box. */
.folio__box { aspect-ratio: var(--fo-ar, 3 / 2); }
.row--single .folio__item { --fo-ar: 16 / 7; }
@media (max-width: 860px) { .row--single .folio__item { --fo-ar: 3 / 2; } }

/* ---- Services: the reference's sticky card stack -----------------------
   This is the scroll effect, and it is pure CSS - no library, no scroll
   listener, nothing to throttle. Three facts do all of it:

     1. Each .svc is `position: sticky; top: 0; min-height: 100vh`. Sticky is
        constrained by the PARENT box, not by its own, so all five pin at the
        top of the viewport at once rather than taking turns.
     2. The wrapper is transparent and only CENTRES its child. The child,
        .svc__card, is OPAQUE. That is the whole trick: card 02 arriving at
        top:0 paints over card 01 and hides it completely. Give the card a
        transparent background and the stack turns into unreadable mush.
     3. The track is therefore 5 x 100vh of scroll, which is what gives each
        card a full screen of dwell before the next one covers it.

   pointer-events: none on the wrapper with auto on the card keeps the dead
   full-screen band from eating clicks meant for the card on top of the stack.

   Kept at every width, as the reference does - it stacks on a phone too. */
.svc {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  pointer-events: none;
}
.svc__card {
  width: 100%;
  pointer-events: auto;
  background: var(--ink);
  border-top: 1px solid var(--hairline-2);
  padding-block: clamp(26px, 3.2vw, 48px);
}

.svc__top {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: clamp(14px, 2vw, 30px);
  row-gap: clamp(8px, 1vw, 14px);
  align-items: baseline;
}
@media (min-width: 900px) {
  .svc__top { grid-template-columns: auto 1fr auto; }
}

/* The only numeral on the site at display size. --bone-faint, not accent, so
   it reads as an index rather than as a second heading competing with one. */
.svc__n {
  grid-column: 1;
  font-family: var(--font-display);
  font-size: var(--t-display);
  line-height: 1;
  font-weight: 400;
  color: var(--bone-faint);
  letter-spacing: var(--track-display);
}
.svc__h {
  grid-column: 2;
  font-family: var(--font-display);
  font-size: var(--t-h2);
  line-height: 1.15;
  font-weight: 400;
  letter-spacing: var(--track-h2);
}
/* Column 2 as well, so the prose hangs off the title rather than off the
   numeral - the indent is what keeps the numeral reading as a margin note. */
.svc__p {
  grid-column: 2;
  color: var(--bone-dim);
  max-width: 62ch;
}
/* Narrow: numeral+title, then prose, then the action - explicit rows, because
   source order puts the CTA before the paragraph so that the WIDE layout can
   lift it onto row 1 without reordering the markup. */
.svc__p   { grid-row: 2; }
.svc__cta { grid-row: 3; grid-column: 2; justify-self: start; }
@media (min-width: 900px) {
  .svc__cta { grid-column: 3; grid-row: 1; justify-self: end; }
  .svc__p   { grid-column: 2 / span 2; }
}

/* Four proofs per service, two-up on a phone so they never shrink to stamps. */
.svc__thumbs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(10px, 1.2vw, 16px);
  margin-top: clamp(18px, 2.4vw, 34px);
}
@media (min-width: 620px) { .svc__thumbs { grid-template-columns: repeat(4, 1fr); } }
.svc__thumb {
  aspect-ratio: 4 / 3;
  border-radius: calc(var(--radius) - 4px);
  overflow: hidden;
  background: var(--ink-card);
  border: 1px solid var(--hairline-2);
}
.svc__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.9s cubic-bezier(0.25, 1, 0.5, 1);
}
.svc__card:hover .svc__thumb img { transform: scale(1.03); }

/* ---- Testimonials ----------------------------------------------------
   NAMED .testi__*, NOT .quote. `.quote` is already taken further down this
   file by the case-study pull-quote (max-width: 22ch), and because that rule
   comes later it silently won the cascade - the cards rendered 375px wide in
   a 687px column with no error anywhere.
   PLACEHOLDER COPY. Every quote, name and company below is filler for the
   client to replace. Do not invent testimonials: an attributed quote is a
   statement a named company did not make, and putting words in a real
   client's mouth is the one thing on this page that cannot be fixed later. */
.testi {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(14px, 1.8vw, 24px);
}
@media (min-width: 760px)  { .testi { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1140px) { .testi { grid-template-columns: repeat(3, 1fr); } }
.testi__card {
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.6vw, 20px);
  padding: clamp(20px, 2.4vw, 32px);
  border: 1px solid var(--hairline-2);
  border-radius: var(--radius);
  background: var(--ink-card);
}
.testi__q {
  font-family: var(--font-display);
  font-size: var(--t-sub);
  line-height: 1.5;
  letter-spacing: var(--track-sub);
  margin: 0;
}
.testi__by { display: flex; align-items: center; gap: 12px; margin-top: auto; }
/* Initials stand in for a client avatar, so the row keeps its shape whether
   or not a photo is ever supplied. */
.testi__av {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--orange-wash);
  color: var(--orange);
  font-family: var(--font-display);
  font-size: var(--t-small);
}
.testi__n { font-size: var(--t-small); }
.testi__r { font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone-faint); }

/* ---- Closing mark ----------------------------------------------------
   The 3D stage, moved out of the hero to the foot of the page: the page
   opens on a claim and closes on the mark.

   No panel here. In the hero the stage carried a tinted, bordered, rounded
   box because it had to hold the top of the page on its own; as a sign-off it
   reads better floating on the page ground, and smaller. .stage--bare strips
   the background, border and radius and drops the height by roughly half. */
.markband { padding-block: 0 var(--section); }
.stage--bare {
  background: none;
  border: 0;
  border-radius: 0;
  height: clamp(200px, 30vh, 340px);
}
/* The specular sweep is a highlight ON the panel; with no panel it is a grey
   smear across the page. The dark-theme rule is listed explicitly because
   [data-theme='dark'] .stage::after outranks a bare .stage--bare::after and
   the sweep would survive in dark only. */
.stage--bare::before,
.stage--bare::after,
[data-theme='dark'] .stage--bare::after { display: none; }
.stage--bare .stage__hint { opacity: 0.75; }

/* ---- Client wall -----------------------------------------------------
   Slots, not images: there are no client logo files yet, so each cell holds
   the brand name set in the display face. Drop an <img> in beside the span
   and delete the span - the cell is already sized and centred for it. */
.clients {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--hairline-2);
  border: 1px solid var(--hairline-2);
  border-radius: var(--radius);
  overflow: hidden;
}
@media (min-width: 640px)  { .clients { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1000px) { .clients { grid-template-columns: repeat(5, 1fr); } }
.client {
  display: grid;
  place-items: center;
  min-height: clamp(92px, 9vw, 132px);
  padding: clamp(14px, 1.8vw, 24px);
  background: var(--ink);
  text-align: center;
  transition: background-color var(--dur-fast) var(--ease);
}
.client:hover { background: var(--veil); }
.client img { max-width: 100%; max-height: 52px; object-fit: contain; }
.client__name {
  font-family: var(--font-display);
  font-size: var(--t-small);
  letter-spacing: var(--track-ui);
  color: var(--bone-dim);
}

/* -------------------------------------------------------------------------
   9. Project page
   ---------------------------------------------------------------------- */
/* The track is deliberately taller than the viewport: its scroll distance is
   what drives the video's playhead. The sticky child is what you actually see. */
/* Starts at one screen and only earns its full track once the video is
   confirmed scrubbable. Committing 340vh up front would leave 2.4 screens of
   dead scroll on every degradation path. */
.scrub {
  position: relative;
  height: 100vh;
  background: #08080A;

  /* Same reasoning as .filmhero: light type over a dark scrim, both themes. */
  --bone:       #F2F1EF;
  --bone-dim:   #D6D4D0;
  --bone-faint: #B4B2AD;
  --orange:     #FFC4A2;
  color: var(--bone);
}
.scrub[data-scrub='ready'] { height: var(--scrub-track, 320vh); }
.scrub__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  place-items: center;
  overflow: hidden;
}
@supports (height: 100svh) {
  .scrub__sticky { height: 100svh; }
}
.scrub[data-scrub='poster'] .scrub__poster { opacity: 1; }
/* A progress cue that can never move is noise — hide it in the poster state. */
.scrub:not([data-scrub='ready']) .scrub__cue { display: none; }
.scrub__media {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
.scrub__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity 0.5s var(--ease);
}
.scrub__overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--pad);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gap);
  background: linear-gradient(to top, rgba(8,8,10,0.85), transparent);
  pointer-events: none;
}

/* Spec stack: lowercase key/value, reads like a technical sheet. */
.spec { display: grid; grid-template-columns: auto 1fr; gap: 10px clamp(18px, 2.4vw, 40px); font-size: var(--t-small); }
.spec dt { color: var(--bone-faint); text-transform: lowercase; }
.spec dd { color: var(--bone-dim); margin: 0; }

.media { border-radius: var(--radius); overflow: hidden; background: var(--ink-card); border: 1px solid var(--hairline-2); }
.media img { width: 100%; height: 100%; object-fit: cover; }
.media--band { height: var(--band); }
.media--tall { height: var(--band-tall); }
/* Design presentations — flat artwork, dielines, nutrition panels — carry
   their own aspect instead of a fixed band height. A band crops to a set
   height, which on this kind of image cuts the very detail it exists to show.
   Pass the source ratio as --ar. */
.media--keep { height: auto; aspect-ratio: var(--ar, 16 / 9); }

.caption { font-size: var(--t-small); color: var(--bone-faint); padding-top: 14px; max-width: 52ch; }

.quote {
  font-family: var(--font-display);
  font-size: var(--t-title);
  line-height: var(--lh-title);
  letter-spacing: var(--track-title);
  font-weight: 400;
  max-width: 22ch;
}
.quote span { color: var(--orange); }

.pager { display: flex; align-items: center; justify-content: space-between; gap: var(--gap); flex-wrap: wrap; }

/* -------------------------------------------------------------------------
   10. Footer
   ---------------------------------------------------------------------- */
/* A full-bleed tinted band, not a bordered strip of page. .footer__inner
   carries the .wrap constraint so the colour runs edge to edge behind it. */
.footer {
  background: var(--footer-bg);
  border-top: 1px solid var(--hairline-2);
  margin-top: clamp(64px, 8vw, 130px);
}
.footer__inner { padding-block: clamp(52px, 6.4vw, 104px) 0; }

.footer__cta {
  font-family: var(--font-display);
  font-size: var(--t-display);
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  font-weight: 400;
  margin-left: -0.02em;
  max-width: 16ch;          /* forces the wrap that makes it read as a statement */
}
.footer__cta a:hover { color: var(--footer-accent); }
/* A hairline under the CTA, fading out — the same device as .rule--accent, so
   the footer opens the way sections elsewhere on the site do. */
.footer__cta::after {
  content: '';
  display: block;
  height: 1px;
  margin-top: clamp(30px, 3.4vw, 52px);
  background: linear-gradient(90deg, var(--footer-accent), transparent 58%);
}

/* 12-column body: a wide brand column, then two link columns. */
.footer__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(28px, 3vw, 48px);
  padding-top: clamp(40px, 5vw, 80px);
  padding-bottom: clamp(28px, 3.4vw, 48px);
}
@media (min-width: 860px) {
  .footer__grid { grid-template-columns: repeat(12, 1fr); gap: clamp(24px, 2.6vw, 40px); }
  .footer__brand { grid-column: span 5; }
  .footer__col   { grid-column: span 3; }
  .footer__col:last-of-type { grid-column: span 4; }
}

.footer__brand { display: flex; flex-direction: column; gap: clamp(16px, 1.8vw, 24px); }
.footer__mark {
  /* display:block is load-bearing. This span sits inside the <a>, so unlike
     .brand__mark in the header it is NOT a flex item and is not blockified -
     and width/aspect-ratio do nothing on an inline box. Without this the mark
     lays out at 0x0 and the eagle silently never paints. */
  display: block;
  width: clamp(44px, 5vw, 64px);
  aspect-ratio: 202 / 158;
  background: var(--bone);
  -webkit-mask: url('/assets/img/brand/urooj-mark.svg') no-repeat left center / contain;
  mask: url('/assets/img/brand/urooj-mark.svg') no-repeat left center / contain;
}
.footer__tag { font-size: var(--t-small); color: var(--bone-dim); max-width: 44ch; }

/* Contact rows — icon in a bordered square, label above the value. */
.footer__contacts { display: flex; flex-direction: column; gap: 14px; }
.footer__contact { display: flex; align-items: center; gap: 12px; }
.footer__ico {
  flex: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  border: 1px solid var(--hairline);
  color: var(--footer-accent);
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.footer__contact:hover .footer__ico { background: var(--orange-wash); border-color: var(--orange); }
.footer__ico svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* These stay INLINE on purpose. The footer template in tools-apply-footer.ps1
   puts a literal <br> between the key and the value, so the pair already
   stacks. Adding display:block here as well makes that <br> render as an extra
   empty line and opens a 24px hole under every label. */
.footer__ck { font-size: var(--t-micro); letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone-faint); }
.footer__cv { font-size: var(--t-small); color: var(--bone); }

/* Column headings take the accent and go uppercase, as on the reference.
   They were --bone at body size, which made them indistinguishable from the
   links beneath them and left the columns looking like loose lists. */
.footer__h {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--footer-accent);
  margin-bottom: 18px;
}
.footer__l { display: flex; flex-direction: column; gap: 11px; font-size: var(--t-small); color: var(--bone-dim); }
/* Links shift right rather than only changing colour: on a tinted ground a
   colour-only hover is easy to miss. */
.footer__l a {
  display: inline-block;
  width: fit-content;
  transition: color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.footer__l a:hover { color: var(--footer-accent); transform: translateX(3px); }

/* Bottom bar: copyright one side, social row the other. Reversed on mobile so
   the icons sit above the legal line, as on the reference. */
.footer__bottom {
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding-block: clamp(20px, 2.4vw, 30px);
  border-top: 1px solid var(--hairline-2);
  font-size: var(--t-micro);
  color: var(--bone-faint);
  text-align: center;
}
@media (min-width: 780px) {
  .footer__bottom { flex-direction: row; text-align: left; }
}

.footer__social { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; }
.footer__social a {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  color: var(--bone-dim);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.footer__social a:hover {
  color: var(--on-orange);
  background: var(--orange-fill);
  border-color: var(--orange-fill);
}
.footer__social svg { width: 16px; height: 16px; fill: currentColor; }

/* 36px is a comfortable size on a mouse but under the 44px touch minimum. */
@media (pointer: coarse) {
  .footer__social { gap: 10px; }
  .footer__social a { width: 44px; height: 44px; }
  .footer__social svg { width: 18px; height: 18px; }
}

/* -------------------------------------------------------------------------
   11. Reveal — one motion idea, applied everywhere
   ---------------------------------------------------------------------- */
/* Scoped to .js, which an inline script in <head> sets immediately. If the
   module script fails, 404s, or the browser can't parse it, the content is
   simply visible instead of an invisible page. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; }
  .tile__img, .tile__hover, .btn::before, .btn::after, .wipe::before { transition: none !important; }
  .stage::after { display: none; }
  /* The header must not slide away for someone who asked for less motion. */
  .header { transition: background-color 0.3s linear, border-color 0.3s linear; }
  .header.is-hidden { transform: none; }
}

/* Skip link */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 12px 18px;
  background: var(--orange-fill);
  color: var(--on-orange);
  font-weight: 500;
}
.skip:focus { left: var(--pad); top: 12px; }
