/* =========================================================================
   components.css - LAYER 4 of 4: components
   -------------------------------------------------------------------------
   Every reusable interface element in the platform, defined exactly once.

   RULES
     1. No raw values. Everything comes from tokens.css.
     2. No layout primitives. Use .grid, .stack, .cluster, .split from
        layout.css rather than writing another display:grid.
     3. Name by role, not appearance. There is no .btn--dark, because a dark
        button on a charcoal section is invisible and the name stops helping.
     4. One component per concept. Before adding a card, check .card.

   Contents
      1. Button
      2. Badge
      3. Alert
      4. Card
      5. Field (form control)
      6. Form
      7. Navigation
      8. Breadcrumb
      9. Side navigation
     10. Dialog
     11. Table
     12. Lists
     13. Specification list
     14. Chip
     15. Search
     16. Filter group
     17. Toolbar and results
     18. Logo
     19. Call to action band
     20. Person
     21. Media
     22. Empty state
     23. Loading skeleton
     24. State panel
     25. Hero
     26. Prose
     27. Footer parts
     28. Enyalius product surfaces
   ========================================================================= */

/* ============================== 1. BUTTON ============================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: var(--control-height);
  padding: var(--control-padding-y) var(--space-6);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-md);
  background: none;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  letter-spacing: 0.02em;
  line-height: var(--leading-tight);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--transition-fast),
              border-color var(--transition-fast),
              color var(--transition-fast),
              transform var(--transition-fast);
}

/* Every control acknowledges a press. This was previously absent across the
   whole platform: buttons, cards and chips changed a border colour on hover
   and did nothing at all when held. Perceived quality is largely feedback,
   and the gap between pressing and the page changing was a void.
   --press-shift becomes 0 under prefers-reduced-motion, so the colour change
   still confirms the press without any movement. */
.btn:active { transform: translateY(var(--press-shift)); }

/* The main action on a light surface. */
.btn--primary {
  background: var(--color-surface-inverse);
  border-color: var(--color-surface-inverse);
  color: var(--color-text-inverse);
}
.btn--primary:hover { background: var(--color-surface-inverse-raised); }
.btn--primary:active { background: var(--color-surface-inverse); }

/* The main action on a charcoal surface. */
.btn--accent {
  background: var(--color-accent-on-dark);
  border-color: var(--color-accent-on-dark);
  color: var(--color-surface-inverse);
}
.btn--accent:hover {
  background: var(--color-accent-line);
  border-color: var(--color-accent-line);
}
.btn--accent:active {
  background: var(--color-accent-on-dark);
  border-color: var(--color-accent-on-dark);
}

/* Secondary action on a light surface. */
.btn--secondary {
  border-color: var(--color-border-strong);
  color: var(--color-text-default);
}
.btn--secondary:hover {
  border-color: var(--color-accent);
  background: var(--color-surface-raised);
}
.btn--secondary:active { background: var(--color-surface-sunken); }

/* Secondary action on a charcoal surface. */
.btn--secondary-invert {
  border-color: var(--color-border-inverse);
  color: var(--color-text-inverse);
}
.btn--secondary-invert:hover {
  border-color: var(--color-accent-on-dark);
  background: var(--color-surface-inverse-raised);
}
.btn--secondary-invert:active { background: var(--color-surface-inverse); }

/* Tertiary: reads as a link, behaves as a button. */
.btn--quiet {
  min-height: auto;
  padding: var(--space-2) 0;
  border: 0;
  color: var(--color-text-muted);
  font-weight: var(--weight-medium);
  text-decoration: underline;
  text-underline-offset: 0.22em;
}
.btn--quiet:hover { color: var(--color-text-default); }
.on-inverse .btn--quiet { color: var(--color-text-inverse-muted); }

.btn--block { width: 100%; }

/* aria-disabled rather than the disabled attribute: the control stays
   focusable, so it can explain why it is unavailable. A disabled button
   cannot be reached by keyboard and cannot say anything.
   It must not appear to respond, so the press feedback is suppressed. */
.btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; }
.btn[aria-disabled="true"]:active { transform: none; }

.btn__tag {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  opacity: 0.8;
}


/* =============================== 2. BADGE ============================= */

/* One component replacing five previous patterns: filter counts, category
   labels, status labels, role labels and button tags.

   THE UPPERCASE DEVICE LIVES HERE, AND IN .eyebrow. NOWHERE ELSE.
   Small letter-spaced capitals were previously applied to twenty-one
   different things - headings, labels, roles, breadcrumbs, table headers,
   footer titles. When everything is a label, nothing is. Every other use was
   removed and returned to sentence case; these two remain because they are
   genuinely labels and are used sparingly. Please do not add a third. */
.badge {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: var(--leading-tight);
}

/* A numeric count in a filled pill. */
.badge--count {
  min-width: 1.375rem;
  padding: 0.05rem var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  font-size: var(--text-xs);
  letter-spacing: 0;
  text-align: center;
}

/* A taxonomy label. Gold, so it uses the text-safe gold. */
.badge--category { color: var(--color-accent-text); }
.on-inverse .badge--category { color: var(--color-accent-on-dark); }
.badge--muted { color: var(--color-text-muted); }
.on-inverse .badge--muted { color: var(--color-text-inverse-muted); }

/* A state label: "Coming soon", "Placeholder". */
.badge--status { color: var(--color-accent-on-dark); }
.badge--status-muted { color: var(--color-text-inverse-muted); }




/* =============================== 3. ALERT ============================= */

/* One component replacing six previous patterns: disclaimers, callouts,
   empty states, error panels, form notices and content-gap notes. */
.alert {
  padding: var(--space-5);
  background: var(--color-surface-raised);
  border: var(--hairline);
  border-left: var(--border-width-accent) solid var(--color-accent);
}
.alert__title {
  margin-bottom: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
}
.alert__body { font-size: var(--text-sm); color: var(--color-text-muted); }
.alert__body:last-child { margin-bottom: 0; }
.alert .btn { margin-top: var(--space-4); }

.alert--info {
  background: var(--color-status-info-wash);
  border-left-color: var(--color-status-info);
}
.alert--info .alert__title { color: var(--color-status-info); }

.alert--warning {
  background: var(--color-status-warning-wash);
  border-left-color: var(--color-status-warning);
}
.alert--warning .alert__title { color: var(--color-status-warning); }

.alert--danger {
  background: var(--color-status-danger-wash);
  border-left-color: var(--color-status-danger);
}
.alert--danger .alert__title { color: var(--color-status-danger); }

.alert--success {
  background: var(--color-status-success-wash);
  border-left-color: var(--color-status-success);
}
.alert--success .alert__title { color: var(--color-status-success); }

/* A quiet aside inside prose. */
.alert--note {
  padding: var(--space-4);
  background: var(--color-accent-wash);
  border: 0;
  border-left: var(--border-width-accent) solid var(--color-accent);
}

/* Full-width banner across the top of the page. */
.alert--banner {
  padding: var(--space-3) var(--wrap-gutter);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse-muted);
  border: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  letter-spacing: 0.02em;
  text-align: center;
  margin: 0;
}

/* A prominent notice band with an inline label, used for the legal
   disclaimer that must appear above the fold. */
.alert--band {
  background: var(--color-accent-wash);
  border: 0;
  border-bottom: var(--hairline);
  padding: 0;
}
.alert--band .alert__inner {
  display: grid;
  gap: var(--space-2);
  padding-block: var(--space-5);
}
.alert--band .alert__title { color: var(--color-status-warning); }
.alert--band .alert__body { max-width: var(--measure); color: var(--color-text-default); }

@media (min-width: 48rem) {
  .alert--band .alert__inner {
    grid-template-columns: 7rem minmax(0, 1fr);
    gap: var(--space-5);
    align-items: baseline;
  }
}


/* =============================== 4. CARD ============================== */

