/* About — a state of the hero stage, not a separate page. Requires
   base.css and hero.css.

   Clicking `about` puts `.is-about` on the section. hero.css owns the curtain
   every section shares — the front chevron sliding left onto the mid chevron,
   the fourth chevron arriving from the right edge, the logo folding into the
   corner. What about adds is what rides in on that fourth chevron: the copy
   column, the scroll rail, the gallery and the lightbox.

   All geometry is a percentage of the stage, measured off
   `references site/sections/about/`. */


.hero {
  /* The fourth chevron's two edges, as lines on the stage. The chevron itself
     lives in hero.css. These two line equations are the algebraic source of
     `.about-gallery__peek`'s and `.about-gallery__tile`'s own `clip-path`
     polygons further down — each vertex there is one of these lines solved
     against that box's own edges and rebased onto it (subtracting the box's
     `left`/`top`). The chevron art is sized by height (130%) with `width:
     auto`, so its slope is fixed in pixels by the art's own aspect — 0.579
     px across per px down — and only its offset moves with the viewport.
     Measured off 1600x811 and 1920x1080 captures; both fits agree to four
     decimals and predict either capture within 0.6px:

       lower arm   x = --arrow-far + 19.336dvh + 0.579y
       upper arm   x = --arrow-far + 60.996dvh - 0.579y
       apex        (--arrow-far + 40.166dvh, 35.976dvh)

     This is why the cuts below are written in vw/dvh and not in percentages
     of the clipped element: a percentage polygon stretches with its own box,
     so it only lines up at one aspect ratio and drifts off the arrow
     everywhere else — about 13px at 1920x1080. */

  /* Gallery block. The tile is the opaque panel the photo is inset into, by
     an equal 0.9% on both sides. The reference measures wider on the left,
     but the tile's left edge is where --peek ends, so the gutters are evened
     against the photo rather than by moving the tile — moving it would open
     a seam against the peek.

     0.9% and not less: the gutter also has to hold the photo clear of the
     fourth chevron's lower edge, which crosses the tile's bottom-left
     corner. At 0.65% the photo's own corner overhung that edge by 3px. */
  --tile-left: 69.76%;
  --tile-top: 29.57%;
  --tile-width: 27.76%;
  --tile-height: 38.01%;
  --photo-left: 82.32%;
  --photo-top: 31.14%;
  --photo-width: 14.3%;
  --photo-height: 34.87%;

  /* Copy column and its rail. --copy-left/--copy-width narrowed by 10
     percentage points on request ("make the width of the column 10% less
     on the left side") — the left edge moved right from 36.11% to 46.11%,
     the width dropped from 34.2% to 24.2%, and the right edge (70.31%) held
     fixed, since only the left side was asked to give up width. That right
     edge is also what services.css's --bento-right reads
     (`calc(var(--copy-left) + var(--copy-width))`), so it needed no change,
     and --rail-x (71%, just clear of the right edge) didn't either.

     Later widened back 5 points from the left, on request ("make it wider
     to the left by 5%") — --copy-left: 46.11% -> 41.11%, --copy-width:
     24.2% -> 29.2%, right edge still held at 70.31% (41.11 + 29.2 = 70.31),
     so services' --bento-right and --rail-x again needed no change.

     --copy-left itself STAYS a bare literal — services.css's --bento-right
     and events.css's --events-right both read `calc(var(--copy-left) +
     var(--copy-width))` as a stable anchor for their own right edge, and
     custom properties resolve by cascade at the element that reads them,
     not by which file declared them: if --copy-left were tied to
     --shift-front, those two cross-file reads would resolve --shift-front
     at *their own* element (inside .is-services/.is-events, where it's
     always the base 0cqw, not -9cqw) and silently land 9 points too far
     right. Tried exactly that first, caught it from a visibly widened
     services grid, reverted to this. */
  --copy-left: 41.11%;
  /* The copy column's own `left`, unlike --copy-left above, DOES follow the
     front chevron's travel — matching the pattern products.css/events.css
     already use for their own left edge (--products-left off --shift-back,
     --events-left off --shift-quad). Without this, retuning --shift-front
     in the future would silently desync the column from the chevron it
     sits next to. Base is 50.11%, so --shift-front's always-exactly-
     -9cqw whenever `about` is showing lands this at 41.11%, the same
     value --copy-left holds. Only about's own elements (below) read this
     one; --bento-right/--events-right keep reading the stable --copy-left
     above, untouched. */
  --copy-left-live: calc(52.11% + var(--shift-front));
  /* --copy-width itself (below) stays the stable, original 29.2% — it's
     what services'/events' own --bento-right/--events-right read
     (`calc(var(--copy-left) + var(--copy-width))`), the same cross-file
     stability --copy-left already gets its own comment for above.
     --copy-width-live is the about-local width .about__copy itself
     actually reads — a plain alias to --copy-width here (so it's
     identical by default), overridden separately per breakpoint below
     (see "Laptop tier" further down) rather than ever touching the shared
     --copy-width directly. This split exists because an earlier pass
     narrowed --copy-width itself, which silently narrowed services'/
     events' own right edge too (neither was asked to change) — caught
     and fixed by introducing this pair.

     Desktop tier narrowed 2 points from the left, on request ("make the
     column 2% narrower from the left, only on desktop") — --copy-left-live
     above moved 50.11% -> 52.11%, and this dropped the matching 2 points
     off the width so the right edge (--copy-left-live + --copy-width-live)
     holds fixed. Laptop tier's own override below is untouched. */
  --copy-width-live: calc(var(--copy-width) - 2%);
  --copy-width: 29.2%;
  --rail-x: 71%;
}

/* ---------- Laptop tier ----------

   Everything in this block is laptop-only geometry (aspect <= 1.85:1 —
   see hero.css's own "Sub-page content geometry" comment, once one
   exists, for the shared threshold; for now this is the first file to
   introduce it, on request: "make this breakpoint thing and what we have
   did in this session apply it only on the laptop version"). Outside this
   range (a plain wide desktop window) the section falls back to the base
   `.hero` values just above, i.e. today's pre-this-session geometry.
   Every property overridden here was tuned this session against laptop-
   sized captures (1600x900 and similar) specifically, one request at a
   time — see each one's own history below. */
