/* ---------------------------------------------------------------- tokens */
:root{
  /* materials */
  --crude:#0E1412;        /* petroleum ground — green-undertoned, not black */
  --crude-2:#131B18;      /* raised dark surface */
  --steel:#1B2320;        /* panel */
  --cement:#E9E6DD;       /* MENA plant limestone — the light half */
  --cement-2:#DFDBD0;
  --ink:#111815;          /* text on cement */
  --ink-2:#4A5551;

  /* stream state — the only two accents, and they mean something */
  --sour:#D9992B;         /* untreated. NEVER decorative. */
  --sour-dim:#8A6118;
  --green:#93C455;        /* treated / active. From the logo mark. */
  --green-d:#4E7420;
  --green-hi:#B2DA7E;

  --line-d:rgba(233,230,221,.13);
  --line-l:#CFCABC;
  --dim-d:#9AA79F;
  --dim-l:#5C6763;

  --display:'IBM Plex Sans Condensed','Segoe UI Semibold',sans-serif;
  --sans:'IBM Plex Sans','Segoe UI',system-ui,sans-serif;
  --mono:'IBM Plex Mono','Cascadia Mono',Consolas,monospace;

  --gutter:76px;
  --shell:1220px;
  --hd:72px;

  /* Tone-fade depth. Matches .sec's own vertical padding exactly, so every
     ground change completes in dead space and no text sits on a ramp. */
  --fade:clamp(4rem,7.5vw,7rem);

  /* Crossing between the dark half and the light half is a ~200-level jump —
     an order of magnitude more than any other boundary here, and the only
     kind that can band. Those get extra room on both sides, half the ramp in
     each. The mid hexes are the joins the no-color-mix fallbacks meet at;
     --dawn-mid is overridden inline where the dark side is not --steel. */
  --ramp-x:clamp(2rem,6vw,6rem);
  --ramp-lg:calc(var(--fade) + var(--ramp-x));
  --dawn-mid:#85877F;     /* oklab midpoint, --steel to --cement */
  --dusk-mid:#7B7E77;     /* oklab midpoint, --cement to --night */

  --night:#0A0F0D;        /* footer ground */

  /* ---- photographic grade ------------------------------------------------
     The client's note, 2026-08-30: "if you're modernizing it then you have to
     ensure all visuals match". Measured, he is right. The imagery arrived from
     three unrelated sources and never agreed: stock photography off the source
     site (a pink sunset pumpjack at +18 warm, a cyan-sky tank farm at -59, a
     teal ship at -92 and 83% saturation), desert 3-D renders (sand + cyan sky)
     and studio 3-D renders (cold blue-grey). A 190-point warm/cool swing
     inside one page set.

     A graded photograph is reduced to ONE hue. Not a wash over the top — the
     original colour is discarded and only its luminosity is kept, so a sunset
     and an overcast tank farm come out of the same material.

     REVISED 2026-08-30, second pass. The first pass graded every photograph on
     the site. The owner walked it back to a named list of sections — see .gr
     below — so the page heroes and the videos are now full colour again and
     the grade carries only the supporting imagery.

     Set from TINT at the top of this file, so the CSS and the SVG matrix in
     gen-site.mjs can never drift apart. */
  --tint:#8F9691;
}

