/* ==========================================================================
   Green Ferro Alloys Private Limited
   Zero-build modern CSS — cascade layers, nesting, container queries,
   scroll-driven animations, view transitions.
   Type: Archivo (display) / IBM Plex Sans (body) / IBM Plex Mono (data)
   Palette sampled from the company logo.
   ========================================================================== */

@layer reset, tokens, base, layout, components, utils;

/* ---------------------------------------------------------------- reset -- */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }
  html { -webkit-text-size-adjust: 100%; }
  body { -webkit-font-smoothing: antialiased; }
  img, svg, video, iframe { display: block; max-inline-size: 100%; }
  input, button, textarea, select { font: inherit; color: inherit; }
  ul[class], ol[class] { list-style: none; padding: 0; }
  a { color: inherit; text-decoration: none; }
  h1, h2, h3, h4 { text-wrap: balance; }
  p, li { text-wrap: pretty; }
}

/* --------------------------------------------------------------- tokens -- */
@layer tokens {
  :root {
    /* Brand — sampled from the logo artwork */
    --forest: #126223;
    --forest-dk: #0b431a;
    --forest-xdk: #062c11;
    --green: #0a9144;
    --leaf: #52ba25;
    --lime: #aeef53;

    /* Neutrals — warm paper + graphite, not blue-grey */
    --ink: #14170f;
    --ink-2: #3d4438;
    --ink-3: #6b7364;
    --paper: #f6f5f1;
    --paper-2: #edece5;
    --white: #fff;
    --rule: #d9d8cf;
    --rule-dk: color-mix(in srgb, var(--white) 16%, transparent);

    /* Steel accent (replaces the old gold) */
    --steel: #8b9199;
    --steel-lt: #c3c7cc;

    /* Type */
    --ff-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
    --ff-body: "IBM Plex Sans", system-ui, sans-serif;
    --ff-mono: "IBM Plex Mono", ui-monospace, "SF Mono", monospace;

    /* Fluid scale */
    --t-mega: clamp(2.75rem, 7vw, 6rem);
    --t-xl: clamp(2rem, 4.4vw, 3.5rem);
    --t-lg: clamp(1.5rem, 2.6vw, 2.15rem);
    --t-md: clamp(1.15rem, 1.5vw, 1.35rem);
    --t-sm: 0.9375rem;
    --t-xs: 0.8125rem;
    --t-label: 0.6875rem;

    /* Space */
    --shell: 1240px;
    --pad: clamp(1.15rem, 4vw, 2.5rem);
    --gap: clamp(1.5rem, 3vw, 2.5rem);
    --sec: clamp(4.5rem, 9vw, 8rem);

    --ease: cubic-bezier(.2, .7, .3, 1);
  }
}

/* ----------------------------------------------------------------- base -- */
@layer base {
  html {
    scroll-behavior: smooth;
    scroll-padding-block-start: 7.5rem;
    /* `clip` rather than `hidden`: it contains the off-canvas nav drawer
       without creating a scroll container, so the sticky masthead survives. */
    overflow-x: clip;
  }

  body {
    font-family: var(--ff-body);
    font-size: 1rem;
    line-height: 1.65;
    color: var(--ink-2);
    background: var(--paper);
    overflow-x: clip;
  }

  h1, h2, h3, h4 {
    font-family: var(--ff-display);
    color: var(--ink);
    font-weight: 700;
    line-height: 1.02;
    letter-spacing: -0.032em;
  }

  strong { color: var(--ink); font-weight: 600; }

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

  ::selection { background: var(--lime); color: var(--ink); }

  /* Cross-document page transitions */
  @view-transition { navigation: auto; }
}

