/* ══════════════════════════════════════════════════════════════════════════
   app.css — the SJMC application layer.

   Loads the imported Industry design system into the `components` cascade
   layer so Tailwind utilities (which sit in the later `utilities` layer) can
   override it without !important, then builds the editorial site layer on
   top: patterns, display typography, bands, cards, and motion.

   Every colour here reads a custom property. Those properties are generated
   by backend/palette.php from the five hex values in config.php and injected
   as an unlayered :root block by Components/head.php, so this file never
   needs editing to re-skin the site.
   ══════════════════════════════════════════════════════════════════════ */
@import url('industry.css') layer(components);

/* ══ Ground ═════════════════════════════════════════════════════════════ */

html {
  background-color: var(--color-page);
  /* Anchor jumps glide instead of snapping; neutralised for reduced motion
     at the bottom of this file. */
  scroll-behavior: smooth;
  /* Sticky nav must not cover the target of an anchor link. */
  scroll-padding-top: 96px;
  -webkit-text-size-adjust: 100%;
  /* Deliberately NO overflow-x here (and none on body): `overflow` on either
     element breaks `position: sticky` for the navbar. Wide content is
     contained at the source instead — every table and diagram lives in a
     `.scroll-x` wrapper that scrolls itself. */
}

body.site-body {
  /* Transparent so the fixed pattern layer below shows the html ground. */
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
  min-height: 100vh;
}

/* Content sits above the fixed pattern layer.
   The navbar is deliberately NOT included: `body.site-body > *` has
   specificity (0,1,1) and would beat `.site-nav` (0,1,0), silently
   overriding `position: sticky` with `relative` and killing the sticky
   header. The nav supplies its own stacking via `sticky` + `z-index: 50`. */
body.site-body > main,
body.site-body > footer { position: relative; z-index: 1; }

/* ══ Background patterns ════════════════════════════════════════════════
   Set SITE_PATTERN in config.php to 'topo' | 'grid' | 'dots' | 'hex' |
   'none'. Strength is multiplied down hard — these are meant to sit right
   at the threshold of perception, never to compete with the text.
   ═══════════════════════════════════════════════════════════════════════ */

.site-body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: var(--pattern-color);
  opacity: calc(var(--pattern-strength, 0.3) * 0.085);
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
}
.site-body.pattern-none::before { display: none; }

/* Topographic contour lines. */
.site-body.pattern-topo::before {
  -webkit-mask-image: var(--mask-topo);
  mask-image: var(--mask-topo);
  -webkit-mask-size: 520px;
  mask-size: 520px;
}
/* Hexagonal mesh. */
.site-body.pattern-hex::before {
  -webkit-mask-image: var(--mask-hex);
  mask-image: var(--mask-hex);
  -webkit-mask-size: 56px;
  mask-size: 56px;
}

/* Grid and dots need no mask — CSS gradients take the palette directly, so
   they tint themselves. */
.site-body.pattern-grid::before {
  background-color: transparent;
  opacity: 1;
  background-image:
    linear-gradient(to right,  var(--pattern-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--pattern-line) 1px, transparent 1px);
  background-size: 64px 64px;
}
.site-body.pattern-dots::before {
  background-color: transparent;
  opacity: 1;
  background-image: radial-gradient(var(--pattern-dot) 1.4px, transparent 1.5px);
  background-size: 26px 26px;
}

:root {
  --pattern-line: color-mix(in srgb, var(--pattern-color) 7%, transparent);
  --pattern-dot:  color-mix(in srgb, var(--pattern-color) 12%, transparent);

  /* Two irregular contour clusters at different rotations. Nesting one
     hand-drawn blob at rising scales reads as terrain, where concentric
     ellipses would read as bullseyes. */
  --mask-topo: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='520' height='520' viewBox='0 0 520 520'%3E%3Cg fill='none' stroke='%23fff' stroke-width='1.5' stroke-linejoin='round'%3E%3Cg transform='translate(150 165) rotate(-14)'%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(.22)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(.44)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(.66)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(.9)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(1.18)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(1.5)'/%3E%3C/g%3E%3Cg transform='translate(395 390) rotate(28)'%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(.28)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(.55)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(.84)'/%3E%3Cpath d='M0-92C44-96 86-64 92-22 98 20 74 66 30 84-14 102-64 88-84 50-104 12-96-40-62-70-44-86-22-90 0-92Z' transform='scale(1.15)'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");

  --mask-hex: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='97' viewBox='0 0 56 97'%3E%3Cg fill='none' stroke='%23fff' stroke-width='1.3'%3E%3Cpath d='M28 1 L54 16 L54 46 L28 61 L2 46 L2 16 Z'/%3E%3Cpath d='M28 61 L28 91 M28 91 L54 106 M28 91 L2 106'/%3E%3C/g%3E%3C/svg%3E");
}

/* ══ Editorial typography ═══════════════════════════════════════════════ */

.display {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.022em;
  text-wrap: balance;
  margin: 0;
  color: var(--color-ink);
}
.display-xl { font-size: clamp(2.6rem, 6.2vw, 5rem); }
.display-lg { font-size: clamp(2.1rem, 4.4vw, 3.4rem); }
.display-md { font-size: clamp(1.65rem, 3vw, 2.35rem); }

/* Small uppercase label that opens a section. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--color-secondary-text);
  margin: 0 0 14px;
}
.eyebrow::before {
  content: "";
  width: 28px;
  height: 2px;
  background: currentColor;
  flex: none;
}
.eyebrow-plain::before { display: none; }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.22rem);
  line-height: 1.62;
  color: var(--color-body);
  max-width: 62ch;
  text-wrap: pretty;
}
.prose { max-width: 68ch; }
.prose p { margin: 0 0 1.1em; color: var(--color-body); }
.prose p:last-child { margin-bottom: 0; }
.prose strong { color: var(--color-ink); font-weight: 600; }

/* A heavier rule used to close a masthead. */
.rule-strong {
  height: 3px;
  border: 0;
  margin: 0;
  background: var(--color-ink);
  opacity: 0.9;
}

/* ══ Layout ═════════════════════════════════════════════════════════════ */

.shell {
  width: 100%;
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: 20px;
}
@media (min-width: 768px)  { .shell { padding-inline: 40px; } }
@media (min-width: 1280px) { .shell { padding-inline: 56px; } }

