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

   Clicking `products` continues the same chain about/services already run —
   one chevron travelling onto the one behind it — one lane further: the back
   chevron (already carrying the `products` label) travels onto quad, the
   fourth chain chevron added for `events` (see `.hero.is-products` in
   hero.css). The front and mid chevrons still vanish instantly rather than
   travelling, matching their exit for every other section that doesn't own
   their lane. Back itself stays visible and parked here through `events`
   too — events.css is what first hides it, once quad starts travelling in
   its turn. `contact` no longer plays any part of back's own travel in
   reverse (that now belongs to `events`/`contact` and quad — see
   events.css/contact.css); back simply un-shifts to rest, invisibly, once
   it is hidden. hero.css still plays the fourth (`.arrow--far`) chevron's
   arrival the same way it does for `about`/`services` — that part of the
   curtain is shared and unrelated to quad, a separate element.

   2026-09-13 restructure: the page used to be a flat 3-card device grid
   (IMR2/IMR3/Community Phone) with a top-left title + lead paragraph, in the
   narrower 38.5%-67% band. Replaced on request with a deeper, one-grid
   catalogue matching a new wireframe (`references site/sections/products/
   products_new_arrangement.png`) and new copy (`Products new texts.docx`):
   a full-width PRISMA Services banner, a 3-card row of software/service
   offerings (Communication Solutions, PRISMA Online, Inmate Applications),
   a plain "devices" label, then the original 3 device cards — renamed
   Mobile-IMS2 / Tablet-PS101 / Wall-mounted Phone but otherwise the same
   photos/bullets/datasheets as before (see about.js). The old
   `.products__head`/`.products__lead` paragraph (stale copy about vehicle
   fleet SIMs/IoT, left over from a generic template and never matching this
   site's correctional-communications subject) is gone — the banner card now
   carries that introductory role instead.

   No pixel-precise reference mockup exists for this new arrangement — only
   the rough wireframe above, which has no colour/type/sizing information —
   so the whole grid below is a first-pass placeholder pending a
   render-and-measure pass, the same status events.css's own grid carries.
   All 8 grid children (1 banner + 3 solutions cards + 1 label + 3 device
   cards) reuse the single `.products-card`/`.products__grid` bento system
   already proven in services.css/events.css (`grid-column: span N` on an
   otherwise uniform grid) rather than inventing a second layout mechanism. */

.hero {
  /* --products-left is tied to --shift-back (hero.css's travel for the back
     chevron on `.is-products`, see hero.css's own --arrow-back comment for
     the retuning history) rather than a bare literal: the chevron shifts
     left on entry and leaves open ground between itself and the content
     block if the block doesn't follow, so the block's own left edge moves
     by the same amount the chevron does. Live reference, not a one-off copy
     of the number — retuning --shift-back moves this with it.

     --products-right widened from the old flat 67% to match services'/
     events'/contact's own shared right edge (`--copy-left + --copy-width`,
     about.css) — the old narrower band was sized for a 3-card row; the new
     catalogue needs the same width every other section's bento grid gets,
     and there's no longer a reason (the old, now-removed, second gallery)
     to keep it narrower. */
  --products-left: calc(38.5% + var(--shift-back));
  --products-right: calc(var(--copy-left) + var(--copy-width));
  /* 18%, matching services'/about's own head/copy-column top (--bento-top),
     on request — was 20%, leaving a visibly bigger gap under
     `.products__title` than services' own title-to-lead gap. */
  --products-top: 18%;
  --products-gutter: 0.9cqw;
  --products-card-pad: 1.2cqw;
  /* Bottom padding only, smaller than --products-card-pad — the card's
     horizontal padding (both --products-card-name/__body's left/right
     margins) is untouched, only the gap below the last line shrank. */
  --products-card-pad-bottom: 0.6cqw;
}

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

   Laptop-only geometry (aspect <= 1.85:1 — see about.css's own "Laptop
   tier" block for the fuller rationale). Outside this range the section
   falls back to --products-right just above. Mirrors services'/events' own
   "+3%"/"+2%" laptop widening term rather than a bare literal, so it stays
   tied to the same live right-edge reference. */
@media (max-aspect-ratio: 1.85/1) {
  .hero {
    --products-right: calc(var(--copy-left) + var(--copy-width) + 3%);
  }
}

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

.is-products .products-layer {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--ease-fade) var(--delay-settle),
    visibility 0s linear var(--delay-settle);
}

/* `products` is now the section that travels — its own back chevron carries
   its label onto quad, the same self-hiding-as-current-section pattern
   `.hero-nav__link--about`/`--services` already use for `about`/`services`.
   hero.css's unconditional rule now delays `opacity` for the
   *leaving* direction (so the label stays hidden until the chevron has slid
   home, rather than fading back into view mid-flight), so this rule restates
   the eased transition to keep entering's fade-out-while-travelling
   unchanged. */
.is-products .hero-nav__link--products {
  opacity: 0;
  pointer-events: none;
  transition: transform var(--ease-move), opacity var(--ease-move),
    color var(--ease-ui);
}

/* ---------- No-theatre exit ---------- */

/* Front's base rule (hero.css) lists `opacity` in its transition, which is
   what lets it fade for `services` — here that has to be overridden back to
   `none`, or it would ease exactly like `services` does. */
.is-products .arrow--front {
  opacity: 0;
  transition: none;
}

/* Mid's base rule (hero.css) now lists `opacity` too, so it can ease in
   arriving here backward from services — that would also ease this vanish
   unless overridden back to instant, matching `.arrow--front` just above. */
.is-products .arrow--mid {
  opacity: 0;
  transition: none;
}

/* ---------- Header ---------- */

/* Same top-left convention `.about__title`/`.services__title` use. */
.products__title {
  position: absolute;
  left: var(--products-left);
  top: 11%;
  width: calc(var(--products-right) - var(--products-left));
  margin: 0;
  font-family: var(--font-utility);
  font-size: var(--size-heading);
  font-weight: normal;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--paper);
}

