/* =============================================================
   Components

   Discrete, reusable pieces: notices, metadata, definition lists,
   buttons, the FAQ accordion, photography, and the card grid.

   Structure only. Every value is a token from tokens.css — no
   hard-coded hex, spacing or type values in here (see CLAUDE.md).

   Load order: 5th, after typography.css. See the stylesheet block in
   _includes/layouts/base.njk for the full order and why it matters.
   ============================================================= */

/* --- Notices ------------------------------------------------------ */

.notice {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-m);
  background: var(--color-bg-subtle);
  padding: var(--space-s) var(--space-m);
  margin: var(--space-l) 0;
  max-width: var(--measure);
}

/* Unresolved content that must not reach a live host. The visible
   treatment is in jshw-chrome.css, which is loaded only on builds that
   carry pre-launch surfaces — so these blocks are styled as JSHW
   scaffolding rather than as part of the client's design, and their
   visibility does not depend on the client's palette. */
.notice--outstanding {
  padding: var(--space-xs) var(--space-s);
}

.meta {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* --- Detail list -------------------------------------------------- */

/* Label/value pairs for identity details (legal notice). A <dl> because
   these genuinely are term/definition pairs — the association is real,
   so a screen reader should get it rather than reading two loose
   columns of text. */
.detail-list {
  display: grid;
  grid-template-columns: minmax(10rem, auto) 1fr;
  gap: var(--space-2xs) var(--space-l);
  margin: var(--space-m) 0 var(--space-l);
  max-width: var(--measure);
}

.detail-list dt {
  font-weight: var(--weight-medium);
  color: var(--color-text-muted);
}

.detail-list dd {
  margin: 0;
}

.detail-list address {
  font-style: normal;
}

/* Stack on narrow screens — two columns at 320px would leave the value
   column unusably thin. */
@media (max-width: 30rem) {
  .detail-list {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .detail-list dd {
    margin-bottom: var(--space-s);
  }
}

/* --- Button ------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  background: var(--color-brand);
  color: var(--color-text);
  text-decoration: none;
  font-weight: var(--weight-medium);
  border-radius: var(--radius-pill);
  padding: var(--space-2xs) var(--space-m);
  min-height: var(--target-comfortable);
}

.button:hover {
  background: var(--color-brand-strong);
  color: var(--color-text-inverse);
}

/* --- FAQ / accordion --------------------------------------------- */

.faq {
  margin-top: var(--space-2xl);
}

.faq__item {
  border-bottom: 1px solid var(--color-border);
}

.faq__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  padding: var(--space-s) 0;
  font-weight: var(--weight-medium);
  cursor: pointer;
  list-style: none;
}

.faq__question::-webkit-details-marker {
  display: none;
}

.faq__marker {
  flex-shrink: 0;
  transition: transform var(--duration-base) var(--ease);
}

/* The chevron is the open/closed indicator, so it turns for everyone.
   It used to turn only under prefers-reduced-motion: no-preference,
   which left reduced-motion visitors with no visual state at all. The
   motion tokens already collapse --duration-base to ~0 for them, so
   they get an instant flip rather than no flip. */
.faq__item[open] .faq__marker {
  transform: rotate(180deg);
}

.faq__answer {
  padding: 0 0 var(--space-s);
}

/* Open and close by animating the answer's height instead of snapping.
   interpolate-size lets block-size transition to and from `auto`;
   ::details-content is the box <details> hides and shows. Its
   content-visibility flip is discrete, so allow-discrete holds it
   visible until the closing transition has finished.

   Both gates are deliberate. @supports: a browser that has
   ::details-content but not interpolate-size would get block-size: 0
   with nothing to animate it open, so it keeps the native snap. Motion:
   a panel growing under the reader is the kind of large movement
   reduced-motion asks us to drop. Find-in-page still opens a closed
   answer, because <details> handles that natively. */
@supports (interpolate-size: allow-keywords) {
  @media (prefers-reduced-motion: no-preference) {
    .faq__item {
      interpolate-size: allow-keywords;
    }

    .faq__item::details-content {
      block-size: 0;
      overflow-y: clip;
      transition:
        content-visibility var(--duration-base) var(--ease) allow-discrete,
        block-size var(--duration-base) var(--ease);
    }

    .faq__item[open]::details-content {
      block-size: auto;
    }
  }
}

/* --- Photography ---------------------------------------------------
   Rendered by the `photo` shortcode (.eleventy.js) from sizes generated
   by tools/build-photos.mjs. Stock or illustrative photos are never
   captioned as the client's actual team, office or clients. The organic
   mask radii are extracted from the client's original like every other
   token, because this is a refactor, not a redesign.

   A fixed frame per breakpoint, not the photo's natural shape. At full
   column width a near-square photo ran ~1000px tall on a laptop, and
   object-fit: cover did nothing because nothing fixed the height.
   Sources are pre-cropped to 16:9 around their subject, so a centred
   cover crop keeps it in frame: 4:3 on phones, 16:9 from 640px, 21:9
   from 1024px. The max-height stops a landscape phone being filled by
   one photo. These ratios are what the shortcode's `sizes` is computed
   from; change one and the other must follow. */
.photo {
  margin: var(--space-l) 0;
  max-width: none;
}

.photo img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  max-height: 70svh;
  object-fit: cover;
  object-position: center;
  border-radius: var(--radius-organic-1);
}