@media (max-aspect-ratio: 1.85/1) {
  .hero {
    /* Pulled right of the synced value above, on request ("move the
       column with the rail 1% right", then "now with 2%", then "make it
       5%" — found to visibly crowd both the rail's text-side neighbor and
       the gallery once screenshotted at 5% — then "make it 3% and narrow
       the column by 2%", then "position should be 4%", then "make the
       position to 3.5% and make the column narrower by 0.5%", then
       finally "make it 1% narrower"). Base 50.11% -> 53.61%, so the
       column (and --rail-x just below, to stay clear of its new right
       edge) sits 3.5% right of where --copy-left/--copy-width alone
       would still put it. This is about's own rendered position only;
       --copy-left itself is unchanged, so services'/events' own
       --bento-right/--events-right reads (which key off --copy-left, not
       this) are unaffected.

       Moved a further 2 points right on request ("move the title and
       paragraph by 2% to the right, keep the rail in its position"):
       53.61% -> 55.61%. Width (--copy-width-live) is untouched, so this
       is a pure translate of the title/copy box, not a narrowing —
       --rail-x (below) was deliberately left alone, since the rail was
       asked to stay put. The box's own nominal right edge (left + width
       = 55.61% + 25.7% = 81.31%) sits well past --rail-x's 74.5% — the
       paragraph's actual wrapped text does reach into the rail's line at
       this width (confirmed by screenshot at 1600x900: "operational" and
       "...services be" both run into/through the rail), and was already
       nearly touching it before this move (79.31% right edge). Flagged to
       the user with a before/after screenshot; accepted as-is — the
       overlap is not being treated as a bug to fix. */
    --copy-left-live: calc(55.61% + var(--shift-front));
    /* Narrowed from the plain --copy-width alias above: dropped 2 points
       (29.2% -> 27.2%) at the "3% and narrow by 2%" step, then 0.5
       (-> 26.7%), then a further 1 (-> 25.7%) at the final "make it 1%
       narrower" step. */
    --copy-width-live: 25.7%;
    /* Kept clear of the copy column's new right edge above. */
    --rail-x: 74.5%;

    /* The gallery's thumbnail-state size/position (--tile-left/-width/
     -height, --photo-*) was shrunk ~15% and moved right here for a
     while ("make the about gallery before clicking on it smaller and
     move it to the right a bit"), then nudged left several times over
     several follow-ups, then reverted in full ("okay also revert the
     scaling") — back to no laptop-tier override at all for a while.
     Only the peek clip-path fix below (a real bug, not this experiment)
     survived that whole run.

     A second, separate experiment ("now just move this whole galler
     2% right" — no resize this time, just `--tile-left`/`--photo-left`
     plus the peek/nav-arrow `left`s below) went through +2, +1.5, then
     +1.7 points before being reverted in full too ("okay revert this
     whole thing back to before moving it to the right"), on the same
     "clips the nav-next arrow" problem described just below.

     Revisited and actually landed in an earlier session ("move the images
     and the arrows 2% to the right"). Base 69.76% -> 71.76% / 82.32% ->
     84.32%, a plain +2 points on both — this is the tile/photo pair;
     the peek's own `left` and the two nav arrows' `left`s are literals
     on their own rules further down (`.hero .about-gallery__peek`,
     `.hero .about-gallery__nav--prev/--next`), not read from these two
     custom properties, so each needed its own matching edit down there.
     `--next` is the one exception, not a plain +2 — see its own rule
     for why.

     `--tile-left` alone was then moved back 2 points on request
     (2026-09-01, "move the about tile 2% to the left") — 71.76% ->
     69.76%, i.e. back to the same value as the desktop base rule.
     Confirmed with the user this was meant literally (`--tile-left`
     only, not the whole tile/photo/peek/arrow group above) — since
     `.about-gallery__tile`'s own fill is the same color as the page
     background, this has no visible effect on its own. `--photo-left`
     (84.32%) and the peek/nav-arrow literals below are unchanged, so
     they're now 2 points to the right of `--tile-left` where they used
     to share its value — a deliberate, confirmed-good mismatch, not
     drift to fix. */
    --tile-left: 69.76%;
    --photo-left: 84.32%;
  }

  .about__copy {
    /* 1 point smaller than the base 61% below, on request ("make its
       height 1% samller"). */
    height: 60%;
  }

  /* Bug fix, not a design change: the peek photo's clip-path (base rule,
     below) is deliberately tuned so its taper reaches *exactly* the peek's
     own right edge at the bottom — bottomcut (`-16.65cqw + 55.715cqh`)
     equals `--photo-width`... no, equals the peek's own fixed 11.66%-of-
     stage width almost exactly (186.56px vs 186.55px measured at
     1600x813) — confirmed not a coincidence. That equality only holds
     near the design aspect the constants were fit at. The chevron-tracking
     terms are `cqh`-heavy, so as `.hero` gets relatively taller at laptop
     aspect (clamped as low as 1.7:1 by `--stage-min-aspect`), bottomcut
     grows past the peek's own box — measured 138% of the peek's width at
     the 1.7 floor, still 115% at the laptop tier's own 1.85 edge. Past
     100% the polygon's clip line sits outside the box entirely, so the
     visible photo collapses to a point partway up instead of tapering to
     the tile's bottom-left corner, leaving the corner as a bare gap
     instead of a graceful taper — this is the "gallery overflows onto the
     arrow" report.

     Re-deriving bottomcut to land back on 100% would need it to reach
     ~138% of the *available* gap to the main photo at the 1.7 floor
     (measured: 244px of clip-path-formula travel vs only 190px of actual
     room before `--photo-left` begins) — the chevron's real edge has
     moved further right than the laptop-tier layout has room for, so
     exactly tracking it isn't possible here regardless of constants.
     Pinned instead to a fixed taper proportional to the peek's own box
     (not `cqw`/`cqh`-tracked) — same visual proportions the desktop taper
     has (37.3% visible width at the top edge, narrowing to the peek's own
     bottom-right corner, with the same small kink partway down) — so it
     always closes cleanly regardless of exactly where in the laptop
     aspect band the window lands. No laptop reference mockup exists to
     match pixel-for-pixel (like `events`, see CLAUDE.md); "closes cleanly,
     same proportions as desktop" is the bar here.

     Selector is `.hero .about-gallery__peek`, not the bare class the base
     rule below uses: the base `.about-gallery__peek` rule sits *after*
     this media block in the file, so at equal specificity it would win
     the cascade regardless of which aspect actually matches — confirmed
     via computed style (`getComputedStyle` still showed the base
     `cqw`/`cqh` polygon at 1512x982 with only the bare class here). The
     extra `.hero` ancestor is harmless (peek is always inside it) and
     raises specificity enough to win unconditionally once the media
     query itself matches. */
  .hero .about-gallery__peek {
    /* Revived 2026-08-31: this had been set to `none` (see the history
       below this comment) on the reasoning that the gallery sits in front
       of every chevron now, so there was supposedly no edge left to track.
       That reasoning missed a second job this taper was always doing —
       the one the file's own top-of-file `--tile-left`/`--rail-x` comment
       still describes: keeping the peek's *visible* pixels short of
       wherever the rail sits, even though the box itself is wide enough to
       reach under it. With this at `none`, the peek rendered as a plain
       rectangle running the box's full literal width, and at laptop aspect
       specifically — where `--rail-x` sits at 74.5%, well inside this
       box's own 69.76%–81.42% span — the rail's hairline and down-arrow
       ended up drawn directly across the photo. Screenshot-confirmed at
       1512x982 (clamped ~1.7) and 1366x768 (~1.78); desktop doesn't have
       this (`--rail-x: 71%` also sits inside this box there, but desktop
       was never reported broken and is intentionally left untouched here).

       Plain `%` polygon, not `cqw`/`cqh` — resolves against this box's own
       width/height directly, so it can't drift the way the old dvh-tracked
       version did (see "the gallery overflows onto the arrow" history just
       below), and its minimum x only has to clear the rail once, not at
       every aspect. Kept the same shape family the old fix already
       described (wide at the top, tapering to a point at the box's own
       bottom-right corner).

       Minimum x is 28% of the box width today. This paragraph used to claim
       a much narrower cut (a "was 42%/58%, now 40%/60%" history ending near
       the rail's own footprint with a comfortable ~52px margin) — that text
       had drifted out of sync with the live rule for an unknown stretch of
       time; the code actually read 30% (not 60%) when checked on
       2026-09-01, leaving only a ~0.2px gap to the rail (`--rail-x: 74.5%`)
       instead of the ~53px the old comment described — visually read as the
       taper running right along the rail. Tried restoring it to 60% to
       reproduce the documented margin; screenshot rejected on request
       ("not good"), reverted back to 30%. Immediately after, moved 2 points
       further left on request ("move that polygon 2% to the left"): 30% ->
       28%. So today's 28% is a deliberate, confirmed-good value, not a typo
       to "fix" back toward 60% — don't re-derive it from the rail-clearance
       math this comment used to cite; that math no longer describes what's
       wanted here. */
    clip-path: polygon(100% 0%, 28% 0%, 100% 100%);
    /* Matches --tile-left's own +2 point shift above ("move the images and
       the arrows 2% to the right") — the peek's box is otherwise a bare
       literal (its base rule, further down, doesn't read --tile-left even
       though the two happen to share a value at the base geometry), so it
       needs its own copy of the same shift rather than picking it up
       automatically. */
    left: 71.76%;
    /* top/left/width/height all briefly had their own laptop-tier
       overrides here too, across two separate resize/reshift
       experiments — both fully reverted (see the long comment on `.hero`
       above), leaving only this `clip-path` fix. At the base geometry the
       box this clip-path applies to is `left: 69.76%; top: 33.99%;
       width: 11.66%; height: 28.84%` (this file's own base
       `.about-gallery__peek` rule, further down).

       An attempt to instead widen this rule to exactly track the
       chevron's true edge ("place the tile photo... exactly into the
       corner") was also tried and reverted same-session ("revert it its
       not good") — it required growing well past this box's width, which
       pushed the peek's visible region into an overlap with the main
       photo. Kept here only so a future session doesn't re-try the same
       approach expecting a different result — closing that overlap needs
       `--photo-left` (or the main photo's own geometry) to move too, not
       just the peek. */
  }

  /* Laptop-tier only, on request: the gallery sits *behind* `.arrow--far`
     was the first attempt here — reverted the same session once rendered:
     `.arrow--far`'s image is sized to cover the entire gallery footprint,
     and (per hero.html's own note that "arrows are currently flat/uniform
     color") its body is close enough to `--blue-deep` to read as empty
     background, so "behind far" didn't nick a corner, it erased the whole
     gallery invisibly. Landed instead on "between `.arrow--front` and
     `.arrow--far`": gallery gets the same z-index (3) as both. Front never
     geometrically reaches the gallery's screen area at this or any aspect,
     so the only tie that matters in practice is against far — and ties
     resolve by DOM order, with `.about-gallery` sitting later in
     hero.html than either arrow, so it wins and paints on top of far,
     restoring full visibility. No chevron actually cuts into the gallery's
     corner this way (that would need a real clip-path re-derived against
     far's true edge, not attempted here) — this only fixes the swallow.
     Desktop is unaffected either way (gallery in front, via `.about-
     layer`'s unconditional z-index:20 from hero.css's `.section-layer`
     rule).

     `.about-layer`'s z-index is what traps every descendant above the
     chevrons — a descendant can never out-rank an element outside its own
     ancestor's stacking context, so the only way to let the gallery's
     z-index compete directly with an *arrow* (a sibling of `.about-layer`,
     not a descendant of it) is to stop `.about-layer` itself from
     establishing one here. `.about__title`/`.about__copy`/`.about-rail`
     get their own explicit z-index (above the gallery's 3, so the text
     stays readable over it regardless) to compensate for losing the
     layer's former z-index:20. **Any new direct child of `.about-layer`
     needs adding to that same list, or it silently paints underneath the
     (fully opaque) chevron art at this tier** — caught once already, when
     the motto banner (`.about__motto`, see its own comment further down)
     briefly lived here as a direct child: it shipped without a z-index,
     and computed style showed everything correct (visible, opacity 1,
     real geometry, even a debug `background: lime` set via devtools)
     while still not appearing in any screenshot, because a chevron with
     an explicit z-index was painting over it. Confirmed via
     `document.elementFromPoint` at the element's own center returning
     `.about-gallery` (z-index 3) instead of the element itself, and via a
     `getComputedStyle` diff against `.about__title` that isolated
     `z-index: auto` vs `4` as the one differing property. The motto
     banner has since moved to be a child of `.about__copy` instead (see
     its own comment), which sidesteps this trap entirely — but the trap
     itself is still real for whatever direct child of `.about-layer`
     comes next. */
  .about-layer {
    z-index: auto;
  }

  .about-gallery {
    /* z-index is a no-op on a `position: static` element (its base rule,
       further down, sets none), so it needs its own positioning for the
       z-index above to take effect at all. `position: relative` alone
       would leave it sized by normal flow — width matching `.about-layer`
       (fine), but height auto-collapsing to ~0 since every child inside is
       itself `position: absolute` and contributes nothing to flow height —
       and every child's own left/top percentages resolve against *this*
       box once it's positioned, not `.about-layer` anymore, so a
       collapsed height would break all of them vertically. `inset: 0`
       instead makes it full-bleed, matching `.about-layer`'s own box
       exactly, so nothing downstream needs to change. */
    position: absolute;
    inset: 0;
    z-index: 3;
  }

  .about__title,
  .about__copy,
  .about-rail {
    z-index: 4;
  }

  /* Nav arrows, same "move the images and the arrows 2% to the right"
     request as --tile-left/--photo-left above — both are bare literals on
     their base rule (further down), not tied to either custom property, so
     each needs its own laptop-tier override. `.hero` prefix for the same
     cascade-order reason the peek's own override above needs it: the base
     `.about-gallery__nav--prev/--next` rules sit later in the file, so a
     bare-class selector here would lose to them even once this media query
     matches. */
  .hero .about-gallery__nav--prev {
    /* +5 points on request ("move them 5% to the right") — 82.31% ->
       87.31%. `--next` got the same request but couldn't take the full
       5 points; see its own rule below. Then pulled back 3 points
       ("move the left arrow back by 3%", 87.31% -> 84.31%), then back a
       further 2 ("okay back by 2%", 84.31% -> 82.31%, net back to the
       pre-shift value), then +1 ("now move it to right by 1%", 82.31% ->
       83.31%), then -0.5 ("okay now 0,5% to the left", 83.31% -> 82.81%),
       then +0.2 on a final follow-up ("okay no move the arrow left 0.2%
       to the right", 82.81% -> 83.01%). */
    left: 83.01%;
  }

  /* 20% smaller, on request ("make the nav bar prev and next 20% smaller")
     — base width is 1.49% (shared with `.lightbox__step` on its own base
     rule, which this selector deliberately does not touch), 1.49 * 0.8 =
     1.19%. The svg inside scales with it (its own rule is `width: 100%;
     height: auto`), so the icon shrinks proportionally, not just its
     clickable box. Left unpositioned otherwise — `left` is each arrow's
     own left edge, so shrinking width only pulls each icon's *right* edge
     inward, giving `--next` a bit more clearance from the stage edge on
     top of the margin its own override above already keeps. */
  .hero .about-gallery__nav {
    width: 1.19%;
  }

  /* Not a plain +5 like `--prev` just got ("move them 5% to the right"),
     on purpose — same stage-edge constraint as this arrow's earlier +2
     shift (see the long comment on `.hero` above): `.hero` has `overflow:
     hidden` (hero.css), and at its current 1.19%-wide box (post-shrink,
     above) there's only 0.6 points of room between its resting right edge
     (99.4%) and the stage's own 100% edge — nowhere near 5 points of
     travel available. Pulled in only as far as that room allows: 98.21% ->
     98.51%, keeping a 0.3-point clearance to the right edge (99.7%)
     rather than pushing flush to it.

     A further +0.5 request ("move the right arrow also 0.5% to the
     right") hit the same ceiling again — only 0.3 points of room were
     left, not 0.5. Took as much as a small safety margin allows: +0.2
     (98.51% -> 98.71%, right edge 99.9%), leaving 0.1 points clear of
     the stage's own edge rather than landing exactly flush on it (a
     flush edge risks sub-pixel clipping from rounding/antialiasing under
     `overflow: hidden`). Any further rightward request here needs
     `--next` shrunk smaller first to free up more room — see the
     shrink's own comment above. */
  .hero .about-gallery__nav--next {
    left: 98.71%;
  }
}