*{box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:calc(var(--hd) + 12px)}
body{
  margin:0;background:var(--crude);color:#E6EDE7;
  font-family:var(--sans);font-size:16px;line-height:1.62;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
img,video{max-width:100%;display:block}

/* ------------------------------------------------------- the grade, applied
   Two mechanisms, and they must land on identical pixels. They are not
   interchangeable:

   - .pcard and .phero.img carry the photograph as their own background-image,
     set inline by the generator. background-blend-mode:luminosity blends that
     image against the element's background-color, keeping the image's
     luminosity and taking hue+saturation from --tint. It touches nothing but
     the background layer, so the copy on top is untouched. A filter cannot be
     used on these: filter rasterises an element WITH its children and there is
     no way to exempt them, so the headline, the tag and the body copy would
     all be dragged through the grade with the photograph.

   - <img> and <video> cannot use background-blend-mode, so they take
     filter:url(#grade) — an feColorMatrix defined inline in every page.

   WHY AN SVG MATRIX AND NOT A CSS FILTER CHAIN. The two paths have to agree,
   because ph-static-mixer.jpg and ph-vapx-tote.jpg each render through BOTH:
   as a .pcard background on industries-we-serve, and as a .panel>img on the
   home page. If they disagree, the same photograph looks different on two
   pages — which is the complaint this whole change exists to answer.

   No CSS shorthand chain can agree. Measured against the blend over three real
   images, worst-case channel error:

       grayscale(1)                                    mean  4.5-10.2, worst 18
       grayscale(1) sepia(1) saturate(.153)            mean 30.0-34.4, worst 60
       grayscale(1) sepia(1) saturate(.153) hue-rotate mean 25.2-28.0, worst 52
       feColorMatrix, 0.3/0.59/0.11 + offset column    mean  0.00,     worst  3

   Two reasons the shorthands cannot close the gap. grayscale() is defined on
   the Rec.709 luma coefficients (0.2126/0.7152/0.0722) while the blend's Lum()
   uses 0.3/0.59/0.11 — on a saturated red that is a 22-unit difference, and
   the images that clash worst are the saturated ones. And SetLum applies a
   CONSTANT per-channel offset, which no filter shorthand can express;
   feColorMatrix carries it in the fifth column.

   Why the inline background-image is not moved into a custom property so both
   paths could share one mechanism: a relative url() inside a custom property
   resolves against the stylesheet that consumes it, not the document — see the
   note on .phero.img below. It would silently render nothing.

   OPT-IN, NOT OPT-OUT. The first pass graded everything and exempted three
   things by name. The owner's second pass on 2026-08-30 reversed the polarity:
   grade a named list of sections and leave the rest alone. That is why the
   selectors below are all scoped under .gr, which gen-site.mjs puts on exactly
   five sections:

     index.html          "Who we are"      the two field photographs
     index.html          "Our products"    the four product cards and the two
                                           wide process cards in the same grid
     index.html          "Case studies"    the terminal photograph, two covers
     industries-we-serve "Case study — …"  the same two covers
     apcotane / vap-x-gas / vap-x-oil      "Related process"

   Everything else is full colour again: every page hero (.phero.img), every
   video, the industries application cards, the mercaptan application cards,
   and the whole Apollo Control Automation page.

   The polarity flip also retires the old opt-out list. .brand img and .iso img
   sit outside every .gr section, and the Apollo Control dashboard is on a page
   that carries no .gr at all, so none of them need naming any more. */
.gr .panel>img,.gr .thumb img{filter:url(#grade)}
.gr .pcard{background-color:var(--tint);background-blend-mode:luminosity}

/* The <svg> carrying the #grade filter. width/height 0 is not enough on its
   own — it is still an inline-level box and adds a line's worth of height at
   the top of every page. */
.gradedef{position:absolute;width:0;height:0;overflow:hidden;pointer-events:none}
a{color:inherit}
p{margin:0 0 1rem;max-width:64ch}
:focus-visible{outline:2px solid var(--green);outline-offset:3px;border-radius:2px}

/* --------------------------------------------------------------- type ---- */
h1,h2,h3{margin:0;font-family:var(--display);font-weight:700;line-height:1.04;letter-spacing:-.012em}
h1{font-size:clamp(2.6rem,6.1vw,5.1rem);text-transform:uppercase;letter-spacing:-.018em}
h2{font-size:clamp(1.9rem,3.5vw,2.9rem);text-transform:uppercase}
h3{font-size:1.12rem;letter-spacing:.004em}

/* eyebrow — a plant label, not a marketing kicker */
.eb{
  font-family:var(--mono);font-size:.74rem;font-weight:600;letter-spacing:.24em;
  text-transform:uppercase;color:var(--green);margin:0 0 .9rem;
  display:flex;align-items:center;gap:.7rem;max-width:none;
}
.eb::after{content:"";flex:1;max-width:110px;height:1px;background:currentColor;opacity:.42}
.light .eb{color:var(--green-d)}

.lede{font-size:1.06rem;color:#C4D0C7;max-width:60ch}
.light .lede{color:var(--ink-2)}

/* every measured value on this site is mono */
sub{font-size:.66em;line-height:0;vertical-align:-.2em;letter-spacing:0}
.num{font-family:var(--mono);font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.01em}

/* -------------------------------------------------------------- layout --- */
.shell{max-width:var(--shell);margin-inline:auto;padding-inline:2.2rem}
body.railed .shell{padding-left:calc(2.2rem + var(--gutter))}
.sec{padding:clamp(4rem,7.5vw,7rem) 0;position:relative}
.sec.light{background:var(--cement);color:var(--ink)}
.sec.light h1,.sec.light h2,.sec.light h3{color:var(--ink)}

/* Section grounds meet as ramps, not as edges. One class, with the adjacency
   passed in at each use: --ground is this section's own colour, --from the
   ground above it, --to the ground below. A section that paints nothing (the
   plain --crude body) is faded to by its neighbours; where two painted
   sections meet, the lower one owns the join and the upper one leaves --to
   unset. Every ramp is --fade deep — exactly .sec's own vertical padding — so
   it completes in dead space and no text sits on moving ground. The hero
   already ended this way (.hero::after); this carries it down the page.
   Selectors are .sec-qualified to outrank .sec.light. */
.sec.tone{
  background:linear-gradient(180deg,
    var(--from,var(--ground)) 0,
    var(--ground) var(--fade),
    var(--ground) calc(100% - var(--fade)),
    var(--to,var(--ground)) 100%);
}

/* The two crossings between the dark half and the light half are the only
   steps big enough to band — around 200 levels, against 5 to 13 for every
   other boundary here. Each gets --ramp-lg from BOTH sides, so the ramp is
   ~390px at 1440 and centred on the join rather than hanging off one side,
   and both halves are the same length so their slopes match where they meet.
   Each half is a smoothstep: motionless at the outer end, fastest at the
   join. A linear ramp of any length still shows two edges, because the eye
   reads the corners where the slope changes, not the slope itself. */
.sec.to-dawn{
  padding-bottom:var(--ramp-lg);
  background:linear-gradient(180deg,
    var(--from,var(--ground)) 0,var(--ground) var(--fade),
    var(--ground) calc(100% - var(--ramp-lg)),var(--dawn-mid) 100%);
}
.sec.light.tone-dawn{
  padding-top:var(--ramp-lg);
  background:linear-gradient(180deg,
    var(--dawn-mid) 0,var(--cement) var(--ramp-lg),var(--cement) 100%);
}
/* Mirror image at the foot of the page: the light half meeting the footer's
   near-black. Only the home page ends this way — every other page ends on the
   green CTA band, whose edges are an accent and stay crisp on purpose. */
.sec.light.to-dusk{
  padding-bottom:var(--ramp-lg);
  background:linear-gradient(180deg,
    var(--cement) 0,var(--cement) calc(100% - var(--ramp-lg)),var(--dusk-mid) 100%);
}
footer.dusk{
  border-top:0;
  padding-top:calc(var(--ramp-lg) + clamp(3rem,5vw,4.5rem));
  background:linear-gradient(180deg,var(--dusk-mid) 0,var(--night) var(--ramp-lg),var(--night) 100%);
}

/* The eased curves. Gated on @supports, NOT stated as a second declaration
   after a plain one: every rule above contains var(), and a declaration
   containing var() is accepted at parse time and only fails later, at
   computed-value time. Failing there falls to unset — transparent — not back
   to the previous declaration. @supports is settled at parse time, so the
   plain rules above genuinely survive where color-mix() is missing, and the
   --*-mid hexes are the joins those fallbacks meet at. */
@supports (background:linear-gradient(#000,color-mix(in oklab,#000,#fff))){
  .sec.to-dawn{
    background:linear-gradient(180deg,
      var(--from,var(--ground)) 0,var(--ground) var(--fade),
      var(--ground) calc(100% - var(--ramp-lg)),
      color-mix(in oklab,var(--ground),var(--cement) 3.125%)  calc(100% - var(--ramp-lg)*.75),
      color-mix(in oklab,var(--ground),var(--cement) 12.5%)   calc(100% - var(--ramp-lg)*.5),
      color-mix(in oklab,var(--ground),var(--cement) 28.125%) calc(100% - var(--ramp-lg)*.25),
      color-mix(in oklab,var(--ground),var(--cement) 50%) 100%);
  }
  .sec.light.tone-dawn{
    background:linear-gradient(180deg,
      color-mix(in oklab,var(--dawn-from),var(--cement) 50%) 0,
      color-mix(in oklab,var(--dawn-from),var(--cement) 71.875%) calc(var(--ramp-lg)*.25),
      color-mix(in oklab,var(--dawn-from),var(--cement) 87.5%)   calc(var(--ramp-lg)*.5),
      color-mix(in oklab,var(--dawn-from),var(--cement) 96.875%) calc(var(--ramp-lg)*.75),
      var(--cement) var(--ramp-lg),var(--cement) 100%);
  }
  .sec.light.to-dusk{
    background:linear-gradient(180deg,
      var(--cement) 0,var(--cement) calc(100% - var(--ramp-lg)),
      color-mix(in oklab,var(--cement),var(--night) 3.125%)  calc(100% - var(--ramp-lg)*.75),
      color-mix(in oklab,var(--cement),var(--night) 12.5%)   calc(100% - var(--ramp-lg)*.5),
      color-mix(in oklab,var(--cement),var(--night) 28.125%) calc(100% - var(--ramp-lg)*.25),
      color-mix(in oklab,var(--cement),var(--night) 50%) 100%);
  }
  footer.dusk{
    background:linear-gradient(180deg,
      color-mix(in oklab,var(--cement),var(--night) 50%) 0,
      color-mix(in oklab,var(--cement),var(--night) 71.875%) calc(var(--ramp-lg)*.25),
      color-mix(in oklab,var(--cement),var(--night) 87.5%)   calc(var(--ramp-lg)*.5),
      color-mix(in oklab,var(--cement),var(--night) 96.875%) calc(var(--ramp-lg)*.75),
      var(--night) var(--ramp-lg),var(--night) 100%);
  }
}
.split{display:grid;grid-template-columns:1.02fr .98fr;gap:clamp(2rem,4.4vw,4rem);align-items:start}
@media(max-width:920px){.split{grid-template-columns:1fr}}

/* ROUND 5 — the picture column in "who we are", squared up against the prose.
   Two equal rows filling the column, each panel splitting into image + caption
   so the photograph takes whatever height is left and crops to it. Above 920px
   only: once the split collapses to one column there is no neighbouring text
   to align to, and forcing equal rows there would squash both photographs. */
.imgcol{display:grid;gap:1.4rem;align-content:start}
@media(min-width:921px){
  .split > .imgcol{align-self:stretch;height:100%;grid-template-rows:1fr 1fr;align-content:stretch}
  .imgcol > .panel{display:grid;grid-template-rows:1fr auto;min-height:0}
  .imgcol > .panel img{width:100%;height:100%;object-fit:cover;min-height:0}
}

/* ------------------------------------------------------- instrument rail - */
/* The treatment train, as plant signage down the left edge. Desktop only;
   below 1080px it is removed entirely rather than being squeezed. */
#rail{
  position:fixed;left:0;top:var(--hd);bottom:0;width:var(--gutter);z-index:60;
  border-right:1px solid var(--line-d);background:rgba(14,20,18,.9);
  display:flex;flex-direction:column;align-items:center;padding:1.6rem 0;
}
#rail .stateChip{
  font-family:var(--mono);font-size:.7rem;font-weight:600;letter-spacing:.2em;
  writing-mode:vertical-rl;text-orientation:mixed;padding:.6rem .3rem;
  border:1px solid var(--sour-dim);color:var(--sour);border-radius:3px;
  transition:color .5s,border-color .5s;
}
#rail.sweet .stateChip{color:var(--green);border-color:var(--green-d)}
#rail .stops{margin-top:1.5rem;display:flex;flex-direction:column;gap:0;flex:1}
#rail .stop{
  display:flex;flex-direction:column;align-items:center;gap:.5rem;flex:1;
  text-decoration:none;color:var(--dim-d);
}
#rail .stop b{
  font-family:var(--mono);font-size:.76rem;font-weight:600;letter-spacing:.02em;
  transition:color .3s;
}
#rail .stop i{
  writing-mode:vertical-rl;font-style:normal;font-family:var(--mono);
  font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;opacity:.62;
}
#rail .stop span{flex:1;width:1px;background:var(--line-d);position:relative}
#rail .stop:last-child span{display:none}
#rail .stop.on b,#rail .stop:hover b{color:var(--green)}
#rail .stop.on i{opacity:1;color:#D6E2D6}
@media(max-width:1079px){#rail{display:none}body.railed .shell{padding-left:2.2rem}}
@media(max-width:640px){.shell,body.railed .shell{padding-inline:1.25rem}}

/* -------------------------------------------------------------- header --- */
header{
  position:sticky;top:0;z-index:90;background:var(--cement);color:var(--ink);
  border-bottom:1px solid var(--line-l);
}
.hd{
  display:flex;align-items:center;gap:1rem;height:var(--hd);
  max-width:1400px;margin-inline:auto;padding-inline:2rem;flex-wrap:nowrap;
}
@media(max-width:640px){.hd{padding-inline:1.25rem}}
.brand{display:flex;align-items:center;gap:1rem;text-decoration:none;flex:none}
.brand img{height:46px;width:auto}
/* Region label: plain text with a rule. No pill, no chip, no wordmark —
   owner decision from round 3, carried forward unchanged. */
.region{
  font-family:var(--mono);font-size:.7rem;font-weight:600;letter-spacing:.22em;
  text-transform:uppercase;color:#6E7A72;white-space:nowrap;
  border-left:1px solid var(--line-l);padding-left:.9rem;
}
nav{margin-left:auto;align-self:stretch;display:flex;align-items:center;gap:clamp(.5rem,1.15vw,1.15rem);flex-wrap:nowrap}
nav>a,.dd>button{
  font-size:.82rem;font-weight:600;color:#38423E;text-decoration:none;white-space:nowrap;
  background:none;border:0;padding:.3rem .1rem;cursor:pointer;font-family:var(--sans);
  transition:color .18s;
}
nav>a:hover,.dd>button:hover,.dd.open>button{color:var(--green-d)}
/* The dropdown trigger fills the full header height, so the menu can open
   flush against the header's bottom edge with no gap for the pointer to fall
   through on its way down. The menu used to sit 12px below the button, and
   that 12px was outside .dd — so the pointer left :hover mid-journey and the
   menu closed before you could reach it. */
.dd{position:relative;align-self:stretch;display:flex;align-items:center}
.dd>button::after{content:"▾";font-size:.62em;margin-left:.3em;vertical-align:.15em}
.dd .menu{
  position:absolute;top:100%;left:50%;transform:translateX(-50%);
  background:var(--cement);border:1px solid var(--line-l);border-top:2px solid var(--green-d);
  min-width:236px;box-shadow:0 20px 46px rgba(17,24,21,.2);display:none;z-index:91;padding:.35rem 0;
}
/* Belt and braces: a few pixels of invisible bridge over the header's bottom
   border, so sub-pixel rounding can't reopen the same hole. */
.dd .menu::before{content:"";position:absolute;left:0;right:0;top:-5px;height:5px}
.dd.open .menu,.dd:hover .menu,.dd:focus-within .menu{display:block}
.dd .menu a{display:block;padding:.62rem 1.15rem;font-size:.82rem;font-weight:600;color:#38423E;text-decoration:none;white-space:nowrap}
.dd .menu a:hover{background:var(--cement-2);color:var(--green-d)}
.navcta{
  background:var(--green-d);color:#fff !important;padding:.62rem 1.05rem;
  font-weight:700;font-size:.78rem !important;letter-spacing:.01em;
}
.navcta:hover{background:#3F5E19}
.burger{
  margin-left:auto;display:none;background:none;border:1px solid var(--line-l);
  color:var(--ink);font-size:1.1rem;padding:.32rem .7rem;cursor:pointer;
}
/* Owner rule: the nav never wraps. Full bar, or burger — nothing between. */
@media(max-width:1180px){
  nav{
    position:fixed;inset:var(--hd) 0 auto 0;background:var(--cement);
    border-bottom:1px solid var(--line-l);flex-direction:column;align-items:stretch;
    gap:0;padding:.5rem 1.25rem 1.2rem;display:none;z-index:89;
    max-height:calc(100vh - var(--hd));overflow:auto;
  }
  nav.open{display:flex}
  nav>a,.dd>button{padding:.85rem .3rem;border-bottom:1px solid var(--cement-2);font-size:.95rem;text-align:left;width:100%}
  /* Undo the desktop trigger's flex row: here the menu stacks under the
     button as an accordion, not beside it. */
  .dd{display:block;align-self:auto}
  .dd .menu{position:static;transform:none;box-shadow:none;border:0;border-left:2px solid var(--green-d);margin:.1rem 0 .4rem 1rem;display:none}
  .dd .menu::before{display:none}
  .dd:hover .menu,.dd:focus-within .menu{display:none}
  .dd.open .menu,.dd.open:hover .menu,.dd.open:focus-within .menu{display:block}
  .navcta{text-align:center;margin-top:.85rem;padding:.9rem}
  .burger{display:block}
}

/* ---------------------------------------------------------------- hero --- */
/* Round 3's hero ran ~1900px before the headline landed. This one is capped
   and bottom-weighted so the readout sits above the fold at 900px. */
.hero{
  position:relative;min-height:min(94vh,860px);display:flex;align-items:flex-end;
  padding:calc(var(--hd) + 2rem) 0 3.2rem;overflow:hidden;
}
.hero>video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
/* Two scrims: one for legibility on the left, one to seat the section. */
.hero::before{
  content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(100deg,rgba(14,20,18,.95) 0%,rgba(14,20,18,.86) 38%,rgba(14,20,18,.42) 72%,rgba(14,20,18,.66) 100%);
}
.hero::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:38%;z-index:1;
  background:linear-gradient(to bottom,rgba(14,20,18,0),var(--crude));
}
.hero .shell{position:relative;z-index:2;width:100%}
.hero h1{max-width:15ch;margin:.2rem 0 1.3rem}
.hero h1 em{font-style:normal;color:var(--green)}
.hero .lede{max-width:52ch}
.btns{display:flex;flex-wrap:wrap;gap:.8rem;margin:1.7rem 0 2.4rem}
.btn{
  display:inline-flex;align-items:center;gap:.55rem;padding:.92rem 1.5rem;
  font-family:var(--sans);font-size:.86rem;font-weight:700;text-decoration:none;
  border:1px solid transparent;cursor:pointer;transition:background .18s,border-color .18s,color .18s;
}
/* ROUND 5 · piece 04 — CTA treatments.
   The primary button carries the site's own colour rule as a gesture: it rests
   sour and ramps to sweet under the cursor, left to right, the same direction
   the treatment train runs. The ghost button gets an arrow that arrives.
   Both are pure CSS with no script, and both degrade to round 4's flat states
   if transforms are unavailable. */
.btn{position:relative;overflow:hidden;isolation:isolate;transition:color .3s}

/* No border on the primary button, and 1px more padding so the box is exactly
   the size it was. The amber ring that survived a completed fill was that
   border: .btn carries a 1px solid transparent border with the background
   painting underneath it, and overflow:hidden clips the liquid to the
   PADDING box — so no negative inset could ever reach the border ring.
   Removing the border removes the ring; there is nothing left to cover. */
.btn.primary{
  background:var(--sour);color:#20180A;
  border:0;padding:calc(.92rem + 1px) calc(1.5rem + 1px);
}
/* z-index MUST be -1, not 0. A positioned pseudo-element at z-index:0 paints
   ABOVE the button's own inline text — the label would disappear under the
   sweep. isolation:isolate on .btn keeps -1 above the button's background
   instead of escaping to the section behind it.

   LIQUID, second attempt (owner, 2026-08-26: "the liquid is no liquid its
   still seems like the one you did before"). He was right. The first attempt
   only eased a border-radius on a rectangle: the front stayed a single smooth
   curve, which the eye still reads as an edge. A liquid surface is not a
   curve, it is a curve that CHANGES SHAPE while it moves. That needs a shape
   whose silhouette varies — see the discs below — and it needs time, so the
   fill was slowed from 0.62s to 1.5s. */
/* The body of the liquid. It fills left to right over 1.5s — deliberately
   slow, so it reads as something being poured in rather than a panel sliding
   across. Its own right edge is a straight line and is never seen: the two
   discs below always overhang it. */
.btn.primary .liq{
  /* --lead only has to clear the CREST, not a whole disc — the discs sit
     behind the front and the crest breaks it by at most ~17px. */
  --lead:40px;
  /* inset:0 is correct now that the primary button has no border: the padding
     box IS the whole button, so the fill covers it edge to edge. */
  position:absolute;inset:0;
  z-index:-1;background:var(--green);
  /* Back to left-to-right (owner, 2026-08-26: "revert it to what it was but
     make it slower"), at the 2.2s the upward version was slowed to. */
  transform:translateX(calc(-100% - var(--lead)));
  transition:transform 2.2s cubic-bezier(.38,.05,.25,1);
}
.btn.primary:hover .liq,.btn.primary:focus-visible .liq{transform:translateX(0)}

/* The surface. A rounded SQUARE rotating about its own centre has a silhouette
   that swells and flattens as it turns — that is a real moving meniscus, and
   it is the difference between this and the last attempt, which only eased a
   border-radius on a rectangle and still read as a straight edge.

   Each disc is centred ON the body's leading edge, so its left half always
   overlaps by ~110px — far more than the button is tall — and the join can
   never show through. What the eye sees is the disc's right silhouette,
   wobbling. Two of them, different sizes and speeds, so the surface never
   repeats on a short loop. */
.btn.primary .liq::before,.btn.primary .liq::after{
  content:"";position:absolute;background:var(--green);
  /* Centred vertically, and pushed a full radius BEHIND the body's right edge
     — that edge is the front. Same rule as the upward version, rotated: the
     disc must live inside the liquid, with only its leading silhouette
     crossing the front. Centring it ON the front puts a whole radius of green
     ahead of the waterline, which is what filled the entire button. */
  top:50%;left:100%;
}
/* The discs sit almost entirely BELOW the surface. Only their crest breaks it.
   Getting this wrong is easy and was got wrong once: centring a disc ON the
   surface line (margin-top:-260px) fills its whole 260px radius ABOVE the
   waterline, which paints the entire button green the moment the fill starts
   moving. margin-top:0 puts the centre a full radius below the surface, so
   what crosses it is the top few pixels of the silhouette.

   The swing comes from a rounded square not being a circle: distance from its
   centre to the outline varies between the inradius (side/2) and the corner,
   so as it turns the crest rises and falls. 520 at 43% swings about 15px;
   420 at 40% about 17px. On a ~46px-tall button that is a wave, not a ripple.

   Width matters too — a rounded square is NARROWEST when axis-aligned, so
   520px is the guaranteed span across every rotation, not the 735px it
   reaches on the diagonal. The widest CTA on this site is 352px. */
.btn.primary .liq::before{
  width:520px;height:520px;margin:-260px 0 0 -520px;
  border-radius:43%;
  animation:swell 11s linear infinite;
}
.btn.primary .liq::after{
  width:420px;height:420px;margin:-210px 0 0 -420px;
  border-radius:40%;opacity:.7;
  animation:swell 7.5s linear infinite reverse;
}
/* Centring is done with negative margins, NOT with a translate, precisely so
   the keyframe can own the transform property outright. An animation replaces
   that property wholesale, so a translate(-50%,-50%) set elsewhere would be
   dropped the moment the rotation starts and both discs would jump out of
   place. (No backticks in this file — the whole stylesheet is one template
   literal, and a stray backtick closes it.) */
@keyframes swell{
  from{ transform:rotate(0deg) }
  to  { transform:rotate(360deg) }
}
.btn.primary:hover,.btn.primary:focus-visible{color:#0E1412}
/* No opt-out needed for the CTA band: its button is a plain .btn, not
   .btn.primary, so it never receives a .liq in the first place. It stays
   round 4's white-on-green inversion, which is already its own accent. */

/* The ghost button animates the arrow the LABEL ALREADY HAS rather than
   adding one. A ::after arrow here produced "See the chemistry ↓ →" — two
   arrows, one of them pointing the wrong way, since this button scrolls the
   page down rather than navigating onward. The nudge follows the direction of
   the glyph that is actually there. */
.btn.ghost{border-color:rgba(233,230,221,.28);color:#E6EDE7}
.btn.ghost:hover{border-color:var(--green);color:var(--green)}
.btn.ghost i{font-style:normal;display:inline-block;transition:transform .34s cubic-bezier(.22,.8,.3,1)}
.btn.ghost:hover i,.btn.ghost:focus-visible i{transform:translateY(3px)}

@media (prefers-reduced-motion: reduce){
  .btn.primary .liq{transition:none}
  .btn.primary .liq::before,.btn.primary .liq::after{animation:none}
  .btn.ghost i{transition:none}
}
.light .btn.ghost{border-color:var(--line-l);color:var(--ink)}
.light .btn.ghost:hover{border-color:var(--green-d);color:var(--green-d)}

/* ============================================================== READOUT ===
   The signature element. One component, two modes:
     .readout            — hero, runs once on load
     .readout.is-live    — lab section, draggable
   The endpoints are the real case-study figures. Everything between them is
   an animation and is labelled as one. */
.readout{
  border:1px solid var(--line-d);background:rgba(19,27,24,.86);max-width:660px;
  transition:border-color .6s;
}
.light .readout{background:#FFF;border-color:var(--line-l)}
.readout.sweet{border-color:var(--green-d)}
.readout .rHead{
  display:flex;justify-content:space-between;align-items:center;gap:1rem;
  padding:.72rem 1.15rem;border-bottom:1px solid var(--line-d);
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.2em;
  text-transform:uppercase;color:var(--dim-d);
}
.light .readout .rHead{border-color:var(--line-l);color:var(--dim-l)}
.readout .rMain{display:flex;align-items:center;gap:1.15rem;padding:1.25rem 1.15rem 1.05rem;flex-wrap:wrap}
.readout .rVal{display:flex;align-items:baseline;gap:.42rem;flex:none;min-width:9.4ch}
.readout .rVal b{
  font-family:var(--mono);font-weight:600;font-variant-numeric:tabular-nums;
  font-size:clamp(2rem,4.4vw,2.9rem);line-height:1;letter-spacing:-.03em;
  color:var(--sour);transition:color .5s;
}
.readout.sweet .rVal b{color:var(--green)}
.readout .rVal span{font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;color:var(--dim-d)}
.light .readout .rVal span{color:var(--dim-l)}
/* the bar is 24 discrete cells — a bargraph gauge, not a progress bar */
.readout .rBar{flex:1;min-width:170px;display:flex;gap:2px;height:30px}
.readout .rBar i{flex:1;background:var(--sour);opacity:.9;transition:opacity .18s,background .5s}
.readout .rBar i.off{background:#2A3430;opacity:1}
.light .readout .rBar i.off{background:var(--cement-2)}
.readout.sweet .rBar i{background:var(--green)}
.readout .rState{
  font-family:var(--mono);font-size:.76rem;font-weight:600;letter-spacing:.2em;
  padding:.42rem .8rem;border:1px solid var(--sour-dim);color:var(--sour);
  flex:none;transition:color .5s,border-color .5s;
}
.readout.sweet .rState{color:var(--green);border-color:var(--green-d)}

/* ROUND 5 · pieces 01 and 03 — canvas grounds.
   A ground sits behind everything in its section and is never read against.
   .shell is already position:relative elsewhere; these two sections get an
   explicit stacking context so the canvas cannot rise above the copy. */
.sec.hasflow,.seisband{position:relative;isolation:isolate}
.sec.hasflow > .ground,.seisband > .ground{
  position:absolute;inset:0;width:100%;height:100%;display:block;z-index:0;
  pointer-events:none;
}
.sec.hasflow > .shell{position:relative;z-index:1}
/* The seismic band spans two sections, so the sections themselves must sit
   above the canvas — not their shells, which would leave each section's own
   background painting over the traces. Neither section paints a ground. */
.seisband > .sec{position:relative;z-index:1;background:transparent}

/* Site-wide seismic grounds. .sec and .phero are already position:relative, so
   the canvas anchors to the section it was injected into. It sits at z-index 0,
   ABOVE the section's own painted gradient and BELOW the shell, which is what
   lets the traces read over a .sec.tone ramp rather than under it. */
.seisG{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  z-index:0;pointer-events:none;
}
/* ROUND 5 · reading cards — preset E, the owner's answer to the seismic
   running behind body copy, chosen 2026-08-26.

   The alternatives all attacked the ground: fade it, mask it, drop the amber
   horizon. This leaves the ground at full strength and gives the copy a
   surface, which measures better anyway — a trace that sits 52 levels above
   the page sits 20 where it passes behind a card at 62%.

   It is not a new device: this design system already has .panel, a dark
   surface with a hairline rule. The card is a TINT OF THE PAGE GROUND rather
   than black; black punches a hole in a petroleum-dark page.

   Scoped :not(.light) deliberately. A dark card on the limestone half would be
   the worst-looking thing on the site, and .split is used on both halves. The
   photograph column in "who we are" is .imgcol, not .rv, so it is untouched. */
/* An OPAQUE DARK GREY card, replacing the 62% tint. Tried on 2026-08-26 and
   KEPT — the owner confirmed the seismic showing above, below and around the
   card is the intended reading, not a loss.
   Text colour is deliberately untouched — this is a surface change only, so
   nothing inside the card needs to invert and no second palette appears.

   Opaque rather than translucent is the point: at 62% the traces still came
   through at 20 levels. At full opacity nothing comes through at all, and the
   card reads as a panel sitting on the ground rather than a smudge of it.

   #232927 is a step lighter and a shade greyer than --steel (#1B2320), so it
   separates from the petroleum ground without becoming a light box. Not
   tokenised: this is a trial, and a one-off hex is easier to nudge or drop
   than a new token that other rules start leaning on.

   Not a trial any more: do not revert it as one. */
/* SELECTOR: .rcard, a class the GENERATOR adds — not a CSS match on .rv.

   Two CSS-only selectors were tried and both were wrong. .rv swept up every
   component that wants the reveal animation, so the panel landed on the stats
   strip, the photographic link cards and the automation console. [class="rv"]
   fixed that and broke worse: the reveal script adds "in" on scroll, so the
   attribute stops matching and the card vanished the moment it appeared.
   addTextCards() now tags the columns at build time instead.

   PROPERTY: background-color, NOT the background shorthand. The shorthand
   resets background-size and background-position to initial, and while this
   rule was matching .pcard it reset those cards from cover/center to
   auto/0% 0% — the photograph stopped filling its card and sat at natural size
   in the top-left corner. (No backticks in this file: it is one template
   literal, and a stray backtick closes it.) */
.sec:not(.light) > .shell.split > .rcard,
.sec:not(.light) > .shell > .rcard{
  background-color:#232927;
  border:1px solid var(--line-d);
  padding:clamp(1.1rem,2.3vw,1.8rem);
}
/* Both cards in a split run to the same depth. .split sets align-items:start,
   which is right for bare text — two columns of copy should begin together and
   end wherever they end. It is wrong the moment those columns become drawn
   boxes: unequal boxes read as a mistake and the shorter one looks cut off.
   align-self only on the carded columns, so every other .split keeps start.

   Below 920px .split collapses to one column, so each card is its own row and
   stretch has nothing to equalise — which is correct, not a gap in the fix.

   .specstrip is excluded from the card entirely: it carries .rv but it is the
   full-width stats band, spans grid-column 1/-1, and already paints its own
   cells. Carding it drew a box around a component that is all boxes. */
.sec:not(.light) > .shell.split > .rcard{align-self:stretch}
/* The single-column card hugs its text instead of running the full shell. A
   lede is capped at 60ch, so a card stretched to 1220px would be a wide grey
   band with copy in its left third — a worse shape than no card at all. */
.sec:not(.light) > .shell > .rcard{
  max-width:min(100%,78ch);
  margin-bottom:clamp(1.6rem,3vw,2.4rem);
}
.sec:not(.light) > .shell > .rcard > :last-child{margin-bottom:0}

/* A .to-dawn section is dark at the top and LIGHT at the bottom — it carries
   the crossing into the limestone half. Fully-light sections are excluded from
   the seismic outright, and this is the same problem arriving gradually: green
   traces at 4-14% alpha over the pale end of the ramp read as dirt, not as
   material. The canvas is masked out over exactly the ramp the section uses,
   so the traces stop where the ground stops being dark. */
.sec.to-dawn > .seisG{
  -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - var(--ramp-lg)),transparent 100%);
  mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - var(--ramp-lg)),transparent 100%);
}
.sec > .shell,.phero > .shell{position:relative;z-index:1}
/* The lab panel must stay opaque: nothing is read over moving pixels. */
.sec.hasflow .readout{background:var(--crude);backdrop-filter:none}

.readout .rFoot{
  display:flex;flex-wrap:wrap;gap:0;border-top:1px solid var(--line-d);
}
.light .readout .rFoot{border-color:var(--line-l)}
.readout .rFoot div{
  flex:1;min-width:140px;padding:.78rem 1.15rem;border-right:1px solid var(--line-d);
}
.light .readout .rFoot div{border-color:var(--line-l)}
.readout .rFoot div:last-child{border-right:0}
.readout .rFoot b{
  display:block;font-family:var(--mono);font-weight:600;font-size:.98rem;color:#E6EDE7;letter-spacing:-.01em;
}
.light .readout .rFoot b{color:var(--ink)}
.readout .rFoot span{
  font-family:var(--mono);font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;color:var(--dim-d);
}
.light .readout .rFoot span{color:var(--dim-l)}
.rNote{
  font-family:var(--mono);font-size:.72rem;line-height:1.75;letter-spacing:.05em;
  color:var(--dim-d);max-width:660px;margin:.8rem 0 0;text-transform:uppercase;
}
.light .rNote{color:var(--dim-l)}
/* live/draggable mode */
.readout.is-live{max-width:none}
.readout.is-live .rBar{height:64px;cursor:ew-resize;touch-action:none}
.readout.is-live .rBar i:hover{opacity:.72}

/* --------------------------------------------------------------- panels -- */
.panel{border:1px solid var(--line-d);background:var(--crude-2)}
.light .panel{border-color:var(--line-l);background:#FFF}
.panel>img,.panel>video{width:100%;height:auto}
/* Cap panel photography so a tall source image can't stretch one column of a
   split far past the other. */
.panel>img{max-height:360px;object-fit:cover}
.pcap{
  display:flex;justify-content:space-between;gap:1rem;padding:.62rem .9rem;
  border-top:1px solid var(--line-d);font-family:var(--mono);font-size:.7rem;
  font-weight:600;letter-spacing:.17em;text-transform:uppercase;color:var(--dim-d);
}
.light .pcap{border-color:var(--line-l);color:var(--dim-l)}

/* Datasheet band — the source site's own register, run full width beneath the
   split so neither column is left with a tail of empty space. */
.specstrip{
  display:grid;grid-template-columns:repeat(3,1fr);gap:1px;
  background:var(--line-d);border:1px solid var(--line-d);
  margin-top:clamp(2.2rem,4vw,3.4rem);
}
@media(max-width:860px){.specstrip{grid-template-columns:repeat(2,1fr)}}
@media(max-width:520px){.specstrip{grid-template-columns:1fr}}
.specstrip>div{background:var(--crude);padding:1.2rem 1.35rem}
.specstrip b{
  display:block;font-family:var(--mono);font-weight:600;font-size:1.2rem;
  color:var(--green-hi);letter-spacing:-.01em;font-variant-numeric:tabular-nums;
}
.specstrip b.warn{color:var(--sour)}
.specstrip span{
  display:block;margin-top:.45rem;font-family:var(--mono);font-size:.7rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--dim-d);
}

/* --------------------------------------------------------- product cards - */
.pgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line-d);margin-top:2.4rem}
@media(max-width:1000px){.pgrid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.pgrid{grid-template-columns:1fr}}
.pcard{
  position:relative;display:flex;flex-direction:column;justify-content:flex-end;
  min-height:340px;padding:1.4rem;text-decoration:none;color:#E6EDE7;
  background-size:cover;background-position:center;isolation:isolate;overflow:hidden;
}
.pcard::before{
  content:"";position:absolute;inset:0;z-index:-1;
  /* Two scrims: the main bottom-weighted one for the copy, plus a short top
     one so the category label stays legible over a bright sky. */
  background:linear-gradient(to bottom,rgba(14,20,18,.62) 0%,rgba(14,20,18,0) 24%),
             linear-gradient(to top,rgba(14,20,18,.95) 14%,rgba(14,20,18,.5) 58%,rgba(14,20,18,.18) 100%);
  transition:background .3s;
}
.pcard:hover::before{
  background:linear-gradient(to bottom,rgba(14,20,18,.62) 0%,rgba(14,20,18,0) 24%),
             linear-gradient(to top,rgba(14,20,18,.97) 12%,rgba(14,20,18,.72) 62%,rgba(14,20,18,.42) 100%);
}
.pcard .tag{
  font-family:var(--mono);font-size:.7rem;font-weight:600;letter-spacing:.19em;
  text-transform:uppercase;color:var(--green);margin-bottom:auto;
}
.pcard h3{margin:.9rem 0 .5rem;text-transform:uppercase}
/* Reserve three lines so every card's heading sits on the same baseline —
   the descriptions carry over from the source at 2–3 lines each. */