/* One component replacing seven previous patterns: product cards, service
   cards, insight cards, area cards, pillars, routes and panels. */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--card-padding);
  background: var(--color-surface-raised);
  border: var(--hairline);
  border-top: var(--border-width-accent) solid transparent;
  transition: border-color var(--transition-base),
              background-color var(--transition-fast),
              box-shadow var(--transition-fast),
              transform var(--transition-fast);
}

/* An interactive card lifts, gains a shadow and reveals its gold rule.
   Previously the only feedback was the rule appearing at the top edge - the
   point furthest from the cursor - on a card whose entire surface is
   clickable. Nothing changed under the pointer.
   Keyboard focus gets the identical treatment, so both routes feel the same. */
.card--interactive { cursor: pointer; }
.card--interactive:hover,
.card--interactive:focus-within {
  border-top-color: var(--color-accent);
  background: var(--color-surface-raised);
  transform: translateY(var(--lift-shift));
  box-shadow: var(--shadow-lift);
}
.card--interactive:active {
  transform: translateY(var(--press-shift));
  box-shadow: none;
}

/* Permanently marked. */
.card--marked { border-top-color: var(--color-accent); }

/* On a charcoal section. */
.card--inverse {
  background: var(--color-surface-inverse-raised);
  border-color: var(--color-border-inverse);
  border-top-color: var(--color-accent-on-dark);
}

/* No border or background; a rule above the content only. */
.card--bare {
  padding: var(--space-5) 0 0;
  background: none;
  border: 0;
  border-top: var(--border-width-accent) solid var(--color-accent);
}
.card--bare-inverse {
  padding: var(--space-4) 0 0;
  background: none;
  border: 0;
  border-top: var(--hairline-inverse);
}

.card--compact { padding: var(--space-5); }

.card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: var(--hairline);
}
.card--inverse .card__head,
.card--bare-inverse .card__head { border-bottom-color: var(--color-border-inverse); }

/* A head with no rule beneath it. */
.card__head--plain { padding-bottom: 0; border-bottom: 0; margin-bottom: var(--space-3); }

.card__index {
  margin-bottom: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-accent-text);
}
.on-inverse .card__index { color: var(--color-accent-on-dark); }

.card__title {
  margin-bottom: var(--space-3);
  font-size: var(--text-xl);
}
.card--inverse .card__title,
.card--bare-inverse .card__title { color: var(--color-text-inverse); }

.card__title--sm { font-size: var(--text-lg); }

/* Makes the whole card activate the card's own link, while the accessible
   name stays on the link itself. */
.card__link { text-decoration: none; }
.card__link::after { content: ""; position: absolute; inset: 0; }

/* Internal rhythm is one step tighter than the card's own padding, so the
   edge of the card reads as the outer boundary and the gaps inside it read
   as grouping. Previously text, meta and actions were all separated by
   --space-5 - the same distance as the padding - which left a product card
   in a three-column grid noticeably taller than it needed to be. */
.card__text {
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.card--inverse .card__text,
.card--bare-inverse .card__text { color: var(--color-text-inverse-muted); }
.card__text:last-child { margin-bottom: 0; }

/* .card__meta, .card__price and .card__price--tbc were removed in Sprint 2.
   Content pack section 17 limits a product card to a category, a title, one
   summary and View details; audience, format and price moved to the detail
   panel, where the price is a .spec row. Nothing else used them. */

/* Pushes actions to the bottom so buttons align across a row. */
.card__actions { margin-top: auto; padding-top: var(--space-2); }
.card__note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--color-text-subtle);
}
.card--inverse .card__note { color: var(--color-text-inverse-muted); }

/* Non-link "read more" affordance on a card whose title is the link. */
.card__more {
  margin-top: auto;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-accent-text);
}


/* ============================== 5. FIELD ============================== */

/* One form control component: label, control, hint, error. Used by every
   input, select and textarea on the platform. */
.field { margin-bottom: var(--space-5); }

.field__label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
}

/* The compact uppercase variant used above search and sort. */
.field__label--compact {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-text-muted);
}

.field__required { font-weight: var(--weight-regular); color: var(--color-text-subtle); }

.field__control {
  width: 100%;
  min-height: var(--control-height);
  padding: var(--control-padding-y) var(--control-padding-x);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface-default);
  color: var(--color-text-default);
  font-size: var(--text-md);
}
.field__control:hover { border-color: var(--color-accent); }
.field__control::placeholder { color: var(--color-text-subtle); }
.field__control:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-subtle);
  cursor: not-allowed;
}

select.field__control { cursor: pointer; }
textarea.field__control { min-height: 8rem; resize: vertical; line-height: var(--leading-snug); }

.field__hint {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-subtle);
}

.field--check { display: flex; align-items: flex-start; gap: var(--space-3); }
.field--check .field__label { margin-bottom: 0; font-weight: var(--weight-regular); color: var(--color-text-muted); }

.field__check {
  flex: 0 0 auto;
  width: 1.0625rem;
  height: 1.0625rem;
  margin-top: 0.25rem;
  accent-color: var(--color-surface-inverse);
  cursor: pointer;
}


/* =============================== 6. FORM ============================== */

.form {
  padding: var(--space-6);
  background: var(--color-surface-raised);
  border: var(--hairline);
}
.form__actions {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: var(--hairline);
}

/*
   DISCLOSURE

   Content that is worth keeping on the page but is not worth making the
   reader walk past. Native <details>, so it works with no JavaScript and
   carries the correct expanded/collapsed semantics for free.

   Its first use is the contact page, where two forms that cannot be
   submitted were the first thing a visitor met. They still exist and can
   still be reviewed - behind one click instead of in front of the only
   route that actually works.
*/
.disclosure {
  border: var(--hairline);
  border-top: var(--border-width-accent) solid var(--color-accent);
  background: var(--color-surface-raised);
}

.disclosure__summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5);
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  cursor: pointer;
  list-style: none;
}
.disclosure__summary::-webkit-details-marker { display: none; }
.disclosure__summary:hover { color: var(--color-accent-text); }
.disclosure__summary:focus-visible { outline-offset: calc(-1 * var(--focus-ring-offset)); }

.disclosure__label { flex: 1 1 auto; }
.disclosure__note { font-weight: var(--weight-regular); color: var(--color-text-subtle); }

/* Same chevron as the filter groups, rotated by the open state. */
.disclosure__chevron {
  flex: 0 0 auto;
  width: 0.55rem;
  height: 0.55rem;
  border-right: 1.5px solid var(--color-accent);
  border-bottom: 1.5px solid var(--color-accent);
  transform: rotate(45deg);
  transition: transform var(--transition-fast);
}
.disclosure[open] .disclosure__chevron { transform: rotate(-135deg); }

/* The form inside supplies its own frame, so the disclosure drops its. */
.disclosure[open] .disclosure__summary { border-bottom: var(--hairline); }
.disclosure .form { border: 0; background: none; }

.disclosure__body { padding: var(--space-5); }

/* A stack of disclosures reads as one list rather than separate panels, so
   the borders collapse into shared rules. Used for the service FAQs, where
   three questions sitting in three floating boxes would look like three
   unrelated things. */
.faq { border: var(--hairline); border-top: var(--border-width-accent) solid var(--color-accent); }
.faq .disclosure { border: 0; border-bottom: var(--hairline); background: none; }
.faq .disclosure:last-child { border-bottom: 0; }

/* A quiet closing note. Not a headed section: content pack section 9 forbids
   the heading this replaces, but the disclaimer itself still earns its place. */
.doc__note {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: var(--hairline);
  font-size: var(--text-sm);
  color: var(--color-text-subtle);
}


/* ============================ 7. NAVIGATION =========================== */

/*
   PROGRESSIVE ENHANCEMENT, THE RIGHT WAY ROUND.

   The navigation is VISIBLE by default and JavaScript collapses it. It used
   to be the opposite - hidden by default, revealed by a data-open attribute
   that only site.js could set - which meant that on a phone without
   JavaScript all seven navigation links, including the main call to action,
   were display:none while the "Menu" button remained visible and did
   nothing. The file's own header comment claimed the site was "still fully
   usable" in that state. It was not.

   Now the failure mode is a nav that is already open, which is merely
   inelegant rather than broken. site.js sets data-open="false" on load,
   which is the only thing that hides it.
*/
.nav { display: block; }
.nav[data-open="false"] { display: none; }
.nav[data-open="true"]  { display: block; }