/* ---------- Card grid ---------- */

/* One grid holds all 8 children (banner, 3 solutions cards, the "devices"
   label, 3 device cards) — the same bento approach services.css/events.css
   already use (`grid-column: span N` on individual cards), rather than a
   second absolutely-positioned block for the device row. Row heights are a
   first-pass placeholder (see file header) — banner shortest, the label
   thinnest, the two 3-card rows carrying most of the height. */
/* `height: auto`, not `var(--products-height)` — on request ("move the
   grid up until the read more button... you have stretched the grid top
   to bottom, I said just move up... the size of the grids should have
   stayed"). A fixed grid height combined with `fr` rows means shrinking
   one row's `fr` share hands that freed space to every other `fr` row,
   stretching them — not what was wanted. Every row below the banner is
   now a fixed `cqh` length instead of a fr share of a fixed total, so
   each keeps the exact size it had before this change; only row 1 (an
   `auto` track, sized to its own content) changed, which is what actually
   moves rows 2-4 up — the grid's own total height is now whatever the sum
   of its rows comes to, not a claimed fraction of `.hero`. The `cqh`
   values below are that same prior proportion
   (`--products-height: 72%` × each row's old `fr` share of the old
   `2.2fr 0.55fr 0.22fr 2.2fr` template, `72% ≈ 72cqh` in this container),
   converted once and then left as plain literals — retune them directly
   if a row ever needs to change size again, the same way every other
   fixed-`cqh` value on this page is tuned. */
.products__grid {
  position: absolute;
  left: var(--products-left);
  top: var(--products-top);
  width: calc(var(--products-right) - var(--products-left));
  height: auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Row 1 (the banner) is `auto` so it hugs its own content (title +
     paragraph + Read More button) exactly, rather than claiming a fixed
     share that left empty space below the button. This also sidesteps
     the squeeze bug a `minmax(0, Nfr)` track hit earlier (documented
     above): an `auto` track's minimum size comes from its content, so it
     can't be squeezed thinner than the paragraph needs the way an
     explicit `minmax(0, …)` track could. `.products-card--banner` also
     switches to `justify-content: flex-start` (overriding
     `.products-card--text`'s own `center`) so its content hugs the row's
     top edge instead of centering within it. */
  grid-template-rows: auto 7.7cqh 3.1cqh 30.7cqh;
  gap: var(--products-gutter);
}

/* Opaque and square-cornered, like every other panel on the site.
   position: relative makes it the containing block for `__open`'s
   full-card overlay below — the whole card (image, title, body) is one
   click target, not just the photo. */
.products-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--panel-gallery);
  box-shadow: 0 0.5cqh 0.9cqw rgba(var(--shadow-ink), 0.22);
}