/* ---------- State ---------- */

/* The base `.section-layer` rule is in hero.css; this turns about's own layer
   on. The fourth chevron's band never reaches left of its own apex, so it
   clears the rail and the copy column entirely. */
.is-about .about-layer {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--ease-fade) var(--delay-settle),
    visibility 0s linear var(--delay-settle);
}

/* `about` is the current section once it lands on the mid chevron's point, so
   it stops being a link there. `services`, which owns that point in the hero,
   stays visible underneath it and is the way on to the next section.

   Restates the eased `transition` (`hero.css`'s unconditional rule now
   delays `opacity` instead, for the leaving direction) so entering still
   fades this out smoothly as it travels in, unchanged from before. */
.is-about .hero-nav__link--about {
  opacity: 0;
  pointer-events: none;
  transition: transform var(--ease-move), opacity var(--ease-move),
    color var(--ease-ui);
}

/* All four chain chevrons + the decorative `.arrow--far` were previously
   hidden outright for `about` (both here and in hero.css's shared
   `.arrow--far` arrival group) because the curtain bands were reported
   showing through under part of the copy paragraph at some window aspect.
   Restored on a later request instead of left hidden — matches the
   reference (`about_full.png`), which shows the front-onto-mid travel and
   the framing fourth chevron with the copy column sitting cleanly in the
   open band between them at the design aspect. If the overlap resurfaces
   at a specific window aspect, screenshot that aspect and fix the actual
   geometry drift (the copy column's `cqw`-based position vs. the
   chevrons' `cqh`-driven diagonal edges — see hero.css's tip-offset drift
   fix for the same class of bug) rather than re-hiding the chevrons. */

/* ---------- Copy column ---------- */

.about__title {
  position: absolute;
  left: var(--copy-left-live);
  top: 11%;
  margin: 0;
  font-family: var(--font-utility);
  font-size: var(--size-heading);
  font-weight: normal;
  /* Far wider than the nav's tracking — the section title is the one place
     the copy column borrows the wordmark's spacing. */
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--paper);
}

/* Motto banner — a short mission statement, on request. Lives as the
   *first child inside* `.about__copy` (hero.html), not a sibling of it —
   tried as a sibling pinned above the scroll first, reverted on report
   that it read as a sticky header while the paragraph scrolled underneath
   it ("i dont want it, if i scroll down the motto should disappear, like
   it the part of the paragraph"). As a normal in-flow child of the
   scrolling column it scrolls away with the rest of the text instead, and
   the column's own width/position needed no separate copy of
   `--copy-left-live`/`--copy-width-live` — it simply inherits its
   parent's box. `.about__copy`'s own `top`/`height` are back to their
   original plain `18%`/`61%` for the same reason: nothing needs carving
   out of the column's footprint anymore, since the banner now counts
   toward the column's own scrollable content instead of sitting outside
   it — see CLAUDE.md's "The about choreography" for the full history of
   the sibling approach this replaced.

   Filled with `--panel-elevated` (light blue, on request) — the "floating
   above the base panel" tone this token already carries elsewhere (the
   lightbox pdf box, cta-card hover), reused here rather than introducing
   a new blue. The small icon (the site's own star-only brand crop,
   already used for the favicon) sits fully inside the box, directly on
   the panel — no badge disc behind it (tried a `--paper` circle first,
   removed on request). */
.about__motto {
  display: flex;
  align-items: center;
  gap: 1.4cqw;
  background: var(--panel-elevated);
  padding: 1.6cqh 1.8cqw;
  margin: 0 0 var(--lh-body);
}

.about__motto-icon {
  width: 4cqw;
  height: 4cqw;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.about__motto-icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.about__motto-text {
  margin: 0;
  font-family: var(--font-utility);
  font-size: 2.1cqh;
  line-height: 2.7cqh;
  color: var(--paper);
}

.about__copy {
  position: absolute;
  left: var(--copy-left-live);
  top: 18%;
  width: var(--copy-width-live);
  /* Bottom edge pulled up 5 points on request ("reduce the height on the
     bottom by 5%") — was 71.9%, top held at 17.6% so only the bottom moved.
     Nudged to 66%, then pulled up another 5 points to 61% on request. Top
     then nudged from 17.6% to 18% on request, height left unchanged. A
     further 1-point reduction ("make its height 1% samller") is laptop-
     tier only — see the "Laptop tier" media block above. */
  height: 61%;
  overflow-y: auto;
  scrollbar-width: none;
  font-family: var(--font-utility);
  font-size: var(--size-body);
  line-height: var(--lh-body);
  color: var(--paper);
  /* The reference fades the last lines out rather than clipping them, so
     the column reads as continuing past the frame. The gradient's own stops
     (88%/100%) never change — animating them directly needs a registered
     custom property (`@property`), which turned out not to actually
     interpolate `mask-image` in practice (verified: the computed value sat
     frozen at 88% long past the transition's duration). `mask-size` is
     transitioned instead: growing the mask taller than the box, anchored at
     its top, pushes the gradient's fixed 88%-100% fade band down past the
     box's own bottom edge — same visual result, standard animatable
     property, no registration needed. */
  -webkit-mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: top;
  mask-position: top;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  transition: -webkit-mask-size var(--ease-ui), mask-size var(--ease-ui);
}

/* Once scrolled to the actual end of the text, there's no more content
   below to fade into — the gradient was just eating the last 1-2 lines
   with nothing to justify it. `about.js`'s syncRail adds this class once
   the column's scroll position reaches its max. Growing the mask to 120%
   of the box's height (anchored top, per mask-position above) puts the
   gradient's 88% stop at 0.88 * 120% = 105.6% of the box's real height —
   past the visible bottom edge — so the entire visible box lands in the
   gradient's opaque zone and reads fully solid. */
.about__copy.is-at-end {
  -webkit-mask-size: 100% 120%;
  mask-size: 100% 120%;
}

.about__copy::-webkit-scrollbar {
  display: none;
}

.about__copy p {
  margin: 0 0 var(--lh-body);
}

.about__copy p:last-child {
  margin-bottom: 0;
}

/* ---------- Scroll rail ---------- */

/* Replaces the native scrollbar the column hides. Up and down chevrons step
   the column; the diamond reports and drags the position. */
/* Laid out absolutely rather than with flex: a percentage margin in a
   column flex resolves against the container's *width*, and this container
   is 1.54% wide, so the gaps the reference shows would collapse to nothing. */
.about-rail {
  position: absolute;
  left: var(--rail-x);
  /* Moved up 2 points on request ("move up the about rail by 3%", then
     corrected to "only by 2") — was 13.14%, height untouched so the whole
     rail just translates upward. */
  top: 11.14%;
  /* Shrunk by the same 10.9 points the copy column's own height lost from
     its bottom (71.9% -> 61%, top held fixed both times), so the rail keeps
     tracking the column's shorter bottom edge. Was 79.5%. */
  height: 68.6%;
  width: 1.54%;
  transform: translateX(-50%);
}

.about-rail__step {
  position: absolute;
  left: 0;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  color: var(--paper);
  cursor: pointer;
  transition: opacity var(--ease-ui), transform var(--ease-ui);
}

.about-rail__step svg {
  display: block;
  width: 100%;
  height: auto;
}

.about-rail__step:hover {
  opacity: 0.7;
}

.about-rail__step--up {
  top: 0;
}

.about-rail__step--down {
  bottom: 0;
}

.about-rail__step--up:active {
  transform: translateY(-2px);
}

.about-rail__step--down:active {
  transform: translateY(2px);
}


.about-rail__track {
  position: absolute;
  left: 50%;
  top: 2.84%;
  bottom: 3.14%;
  width: 1px;
  margin-left: -0.5px;
  background: var(--paper);
  cursor: pointer;
}

/* The line is drawn end to end, but the diamond travels a shorter band
   inside it, so it never collides with either chevron. Its rest position is
   where the reference puts it. */
.about-rail__travel {
  position: absolute;
  left: 0;
  right: 0;
  top: 6.7cqh;
  bottom: 6.7cqh;
}

/* An outlined diamond, not a filled one — the track hairline runs visibly
   straight through it. Sized against the stage, because the track it lives
   in is one pixel wide. It is a fixed-size position marker rather than a
   proportional thumb, which is how the reference draws it. */
.about-rail__thumb {
  position: absolute;
  left: 50%;
  top: 0;
  width: 0.66cqw;
  height: 1.81cqh;
  color: var(--paper);
  transform: translateX(-50%);
}

.about-rail__thumb svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* ---------- Gallery ---------- */

/* The lightbox takes the gallery's place rather than covering it: the
   caption panel lands in the same band the tile occupied, and the tile is
   opaque, so leaving it behind would show through as a seam. */
.is-lightbox .about-gallery {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--ease-fade),
    visibility 0s linear var(--ease-fade);
}

.about-gallery {
  transition: opacity var(--ease-fade);
}