.section     { padding-block: clamp(56px, 8vw, 104px); }
.section-sm  { padding-block: clamp(38px, 5vw, 64px); }

/* Alternating band in the palette's ground colour. */
.band {
  background: var(--color-band);
  border-block: 1px solid var(--color-hairline);
}
.band-tint { background: var(--color-accent-50); }
.band-ink {
  background: var(--color-accent-900);
  color: var(--color-accent-50);
}
.band-ink .display,
.band-ink .stat-num { color: #fff; }
.band-ink .lede,
.band-ink .prose p  { color: color-mix(in srgb, #fff 78%, transparent); }
.band-ink .eyebrow  { color: var(--color-accent-300); }

@layer components {
  /* ══ Components ═════════════════════════════════════════════════════════
     The imported system draws components as transparent wireframes. For a
     production site they get real surfaces, hairlines and elevation.
     ═══════════════════════════════════════════════════════════════════════ */

  .card, .dialog {
    background: var(--color-elevated);
    border: 1px solid var(--color-hairline);
    border-radius: 3px;
  }

  /* Editorial surface card with a hover lift. */
  .panel {
    background: var(--color-elevated);
    border: 1px solid var(--color-hairline);
    border-radius: 3px;
    padding: clamp(20px, 2.4vw, 30px);
    transition: transform .32s cubic-bezier(.22,1,.36,1),
                box-shadow .32s cubic-bezier(.22,1,.36,1),
                border-color .32s ease;
  }
  a.panel, .panel-interactive { cursor: pointer; text-decoration: none; display: block; }
  a.panel:hover, .panel-interactive:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
    border-color: color-mix(in srgb, var(--color-accent) 40%, transparent);
  }
  a.panel:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }

  /* Buttons — filled, with real affordance. */
  .btn {
    border-radius: 3px;
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.95rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 11px 22px;
    min-height: 44px;             /* touch target */
    cursor: pointer;
    transition: background-color .2s ease, color .2s ease,
                border-color .2s ease, box-shadow .2s ease;
  }
  .btn-primary {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-on-accent);
  }
  .btn-primary:hover  { background: var(--color-accent-700); border-color: var(--color-accent-700); }
  .btn-primary:active { background: var(--color-accent-800); }
  .btn-secondary {
    background: transparent;
    border-color: color-mix(in srgb, var(--color-ink) 30%, transparent);
    color: var(--color-ink);
  }
  .btn-secondary:hover { background: color-mix(in srgb, var(--color-ink) 6%, transparent); }
  .btn-ghost { color: var(--color-ink-link); text-transform: none; letter-spacing: 0; }
  .btn-on-ink {
    background: #fff;
    border-color: #fff;
    color: var(--color-accent-900);
  }
  .btn-on-ink:hover { background: var(--color-accent-100); border-color: var(--color-accent-100); }

  .tag {
    border-radius: 999px;
    padding: 4px 12px;
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.72rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
  }
  .tag-accent   { background: var(--color-accent-100);   color: var(--color-accent-800); }
  .tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
  .tag-neutral  { background: var(--color-neutral-100);  color: var(--color-neutral-800); }
  .tag-outline  { border: 1px solid currentColor; color: var(--color-ink-link); background: transparent; }

  .input {
    background: var(--color-elevated);
    border-radius: 3px;
    min-height: 44px;
    font-size: 16px;              /* never trigger iOS zoom */
  }
}

@layer components {
  /* ══ Spec sheet — the numbered data table from the design ═══════════════ */

  .spec {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.97rem;
    background: var(--color-elevated);
  }
  .spec th {
    text-align: left;
    font-family: var(--font-heading);
    font-size: 0.72rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--color-body);
    padding: 12px 16px;
    border-bottom: 2px solid var(--color-ink);
    white-space: nowrap;
  }
  .spec td {
    padding: 16px;
    border-bottom: 1px solid var(--color-hairline);
    vertical-align: top;
  }
  .spec tbody tr { transition: background-color .18s ease; }
  .spec tbody tr:hover { background: var(--color-accent-50); }
  .spec .spec-no {
    font-family: var(--font-heading);
    font-variant-numeric: tabular-nums;
    color: var(--color-secondary-text);
    font-weight: 600;
    width: 56px;
  }
  .spec .spec-name { font-weight: 600; color: var(--color-ink); }

  /* ══ Stats ══════════════════════════════════════════════════════════════ */

  .stat-num {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: clamp(2.4rem, 4.4vw, 3.6rem);
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--color-accent);
    font-variant-numeric: tabular-nums;
  }
  .stat-label {
    font-family: var(--font-heading);
    font-size: 0.82rem;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--color-body);
    margin-top: 8px;
  }

  /* ══ Numbered step list ═════════════════════════════════════════════════ */

  .step { position: relative; padding-left: 62px; }
  .step::before {
    content: attr(data-step);
    position: absolute;
    left: 0;
    top: 0;
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--color-accent);
    color: var(--color-on-accent);
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.95rem;
  }
  /* Connector line between steps. */
  .step:not(:last-child)::after {
    content: "";
    position: absolute;
    left: 21px;
    top: 52px;
    bottom: -22px;
    width: 2px;
    background: color-mix(in srgb, var(--color-accent) 22%, transparent);
  }

  /* ══ Pull quote ═════════════════════════════════════════════════════════ */

  .quote {
    font-family: var(--font-display);
    font-size: clamp(1.2rem, 2.1vw, 1.6rem);
    line-height: 1.45;
    font-style: italic;
    color: var(--color-ink);
    margin: 0;
    text-wrap: pretty;
  }
  .quote-mark {
    font-family: var(--font-display);
    font-size: 3.4rem;
    line-height: 0.6;
    color: var(--color-accent-300);
    display: block;
    margin-bottom: 6px;
  }

  /* ══ Media ══════════════════════════════════════════════════════════════ */

  .media {
    position: relative;
    overflow: hidden;
    background: var(--color-neutral-200);
    border-radius: 3px;
  }
  .media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform .6s cubic-bezier(.22,1,.36,1);
  }
  .media-zoom:hover img { transform: scale(1.045); }

  /* Ink scrim so white type stays legible over any photo. */
  .media-scrim::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top,
      color-mix(in srgb, var(--color-accent-900) 88%, transparent) 0%,
      color-mix(in srgb, var(--color-accent-900) 42%, transparent) 45%,
      transparent 78%);
  }
  .media-scrim > .media-caption { position: absolute; z-index: 2; inset-inline: 0; bottom: 0; }

  .ratio-4-3  { aspect-ratio: 4 / 3; }
  .ratio-3-2  { aspect-ratio: 3 / 2; }
  .ratio-1-1  { aspect-ratio: 1 / 1; }
  .ratio-16-9 { aspect-ratio: 16 / 9; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Navbar
   ══════════════════════════════════════════════════════════════════════ */

.site-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  /* Deliberately opaque: a translucent bar would need backdrop-filter, and
     that creates an isolation group which stops the logo's mix-blend-mode
     (below) from reaching this background. */
  background: var(--color-page);
  border-bottom: 1px solid transparent;
  transition: border-color .3s ease, box-shadow .3s ease;
}
/* Toggled by the IntersectionObserver sentinel in assets/js/motion.js —
   the nav has "left the top of the page". */