/* Cards with no thumbnail image: the banner plus the 3 solutions-row cards
   (Communication Solutions/PRISMA Online/Inmate Applications) — none of
   them physical devices, so none get product photography on the card
   face. (Their own real photos, added 2026-09-13, live in the lightbox
   only — kept off the card face on request: "on the main page the photo
   shouldn't be visible".) Centering their (now short) content vertically
   reads better than the top-aligned flow `.products-card__name`'s own
   margin gives every photo card, where the image above already occupies
   the top of the box. */
.products-card--text {
  justify-content: center;
}

/* The PRISMA Services banner — the catalogue's own anchor item, spanning
   every column on row 1. It carries the docx source's own first paragraph
   in full (about.js's PRISMA_CAPTION.text[0], same words, verbatim in
   `.products-card__body` here) rather than a one-line teaser, so it reads
   as a proper lead-in paragraph — closer to `.products__lead`'s old role.
   Name set larger, closer to `.products__title`'s own weight, to read as
   the top of the hierarchy the wireframe draws it as. The 3 solutions-row
   cards below it, on request, drop their own teaser paragraph entirely —
   title only, no `.products-card__body` in their markup at all.

   No panel behind it either, on request — unlike every other card here it
   drops `.products-card`'s own background/shadow entirely and reads as
   plain text sitting directly on the stage, the same treatment
   `.products__title`/the old `.products__lead` got before this
   restructure. Unlike every other card, this one has no `.products-card__open`
   full-cover button at all — the title/paragraph are plain, unclickable
   text; only `.products-card__more` ("Read More") is a real `<button>` and
   the sole click target that opens the lightbox (on request — the
   paragraph read as accidentally clickable otherwise). See
   `.products-card__more`'s own comment below for how it's wired. */
.products-card--banner {
  grid-column: 1 / -1;
  background: none;
  box-shadow: none;
  /* Overrides `.products-card--text`'s own `center` (this rule comes
     later in source order, same specificity) — the row is now `auto`-
     sized to this content (see `.products__grid`'s own comment), so
     top-aligning vs. centering makes little visual difference on its own,
     but it keeps the content pinned to the row's top edge rather than
     leaving the alignment ambiguous if the row's `auto` height and the
     content's actual height ever drift apart by a pixel or two. */
  justify-content: flex-start;
}

/* No panel now to justify the usual `--products-card-pad` inset (that
   padding exists so a card's text doesn't touch its own painted edge) —
   zeroed on request so the paragraph's own text runs the full width of the
   grid below it, edge to edge, rather than sitting inset from it. */
.products-card--banner .products-card__name,
.products-card--banner .products-card__body {
  margin-left: 0;
  margin-right: 0;
}

.products-card--banner .products-card__name {
  font-size: var(--size-heading);
}

/* Body text styled like a section's own lead paragraph (`.events__lead`/
   `.products__lead`'s old rule) rather than the smaller `.products-card__
   body` caption size every other card uses — on request, since this is a
   full paragraph now, not a one-line teaser. */
.products-card--banner .products-card__body {
  font-size: var(--size-body);
  line-height: var(--lh-body);
}

/* `.products-card__more` ("Read More" here, "Learn more" on every other
   card) is `display: none` on desktop by default (see its own comment
   below) — every other card signals it's clickable via the image's own
   hover-zoom, but the banner has no image, so on request it gets its own
   always-visible "Read More" affordance instead, reusing the exact same
   paper-plate/red-chevron control every other card already shows on
   mobile. `margin: 1.4cqh 0` (was `var(--products-card-pad)` horizontally)
   to sit flush with the paragraph's own zeroed side margin above it,
   rather than inset from it. The base rule sets no `font-family`/
   `font-size` of its own — until now `.products-card__more` only ever
   rendered inside the mobile-flow query, which supplies a literal
   `0.85rem` there; this is the first time it renders on the `cqh`-based
   desktop/stage tier, so it needs its own size here or it inherits
   whatever ambient size happens to cascade down (rendered illegibly
   small in testing). `flex: none` stops it being swept into the same
   `flex: 1 1 auto`/shrink squeeze the paragraph above it can hit in a
   tight row — confirmed reproducible (2 of 3 renders) before this was
   added: the button collapsed to a sliver whenever the paragraph
   happened to wrap one line taller than expected. */