@media (min-width: 640px) {
  .photo img {
    aspect-ratio: 16 / 9;
  }
}

@media (min-width: 1024px) {
  .photo img {
    aspect-ratio: 21 / 9;
  }
}

/* Faces sit in the top third of people photos; a centred 21:9 crop
   would cut through them. Set per photo with `frame: "top"` in
   config/photos.json. */
.photo--top img {
  object-position: center 25%;
}

.photo--alt img {
  border-radius: var(--radius-organic-2);
}

.photo figcaption {
  margin-top: var(--space-2xs);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* --- Network hero -------------------------------------------------
   The hero takes the full column so network.js has room to draw; its
   text keeps the reading measure. The canvas exists only when the
   script ran, so without JavaScript this is the ordinary hero with a
   little more space around it. Masked so the nodes thin out behind the
   text and fade at every edge instead of being cut off. Without
   mask-composite the two masks add up rather than intersect: the edges
   stay hard, which is plainer, not broken. */
.hero--network {
  position: relative;
  isolation: isolate;
  max-width: none;
  padding-block: var(--space-xl);
}

.hero--network > :not(.network) {
  max-width: var(--measure);
}

.network {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  opacity: 0.5;
  mask-image:
    linear-gradient(to right, transparent, black 40%, black 85%, transparent),
    linear-gradient(to bottom, transparent, black 15%, black 85%, transparent);
  mask-composite: intersect;
}

@media (min-width: 1024px) {
  .network {
    opacity: 1;
  }
}

/* --- Card grid (core values, expertise, services) ----------------- */

.card-grid {
  display: grid;
  gap: var(--space-l);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  margin: var(--space-l) 0 var(--space-xl);
  max-width: none;
}

.card-grid__item h3 {
  font-size: var(--text-lg);
  margin: 0 0 var(--space-2xs);
}

.card-grid__item p {
  margin: 0;
  max-width: none;
}

/* Sits INSIDE the heading it labels, as a block-level <span> — see A11Y-9.
   As a sibling <p> the label was visual only: a screen reader navigating by
   heading list never heard it, because it was not part of any heading's
   accessible name. Inside the <h3> it is, and the label reads as what it
   has always looked like.
   font-weight is pinned because the span now inherits the heading's 700.
   font-size, colour and line-height need no such guard — the first two are
   set here, and --leading-body is unitless so it re-resolves against this
   element's own font-size rather than the heading's. */
.card-grid__eyebrow {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-normal);
  margin: 0 0 var(--space-3xs);
}