/* The toggle's own display is set in its component block below, where it is
   hidden by default and revealed only once site.js marks the header as
   enhanced. It is not set here: a bare .nav-toggle rule at this point is
   overridden by the component block further down the file, which is exactly
   how the inert-button bug survived the first fix. */

.nav__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-block: var(--space-4);
}
.nav__link {
  display: block;
  padding: var(--space-3) 0;
  border-bottom: var(--border-width) solid transparent;
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  text-decoration: none;
  transition: border-color var(--transition-fast);
}
.nav__link:hover { border-bottom-color: var(--color-accent); }
.nav__link:active { color: var(--color-accent-text); }

/* aria-current is the source of truth for "you are here"; the visual
   treatment follows the attribute rather than a separate class. */
.nav__link[aria-current="page"] {
  border-bottom-color: var(--color-accent);
  color: var(--color-text-default);
}

.nav__cta { margin-bottom: var(--space-4); width: 100%; }

/* Hidden until site.js sets data-enhanced on the header. A visitor without
   JavaScript must never be shown a Menu button that cannot open anything -
   which is precisely what happened before: the button was always visible and
   the navigation it controlled was display:none with no way to reveal it. */
.nav-toggle { display: none; }

.site-header[data-enhanced="true"] .nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--control-height);
  padding: var(--space-2) var(--control-padding-x);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  cursor: pointer;
}

.nav-toggle__label { line-height: var(--leading-tight); }

/* Three bars drawn with borders: no icon font, no SVG file, no request. */
.nav-toggle__bars {
  position: relative;
  display: block;
  width: 1rem;
  height: 0.6rem;
  border-top: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
}
.nav-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  border-top: 1.5px solid currentColor;
  transform: translateY(-50%);
}

@media (min-width: 34rem) {
  .nav__cta { width: auto; }
}

@media (min-width: 60rem) {
  .site-header[data-enhanced="true"] .nav-toggle { display: none; }
  .nav,
  .nav[data-open="false"],
  .nav[data-open="true"] { display: flex; align-items: center; gap: var(--space-6); }
  .nav__list { flex-direction: row; gap: var(--space-5); padding-block: 0; }
  /* A navigation label is one word to a reader even when it is two words to a
     typesetter. Without this the row simply compresses its items, and the
     first label with a space in it breaks across two lines - "Policy Store"
     read as two entries. Forbidding the break moves the flexing onto the call
     to action, which is a sentence and may wrap. No effect above 80rem, where
     the row already fits at its natural width. */
  .nav__link { padding: var(--space-2) 0; white-space: nowrap; }
  .nav__cta { margin-bottom: 0; width: auto; }
}

/* Between the width where the horizontal navigation appears and the width
   where the wrap is capped, the header is genuinely over-subscribed: at 960px
   the logo, seven labels and the call to action want 1027px of an available
   896px. Holding the labels intact alone would push the call to action onto a
   third line, so this band buys the room back from the two gaps rather than
   from the type. Bounded deliberately - above 80rem nothing here applies and
   the header is untouched. */
@media (min-width: 60rem) and (max-width: 79.99rem) {
  .nav,
  .nav[data-open="false"],
  .nav[data-open="true"] { gap: var(--space-5); }
  .nav__list { gap: var(--space-4); }
}


/* ============================ 8. BREADCRUMB =========================== */

.breadcrumb { margin-bottom: var(--space-5); }
.breadcrumb__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-inverse-muted);
}
.breadcrumb__list li:not(:last-child)::after {
  content: "/";
  margin-left: var(--space-2);
  color: var(--color-accent-on-dark);
}
.breadcrumb__list a { color: var(--color-text-inverse-muted); }
.breadcrumb__list a:hover { color: var(--color-text-inverse); }


/* ========================= 9. SIDE NAVIGATION ========================= */

.sidenav {
  padding: var(--space-5);
  background: var(--color-surface-raised);
  border: var(--hairline);
  border-top: var(--border-width-accent) solid var(--color-accent);
  align-self: start;
}
.sidenav__heading {
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: var(--hairline);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
}
.sidenav__list li { border-bottom: var(--hairline); }
.sidenav__list li:last-child { border-bottom: 0; }
.sidenav__list a,
.sidenav__current {
  display: block;
  padding: var(--space-3) 0;
  font-size: var(--text-sm);
  text-decoration: none;
}
.sidenav__list a:hover { color: var(--color-accent-text); }
.sidenav__current {
  padding-left: var(--space-3);
  border-left: var(--border-width-accent) solid var(--color-accent);
  font-weight: var(--weight-bold);
  color: var(--color-text-default);
}
.sidenav__actions { margin-top: var(--space-5); }


/* ============================== 10. DIALOG ============================ */

.dialog {
  width: min(48rem, calc(100vw - var(--space-6)));
  max-height: min(92vh, 60rem);
  padding: 0;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--color-surface-raised);
  color: var(--color-text-default);
  box-shadow: var(--shadow-dialog);
  overflow: hidden;
}
.dialog--narrow { width: min(34rem, calc(100vw - var(--space-6))); }

/*
   MOTION

   The native dialog cuts in. At this size that reads as a jump: the whole
   page is replaced in one frame with no indication of where the panel came
   from. It rises and fades instead, which takes 180ms and tells the reader
   the panel arrived on top of the page rather than instead of it.

   @starting-style is what makes an entry animation possible at all - it
   supplies the "before" state for an element that has only just been given a
   box. Browsers that do not know the rule ignore it and the dialog simply
   appears, which is the behaviour we had.

   The exit is separated into an @supports block because it needs display and
   overlay to animate discretely. Without allow-discrete the dialog would be
   removed from the top layer on the first frame and there would be nothing
   left to fade; with the guard, a browser lacking it keeps the entry
   animation rather than losing both.

   --transition-enter is 0ms under prefers-reduced-motion, so the panel still
   opens and closes - it just does not travel.
*/
.dialog {
  opacity: 0;
  transform: translateY(var(--space-4));
  transition: opacity var(--transition-enter),
              transform var(--transition-enter);
}
.dialog[open] {
  opacity: 1;
  transform: translateY(0);
}
@starting-style {
  .dialog[open] {
    opacity: 0;
    transform: translateY(var(--space-4));
  }
}

.dialog::backdrop { background: var(--backdrop); }

@supports (transition-behavior: allow-discrete) {
  .dialog {
    transition: opacity var(--transition-enter),
                transform var(--transition-enter),
                overlay var(--transition-enter) allow-discrete,
                display var(--transition-enter) allow-discrete;
  }

  .dialog::backdrop {
    opacity: 0;
    transition: opacity var(--transition-enter),
                overlay var(--transition-enter) allow-discrete,
                display var(--transition-enter) allow-discrete;
  }
  .dialog[open]::backdrop { opacity: 1; }

  @starting-style {
    .dialog[open]::backdrop { opacity: 0; }
  }
}

.dialog__panel {
  display: flex;
  flex-direction: column;
  max-height: min(92vh, 60rem);
}

.dialog__head {
  position: relative;
  flex: 0 0 auto;
  padding: var(--space-6) var(--space-10) var(--space-5) var(--space-6);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  border-bottom: var(--border-width-accent) solid var(--color-accent-on-dark);
}
.dialog__head :focus-visible { outline-color: var(--color-focus-ring-inverse); }

/* The same device as .eyebrow, in a dialog header. Counts as that use, not
   a third one. */
.dialog__eyebrow {
  margin-bottom: var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--color-accent-on-dark);
}
.dialog__title { font-size: var(--text-2xl); color: var(--color-text-inverse); }