/* The previous slide peeks out to the left of the tile, cut along the
   fourth chevron's own edges (found missing from this rule entirely on
   2026-09-01 — an earlier, undocumented edit had deleted it outright,
   leaving a plain uncut rectangle; restored from `_backup_pre_flexbox/
   about.css`, see "The about choreography" in CLAUDE.md). The gallery
   staying in front of every chevron (see the laptop-tier media block's
   `.about-layer` z-index comment above) means this clip isn't needed to
   hide the box *behind* a chevron — but it's still needed for two other
   reasons: it tapers the rendered edge to match the chevron's diagonal
   instead of a hard rectangle corner, and it trims the box's own visible
   pixels clear of `--rail-x` (71%), which the raw box (69.76%–81.42%)
   would otherwise paint straight through. Both vertices are the master
   line equations above, rebased onto this box (subtracting its own
   69.76% left / 33.99cqh top). */
.about-gallery__peek {
  position: absolute;
  left: 69.76%;
  top: 33.99%;
  width: 11.66%;
  height: 28.84%;
  object-fit: cover;
  background: var(--blue-deep);
  clip-path: polygon(
    calc(var(--arrow-far-n) * 1cqw - 69.76cqw + 41.316cqh) 0,
    100% 0,
    100% 100%,
    calc(var(--arrow-far-n) * 1cqw - 69.76cqw + 55.715cqh) 100%,
    calc(var(--arrow-far-n) * 1cqw - 69.76cqw + 40.166cqh) 1.986cqh
  );
  /* Sits on the tile now, not bare page ground, so it needs its own depth:
     darkened and dropped a shadow to read as set apart from the panel
     behind it. */
  filter: brightness(0.55) saturate(0.9) drop-shadow(0.3cqw 0.4cqh 0.8cqw rgba(var(--shadow-ink), 0.5));
  /* No transition of its own — its `src` swap (about.js's `slide()`) is a
     plain instant cut in step with the main photo's own promotion. A
     crossfade, and later a clone flying between this box and the main
     photo's, were both tried (2026-09-13) and dropped the same day on
     request; see CLAUDE.md for what was tried and why. This element's
     clip-path is plotted in `cqw`/`cqh` relative to `.hero` itself (see the
     master line equations above), not its own box, so it must stay
     anchored exactly where it is regardless. */
}

/* Opaque background panel behind `.about-gallery__open`, cut the same way
   as `.about-gallery__peek` just above (same missing-clip-path regression,
   restored from the same backup on 2026-09-01) — the tile's own fill,
   `--blue-deep`, is identical to the page ground, so the taper here is
   purely cosmetic edge-matching against the chevron, not a visibility
   fix, but the peek's clip above still needs the tile's own left edge to
   read consistently with it. */
.about-gallery__tile {
  position: absolute;
  left: var(--tile-left);
  top: var(--tile-top);
  width: var(--tile-width);
  height: var(--tile-height);
  background: var(--blue-deep);
  clip-path: polygon(
    calc(var(--arrow-far-n) * 1cqw - 69.76cqw + 43.875cqh) 0,
    100% 0,
    100% 100%,
    calc(var(--arrow-far-n) * 1cqw - 69.76cqw + 58.465cqh) 100%,
    calc(var(--arrow-far-n) * 1cqw - 69.76cqw + 40.166cqh) 6.406cqh
  );
}

.about-gallery__open {
  position: absolute;
  left: var(--photo-left);
  top: var(--photo-top);
  width: var(--photo-width);
  height: var(--photo-height);
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  overflow: hidden;
}

.about-gallery__photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--blue-deep);
  transition: transform var(--ease-ui);
}

.about-gallery__open:hover .about-gallery__photo {
  transform: scale(1.03);
}

.about-gallery__open:active .about-gallery__photo {
  transform: scale(1.01);
}

/* Gradient overlay plus a colour layer, so a photograph of any temperature
   still sits inside the blue. Thumbnail only — at full size the photograph
   is the subject and carries no treatment of its own. */
.about-gallery__open::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to top, rgba(0, 30, 46, 0.45), transparent 55%);
}

.about-gallery__open::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: var(--blue-deep);
  mix-blend-mode: multiply;
  opacity: 0.12;
}

/* Desktop has no dot indicator (the tile/peek/chevron-notch treatment
   already shows the "previous" photo directly) — only the mobile/tablet
   carousel (about.css's own mobile-flow block, further down) turns this
   on. Hidden by default rather than left unstyled: unstyled, this empty
   container's JS-appended `<button>` dots (about.js's `createGallery`)
   would render with native browser button chrome at the gallery box's
   default static top-left position. */
.about-gallery__dots {
  display: none;
}

.about-gallery__nav,
.lightbox__step {
  position: absolute;
  width: 1.49%;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  color: var(--paper);
  cursor: pointer;
  transition: transform var(--ease-ui), opacity var(--ease-ui);
}

.about-gallery__nav svg,
.lightbox__step svg,
.lightbox__close svg {
  display: block;
  width: 100%;
  height: auto;
}

.about-gallery__nav {
  top: 48.55%;
  transform: translateY(-50%);
}

.about-gallery__nav--prev {
  left: 80.31%;
}

.about-gallery__nav--next {
  left: 97.87%;
}

.about-gallery__nav:hover {
  opacity: 0.75;
}

.about-gallery__nav--prev:active {
  transform: translate(-3px, -50%);
}

.about-gallery__nav--next:active {
  transform: translate(3px, -50%);
}

/* ---------- Lightbox ---------- */

/* Deliberately undimmed: the reference keeps the copy column readable
   behind the overlay and lets the panels alone carry the layer change. */
.lightbox {
  position: absolute;
  inset: 0;
  z-index: 30;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--ease-fade), visibility 0s linear var(--ease-fade);
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--ease-fade);
}

.lightbox__backdrop {
  position: absolute;
  inset: 0;
  border: 0;
  padding: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  cursor: default;
}


/* The frame is the tile grown around the image on all four sides.

   It is the topmost panel, which is what keeps every other panel's shadow
   off it — a box-shadow spreads in every direction, and both the caption's
   and the next tab's would otherwise wash across this frame and drag it
   off-colour. Because its right edge meets the next tab's left edge
   exactly, it also clips that tab's shadow to three sides. Every panel in
   the open lightbox has to read as exactly --panel-open. */
.lightbox__frame {
  position: absolute;
  left: 38.86%;
  top: 13.63%;
  width: 28.28%;
  height: 69.89%;
  z-index: 3;
  background: var(--panel-open);
}

/* Inside the frame, so the frame's treatment layer paints over the image
   rather than under it. */
.lightbox__image {
  position: absolute;
  left: 2.9%;
  top: 3.02%;
  width: 94.2%;
  height: 94.03%;
  object-fit: cover;
  background: var(--blue-deep);
}

.lightbox__close {
  position: absolute;
  left: 66.22%;
  top: 10.2%;
  width: 1.44%;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  color: var(--paper);
  cursor: pointer;
  transition: opacity var(--ease-ui), transform var(--ease-ui);
}

.lightbox__close:hover {
  opacity: 0.75;
}

.lightbox__close:active {
  transform: scale(0.9);
}

/* Tabs hugging the image, drawn in the same colour so they read as part of
   the frame rather than as floating buttons. The next tab stops where the
   caption panel begins, so the two never seam, while its chevron carries on
   across the join — which is where the reference draws it. */
.lightbox__step {
  top: 43.06%;
  height: 11.08%;
  width: auto;
  /* Above the caption panel: the next chevron deliberately overflows its
     tab and across the join, so it has to paint over the caption, not
     under it. */
  z-index: 2;
  background: var(--panel-open);
  transform: none;
}