.products-card--banner .products-card__more {
  display: inline-flex;
  flex: none;
  /* Top margin shrunk from the shared `1.4cqh` (matches an image-to-title
     gap elsewhere) to sit noticeably closer to the paragraph right above
     it, on request; bottom zeroed since the grid's own row `gap` already
     separates it from the solutions row underneath. */
  margin: 0.4cqh 0 0;
  font-family: var(--font-utility);
  font-size: var(--size-caption);
}

/* A transparent button stretched over the whole card (`inset: 0`), placed
   before the visible content in the DOM so it paints on top and catches
   every click, but stays invisible — the real content underneath is
   still what's seen. `aria-label` carries the accessible name since the
   button itself has no visible text of its own. */
.products-card__open {
  position: absolute;
  inset: 0;
  z-index: 1;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}

.products-card__open:hover ~ .products-card__image-wrap .products-card__image {
  transform: scale(1.03);
}

.products-card__open:active ~ .products-card__image-wrap .products-card__image {
  transform: scale(1.01);
}

/* Was a fixed 55% height until the device cards' own body paragraph was
   dropped (title only, on request); now `flex: 1 1 auto` instead of a
   percentage, so the photo grows to fill every bit of the card the title
   doesn't need, docking `.products-card__name` to the card's bottom edge
   (only the 3 device cards ever have an image-wrap at all — the 4
   text-only concept cards' markup has no such element, so this rule no
   longer needs a shared percentage that both kinds of card have to fit). */
.products-card__image-wrap {
  display: block;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

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

.products-card--ps101 .products-card__image {
  object-position: center 35%;
}

.products-card__name {
  margin: 1.4cqh var(--products-card-pad) var(--products-card-pad-bottom);
  font-family: var(--font-utility);
  font-size: var(--size-body);
  font-weight: normal;
  line-height: 1.15;
  color: var(--paper);
}

.products-card__body {
  margin: 0.8cqh var(--products-card-pad) var(--products-card-pad-bottom);
  font-family: var(--font-utility);
  font-size: var(--size-caption);
  line-height: var(--lh-caption);
  color: var(--paper);
}

/* The plain "devices" sub-heading — row 3 of the grid, spanning every
   column like the banner, but not a card: no background/shadow/click
   target, just a label separating the software/service row above it from
   the physical device row below, matching the wireframe's own "Devices"
   heading. Centered on its own (thin) row rather than top-aligned, since
   the row's height rarely matches the text's own. Typography matches
   `.products-card__name` on request — same size/weight/line-height/color,
   no more uppercase/tracking, so it reads as one card title, not a small
   dim caption. */
.products__label {
  grid-column: 1 / -1;
  align-self: center;
  margin: 0;
  font-family: var(--font-utility);
  font-size: var(--size-body);
  font-weight: normal;
  line-height: 1.15;
  color: var(--paper);
}

/* On every card but the banner, this is still the plain `<span
   aria-hidden="true">` it always was: mobile/tablet-only affordance
   spelling out a click target the full-cover `.products-card__open`
   button (see its own comment above) already owns — desktop relies on the
   image's own hover-zoom to signal that, but there is no hover on touch.
   Hidden on desktop by default, only switched on inside the mobile-flow
   query below — sized there in vw/rem, not `cqw`, since `.hero` drops to
   `container-type: normal` in that query (see the `.lightbox__pdf` note
   elsewhere in this codebase for why a `cqw` value silently collapses
   there). `align-self: flex-start` keeps it hugging the card's left edge
   instead of stretching full-width — `.products-card` is a column flex
   container, so a flex item's width would otherwise default to stretch.
   Margin matches `.products-card__name`/`__body` exactly (`var(--products-
   card-pad)` horizontal) so it lines up with the title/body text above it
   at any tier without its own per-breakpoint override.

   On the banner card it's a real `<button>`, not a span — the paragraph
   above it read as accidentally clickable while the whole card sat under
   `.products-card__open`'s full-cover overlay (on request, that overlay
   is gone from the banner entirely; see its own comment above), so this
   button is now the sole click target that opens the lightbox, wired
   directly in about.js. The reset properties below (`background`/`border`/
   `padding`/`appearance`/`font`/`cursor`) only matter for that real button;
   they're no-ops on the plain `<span>` every other card still uses. */
.products-card__more {
  display: none;
  align-self: flex-start;
  align-items: center;
  gap: 0.5em;
  margin: 1.4cqh var(--products-card-pad);
  background: none;
  border: 0;
  padding: 0;
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  font-family: var(--font-utility);
  font-weight: normal;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--paper-dim);
  cursor: pointer;
  transition: color var(--ease-ui);
}