.dialog__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: var(--hairline-inverse);
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text-inverse);
  font-size: var(--text-2xl);
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}
.dialog__close:hover {
  border-color: var(--color-accent-on-dark);
  background: var(--color-surface-inverse-raised);
}
.dialog__close:active { transform: translateY(var(--press-shift)); }

.filters-toggle:active,
.nav-toggle:active { transform: translateY(var(--press-shift)); }
.sidenav__list a:active { color: var(--color-accent-text); }

.dialog__body {
  flex: 1 1 auto;
  padding: var(--space-6);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.dialog__summary {
  margin-bottom: var(--space-4);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  color: var(--color-text-default);
}
.dialog__text { font-size: var(--text-sm); color: var(--color-text-muted); }

.dialog__subheading {
  margin: var(--space-7) 0 var(--space-4);
  padding-bottom: var(--space-2);
  border-bottom: var(--hairline-accent);
  font-size: var(--text-lg);
}
.dialog__footnote {
  margin-top: var(--space-7);
  padding-top: var(--space-4);
  border-top: var(--hairline);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--color-text-subtle);
}

.dialog__foot {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6);
  background: var(--color-surface-default);
  border-top: var(--hairline);
}
.dialog__foot .btn { flex: 1 1 12rem; }

@media (min-width: 48rem) {
  .dialog__foot .btn { flex: 0 1 auto; }
}


/* =============================== 11. TABLE ============================ */

/* Wide tables must scroll inside their own container, never force the page
   to scroll horizontally. */
.table-wrap {
  overflow-x: auto;
  border: var(--hairline);
  background: var(--color-surface-raised);
}
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.table caption {
  padding: var(--space-4) var(--space-5);
  border-bottom: var(--hairline);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-text-muted);
  text-align: left;
}
.table th,
.table td {
  padding: var(--space-3) var(--space-5);
  border-bottom: var(--hairline);
  text-align: left;
  vertical-align: top;
}
.table thead th {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-text-subtle);
  white-space: nowrap;
}
.table tbody tr:last-child td { border-bottom: 0; }
.table td { color: var(--color-text-muted); }
.table--fixed { table-layout: fixed; }


/* =============================== 12. LISTS ============================ */

/* Gold hairline instead of a bullet. */
.list-marker li {
  position: relative;
  padding-left: var(--space-6);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--color-text-muted);
}
.list-marker li:last-child { margin-bottom: 0; }
.list-marker li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.7em;
  width: var(--space-4);
  height: var(--border-width);
  background: var(--color-accent);
}
.on-inverse .list-marker li { color: var(--color-text-inverse-muted); }

/* Numbered, counter-driven so the markup stays clean. */
.list-steps { counter-reset: step; }
.list-steps li {
  counter-increment: step;
  position: relative;
  padding-left: var(--space-7);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--color-text-muted);
}
.list-steps li:last-child { margin-bottom: 0; }
.list-steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-accent-text);
}

/* Numbered with a heading per step. */
.list-steps--titled li {
  padding: var(--space-5) 0 var(--space-5) var(--space-8);
  margin-bottom: 0;
  border-bottom: var(--hairline);
}
.list-steps--titled li:last-child { border-bottom: 0; }
.list-steps--titled li::before { top: var(--space-5); }
.list-steps__title { margin-bottom: var(--space-2); font-size: var(--text-lg); }

/* Rows separated by hairlines. */
.list-divided li {
  padding: var(--space-3) 0;
  border-bottom: var(--hairline);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.list-divided li:last-child { border-bottom: 0; }


/* ======================== 13. SPECIFICATION LIST ====================== */

.spec { border-top: var(--hairline); }
.spec__row {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-4) 0;
  border-bottom: var(--hairline);
}
.spec dt {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-text-subtle);
}
.spec dd { font-size: var(--text-sm); color: var(--color-text-default); }

/* Boxed variant used inside the product dialog. */
.spec--boxed {
  margin-block: var(--space-6);
  padding: var(--space-5);
  background: var(--color-surface-default);
  border: var(--hairline);
}
.spec--boxed .spec__row:last-child { border-bottom: 0; padding-bottom: 0; }

@media (min-width: 34rem) {
  .spec__row { grid-template-columns: 11rem minmax(0, 1fr); gap: var(--space-4); align-items: baseline; }
}


/* =============================== 14. CHIP ============================= */

.chip-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-bottom: var(--space-5);
  margin-bottom: var(--space-5);
  border-bottom: var(--hairline);
}
.chip-bar__label {
  margin: 0 var(--space-2) 0 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  color: var(--color-text-muted);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: var(--hairline-accent);
  border-radius: var(--radius-pill);
  background: var(--color-accent-wash);
  color: var(--color-text-default);
  font-size: var(--text-sm);
  line-height: var(--leading-tight);
  cursor: pointer;
  transition: border-color var(--transition-fast),
              background-color var(--transition-fast),
              transform var(--transition-fast);
}
.chip:hover { border-color: var(--color-accent); background: var(--color-surface-raised); }
.chip:active { transform: translateY(var(--press-shift)); background: var(--color-surface-sunken); }
.chip__group {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-text-muted);
}
.chip__label { font-weight: var(--weight-medium); }
/* Gold would only reach 4.13:1 on the chip's gold wash, so this glyph uses
   the muted text colour. The chip keeps its identity from border and fill. */
.chip__dismiss { font-size: var(--text-md); line-height: 1; color: var(--color-text-muted); }


/* ============================== 15. SEARCH ============================ */

.search__field {
  display: flex;
  align-items: stretch;
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface-raised);
  transition: border-color var(--transition-fast);
}
.search__field:hover { border-color: var(--color-accent); }
/* The whole field reads as one control, so the ring goes round the wrapper. */
.search__field:focus-within {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset);
  border-color: var(--color-accent);
}
.search__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--control-height);
  padding: var(--control-padding-y) var(--control-padding-x);
  border: 0;
  background: none;
  font-size: var(--text-md);
}
.search__input:focus { outline: none; }
.search__input::placeholder { color: var(--color-text-subtle); }
.search__input::-webkit-search-cancel-button { display: none; }

.search__clear {
  flex: 0 0 auto;
  padding-inline: var(--control-padding-x);
  border: 0;
  border-left: var(--hairline);
  background: none;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
}
.search__clear:hover { color: var(--color-text-default); }
.search__clear[hidden] { display: none; }


/* =========================== 16. FILTER GROUP ========================= */

.filters {
  padding: var(--space-5);
  background: var(--color-surface-raised);
  border: var(--hairline);
  border-top: var(--border-width-accent) solid var(--color-accent);
}
.filters__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-2);
  border-bottom: var(--hairline);
}
.filters__heading {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
}

/* Native <details> gives collapsible groups with keyboard support and no JS. */
.filter-group { border-bottom: var(--hairline); }
.filter-group:last-child { border-bottom: 0; }

.filter-group__summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  cursor: pointer;
  list-style: none;
}
.filter-group__summary::-webkit-details-marker { display: none; }
.filter-group__summary:focus-visible { outline-offset: calc(-1 * var(--focus-ring-offset)); }
.filter-group__label { flex: 1 1 auto; }

.filter-group__chevron {
  flex: 0 0 auto;
  width: 0.55rem;
  height: 0.55rem;
  border-right: 1.5px solid var(--color-accent);
  border-bottom: 1.5px solid var(--color-accent);
  transform: rotate(45deg);
  transition: transform var(--transition-fast);
}
.filter-group[open] .filter-group__chevron { transform: rotate(-135deg); }

.filter-group__fieldset { border: 0; margin: 0; padding: 0 0 var(--space-4); }
.filter-group__hint {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-text-subtle);
}

.filter-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  cursor: pointer;
}
.filter-option input[type="checkbox"] {
  flex: 0 0 auto;
  width: 1.0625rem;
  height: 1.0625rem;
  margin: 0.2rem 0 0;
  accent-color: var(--color-surface-inverse);
  cursor: pointer;
}
.filter-option__text { flex: 1 1 auto; }
.filter-option__facet {
  flex: 0 0 auto;
  padding-top: 0.15rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}