.pcard p{font-size:.85rem;color:#BECABF;margin:0;min-height:calc(3 * 1.5em)}
.pcard.wide p{min-height:calc(2 * 1.5em)}
.pcard .go{
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.17em;
  text-transform:uppercase;color:var(--green);margin-top:1rem;
}
.pcard .go::after{content:" →";transition:margin .18s}
.pcard:hover .go::after{margin-left:.3em}
.pcard.wide{min-height:290px}
.specrow{display:flex;flex-wrap:wrap;gap:.4rem 1.5rem;margin-top:.95rem}
.specrow span{font-family:var(--mono);font-size:.7rem;letter-spacing:.15em;text-transform:uppercase;color:var(--dim-d)}
.specrow span b{color:#E6EDE7;font-weight:600}

/* the fifth product — a slim row, as on the source */
.mrow{
  display:flex;align-items:center;gap:1.2rem;flex-wrap:wrap;
  padding:1.15rem 1.4rem;border:1px solid var(--line-d);border-top:0;background:var(--crude-2);
  text-decoration:none;color:inherit;
}
.mrow:hover{background:var(--steel)}
.mrow .tag{font-family:var(--mono);font-size:.7rem;font-weight:600;letter-spacing:.19em;text-transform:uppercase;color:var(--green)}
.mrow b{font-family:var(--display);font-size:1.05rem;text-transform:uppercase;letter-spacing:.005em}
.mrow em{font-style:normal;font-size:.85rem;color:var(--dim-d)}
.mrow .go{
  margin-left:auto;font-family:var(--mono);font-size:.72rem;font-weight:600;
  letter-spacing:.17em;text-transform:uppercase;color:var(--green);
}
.mrow .go::after{content:" →"}

/* ----------------------------------------------------------------- misc -- */
.ticks{list-style:none;padding:0;margin:1.3rem 0 0;display:grid;gap:.55rem}
.ticks li{position:relative;padding-left:1.7rem;font-size:.93rem;color:#C4D0C7}
.ticks li::before{
  content:"";position:absolute;left:0;top:.58em;width:9px;height:9px;
  background:var(--green);
}
.light .ticks li{color:var(--ink-2)}
.light .ticks li::before{background:var(--green-d)}

.golink{
  display:inline-block;font-family:var(--mono);font-size:.74rem;font-weight:600;
  letter-spacing:.17em;text-transform:uppercase;color:var(--green);text-decoration:none;
  border-bottom:1px solid currentColor;padding-bottom:.18rem;margin-top:.9rem;
}
.light .golink{color:var(--green-d)}
.golink::after{content:" →"}

.console{position:relative;border:1px solid var(--line-d)}
.console video{width:100%;height:auto}
.console .bar{
  display:flex;flex-wrap:wrap;gap:.2rem 1.5rem;padding:.7rem 1rem;
  border-top:1px solid var(--line-d);background:var(--crude-2);
  font-family:var(--mono);font-size:.7rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--dim-d);
}
.console .bar b{color:var(--green)}

/* case feature */
.case{display:grid;grid-template-columns:1.35fr .65fr;gap:0;margin-top:2.4rem;border:1px solid var(--line-l)}
@media(max-width:860px){.case{grid-template-columns:1fr}}
.case .body{padding:clamp(1.5rem,3vw,2.4rem);background:#FFF}
/* The figures column fills the feature's full height rather than floating a
   centred cluster in a tall black field. */
.case .figs{background:var(--ink);color:#E6EDE7;display:grid;grid-auto-rows:1fr;gap:0}
.case .figs>div{
  padding:1.5rem clamp(1.2rem,2.2vw,1.8rem);border-bottom:1px solid rgba(233,230,221,.14);
  display:flex;flex-direction:column;justify-content:center;
}
.case .figs>div:last-child{border-bottom:0}
.case .figs b{
  display:block;font-family:var(--mono);font-weight:600;font-variant-numeric:tabular-nums;
  font-size:clamp(1.5rem,2.7vw,2rem);line-height:1;color:var(--green);letter-spacing:-.02em;
}
.case .figs span{
  display:block;margin-top:.5rem;font-family:var(--mono);font-size:.7rem;
  letter-spacing:.15em;text-transform:uppercase;color:#93A099;line-height:1.7;
}
.quote{
  font-family:var(--display);font-size:clamp(1.15rem,2vw,1.5rem);line-height:1.28;
  color:var(--ink);border-left:3px solid var(--green-d);padding:.15rem 0 .15rem 1.1rem;margin:1.5rem 0 0;
}

.lgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;margin-top:2.2rem}
@media(max-width:900px){.lgrid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.lgrid{grid-template-columns:1fr}}
/* Column-count modifiers. These were inline style="grid-template-columns:…"
   on four grids, and an inline style outranks every media query, so those
   four grids never collapsed on a phone (2026-09-08 pre-launch pass: the
   industries cards sat three abreast at 390px, one word per line). Classes
   instead, with the same collapse points as the base grids. */
.pgrid.cols2{grid-template-columns:repeat(2,1fr)}
.pgrid.cols3{grid-template-columns:repeat(3,1fr)}
.lgrid.cols2{grid-template-columns:repeat(2,1fr)}
@media(max-width:1000px){.pgrid.cols3{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.pgrid.cols2,.pgrid.cols3,.lgrid.cols2{grid-template-columns:1fr}}
.lcard{background:#FFF;border:1px solid var(--line-l);padding:1.5rem;display:flex;flex-direction:column}
.lcard h3{margin-bottom:.55rem}
.lcard p{font-size:.9rem;color:var(--ink-2);margin:0}
.lcard .golink{margin-top:auto;padding-top:.9rem}
.lcard .thumb{margin:-1.5rem -1.5rem 1.2rem;border-bottom:1px solid var(--line-l);background:var(--cement-2)}
/* A thumb with NOTHING after it fills the card. On Industries We Serve the two
   case-study covers are shown as bare thumbnails — the download link is a
   single button below the grid, not one per card — so .lcard was reserving
   1.2rem of thumb margin plus its own 1.5rem bottom padding for a heading and
   a paragraph that never arrive. That reserved space rendered as a ~44px white
   band under each photograph. :only-child states the actual condition rather
   than tagging those two cards, so any future image-only card is right by
   default. The bottom rule goes too: it would sit directly on the card's own
   border. */
.lcard > .thumb:only-child{margin-bottom:-1.5rem;border-bottom:0}

.iso{
  display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;margin-top:2.2rem;
  padding:1.5rem;background:#FFF;border:1px solid var(--line-l);border-left:3px solid var(--green-d);
}
.iso p{margin:0;font-size:.92rem;color:var(--ink-2);flex:1;min-width:260px}
.chips{display:flex;gap:.5rem;flex-wrap:wrap}
.chips span{
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.15em;
  text-transform:uppercase;color:var(--ink-2);border:1px solid var(--line-l);padding:.4rem .7rem;
}

/* form */
.form{display:grid;gap:.85rem}
.cats{display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:.3rem}
.cats button{
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.14em;
  padding:.55rem .85rem;border:1px solid var(--line-l);background:#FFF;color:var(--ink-2);cursor:pointer;
}
.cats button.on{background:var(--green-d);border-color:var(--green-d);color:#FFF}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:.85rem}
@media(max-width:560px){.row2{grid-template-columns:1fr}}
.form input,.form textarea{
  font-family:var(--sans);font-size:.92rem;padding:.85rem .95rem;
  border:1px solid var(--line-l);background:#FFF;color:var(--ink);width:100%;
}
.form textarea{min-height:132px;resize:vertical}
.form input::placeholder,.form textarea::placeholder{color:#8A938F}
.form .sub{
  justify-self:start;background:var(--green-d);color:#FFF;border:0;
  padding:.95rem 1.8rem;font-family:var(--sans);font-size:.86rem;font-weight:700;cursor:pointer;
}
.form .sub:hover{background:#3F5E19}
.formnote{font-family:var(--mono);font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;color:var(--dim-l);margin:0}

/* footer */
footer{background:var(--night);border-top:1px solid var(--line-d);padding:clamp(3rem,5vw,4.5rem) 0 0;color:#AEBBB2;font-size:.87rem}
.fgrid{display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr 1.3fr;gap:2rem}
@media(max-width:1000px){.fgrid{grid-template-columns:1fr 1fr}}
@media(max-width:560px){.fgrid{grid-template-columns:1fr}}
footer h4{
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.19em;
  text-transform:uppercase;color:var(--green);margin:0 0 1rem;
}
footer ul{list-style:none;padding:0;margin:0;display:grid;gap:.5rem}
footer a{text-decoration:none}
footer a:hover{color:var(--green-hi)}
.fname{font-family:var(--display);font-size:1.3rem;text-transform:uppercase;color:#E6EDE7;margin:0}
/* Pending-contact slots. These are the only three unresolved claims left on
   the project; they must never render as an assertion. */
.slot{border:1px dashed var(--sour-dim);padding:.62rem .8rem;margin-bottom:.55rem}
.slot b{
  display:block;font-family:var(--mono);font-size:.7rem;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--sour);
}
.slot span{font-size:.82rem;color:#8E9A92}

/* .person is .slot ANSWERED, and the difference between them is the point of
   the pair: .slot is a dashed --sour-dim box because --sour means untreated and
   an unanswered contact is exactly that; .person is a solid --line-d rule and a
   --green label, because --green means treated. A reader can tell which
   contacts are real from across the room without reading a word. Do not
   converge the two styles. (2026-09-08: the address arrived, so no .slot
   renders any more; the rule stays for the next pending claim. The address
   lines are .person spans, styled like the links.) */
.person{border-left:2px solid var(--green-d);padding:.1rem 0 .1rem .8rem;margin-bottom:.85rem}
.person b{
  display:block;font-family:var(--mono);font-size:.7rem;font-weight:600;
  letter-spacing:.16em;text-transform:uppercase;color:var(--green);
}
.person a,.person span{display:block;margin-top:.28rem;font-size:.84rem;color:#C8D2CA;text-decoration:none;word-break:break-word}
.person a:hover,.person a:focus-visible{color:var(--green);text-decoration:underline}
.light .person a,.light .person span{color:var(--ink-2)}
.light .person b{color:var(--green-d)}

.fbase{
  display:flex;justify-content:space-between;gap:1rem;flex-wrap:wrap;
  margin-top:3rem;padding:1.15rem 0;border-top:1px solid var(--line-d);
  font-family:var(--mono);font-size:.72rem;letter-spacing:.15em;text-transform:uppercase;color:#71807A;
}

/* ======================================================= interior pages ===
   Everything below is used by the twelve pages other than home. The home page
   sets the language; these reuse its tokens rather than inventing new ones. */

/* Interior hero. Shorter than home's — these pages are read, not landed on. */
.phero{position:relative;overflow:hidden;background:var(--crude-2)}
/* The photograph is set inline as background-image on this element directly.
   It must NOT go through a custom property: a relative url() inside a
   custom property resolves against the stylesheet that consumes the var(),
   not against the document, so assets/site.css would look for the media
   folder beside itself and silently render nothing. */
.phero.img{background-size:cover;background-position:center}
/* Two stops only: opaque enough to read the headline on the left, open enough
   on the right that the photograph is actually visible. */
.phero::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(14,20,18,.95) 0%,rgba(14,20,18,.89) 36%,rgba(14,20,18,.56) 74%,rgba(14,20,18,.7) 100%);
}
/* Pages with no hero photograph get a flat ground rather than a gradient
   fading to nothing. */
.phero:not(.img)::after{background:none}
/* The photograph is cut off flat at the section's bottom edge against --crude,
   which is the same hard line the tone fades exist to remove — and the home
   hero already dissolves into the ground this way (.hero::after). Layered on
   top of the legibility scrim, which stays exactly as it was. */
.phero.img::after{
  background:
    linear-gradient(180deg,rgba(14,20,18,0) 62%,var(--crude) 100%),
    linear-gradient(100deg,rgba(14,20,18,.95) 0%,rgba(14,20,18,.89) 36%,rgba(14,20,18,.56) 74%,rgba(14,20,18,.7) 100%);
}
.phero .shell{position:relative;z-index:2;padding-block:clamp(3rem,6vw,5rem) clamp(2.6rem,5vw,4rem)}
.phero h1{font-size:clamp(2rem,4.4vw,3.5rem);max-width:19ch}
.phero h1 em{font-style:normal;color:var(--green)}
.phero .lede{margin-top:1.2rem;max-width:58ch}

.crumb{
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.17em;
  text-transform:uppercase;color:var(--dim-d);margin:0 0 1.1rem;max-width:none;
}
.crumb a{text-decoration:none;color:var(--dim-d)}
.crumb a:hover{color:var(--green)}
.crumb b{color:var(--green);font-weight:600}

/* Attribute chips. Named .attrs, NOT .chips — .chips already means the
   certification row on the light industries band, and one class doing two
   jobs on two backgrounds is how a stylesheet starts fighting itself. */
.attrs{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:1.7rem}
.attrs span{
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;border:1px solid var(--green-d);color:var(--green-hi);
  padding:.5rem .9rem;
}

/* Numbered application steps — a real sequence, so the numbering earns itself. */
.steps{list-style:none;counter-reset:s;padding:0;margin:1.3rem 0 0;display:grid;gap:0}
.steps li{
  counter-increment:s;display:grid;grid-template-columns:2.8rem 1fr;gap:1rem;
  padding:.85rem 0;border-bottom:1px solid var(--line-d);font-size:.93rem;color:#C4D0C7;
}
.steps li:first-child{border-top:1px solid var(--line-d)}
.steps li::before{
  content:counter(s,decimal-leading-zero);font-family:var(--mono);font-weight:600;
  font-size:.82rem;color:var(--green);letter-spacing:.02em;padding-top:.15em;
}
.light .steps li{color:var(--ink-2);border-color:var(--line-l)}
.light .steps li:first-child{border-color:var(--line-l)}
.light .steps li::before{color:var(--green-d)}

/* Spec table — datasheet, not decoration. */
.specs{width:100%;border-collapse:collapse;font-size:.9rem;margin-top:1rem;border:1px solid var(--line-d)}
.specs th,.specs td{text-align:left;padding:.72rem .95rem;border-bottom:1px solid var(--line-d);vertical-align:top}
.specs tr:last-child th,.specs tr:last-child td{border-bottom:0}
.specs th{
  font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--dim-d);width:42%;
}
.specs td{font-family:var(--mono);font-size:.86rem;color:#E6EDE7}

/* Big figures. Same treatment as the home readout's foot so the site reads
   as one system. */
.bignums{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:1px;
  background:var(--line-d);border:1px solid var(--line-d);margin-top:2rem;
}
.bignums>div{background:var(--crude);padding:1.5rem 1.4rem}
.bignums b{
  display:block;font-family:var(--mono);font-weight:600;font-variant-numeric:tabular-nums;
  font-size:clamp(1.6rem,3vw,2.2rem);line-height:1;color:var(--green);letter-spacing:-.02em;
}
.bignums span{
  display:block;margin-top:.7rem;font-size:.85rem;line-height:1.6;color:var(--dim-d);
}

/* Dark feature cards. A separate class from .lcard rather than a variant of
   it: .lcard is the white card used in the light sections, and one class
   serving two backgrounds is how a stylesheet starts fighting itself. */
.dgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:1px;
  background:var(--line-d);border:1px solid var(--line-d);margin-top:2rem}
.dcard{background:var(--crude);padding:1.5rem 1.4rem}
.dcard h3{margin-bottom:.6rem;text-transform:none}
.dcard p{margin:0;font-size:.9rem;color:var(--dim-d)}

/* The Apollo team film, directly under the hero. Held to a readable width and
   centred rather than run full-bleed — it is a talking head, and stretching it
   to 1220px would make the subject smaller, not bigger. */
.film{max-width:880px;margin-inline:auto}
.film .eb{justify-content:center}
.film .eb::after{display:none}
.film video{width:100%;height:auto;background:#000}

/* Engineering drawings, drawn as SVG rather than dropped in as a raster with
   a baked white background. Scales to any width and takes the page's colours. */
/* Below ~700px the tag text would shrink below legibility, so the drawing
   holds a minimum width and scrolls inside its own box. The page itself still
   never scrolls sideways — that is the rule this respects. */
.dwgwrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.dwg{display:block;width:100%;min-width:660px;height:auto;background:var(--crude)}
.dwg .dl{font-family:var(--mono);font-size:6.5px;font-weight:600;letter-spacing:.09em;fill:#9AA79F}
.dwg .ds{font-family:var(--mono);font-size:8.5px;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;fill:#9AA79F}
.dwg .dt{font-family:var(--display);font-size:13px;font-weight:700;letter-spacing:.01em;fill:#E6EDE7}
.light .dwg{background:#FFF}
.light .dwg .dl,.light .dwg .ds{fill:#5C6763}
.light .dwg .dt{fill:var(--ink)}

/* Legend for the drawing's two coloured paths. */
.dwgkey{
  display:flex;flex-wrap:wrap;gap:.4rem 1.6rem;padding:.7rem .9rem;
  border-top:1px solid var(--line-d);font-family:var(--mono);font-size:.7rem;
  font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--dim-d);
}
.dwgkey span{display:flex;align-items:center;gap:.5rem}
.dwgkey i{width:1.4rem;height:0;border-top:3px solid currentColor;display:block}
.dwgkey .sour{color:var(--sour)}
.dwgkey .sweet{color:var(--green)}

/* Video panels. Vimeo embeds keep a reserved 16:9 box so nothing shifts. */
.vidgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1.4rem;margin-top:2rem}
.viframe{position:relative;padding-top:56.25%;background:#000}
.viframe iframe{position:absolute;inset:0;width:100%;height:100%;border:0}

/* Click-to-load facade for the white Vimeo embeds. An iframe takes no poster,
   so the card is a real <img> in a <button> and the player is built on click.
   The cue is the character the captions already use, set in mono at the centre
   of the frame — a glyph, not a device: no panel, no rounding, no shadow. Its
   only state is colour. See the long note at vimeoCard() in gen-site.mjs. */
.vlazy{display:block;width:100%;border:0;padding:0;padding-top:56.25%;cursor:pointer}
.vlazy .vcard{position:absolute;inset:0;width:100%;height:100%;display:block}
/* Both labels are SVG <text> in the card's own 1920x1080 space, so they scale
   with the artwork instead of holding a CSS size while the picture shrinks.
   34px against the card's baked MENA REGION at ~26px makes the film's name the
   primary label and leaves the region line subordinate; the tracking is the
   card's own, measured off it. */
.vct{font-family:var(--mono);font-weight:600;font-size:34px;letter-spacing:6.5px;fill:var(--cement)}
.vcp{font-family:var(--mono);font-size:64px;fill:var(--cement);transition:fill .18s}
.vlazy:hover .vcp,.vlazy:focus-visible .vcp{fill:var(--green)}


/* FAQ accordion */
.acc{border:1px solid var(--line-d);margin-top:1.2rem}
.acc details{border-bottom:1px solid var(--line-d)}
.acc details:last-child{border-bottom:0}
.acc summary{
  cursor:pointer;list-style:none;padding:1.05rem 1.2rem;font-weight:600;font-size:.95rem;
  display:flex;justify-content:space-between;gap:1rem;align-items:flex-start;
  transition:background .18s,color .18s;
}
.acc summary::-webkit-details-marker{display:none}
.acc summary::after{content:"+";font-family:var(--mono);color:var(--green);flex:none;font-size:1.15rem;line-height:1}
.acc details[open] summary::after{content:"–"}
.acc summary:hover{background:var(--crude-2);color:var(--green-hi)}
.acc details[open] summary{color:var(--green-hi)}
.acc .a{padding:0 1.2rem 1.2rem;border-top:1px solid var(--line-d)}
.acc .a p{margin:1rem 0 0;font-size:.92rem;color:#C4D0C7;max-width:78ch}

/* Product-variant tabs */
.tabs{display:flex;flex-wrap:wrap;gap:1px;background:var(--line-d);border:1px solid var(--line-d);margin-top:1.8rem}
.tabs button{
  flex:1;min-width:170px;background:var(--crude);border:0;cursor:pointer;
  font-family:var(--mono);font-size:.74rem;font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;color:var(--dim-d);padding:.95rem 1rem;transition:background .18s,color .18s;
}
.tabs button:hover{color:var(--green-hi)}
.tabs button.on{background:var(--green-d);color:#fff}
.tabpanes{border:1px solid var(--line-d);border-top:0;padding:1.6rem 1.4rem}
.tabpanes>div{display:none}
.tabpanes>div.on{display:block}
.tabpanes p{margin:0}

/* Closing band — same on every interior page. */
.ctaband{background:var(--green-d);color:#fff;padding:clamp(2.6rem,5vw,4rem) 0}
.ctaband .shell{display:flex;gap:2rem;align-items:center;justify-content:space-between;flex-wrap:wrap}
.ctaband h2{font-size:clamp(1.4rem,2.5vw,2.1rem);color:#fff;max-width:26ch}
.ctaband .eb{color:#C9E6A2}
.ctaband .eb::after{background:#C9E6A2}
.ctaband .btn{background:#fff;color:var(--green-d);border-color:#fff}
.ctaband .btn:hover{background:var(--cement)}

/* Automation demo slider */
.demo{border:1px solid var(--line-d);background:var(--crude-2);padding:1.5rem 1.4rem}
.demo label{
  display:block;font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.15em;
  text-transform:uppercase;color:var(--dim-d);margin-bottom:.7rem;
}
.demo input[type=range]{width:100%;accent-color:var(--green);height:2rem;cursor:ew-resize}
.demo .vals{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:1px;background:var(--line-d);border:1px solid var(--line-d);margin-top:1.1rem}
.demo .vals>div{background:var(--crude);padding:.9rem 1rem}
.demo .vals b{display:block;font-family:var(--mono);font-weight:600;font-size:1.1rem;color:var(--green)}
.demo .vals b.warn{color:var(--sour)}
.demo .vals span{display:block;margin-top:.35rem;font-family:var(--mono);font-size:.7rem;letter-spacing:.13em;text-transform:uppercase;color:var(--dim-d)}

/* ------------------------------------------------- the Apollo Control console
   Rebuilt from the source screenshot. Every rule below is .demo .vals at a
   different scale: cells sit on --crude over a --line-d ground with 1px gaps,
   so the rules between them ARE the ground showing through. No card has a
   border of its own, no shadow, no rounding. See dashboard.mjs for what was
   measured off the screenshot and where each device comes from. */
/* The console gets a wider container than the rest of the page, and the reason
   is arithmetic rather than taste: the source plots its chart 1115px wide with
   eleven two-line date labels under it, and inside the 1220px shell the chart
   lands at ~700px, where those labels are 6px tall and unreadable. At 1720 the
   plot is back to roughly 1:1 with the screenshot and the labels are the size
   Apollo set them. Full-bleed is already in this site's vocabulary — the CTA
   band runs edge to edge — so this is a width, not a new device. */
/* The console sits on the page's OWN gutter. It had a 1720px container of its
   own, which put its left edge 155px outside every other element on the page at
   a 1544 viewport and 250px outside at 1920 — the misalignment the owner called
   out. The chart's width problem is solved inside the console instead, by
   giving it a row to itself; see the note in dashboard.mjs. */
.dashbleed{margin-top:1.8rem}
.dash{
  display:grid;grid-template-columns:172px 1fr;gap:1px;
  background:var(--line-d);border:1px solid var(--line-d);
}
.dash .dside{background:var(--crude);display:flex;flex-direction:column;padding:1.1rem 0 1.1rem}
.dash .dlogo{width:88px;height:auto;margin:0 auto 1.6rem;display:block}
.dash .dnav{display:flex;flex-direction:column}
.dash .dnav span{
  padding:.62rem 1.1rem;font-family:var(--mono);font-size:.72rem;letter-spacing:.13em;
  text-transform:uppercase;color:var(--dim-d);border-left:2px solid transparent;
}
/* The active row. The source marks it with a pale green wash and a green bar;
   the bar is the site's own --green and the wash is --green at low alpha, which
   is how .rail marks its live stop. */
.dash .dnav span.on{color:var(--green);border-left-color:var(--green);background:rgba(147,196,85,.07)}
.dash .dtools{margin-top:auto;display:flex;flex-direction:column;gap:.9rem;padding:0 1.1rem;align-items:flex-start}
.dash .dtools span{font-family:var(--mono);font-size:.95rem;color:var(--dim-d);line-height:1}
.dash .dtools b{
  width:26px;height:26px;display:grid;place-items:center;background:var(--green-d);
  color:#fff;font-family:var(--mono);font-size:.72rem;font-weight:600;border-radius:50%;
}
.dash .dmain{background:var(--crude);display:grid;gap:1px;background:var(--line-d)}
/* max-width is released explicitly: the site's base rule is p{max-width:64ch},
   which is right for prose and wrong for a console heading — it held the title
   to 829px inside a 1283px row and left the hairline ground showing beside it. */
.dash .dtitle{max-width:none;
  grid-column:1/-1;background:var(--crude);margin:0;padding:1.15rem 1.3rem;
  font-family:var(--display);font-weight:700;font-size:clamp(1.05rem,2.2vw,1.5rem);
  letter-spacing:.01em;text-transform:uppercase;color:var(--cement);
}
.dash .dcards{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line-d)}
.dash .dcards.drow2{grid-template-columns:1.45fr 1fr}
.dash .dcards.drow3{grid-template-columns:1fr}
.dash .dcell{background:var(--crude);display:flex;flex-direction:column;min-width:0}
.dash .dch{
  display:flex;justify-content:space-between;align-items:center;gap:.6rem;
  padding:.7rem .85rem;border-bottom:1px solid var(--line-d);
  font-family:var(--sans);font-weight:600;font-size:.78rem;color:var(--cement);
}
.dash .dch i{font-style:normal;color:var(--green);letter-spacing:.06em;line-height:1}
.dash .dbody{padding:.9rem .85rem;flex:1;min-width:0;display:flex;flex-direction:column}
.dash .dcentre{display:grid;place-items:center;padding-block:1.1rem}
.dash .dmap{padding:0}
.dash .dmap img{width:100%;height:100%;min-height:150px;object-fit:cover;display:block}
/* The map takes the site's photographic grade, like every other picture here.
   It is an <img>, so it uses filter:url(#grade) — the same feColorMatrix path
   as .gr .panel>img — rather than the background-blend used by .pcard. It is
   written out explicitly because the grade is opt-in under .gr and this page
   carries no .gr: the console's own green and amber are data, not decoration,
   and must not be dragged through a hue reduction with the photograph. */
.dash .dmap img{filter:url(#grade)}
/* The tank and the gauge are drawn a shade smaller than their cells so they sit
   in the card the way they do on the source screen — the circle is 66% of its
   card there, and at 100% of the body box it reads as a different drawing. */
.dash .dcentre .dsvg{max-width:84%}

/* Field labels are .pcap's mono; values are plain sans, as on the source. */
.dash .dspec{margin:0;padding:.9rem .85rem}
.dash .dspec dt{
  font-family:var(--mono);font-size:.6rem;font-weight:600;letter-spacing:.15em;
  text-transform:uppercase;color:var(--green);margin-top:.85rem;
}
.dash .dspec dt:first-child{margin-top:0}
.dash .dspec dd{max-width:none;margin:.24rem 0 0;font-size:.86rem;color:var(--cement);word-break:break-word}

.dash .dlast{margin:0;font-family:var(--mono);font-size:.66rem;font-weight:600;letter-spacing:.15em;text-transform:uppercase;color:var(--green)}
.dash .dnum{max-width:none;margin:.5rem 0 0;font-family:var(--display);font-weight:700;font-size:clamp(1.7rem,3.6vw,3.4rem);color:var(--cement);line-height:1}
.dash .dstamp{margin:auto 0 0;padding-top:1.1rem;font-family:var(--mono);font-size:.56rem;letter-spacing:.06em;text-transform:uppercase;color:var(--dim-d);white-space:nowrap}
.dash .dset{margin:0 0 .8rem;font-size:.88rem;color:var(--cement)}
/* Inert by design — a picture of Apollo's control, not a control. */
.dash .dinput{display:block;border:1px solid var(--line-d);padding:.5rem .6rem;font-size:.78rem;color:var(--dim-d)}
.dash .dbtn{display:inline-block;margin-top:.8rem;background:var(--green-d);color:#fff;padding:.42rem 1.1rem;font-size:.78rem;opacity:.55}

.dash .dsvg{width:100%;height:auto;display:block;overflow:visible}
.dash .dchart{margin-top:.2rem}
.dash .d-hole{fill:var(--crude-2)}
.dash .d-fill{fill:var(--green)}
.dash .d-ring{fill:none;stroke:var(--dim-d);stroke-opacity:.45;stroke-width:2}
.dash .d-big{font-family:var(--display);font-weight:700;font-size:44px;fill:var(--cement)}
/* Three stops, not two, and the middle one is measured off the source. Apollo's
   band is still green at 43% across — (169,231,136) — and only turns yellow past
   half way; a straight --green to --sour ramp is olive by the midpoint and reads
   as a warning across an arc whose needle is sitting at 0.41. All three are
   existing tokens: the site's treated colour, its light green, and its untreated
   amber. The gauge therefore runs good-to-bad in this project's own pair rather
   than borrowing the source's yellow. */
.dash .g0{stop-color:var(--green)}
.dash .g1{stop-color:var(--green-hi)}
.dash .g2{stop-color:var(--sour)}
.dash .d-tick{font-family:var(--mono);font-size:14px;fill:var(--dim-d)}
/* The cuts through the ring are the CELL'S OWN GROUND, not a drawn line — the
   source does the same with the card's white, which is why its dial reads as
   one band with ticks in it rather than as five blocks. Butt caps so a cut
   stops flush at the ring's edges instead of poking past them.

   1.9 rather than 2.6: at the ring's mid radius a cut subtends its width as an
   angle, and the source's cuts measure about 1.3 degrees. 2.6 gave 1.96 and the
   band read as chopped; 1.9 gives 1.36. If the radii change, re-derive it —
   the number that matters is the angle, not the stroke. */
.dash .d-cut{stroke:var(--crude);stroke-width:1.9;stroke-linecap:butt}
.dash .d-needle{fill:var(--cement)}
.dash .d-pivot{fill:var(--cement)}
.dash .d-bearing{fill:var(--crude)}
.dash .d-grid{stroke:var(--line-d);stroke-width:1}
.dash .d-vert{stroke:var(--line-d);stroke-width:1}
.dash .d-ax{font-family:var(--mono);font-size:12px;fill:var(--dim-d)}
.dash .d-line{fill:none;stroke:var(--green);stroke-width:2;stroke-linejoin:round;stroke-linecap:round}

@media (max-width:900px){
  .dash{grid-template-columns:1fr}
  .dash .dside{flex-direction:row;align-items:center;gap:1rem;padding:.8rem 1rem}
  .dash .dlogo{margin:0;width:54px}
  .dash .dnav{flex-direction:row}
  .dash .dtools{margin:0 0 0 auto;flex-direction:row;align-items:center;padding:0}
  .dash .dcards{grid-template-columns:repeat(2,1fr)}
  .dash .dcards.drow2,.dash .dcards.drow3{grid-template-columns:1fr}
  .dash .dcell-wide,.dash .dcell-map{grid-column:1/-1}
}

/* Dark form — the light .form rules above are the base; this recolours it. */
.form.dark input,.form.dark textarea{background:var(--crude-2);border-color:var(--line-d);color:#E6EDE7}
.form.dark input::placeholder,.form.dark textarea::placeholder{color:var(--dim-d)}
.form.dark .cats button{background:var(--crude-2);border-color:var(--line-d);color:var(--dim-d)}
.form.dark .cats button.on{background:var(--green-d);border-color:var(--green-d);color:#fff}
.form.dark .sub{background:var(--green);color:var(--crude)}
.form.dark .sub:hover{background:var(--green-hi)}
.okmsg{font-size:.88rem;color:var(--green-hi);margin:0}
.errmsg{font-size:.88rem;color:var(--sour);margin:0}
.light .okmsg{color:var(--green-d)}

/* scroll reveal */
.rv{opacity:0;transform:translateY(18px);transition:opacity .55s ease,transform .55s ease}
.rv.in{opacity:1;transform:none}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms !important;transition-duration:.001ms !important}
  .rv{opacity:1;transform:none}
}