.site-nav.is-stuck {
  border-bottom-color: var(--color-hairline);
  box-shadow: var(--shadow-sm);
}
.site-nav__logo {
  display: block;
  height: 50px;
  width: auto;
  transition: height .3s ease;
  /* sjmc-navbar-logo.webp ships with an opaque white background, which would
     read as a white card on the page ground. Multiply drops the white and
     keeps the artwork — valid because the nav is always light. Remove this
     if the logo is ever re-exported with alpha. */
  mix-blend-mode: multiply;
}
.site-nav.is-stuck .site-nav__logo { height: 40px; }
@media (max-width: 767px) {
  .site-nav__logo { height: 34px; }
  .site-nav.is-stuck .site-nav__logo { height: 30px; }
}

.site-nav__link {
  position: relative;
  padding: 8px 2px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-ink);
  text-decoration: none;
  white-space: nowrap;
}
.site-nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--color-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .28s cubic-bezier(.4, 0, .2, 1);
}
.site-nav__link:hover { color: var(--color-ink-link); }
.site-nav__link:hover::after,
.site-nav__link[aria-current="page"]::after { transform: scaleX(1); }
.site-nav__link[aria-current="page"] { color: var(--color-ink-link); }

/* Mobile drawer — CSS-only, driven by the :checked state of a hidden
   checkbox so the nav works with JavaScript disabled. */
.site-nav__toggle { position: absolute; opacity: 0; pointer-events: none; }
.site-nav__burger { display: none; }
.site-nav__burger span {
  display: block; width: 22px; height: 2px; background: var(--color-ink);
  transition: transform .25s ease, opacity .25s ease;
}
.site-nav__toggle:checked ~ * .site-nav__burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.site-nav__toggle:checked ~ * .site-nav__burger span:nth-child(2) { opacity: 0; }
.site-nav__toggle:checked ~ * .site-nav__burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
.site-nav__toggle:focus-visible ~ * .site-nav__burger { outline: 2px solid var(--color-accent); outline-offset: 3px; }