/* An option that cannot currently return anything. Still readable, clearly
   unavailable, and disabled unless already ticked so it can be removed. */
.filter-option--empty { color: var(--color-text-subtle); }
.filter-option--empty input[type="checkbox"] { cursor: not-allowed; }


/* ======================= 17. TOOLBAR AND RESULTS ====================== */

.toolbar {
  display: grid;
  gap: var(--space-4);
  margin-bottom: var(--space-7);
  padding-bottom: var(--space-7);
  border-bottom: var(--hairline);
}

.filters-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--control-height);
  padding: var(--control-padding-y) var(--control-padding-x);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface-raised);
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  cursor: pointer;
}
.filters-toggle__label { flex: 1 1 auto; text-align: left; }
.filters-toggle[aria-expanded="false"] + .filters { display: none; }
.filters-toggle[aria-expanded="true"] { margin-bottom: var(--space-3); }

.results__count {
  margin-bottom: var(--space-5);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

@media (min-width: 48rem) {
  .toolbar { grid-template-columns: minmax(0, 1fr) 14rem; align-items: end; }
}

@media (min-width: 64rem) {
  .filters-toggle { display: none; }
  /* Both aria-expanded states are listed so this matches the specificity of
     the collapsing rule above and, coming later, wins. */
  .filters-toggle[aria-expanded="false"] + .filters,
  .filters-toggle[aria-expanded="true"] + .filters { display: block; }

  .filters {
    position: sticky;
    top: var(--sticky-offset);
    max-height: calc(100vh - var(--sticky-offset) - var(--space-5));
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

@media (max-height: 40rem) {
  .site-header { position: static; }
  .filters { position: static; max-height: none; }
}


/* ============================== 18. LOGO ============================== */

/*
   The wordmark. No approved logo has been supplied, so the production build
   ships the name set in the display face - a logotype, which is a real and
   defensible answer - while the prototype build adds .logo--slot to make the
   absence obvious to reviewers.

   The dashed variant must never reach production: a box labelled
   "placeholder" in the header of every page undoes the impression the rest
   of the design is working to create. scripts/build-site.ps1 chooses, and
   the production placeholder scan fails the build if the note survives.
*/
.logo {
  display: inline-flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: var(--space-3) 0;
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}
.logo:hover { color: var(--color-accent-text); }
.logo:active { transform: translateY(var(--press-shift)); }

.logo__name {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: var(--leading-tight);
  letter-spacing: 0.01em;
}

/* Prototype only. */
.logo--slot {
  padding-inline: var(--control-padding-x);
  border-style: dashed;
  border-color: var(--color-border-strong);
}
.logo--slot:hover { border-color: var(--color-accent); }

.logo__note {
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  color: var(--color-text-subtle);
}


/* ========================= 19. CALL TO ACTION ========================= */

.cta {
  padding-block: var(--space-10);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  border-top: var(--border-width-accent) solid var(--color-accent);
}
.cta__inner { max-width: 44rem; }
.cta__title {
  margin-bottom: var(--space-4);
  font-size: var(--text-3xl);
  color: var(--color-text-inverse);
}
.cta__text {
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  color: var(--color-text-inverse-muted);
}


/* ============================== 20. PERSON ============================ */

.person__role {
  margin-bottom: var(--space-3);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-accent-on-dark);
}
.person__name { margin-bottom: var(--space-3); font-size: var(--text-2xl); color: var(--color-text-inverse); }
.person__credential {
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: var(--hairline-inverse);
  font-size: var(--text-sm);
  color: var(--color-accent-on-dark);
}


/* =============================== 21. MEDIA ============================ */

/*
   Images. One component, so no page ever writes its own image rules.

   Every image declares intrinsic width and height in the markup, which lets
   the browser reserve the correct box before the file arrives. aspect-ratio
   here keeps that box correct while the image scales, so nothing on the page
   moves as photography loads.

   object-fit: cover on the square variant crops to fill without distorting.
   Faces are never stretched; if a crop is wrong, fix the source, not the CSS.
*/

.media { margin: 0; }

.media__image {
  display: block;
  width: 100%;
  height: auto;
  background: var(--color-surface-sunken);
}

/* Square portrait, cropped to fill. */
.media--square .media__image {
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

/* A framed figure in a light section: hairline plus the gold top rule that
   marks considered content elsewhere in the system. */
.media--framed {
  border: var(--hairline);
  border-top: var(--border-width-accent) solid var(--color-accent);
  background: var(--color-surface-raised);
}

.media__caption {
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-xs);
  color: var(--color-text-subtle);
}

/* .card__media - an image bled to a card's edges - was removed in Sprint 2.
   The only page using it was About, whose team block is now a split layout
   with the portrait beside the text rather than above it. Reinstating it
   means restoring the negative insets AND expressing them with
   --card-padding, which steps at a breakpoint: a bleed that repeats the
   padding value literally stops bleeding the next time the padding changes. */


/* ============================ 22. EMPTY STATE ========================= */

/*
   A reusable empty state for any module: a search with no results, a filter
   that excludes everything, a list not yet populated.

   MOTION
   Fades and rises on entry, fades on exit. The exit is why this cannot be a
   plain [hidden] toggle - an element with display:none cannot animate out -
   so visibility is driven by a data-state attribute and display is never
   switched. --transition-enter becomes 0ms under prefers-reduced-motion, so
   the state still changes instantly without movement.

   NO LAYOUT SHIFT
   The container reserves its space with grid-template-rows rather than
   collapsing, and the artwork declares width and height so the browser can
   reserve the correct box before the file loads. An empty state that reflows
   the page as it appears is worse than no animation at all.

   ACCESSIBILITY
   The host marks the region aria-live="polite" so the change is announced.
   The artwork is decorative and carries an empty alt, because the headline
   immediately below already says the same thing - announcing it twice is
   noise, not help.
*/

.empty-state {
  display: grid;
  justify-items: center;
  gap: var(--space-5);
  padding: var(--space-9) var(--space-6);
  text-align: center;
  background: var(--color-surface-raised);
  border: var(--hairline);
  border-top: var(--border-width-accent) solid var(--color-accent);
}

/* Hidden by default, without display:none, so it can animate both ways. */
.empty-state[data-state="hidden"] {
  opacity: 0;
  visibility: hidden;
  transform: translateY(var(--space-3));
  pointer-events: none;
}
.empty-state[data-state="visible"] {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}
.empty-state {
  transition: opacity var(--transition-enter),
              transform var(--transition-enter),
              visibility 0s linear var(--transition-enter);
}
.empty-state[data-state="visible"] {
  transition: opacity var(--transition-enter),
              transform var(--transition-enter),
              visibility 0s;
}

/* Artwork. max-width keeps it from ever exceeding its natural size, and
   height:auto with an aspect ratio from the intrinsic attributes prevents
   distortion at every breakpoint. */
.empty-state__figure {
  width: 100%;
  max-width: 18rem;
  margin: 0;
}
.empty-state__image {
  width: 100%;
  height: auto;
  max-width: 100%;
  object-fit: contain;
}

.empty-state__title { font-size: var(--text-2xl); }

.empty-state__body {
  max-width: 46ch;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.empty-state__body + .empty-state__body { margin-top: var(--space-3); }

.empty-state__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* Narrow screens: smaller artwork, tighter padding, full-width actions so
   the two buttons stack rather than crowd. */
@media (max-width: 34rem) {
  .empty-state { padding: var(--space-7) var(--space-5); }
  .empty-state__figure { max-width: 12rem; }
  .empty-state__actions { width: 100%; }
  .empty-state__actions .btn { flex: 1 1 100%; }
}

@media (min-width: 64rem) {
  .empty-state__figure { max-width: 20rem; }
}


/* ========================= 23. LOADING SKELETON ======================= */

/*
   Placeholder cards shown while the catalogue is being fetched.

   The skeleton reuses .card, so what appears during the wait is the same
   shape, border and elevation as what replaces it. A placeholder that does
   not match the thing it stands in for produces a visible jolt on arrival,
   which is the problem it was meant to solve.

   It is DELAYED by the component that draws it, and never appears at all on
   a fast load. See js/components/productGrid.js.

   It is also aria-hidden. The result count above the grid is already a live
   region announcing "Loading the catalogue"; announcing six empty cards as
   well would be noise.
*/

.skeleton-card {
  gap: var(--space-3);
  animation: skeleton-pulse var(--pulse-duration) ease-in-out infinite;
}

.skeleton__line {
  display: block;
  width: 100%;
  height: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
}

/* Uneven lengths, so it reads as text rather than as a barcode. */
.skeleton__line--badge  { width: 35%; height: var(--space-3); }
.skeleton__line--title  { width: 80%; height: var(--space-6); margin-bottom: var(--space-1); }
.skeleton__line--short  { width: 60%; }
.skeleton__line--action { width: 100%; height: var(--control-height); margin-top: auto; }

/* Opacity rather than a moving gradient: a sweep needs a colour stop, and
   colours live in tokens.css. This carries the same "working" signal with
   nothing to re-theme. */
@keyframes skeleton-pulse {
  0%,
  100% { opacity: 1; }
  50%  { opacity: 0.45; }
}

/* Movement removed, the placeholder kept. Something must still occupy the
   space or the layout jumps when the results land. */
@media (prefers-reduced-motion: reduce) {
  .skeleton-card { animation: none; }
}


/* ========================== 24. STATE PANEL =========================== */

/*
   A whole-page system state: 404, maintenance, and any future page whose job
   is to explain that the thing you wanted is not here.

   Distinct from .empty-state, which sits INSIDE a module and swaps in and out
   as results change. This one owns the page, so it is a two-column layout at
   width rather than a centred block, and it does not animate: there is
   nothing for it to transition from.

   The artwork is decorative and carries an empty alt. Every state's wording
   is live text immediately beside it, so describing the photograph as well
   would announce the same thing twice.
*/

.state-panel {
  display: grid;
  gap: var(--space-7);
  align-items: center;
  max-width: var(--wrap-narrow);
  margin-inline: auto;
}

.state-panel__figure {
  justify-self: center;
  width: 100%;
  max-width: 15rem;
}

.state-panel__body > .section__actions { margin-bottom: var(--space-7); }

.state-panel__more {
  padding-top: var(--space-6);
  border-top: var(--hairline);
}
.state-panel__more .sidenav__heading { border-bottom: 0; padding-bottom: 0; }

@media (min-width: 48rem) {
  .state-panel {
    grid-template-columns: 15rem minmax(0, 1fr);
    gap: var(--space-8);
    align-items: start;
    max-width: none;
  }
  .state-panel__figure { max-width: none; }
}


/* =============================== 25. HERO ============================= */

.hero__lede {
  max-width: var(--measure-short);
  margin-block: var(--space-6) var(--space-7);
}
/* .hero__stats and the .stat__label / .stat__value pair were removed in
   Sprint 2. Content pack section 7 replaced the hero's label-and-value rows
   with three plain trust signals; see .trust-signals below. */

/* Trust signals. Three short statements of standing, not statistics - so they
   are a plain list rather than the label-and-value pairs that were here
   before. Two of those pairs said "Written for: UK employers" and "Available
   as: Advice or documents", which spent the most valuable space on the site
   restating what the page above already made obvious. */
.trust-signals {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-7);
  margin-top: var(--space-9);
  padding-top: var(--space-6);
  border-top: var(--hairline-inverse);
}
.trust-signal {
  position: relative;
  padding-left: var(--space-5);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  color: var(--color-text-inverse);
}
.trust-signal::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: var(--space-3);
  height: var(--border-width-accent);
  background: var(--color-accent-on-dark);
}




/* ============================== 26. PROSE ============================= */

/* Long-form content: service detail pages, profiles, legal pages. Paired
   with the .doc layout primitive. */
.doc__lede {
  margin-bottom: var(--space-6);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  color: var(--color-text-default);
}
.doc__heading {
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-2);
  border-bottom: var(--hairline-accent);
  font-size: var(--text-2xl);
}
.doc p { color: var(--color-text-muted); }
.doc__block {
  padding: var(--space-6);
  background: var(--color-surface-raised);
  border: var(--hairline);
}
/* Images embedded in article and profile bodies via the Content Studio
   editor. Intrinsic width/height in the markup plus height:auto here keeps
   the aspect ratio and reserves the box, so nothing shifts as it loads. */