.lightbox__step svg {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

.lightbox__step--prev {
  left: 36.38%;
  width: 2.48%;
}

.lightbox__step--prev svg {
  left: 22.2%;
  width: 60.1%;
}

/* Mirrors the prev tab: same plate, same size, chevron inset to the same
   distance from the outer edge. It stacks above the caption, so it reads as
   a control sitting on the panel rather than a hole cut in it.

   It casts a soft shadow outward onto the caption, so the tab reads as
   standing proud of the panel. The shadow is written symmetrically and the
   image frame — which stacks above this tab and ends exactly where it
   begins — clips the left half away, leaving top, bottom and right. That is
   also why the frame never picks up a tint from it. */
.lightbox__step--next {
  left: 67.14%;
  width: 2.48%;
  box-shadow: 0.15cqw 0 1.2cqw 0.05cqw rgba(var(--shadow-ink), 0.5),
    0 0.4cqh 1cqw rgba(var(--shadow-ink), 0.35);
}

.lightbox__step--next svg {
  left: 16.9%;
  width: 60.1%;
}

.lightbox__step:hover svg {
  opacity: 0.75;
}

.lightbox__step--prev:active svg {
  transform: translate(-3px, -50%);
}

.lightbox__step--next:active svg {
  transform: translate(3px, -50%);
}

/* Occupies the band the gallery tile vacated, and reaches left far enough
   to meet the image frame with no ground showing between them. The extra
   width is absorbed by padding-left, so the text itself does not move.

   Horizontal padding is a percentage of the stage — which is what a
   percentage resolves against here — but vertical padding would resolve
   against the stage's *width* too, so it is given in vh. */
.lightbox__caption {
  position: absolute;
  left: 67.14%;
  top: 29.48%;
  width: 30.38%;
  height: 38.15%;
  padding: 3.6cqh 1.4% 3.6cqh 3.34%;
  display: flex;
  align-items: stretch;
  gap: 0.7cqw;
  background: var(--panel-open);
  /* An inset shadow down the left edge, where the panel meets the image,
     plus two stacked outer shadows — a tight one to seat the panel and a
     wide, very soft one beneath it, so the falloff reads as a gradient
     rather than a drawn edge. */
  box-shadow: inset 1.3cqw 0 1.7cqw -0.45cqw rgba(var(--shadow-ink), 0.5),
    0.3cqw 0.7cqh 1.2cqw rgba(var(--shadow-ink), 0.35),
    0.8cqw 1.8cqh 4cqw rgba(var(--shadow-ink), 0.45);
  font-family: var(--font-utility);
  font-size: var(--size-caption);
  line-height: var(--lh-caption);
  color: var(--paper);
  overflow: hidden;
}

/* Text-only mode (2026-09-13, on request): the PRISMA Services banner's
   "Read More" lightbox is the one exception that drops the image frame
   entirely — its slide was only ever a generated placeholder graphic, so a
   photo lightbox with a blank tile read worse than no image at all. Driven
   by `PRISMA_CAPTION.noImage` (about.js), which `renderLightbox()` turns
   into this class on `.lightbox` itself. The 3 sibling concept-card
   lightboxes (Communication Solutions/PRISMA Online/Inmate Applications)
   keep their own image frame — this targets `.lightbox`, not a shared
   base rule, so it touches nothing else.

   Higher specificity than `.lightbox__step`'s own mobile-flow rule further
   below (which sets `display: flex`), so this still hides the prev/next
   tabs on that tier too without a separate override there — with only one
   slide and no image, they have nothing left to step between. */
.lightbox.is-text-only .lightbox__frame,
.lightbox.is-text-only .lightbox__step {
  display: none;
}

/* A gallery with only one photo (EN/DE's own PRISMA Online/Inmate
   Applications — see ONLINE_SLIDES/APPS_SLIDES, about.js) has nothing to
   step between. `is-single-slide` (about.js, `renderLightbox()`) reuses
   the same two-class specificity trick `is-text-only` uses just above, so
   this also outranks `.lightbox__step`'s own mobile-flow rule (which sets
   `display: flex`) without a separate override there.

   Both tabs disappear entirely, plate included — changed 2026-09-21 from
   an earlier version that kept the next tab's blue plate in place (for
   the seam between the frame and the caption panel, see its own
   box-shadow comment above) on request; reverted on a follow-up request
   ("remove the right arrow background if there is only one image") since
   the frame and caption already sit flush at the same x-edge (67.14%)
   with the same panel colour, so the plate was never actually bridging a
   visible gap — only ever a UI control, not load-bearing for the seam. */
.lightbox.is-single-slide .lightbox__step {
  display: none;
}

/* Same trio (Communication Solutions/PRISMA Online/Inmate Applications):
   their real photos carry the important content — a phone held near the
   head, an app screenshot's own status bar/title — right at the top edge,
   which `.lightbox__image`'s default centered `cover` crop was cutting
   off in this taller, narrower frame (on report: "in the lightbox the top
   of the images should be visible"). `caption.topCrop` (about.js) drives
   this the same way `noImage` drives `is-text-only` above. */
.lightbox.is-top-crop .lightbox__image {
  object-position: center top;
}

/* Same width as the image frame's own old rectangle (28.28%, same 69.89%
   height ceiling), but now centered on the stage (`left: 50%` +
   `translate(-50%, -50%)` on both axes) rather than parked at the frame's
   old left edge — on request 2026-09-13 ("make it horizontally center on
   the page"), superseding the "left side until the X closing button"
   placement this comment used to describe. `.lightbox__close` gets its own
   is-text-only override just below to follow the panel's new right edge
   instead of the old frame's.
   Height is `auto` (capped at the frame's old height as a ceiling) rather
   than stretched to fill — a fixed-height flex column left the panel
   mostly empty below three paragraphs of text at the wider size, so the
   panel sizes to its own content and centers vertically in the lightbox
   instead (`top: 50%` + `translateY(-50%)`, on request: "the box should be
   in the middle"); narrower now, a full noImage caption is more likely to
   exceed the height ceiling than at the old width, but that is exactly
   what the scroll rail below already exists to handle.

   Typography styled like the about page's own text, on request ("it
   should look like the about page text") — the base rule's
   `--size-caption`/`--lh-caption` sizing is dropped in favour of
   `.about__copy`'s own `--size-body`/`--lh-body` (the same pair
   `.lightbox__caption strong` already uses for the bold lead, so lead and
   body now share one scale here instead of the lead reading a step larger
   than its own paragraph). The panel fill/shadow themselves went through
   two false starts before landing back close to the base rule's own
   treatment: first tried as fully panel-less text (no background, no
   shadow at all), which then read unreadably over the busy products grid
   showing straight through it, so a solid `--blue-deep` fill (the about
   section's own background colour) was tried on `.lightbox__backdrop`
   instead — full-bleed behind the entire lightbox — to give the text the
   same clean, empty ground `.about__copy` reads against; that in turn
   read as a full-screen takeover rather than a text box ("but just in the
   box"), then even confined to just this box, `--blue-deep` matches the
   page background exactly and the box read as invisible/borderless not a
   panel. Landed on request ("add the lightblue background with the usual
   box shadow") back on the base rule's own `--panel-open` fill and its
   ordinary two-layer floating shadow — just without the inset "seam"
   component of the base rule's own shadow, which was drawn assuming an
   image frame sits to this box's left and would read as a stray line down
   the middle of nothing here. */
.lightbox.is-text-only .lightbox__caption {
  left: 50%;
  top: 50%;
  width: 28.28%;
  height: auto;
  max-height: 69.89%;
  transform: translate(-50%, -50%);
  padding: 1.5cqh 1.2cqw;
  background: var(--panel-open);
  box-shadow: 0.3cqw 0.7cqh 1.2cqw rgba(var(--shadow-ink), 0.35),
    0.8cqw 1.8cqh 4cqw rgba(var(--shadow-ink), 0.45);
  font-size: var(--size-body);
  line-height: var(--lh-body);
}

/* `.lightbox__close`'s base position (66.22%) is tuned to sit just past the
   image frame's own right edge (67.14%) — an anchor that doesn't exist in
   this image-less mode. Now that the panel above is centered instead of
   parked at the frame's old left edge, re-anchor the close button to the
   panel's own new right edge (50% + 14.14% = 64.14%) using the same
   0.92%-inside offset the base rule used against the frame, so the button
   still reads as sitting just above the panel's top-right corner. */
.lightbox.is-text-only .lightbox__close {
  left: 63.22%;
}

/* The scrollable half of the caption — `.lightbox__caption` itself no
   longer scrolls (was a flat `overflow-y: auto`, showing the browser's own
   native scrollbar). The 2026-09-13 products restructure added captions
   several paragraphs long (PRISMA Services, PRISMA Online) and one with a
   paragraph, an 11-item bullet list *and* an icon strip (Inmate
   Applications) — none of which fit the fixed height above. Rather than
   the plain native bar, this reuses `.about-rail`'s own up/down-chevron +
   track + diamond-thumb control (`.lightbox__rail`, below) so a long
   caption scrolls with the same control language the about column already
   uses. `overflow-x: hidden` keeps a stray wide child (the icon strip
   wrapping) from opening a horizontal scrollbar too. */
.lightbox__caption-scroll {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
}

.lightbox__caption-scroll::-webkit-scrollbar {
  display: none;
}

/* ---------- Lightbox scroll rail ---------- */

/* `.about-rail`'s own control, rebuilt as a flex sibling of the scroll
   wrapper above rather than a `position: absolute` element sitting outside
   the column — the caption is a fixed-size panel the rail has to fit
   *inside*, not open ground beside it the way the about column has.
   `about.js`'s `syncLightboxRail` only reveals it (`.is-active`) once
   `.lightbox__caption-scroll` actually overflows — most captions
   (the three per-product ones, the short device captions) fit the panel
   outright and show no rail at all, unlike about's own column, which
   always overflows. Hidden by default via `display: none` rather than
   `visibility`, so an inactive rail claims no flex width and the scroll
   wrapper reads full-width in the common case. */
.lightbox__rail {
  position: relative;
  display: none;
  flex: 0 0 1.55cqw;
  color: var(--paper);
}

.lightbox__rail.is-active {
  display: block;
}

.lightbox__rail-step {
  position: absolute;
  left: 0;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  color: inherit;
  cursor: pointer;
  transition: opacity var(--ease-ui), transform var(--ease-ui);
}

.lightbox__rail-step svg {
  display: block;
  width: 100%;
  height: auto;
}

.lightbox__rail-step:hover {
  opacity: 0.7;
}

.lightbox__rail-step--up {
  top: 0;
}

.lightbox__rail-step--down {
  bottom: 0;
}

.lightbox__rail-step--up:active {
  transform: translateY(-2px);
}

.lightbox__rail-step--down:active {
  transform: translateY(2px);
}

.lightbox__rail-track {
  position: absolute;
  left: 50%;
  top: 2.84%;
  bottom: 3.14%;
  width: 1px;
  margin-left: -0.5px;
  background: var(--paper);
  cursor: pointer;
}

/* Same shorter-than-the-track travel band as `.about-rail__travel`, so the
   diamond never collides with either chevron — a fixed `cqh` inset rather
   than a percentage, since it has to read as the same visual clearance
   regardless of how tall any one caption's panel happens to be. */
.lightbox__rail-travel {
  position: absolute;
  left: 0;
  right: 0;
  top: 3.4cqh;
  bottom: 3.4cqh;
}

.lightbox__rail-thumb {
  position: absolute;
  left: 50%;
  top: 0;
  width: 0.66cqw;
  height: 1.81cqh;
  color: var(--paper);
  transform: translateX(-50%);
}

.lightbox__rail-thumb svg {
  display: block;
  width: 100%;
  height: 100%;
}

.lightbox__caption .lightbox__text {
  margin: 0;
}

/* The caption opens on a larger lead-in, so its first line sits on a taller
   line box than the rest of the paragraph. */
.lightbox__caption strong {
  font-size: var(--size-body);
  font-weight: normal;
  line-height: 2.75cqh;
}

/* A longer caption's own paragraph breaks (`caption.text` as an array —
   PRISMA Services/PRISMA Online, about.js) or a titled group's body text
   (Communication Solutions' Voice/Video/E-mail breakdown) — plain spacing
   under the bold lead line, no special treatment beyond a top margin. */
.lightbox__paragraph {
  margin: 0.9cqh 0 0;
}

/* A group's own sub-heading (Communication Solutions) — smaller than the
   caption's main `strong` lead above, so the hierarchy reads title > group
   label > body, matching this page's small-uppercase-label convention used
   elsewhere (`.products__label`, eyebrow tags). Full white
   (`--paper`), not the dimmed `--paper-dim` this used to carry — on
   request, every text in this lightbox should read white. */
.lightbox__group-title {
  display: block;
  margin-top: 1.4cqh;
  font-size: var(--size-caption);
  font-weight: normal;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  line-height: var(--lh-caption);
  color: var(--paper);
}

/* Bullet list under a card's caption text — the three device captions
   (IMS2, PS101, Wall-mounted Phone) and Inmate Applications' own concept
   caption all carry one; about's own caption doesn't set `bullets` and
   never renders this. No native markers, matching this page's own
   hand-built-rectangle convention for every other mark (eyebrow tag,
   meter) — a small square `::before`, not `list-style`. */
.lightbox__list {
  margin: 1cqh 0 0;
  padding: 0;
  list-style: none;
}

.lightbox__list li {
  position: relative;
  margin-top: 0.5cqh;
  padding-left: 1.1em;
}

.lightbox__list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.3em;
  width: 0.4em;
  height: 0.4em;
  background: var(--paper-dim);
}

/* Illustrative app/outlet icon strip — Inmate Applications' own caption
   only (about.js's `APPS_CAPTION.icons`). Small plated thumbnails, wrapped
   flex row rather than a fixed grid, since the exact count is content-
   driven and may grow. Each icon sits on its own `--paper` plate (matching
   `.products-card__more-go`'s "paper plate" language) so a logo supplied
   with a white background doesn't show a seam against this panel's blue. */