@media (max-width: 1023px) {
  .site-nav__burger { display: flex; flex-direction: column; gap: 4px; }
  .site-nav__menu {
    display: grid;
    grid-template-rows: 0fr;
    overflow: hidden;
    width: 100%;
    transition: grid-template-rows .32s cubic-bezier(.4, 0, .2, 1);
  }
  .site-nav__menu > div { min-height: 0; }
  .site-nav__toggle:checked ~ .site-nav__menu { grid-template-rows: 1fr; }
  .site-nav__links {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 4px 0 12px;
    border-top: 1px solid var(--color-hairline);
  }
  .site-nav__link { padding: 13px 2px; }
  .site-nav__link::after { display: none; }
  .site-nav__link[aria-current="page"] { border-left: 2px solid var(--color-accent); padding-left: 10px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Footer

   Layered, so the Tailwind utilities in Components/foot.php still win —
   the row and chip classes below carry only what would otherwise be a long
   utility string repeated on every row.
   ══════════════════════════════════════════════════════════════════════ */

@layer components {
  .site-footer {
    background: var(--color-surface);
    border-top: 1px solid var(--color-hairline);
  }

  /* Faint dot field in the top-right corner, tinted from the palette. */
  .foot-dots {
    position: absolute;
    top: 0;
    right: 0;
    width: min(280px, 34vw);
    height: 190px;
    pointer-events: none;
    background-image: radial-gradient(var(--color-accent-2-300) 1.4px, transparent 1.5px);
    background-size: 15px 15px;
    -webkit-mask-image: linear-gradient(215deg, #000 10%, transparent 72%);
    mask-image: linear-gradient(215deg, #000 10%, transparent 72%);
    opacity: 0.55;
  }

  /* Circular icon holder that opens a row or a column heading. */
  .foot-chip {
    display: grid;
    place-items: center;
    flex: none;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: var(--color-accent-2-100);
    color: var(--color-accent-2-800);
    font-size: 0.95rem;
  }
  .foot-chip-sm { width: 34px; height: 34px; font-size: 0.8rem; }

  /* One line of a footer list: chip, label, trailing arrow. */
  .foot-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding-block: 11px;
    border-bottom: 1px solid var(--color-hairline);
    color: var(--color-body);
    text-decoration: none;
    transition: color .2s ease;
  }
  .foot-list > :last-child .foot-row { border-bottom: 0; }
  a.foot-row:hover { color: var(--color-ink-link); }
  a.foot-row:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

  .foot-row__trail {
    margin-left: auto;
    flex: none;
    color: var(--color-accent-2-700);
    transition: transform .25s cubic-bezier(.22, 1, .36, 1);
  }
  a.foot-row:hover .foot-row__trail { transform: translateX(4px); }

  /* Ink panel carrying the building sketch, with the mockup's cut corner. */
  .foot-art {
    background: var(--color-accent-900);
    border-radius: 3px 3px 84px 3px;
  }
  /* The PNG is gold line-work on a cream ground. Inverting drops the ground
     to near-black — which `screen` then leaves untouched — and the sepia
     pass pulls the inverted (blue) lines back to gold over the ink panel. */
  .foot-art img {
    filter: invert(1) contrast(1.5) sepia(1) saturate(3.4) hue-rotate(-12deg) brightness(1.1);
    mix-blend-mode: screen;
  }

  .foot-social {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    color: var(--color-accent-900);
    font-size: 1.15rem;
    transition: color .2s ease, transform .2s ease;
  }
  .foot-social:hover { color: var(--color-accent-2-700); transform: translateY(-2px); }
}

/* ══════════════════════════════════════════════════════════════════════════
   Motion — CSS transitions driven by IntersectionObserver
   (assets/js/motion.js adds .is-revealed once an element enters the viewport)
   ══════════════════════════════════════════════════════════════════════ */

[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .7s cubic-bezier(.22, 1, .36, 1),
              transform .7s cubic-bezier(.22, 1, .36, 1);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
[data-reveal="left"]  { transform: translateX(-26px); }
[data-reveal="right"] { transform: translateX(26px); }
[data-reveal="scale"] { transform: scale(.96); }
[data-reveal="mask"]  { transform: none; clip-path: inset(0 0 100% 0); opacity: 1; }
[data-reveal].is-revealed { opacity: 1; transform: none; clip-path: inset(0 0 0 0); }
[data-reveal].is-revealed { will-change: auto; }

/* No-JS fallback: without motion.js nothing would ever be revealed. */
.no-js [data-reveal] { opacity: 1; transform: none; clip-path: none; }

/* ══════════════════════════════════════════════════════════════════════════
   ADMIN SHELL — chrome
   ══════════════════════════════════════════════════════════════════════════
   The registrar's panel is a tool, not a brochure. It drops the warm ground,
   the background pattern and the editorial footer for a cool neutral desk
   with a dark navy rail down the left, so white cards read as objects rather
   than as paper on paper.

   This block is deliberately UNLAYERED, exactly like `.site-nav*` above:
   nothing competes with it, and `body.admin-body` must beat `body.site-body`
   (same specificity, later wins). The *component* overrides further down are
   layered instead, so Tailwind utilities still win over them — admin markup
   is full of `class="btn btn-ghost px-2 py-1 text-xs"`.

   Layout rules that already cost us a bug once, restated:
     · `.admin-shell` is body's only element child, and `body.site-body > main,
       > footer` does not match it — so nothing overrides `position` here.
     · No `overflow` on html/body. The rail and the topbar are `position:
       sticky`, which any overflow ancestor would silently break. Wide tables
       scroll inside `.scroll-x`.
   ══════════════════════════════════════════════════════════════════════ */

body.admin-body {
  background: var(--color-admin-bg);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Thin, palette-tinted scrollbars — the default chrome-grey bars are the
   loudest "unfinished" tell on a dense work screen. */
.admin-body * {
  scrollbar-width: thin;
  scrollbar-color: var(--color-admin-line) transparent;
}
/* The topo pattern belongs to the public site; the panel wants a flat desk. */
body.admin-body::before { display: none; }

.admin-shell {
  --admin-rail: 248px;
  display: grid;
  grid-template-columns: var(--admin-rail) minmax(0, 1fr);
  min-height: 100vh;
}

/* ── The rail ──────────────────────────────────────────────────────────── */

.admin-rail {
  position: sticky;
  top: 0;
  z-index: 45;
  align-self: start;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--color-admin-sidebar);
  color: #fff;
  border-right: 1px solid var(--color-admin-sidebar-line);
}
.admin-rail__brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 18px 20px;
  text-decoration: none;
  color: #fff;
  border-bottom: 1px solid var(--color-admin-sidebar-line);
}
/* The navbar logo ships on an opaque white ground. On the light public bar
   `mix-blend-mode: multiply` drops it; over the dark rail that would erase
   the artwork instead, so the mark is drawn as a lettered tile. */
.admin-rail__mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 4px;
  background: var(--color-admin-sidebar-mark);
  color: var(--color-admin-sidebar);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.86rem;
  letter-spacing: 0.04em;
}
.admin-rail__name {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.02rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.1;
}
.admin-rail__sub {
  display: block;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-admin-sidebar-mark);
}

.admin-rail__nav {
  flex: 1;
  overflow-y: auto;
  padding: 14px 12px;
}
.admin-rail__section {
  display: block;
  padding: 6px 10px 8px;
  font-family: var(--font-heading);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--color-admin-sidebar-idle);
}
.admin-rail__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  margin-bottom: 2px;
  border-radius: 6px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  color: var(--color-admin-sidebar-idle);
  text-decoration: none;
  transition: background-color .18s ease, color .18s ease;
}
.admin-rail__link i {
  width: 18px;
  text-align: center;
  font-size: 0.92rem;
  flex: none;
  opacity: 0.75;
  transition: opacity .18s ease;
}
.admin-rail__link:hover {
  background: color-mix(in srgb, #fff 8%, transparent);
  color: #fff;
}
.admin-rail__link:hover i,
.admin-rail__link[aria-current="page"] i { opacity: 1; }
.admin-rail__link[aria-current="page"] {
  background: color-mix(in srgb, #fff 13%, transparent);
  color: #fff;
  /* A hairline ring lifts the active pill off the rail without a gradient. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #fff 10%, transparent);
}
/* The current page is marked by a bar as well as a fill, so the state does
   not rest on colour alone. */
.admin-rail__link[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 6px;
  bottom: 6px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--color-admin-sidebar-mark);
}
.admin-rail__link:focus-visible {
  outline: 2px solid var(--color-admin-sidebar-mark);
  outline-offset: 2px;
}
.admin-rail__count {
  margin-left: auto;
  min-width: 22px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--color-admin-sidebar-mark);
  color: var(--color-admin-sidebar);
  font-size: 0.7rem;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.admin-rail__foot {
  padding: 14px 16px 18px;
  border-top: 1px solid var(--color-admin-sidebar-line);
}
.admin-rail__who {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-admin-sidebar-idle);
}
.admin-rail__user {
  display: block;
  margin-bottom: 12px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1rem;
  color: #fff;
}
/* Sign-out reads on the dark rail, where .btn-secondary's ink border would
   vanish entirely. */
.admin-rail__out {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 9px 14px;
  border: 1px solid color-mix(in srgb, #fff 42%, transparent);
  border-radius: 4px;
  background: transparent;
  color: #fff;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.85rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color .18s ease, border-color .18s ease;
}
.admin-rail__out:hover {
  background: color-mix(in srgb, #fff 12%, transparent);
  border-color: #fff;
}
.admin-rail__out:focus-visible { outline: 2px solid var(--color-admin-sidebar-mark); outline-offset: 2px; }

/* ── The topbar ────────────────────────────────────────────────────────── */

.admin-main { display: flex; flex-direction: column; min-width: 0; }

.admin-top {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 13px clamp(16px, 3vw, 34px);
  /* Slightly translucent + blurred so content sliding underneath reads as
     depth. Safe here: nothing in the admin panel relies on mix-blend-mode,
     which a backdrop-filter isolation group would break (the public nav does,
     which is why it stays opaque). */
  background: color-mix(in srgb, var(--color-admin-surface) 86%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-admin-line);
}
.admin-top__titles { min-width: 0; margin-right: auto; }
.admin-top__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

.admin-kicker {
  display: block;
  font-family: var(--font-heading);
  font-size: 0.7rem;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--color-admin-muted);
}
/* Display serif, not the condensed heading face: the page title is the one
   place the panel should still sound like the institution it belongs to. */
.admin-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.35rem, 2.2vw, 1.7rem);
  letter-spacing: -0.015em;
  line-height: 1.15;
  margin: 0;
  color: var(--color-ink);
}