.doc__image {
  display: block;
  max-width: 100%;
  height: auto;
  margin: var(--space-6) auto;
  border: var(--hairline);
  border-radius: var(--radius-sm);
}


/* =========================== 27. FOOTER PARTS ========================= */

.site-footer__brand {
  margin-bottom: var(--space-3);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  color: var(--color-text-inverse);
}
.site-footer__descriptor {
  margin-bottom: var(--space-4);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  color: var(--color-accent-on-dark);
}
.site-footer__heading {
  margin-bottom: var(--space-3);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--color-accent-on-dark);
}
/* Direct contact details. These are the routes that actually work, so they
   sit in the footer of every page rather than only on the contact page. */
.site-footer__contact li { margin-bottom: var(--space-3); }
.site-footer__contact a {
  font-size: var(--text-sm);
  text-decoration: none;
  border-bottom: var(--border-width) solid transparent;
}
.site-footer__contact a:hover { border-bottom-color: var(--color-accent-on-dark); }

.site-footer__links li { margin-bottom: var(--space-3); }
.site-footer__links a {
  font-size: var(--text-sm);
  text-decoration: none;
  border-bottom: var(--border-width) solid transparent;
}
.site-footer__links a:hover { border-bottom-color: var(--color-accent-on-dark); }
.site-footer__text,
.site-footer__copyright,
.site-footer__statutory {
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--color-text-inverse-muted);
}
.site-footer__copyright { margin-bottom: var(--space-2); }
.site-footer__disclaimer { max-width: var(--measure-short); }


/* ======================= 28. ENYALIUS PRODUCT SURFACES ================= */

/* The product page at /enyalius/.

   Enyalius has its own approved identity - navy, graphite, porcelain and
   steel - which is not the consultancy's warm one. The page therefore needs
   its own surfaces, and this is where they live. It is not a fifth
   stylesheet, because a product page is not a reason to make the system five
   layers, and it is not a parallel set of components either.

   THE APPROACH IS THEMES, NOT CLONES. Almost everything below re-points the
   colours of a component that already exists inside one of four surfaces.
   There is no .eny-card, because .card exists. There is no .eny-grid,
   because .grid exists. Only three things here are genuinely new: the brand
   lockup, the hero's two-part title, and the diagram parts - and none of
   those had a component to re-use.

   SCOPE. Every rule is contained by .eny, which wraps the page body only.
   The site header and footer sit outside it and are deliberately untouched:
   this is a Your People Team page about a product, not a second website. */

.eny { display: block; }


/* ---- the four surfaces ------------------------------------------------
   Each sets a ground and the text colour that passes on it. Headings need
   no rule: base.css gives them no colour of their own, so they inherit. */

.eny-dark  { background: var(--color-eny-onyx);      color: var(--color-eny-text); }
.eny-deep  { background: var(--color-eny-graphite);  color: var(--color-eny-text); }
.eny-light { background: var(--color-eny-porcelain); color: var(--color-eny-ink); }

/* Two light bands in succession need a join, not a colour change. */
.eny-light--ruled { border-top: var(--border-width) solid var(--color-eny-stone); }

/* The hero is onyx with the navy wash reading as a light source above and
   left of the mark. Scoped one level deeper than .eny-dark so the wash wins
   over that shorthand regardless of source order. */
.eny .eny-hero {
  background-image: radial-gradient(120% 100% at 12% -15%,
                                    var(--eny-wash) 0%, transparent 62%);
}

/* Focus must stay visible on every one of those grounds. */
.eny-dark :focus-visible,
.eny-deep :focus-visible,
.eny .cta :focus-visible { outline-color: var(--color-eny-steel); }
.eny-light :focus-visible { outline-color: var(--color-eny-line); }