/* --- Report components (articles) --------------------------------
   The editorial system for long-form audit articles: stat tiles,
   caveat callouts, data tables, pull quotes, status chips. Same rule
   as everything else here — every value a token, nothing invented
   outside tokens.css. */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  margin: var(--space-l) 0;
}

.stat-tile {
  background: var(--color-surface);
  padding: var(--space-m) var(--space-m) var(--space-l);
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
}

.stat-tile__num {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  line-height: 1;
  color: var(--color-text);
}

.stat-tile__num small {
  font-size: 0.5em;
  font-weight: var(--weight-medium);
  color: var(--color-text-muted);
}

.stat-tile__label {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  font-weight: var(--weight-medium);
}

.stat-tile__note {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* The recurring device: state what a number does not prove, right next
   to the number, in its own persistent form rather than a footnote. */
.caveat {
  border-left: 3px solid var(--color-brand);
  background: var(--color-bg-subtle);
  padding: var(--space-s) var(--space-m);
  margin: var(--space-l) 0;
  max-width: var(--measure);
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.caveat__label {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: var(--weight-medium);
  color: var(--color-brand-strong);
}

.caveat p {
  max-width: none;
  margin: 0;
}

.caveat strong {
  color: var(--color-text);
}

/* --- Data table ----------------------------------------------------- */

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  margin: var(--space-l) 0;
}

.data-table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--text-sm);
  min-width: 30rem;
}

.data-table caption {
  text-align: left;
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  padding: var(--space-s) var(--space-m) var(--space-2xs);
}

.data-table th,
.data-table td {
  text-align: left;
  padding: var(--space-2xs) var(--space-m);
  border-bottom: 1px solid var(--color-border);
  vertical-align: baseline;
}

.data-table thead th {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: var(--weight-medium);
  color: var(--color-text-muted);
  white-space: nowrap;
}

.data-table tbody tr:last-child td {
  border-bottom: 0;
}

.data-table td.num,
.data-table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.data-table td.was {
  color: var(--color-error);
}

.data-table td.now {
  color: var(--color-success);
  font-weight: var(--weight-medium);
}

/* --- Status chip ------------------------------------------------------ */

.chip {
  display: inline-block;
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.1em 0.5em;
  border: 1px solid currentcolor;
  border-radius: var(--radius-s);
  white-space: nowrap;
}

.chip--pass {
  color: var(--color-success);
}

.chip--fail {
  color: var(--color-error);
}

.chip--open {
  color: var(--color-warning);
}

/* --- Pull quote (inside .report-band--pull) ------------------------ */

.pull-quote {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  max-width: var(--measure);
}

/* --- Finding-count tiles (audit-section breakdown) ------------------ */

.finding-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  margin: var(--space-l) 0;
}

.finding-tile {
  background: var(--color-surface);
  padding: var(--space-m);
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.finding-tile__code {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--color-brand-strong);
  font-weight: var(--weight-medium);
}

.finding-tile__count {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  line-height: 1;
}

.finding-tile__desc {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* --- Article index: series card and article list ------------------ */

.series-card {
  display: block;
  border: 1px solid var(--color-border);
  border-top: 3px solid var(--color-brand-strong);
  background: var(--color-surface);
  padding: var(--space-l) var(--space-m);
  text-decoration: none;
  margin: var(--space-l) 0;
  max-width: none;
}

.series-card:hover {
  border-top-color: var(--color-brand);
}

.series-card__eyebrow {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2xs);
}

.series-card h2 {
  color: var(--color-text);
  margin: 0 0 var(--space-2xs);
}

.series-card p {
  color: var(--color-text-muted);
  max-width: var(--measure);
  margin: 0;
}

.article-list {
  list-style: none;
  margin: var(--space-l) 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
}

.article-list__item {
  background: var(--color-surface);
  padding: var(--space-m);
}