.lightbox__icons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6cqw;
  margin-top: 1.2cqh;
}

.lightbox__icon {
  display: block;
  width: 2.6cqw;
  height: 2.6cqw;
  padding: 0.3cqw;
  object-fit: contain;
  background: var(--paper);
}

/* Datasheet box — sits under the bullet list, in every product caption
   that sets a `pdf` field (about.js). Sized to its own content (not the
   full caption width), with a small gap between the label and the red
   plate — using `--panel-elevated` (base.css; the same "elevated
   surface" tone `.services-card--cta`'s hover uses) as this box's
   resting colour, so it reads as one step up from the caption panel
   underneath it (base --panel-open → elevated --panel-elevated → the
   red-on-paper mark floating on top), the layering the design
   guardrails call for.

   The box itself is not a control — it holds two independent links that
   do different things (view vs. download), so neither click target can
   be the whole box: the label opens the PDF to view, the red plate
   downloads it. Label text stays --paper (safe on either blue); the
   brand-red mark only ever sits on its own paper plate, per the
   brand-colour contrast rule (red is 1.6:1 on --panel-open, 4.7:1 on
   --paper). */
.lightbox__pdf {
  display: inline-flex;
  align-items: center;
  gap: 1.2cqw;
  margin-top: 1.6cqh;
  padding: 0.7cqh 0.9cqw;
  background: var(--panel-elevated);
  box-shadow: 0 0.25cqh 0.5cqw rgba(var(--shadow-ink), 0.3),
    0 0.7cqh 1.8cqw rgba(var(--shadow-ink), 0.22);
}

/* A flex box in its own right, matching `.lightbox__pdf-go`'s own exact
   height (both 1.6cqw — keep these two in sync if either is ever
   resized) — centering the text on the label's own cross-axis (flex)
   rather than on its line-box (font metrics) is what actually
   guarantees the two sit on one visual center line; relying on the
   outer row's `align-items: center` alone centers each child's *box*,
   and a text line-box reserves descender space this all-caps label
   never uses, so that box's center sits below the glyphs' own. Matching
   the two heights exactly (rather than leaving each to its own natural
   size) removes any doubt — confirmed by pixel-sampling a live
   screenshot: box/icon/text centers land within 1.5px of each other. */
.lightbox__pdf-label {
  display: flex;
  align-items: center;
  height: 1.6cqw;
  font-family: var(--font-utility);
  font-size: var(--size-caption);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--paper);
  text-decoration: none;
  cursor: pointer;
  transition: opacity var(--ease-ui);
}

.lightbox__pdf-label:hover {
  opacity: 0.75;
}

.lightbox__pdf-label:active {
  opacity: 0.55;
}

/* Same plate/shadow family as `.services-card__go`, at its own size for
   this box. Two stacked, low-opacity shadows (a tight contact shadow
   plus a soft wider one) instead of a flat `box-shadow`, so the plate
   reads as sitting proud of the box rather than pasted on it. */
.lightbox__pdf-go {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.6cqw;
  height: 1.6cqw;
  background: var(--paper);
  color: var(--brand-red);
  box-shadow: 0 0.2cqh 0.35cqw rgba(var(--shadow-ink), 0.35),
    0 0.5cqh 1.4cqw rgba(var(--shadow-ink), 0.25);
  cursor: pointer;
  transition: transform var(--ease-ui);
}

.lightbox__pdf-go svg {
  display: block;
  width: 56%;
  height: auto;
}

.lightbox__pdf-go:hover {
  transform: translateY(0.25cqh);
}

.lightbox__pdf-go:active {
  transform: translateY(0.4cqh) scale(0.94);
}

/* ---------- Mobile flow ---------- */

/* Same query as every other mobile/tablet block — see base.css. About's
   stage-only pieces (the fixed-height clipped copy column, the rail that
   navigates it, and the chevron-notch gallery clip-paths, all meaningless
   once the chevron art is hidden and text flows naturally) are replaced
   with plain stacked blocks. */