/* ---- typographic helpers inside the surfaces -------------------------- */

.eny-dark .t-lede,  .eny-dark .t-muted,  .eny-dark .t-subtle,
.eny-deep .t-lede,  .eny-deep .t-muted,  .eny-deep .t-subtle {
  color: var(--color-eny-text-muted);
}
.eny-light .t-lede, .eny-light .t-muted, .eny-light .t-subtle {
  color: var(--color-eny-ink-muted);
}

/* The eyebrow carries the brand colour on every surface, so the section
   labels are the quiet thread of navy and steel running down the page. */
.eny-dark .eyebrow,
.eny-deep .eyebrow { color: var(--color-eny-steel); }
.eny-light .eyebrow { color: var(--color-eny-accent); }

.eny-dark .eyebrow__num,
.eny-deep .eyebrow__num {
  color: var(--color-eny-steel);
  border-right-color: var(--eny-veil-line);
}
.eny-light .eyebrow__num {
  color: var(--color-eny-accent);
  border-right-color: var(--color-eny-stone);
}


/* ---- cards ------------------------------------------------------------
   On the dark grounds a card is a veil over the ground rather than an
   opaque panel: that is what makes a dark band read as depth instead of as
   stacked boxes. The card's own 2px top border, transparent by default,
   becomes the steel or navy rule. */

.eny-dark .card,
.eny-deep .card {
  background: var(--eny-veil);
  border-color: var(--eny-veil-line);
  border-top-color: var(--color-eny-steel);
  border-radius: var(--radius-eny);
}
.eny-dark .card__title, .eny-deep .card__title { color: var(--color-eny-text); }
.eny-dark .card__text,  .eny-deep .card__text  { color: var(--color-eny-text-muted); }
.eny-dark .card__head,  .eny-deep .card__head  { border-bottom-color: var(--eny-veil-line); }

.eny-light .card {
  background: var(--color-eny-panel);
  border-color: var(--color-eny-stone);
  border-top-color: var(--color-eny-line);
  border-radius: var(--radius-eny);
}
.eny-light .card__title { color: var(--color-eny-ink); }
.eny-light .card__text  { color: var(--color-eny-ink-muted); }
.eny-light .card__note  { color: var(--color-eny-ink-muted); }
.eny-light .card__head  { border-bottom-color: var(--color-eny-stone); }

/* .card--bare keeps its rule and drops the panel, on either ground. */
.eny-dark .card--bare,
.eny-deep .card--bare {
  background: none;
  border: 0;
  border-top: var(--border-width-accent) solid var(--color-eny-steel);
  border-radius: 0;
}
.eny-light .card--bare {
  background: none;
  border: 0;
  border-top: var(--border-width-accent) solid var(--color-eny-line);
  border-radius: 0;
}


/* ---- lists ------------------------------------------------------------ */

.eny-dark .list-marker li,
.eny-deep .list-marker li { color: var(--color-eny-text-muted); }
.eny-dark .list-marker li::before,
.eny-deep .list-marker li::before { background: var(--color-eny-steel); }

.eny-light .list-marker li { color: var(--color-eny-ink-muted); }
.eny-light .list-marker li::before { background: var(--color-eny-line); }

.eny-deep .list-steps li { color: var(--color-eny-text-muted); }
.eny-deep .list-steps li::before { color: var(--color-eny-steel); }
.eny-deep .list-steps--titled li { border-bottom-color: var(--eny-veil-line); }
.eny-deep .list-steps__title { color: var(--color-eny-text); }


/* ---- specification list, used for the professional-decision rules ----- */

.eny-dark .spec { border-top-color: var(--eny-veil-line); }
.eny-dark .spec__row { border-bottom-color: var(--eny-veil-line); }
.eny-dark .spec dt { color: var(--color-eny-steel); }
.eny-dark .spec dd { color: var(--color-eny-text-muted); }


/* ---- the hero's standing statements ----------------------------------- */

.eny-dark .trust-signals { border-top-color: var(--eny-veil-line); }
.eny-dark .trust-signal { color: var(--color-eny-text); }
.eny-dark .trust-signal::before { background: var(--color-eny-steel); }


/* ---- buttons ----------------------------------------------------------
   Roles are unchanged: --accent is still the main action on a dark ground
   and --primary still the main action on a light one. Only the colours move.
   On dark the main action is porcelain rather than steel, because the one
   thing on the page a reader must not have to hunt for is the way to ask
   for a demonstration. */

.eny .btn { border-radius: var(--radius-eny-sm); }

.eny-dark .btn--accent,
.eny-deep .btn--accent,
.eny .cta .btn--accent {
  background: var(--color-eny-porcelain);
  border-color: var(--color-eny-porcelain);
  color: var(--color-eny-navy);
}
.eny-dark .btn--accent:hover,
.eny-deep .btn--accent:hover,
.eny .cta .btn--accent:hover {
  background: var(--color-eny-stone);
  border-color: var(--color-eny-stone);
}

/* An opaque border, not the decorative veil. .btn sets text-decoration:none,
   so this outline is the only thing distinguishing a secondary action from a
   run of text sitting beside a filled button - it has to clear 1.4.11 at rest,
   not only on hover. */
.eny-dark .btn--secondary-invert,
.eny-deep .btn--secondary-invert,
.eny .cta .btn--secondary-invert {
  border-color: var(--color-eny-border-invert);
  color: var(--color-eny-text);
}
.eny-dark .btn--secondary-invert:hover,
.eny-deep .btn--secondary-invert:hover,
.eny .cta .btn--secondary-invert:hover {
  border-color: var(--color-eny-steel);
  background: var(--eny-veil);
}

.eny-light .btn--primary {
  background: var(--color-eny-navy);
  border-color: var(--color-eny-navy);
  color: var(--color-eny-porcelain);
}
.eny-light .btn--primary:hover {
  background: var(--color-eny-accent);
  border-color: var(--color-eny-accent);
}
.eny-light .btn--secondary {
  border-color: var(--color-eny-line);
  color: var(--color-eny-accent);
}
.eny-light .btn--secondary:hover {
  border-color: var(--color-eny-navy);
  background: var(--color-eny-panel);
}

.eny-dark .btn--quiet,
.eny-deep .btn--quiet { color: var(--color-eny-text-muted); }
.eny-dark .btn--quiet:hover,
.eny-deep .btn--quiet:hover { color: var(--color-eny-text); }


/* ---- the closing band -------------------------------------------------
   Steel, like every other rule on a dark ground in this section. This band
   closes the page for every reader in every state, so a gold rule here would
   make gold the page's default - which is the one thing the approved identity
   says it must never be. Gold appears once, below, on the card about premium
   access. */

.eny .cta {
  background: var(--color-eny-onyx);
  border-top-color: var(--color-eny-steel);
}
.eny .cta__title { color: var(--color-eny-text); }
.eny .cta__text  { color: var(--color-eny-text-muted); }

/* The only gold on the page: the card describing availability and continuation
   licensing, which is a premium and access context and is the sole context the
   approved identity reserves the colour for. A 2px rule and nothing else - at
   4.83:1 on the white card it clears the threshold for a UI component and
   misses the one for text, so it is never given to a word.
   Written to out-specify .eny-light .card rather than to out-order it: this is
   the most identity-critical rule on the page and it should not be undone by
   someone moving a block. */
.eny-light .card.eny-premium { border-top-color: var(--color-eny-gold); }


/* ---- the brand lockup -------------------------------------------------
   The approved horizontal lockup, served as artwork rather than assembled
   here from a shield file and live text. The wordmark master is a font-stack
   <text> element with no outlined paths, and Montserrat ships with no major
   operating system - so live text would resolve to Futura on macOS, Century
   Gothic on Office Windows and Segoe UI on bare Windows: three logotypes for
   one trademark. scripts/generate-enyalius-lockup.ps1 outlines it once, at
   the master's own metrics, and the alt text carries the name.

   Sized by height, because the lockup's proportions are the master's and
   nothing here should be able to change them. */