.article-list__num {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.article-list__item h3 {
  margin: var(--space-3xs) 0 var(--space-2xs);
}

.article-list__item h3 a {
  color: var(--color-text);
  text-decoration-color: var(--color-brand);
}

.article-list__item p {
  color: var(--color-text-muted);
  margin: 0;
  max-width: var(--measure);
}

/* Pre-launch banner, hard-flag callouts and the review table live in
   jshw-chrome.css — they wear JSHW's brand, not the client's. */

/* Consent banner.
   Fixed to the bottom rather than covering the page: the visitor can read
   what they came for while deciding, which is also why it is not a modal.
   Styled from the client's own tokens — this is the site's furniture, not
   JSHW's, so it must not look like the pre-launch scaffolding.

   Capped at 80% of the viewport and scrollable inside. Without the cap, a
   landscape phone or a desktop at 400% zoom got a banner taller than the
   screen, with its buttons below the bottom edge and no way to reach them. */
.consent {
  position: fixed;
  inset: auto 0 0;
  z-index: 100;
  max-height: 80dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-m);
  background: var(--color-bg);
  border-top: 2px solid var(--color-border-strong);
  box-shadow: 0 -4px 16px rgb(0 0 0 / 12%);
}
.consent[hidden] { display: none; }

.consent__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  margin: 0 0 var(--space-xs);
  max-width: var(--measure);
}

.consent__title {
  margin: 0;
  font-size: 1rem;
}

.consent__toggle {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: var(--space-2xs);
  min-height: var(--target-comfortable);
  padding: 0 var(--space-2xs);
  margin-inline-end: calc(-1 * var(--space-2xs));
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  font-weight: var(--weight-medium);
  text-decoration: underline;
  cursor: pointer;
}

/* Points the way the banner will move: down to shrink, up to open. Turns
   for everyone; the motion tokens make it instant under reduced motion. */
.consent__marker {
  transition: transform var(--duration-base) var(--ease);
}

.consent__summary {
  margin: 0 0 var(--space-xs);
  max-width: var(--measure);
  font-size: 0.9rem;
}

.consent__body {
  margin: 0 0 var(--space-xs);
  max-width: var(--measure);
}

.consent__link {
  margin: 0 0 var(--space-s);
}

/* The second layer, under the buttons that open it. */
.consent__choices {
  margin-top: var(--space-s);
  max-width: var(--measure);
}

.consent__list {
  list-style: none;
  margin: 0 0 var(--space-s);
  padding: 0;
  display: grid;
  gap: var(--space-xs);
  max-width: var(--measure);
}
.consent__option-name { font-weight: 600; }

.consent__option-needs,
.consent__option-state {
  margin-inline-start: var(--space-2xs);
  color: var(--color-text-muted);
}

.consent__option-detail {
  display: block;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

/* Accept all, Reject all and Choose: one component, one size. Each takes an
   equal share of the row and wraps only when the words would not fit. */
.consent__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  max-width: 36rem;
}

.consent__actions .button {
  flex: 1 1 0;
  justify-content: center;
  padding-inline: var(--space-xs);
  white-space: nowrap;
}

.consent__footer {
  margin: 0 0 var(--space-s);
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

@media (min-width: 40em) {
  .consent { padding: var(--space-m) var(--space-l); }
}

/* Compact form: title, one-line summary and the three buttons. The full
   text is one tap away behind Details; the choices, behind Choose. */
.consent:not([data-compact]) .consent__summary,
.consent[data-compact] .consent__details {
  display: none;
}

.consent[data-compact] {
  padding-block: var(--space-xs);
}

/* The toggle's 44px target already spaces the title row. */
.consent[data-compact] .consent__head {
  margin-bottom: 0;
}

.consent[data-compact] .consent__marker {
  transform: rotate(180deg);
}

/* Room underneath. A fixed banner sits over the end of the page, and over
   anything a keyboard user tabs to beneath it (WCAG 2.2 SC 2.4.11, focus
   not obscured). consent.js sets --consent-space to the banner's height
   while it is shown, so the page can scroll its last line clear of it and
   the browser keeps focused elements above it. Unset, both are zero. */
html { scroll-padding-bottom: var(--consent-space, 0); }
body { padding-bottom: var(--consent-space, 0); }