.admin-canvas {
  flex: 1;
  padding: clamp(18px, 2.4vw, 30px) clamp(16px, 3vw, 34px) 56px;
}

.admin-foot {
  padding: 16px clamp(16px, 3vw, 34px) 22px;
  border-top: 1px solid var(--color-admin-line);
  background: var(--color-admin-surface);
  color: var(--color-admin-muted);
  font-size: 0.8rem;
}

/* ── Mobile: the rail becomes an off-canvas drawer ─────────────────────────
   Checkbox-driven, like the public nav, so it works with JavaScript off. */

.admin-drawer { position: absolute; opacity: 0; pointer-events: none; }
.admin-burger { display: none; }
.admin-scrim  { display: none; }

@media (max-width: 1023px) {
  .admin-shell { grid-template-columns: minmax(0, 1fr); }

  .admin-rail {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(var(--admin-rail, 248px), 82vw);
    transform: translateX(-100%);
    transition: transform .3s cubic-bezier(.4, 0, .2, 1);
    z-index: 60;
  }
  .admin-drawer:checked ~ .admin-shell .admin-rail { transform: translateX(0); }

  .admin-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: color-mix(in srgb, var(--color-accent-900) 55%, transparent);
    opacity: 0;
    pointer-events: none;
    transition: opacity .3s ease;
  }
  .admin-drawer:checked ~ .admin-scrim { opacity: 1; pointer-events: auto; }

  .admin-burger {
    display: grid;
    place-items: center;
    flex: none;
    width: 42px;
    height: 42px;
    border: 1px solid var(--color-admin-line-strong);
    border-radius: 4px;
    background: var(--color-admin-surface);
    color: var(--color-ink);
    cursor: pointer;
    font-size: 1rem;
  }
  .admin-drawer:focus-visible ~ .admin-shell .admin-burger {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }
}