@media (max-width: 1024px),
(max-aspect-ratio: 1/1) {

  /* Reorders the section visually (gallery first, then title/copy) without
     touching hero.html's own DOM order — the desktop stage positions every
     child absolutely off explicit left/top values, so reordering the
     source would risk the stacking-order guarantees documented at the
     lightbox further down. `order` only affects flex layout, so this is
     inert on desktop where `.about-layer` isn't a flex container. */
  .about-layer {
    display: flex;
    flex-direction: column;
  }

  .about-gallery {
    order: -1;
  }

  .about__title,
  .about__copy {
    position: static;
    width: 100%;
    left: auto;
    top: auto;
  }

  .about__title {
    padding: 6vh 5vw 0;
  }

  /* `cqh`/`cqw` collapse to nothing under this tier's `container-type:
     normal` (`.hero`, above), so every size here is a plain `vh`/`vw`/`px`
     literal instead of the desktop rule's container-query units. No
     separate side inset is needed — `.about__motto` is a normal in-flow
     child of `.about__copy` here too (see its own comment), so it already
     sits inside that element's own `padding` below; `margin-bottom`
     matches the `2vh` gap `.about__copy p` uses between paragraphs, same
     rhythm. */
  .about__motto {
    margin: 0 0 2vh;
    padding: 1.8vh 4vw;
    gap: 4vw;
  }

  .about__motto-icon {
    width: 12vw;
    height: 12vw;
  }

  .about__motto-text {
    font-size: 0.95rem;
    line-height: 1.5;
  }

  .about__copy {
    height: auto;
    max-height: none;
    overflow: visible;
    padding: 2vh 5vw 4vh;
    -webkit-mask-image: none;
    mask-image: none;
    /* The desktop rule's `transition: mask-size ...` has nothing left to
       animate here — the rail that used to drive it is hidden on mobile,
       and the mask itself is `none` above. Left declared, it can still
       make the browser reserve a masking/compositing layer for this
       element even at rest, which is the kind of leftover GPU layer this
       project has already hit real rendering artifacts from elsewhere
       (see CLAUDE.md's compositing-layer gotchas). Killing it outright
       removes that layer instead of just leaving it idle. */
    transition: none;
  }

  /* `--lh-body` is unitless on mobile (base.css, a valid line-height ratio
     but not a valid margin length), so the desktop rule's
     `margin: 0 0 var(--lh-body)` silently collapses to `margin: 0` here —
     these paragraphs need their own, unit-bearing gap. */
  .about__copy p {
    margin: 0 0 2vh;
  }

  .about__copy p:last-child {
    margin-bottom: 0;
  }

  /* Its whole purpose — navigating a fixed-height clipped column — no
     longer applies once the copy flows naturally into the page scroll. */
  .about-rail {
    display: none;
  }

  /* The tile/peek chevron-notch clip-paths depend on chevron positions that
     don't exist on mobile (§ hero.css). Replace with a plain horizontally
     scrollable photo strip instead of trying to preserve the notch shape;
     tapping a photo still opens the existing lightbox unchanged. */
  .about-gallery {
    /* Was `static`; now the containing block for the overlaid prev/next
       carousel controls and the dot indicator below (both absolutely
       positioned children, siblings of `.about-gallery__open` in
       hero.html — see "The about choreography, mobile carousel" below).
       Doesn't affect the horizontal-scroll/scroll-snap behavior above. */
    position: relative;
    display: flex;
    gap: 0;
    width: 100%;
    /* No side gutter: each photo runs full-bleed to the viewport edge
       instead of sitting inset with visible neighbor peeks. */
    padding: 0 0 4vh;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: 0;
    /* Without this, the browser has to guess gesture intent per-touch: a
       swipe that starts on this strip but is meant to scroll the page down
       can get captured as a horizontal gallery swipe instead. `pan-x`
       claims only the horizontal axis for the gallery, so a vertical swipe
       here falls through to the page while a horizontal one still snaps
       between photos. */
    touch-action: pan-x;
    overscroll-behavior-x: contain;
    -webkit-mask-image: none;
    mask-image: none;
  }

  .about-gallery__peek {
    display: none;
  }

  /* Purely a background panel behind `.about-gallery__open` on desktop —
     the two are siblings, not parent/child (hero.html), so the flex sizing
     belongs on `__open` itself; the tile has nothing left to show once its
     clip-path notch is gone. */
  .about-gallery__tile {
    display: none;
  }

  .about-gallery__open {
    /* Stays a containing block (`relative`, not `static`) so its own
       `::before`/`::after` tint overlays (`inset: 0`) stay confined to the
       photo instead of falling through to the page's root containing
       block — found as a large stray translucent rectangle (page-top,
       ~one document-flow's worth tall) bleeding down over the title/copy
       text below it. `position: relative` still behaves as a normal flex
       item, so nothing else about the mobile strip layout changes.

       `left`/`top` need their own reset here, on top of `position`: the
       desktop rule sets `left: var(--photo-left)` (82.32%) /
       `top: var(--photo-top)` for absolute placement, and those were inert
       under `static` (which ignores left/top entirely) but become a real
       *relative* offset the moment `position` switches to `relative` —
       found shoving the photo ~329px rightward (82.32% of the gallery's
       400px content box) off its padding-inset resting spot, which is what
       both broke the centering and forced genuine horizontal overflow
       (`scrollWidth` 779px against a 500px viewport), surfacing as a
       visible native scrollbar. Resetting both back to `auto` here
       restores the plain in-flow flex position: confirmed via probe,
       `scrollWidth` drops back to exactly match `clientWidth` (no
       overflow, no scrollbar) and the photo sits flush at the gallery's
       own (now zero) padding gutter again. */
    position: relative;
    left: auto;
    top: auto;
    flex: 0 0 100vw;
    width: 100vw;
    height: 75vw;
    scroll-snap-align: start;
  }

  /* Ken Burns: a slow, continuous zoom on the gallery photo, mobile/tablet
     only (on request) — desktop's `.about-gallery__photo` stays fully
     static, only gaining a transform on hover/active. `@keyframes` is
     declared inside this same media query so the animation exists at all
     only once the flow layout is active; `prefers-reduced-motion`
     (base.css) already zeroes every `animation-duration` site-wide, so no
     extra guard is needed here. `alternate` (not a one-way zoom-then-cut)
     keeps the loop seamless since there's no reset frame to jump back to. */
  @keyframes about-gallery-ken-burns {
    from {
      transform: scale(1);
    }
    to {
      transform: scale(1.15);
    }
  }

  .about-gallery__photo {
    animation: about-gallery-ken-burns 16s ease-in-out infinite alternate;
    /* Crossfade on slide change (on request — "when the images are
       changing put a little fade"), mobile/tablet only: `about.js`'s
       `gallery.render` toggles `.is-fading` (opacity 0) before swapping
       `src`, then removes it, so the swap itself happens while invisible
       instead of popping instantly. `160ms` matches `--ease-ui` (the
       site's standard interactive-feedback duration); kept as a literal
       here since `about.js` has to know the same number in milliseconds
       to time its `src` swap to the midpoint of the fade — if this is
       ever retuned, update the `PHOTO_FADE_MS` constant there too. */
    transition: transform var(--ease-ui), opacity 160ms ease;
  }

  .about-gallery__photo.is-fading {
    opacity: 0;
  }

  /* Mobile/tablet carousel controls (on request — "the about gallery
     should be a carousel"). Desktop's bare-chevron nav (`.about-gallery__nav`,
     the base rule above) sits just outside the photo, in the open
     background next to the chevron notch; there's no equivalent open
     ground here (the photo now runs edge-to-edge, see the full-bleed fix
     above), so this tier gets its own plated, circular version overlaid
     directly on the photo instead of hiding it outright. Positioned
     against `.about-gallery` (now `position: relative`, above), not
     `.about-gallery__open` — the buttons are siblings of the photo
     button in hero.html, not descendants, so the gallery wrapper is the
     nearest available containing block. `top: 37.5vw` is half the
     photo's own height (`75vw` on phone; tablet's `aspect-ratio: 4/3` on
     a 100%-wide box computes to the same 0.75x-of-width height) measured
     from the gallery's own top edge, which the photo's top is flush
     against (a flex item with an explicit height doesn't stretch, so it
     sits at the row's cross-start = top).

     Selector is `.hero .about-gallery__nav`, not the bare class: the
     laptop-tier block up top (`@media (max-aspect-ratio: 1.85/1)`) also
     matches a portrait phone (aspect well under 1.85) and sets `.hero
     .about-gallery__nav { width: 1.19%; }` / `.hero .about-gallery__nav--next
     { left: 98.71%; }` at specificity (0,2,0) — higher than a bare
     `.about-gallery__nav` (0,1,0) here, so it would win regardless of
     this rule sitting later in the file. Matching that same `.hero`-
     prefixed specificity is what lets source order decide instead,
     confirmed via computed-style probe: without this prefix the button
     rendered at a near-invisible sliver of a percent width instead of
     the 44px square below.

     Square, not round (on request, "not circle but square as in the
     gallery element") — matching the lightbox's own `.lightbox__step`
     tab plate, the site's one other "plated chevron" control; nothing
     on this site is otherwise rounded except the dot indicator below. */
  .hero .about-gallery__nav {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 37.5vw;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    background: var(--panel-open);
    box-shadow: 0 0.4vh 1.6vw rgba(var(--shadow-ink), 0.35);
    z-index: 2;
  }

  .hero .about-gallery__nav--prev {
    left: 3vw;
  }

  .hero .about-gallery__nav--next {
    left: auto;
    right: 3vw;
  }

  .about-gallery__nav svg {
    width: 40%;
    height: auto;
  }

  /* Same small directional nudge as the desktop chevrons and the shared
     lightbox's own `.lightbox__step` tabs use on `:active` — reported
     back as "a weird popping effect" when this was a whole-plate
     `scale(0.92)` shrink instead (on request: "use the arrows like in
     the header", i.e. this exact nudge). `.hero`-prefixed, not the bare
     `.about-gallery__nav--prev:active` desktop rule (further up this
     file) — that rule is equal specificity to this block's own resting
     `.hero .about-gallery__nav` transform above and sits earlier in the
     file, so without matching that same `.hero` prefix here, the resting
     rule would win the tie by source order and silently swallow the
     active nudge on mobile/tablet. */
  .hero .about-gallery__nav--prev:active {
    transform: translate(-3px, -50%);
  }

  .hero .about-gallery__nav--next:active {
    transform: translate(3px, -50%);
  }

  /* Dot indicator: which of the N photos is current, and a direct-jump
     tap target for each. Built by `about.js` (one `<button>` per slide,
     `.is-active` on the current one) into this empty container —
     `about.css` only styles it, so the dot count never needs to match a
     hardcoded value here. Absolutely positioned (not in-flow) so it
     floats over the bottom of the photo rather than adding its own
     band of vertical space below it. */
  .about-gallery__dots {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(4vh + 2vh);
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 2vw;
    z-index: 2;
  }

  .about-gallery__dot {
    padding: 0;
    border: 0;
    -webkit-appearance: none;
    appearance: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--paper);
    opacity: 0.5;
    box-shadow: 0 0.2vh 0.8vw rgba(var(--shadow-ink), 0.35);
    cursor: pointer;
    transition: opacity var(--ease-ui), transform var(--ease-ui);
  }

  .about-gallery__dot.is-active {
    opacity: 1;
    transform: scale(1.35);
  }

  .about-gallery__dot:active {
    transform: scale(0.85);
  }

  /* Lightbox: stack image above caption instead of the desktop's
     side-by-side frame + tab + caption panel arrangement. */
  .lightbox__frame,
  .lightbox__image {
    position: static;
    width: 100%;
    height: auto;
    left: auto;
    top: auto;
  }

  /* Two independent bugs compound here, both needed to make the pdf
     box (`.lightbox__pdf-label`/`.lightbox__pdf-go`) clickable on mobile:

     1) `overflow: hidden` (desktop base rule, ~line 1007) is inherited
     unchanged into mobile. `.lightbox` is `display: flex; flex-direction:
     column` here (a few lines below) with a fixed viewport height, and
     frame+caption's combined content is taller than that — but a flex
     item's *automatic* minimum main size is content-based only when its
     own `overflow` is `visible`; with anything else (`hidden`, as
     inherited here) the spec drops that minimum to 0, so the flex
     algorithm freely shrinks the caption below its own content height to
     make everything fit, and the caption's own `overflow: hidden` then
     silently clips the overflow — which is the `.lightbox__pdf` box,
     since it's the last child. It never renders at all in the clipped
     region; confirmed by comparing `.lightbox__caption`'s rendered height
     with `overflow: hidden` (275px) against `overflow: visible` (353px,
     matching its content). `overflow: visible` below overrides that
     inherited value so the caption sizes to its full content instead of
     shrinking, and `.lightbox`'s own `overflow-y: auto` (a few lines
     below) becomes the thing that scrolls the excess, as intended.

     2) Even with the content actually laid out, `.lightbox__caption`
     needs to stay a positioned element (not `static` like frame/image
     above) because `.lightbox__backdrop` below becomes `position: fixed`,
     which always opens its own stacking context regardless of z-index —
     a `static` caption falls into painting-order step 3 (in-flow,
     non-positioned) while the backdrop lands in step 6 (positioned), so
     the backdrop paints/hit-tests *above* the caption despite being
     transparent and DOM-first, swallowing every tap meant for the pdf
     links and closing the lightbox instead. z-index 31 matches
     `.lightbox__step`/`.lightbox__close` below, which is why those two
     were never affected by either bug.

     Verified both are independently required: `overflow: visible` alone
     (still `position: static`) still resolves a tap on the label to
     `.lightbox__backdrop`; `position: relative` alone (still `overflow:
     hidden`) still clips the pdf box out of the hit-testable area. Only
     both together resolve a tap to the actual `<a>`. */
  .lightbox__caption {
    position: relative;
    z-index: 31;
    display: block;
    overflow: visible;
    width: 100%;
    height: auto;
    left: auto;
    top: auto;
    /* The desktop shadow set (base rule, ~line 1012) includes an inset
       shadow down the left edge, drawn assuming the image frame sits
       there. Mobile/tablet stack the frame above the caption instead, so
       that edge is open ground, not a seam — without this reset the inset
       shadow reads as a stray dark bar down the panel's left side. */
    box-shadow: none;
    /* The desktop base rule's own padding (~line 1014) is
       `3.6cqh 1.4% 3.6cqh 3.34%` — the `cqh` terms collapse to 0 here
       (`.hero` drops to `container-type: normal` in mobile flow, which
       invalidates every container-query length on any descendant with no
       other query container above it — the same reason `.lightbox__pdf`
       needed its own vh/vw override below), so the panel had no top/bottom
       breathing room at all and only a sliver of side inset. `width: 100%`
       above is border-box, so this padding eats into the panel's own
       interior rather than growing its outer footprint. */
    padding: 2.4vh 5vw;
  }

  /* The desktop `is-text-only` rule's `top: 50%` + `translateY(-50%)`
     centering only makes sense against `position: absolute` — on a
     `position: relative` element (the mobile rule just above) `top`/`left`
     shift it *from* its in-flow position instead of placing it outright,
     which would shove the panel upward by half the lightbox's height.
     Reset back to plain in-flow stacking, same as every other mobile
     caption. */
  .lightbox.is-text-only .lightbox__caption {
    left: auto;
    top: auto;
    width: 100%;
    max-height: none;
    transform: none;
  }

  /* The rail exists to replace a scrollbar on the caption's own fixed-height
     panel — on mobile the panel has no fixed height (`height: auto` above)
     and it's `.lightbox` itself that scrolls instead (its own `overflow-y:
     auto`, a few rules below), so there's nothing for the rail to control
     here. */
  .lightbox__caption-scroll {
    overflow: visible;
    height: auto;
  }

  .lightbox__rail {
    display: none;
  }

  /* Opaque on mobile/tablet only (on request — "i dont want to see any
     of the homepage in the background" with the lightbox open). Desktop's
     own backdrop stays deliberately undimmed (`background: none`, base
     rule above) — this tier's own header/close-bar/frame/caption never
     tile together edge-to-edge the way desktop's absolutely-positioned
     panels do (there's a `5vw` side gutter on `.lightbox`'s own padding,
     the close bar and frame/caption all sit inset within it, and there's
     open space below a short caption before `.lightbox`'s own bottom
     padding), so without a fill of its own, every one of those gaps let
     the homepage section behind show straight through. `--blue-deep` (the
     page's own ground colour) rather than `--panel-open` — this needs to
     read as "the page is gone," not as another panel; a `--panel-open`
     backdrop was tried for a single caption box elsewhere on this site
     and reverted for reading like a stray colour block, an unrelated
     precedent worth knowing before reaching for it here too. */
  .lightbox__backdrop {
    position: fixed;
    background: var(--blue-deep);
  }

  /* Top padding is fixed px, not vh, matching `.mobile-nav`'s own real
     height (84px, see its own comment in hero.css) plus room for
     `.lightbox__close` below it (44px button + a 10px gap on each side) —
     a vh value drifted shorter than the header at realistic phone
     heights, so the close button (and the frame above it) rendered
     underneath the fixed header instead of clear of it. */
  .lightbox {
    position: fixed;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    padding: 148px 5vw 4vh;
    gap: 0;
  }

  .lightbox__frame {
    box-shadow: none;
  }

  .lightbox__image {
    aspect-ratio: 4 / 3;
  }

  /* Communication Solutions only (`COMMS_CAPTION.mobileCenter`, about.js
     — added 2026-09-21, on request: "put the images in the center and
     fit"; `object-fit` corrected from `contain` to `cover` the same day
     on a same-session follow-up, "it should fill the square box" — a
     `contain` first pass showed the full photo but letterboxed it on
     empty `--blue-deep` bars either side, which read as dead space
     rather than a filled box). Its two photos are portrait (~0.667 w/h)
     inside this tier's landscape 4:3 frame; the shared `cover` +
     `.is-top-crop`'s `center top` (still in effect for this same
     caption, see above) already fills the box edge to edge with no
     letterboxing, but crops from the very top of the frame down, cutting
     off most of the photo's actual subject. `object-position: center`
     here (equal specificity to `.is-top-crop`'s own rule, but declared
     later in this file, so it wins on this one tier) keeps `cover`'s
     fill but re-centers the crop vertically instead of anchoring it to
     the top. Scoped to this one caption via the `mobileCenter` flag
     rather than widened to every `topCrop` caption — PRISMA Online's/
     Inmate Applications' own photos are closer to the frame's own
     aspect and still read fine top-cropped. */
  .lightbox.is-mobile-center .lightbox__image {
    object-position: center;
  }

  /* `.lightbox` (position: fixed; inset: 0) is the containing block, same
     as desktop's own `position: absolute` steps — switching from `fixed`
     to `absolute` here makes the buttons scroll along with the frame/
     caption content instead of staying pinned to the viewport (where they
     could drift onto the caption once a tall caption makes the panel
     scroll). `top` is `.lightbox`'s own 148px top padding plus half the
     frame's rendered height (frame content width is 90vw after the
     lightbox's 5vw side padding; `.lightbox__image`'s 4:3 aspect-ratio
     makes that 90vw * 3/4 = 67.5vw tall), landing the step's center on the
     image's own vertical center. */
  .lightbox__step {
    position: absolute;
    top: calc(148px + 33.75vw);
    transform: translateY(-50%);
    height: 44px;
    width: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 31;
  }

  .lightbox__step--prev {
    left: 2vw;
  }

  .lightbox__step--prev svg,
  .lightbox__step--next svg {
    position: static;
    width: 60%;
    transform: none;
  }

  /* Bug: without these, pressing either step tab made the chevron jump
     upward and pop back, reported as "a popping animation [on] the white
     arrow." Desktop's own `.lightbox__step--prev:active svg { transform:
     translate(-3px, -50%); }` (base rule, above) is written assuming the
     svg is still `position: absolute` with `top: 50%` needing that
     `-50%` to stay centered — but this tier's svg is `position: static`
     and already centered by `.lightbox__step`'s own flex layout, so
     applying that same `-50%` on top shifted it up by half its own
     rendered height. It still won, despite this file's own `transform:
     none` reset just above, because `.lightbox__step--prev:active svg`
     (a class + `:active` + `svg`) outranks a plain `.lightbox__step--prev
     svg` at equal source order. Matching that specificity here (adding
     `:active`) is what lets a mobile-appropriate transform — a small
     horizontal-only nudge, no `-50%` — win instead. */
  .lightbox__step--prev:active svg {
    transform: translateX(-3px);
  }

  .lightbox__step--next:active svg {
    transform: translateX(3px);
  }

  .lightbox__step--next {
    left: auto;
    right: 2vw;
    box-shadow: none;
  }

  /* Full-width bar, not a small floating chip (on request, from a
     hand-drawn reference: a strip spanning the same left/right inset as
     the frame/caption below it, flush against its top edge, X sitting at
     the bar's own left). `top: 84px` matches `.mobile-nav`'s real height
     budget (the same literal `.hero`'s own padding-top uses); `height:
     64px` makes the bar's bottom land exactly on `.lightbox`'s own
     `148px` top padding below — i.e. the bar fills the header-to-content
     gap completely, no seam between it and the frame/caption. Filled
     with `--panel-open` — the same background every lightbox panel
     (frame/caption) uses, so "the background color is always the same
     as the element which is opened" — plus the panel's own floating
     shadow, so the bar reads as an extension of that panel rather than a
     control floating loose over whatever happens to sit behind it
     (reported: "the X is over some random element that is on the
     homepage" — the lightbox backdrop itself is intentionally undimmed,
     so an unfilled icon had nothing of its own to visually anchor to).
     Square corners, not rounded (on request) — matches the flat,
     unrounded frame/caption it sits flush against below. Desktop/laptop
     keep the original bare-icon treatment unchanged; this bar is
     mobile/tablet only.

     `position: absolute`, not `fixed` — same reasoning `.lightbox__step`
     already documents above: `.lightbox` (`position: fixed; inset: 0`)
     is this bar's containing block either way, so the numbers below land
     in the same place on first paint, but `fixed` would stay pinned to
     the *viewport* while `.lightbox`'s own `overflow-y: auto` scrolled
     the frame/caption underneath it — reported as the bar "stay[ing]
     still" while "the element is going under it." `absolute` scrolls the
     bar together with that content instead, so it stays visually
     attached to the panel's own top edge at any scroll position. */
  .lightbox__close {
    position: absolute;
    left: 5vw;
    right: 5vw;
    top: 84px;
    width: auto;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    padding: 0 4vw;
    background: var(--panel-open);
    box-shadow: 0 6px 16px -4px rgba(var(--shadow-ink), 0.55);
    z-index: 31;
  }

  .lightbox__close svg {
    width: 22px;
    height: auto;
  }

  /* The desktop-only `is-text-only` override (base rule, ~line 1125) sets
     `left: 63.22%` to park the close button above the centered text-only
     panel's own right edge — a plain `%`, not `cqw`, so it never had a
     mobile reset and its higher specificity (two classes vs. this block's
     one) let it win here too, at any viewport width. Confirmed as the
     cause of "on product Read More the X is not on the top-left side":
     63.22% of a 390px-wide phone is ~247px in, nowhere near the 5vw this
     block sets for every other lightbox. Re-anchor it back to the shared
     bar position. */
  .lightbox.is-text-only .lightbox__close {
    left: 5vw;
  }

  /* Every gap between the caption's own text blocks (bold lead, paragraph
     breaks, group titles, the bullet list, the icon strip) is `cqh`/`cqw`
     on the desktop base rules above (~lines 1272-1331) — same collapse as
     `.lightbox__caption`'s own padding just above: invalid under mobile's
     `container-type: normal`, so every paragraph/list item/group title sat
     flush against the one before it with no visible gap at all (reported
     as the caption text being hard to read). Plain vh values restore the
     spacing, on request ("between the paragraphs add also a little space
     everywhere"). */
  .lightbox__paragraph {
    margin: 1.8vh 0 0;
  }

  .lightbox__group-title {
    margin-top: 2.4vh;
  }

  .lightbox__list {
    margin-top: 1.8vh;
  }

  .lightbox__list li {
    margin-top: 1vh;
  }

  .lightbox__icons {
    margin-top: 2vh;
    gap: 3vw;
  }

  /* The desktop rule sizes this box entirely in `cqw`/`cqh` — `.hero`
     drops to `container-type: normal` in mobile flow (§ hero.css), which
     invalidates those units, so every one of them silently collapsed to
     the property's own initial value here (`auto`/`0`) instead of just
     scaling down: the box shrank to bare text with almost no padding and
     the icon plate all but disappeared. Fixed sizes, not a scaled-down
     `cqw` value, since there's no container left to scale against. */
  .lightbox__pdf {
    gap: 4vw;
    padding: 1.6vh 4vw;
    margin-top: 2vh;
  }

  .lightbox__pdf-label,
  .lightbox__pdf-go {
    height: 44px;
  }

  .lightbox__pdf-go {
    width: 44px;
  }

  .lightbox__pdf-label {
    font-size: 1rem;
  }
}