/* --------------------------------------------------------------- layout -- */
@layer layout {
  .shell {
    inline-size: min(100% - (var(--pad) * 2), var(--shell));
    margin-inline: auto;
  }
  .shell--narrow { --shell: 880px; }

  .sec { padding-block: var(--sec); }
  /* The hero ends on a wide expanse of photograph, so a full section pad under
     it read as a hole. Halved for the section that directly follows only. */
  .hero + .sec { padding-block-start: calc(var(--sec) * .5); }
  .sec--tight { padding-block: calc(var(--sec) * .58); }

  .ink-bg { background: var(--forest-xdk); color: color-mix(in srgb, var(--white) 76%, transparent); }
  .paper-2-bg { background: var(--paper-2); }

  /* Tapping photograph behind the power section.

     The scrim is measured, not guessed. Below 900px the copy spans the full
     width and sits over the bright tap, so it needs a strong uniform scrim
     (body copy 5.9:1). At 900px the copy moves to the right column, which the
     photograph is already dark behind, so the scrim lifts and lets the tap
     show (body 8.1:1, heading 10.2:1). forest-xdk stays underneath as the
     fallback if the image fails to load. */
  .power-bg {
    background-color: var(--forest-xdk);
    background-image:
      linear-gradient(rgb(8 26 14 / .72), rgb(8 26 14 / .72)),
      url("../img/tapping-crew.webp?v=8");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;

    @media (min-width: 900px) {
      background-image:
        linear-gradient(to right,
          rgb(8 26 14 / .12) 0%,  rgb(8 26 14 / .20) 42%,
          rgb(8 26 14 / .50) 58%, rgb(8 26 14 / .58) 100%),
        url("../img/tapping-crew.webp?v=8");
    }
  }

  /* Punakha valley behind the "Why Bhutan" section.

     The scrim is a gradient rather than a flat wash because the photograph is
     not uniformly bright: sky at the top (relative luminance .91 at the 98th
     percentile), dark forested hills through the middle (.16), then bright
     water again at the bottom (.89). A flat scrim heavy enough for the sky
     would erase the hills, which are the part worth seeing.

     The binding constraint is not the heading. Inside .ink-bg the panels drop
     to a 5% white plate with body copy at 66% white, so the photograph shows
     through the cards and that copy is the lowest-contrast text on the page.
     Stops were solved against it, sampled per band at the 98th percentile:
     card body 4.9:1, section body 5.2:1, label 5.5:1, headings 7.5:1 and up.
     forest-xdk stays underneath as the fallback if the image fails to load. */
  /* Bulk bags behind the sizing and packaging section. Cropped before it got
     here: the distant stacks end at 11% of the source frame and the site debris
     and tarpaulin start around 72%, so it keeps only the main stack between.

     The scrim is heavy because it has to be. White bags under white type
     measured 1.25:1 with nothing over them, and this section carries three
     columns of specifications, so legibility beats the photograph. */
  .dispatch-bg {
    position: relative;
    isolation: isolate;
    background-color: var(--forest-xdk);

    /* Taller than its content needs, so cover has less to trim and more of the
       stack is visible - at the old height it was cropping a quarter of the
       frame away. 66.7vw is 100 / 1.5, the image's own ratio, so at common
       widths almost the whole picture shows. Content is centred in the extra
       room rather than sitting at the top of it. */
    min-block-size: min(66.7vw, 56rem);
    display: grid;
    align-content: center;

    &::before {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background-image:
        linear-gradient(rgb(8 26 14 / .72), rgb(8 26 14 / .72)),
        url("../img/bags-dispatch.webp?v=8");
      background-size: cover;
      background-position: center;
      background-repeat: no-repeat;
    }
  }

  .valley-bg {
    position: relative;
    isolation: isolate;
    background-color: var(--forest-xdk);

    /* Photograph and scrim both move onto the pseudo-element so they fade in
       together - separating them would let the picture arrive unscrimmed for a
       moment and flash the copy illegible. */
    &::before {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background-image:
        linear-gradient(to bottom,
          rgb(8 26 14 / .80) 0%,
          rgb(8 26 14 / .66) 26%,
          rgb(8 26 14 / .63) 50%,
          rgb(8 26 14 / .81) 76%,
          rgb(8 26 14 / .87) 100%),
        url("../img/bhutan-valley.webp?v=8");
      background-size: cover;
      background-position: center;
      background-repeat: no-repeat;
    }
  }

  .cols { display: grid; gap: var(--gap); }
  @container (min-width: 640px) { .cols--2 { grid-template-columns: 1fr 1fr; } }

  .grid-2 { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
  .grid-3 { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }

  /* Board listing: five cards. auto-fit squeezes them to five narrow columns
     where the longer job titles break badly, so pin it to three. That also
     splits the group where it should: board on row one, executives on row two. */
  .grid-people {
    display: grid;
    gap: var(--gap);
    grid-template-columns: 1fr;
    @media (min-width: 560px) { grid-template-columns: repeat(2, 1fr); }
    @media (min-width: 900px) { grid-template-columns: repeat(3, 1fr); }
  }

  /* Editorial asymmetric split */
  /* Product photograph filling the otherwise empty first column of the range
     split. Unlike a decorative mark this is content - it shows the material the
     grades beside it describe - so it keeps a real alt and stays on mobile.

     On phones the split collapses to one column, where the full 2:3 portrait
     would run to roughly 500px and push the grade list off the screen. There it
     crops to a band instead; the aspect only opens up once there is a column. */
  /* Mounted like the lab slideshow on the products page: same 3px forest frame,
     same border darkening and the same slow scale on hover. The frame is a
     separate element because the scale has to be clipped by something - scaling
     the image inside its own border would push the picture out past it. */
  .product-frame {
    margin-block-start: 2.25rem;
    border: 3px solid var(--forest);
    background: var(--paper-2);
    overflow: hidden;
    transition: border-color .4s;

    &:hover { border-color: var(--forest-xdk); }

    @media (min-width: 900px) { margin-block-start: 4rem; }
  }

  .product-shot {
    display: block;
    inline-size: 100%;
    block-size: auto;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    object-position: center;

    @media (min-width: 900px) { aspect-ratio: 2 / 3; }
  }

  /* Motion only where it is welcome. The slideshow guards its drift the same way. */
  @media (prefers-reduced-motion: no-preference) {
    .product-shot { transition: scale .9s cubic-bezier(.16, 1, .3, 1); }
    .product-frame:hover .product-shot { scale: 1.05; }
  }


  .split {
    display: grid;
    gap: clamp(2rem, 5vw, 4.5rem);
    align-items: start;
    grid-template-columns: 1fr;

    @media (min-width: 900px) {
      grid-template-columns: 0.85fr 1.15fr;
      &.split--wide-first { grid-template-columns: 1.15fr 0.85fr; }
    }
  }
}

/* ----------------------------------------------------------- components -- */
@layer components {

  /* --- Mono label with index number ------------------------------------ */
  .label {
    font-family: var(--ff-mono);
    font-size: 0.875rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--forest);
    display: flex;
    align-items: center;
    gap: 0.7em;

    &::after {
      content: "";
      block-size: 1px;
      inline-size: clamp(1.5rem, 5vw, 4rem);
      background: currentColor;
      opacity: .45;
    }

    .ink-bg &, .page-head & { color: var(--lime); }
  }

  .label--plain::after { display: none; }

  /* A centred section head, for blocks that have no second column to balance
     against. The label's trailing rule is dropped, since a line running off
     one side of centred text reads as a mistake. */
  .sec-head.sec-head--centre {
    justify-items: center;
    text-align: center;
    max-inline-size: none;
    margin-inline: auto;
    gap: 0;

    & .label::after { display: none; }

    /* Each element gets its own measure rather than one width for the block.
       Display type can run wide; centred body copy must not, because the
       reader has to find the start of every line and it moves each time.
       ch is relative to each element's own size, so one value does both. */
    & h2 { max-inline-size: 26ch; }
    /* 37ch, not 46: the ch unit measures a zero, which is wider than average
       lowercase, so 46ch was landing at 56 characters a line. This gives about
       45, which is the comfortable ceiling for centred copy. */
    & p:not(.label) { max-inline-size: 37ch; }

    & .label { margin-block-end: 1.5rem; }
    & p:not(.label) { margin-block-start: 1rem; }
  }

  /* A split used as a section head: same two-column rhythm as the rest of the
     site, but carrying the trailing space that .sec-head would have given it. */
  .split--head { margin-block-end: clamp(2.5rem, 5vw, 4rem); }

  /* --- Section heading -------------------------------------------------- */
  .sec-head {
    display: grid;
    gap: 1.1rem;

    /* Grid gaps do not collapse with margins, so this adds to the 1.1rem
       rather than replacing it. Keeps a big heading off the copy below. */
    > :is(h1, h2) { margin-block-end: .25em; }
    max-inline-size: 62ch;
    margin-block-end: clamp(2.5rem, 5vw, 4rem);

    h2 { font-size: var(--t-xl); }
    /* :not(.label) so the green section label keeps its own size/colour */
    p:not(.label) { font-size: var(--t-md); color: var(--ink-3); }
    .ink-bg & p:not(.label) { color: color-mix(in srgb, var(--white) 66%, transparent); }
    .ink-bg & h2 { color: var(--white); }
  }

  /* --- Buttons ---------------------------------------------------------- */
  .btn {
    --btn-bg: var(--forest);
    --btn-fg: var(--white);
    display: inline-flex;
    align-items: center;
    gap: 0.65em;
    padding: 0.95em 1.6em;
    background: var(--btn-bg);
    color: var(--btn-fg);
    border: 1px solid var(--btn-bg);
    border-radius: 2px;
    font-family: var(--ff-mono);
    font-size: var(--t-xs);
    font-weight: 500;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);

    & svg { inline-size: 1em; block-size: 1em; flex: none; transition: translate .25s var(--ease); }
    &:hover { --btn-bg: var(--ink); & svg { translate: 3px 0; } }

    &.btn--ghost {
      --btn-bg: transparent;
      --btn-fg: var(--ink);
      border-color: var(--rule);
      &:hover { --btn-bg: var(--ink); --btn-fg: var(--white); border-color: var(--ink); }
    }

    &.btn--on-dark {
      --btn-bg: transparent;
      --btn-fg: var(--white);
      border-color: var(--rule-dk);
      &:hover { --btn-bg: var(--lime); --btn-fg: var(--ink); border-color: var(--lime); }
    }

    &.btn--lime {
      --btn-bg: var(--lime);
      --btn-fg: var(--ink);
      &:hover { --btn-bg: var(--white); border-color: var(--white); }
    }
  }

  .txt-link {
    display: inline-flex;
    align-items: center;
    gap: .5em;
    font-family: var(--ff-mono);
    font-size: var(--t-xs);
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--forest);
    border-block-end: 1px solid color-mix(in srgb, currentColor 35%, transparent);
    padding-block-end: 2px;
    transition: color .2s, border-color .2s;

    & svg { inline-size: 1em; block-size: 1em; transition: translate .25s var(--ease); }
    &:hover { color: var(--ink); border-color: currentColor; & svg { translate: 3px 0; } }
    .ink-bg & { color: var(--lime); &:hover { color: var(--white); } }
  }

  /* --- Masthead --------------------------------------------------------- */
  .masthead {
    position: sticky;
    inset-block-start: 0;
    /* Above the scrim (94) so the drawer, which lives inside this header,
       paints over the dimmed page rather than under it. */
    z-index: 95;
    border-block-end: 1px solid var(--rule);
    transition: border-color .3s;

    /* The blur sits on a pseudo-element, not on the header itself. A
       backdrop-filter establishes a containing block for fixed-position
       descendants, which trapped the mobile nav inside the header: it
       rendered 323x120 in the corner instead of covering the screen. */
    &::before {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background: color-mix(in srgb, var(--paper) 88%, transparent);
      backdrop-filter: blur(10px);
    }

    & > .shell {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1.5rem;
      min-block-size: clamp(6.25rem, 7.4vw, 6.75rem);
    }
    &.is-stuck { border-color: color-mix(in srgb, var(--ink) 18%, transparent); }
  }



  /* The masthead spans the full window rather than the centred 1240px shell, so
     the brand sits on the page gutter instead of 100px inside it. Done by
     lifting the cap on this shell only: .shell is min(100% - pad*2, --shell),
     so a large --shell leaves the gutter as the only inset.

     Preferred over a negative margin on .brand, which moved the logo but left
     the nav where it was, and had to be switched off below 1360px to stop the
     logo sliding off the edge. This scales at every width and stays symmetric. */
  .masthead > .shell { --shell: 200rem; }

  /* Fluid, not fixed. At a flat 1.42rem/4.1rem the brand grew wide enough on a
     375px screen to push the burger off the right edge, so the larger sizes are
     only reached once there is width to spend. */
  .brand {
    display: inline-flex;
    align-items: center;
    gap: .75rem;
    flex: none;

    & img { inline-size: clamp(3.4rem, 5.6vw, 5rem); block-size: clamp(3.4rem, 5.6vw, 5rem); }
    & b {
      font-family: var(--ff-display);
      font-weight: 700;
      font-size: clamp(1.16rem, 1.45vw, 1.28rem);
      letter-spacing: -.025em;
      line-height: 1.05;
      color: var(--ink);
      display: block;
    }
    & span {
      font-family: var(--ff-mono);
      font-size: clamp(0.5625rem, 0.7vw, 0.62rem);
      letter-spacing: .22em;
      text-transform: uppercase;
      color: var(--ink-3);
    }
  }

  .nav {
    display: flex;
    align-items: center;
    gap: .25rem;

    & a {
      padding: .55rem .85rem;
      font-family: var(--ff-mono);
      font-size: var(--t-xs);
      letter-spacing: .08em;
      text-transform: uppercase;
      color: var(--ink-2);
      position: relative;
      transition: color .2s;

      &::after {
        content: "";
        position: absolute;
        inset-block-end: .3rem;
        inset-inline: .85rem;
        block-size: 1px;
        background: var(--forest);
        scale: 0 1;
        transform-origin: left;
        transition: scale .28s var(--ease);
      }
      &:hover { color: var(--forest); }
      &:hover::after, &[aria-current="page"]::after { scale: 1 1; }
      &[aria-current="page"] { color: var(--forest); }
    }
  }

  /* Both of these belong to the drawer, which only exists below 940px. */
  .nav-close { display: none; }

  .burger {
    display: none;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    place-items: center;
    background: none;
    border: 1px solid var(--rule);
    border-radius: 2px;
    cursor: pointer;

    & span { inline-size: 1.1rem; block-size: 1px; background: var(--ink); position: relative; transition: background .2s; }
    & span::before, & span::after {
      content: ""; position: absolute; inset-inline-start: 0; inline-size: 1.1rem; block-size: 1px; background: var(--ink);
      transition: translate .3s var(--ease), rotate .3s var(--ease);
    }
    & span::before { translate: 0 -6px; }
    & span::after { translate: 0 6px; }

    &[aria-expanded="true"] span {
      background: transparent;
      &::before { translate: 0 0; rotate: 45deg; }
      &::after { translate: 0 0; rotate: -45deg; }
    }
  }

  @media (max-width: 940px) {
    .burger { display: grid; }
    .nav {
      position: fixed;
      /* Top-anchored with an auto bottom, so the panel is only as tall as the
         links it holds. Running to the floor left a large empty white area
         under the last item. */
      inset-block-start: 0;
      inset-block-end: auto;
      inset-inline-end: 0;
      max-block-size: 100dvh;
      inline-size: min(86vw, 22rem);
      z-index: 95;
      flex-direction: column;
      align-items: stretch;
      gap: 0;
      background: var(--white);
      padding: 4.75rem 1.5rem 1.25rem;
      border-inline-start: 1px solid var(--rule);
      border-block-end: 1px solid var(--rule);
      translate: 100% 0;
      transition: translate .38s var(--ease);
      overflow-y: auto;

      & a { padding: 1rem .25rem; border-block-end: 1px solid var(--rule); font-size: .8125rem; }
      & a:last-of-type { border-block-end: 0; }
      & a::after { display: none; }
      &.is-open { translate: 0 0; }
    }
    .nav-close {
      display: grid;
      position: absolute;
      inset-block-start: 1.1rem;
      inset-inline-end: 1.25rem;
      inline-size: 2.5rem;
      block-size: 2.5rem;
      display: grid;
      place-items: center;
      padding: 0;
      background: none;
      border: 1px solid var(--rule);
      border-radius: 2px;
      color: var(--ink-2);
      cursor: pointer;
      transition: color .2s, border-color .2s;

      & svg { inline-size: 1rem; block-size: 1rem; }
      &:hover { color: var(--forest); border-color: var(--forest); }
      &:focus-visible { outline: 2px solid var(--forest); outline-offset: 2px; }
    }

    .scrim {
      position: fixed; inset: 0; z-index: 94;
      background: color-mix(in srgb, var(--ink) 45%, transparent);
      opacity: 0; visibility: hidden; transition: opacity .3s;
      &.is-open { opacity: 1; visibility: visible; }
    }
  }

  /* --- Hero ------------------------------------------------------------- */
  /* Plant hero: the switchyard photograph, untinted.

     No scrim, by request. That makes framing the only lever for legibility, so
     the headline is anchored to the bottom of the section where the dark
     cladding sits, and the crop is chosen to keep the sky out from behind it.
     Position is 90% rather than centre: at 75% the top third of the headline
     landed on the pale awning roof and fell to 2.5:1. Contrast is measured on
     the h1's real box against the rendered crop, not an estimate.

     The logo watermark that used to sit here is gone. At 10% over a photograph
     it read as a smudge rather than a mark. */
  .hero {
    position: relative;
    /* forest-xdk stays as the colour underneath, so a failed image load leaves
       the original solid band rather than white. */
    background-color: var(--forest-xdk);
    color: var(--white);

    /* The photograph sits on a pseudo-element rather than on .hero itself.
       Fading the section would take the headline and buttons with it; this way
       only the picture arrives, with forest-xdk showing through underneath
       while it does. Above the fold, so it is time-based like the copy. */
    &::before {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background: url("../img/plant-switchyard.webp?v=8") center top / cover no-repeat;
    }
    overflow: hidden;
    isolation: isolate;

    /* The supplied image is 1264x843, i.e. 1.50:1, and the box is sized to the
       same ratio (100 / 1.5 = 66.7vw) so cover has nothing to trim and the whole
       frame is visible. A shorter box was cropping 27% away.

       The floor is for phones only: a 1.5:1 landscape image in a portrait hero
       cannot be shown whole without letterboxing, so there it still crops. */
    /* 66.7vw is 100 / 1.5, the image's own ratio, so on a tall enough window the
       whole frame shows with nothing cropped. Capped at the viewport minus the
       masthead so the hero plus header always fit one screen - past that point
       cover trims a little off the frame, which is the trade for not scrolling. */
    block-size: min(66.7vw, calc(100svh - 7rem));
    min-block-size: 26rem;
    display: grid;
    align-content: end;
  }

  .hero-inner {
    padding-block: clamp(4.5rem, 11vw, 9rem) clamp(3rem, 6vw, 5rem);
    max-inline-size: min(100%, 60rem);
  }

  .hero h1 {
    font-size: var(--t-mega);
    color: var(--white);
    margin-block: 1.5rem 1.75rem;
    letter-spacing: -0.042em;
    /* ch resolves against the h1's own display size, so this is a real
       headline measure rather than a body-text one. */
    max-inline-size: 15ch;

    & em {
      font-style: normal;
      color: var(--lime);
      /* Hairline underscore rather than a colour-only accent */
      box-shadow: inset 0 -0.09em 0 color-mix(in srgb, var(--lime) 40%, transparent);
    }
  }

  .hero p {
    font-size: var(--t-md);
    color: color-mix(in srgb, var(--white) 74%, transparent);
    max-inline-size: 46ch;
  }

  .hero-actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-block-start: 2.5rem; }

  /* --- Page header (inner pages) ---------------------------------------- */
  .page-head {
    background: var(--forest-xdk);
    color: var(--white);
    padding-block: clamp(3.5rem, 8vw, 6rem) clamp(2.5rem, 5vw, 4rem);
    position: relative;
    overflow: hidden;
    isolation: isolate;

    &::before {
      content: "";
      position: absolute;
      inset: 0;
      background: repeating-linear-gradient(90deg, transparent 0 calc(25% - 1px), var(--rule-dk) calc(25% - 1px) 25%);
      z-index: -1;
    }
    & h1 { font-size: var(--t-xl); color: var(--white); margin-block: 1.25rem .9rem; }
    & p:not(.label) { color: color-mix(in srgb, var(--white) 72%, transparent); max-inline-size: 56ch; font-size: var(--t-md); }
  }

  .crumbs {
    display: flex;
    gap: .6rem;
    margin-block-start: 1.75rem;
    font-family: var(--ff-mono);
    font-size: var(--t-label);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--white) 52%, transparent);

    & a:hover { color: var(--lime); }
    & [aria-current] { color: var(--lime); }
  }

  /* --- Stat row --------------------------------------------------------- */
  .stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
    border-block: 1px solid var(--rule);

    & > div {
      padding: clamp(1.5rem, 3vw, 2.25rem) clamp(1rem, 2vw, 1.75rem);
      border-inline-start: 1px solid var(--rule);
      &:first-child { border-inline-start: 0; padding-inline-start: 0; }
    }
    & dt {
      font-family: var(--ff-mono);
      font-size: var(--t-label);
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--ink-3);
      margin-block-end: .6rem;
    }
    & dd {
      font-family: var(--ff-display);
      font-size: clamp(2rem, 4vw, 3rem);
      font-weight: 700;
      letter-spacing: -.04em;
      color: var(--ink);
      line-height: 1;
    }
    & dd sup { font-size: .45em; vertical-align: super; color: var(--leaf); font-weight: 600; }
  }

  /* --- Numbered process list ------------------------------------------- */
  .steps { border-block-start: 1px solid var(--rule); }

  .step {
    display: grid;
    gap: .5rem 2rem;
    padding-block: clamp(1.5rem, 3vw, 2.25rem);
    border-block-end: 1px solid var(--rule);
    grid-template-columns: 1fr;
    transition: background .3s var(--ease);

    @media (min-width: 760px) {
      grid-template-columns: 5rem 16rem 1fr;
      align-items: baseline;
    }
    &:hover { background: color-mix(in srgb, var(--leaf) 6%, transparent); }

    & .n {
      font-family: var(--ff-mono);
      font-size: var(--t-xs);
      color: var(--leaf);
      letter-spacing: .1em;
    }
    & h3 { font-size: var(--t-md); letter-spacing: -.02em; }
    & p { color: var(--ink-3); font-size: var(--t-sm); }
  }

  /* --- Per-product spec cards (products) --------------------------------- */
  .grid-pair {
    display: grid;
    gap: var(--gap);
    grid-template-columns: 1fr;
    @media (min-width: 820px) { grid-template-columns: 1fr 1fr; }
  }

  .spec-card {
    background: var(--white);
    border: 1px solid var(--rule);
    border-radius: 2px;
    padding: clamp(1.5rem, 3vw, 2rem);
    scroll-margin-block-start: 7.5rem;
    transition: border-color .3s var(--ease), translate .3s var(--ease);

    &:hover { border-color: var(--forest); translate: 0 -3px; }
    & h3 { font-size: var(--t-lg); margin-block: .55rem .3rem; }
    & .cls {
      font-family: var(--ff-mono);
      font-size: var(--t-xs);
      color: var(--ink-3);
      margin-block-end: 1.25rem;
    }
  }

  /* Compact chemistry readout: replaces a full table per product. */
  .chem {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(4.75rem, 1fr));
    gap: 1px;
    background: var(--rule);
    border: 1px solid var(--rule);

    & > div { background: var(--white); padding: .7rem .8rem; }
    & dt {
      font-family: var(--ff-mono);
      font-size: var(--t-label);
      letter-spacing: .1em;
      text-transform: uppercase;
      color: var(--ink-3);
    }
    & dd {
      font-family: var(--ff-mono);
      font-size: .9375rem;
      font-weight: 600;
      color: var(--ink);
      margin: .15rem 0 0;
      font-variant-numeric: tabular-nums;
      white-space: nowrap;
    }
  }

  /* --- Range teaser list (home) ----------------------------------------- */
  .range-list {
    border-block-start: 1px solid var(--rule);
    margin-block-start: 1.75rem;

    /* On the products page these rows are plain divs rather than links - the
       reader is already on the page those anchors point at. The layout is
       shared; only the real links get the hover affordance, so a non-clickable
       row never lights up as though it were interactive. */
    & :is(a, div) {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      justify-content: space-between;
      gap: .35rem 1.5rem;
      padding: 1.05rem .6rem 1.05rem 0;
      border-block-end: 1px solid var(--rule);
      transition: background .25s var(--ease), padding .25s var(--ease);
    }
    & a:hover {
      background: color-mix(in srgb, var(--leaf) 7%, transparent);
      padding-inline: .6rem;
    }
    /* Inbound /products#<grade> links land here; clear the sticky masthead. */
    & [id] { scroll-margin-block-start: 8rem; }
    & .nm {
      font-family: var(--ff-display);
      font-weight: 600;
      font-size: var(--t-md);
      letter-spacing: -.02em;
      color: var(--ink);
    }
    & .cl {
      font-family: var(--ff-mono);
      font-size: var(--t-label);
      letter-spacing: .12em;
      text-transform: uppercase;
      color: var(--ink-3);
    }
  }

  /* --- Spec table ------------------------------------------------------- */
  .table-scroll { overflow-x: auto; border: 1px solid var(--rule); background: var(--white); }

  table.spec {
    inline-size: 100%;
    border-collapse: collapse;
    font-family: var(--ff-mono);
    font-size: var(--t-sm);
    min-inline-size: 40rem;
    font-variant-numeric: tabular-nums;

    /* The caption is the table's title, not a footnote label, so it is set in
       the display face at heading scale with a brand rule above it. */
    & caption {
      caption-side: top;
      text-align: start;
      padding: 1.35rem 1.25rem 1.15rem;
      background: var(--paper-2);
      border-block-start: 3px solid var(--leaf);
      border-block-end: 1px solid var(--rule);
      font-family: var(--ff-display);
      font-weight: 700;
      font-size: var(--t-lg);
      line-height: 1.15;
      letter-spacing: -0.01em;
      text-transform: none;
      color: var(--forest);
    }
    & th, & td { padding: .85rem 1.25rem; text-align: start; border-block-end: 1px solid var(--rule); }
    & thead th {
      background: var(--forest-xdk);
      color: var(--white);
      font-size: var(--t-label);
      letter-spacing: .12em;
      text-transform: uppercase;
      font-weight: 500;
      white-space: nowrap;
    }
    & tbody th { color: var(--ink); font-weight: 600; }
    & tbody tr:last-child :is(th, td) { border-block-end: 0; }
    & tbody tr:nth-child(even) { background: color-mix(in srgb, var(--paper) 55%, transparent); }
    & td { color: var(--ink-2); }
  }

  /* Portfolio table: grade names carry a small qualifier, rows are anchors. */
  .spec--portfolio {
    min-inline-size: 52rem;

    & tbody th { line-height: 1.3; }
    & .g-sub {
      display: block;
      font-size: var(--t-label);
      font-weight: 400;
      letter-spacing: .1em;
      text-transform: uppercase;
      color: var(--leaf);
      margin-block-start: .2rem;
    }
    & tbody tr:target { background: color-mix(in srgb, var(--leaf) 12%, transparent); }
  }
  .spec tr[id] { scroll-margin-block-start: 8rem; }

  /* --- Feature list ----------------------------------------------------- */
  .ticks {
    display: grid;
    gap: 1rem;

    & li { display: grid; grid-template-columns: auto 1fr; gap: .85rem; align-items: start; font-size: var(--t-sm); }
    & svg { inline-size: 1.05rem; block-size: 1.05rem; margin-block-start: .3rem; color: var(--leaf); flex: none; }
  }

  /* --- Bordered info panel --------------------------------------------- */
  .panel {
    border: 1px solid var(--rule);
    background: var(--white);
    padding: clamp(1.5rem, 3vw, 2.25rem);
    display: grid;
    /* Cards in a row are stretched to a common height. Without this, the spare
       height in the shorter ones is handed to their rows, so their headings sit
       lower than the tallest card's and the row reads as misaligned - 78px vs
       94px from the top in the "Why Bhutan" three. Pack the rows to the top and
       let the spare height fall to the bottom of the card instead. */
    align-content: start;
    gap: 1rem;
    border-radius: 2px;

    & h3 { font-size: var(--t-md); }
    & p, & address { font-size: var(--t-sm); color: var(--ink-3); font-style: normal; }
    & a:hover { color: var(--forest); }

    .ink-bg & {
      background: color-mix(in srgb, var(--white) 5%, transparent);
      border-color: var(--rule-dk);
      & h3 { color: var(--white); }
      & p, & address { color: color-mix(in srgb, var(--white) 66%, transparent); }
    }

    /* Over the dispatch photograph the 5% lift above goes the wrong way: the
       bags behind these cards are the brightest thing in the frame, and the
       66% body copy fell to 4.01:1 against them. Tint down instead of up. At
       30% ink the copy reads 5.58:1 with headroom, and the cards separate from
       a busy photograph the way they separate from flat ink elsewhere. */
    .dispatch-bg & {
      background: color-mix(in srgb, var(--forest-xdk) 30%, transparent);
    }
  }

  .panel-index {
    font-family: var(--ff-mono);
    font-size: var(--t-label);
    letter-spacing: .14em;
    color: var(--leaf);
    text-transform: uppercase;
  }

  /* --- Quoted vision / mission statement -------------------------------- */
  /* Two statements side by side. No card and no quote bar - both put a heavy
     frame around words that only needed separating from each other, which one
     hairline does. The role leads in the site's label style so you know which
     statement you are reading before you read it. */
  .pair {
    display: grid;
    gap: clamp(2rem, 4vw, 3.5rem);

    /* A subtitle, not a caption: a step up from the site's label size, with
       room under it. */
    & .label {
      font-size: 1.05rem;
      margin-block-end: 1.15rem;
    }

    /* The ethos is the section now that nothing sits above it, so it is set as
       display type rather than body copy. */
    & p:not(.label) {
      font-family: var(--ff-display);
      font-size: var(--t-lg);
      font-weight: 700;
      line-height: 1.3;
      letter-spacing: -0.015em;
      color: var(--ink);
      margin: 0;
      text-wrap: pretty;
    }

    @media (min-width: 760px) {
      grid-template-columns: 1fr 1fr;
      & > * + * {
        border-inline-start: 1px solid var(--rule);
        padding-inline-start: clamp(2rem, 4vw, 3.5rem);
      }
    }
  }

  .statement {
    border-inline-start: 3px solid var(--leaf);
    padding-inline-start: clamp(1.15rem, 3vw, 2rem);

    & p {
      font-family: var(--ff-display);
      font-size: var(--t-lg);
      font-weight: 600;
      line-height: 1.3;
      letter-spacing: -0.02em;
      color: var(--ink);
      text-wrap: pretty;
    }
    & cite {
      display: block;
      margin-block-start: .9rem;
      font-family: var(--ff-mono);
      font-size: var(--t-label);
      font-style: normal;
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--ink-3);
    }
    /* Only when they are stacked in one column. Inside a grid the columns
       carry their own gap, and this margin pushed the second statement below
       the first instead of level with it. */
    & + .statement { margin-block-start: clamp(1.75rem, 4vw, 2.5rem); }
    .grid-2 > & + & { margin-block-start: 0; }


    .ink-bg & {
      border-inline-start-color: var(--lime);
      & p { color: #fff; }
      & cite { color: color-mix(in srgb, var(--white) 60%, transparent); }
    }
  }

  /* --- Partner card ------------------------------------------------------ */
  .partner {
    border: 1px solid var(--rule);
    background: var(--white);
    border-radius: 2px;
    padding: clamp(1.5rem, 3vw, 2.25rem);
    display: flex;
    flex-direction: column;
    gap: .9rem;
    transition: border-color .3s var(--ease), translate .3s var(--ease);

    &:hover { border-color: var(--forest); translate: 0 -3px; }
    & h3 { font-size: var(--t-md); }
    & p { font-size: var(--t-sm); color: var(--ink-3); }
    & .txt-link { margin-block-start: auto; align-self: flex-start; }

    /* Partner marks are supplied artwork on light grounds: contain, never crop.
       Mark and name are centred; the prose below stays ranged left, because
       centred body copy over this measure is hard to read. */
    & .partner-logo {
      inline-size: clamp(6.5rem, 13vw, 9.5rem);
      block-size: clamp(6.5rem, 13vw, 9.5rem);
      object-fit: contain;
      align-self: center;
      margin-block-end: .35rem;
    }
    & h3 { text-align: center; }

    /* A white-on-white mark disappears against the card, so give it an edge.
       The artwork's circle meets the frame, so the ring traces it exactly. */
    & .partner-logo--ring {
      border-radius: 50%;
      border: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
      background: var(--white);
    }
  }

  /* --- Board card: portrait, name, post, company ------------------------
     Full-bleed square portrait rather than a small avatar: a board page
     should read with some weight. Photo and monogram share the same slot so
     cards stay uniform until every portrait exists. */
  .person {
    /* Flex, not the inherited grid: a stretched grid card distributes its
       spare height into the auto rows, which then inflates anything with an
       aspect-ratio past the card width. */
    display: flex;
    flex-direction: column;
    align-items: stretch;
    padding: 0;
    gap: 0;
    overflow: hidden;
    text-align: center;

    & .portrait,
    & .monogram {
      inline-size: 100%;
      aspect-ratio: 1;
      flex: none;
      display: block;
      margin: 0;
      border-radius: 0;
    }
    /* height="512" on the tag is a presentational hint; without this the
       attribute wins over aspect-ratio and the photo renders 372x512. */
    & .portrait { block-size: auto; object-fit: cover; object-position: center; }
    & .monogram {
      display: grid;
      place-items: center;
      background: var(--paper-2);
      border-block-end: 1px solid var(--rule);
      font-family: var(--ff-display);
      font-size: clamp(2.5rem, 6vw, 3.75rem);
      font-weight: 700;
      letter-spacing: .01em;
      color: var(--steel);
    }

    & h3 {
      font-size: var(--t-md);
      margin-block-start: 1.5rem;
      padding-inline: 1.5rem;
    }
    & .post {
      font-family: var(--ff-mono);
      font-size: var(--t-label);
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--leaf);
      margin-block-start: .45rem;
      padding-inline: 1.5rem;
    }
    & .org {
      font-size: var(--t-sm);
      color: var(--ink-3);
      margin-block: .6rem 0;
      padding: 0 1.5rem 1.6rem;
    }
  }

  /* --- Timeline (about) ------------------------------------------------- */
  .tl { border-block-start: 1px solid var(--rule); }
  .tl-row {
    display: grid;
    gap: .5rem 2rem;
    padding-block: clamp(1.5rem, 3vw, 2rem);
    border-block-end: 1px solid var(--rule);
    grid-template-columns: 1fr;

    @media (min-width: 760px) { grid-template-columns: 8rem 1fr; }

    & .yr {
      font-family: var(--ff-mono);
      font-size: var(--t-sm);
      color: var(--forest);
      font-weight: 500;
      font-variant-numeric: tabular-nums;
    }
    & h3 { font-size: var(--t-md); margin-block-end: .4rem; }
    & p { font-size: var(--t-sm); color: var(--ink-3); }
  }

  /* --- Native accordion ------------------------------------------------- */
  .faq {
    border-block-start: 1px solid var(--rule);

    & details {
      border-block-end: 1px solid var(--rule);

      & summary {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 1.5rem;
        padding-block: 1.35rem;
        cursor: pointer;
        list-style: none;
        font-family: var(--ff-display);
        font-weight: 600;
        font-size: var(--t-md);
        color: var(--ink);
        letter-spacing: -.02em;
        transition: color .2s;

        &::-webkit-details-marker { display: none; }
        &:hover { color: var(--forest); }

        &::after {
          content: "";
          inline-size: .7rem;
          block-size: .7rem;
          flex: none;
          border-inline-end: 1.5px solid currentColor;
          border-block-end: 1.5px solid currentColor;
          rotate: 45deg;
          transition: rotate .3s var(--ease);
        }
      }
      &[open] summary::after { rotate: -135deg; }
      & .faq-body { padding-block-end: 1.35rem; font-size: var(--t-sm); color: var(--ink-3); max-inline-size: 70ch; }
    }
  }

  /* --- Form ------------------------------------------------------------- */
  .form { display: grid; gap: 1.15rem; }
  .form-row { display: grid; gap: 1.15rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }

  .field {
    display: grid;
    gap: .45rem;

    & label {
      font-family: var(--ff-mono);
      font-size: var(--t-label);
      letter-spacing: .12em;
      text-transform: uppercase;
      color: var(--ink-2);
    }
    & :is(input, select, textarea) {
      padding: .8rem .9rem;
      background: var(--white);
      border: 1px solid var(--rule);
      border-radius: 2px;
      font-size: var(--t-sm);
      transition: border-color .2s, box-shadow .2s;

      &:focus {
        outline: none;
        border-color: var(--forest);
        box-shadow: 0 0 0 3px color-mix(in srgb, var(--leaf) 22%, transparent);
      }
    }
    & textarea { resize: vertical; min-block-size: 8rem; }
    & .msg { font-size: var(--t-xs); color: #b3261e; display: none; }

    &:has(:user-invalid) {
      & :is(input, select, textarea) { border-color: #b3261e; }
      & .msg { display: block; }
    }
  }

  /* --- Phone: country code selector ------------------------------------- */
  .tel { position: relative; display: grid; grid-template-columns: auto 1fr; }

  .tel-btn {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .8rem .7rem .8rem .9rem;
    background: var(--white);
    border: 1px solid var(--rule);
    border-inline-end: 0;
    border-radius: 2px 0 0 2px;
    font: inherit;
    font-size: var(--t-sm);
    color: var(--ink);
    cursor: pointer;
    white-space: nowrap;
    transition: border-color .2s, background .2s;

    &:hover { background: var(--paper); }
    &:focus-visible {
      outline: none;
      border-color: var(--forest);
      box-shadow: 0 0 0 3px color-mix(in srgb, var(--leaf) 22%, transparent);
      z-index: 2;
    }
  }

  .tel-flag { font-size: 1.1em; line-height: 1; }
  .tel-code {
    font-family: var(--ff-mono);
    font-variant-numeric: tabular-nums;

    &.is-empty { color: var(--ink-3); }
  }
  .tel-chev {
    inline-size: .8rem;
    block-size: .8rem;
    color: var(--ink-3);
    transition: transform .2s;
  }
  .tel-btn[aria-expanded="true"] .tel-chev { transform: rotate(180deg); }

  .field .tel input#phone { border-radius: 0 2px 2px 0; }

  .tel-pop {
    position: absolute;
    inset-block-start: calc(100% + .35rem);
    inset-inline: 0;
    z-index: 30;
    background: var(--white);
    border: 1px solid var(--rule);
    border-radius: 2px;
    box-shadow: 0 14px 34px -14px rgb(20 23 15 / .3);
    overflow: hidden;
  }

  .tel-search-wrap { padding: .55rem; border-block-end: 1px solid var(--rule); }

  /* Beat the generic .field input rule for the search box inside the panel. */
  .field .tel-pop input.tel-search {
    inline-size: 100%;
    padding: .55rem .7rem;
    font-size: var(--t-sm);
    border-color: var(--rule);
  }

  .tel-list {
    list-style: none;
    margin: 0;
    padding: .25rem;
    max-block-size: 15rem;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .tel-opt {
    display: grid;
    grid-template-columns: 1.5rem 1fr auto;
    align-items: center;
    gap: .6rem;
    padding: .5rem .6rem;
    font-size: var(--t-sm);
    border-radius: 2px;
    cursor: pointer;

    &:hover, &.is-active { background: color-mix(in srgb, var(--leaf) 15%, transparent); }
    &[aria-selected="true"] { font-weight: 600; }
  }
  .tel-opt-flag { font-size: 1.1em; line-height: 1; }
  .tel-opt-dial {
    font-family: var(--ff-mono);
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
  }
  .tel-empty { padding: .85rem .6rem; font-size: var(--t-sm); color: var(--ink-3); }

  /* Carry the invalid state onto the country button, and keep it off the
     search box, which has no constraints of its own. */
  .field:has(:user-invalid) .tel-btn { border-color: #b3261e; }
  .field:has(:user-invalid) .tel-pop input.tel-search { border-color: var(--rule); }

  /* The picture column runs taller than the commitments list beside it, so
     the default section padding left a large empty band underneath. Trimming
     the tail and letting the columns bottom-align closes it without shrinking
     the photograph. */
  /* The picture column runs taller than the commitments beside it. Bottom
     aligning the text closed the gap but dropped the heading down the page,
     so instead the copy keeps its place at the top and the list is given room
     to breathe until the two columns come out close to level. */
  .sec--assurance {
    padding-block-end: clamp(2rem, 4vw, 3.5rem);

    & .ticks {
      gap: 1.75rem;

      & li { font-size: 1.0625rem; line-height: 1.6; }
      & svg {
        inline-size: 1.1rem;
        block-size: 1.1rem;
        margin-block-start: .38rem;
      }
    }
  }

  /* --- Slideshow --------------------------------------------------------
     Until slides.js adds .is-live the slides just stack in normal flow, so a
     visitor without JS sees all three photographs rather than none. */
  /* Sized and finished the way Druk Wang treat theirs: a feature image beside
     text runs at about 43% of the viewport, square cornered, with no border,
     no shadow and no decoration. Measured on their site rather than guessed.
     Filling the column gets us there, and it is what stops the picture reading
     as a small tile dropped into the layout. */
  /* --- Full bleed material filmstrip ------------------------------------
     Three photographs across on a wide screen, edge to edge. Narrower than
     that it turns into a scroll-snapped carousel, which is the only width
     where the next control earns its place - see .filmstrip-next. */
  .filmstrip {
    --strip-gap: clamp(.85rem, 1.8vw, 1.5rem);

    position: relative;
    margin-block-start: clamp(2.5rem, 5vw, 4rem);
    /* Full bleed, and no cap: the strip runs the whole width of the page and
       off both edges. Held inside the shell it stopped short of the right edge
       with a band of paper beside it, which read as a mistake rather than as a
       strip that carries on. */
  }

  .filmstrip-track {
    display: grid;
    grid-auto-flow: column;
    column-gap: var(--strip-gap);
    /* Never a whole number, so the strip visibly runs past its right edge and
       there is something to swipe to. Solve n * cell + (ceil(n) - 1) * gap =
       100% for the cell, where n is how many are meant to show: 1.33 on a
       phone, 1.67 on a tablet, 2.78 on a desktop. The gaps have to come out of
       the width or the columns stop adding up to the track and the last one
       loses its edge. Raising n is what makes the photographs smaller - the
       track is the full width of the page, so the only way to shrink a cell is
       to fit more of them across. */
    grid-auto-columns: calc((100% - var(--strip-gap)) / 1.6);
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: x mandatory;
    /* The strip is a picture, not a document; a scrollbar under it reads as
       chrome. The snap points and the peeking slice carry the affordance. */
    scrollbar-width: none;
    &::-webkit-scrollbar { display: none; }

    @media (min-width: 700px)  { grid-auto-columns: calc((100% - var(--strip-gap)) / 1.9); }
    @media (min-width: 1000px) { grid-auto-columns: calc((100% - var(--strip-gap) * 2) / 2.6); }
  }

  .filmstrip-cell {
    margin: 0;
    scroll-snap-align: start;

    /* The last photograph can never align its left edge with the track - the
       scroll runs out first - so with mandatory snapping there would be no
       valid stop at the end and the strip could refuse to rest there. Aligned
       by its right edge instead, the end of the scroll is a snap point. */
    &:last-child { scroll-snap-align: end; }

    & img {
      display: block;
      inline-size: 100%;
      block-size: auto;
      /* Square: the midpoint. The 4:5 the files were exported at made a band
         over 500px tall that dominated the page, but cropping to landscape cut
         so much off that the material stopped being legible. A square cell is
         16% shorter than the original and still shows the stone. */
      aspect-ratio: 1 / 1;
      object-fit: cover;
      background: var(--paper-2);
    }
  }

  /* Only ever revealed by the script, and only when the track actually
     overflows. With three photographs and three columns there is nothing to
     advance to, so on a wide screen this stays hidden rather than sitting
     there as a control that does nothing. Add a fourth photograph and it
     appears on its own. */
  .filmstrip-next {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-end: clamp(1rem, 3vw, 2rem);
    translate: 0 -50%;
    inline-size: 3.5rem;
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: var(--white);
    color: var(--ink);
    cursor: pointer;
    box-shadow: 0 2px 14px rgb(6 44 17 / .28);
    transition: background .25s var(--ease), color .25s var(--ease), scale .25s var(--ease);

    & svg { inline-size: 1.25rem; }

    /* display:grid above outranks the browser's own [hidden] rule, which sits
       below every author layer. Without this the button stays on screen after
       the script hides it. */
    &[hidden] { display: none; }

    &:hover, &:focus-visible {
      background: var(--forest);
      color: var(--white);
      scale: 1.06;
    }
  }

  .slides {
    position: relative;
    margin-block-start: 1.75rem;
  }

  .slides-frame {
    position: relative;
    overflow: hidden;
    background: var(--paper-2);
    /* Enough of a mount to give the picture an edge on a paper-coloured page,
       without the frame competing with the photograph inside it. */
    border: 3px solid var(--forest);
  }

  .slide {
    position: relative;
    margin: 0;


    & img {
      display: block;
      inline-size: 100%;
      block-size: auto;
      /* The three frames are different shapes, one square and two portrait,
         so a fixed ratio plus cover keeps the frame from jumping on change. */
      aspect-ratio: 6 / 7;
      object-fit: cover;
      object-position: center;
    }
  }

  /* Hover. The slideshow already pauses under the pointer; this makes that
     pause feel intended rather than like a stall: the picture leans in and the
     frame darkens. */
  .slides.is-live .slides-frame { transition: border-color .4s; }
  .slides.is-live:hover .slides-frame { border-color: var(--forest-xdk); }


  .slides.is-live {
    & .slides-frame { display: grid; }
    /* Every slide occupies the same cell, so the frame is the height of the
       tallest and nothing shifts as they cross-fade. */
    & .slide {
      grid-area: 1 / 1;
      opacity: 0;
      pointer-events: none;
      transition: opacity .9s cubic-bezier(.4, 0, .2, 1);
    }
    & .slide.is-on { opacity: 1; pointer-events: auto; z-index: 2; }
    /* Held opaque underneath until the incoming slide has fully arrived. */
    & .slide.was-on { opacity: 1; z-index: 1; transition: none; }

    /* A slow drift while a slide is shown. It is what stops a still frame
       feeling static, and it is small enough not to be noticed as motion. */
    & .slide img {
      scale: 1.06;
      transition: scale 6s linear;
    }
    & .slide.is-on img { scale: 1; }
    &:hover .slide.is-on img { scale: 1.05; transition: scale .9s cubic-bezier(.16,1,.3,1); }
  }

  @media (prefers-reduced-motion: reduce) {
    .slides.is-live .slide { transition: none; }
    .slides.is-live .slide img,
    .slides.is-live:hover .slide.is-on img { scale: 1; transition: none; }
  }

  /* Honeypot: off-screen for sighted users, invisible to AT, bots fill it. */
  .hp {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    opacity: 0;
    pointer-events: none;
    left: -9999px;
  }

  .form-status {
    display: none;
    padding: .9rem 1.1rem;
    border: 1px solid var(--forest);
    background: color-mix(in srgb, var(--leaf) 10%, transparent);
    color: var(--forest-dk);
    font-size: var(--t-sm);
    border-radius: 2px;

    &.is-shown { display: block; }

    &.is-error {
      border-color: #b3261e;
      background: color-mix(in srgb, #b3261e 8%, transparent);
      color: #8c1d18;
    }
  }

  /* Submit button while the request is in flight */
  .btn[aria-busy="true"] { opacity: .65; cursor: progress; }
  .btn:disabled { cursor: not-allowed; }

  /* --- Callout band ----------------------------------------------------- */
  .band {
    background: var(--forest);
    color: var(--white);
    padding: clamp(2.25rem, 5vw, 3.5rem);
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    align-items: center;
    justify-content: space-between;
    border-radius: 2px;

    & h2 { color: var(--white); font-size: var(--t-lg); margin-block-end: .5rem; }
    & p { color: color-mix(in srgb, var(--white) 76%, transparent); max-inline-size: 52ch; font-size: var(--t-sm); }
  }

  /* --- Footer ----------------------------------------------------------- */
  .footer {
    background: var(--forest-xdk);
    color: color-mix(in srgb, var(--white) 62%, transparent);
    padding-block: clamp(3rem, 6vw, 4.5rem) 0;
    font-size: var(--t-sm);

    & h4 {
      font-family: var(--ff-mono);
      font-size: var(--t-label);
      font-weight: 500;
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--lime);
      margin-block-end: 1.15rem;
    }
    & a:hover { color: var(--white); }
    /* The footer lists carry no class, so the global reset does not reach
       them — strip the markers and indent here. */
    & ul { list-style: none; padding: 0; margin: 0; }
    & li + li { margin-block-start: .6rem; }
    & address { font-style: normal; }
  }

  .footer-grid {
    display: grid;
    gap: var(--gap);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
    padding-block-end: clamp(2.5rem, 5vw, 3.5rem);
  }

  .footer-brand {
    & img { inline-size: 5rem; block-size: 5rem; margin-block-end: 1.1rem; }
    & b { font-family: var(--ff-display); font-size: 1.05rem; color: var(--white); display: block; letter-spacing: -.025em; }
    & p { margin-block-start: .75rem; max-inline-size: 34ch; }
  }

  .footer-foot {
    border-block-start: 1px solid var(--rule-dk);
    padding-block: 1.4rem;
    display: flex;
    flex-wrap: wrap;
    gap: .5rem 1.5rem;
    justify-content: space-between;
    font-family: var(--ff-mono);
    font-size: var(--t-label);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--white) 46%, transparent);
  }

  /* --- Back to top ------------------------------------------------------ */
  .totop {
    position: fixed;
    inset-block-end: 1.5rem;
    inset-inline-end: 1.5rem;
    z-index: 80;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    display: grid;
    place-items: center;
    background: var(--ink);
    color: var(--white);
    border: 0;
    border-radius: 2px;
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    translate: 0 .5rem;
    transition: opacity .3s, visibility .3s, translate .3s var(--ease), background .2s;

    &.is-shown { opacity: 1; visibility: visible; translate: 0 0; }
    &:hover { background: var(--forest); }
    & svg { inline-size: 1.1rem; block-size: 1.1rem; }
  }

  /* The mark that fills the empty column beside "Who we are". Purely
     decorative - the company name is in the copy next to it - so the image is
     alt="" and stays out of the accessibility tree. Sized to sit level with
     that copy rather than to a fixed width, so the two columns read as one
     block instead of a picture with text parked beside it. */
  .about-mark {
    display: grid;
    justify-items: start;
    gap: clamp(1.5rem, 3vw, 2.25rem);

    & img {
      inline-size: min(100%, 9rem);
      block-size: auto;
    }

    @media (min-width: 900px) {
      /* Centred in its own column: left aligned it left a wide band of empty
         paper down one side of the mark, which read as a gap rather than as
         space around it. */
      & img { inline-size: min(100%, 20rem); justify-self: center; }
    }
  }

  /* The copy beside the mark sits level with its middle instead of at the top
     of the row, so the two columns read as one block rather than a picture
     with text parked alongside. */
  @media (min-width: 900px) {
    .about-mark + .flow { align-self: center; }
  }

  /* --- Founders timeline ----------------------------------------------------
     One line per founding company, side by side, joining into one at Green
     Ferro. The founders' lines are white; the joined line is lime, so the
     colour only arrives where the two meet.

     Every position hangs off two custom properties, the centres of the two
     lines measured from the left edge of .lineage. All the pieces - the two
     long lines, each entry's dot, the SVG where they curve together, the seal
     at the end - are full-width blocks inside it, so one pair of values lines
     them all up. On a phone both lines run down the left with the entries to
     their right; from 1000px they run down the middle, well apart - the two
     were separate companies until the joint venture, and the space between
     their lines is what says so - each company's mark on the head of its own
     line and its entries in their own column.

     The finished state is the default. Only .is-live, which lineage.js adds,
     hides anything, so with no script everything is simply drawn. */
  .lineage {
    --lg: 3rem;                                     /* between the two lines */
    /* On a phone the two lines sit on top of each other, which is to say one
       line. Two rails a finger apart cost a quarter of the screen and still
       left the reader working out which one an entry hung from; the company is
       named on the entry itself, which says it plainly. */
    --xp: 2rem;                                     /* the line */
    --xd: var(--xp);
    --indent: 3.75rem;                              /* where entry text starts */
    --mark: 4.5rem;                                 /* company mark */
    --seal: 4rem;                                   /* Green Ferro mark */
    --line-start: calc(var(--mark) / 2);
    --dot: .875rem;
    --glow: 1.5rem;
    --drawn: color-mix(in srgb, var(--white) 70%, transparent);

    position: relative;
    max-inline-size: 72rem;
    margin: clamp(3rem, 6vw, 4.5rem) auto 0;

    @media (min-width: 1000px) {
      --lg: 16rem;
      --xp: calc(50% - var(--lg) / 2);
      --xd: calc(var(--xp) + var(--lg));            /* two lines again */
      --seal: var(--mark);
      /* Text sits 1.75rem off each line: close enough to belong to it. */
      --gutter: calc(var(--lg) + 3.5rem);
      --mark: 8rem;
      /* The mark sits on the line, so the line leaves from its lower edge. */
      --line-start: var(--mark);
    }
  }

  /* The two long lines, drawn as a trail: nothing ahead of it, a glowing point
     at its head, and the line brightening toward that point before it settles
     back to its resting colour behind. --lp is how far it has travelled,
     --trail the length of the brightening and --head the point's strength;
     all three are written by lineage.js and rest at the finished drawing.

     Each pseudo-element is the width of the glow, with the line itself a 2px
     stripe down its middle, so the point can be wider than the line it sits
     on. Backgrounds rather than layout, so nothing reflows as it travels. */
  .lineage-run {
    --lp: 1;
    --trail: 0px;
    --head: 0;
    position: relative;

    &::before, &::after {
      content: "";
      position: absolute;
      inset-block: var(--line-start) 0;
      inset-inline-start: calc(var(--xp) - var(--glow) / 2);
      inline-size: var(--glow);
      background:
        /* The point. A percentage position is a fraction of the space left
           over, not of the height, so the length term corrects it back to the
           tip of the line exactly. */
        radial-gradient(circle closest-side,
            rgb(255 255 255 / var(--head)) 0 30%,
            rgb(255 255 255 / calc(var(--head) * .3)) 60%,
            transparent)
          50% calc(var(--lp) * 100% + (var(--lp) - .5) * var(--glow))
          / var(--glow) var(--glow) no-repeat,
        /* The line, brightening over its last --trail. */
        linear-gradient(to bottom, var(--drawn) calc(100% - var(--trail)), var(--white))
          50% 0 / 2px calc(var(--lp) * 100%) no-repeat;
    }
    &::after { inset-inline-start: calc(var(--xd) - var(--glow) / 2); }
  }

  /* Company marks. A legend above the entries on a phone; from 1000px each mark
     sits on the head of its own line, name outboard of it. */
  .lineage-heads {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    gap: .75rem 1.75rem;
    padding-inline-start: var(--indent);
    margin-block-end: 2.25rem;

    /* Where each line starts, on a phone, where the marks are not on them. */
    &::before, &::after {
      content: "";
      position: absolute;
      inset-block-start: calc(var(--mark) / 2 - var(--dot) / 2);
      inset-inline-start: calc(var(--xp) - var(--dot) / 2);
      inline-size: var(--dot);
      block-size: var(--dot);
      border-radius: 50%;
      background: var(--white);
    }
    &::after { inset-inline-start: calc(var(--xd) - var(--dot) / 2); }

    @media (min-width: 1000px) {
      display: grid;
      grid-template-columns: 1fr var(--gutter) 1fr;
      /* The phone legend's flex gap would become column gaps here and push
         both marks 28px outboard of their lines. */
      gap: 0;
      padding-inline-start: 0;
      margin-block-end: 2.75rem;
      &::before, &::after { display: none; }
    }
  }

  .lineage-head {
    display: flex;
    align-items: center;
    gap: .9rem;
    margin: 0;
    font-family: var(--ff-display);
    font-weight: 700;
    /* The marks carry the identity; the names only label them. */
    font-size: 1rem;
    line-height: 1.15;
    color: var(--white);

    @media (min-width: 1000px) { font-size: var(--t-md); gap: 1.25rem; }

    /* Pelden's artwork is pale and sits small in its disc; a ring in its own
       blue gives it the weight Druk Wang's navy seal has on its own. */
    &.lineage-head--p img { box-shadow: 0 0 0 4px #1678b0; }

    & img {
      position: relative;
      z-index: 1;
      inline-size: var(--mark);
      block-size: var(--mark);
      padding: .25rem;
      border-radius: 50%;
      background: var(--white);
      object-fit: contain;
    }

    @media (min-width: 1000px) {
      /* Pull each mark out of its column into the gutter until its centre is
         on its line: the gap between column edge and line, plus half a mark. */
      --onto-line: calc(-1 * ((var(--gutter) - var(--lg)) / 2 + var(--mark) / 2));

      &.lineage-head--p { grid-column: 1; flex-direction: row-reverse; & img { margin-right: var(--onto-line); } }
      &.lineage-head--d { grid-column: 3; & img { margin-left: var(--onto-line); } }
    }
  }

  .lineage-events {
    list-style: none;
    margin: 0;
    padding: 0 0 2.5rem;
    display: grid;
    gap: 1.75rem;

    /* Two columns, one per company, each packed from the top. The list stays
       in date order in the source, which is what a phone and a screen reader
       get; dense placement lifts each company's entries up its own column
       instead of making every entry wait for the one before it on the other
       side, which is what left long empty runs down the shorter column. */
    @media (min-width: 1000px) {
      grid-template-columns: 1fr var(--gutter) 1fr;
      grid-auto-flow: row dense;
      gap: clamp(1.75rem, 2.5vw, 2.25rem) 0;
    }
  }

  .ev {
    position: relative;
    padding-inline-start: var(--indent);

    /* The entry's dot, on its own company's line. */
    &::before {
      content: "";
      position: absolute;
      inset-block-start: .3rem;
      inset-inline-start: calc(var(--xp) - var(--dot) / 2);
      inline-size: var(--dot);
      block-size: var(--dot);
      border-radius: 50%;
      background: var(--white);
    }
    &.ev--d::before { inset-inline-start: calc(var(--xd) - var(--dot) / 2); }

    & h3 {
      font-size: var(--t-md);
      color: var(--white);
      margin-block: .4rem .35rem;
    }
    & p:not(.ev-meta) {
      font-size: 1rem;
      line-height: 1.55;
      color: color-mix(in srgb, var(--white) 70%, transparent);
      margin: 0;
    }

    @media (min-width: 1000px) {
      padding-inline-start: 0;

      /* An entry is now only as wide as its column, so its dot is placed from
         the column edge nearest the lines: out across the gap to its line. */
      --to-line: calc(-1 * (var(--gutter) - var(--lg)) / 2 - var(--dot) / 2);
      &.ev--p { grid-column: 1; text-align: end; }
      &.ev--p::before { inset-inline-start: auto; inset-inline-end: var(--to-line); }
      &.ev--d { grid-column: 3; }
      &.ev--d::before { inset-inline-start: var(--to-line); }
    }
  }

  .ev-meta {
    display: flex;
    flex-wrap: wrap;                 /* a full company name and a date on a phone */
    gap: .2rem .75rem;
    margin: 0;
    font-family: var(--ff-mono);
    font-size: var(--t-xs);
    letter-spacing: .12em;
    line-height: 1.6;
    text-transform: uppercase;
    color: var(--lime);

    @media (min-width: 1000px) {
      .ev--p & { justify-content: flex-end; }
    }
  }

  /* Which company an entry belongs to. Needed on a phone, where both lines are
     on the same side; from 1000px the side says it, so the tag is hidden there
     but left for screen readers, which never see the sides. */
  .ev-co {
    /* After the date rather than before it: the year is what a reader scans
       for, and these names are long enough to bury it. */
    order: 2;
    font-size: var(--t-label);
    color: color-mix(in srgb, var(--white) 55%, transparent);

    @media (min-width: 1000px) {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }
  }

  /* Where they join. Each SVG is placed so its middle is midway between the
     two lines above it; its viewBox puts the lines --lg apart at a 16px root. */
  .lineage-merge {
    --lc: 1;
    --ls: 1;
    display: block;
    overflow: visible;
    fill: none;
    stroke-width: 2;

    & .merge-line,
    & .merge-stem { stroke-dasharray: 1; }
    & .merge-line { stroke-dashoffset: calc(1 - var(--lc)); }
    & .merge-stem { stroke: var(--lime); stroke-width: 3; stroke-dashoffset: calc(1 - var(--ls)); }

    & .merge-head {
      fill: var(--white);
      stroke: none;
      opacity: 0;
      filter: drop-shadow(0 0 4px var(--white));
    }
    & .merge-head--stem { fill: var(--lime); filter: drop-shadow(0 0 6px var(--lime)); }
  }
  .lineage-merge--sm {
    inline-size: 3.5rem;
    /* Shorter than the wide one: with nothing to converge, a long run of bare
       line before the mark reads as a hole rather than as a journey. */
    block-size: 7.5rem;
    margin-inline-start: calc((var(--xp) + var(--xd)) / 2 - 1.75rem);
    @media (min-width: 1000px) { display: none; }
  }
  .lineage-merge--lg {
    display: none;
    inline-size: 16.5rem;
    block-size: 13rem;
    margin-inline-start: calc((var(--xp) + var(--xd)) / 2 - 8.25rem);
    @media (min-width: 1000px) { display: block; }
  }

  .lineage-end {
    display: grid;
    /* The mark on the line, its copy in a column to the right of it. The
       column is the mark's own width plus a gap, so the text clears it
       whatever size the mark is set to. */
    grid-template-columns: var(--seal) 1fr;
    column-gap: .75rem;
    align-items: start;

    & > :not(.lineage-seal) { grid-column: 2; }
    & h3 {
      font-size: var(--t-lg);
      color: var(--white);
      margin-block: .35rem .5rem;
    }
    & p:not(.ev-meta) {
      font-size: var(--t-sm);
      color: color-mix(in srgb, var(--white) 66%, transparent);
      margin: 0;
      max-inline-size: 34ch;
    }

    @media (min-width: 1000px) {
      grid-template-columns: 1fr;
      justify-items: center;
      text-align: center;
      & > :not(.lineage-seal) { grid-column: 1; }
      & .ev-meta { justify-content: center; }
    }
  }

  .lineage-seal {
    /* Spans the year, the heading and the copy beside it. */
    grid-row: span 3;
    inline-size: var(--seal);
    block-size: var(--seal);
    max-inline-size: none;
    margin-inline-start: calc((var(--xp) + var(--xd)) / 2 - var(--seal) / 2);
    padding: .3rem;
    border-radius: 50%;
    background: var(--white);
    box-shadow: 0 0 0 3px var(--lime);

    @media (min-width: 1000px) {
      grid-row: auto;
      margin-inline-start: 0;
      margin-block-end: 1.25rem;
    }
  }

  /* --- Drawing, only once lineage.js is running ---------------------------
     Nothing is shown ahead of the trail. Each dot appears as the trail reaches
     it, with a single ripple, and its entry fades up beside it. */
  .lineage.is-live {
    & .ev:not(.is-lit)::before,
    & .lineage-heads:not(.is-lit)::before,
    & .lineage-heads:not(.is-lit)::after { opacity: 0; scale: .2; }
    & .ev::before,
    & .lineage-heads::before,
    & .lineage-heads::after {
      transition: opacity .3s var(--ease), scale .55s cubic-bezier(.34, 1.56, .64, 1);
    }
    & .ev.is-lit:not(.no-anim)::before { animation: lineage-ripple .9s ease-out; }

    & :is(.ev > .ev-body, .lineage-head, .lineage-end > *) {
      opacity: 0;
      translate: 0 .75rem;
      transition: opacity .8s var(--ease), translate .8s var(--ease);
    }
    & .lineage-seal {
      translate: none;
      scale: .6;
      transition: opacity .5s var(--ease), scale .9s cubic-bezier(.34, 1.56, .64, 1);
    }

    & :is(.ev.is-lit > .ev-body, .lineage-heads.is-lit > .lineage-head, .lineage-end.is-lit > *) {
      opacity: 1;
      translate: 0 0;
      scale: 1;
    }
    & .lineage-heads.is-lit > .lineage-head--d { transition-delay: .12s; }
    & .lineage-end.is-lit > :nth-child(2) { transition-delay: .15s; }
    & .lineage-end.is-lit > :nth-child(3) { transition-delay: .25s; }
    & .lineage-end.is-lit > :nth-child(4) { transition-delay: .35s; }

    /* Anything already above the reader on arrival is shown, not replayed. */
    & .no-anim, & .no-anim > *, & .no-anim::before { transition: none; animation: none; }
  }

  @keyframes lineage-ripple {
    from { box-shadow: 0 0 0 0 rgb(255 255 255 / .55); }
    to   { box-shadow: 0 0 0 .9rem rgb(255 255 255 / 0); }
  }
}

/* ---------------------------------------------------------------- utils -- */
@layer utils {
  /* Spacing between flowed items is 1em of the item itself, which is right
     for body copy but leaves a large heading almost touching the paragraph
     under it: the gap was sized by the 18px paragraph, not the 56px heading.
     Headings therefore carry their own trailing space, in their own em so it
     scales with the type. Margins collapse here, so the larger of the two wins
     rather than the two adding up. */
  .flow > * + * { margin-block-start: 1em; }
  .flow > :is(h1, h2, h3, h4) { margin-block-end: .55em; }
  .mono { font-family: var(--ff-mono); }
  .lede { font-size: var(--t-md); color: var(--ink-2); }
  .measure { max-inline-size: 62ch; }
  .sr-only {
    position: absolute; inline-size: 1px; block-size: 1px;
    padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .stack-sm { display: grid; gap: .75rem; }
  .stack { display: grid; gap: 1.5rem; }

  /* Contact: the heading block, the form and the boxes in one grid, so the
     boxes start exactly where the form starts.

     The offset used to be calculated from the type scale, which held at wide
     widths and drifted 33px at 1000px, because the lede wraps to another line
     as the column narrows and a formula cannot see that. Letting the grid put
     the form and the boxes on the same row means the browser measures the
     heading itself, at every width, with no number to keep in sync. */
  @media (min-width: 900px) {
    .split--wide-first:has(.enquiry-head) {
      grid-template-rows: auto 1fr;

      & > .enquiry-head { grid-column: 1; grid-row: 1; }
      & > .enquiry-form { grid-column: 1; grid-row: 2; }
      & > .stack        { grid-column: 2; grid-row: 2; }
    }
  }



  .mt-l { margin-block-start: clamp(2rem, 4vw, 3rem); }

  /* ---------------------------------------------------------------------
     Reveal motion.

     Driven by IntersectionObserver in reveal.js, not by CSS scroll timelines.
     animation-timeline: view() is Chrome-only, so building on it meant Safari
     and Firefox visitors got a completely static site. This runs everywhere.

     The hidden state is scoped to .js-reveal, a class the script only adds
     once it has successfully set up. If the script fails, never loads, or the
     visitor asks for reduced motion, nothing is ever hidden and the page
     renders normally. Copy nobody can read is worse than copy that does not
     animate.

     Long durations and short travel: a heading that takes its time reads
     confident, one that snaps in reads like a template.
     --------------------------------------------------------------------- */
  @media (prefers-reduced-motion: no-preference) {
    /* Travel and duration are custom properties, not direct declarations.
       Variants that want a bigger move only change the property, so they can
       never out-specify the .is-in reset and leave an element stranded off
       its natural position. That bug is invisible until you look for it: the
       element fades in correctly but never slides back. */
    .js-reveal .reveal {
      --reveal-travel: 1.5rem;
      --reveal-duration: 1.1s;
      opacity: 0;
      translate: 0 var(--reveal-travel);
      transition:
        opacity   var(--reveal-duration) cubic-bezier(.16, 1, .3, 1) var(--reveal-delay, 0ms),
        translate var(--reveal-duration) cubic-bezier(.16, 1, .3, 1) var(--reveal-delay, 0ms);
    }

    /* A headline carries the most weight, so it travels furthest and settles
       slowest. */
    .js-reveal :is(.sec-head, .flow) > :is(h1, h2).reveal {
      --reveal-travel: 2rem;
      --reveal-duration: 1.35s;
    }

    /* Cards outweigh a line of text. Two large panels side by side enter the
       viewport together, so they need real distance and a wide offset between
       them for the sequence to read at all. */
    .js-reveal :is(.grid-2, .grid-3, .grid-pair, .grid-people, .stack) > .reveal {
      --reveal-travel: 2.5rem;
      --reveal-duration: 1.25s;
    }

    /* Always last, and only ever competing with the base rule above. */
    .js-reveal .reveal.is-in { opacity: 1; translate: 0 0; }

    /* transition is a single property, so the rule above replaces whatever
       .product-frame set in the layout layer - and layer order means nothing
       declared there can win, however specific the selector. Restated here in
       utils, keeping the reveal's own transitions so the fade-in still runs. */
    .js-reveal .product-frame.reveal {
      transition:
        border-color .4s,
        opacity   var(--reveal-duration) cubic-bezier(.16, 1, .3, 1) var(--reveal-delay, 0ms),
        translate var(--reveal-duration) cubic-bezier(.16, 1, .3, 1) var(--reveal-delay, 0ms);
    }

    /* Content the visitor has already scrolled past appears without a
       transition. Animating it would mean a page reloaded half way down
       replays every section above the reader at once. */
    .js-reveal .reveal.no-anim { transition: none; }
  }

  /* Above the fold there is nothing to scroll, so the hero and page headers
     animate on load. Time-based, so it runs in every browser. */
  @media (prefers-reduced-motion: no-preference) {
    :is(.hero-inner, .page-head .shell) > * {
      animation: enter 1.15s cubic-bezier(.16, 1, .3, 1) both;
    }
    :is(.hero-inner, .page-head .shell) > *:nth-child(1) { animation-delay: .12s; }
    :is(.hero-inner, .page-head .shell) > *:nth-child(2) { animation-delay: .30s; }
    :is(.hero-inner, .page-head .shell) > *:nth-child(3) { animation-delay: .48s; }
    :is(.hero-inner, .page-head .shell) > *:nth-child(4) { animation-delay: .66s; }
    :is(.hero-inner, .page-head .shell) > *:nth-child(n+5) { animation-delay: .82s; }
    :is(.hero-inner, .page-head .shell) > :is(h1, h2) { animation-duration: 1.4s; }

    /* The hero photograph arrives just ahead of the copy that sits on it, so the
       picture is already there to read the headline against rather than the two
       resolving at once. No travel - a background that slides looks like a
       loading glitch. */
    /* Tied to the image being ready, not to the clock. A time-based fade starts
       when the element renders, so on a cold load it ran itself out against an
       empty box while the photograph was still in flight, and the picture then
       arrived at full opacity all at once - the fade was over before there was
       anything to fade.

       Both classes below are set by the inline script in the page head:
       .hero-fade before first paint, .hero-ready once the image has decoded.
       Hiding is deliberately gated on a script-set class, so if scripting is
       off or fails, no rule hides the photograph and it simply shows. */
    :root.hero-fade .hero::before { opacity: 0; transition: opacity 1.4s cubic-bezier(.16, 1, .3, 1); }
    :root.hero-ready .hero::before { opacity: 1; }
  }


  /* The valley photograph is well below the fold, so a load-time fade would be
     long finished before anyone reached it. It waits on the section's own
     revealed content instead: :has() lets the parent react when any child the
     reveal system tracks has arrived, which needs no change to reveal.js.

     Scoped to .js-reveal so that with JavaScript off the picture is simply
     there, rather than stuck at zero opacity forever. */
  @media (prefers-reduced-motion: no-preference) {
    .js-reveal .valley-bg::before {
      opacity: 0;
      transition: opacity 2.4s cubic-bezier(.16, 1, .3, 1);
    }
    .js-reveal .valley-bg:has(.reveal.is-in)::before { opacity: 1; }
  }
  @keyframes enter {
    from { opacity: 0; translate: 0 1.4rem; }
    to   { opacity: 1; translate: 0 0; }
  }

  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
    @view-transition { navigation: none; }
  }
}