/* Paper plate, red chevron — same mark/reasoning as `.services-card__go`:
   red only ever sits on `--paper`, never directly on `--panel-gallery`
   (this card's own background), which measures 1.6:1 for red — see the
   "Brand colours" note. */
.products-card__more-go {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.6em;
  height: 1.6em;
  background: var(--paper);
  color: var(--brand-red);
  transition: transform var(--ease-ui);
}

.products-card__more-go svg {
  display: block;
  width: 34%;
  height: auto;
}

.products-card__open:hover ~ .products-card__more,
.products-card__open:active ~ .products-card__more {
  color: var(--paper);
}

.products-card__open:hover ~ .products-card__more .products-card__more-go {
  transform: translateX(3px);
}

.products-card__open:active ~ .products-card__more .products-card__more-go {
  transform: translateX(1px);
}

/* The banner's own "Read More" is a real button, not a sibling of
   `.products-card__open` (it has none) — so it needs its own hover/active/
   focus-visible rules instead of the sibling-combinator ones above.
   `:focus-visible` is spelled out explicitly here (not left to base.css's
   global ring alone) so keyboard focus also gets the same "about to open"
   treatment hover/active already give it. */
.products-card--banner .products-card__more:hover,
.products-card--banner .products-card__more:active,
.products-card--banner .products-card__more:focus-visible {
  color: var(--paper);
}

.products-card--banner .products-card__more:hover .products-card__more-go,
.products-card--banner .products-card__more:focus-visible .products-card__more-go {
  transform: translateX(3px);
}

.products-card--banner .products-card__more:active .products-card__more-go {
  transform: translateX(1px);
}

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

@media (max-width: 1024px), (max-aspect-ratio: 1/1) {
  .products__title,
  .products__grid {
    position: static;
    left: auto;
    top: auto;
    width: 100%;
  }

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

  .products__grid {
    height: auto;
    padding: 3vh 5vw 5vh;
    grid-template-columns: 1fr;
    grid-template-rows: none;
    gap: 3vh;
  }

  .products-card--banner,
  .products__label {
    grid-column: auto;
  }

  .products__label {
    padding: 0.5vh 0;
  }

  /* Relied on the grid cell's implicit 55%-of-card height on desktop; needs
     its own ratio now that the card's height is just its content's. The
     full-card `__open` overlay needs no override — `inset: 0` already
     tracks the card's height at any tier. (The banner card has no image at
     any tier, so it needs no entry here.) */
  .products-card__image-wrap {
    height: auto;
    aspect-ratio: 4 / 3;
  }

  .products-card--banner .products-card__name {
    font-size: var(--size-body);
  }

  .products-card__more {
    display: inline-flex;
    font-size: 0.85rem;
  }
}

/* Tablet tier: same single-column stack as phone, above, with content
   running near-full-width (a flat 32px side gutter, not a centered
   narrower column) and grid spacing scaled up for the larger screen.
   First pass, no tablet reference exists yet. */
@media (min-width: 768px) and (max-width: 1024px) {
  .products__title {
    padding: 8vh 32px 0;
  }

  .products__grid {
    padding: 4vh 32px 6vh;
    gap: 3.5vh;
  }

  .products-card__more {
    font-size: 0.95rem;
  }
}