/* Tablet tier: same flow structure as phone, above, with content running
   near-full-width (a flat 32px side gutter, not a centered narrower
   column — a wide, centered `max-width` column was tried first and
   reverted on request: it read as too much dead margin on a 768-1024px
   screen). The gallery itself is the one exception to that 32px gutter:
   its photo runs fully edge-to-edge (no side gutter at all, same as the
   phone tier below), on request ("the about image should be full width
   until the edge of the viewport"). First pass, no tablet reference
   exists yet. */
@media (min-width: 768px) and (max-width: 1024px) {
  .about__title {
    padding: 8vh 32px 0;
  }

  .about__motto {
    margin: 0 0 2.4vh;
    padding: 2vh 32px;
    gap: 24px;
  }

  .about__motto-icon {
    width: 76px;
    height: 76px;
  }

  .about__motto-text {
    font-size: 1.05rem;
  }

  .about__copy {
    padding: 3vh 32px 5vh;
  }

  .about__copy p {
    margin: 0 0 2.4vh;
  }

  /* Equal top/bottom padding, not a min-height band + align-items —
     tried that first (a band filling the ~96px-header-minus-viewport
     space, photo centered in it with align-items) and it was
     provably symmetric (267.5px measured both sides via a
     getBoundingClientRect probe) but read as too much dead blue space
     on request, so reverted to plain padding: a literal, same value on
     both sides is symmetric by construction and needs no flex-centering
     logic to get there, at a far more modest size. Horizontal padding is
     zero (not the 32px every other flow block uses) so the photo itself
     reaches both viewport edges. */
  .about-gallery {
    gap: 0;
    padding: 6vh 0;
    scroll-padding-inline: 0;
  }

  .about-gallery__open {
    flex: 0 0 100%;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
  }

  /* Same header-clearance arithmetic as the phone tier above, scaled to
     the tablet header's own real height (96px, matching `.hero`'s own
     tablet-tier padding-top in hero.css): 96 + 10 gap = 106px step-tab
     top; 96 + 10 + 52 + 10 = 168px total lightbox top padding. The close
     bar itself runs from the header (96px) straight to that same 168px,
     so its own height is just the difference (72px) — no separate gap
     budget, since (unlike the step tabs) it's meant to sit flush against
     both the header above and the frame/caption below. */
  .lightbox {
    padding-top: 168px;
  }

  .lightbox__step {
    top: calc(168px + 33.75vw);
    width: 52px;
    height: 52px;
  }

  .lightbox__close {
    top: 96px;
    height: 72px;
  }

  .lightbox__close svg {
    width: 26px;
  }
}