@layer components {
  /* ══ Admin components ═══════════════════════════════════════════════════
     Scoped to `.admin-body` so the public site keeps the editorial styling
     it already has — the brief was that only the panel needed work. Layered,
     so per-element Tailwind utilities still override these.
     ═══════════════════════════════════════════════════════════════════════ */

  /* Surfaces get a real edge. The shared hairline (ink @10%, 1.24:1 on white)
     left every card floating; these read as bounded objects. */
  .admin-body .card,
  .admin-body .panel,
  .admin-body .dialog {
    background: var(--color-admin-surface);
    border: 1px solid var(--color-admin-line);
    border-radius: 6px;
  }
  /* Two-layer ambient shadow: a crisp 1px contact line plus a soft spread.
     One flat shadow is what made the surfaces read as pasted-on. */
  .admin-body .card,
  .admin-body .panel {
    box-shadow:
      0 1px 1px color-mix(in srgb, var(--color-ink) 4%, transparent),
      0 2px 6px color-mix(in srgb, var(--color-ink) 5%, transparent);
  }
  .admin-body .panel { padding: clamp(16px, 1.8vw, 22px); }
  .admin-body .dialog { border-radius: 8px; }

  /* A card that leads with a heading strip, so grouped controls read as one
     object instead of a floating cluster. `.admin-card` carries no padding of
     its own — the head and body supply theirs — which keeps the strip flush
     with the border instead of relying on negative margins that any Tailwind
     padding utility on the card would then break. */
  .admin-card { padding: 0; gap: 0; overflow: hidden; }
  .admin-card__head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 11px 16px;
    background: var(--color-admin-sunken);
    border-bottom: 1px solid var(--color-admin-line);
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.78rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-admin-muted);
  }
  .admin-card__head h1,
  .admin-card__head h2,
  .admin-card__head h3 { margin: 0; font: inherit; color: inherit; }
  .admin-card__body { padding: 16px; }
  /* The head is a strip, so its actions drop back to the compact size. */
  .admin-card__head .btn { margin-block: -3px; }

  /* The 55%-alpha muted text inherited from the design system measures
     4.30:1 — below AA, and it is used at 11–13px. Scoped here rather than
     fixed globally: the public pages are not part of this brief. */
  .admin-body .text-muted,
  .admin-body .card-meta,
  .admin-body figcaption { color: var(--color-admin-muted); }
  .admin-body .field > label {
    color: var(--color-ink);
    font-weight: 600;
    font-size: 0.78rem;
    letter-spacing: 0.06em;
  }

  /* ── Controls ───────────────────────────────────────────────────────────
     SC 1.4.11 wants 3:1 on the boundary of a control. The inherited input
     border was 1.49:1 and .btn-secondary's 2.02:1 — both invisible on a
     white card, which is most of why the panel felt unfocused. */
  .admin-body .input {
    border: 1px solid var(--color-admin-line-strong);
    border-radius: 4px;
    color: var(--color-ink);
  }
  .admin-body .input::placeholder { color: var(--color-admin-muted); }
  .admin-body .input:hover { border-color: var(--color-accent-400); }
  .admin-body .input:focus-visible,
  .admin-body .input:focus {
    border-color: var(--color-accent);
    outline: 2px solid color-mix(in srgb, var(--color-accent) 38%, transparent);
    outline-offset: 0;
  }
  .admin-body select.input { cursor: pointer; }
  .admin-body textarea.input { min-height: 96px; line-height: 1.5; }

  /* Native controls follow the palette instead of the browser's blue. */
  .admin-body input[type="checkbox"],
  .admin-body input[type="radio"],
  .admin-body input[type="range"] { accent-color: var(--color-accent); }

  /* Sized only where the box is the control the reader clicks. `.radio` and
     `.seg-opt` deliberately collapse their input to 0×0 and draw `.dot` or a
     highlighted label instead — giving those a size would drop a stray native
     box into the middle of the widget. */
  .admin-body td > input[type="checkbox"],
  .admin-body th > input[type="checkbox"] { width: 17px; height: 17px; cursor: pointer; }

  .admin-body .radio .dot { border-color: var(--color-admin-line-strong); }
  /* Round means "pick one", square means "pick several". The imported system
     draws every `.radio .dot` as a circle, so a multi-select filter looked
     like a set of radio buttons — a checkbox gets corners instead. */
  .radio input[type="checkbox"] + .dot { border-radius: 3px; }
  .radio input[type="checkbox"]:checked + .dot {
    box-shadow: none;
    background: var(--color-accent);
    border-color: var(--color-accent);
    position: relative;
  }
  .radio input[type="checkbox"]:checked + .dot::after {
    content: "";
    position: absolute;
    left: 4.5px;
    top: 1px;
    width: 4px;
    height: 8px;
    border: solid var(--color-on-accent);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
  }
  .admin-body .radio input:checked + .dot {
    border-color: var(--color-accent);
    box-shadow: inset 0 0 0 4px var(--color-admin-surface);
  }
  .admin-body .seg { border-color: var(--color-admin-line-strong); border-radius: 4px; }
  .admin-body .seg-opt + .seg-opt { border-left-color: var(--color-admin-line-strong); }
  .admin-body .seg-opt {
    font-family: var(--font-heading);
    font-weight: 600;
    letter-spacing: 0.05em;
    padding: 8px 16px;
  }
  .admin-body .seg-opt:has(input:checked) {
    background: var(--color-accent);
    color: var(--color-on-accent);
  }

  .admin-body .btn {
    border-radius: 5px;
    font-size: 0.85rem;
    letter-spacing: 0.07em;
    cursor: pointer;
  }
  /* Press feedback. Transform only — no width/height animation. */
  .admin-body .btn:active { transform: translateY(1px); }
  .admin-body .btn-secondary {
    background: var(--color-admin-surface);
    border-color: var(--color-admin-line-strong);
    color: var(--color-ink);
  }
  .admin-body .btn-secondary:hover {
    background: var(--color-admin-sunken);
    border-color: var(--color-ink);
  }
  /* An inset top highlight gives the filled button a lit edge — the cheapest
     way to read as "raised" without a gradient. */
  .admin-body .btn-primary {
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent),
      0 1px 2px color-mix(in srgb, var(--color-ink) 22%, transparent);
  }
  .admin-body .btn-danger {
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, #fff 20%, transparent),
      0 1px 2px color-mix(in srgb, var(--color-ink) 22%, transparent);
  }
  .admin-body .btn-ghost {
    border-color: transparent;
    color: var(--color-ink-link);
  }
  .admin-body .btn-ghost:hover {
    background: var(--color-accent-50);
    border-color: var(--color-admin-line);
  }

  /* Destructive actions. Colour alone never carries the meaning — every one
     of these also says "Delete" and is guarded by a confirm(). */
  .btn-danger {
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: var(--color-on-danger);
  }
  .btn-danger:hover  { background: var(--color-danger-700); border-color: var(--color-danger-700); }
  .btn-danger:active { background: var(--color-danger-800); }
  .admin-body .btn-danger-ghost {
    border-color: transparent;
    color: var(--color-danger-text);
  }
  .admin-body .btn-danger-ghost:hover {
    background: var(--color-danger-50);
    border-color: var(--color-danger-200);
    color: var(--color-danger-700);
  }

  /* One size for the buttons that live inside a table row or a photo tile.
     There were three slightly different ones before, which is why the action
     columns never lined up. */
  .btn-mini {
    min-height: 32px;
    padding: 5px 10px;
    font-size: 0.76rem;
    letter-spacing: 0.05em;
    gap: 5px;
    white-space: nowrap;   /* "Check in" must not wrap to two lines */
  }
  /* Nowrap by default: inside a table cell a wrapped action group doubles the
     row height and drops "Delete" onto its own line. Callers that are not in
     a table (the enquiry cards) add Tailwind's `flex-wrap`, which wins from
     the utilities layer. */
  .row-actions { display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; }

  /* ── Chips: one active convention everywhere ──────────────────────────── */

  .chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 6px 13px;
    border: 1px solid var(--color-admin-line-strong);
    border-radius: 999px;
    background: var(--color-admin-surface);
    color: var(--color-ink);
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 0.8rem;
    letter-spacing: 0.05em;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color .18s ease, border-color .18s ease, color .18s ease;
  }
  .chip:hover { background: var(--color-accent-50); border-color: var(--color-accent-400); }
  .chip[aria-current="true"] {
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-on-accent);
  }
  .chip__count {
    padding: 0 6px;
    border-radius: 999px;
    background: var(--color-admin-sunken);
    color: var(--color-admin-muted);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
  }
  .chip[aria-current="true"] .chip__count {
    background: color-mix(in srgb, #fff 24%, transparent);
    color: var(--color-on-accent);
  }
  .chip:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

  .tag-danger { background: var(--color-danger-100); color: var(--color-danger-800); }

  /* ── Stats ──────────────────────────────────────────────────────────────
     The dashboard tiles are links; they need an obvious affordance. */
  .stat-value {
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 2.4rem;
    line-height: 1;
    letter-spacing: -0.01em;
    color: var(--color-ink);
    font-variant-numeric: tabular-nums;
  }
  .card-kicker {
    font-family: var(--font-heading);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-admin-muted);
  }
  .admin-body a.card {
    text-decoration: none;
    cursor: pointer;
    transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease;
  }
  .admin-body a.card:hover {
    border-color: var(--color-accent-400);
    transform: translateY(-2px);
    box-shadow:
      0 1px 1px color-mix(in srgb, var(--color-ink) 4%, transparent),
      0 10px 24px color-mix(in srgb, var(--color-ink) 10%, transparent);
  }
  .admin-body a.card:hover .stat-value { color: var(--color-accent); }
  .admin-body a.card:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }

  /* ── Tables ─────────────────────────────────────────────────────────── */

  .admin-body .table { background: var(--color-admin-surface); }
  .admin-body .table th {
    /* Deliberately not `position: sticky`: the table lives inside a
       `.scroll-x` wrapper, whose `overflow-x: auto` computes overflow-y to
       `auto` as well, so a sticky header would anchor to a box that never
       scrolls vertically — a no-op that only invites confusion later. */
    background: var(--color-admin-sunken);
    color: var(--color-admin-muted);
    font-size: 0.7rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    padding: 10px 14px;
    border-bottom: 1px solid var(--color-admin-line);
  }
  .admin-body .table td {
    padding: 12px 14px;
    /* Row separators are structure, not edges: lighter than the card border
       so a long table reads as one object instead of a stack of boxes. */
    border-bottom: 1px solid color-mix(in srgb, var(--color-admin-line) 55%, transparent);
    vertical-align: middle;
  }
  .admin-body .table tbody tr:last-child td { border-bottom: 0; }
  .admin-body .table tbody tr { transition: background-color .15s ease; }
  .admin-body .table tbody tr:hover { background: var(--color-accent-50); }

  /* Selection was a 9% accent wash — about 1.1:1 against an unselected row,
     which made bulk actions effectively invisible. It now carries a fill AND
     a left marker, so it survives both low contrast and colour blindness. */
  .admin-body .table tbody tr:has(input[type="checkbox"]:checked) {
    background: var(--color-accent-100);
  }
  .admin-body .table tbody tr:has(input[type="checkbox"]:checked) td:first-child {
    box-shadow: inset 3px 0 0 var(--color-accent);
  }

  /* The selection toolbar appears only while something is selected. Doing this
     with :has() rather than JS keeps bulk actions working unscripted. */
  .bulk-bar { display: none; }
  #bulk-form:has(tbody input[name="ids[]"]:checked) .bulk-bar { display: flex; }
  .admin-body .bulk-bar {
    position: sticky;
    top: 74px;
    z-index: 20;
    background: var(--color-accent);
    border-color: var(--color-accent);
    color: var(--color-on-accent);
    box-shadow: var(--shadow-md);
  }
  .admin-body .bulk-bar .btn-secondary {
    background: transparent;
    border-color: color-mix(in srgb, #fff 55%, transparent);
    color: var(--color-on-accent);
  }
  .admin-body .bulk-bar .btn-secondary:hover {
    background: color-mix(in srgb, #fff 15%, transparent);
    border-color: #fff;
  }

  /* ── Alerts ─────────────────────────────────────────────────────────── */

  .alert {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 11px 14px;
    border: 1px solid var(--color-admin-line-strong);
    border-left-width: 4px;
    border-radius: 4px;
    background: var(--color-admin-surface);
    font-size: 0.9rem;
    color: var(--color-ink);
  }
  .alert-danger {
    border-color: var(--color-danger-300);
    border-left-color: var(--color-danger);
    background: var(--color-danger-50);
    color: var(--color-danger-800);
  }
  .alert-info {
    border-color: var(--color-accent-200);
    border-left-color: var(--color-accent);
    background: var(--color-accent-50);
  }

  /* ── Dialogs ────────────────────────────────────────────────────────── */

  /* Don't scroll the page behind an open modal. */
  body:has(.dialog-backdrop) { overflow: hidden; }
  .dialog-backdrop {
    background: color-mix(in srgb, var(--color-accent-900) 55%, transparent);
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
    /* The imported system gives the backdrop no z-index, so it stacked below
       the admin rail (45/60) and the sticky topbar (40) — the topbar painted
       straight over the dialog's title bar. Nothing in the panel may sit above
       a modal. */
    z-index: 100;
  }
  .admin-body .dialog {
    box-shadow: 0 24px 60px color-mix(in srgb, var(--color-ink) 34%, transparent);
    max-height: 92vh;
    overflow: hidden;             /* keep the head/foot strips inside the radius */
  }
  /* Every admin dialog wraps its contents in a <form>, which sits between
     `.dialog` (the flex column with the height cap) and `.dialog-body` (the
     part that should scroll). Without these the form is an unconstrained block
     and a tall form overflows the viewport in both directions — the title bar
     and the Save button both end up off-screen. `min-height: 0` is the part
     that actually matters: a flex item's default `min-height: auto` refuses to
     shrink below its content. */
  .admin-body .dialog > form {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
  }
  /* Padding lives on the three parts, not on `.dialog`. The head and foot
     strips have to reach the dialog's edges; doing that with negative margins
     against the parent's padding breaks the moment `overflow: hidden` is added
     to clip the corner radius — the strips get clipped away instead. */
  .admin-body .dialog { padding: 0; gap: 0; }
  .admin-body .dialog > form { gap: 0; }

  .admin-body .dialog-title {
    flex: none;
    margin: 0;
    padding: 15px 22px;
    border-bottom: 1px solid var(--color-admin-line);
    font-family: var(--font-heading);
    font-size: 1.1rem;
    letter-spacing: 0.03em;
    color: var(--color-ink);
  }
  .admin-body .dialog-body {
    flex: 1;
    min-height: 0;                /* see the note on `.dialog > form` above */
    overflow-y: auto;
    padding: 18px 22px;
    font-size: 0.92rem;
    opacity: 1;
    color: var(--color-ink);
  }
  .admin-body .dialog-actions {
    flex: none;
    margin: 0;
    padding: 14px 22px;
    border-top: 1px solid var(--color-admin-line);
    background: var(--color-admin-bg);
  }

  /* ── Blueprint registration marks ─────────────────────────────────────
     The markup has emitted `<i class="corner tl">…` beside every `.blueprint`
     wrapper since the design import, but no rule ever existed — roughly 500
     elements rendering nothing. They are the panel's own motif, so they now
     draw: a hairline tick at each corner of a framed object. Suppressed
     inside buttons and tags, where at that scale they only add noise. */
  .blueprint > .corner {
    position: absolute;
    z-index: 2;
    width: 7px;
    height: 7px;
    pointer-events: none;
    border: 1px solid var(--color-accent-400);
    opacity: 0.9;
  }
  /* Inset rather than straddling the border: `.admin-card` clips to its radius
     with `overflow: hidden`, and a mark at -1px is exactly what that clips
     away. Sitting just inside also reads more like a drawing's registration
     tick than a bracket around the box. */
  .blueprint > .corner.tl { top: 4px; left: 4px;  border-right: 0; border-bottom: 0; }
  .blueprint > .corner.tr { top: 4px; right: 4px; border-left: 0;  border-bottom: 0; }
  .blueprint > .corner.bl { bottom: 4px; left: 4px;  border-right: 0; border-top: 0; }
  .blueprint > .corner.br { bottom: 4px; right: 4px; border-left: 0;  border-top: 0; }
  .btn > .corner,
  .tag > .corner { display: none; }

  .toast {
    position: fixed;
    bottom: 24px;
    left: 50%;
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--color-accent-900);
    color: #fff;
    padding: 12px 22px;
    border-radius: 4px;
    font-size: 0.9rem;
    z-index: 999;
    box-shadow: var(--shadow-lg);
    animation: toast-in .32s cubic-bezier(.22, 1, .36, 1) both;
  }
  @keyframes toast-in {
    from { opacity: 0; transform: translate(-50%, 14px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
  }

  /* Horizontal content scrolls inside its own box, never the page. */
  .scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

  /* ══ Document language ══════════════════════════════════════════════════
     The look the credential card established, made reusable: hard 2px frames,
     solid offset shadows instead of blurred ones, mono uppercase micro-labels.
     It reads as institutional paperwork rather than a generic SaaS panel, and
     because it lives here every admin page inherits it without repeating a
     forty-utility class string per element.

     Every colour is a palette token — a literal hex or a Tailwind `neutral-*`
     would break re-skinning and is what made the first pass look generic.
     ═══════════════════════════════════════════════════════════════════════ */

  /* A bordered action that presses like a stamp. */
  .btn-doc {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 38px;
    padding: 8px 14px;
    border: 1px solid var(--color-admin-line-strong);
    background: var(--color-admin-surface);
    color: var(--color-ink);
    font-family: var(--font-heading);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: border-color .2s ease, color .2s ease, box-shadow .2s ease, transform .2s ease;
  }
  /* The left rule that wipes in on hover — the one flourish, and it costs
     nothing because it animates transform only. */
  .btn-doc::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    width: 2px;
    background: currentColor;
    transform: scaleY(0);
    transform-origin: top;
    transition: transform .25s cubic-bezier(.22, 1, .36, 1);
  }
  .btn-doc:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
    transform: translateY(-1px);
    box-shadow: 3px 3px 0 0 color-mix(in srgb, var(--color-accent) 22%, transparent);
  }
  .btn-doc:hover::before { transform: scaleY(1); }
  .btn-doc:active { transform: translateY(1px); box-shadow: none; }
  .btn-doc:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

  .btn-doc--danger { border-color: var(--color-danger-300); color: var(--color-danger-text); }
  .btn-doc--danger:hover {
    border-color: var(--color-danger);
    color: var(--color-danger-700);
    background: var(--color-danger-50);
    box-shadow: 3px 3px 0 0 color-mix(in srgb, var(--color-danger) 20%, transparent);
  }
  .btn-doc--go { border-color: var(--color-accent-2-700); color: var(--color-accent-2-800); }
  .btn-doc--go:hover {
    border-color: var(--color-accent-2-700);
    color: var(--color-accent-2-800);
    background: var(--color-accent-2-50);
    box-shadow: 3px 3px 0 0 color-mix(in srgb, var(--color-accent-2-700) 22%, transparent);
  }

  /* Mono micro-label: the "FATHER'S / GUARDIAN'S NAME" treatment. */
  .meta-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-heading);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--color-admin-muted);
  }
  .meta-label i { color: var(--color-accent-2-700); }

  /* Small squared-off code/tag, for enrollment numbers and cluster names. */
  .mono-tag {
    display: inline-flex;
    align-items: center;
    padding: 2px 7px;
    border: 1px solid var(--color-admin-line);
    background: var(--color-admin-bg);
    font-family: var(--font-heading);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-admin-muted);
    transition: border-color .2s ease, color .2s ease;
  }
  .mono-tag:hover { border-color: var(--color-admin-line-strong); color: var(--color-ink); }

  /* Status pips — a filled square plus a word, never colour alone. */
  .pip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-family: var(--font-heading);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
  }
  .pip::before {
    content: "";
    width: 7px;
    height: 7px;
    flex: none;
    background: currentColor;
  }
  .pip--active  { color: var(--color-accent-2-800); }
  .pip--alumni  { color: var(--color-admin-muted); }
  .pip--pending { color: var(--color-danger-700); }

  /* ══ Verification credential (student-verify.php) ═══════════════════════
     Hard-edged frame and a solid offset shadow — deliberately unlike the
     site's soft cards, because this artefact gets printed and screenshotted
     and should read as a document. Written here rather than as Tailwind
     arbitrary values: the browser build will not resolve a `var()` inside a
     `shadow-[…]` utility, so those silently render as no shadow at all.
     ═══════════════════════════════════════════════════════════════════════ */

  .credential {
    border: 2px solid var(--color-accent-900);
    box-shadow: 8px 8px 0 0 var(--color-accent-900);
    transition: box-shadow .3s cubic-bezier(.22, 1, .36, 1);
  }
  .credential:hover { box-shadow: 10px 10px 0 0 var(--color-accent-2-700); }
  .credential__aside { border-color: var(--color-accent-900); }
  .credential__photo {
    border: 2px solid var(--color-accent-900);
    box-shadow: 4px 4px 0 0 var(--color-accent-900);
  }
  .credential__seal {
    border: 2px solid var(--color-accent-900);
    box-shadow: 2px 2px 0 0 var(--color-accent-900);
  }
  .credential__badge {
    border: 2px solid currentColor;
    box-shadow: 2px 2px 0 0 currentColor;
  }

  @media print {
    /* Printing a credential should yield the card, not the website. */
    .site-nav, .site-footer, .toast, .credential__print-hide { display: none !important; }
    .credential { box-shadow: none; }
  }
}

/* ══ Reduced motion ═════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal],
  [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    clip-path: none;
    transition: none;
  }
  .site-nav, .site-nav__logo, .site-nav__link::after, .site-nav__menu,
  .panel, .media img, .btn, .spec tbody tr,
  .foot-row, .foot-row__trail, .foot-social,
  .admin-rail, .admin-scrim, .admin-rail__link, .admin-rail__out,
  .chip, .admin-body .table tbody tr, .admin-body a.card {
    transition: none;
  }
  .toast { animation: none; }
}