.eny-lockup {
  width: auto;
  height: var(--space-9);
  margin-bottom: var(--space-6);
}

/* The reversed lockup is white artwork and print paints the ground white, so
   the mark would vanish on paper. Both variants are in the markup and the
   media query chooses; content:url() would put a filename in the stylesheet,
   which the class validator reads as a selector, and is unreliable on
   replaced elements in any case. */
.eny-lockup--ink { display: none; }


/* ---- the hero's two-part title ---------------------------------------- */

.eny-hero__title { display: block; }
/* Size comes from .t-display on the element itself; copying the three
   declarations here would give the display scale two definitions. */
.eny-hero__line {
  display: block;
  max-width: var(--measure-short);
}


/* ---- section heads ----------------------------------------------------
   A heading and its lede have no space between them, because h2 carries no
   bottom margin and .t-lede carries no top one. Everywhere else on the site
   that pairing sits in a .page-head, where .hero__lede supplies the gap. In a
   .section__head nothing does, and three lines of lede jammed under a
   three-line heading reads as one run-on block. */
.eny .section__head .t-lede { margin-top: var(--space-5); }

/* An aside is as tall as what it says. .card sets height:100% so a row of
   cards squares off, which is right in a grid and wrong beside a column of
   prose four times its length - it leaves a panel two-thirds empty. */
.eny .split__aside { height: auto; }


/* ---- two blocks that follow a section's main content ------------------ */

/* The seven things a case brings into one place. Separated from the prose
   above it by the same measure a section head is separated from its body. */
.eny-tiles { margin-top: var(--section-head-space); }

/* The closing qualification under a section - what the practice does not do,
   what the product does not certify. Never a button row, so not
   .section__actions. */
.eny-note { margin-top: var(--section-head-space); }

/* An inline literal in body copy - the [REQUIRED] marker a draft carries where
   a merge field could not be resolved. The house treatment is a warm sunken
   chip, which on porcelain would be the one warm object on the page. */
.eny code {
  background: var(--color-eny-panel);
  border: var(--border-width) solid var(--color-eny-stone);
  border-radius: var(--radius-eny-sm);
  color: var(--color-eny-accent);
}


/* ---- product screenshots ----------------------------------------------
   The house .media--framed carries a gold top rule, which is the practice's
   accent and the one colour this page reserves. Re-pointed, like everything
   else in this section, rather than cloned. The sunken placeholder behind a
   loading image is warm, so that moves too - a screenshot of a navy product
   should not flash beige while it arrives. */

.eny-light .media--framed {
  background: var(--color-eny-panel);
  border-color: var(--color-eny-stone);
  border-top-color: var(--color-eny-line);
  border-radius: var(--radius-eny);
}
.eny-light .media__image { background: var(--color-eny-porcelain); }
.eny-light .media__caption { color: var(--color-eny-ink-muted); }


/* ---- figures and diagrams ---------------------------------------------
   The diagrams restate, in one picture, a sequence that the surrounding
   text already gives in words. That is why they are aria-hidden: to a
   screen reader they would be a second, worse copy of the list beside them. */

/* Hidden by default and shown from 48rem, because below that the diagram is
   narrower than about 330px and labels drawn at 12 units in a 480-unit viewBox
   land under eight pixels. It is removed rather than shrunk, and nothing is
   lost: the figure is aria-hidden in the first place, being a second telling
   of the sequence the prose already gives in words. A diagram too small to
   read is worse than no diagram. */
.eny-figure {
  display: none;
  margin: 0;
  padding: var(--space-5);
  background: var(--eny-veil);
  border: var(--border-width) solid var(--eny-veil-line);
  border-radius: var(--radius-eny-lg);
}
/* This override belongs AFTER the rule it overrides. A media query adds no
   specificity, so the same selector placed earlier in the file simply loses. */
@media (min-width: 48rem) {
  .eny-figure { display: block; }
}

.eny-figure__caption {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: var(--border-width) solid var(--eny-veil-line);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--color-eny-text-muted);
}

/* Diagram parts. Type sizes are SVG attributes, not CSS, so the labels
   scale with the viewBox instead of drifting out of their panels. */
.eny-dia {
  width: 100%;
  max-width: var(--eny-diagram-width);
  height: auto;
  margin-inline: auto;
}
.eny-dia__panel { fill: var(--eny-veil); stroke: var(--eny-veil-line); }
.eny-dia__panel--key { fill: none; stroke: var(--color-eny-steel); }
.eny-dia__edge { fill: none; stroke: var(--eny-veil-line); }
.eny-dia__arrow { fill: var(--color-eny-steel); }
.eny-dia__title { fill: var(--color-eny-text); font-family: var(--font-body); font-weight: var(--weight-semi); }
.eny-dia__meta { fill: var(--color-eny-text-muted); font-family: var(--font-body); }
.eny-dia__key { fill: var(--color-eny-steel); font-family: var(--font-body); font-weight: var(--weight-semi); }
.eny-dia__num { fill: var(--color-eny-steel); font-family: var(--font-mono); }


/* ---- print ------------------------------------------------------------
   Everything above paints light text on a dark ground, which on paper is a
   page of ink with white words cut out of it.

   Resetting the grounds is not enough on its own. Every colour this section
   re-points does so through a descendant selector, which outranks the ground
   rule - so a band whose background has gone white still holds text at
   #E9EDF3, and prints as nothing. The list below is long for that reason, and
   is written out rather than starred so it stays reviewable. */

@media print {
  .eny-dark, .eny-deep, .eny-light, .eny .cta {
    background: var(--color-print-paper);
    color: var(--color-print-ink);
  }

  /* The hero's navy wash is a background-image and survives the shorthand
     above. A full-bleed gradient is a page of wasted ink that reads as a
     printer fault rather than as a design. */
  .eny .eny-hero { background-image: none; }

  .eny .t-lede, .eny .t-muted, .eny .t-subtle,
  .eny .eyebrow, .eny .eyebrow__num,
  .eny .card__title, .eny .card__text, .eny .card__note,
  .eny .list-marker li, .eny .list-steps li, .eny .list-steps__title,
  .eny .spec dt, .eny .spec dd,
  .eny .trust-signal,
  .eny .cta__title, .eny .cta__text,
  .eny .eny-figure__caption,
  .eny .btn { color: var(--color-print-ink); }

  .eny .list-marker li::before,
  .eny .trust-signal::before { background: var(--color-print-rule); }
  .eny .list-steps li::before { color: var(--color-print-ink); }
  .eny .trust-signals,
  .eny .spec, .eny .spec__row,
  .eny .list-steps--titled li,
  .eny .eyebrow__num,
  .eny .eny-figure__caption,
  .eny-light--ruled,
  .eny .cta { border-color: var(--color-print-rule); }

  /* A filled button is a solid block of ink for a control nobody can press. */
  .eny .btn { background: none; border-color: var(--color-print-rule); }

  .eny .card { background: none; border-color: var(--color-print-rule); }
  /* display, not only colour: below 48rem the figure is display:none, and a
     print job must not depend on which min-width the printing device reports. */
  .eny .eny-figure { display: block; background: none; border-color: var(--color-print-rule); }

  /* The diagrams are drawn in veils and steel, both of which are white-ish.
     Inked, they carry over to paper; left alone they print as empty boxes. */
  .eny .eny-dia__panel { fill: none; stroke: var(--color-print-rule); }
  .eny .eny-dia__panel--key { fill: none; stroke: var(--color-print-ink); }
  .eny .eny-dia__edge { stroke: var(--color-print-rule); }
  .eny .eny-dia__arrow { fill: var(--color-print-ink); }
  .eny .eny-dia__title,
  .eny .eny-dia__key,
  .eny .eny-dia__meta,
  .eny .eny-dia__num { fill: var(--color-print-ink); }

  /* The reversed lockup is white artwork and the ground is now white. */
  .eny-lockup { display: none; }
  .eny-lockup--ink { display: block; }
}
