/* ===========================================================================
   One Residence Community Gym — base stylesheet

   Tokens transcribed from the approved Claude Design project
   (e48e2821-ab00-4d25-8913-8938b91958ca), option 1a "Foundations".
   This file is the only place colour and type are defined.

   Design rules enforced here:
     - Accent is the only chromatic colour in the public UI.
     - Public body text never drops below 17px.
     - One column, 390–640px, centred on desktop.
     - Motion is functional only: 150ms opacity on HTMX swaps, nothing else.
   =========================================================================== */

:root {
  /* Colour ---------------------------------------------------------------- */
  --surface: #ffffff;
  --canvas: #f4f5f6;
  --ink: #14171a;
  --ink-60: #5b6570;
  --ink-40: #8a939c;

  /* Overridden per-deployment from the `settings` table. */
  --accent: #2c6bed;
  --accent-hover: #1f53c4;

  /* Admin only, always paired with a text label — never colour alone. */
  --status-published: #1e9e6a;
  --status-published-ink: #177a52;
  --status-draft: #c98a0e;
  --status-draft-ink: #956706;
  --status-archived: #d0453b;

  --warn-bg: rgba(201, 138, 14, 0.09);
  --warn-border: rgba(201, 138, 14, 0.2);
  --warn-ink: #7a5505;

  --hairline: rgba(16, 22, 28, 0.09);
  --hairline-soft: rgba(16, 22, 28, 0.07);
  --hairline-strong: rgba(16, 22, 28, 0.16);
  --thumb-bg: #e9ebed;

  /* Type ------------------------------------------------------------------
     Geist is self-hosted when the woff2 subsets are added to public/fonts;
     see public/fonts/README.md. The stack degrades to the platform UI sans,
     which shares Geist's metrics closely enough that layout does not shift. */
  --font-sans: Geist, -apple-system, BlinkMacSystemFont, "Segoe UI",
    "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas,
    monospace;

  /* Geometry -------------------------------------------------------------- */
  --radius-card: 18px;
  --radius-inner: 12px;
  --radius-media: 14px;
  --radius-button: 10px;
  --radius-badge: 6px;

  --tap: 52px;
  --tap-min: 44px;

  --page-pad: 24px;
  --measure: 62ch;
  --shell-max: 640px;
}

/* Reset --------------------------------------------------------------------*/

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  color: var(--accent-hover);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--surface);
  color: var(--ink);
  padding: 12px 16px;
  z-index: 10;
  border-radius: 0 0 var(--radius-button) 0;
}

.skip-link:focus {
  left: 0;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Type scale ---------------------------------------------------------------*/

.t-display {
  font: 600 30px/1.14 var(--font-sans);
  letter-spacing: -0.024em;
  color: var(--ink);
  margin: 0;
}

.t-title {
  font: 600 24px/1.2 var(--font-sans);
  letter-spacing: -0.018em;
  color: var(--ink);
  margin: 0;
}

.t-section {
  font: 600 19px/1.3 var(--font-sans);
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0;
}

.t-body {
  font: 400 17px/1.55 var(--font-sans);
  color: var(--ink);
  margin: 0;
  max-width: var(--measure);
}

.t-lede {
  font: 400 16px/1.5 var(--font-sans);
  color: var(--ink-60);
  margin: 0;
  max-width: var(--measure);
  text-wrap: pretty;
}

.t-label {
  font: 500 15px/1.4 var(--font-sans);
  color: var(--ink);
}

.t-caption {
  font: 400 13px/1.4 var(--font-sans);
  color: var(--ink-40);
}

.t-overline {
  font: 500 11px/1 var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-40);
}

.t-mono {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-40);
}

@media (min-width: 640px) {
  .t-display {
    font-size: 32px;
    line-height: 1.15;
    letter-spacing: -0.022em;
  }
}

/* Shell --------------------------------------------------------------------*/

.shell {
  max-width: var(--shell-max);
  margin-inline: auto;
  min-height: 100dvh;
  background: var(--surface);
  display: flex;
  flex-direction: column;
}

.shell__main {
  flex: 1;
}

.section {
  padding: 22px var(--page-pad) 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.stack {
  padding: 0 var(--page-pad);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.stack--tight {
  gap: 10px;
}

.stack--pad-top {
  padding-top: 18px;
}

.stack--pad-bottom {
  padding-bottom: 28px;
}

/*
 * Spacing modifiers.
 *
 * These carry values that used to sit in `style` attributes on the templates.
 * The public CSP is `style-src 'self' 'nonce-...'`, and a nonce does not
 * authorise a style *attribute* — only a <style> element — so every one of
 * those declarations was being dropped by the browser and the spacing they
 * describe never actually rendered.
 *
 * Named for the value they apply rather than for a role. The point of the
 * change was to preserve the intended spacing exactly, and a numeric name is
 * checkable against the markup it replaced; a semantic one would hide whether
 * the number survived.
 */
.section--pt-12 {
  padding-top: 12px;
}

.section--pt-20 {
  padding-top: 20px;
}

.section--pb-4 {
  padding-bottom: 4px;
}

.section--pb-18 {
  padding-bottom: 18px;
}

.section--pb-20 {
  padding-bottom: 20px;
}

.stack--gap-8 {
  gap: 8px;
}

.stack--gap-10 {
  gap: 10px;
}

.btn--gap-top {
  margin-top: 8px;
}

/* Brand mark ---------------------------------------------------------------*/

.mark {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--ink);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: 600 15px/1 var(--font-sans);
  flex: none;
}

.mark--sm {
  width: 24px;
  height: 24px;
  border-radius: 6px;
  font-size: 11px;
}

/* Back bar + breadcrumb ----------------------------------------------------*/

.back-bar {
  position: sticky;
  top: 0;
  z-index: 5;
  height: var(--tap);
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 18px;
  border-bottom: 1px solid rgba(16, 22, 28, 0.08);
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: saturate(180%) blur(8px);
  color: var(--accent);
  font: 500 16px/1 var(--font-sans);
}

.back-bar__chev {
  font: 400 20px/1 var(--font-sans);
}

.breadcrumb {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font: 400 13px/1.3 var(--font-sans);
  color: var(--ink-40);
}

.breadcrumb a {
  color: var(--ink-40);
}

.breadcrumb a:hover {
  color: var(--ink-60);
}

.breadcrumb__current {
  color: var(--ink-60);
}

/* Home cards ---------------------------------------------------------------*/

.home-card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--surface);
  color: inherit;
  min-height: var(--tap);
}

.home-card:hover {
  color: inherit;
  border-color: var(--hairline-strong);
}

.home-card__media {
  width: 138px;
  height: 184px;
  flex: none;
  border-radius: var(--radius-inner);
}

.home-card__title {
  flex: 1;
  font: 600 24px/1.2 var(--font-sans);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.chev {
  flex: none;
  color: var(--ink-40);
  font: 400 24px/1 var(--font-sans);
  padding-right: 8px;
}

/* Item card (collection child, `card` layout) ------------------------------*/

.card {
  display: block;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--surface);
  color: inherit;
}

.card:hover {
  color: inherit;
  border-color: var(--hairline-strong);
}

.card__media {
  height: 240px;
}

.card__body {
  padding: 16px 18px;
  display: flex;
  align-items: center;
  gap: 12px;
  border-top: 1px solid var(--hairline-soft);
}

.card__text {
  flex: 1;
  min-width: 0;
}

.card__title {
  font: 600 21px/1.2 var(--font-sans);
  letter-spacing: -0.018em;
  color: var(--ink);
}

.card__sub {
  font: 400 15px/1.45 var(--font-sans);
  color: var(--ink-40);
  text-wrap: pretty;
}

.card__chev {
  flex: none;
  color: var(--ink-40);
  font: 400 22px/1 var(--font-sans);
}

/* Item row (collection child, `row` layout) --------------------------------*/

.row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-media);
  background: var(--surface);
  color: inherit;
  min-height: var(--tap-min);
}

.row:hover {
  color: inherit;
  border-color: var(--hairline-strong);
}

.row__thumb {
  width: 64px;
  height: 64px;
  flex: none;
  border-radius: var(--radius-button);
}

.row__text {
  flex: 1;
  min-width: 0;
}

.row__title {
  font: 600 17px/1.25 var(--font-sans);
  color: var(--ink);
}

.row__sub {
  font: 400 13px/1.4 var(--font-sans);
  color: var(--ink-40);
}

.row__chev {
  flex: none;
  color: var(--ink-40);
  font: 400 20px/1 var(--font-sans);
  padding-right: 6px;
}

/* Buttons ------------------------------------------------------------------*/

.btn {
  height: var(--tap);
  border-radius: var(--radius-button);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 18px;
  font: 600 16px/1 var(--font-sans);
  border: 1px solid transparent;
  cursor: pointer;
  text-align: center;
}

.btn--primary {
  background: var(--accent);
  color: #fff;
}

.btn--primary:hover {
  background: var(--accent-hover);
  color: #fff;
}

.btn--secondary {
  background: var(--surface);
  border-color: var(--hairline-strong);
  color: var(--ink);
}

.btn--secondary:hover {
  color: var(--ink);
}

.btn--sm {
  height: 38px;
  padding: 0 14px;
  font-size: 14px;
}

/* Footer -------------------------------------------------------------------*/

.footer {
  margin-top: auto;
  padding: 18px var(--page-pad) 22px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-top: 1px solid var(--hairline-soft);
}

.footer p {
  margin: 0;
  font: 400 13px/1.5 var(--font-sans);
  color: var(--ink-40);
}

/* Prose --------------------------------------------------------------------
   Rendered Markdown body. Mirrors design 1b/04, 1b/04b, 1b/05 and 1b/06:
   19px section headings, 17px body, numbered steps as filled dark discs,
   unordered items marked with an em dash in ink-40. */

.prose {
  gap: 14px;
}

.prose > * {
  margin: 0;
  max-width: var(--measure);
}

.prose h2 {
  font: 600 19px/1.3 var(--font-sans);
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-top: 8px;
}

.prose h3 {
  font: 600 17px/1.35 var(--font-sans);
  color: var(--ink);
  margin-top: 6px;
}

.prose h4 {
  font: 600 15px/1.4 var(--font-sans);
  color: var(--ink);
}

.prose p {
  font: 400 17px/1.55 var(--font-sans);
  color: var(--ink);
  text-wrap: pretty;
}

.prose a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.prose code {
  font: 400 15px/1.4 var(--font-mono);
  color: var(--ink-60);
}

.prose hr {
  border: 0;
  height: 1px;
  background: var(--hairline);
  width: 100%;
}

.prose ol,
.prose ul {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.prose li {
  font: 400 16px/1.5 var(--font-sans);
  color: var(--ink);
  text-wrap: pretty;
}

/* Numbered steps — design 1b/04: 26px filled disc, white numeral. */
.prose ol {
  counter-reset: step;
}

.prose ol > li {
  counter-increment: step;
  position: relative;
  padding-left: 40px;
  min-height: 26px;
}

.prose ol > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 26px;
  height: 26px;
  border-radius: 13px;
  background: var(--ink);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 600 13px/1 var(--font-sans);
}

/* Unordered items — design 1b/06: em dash in ink-40. */
.prose ul > li {
  position: relative;
  padding-left: 22px;
}

.prose ul > li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: var(--ink-40);
}

.prose blockquote {
  border-left: 2px solid var(--hairline-strong);
  padding-left: 14px;
  color: var(--ink-60);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Callouts -----------------------------------------------------------------
   [!WARNING] → amber safety callout (design 1b/04b, 1b/05).
   [!NOTE]    → neutral grey panel (design 1b/05, "Attaching it"). */

.callout {
  border-radius: var(--radius-inner);
  padding: 14px 16px;
  display: flex;
  gap: 10px;
  width: 100%;
  max-width: var(--measure);
}

.callout__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.callout__body > * {
  margin: 0;
}

.callout--warning {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
}

.callout--warning .callout__glyph {
  font: 600 15px/1.45 var(--font-sans);
  color: var(--status-draft-ink);
  flex: none;
}

.callout--warning .callout__body,
.callout--warning .callout__body p {
  font: 400 15px/1.45 var(--font-sans);
  color: var(--warn-ink);
  text-wrap: pretty;
}

.callout--note {
  background: var(--canvas);
  padding: 16px 18px;
}

.callout--note .callout__body,
.callout--note .callout__body p {
  font: 400 15px/1.55 var(--font-sans);
  color: var(--ink-60);
  text-wrap: pretty;
}

/* Empty + not-found states -------------------------------------------------*/

.notice {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 48px 32px;
  gap: 14px;
}

.notice__glyph {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-inner);
  background: var(--canvas);
  display: flex;
  align-items: center;
  justify-content: center;
  font: 500 20px/1 var(--font-sans);
  color: var(--ink-40);
}

.notice__title {
  font: 600 26px/1.18 var(--font-sans);
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0;
}

.notice__path {
  font: 400 12px/1.5 var(--font-mono);
  color: #98a0a8;
  margin-top: 6px;
  word-break: break-all;
}

.empty {
  padding: 20px;
  border: 1px dashed var(--hairline-strong);
  border-radius: var(--radius-inner);
  color: var(--ink-40);
  font: 400 15px/1.5 var(--font-sans);
  text-align: center;
}

/* ===========================================================================
   Authentication — design 1c/A1. A 400px column centred on the canvas.
   =========================================================================== */

.auth {
  background: var(--canvas);
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 20px;
}

.auth__column {
  width: 100%;
  max-width: 400px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.auth__intro {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.auth__heading {
  font: 600 26px/1.2 var(--font-sans);
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0;
}

.auth__site {
  font: 400 15px/1.5 var(--font-sans);
  color: var(--ink-60);
  margin: 0;
}

.auth__card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 26px;
  box-shadow: 0 1px 3px rgba(16, 22, 28, 0.05);
}

.auth__footnote {
  font: 400 13px/1.5 var(--font-sans);
  color: var(--ink-40);
  text-align: center;
  margin: 0;
  text-wrap: pretty;
}

/* Forms --------------------------------------------------------------------*/

.form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form__error {
  margin: 0;
  padding: 12px 14px;
  border-radius: 9px;
  background: rgba(208, 69, 59, 0.08);
  border: 1px solid rgba(208, 69, 59, 0.24);
  color: #9c2b23;
  font: 400 14px/1.45 var(--font-sans);
}

/*
 * The error summary is always a lead paragraph over a list of messages, in
 * four different admin forms. Matched by element rather than given classes:
 * there is only ever this one shape inside `.form__error`, and the four call
 * sites were carrying identical style attributes the CSP discarded.
 */
.form__error p {
  margin: 0 0 6px;
}

.form__error ul {
  margin: 0;
  padding-left: 18px;
}

.form__hint {
  margin: 0;
  font: 400 13px/1.5 var(--font-sans);
  color: var(--ink-60);
  text-wrap: pretty;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.field__label {
  font: 500 13px/1 var(--font-sans);
  color: var(--ink);
}

.field__hint {
  margin: 0;
  font: 400 12px/1.4 var(--font-sans);
  color: var(--ink-40);
}

/* Hint sitting below its control, rather than directly under the label. */
.field__hint--gap {
  margin-top: 4px;
}

/* Hint tucked up under a `.t-label`, which carries its own bottom spacing. */
.field__hint--pull-up {
  margin-top: -4px;
}

/* A caption following body copy, rather than opening a block. */
.t-caption--gap {
  margin-top: 4px;
}

.t-caption--gap-6 {
  margin-top: 6px;
}

/* A button sitting inline with the text that explains it. */
.control-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.input {
  height: 44px;
  border-radius: 9px;
  border: 1px solid var(--hairline-strong);
  background: var(--surface);
  padding: 0 13px;
  font: 400 15px/1 var(--font-sans);
  color: var(--ink);
  width: 100%;
}

.input:focus {
  outline: none;
  border: 1.5px solid var(--accent);
  box-shadow: 0 0 0 3px rgba(44, 107, 237, 0.14);
}

.btn--form {
  height: 46px;
  font-size: 15px;
  width: 100%;
}

/* ===========================================================================
   Admin placeholder — desktop-first, minimal. The full admin UI is a later
   slice; this is only enough to demonstrate an HTMX swap against D1.
   =========================================================================== */

.admin {
  background: var(--canvas);
  min-height: 100dvh;
}

.admin__bar {
  height: 56px;
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 22px;
}

.admin__brand {
  font: 600 14px/1 var(--font-sans);
  color: var(--ink);
}

.admin__nav {
  display: flex;
  gap: 2px;
  margin-left: 18px;
}

.admin__nav-item {
  padding: 7px 12px;
  border-radius: 7px;
  font: 400 14px/1 var(--font-sans);
  color: var(--ink-60);
}

.admin__nav-item:hover {
  color: var(--ink);
}

.admin__nav-item--active {
  background: var(--canvas);
  color: var(--ink);
  font-weight: 500;
}

.admin__account {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
}

.admin__view-site {
  font: 400 13px/1 var(--font-sans);
  color: var(--ink-60);
}

.admin__user {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font: 500 13px/1.2 var(--font-sans);
  color: var(--ink);
}

.admin__role {
  font: 400 11px/1 var(--font-mono);
  color: var(--ink-40);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.admin__logout {
  margin: 0;
}

/* The admin workspace is desktop-first and uses the width it is given.
   It carried `max-width: 900px`, which left the content table about 476px wide
   (900 − 64 padding − 300 tree − 60 pane padding) with the rest of a 1600px
   window empty. Pages that genuinely want a reading measure set their own —
   `.settings__main` and `.narrow` — rather than every page inheriting one. */
.admin__body {
  padding: 28px 32px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.admin__banner {
  padding: 14px 16px;
  border-radius: var(--radius-inner);
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  color: var(--warn-ink);
  font: 400 15px/1.45 var(--font-sans);
}

.tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  /* Three counters, each holding a word and a number. Removing the shell's
     900px cap gave every page the window; stretching these across it would
     make them thin rather than useful, so they keep the width they had. */
  max-width: 900px;
}

.tile {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tile__label {
  display: flex;
  align-items: center;
  gap: 7px;
  font: 500 13px/1 var(--font-sans);
  color: var(--ink-60);
}

.tile__value {
  font: 600 30px/1 var(--font-sans);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.dot {
  width: 7px;
  height: 7px;
  border-radius: 4px;
  flex: none;
}

.dot--published {
  background: var(--status-published);
}

.dot--draft {
  background: var(--status-draft);
}

.dot--archived {
  background: var(--status-archived);
}

.card-panel {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

@media (max-width: 720px) {
  .tiles {
    grid-template-columns: 1fr;
  }
}

/* HTMX --------------------------------------------------------------------
   The only animation in the system. Public pages load no JavaScript at all,
   so this applies to the admin area only. */

.fade {
  opacity: 1;
  transition: opacity 150ms ease-out;
}

.fade.htmx-swapping {
  opacity: 0;
}

.htmx-request .htmx-indicator,
.htmx-indicator.htmx-request {
  opacity: 1;
}

.htmx-indicator {
  opacity: 0;
  transition: opacity 150ms ease-out;
  color: var(--ink-40);
  font: 400 13px/1 var(--font-sans);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ===========================================================================
   Admin content tree and editor — design 1c/A3 and 1c/A4.
   =========================================================================== */

.preview-bar {
  margin: 0;
  padding: 10px 16px;
  background: var(--ink);
  color: #fff;
  font: 500 13px/1.4 var(--font-sans);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.preview-bar a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 9px;
  border-radius: var(--radius-badge);
  font: 500 12px/24px var(--font-sans);
  white-space: nowrap;
}

.badge--published {
  background: rgba(30, 158, 106, 0.1);
  color: var(--status-published-ink);
}

.badge--draft {
  background: rgba(201, 138, 14, 0.12);
  color: var(--status-draft-ink);
}

.badge--archived {
  background: rgba(208, 69, 59, 0.1);
  color: #9c2b23;
}

/* Marks the label stock to reach for. Deliberately the quietest badge here:
   a neutral surface and a hairline, so it reads as a note beside the name
   rather than as a status the way published and draft do. */
.badge--recommended {
  background: var(--canvas);
  color: var(--ink-60);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
}

.notice-bar {
  margin: 0;
  padding: 11px 14px;
  border-radius: 9px;
  font: 400 14px/1.45 var(--font-sans);
}

.notice-bar--ok {
  background: rgba(30, 158, 106, 0.09);
  border: 1px solid rgba(30, 158, 106, 0.24);
  color: var(--status-published-ink);
}

.notice-bar--error {
  background: rgba(208, 69, 59, 0.08);
  border: 1px solid rgba(208, 69, 59, 0.24);
  color: #9c2b23;
}

/* Tree sidebar --------------------------------------------------------------*/

.content-layout {
  display: grid;
  /* minmax(0, 1fr), not 1fr: the latter floors at auto, which a wide table can
     push past and force the whole page to scroll sideways. */
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 0;
  align-items: start;
}

.tree {
  border-right: 1px solid var(--hairline);
  min-height: 60vh;
  padding-bottom: 20px;
}

.tree__filter {
  display: flex;
  gap: 8px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--hairline-soft);
}

.input--sm {
  height: 34px;
  font-size: 13px;
  border-radius: 8px;
}

.tree__list,
.tree__children {
  list-style: none;
  margin: 0;
  padding: 0;
}

.tree__list {
  padding: 8px;
}

.tree__row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  padding: 0 10px 0 0;
  border-radius: 7px;
  font: 400 14px/1.2 var(--font-sans);
}
/*
 * Depth indent, one rule per level.
 *
 * The indent stays on the row rather than on the nested `.tree__children`,
 * because the row is what carries the hover and active background — moving the
 * padding to the list would inset that highlight instead of letting it span
 * the sidebar. `level` came from a style attribute the CSP discarded, so the
 * tree has been rendering completely flat.
 *
 * Ten levels is far past anything the content hierarchy produces; `branch()`
 * clamps to the last one so a deeper tree stops indenting rather than losing
 * its indent altogether.
 */
.tree__row[data-depth="1"] {
  padding-left: 18px;
}

.tree__row[data-depth="2"] {
  padding-left: 36px;
}

.tree__row[data-depth="3"] {
  padding-left: 54px;
}

.tree__row[data-depth="4"] {
  padding-left: 72px;
}

.tree__row[data-depth="5"] {
  padding-left: 90px;
}

.tree__row[data-depth="6"] {
  padding-left: 108px;
}

.tree__row[data-depth="7"] {
  padding-left: 126px;
}

.tree__row[data-depth="8"] {
  padding-left: 144px;
}

.tree__row[data-depth="9"] {
  padding-left: 162px;
}


.tree__row--active {
  background: rgba(44, 107, 237, 0.09);
}

.tree__row:hover {
  background: var(--canvas);
}

.tree__toggle {
  width: 14px;
  flex: none;
  color: var(--ink-40);
  font-size: 11px;
  text-align: center;
}

.tree__toggle--leaf {
  opacity: 0.35;
}

.tree__label {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 6px 0;
}

.tree__label--section {
  font-weight: 500;
}

.tree__count {
  font: 400 12px/1 var(--font-mono);
  color: var(--ink-40);
}

/* Administrators only: the tree's top level is Sections, managed in their own area. */
.tree__manage {
  display: block;
  margin-top: 14px;
  padding: 0 10px;
  font: 500 13px/1.4 var(--font-sans);
  color: var(--accent);
  text-decoration: none;
}

.tree__manage:hover {
  text-decoration: underline;
}

/* Scoped table --------------------------------------------------------------*/

.content-main {
  background: var(--canvas);
  padding: 26px 30px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 0;
  min-height: 60vh;
}

.content-main__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
}

.content-main__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* Admin content table -------------------------------------------------------*/

.table-scroll {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  overflow-x: auto;
  max-width: 100%;
}

/*
 * `auto`, not `fixed`.
 *
 * An earlier version used `table-layout: fixed` with declared widths and
 * `resize: horizontal` on each header. The resize handles were a mistake: the
 * table layout algorithm owns column widths and overrides the width `resize`
 * writes, so dragging one produced results that varied by column and by
 * browser, and could leave columns collapsed to nothing. A control that does
 * not do what it looks like it does is worse than no control.
 *
 * With `auto`, the browser sizes each column to the width its content actually
 * needs, and `width: 100%` makes the title column absorb whatever is left over
 * so the table fills the panel.
 */
.table {
  width: 100%;
  border-collapse: collapse;
  table-layout: auto;
  /* Sum of the column floors below. Under this the wrapper scrolls, which
     beats shrinking every title to an ellipsis on a narrow laptop window. */
  min-width: 720px;
}

.table th,
.table td {
  padding: 11px 16px;
  text-align: left;
  vertical-align: middle;
}

/*
 * `width: 1%` with `nowrap` is the shrink-to-fit idiom: the browser treats it
 * as a minimum, then grows the cell to exactly the width its content needs.
 * Every column except the title does this, so the title takes the remainder.
 */
.table__col-status,
.table__col-contents,
.table__col-updated,
.table__col-flag,
.table__col-actions {
  width: 1%;
  white-space: nowrap;
}

.table__col-title {
  /* The only flexible column, so it absorbs whatever the fixed columns leave.
     `max-width: 0` is what makes `text-overflow: ellipsis` work at all inside
     `table-layout: auto` — the ellipsis needs a definite width — and the floor
     stops it collapsing before the wrapper starts scrolling. */
  max-width: 0;
  min-width: 300px;
}

.table thead th {
  border-bottom: 1px solid var(--hairline-soft);
  background: #fafbfb;
  font: 500 12px/1 var(--font-sans);
  color: var(--ink-40);
  white-space: nowrap;
}

.table tbody td {
  border-bottom: 1px solid rgba(16, 22, 28, 0.06);
  font: 400 14px/1.3 var(--font-sans);
}

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

/* Pinned so the menu button stays reachable when the table scrolls. */
.table__col-actions {
  position: sticky;
  right: 0;
  background: var(--surface);
  box-shadow: inset 1px 0 0 var(--hairline-soft);
  text-align: right;
}

.table thead .table__col-actions {
  background: #fafbfb;
}

.table__row.is-dragging td {
  opacity: 0.5;
  background: var(--canvas);
}

.table__title {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.table__title-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.table__title a {
  color: var(--ink);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.table__grip {
  color: #c7cdd2;
  cursor: grab;
  flex: none;
}

.table__slug {
  font: 400 12px/1.4 var(--font-mono);
  color: var(--ink-40);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.table__meta {
  color: var(--ink-40);
  font-size: 13px;
}

.table__empty {
  padding: 28px 18px;
  text-align: center;
  color: var(--ink-40);
  font: 400 14px/1.5 var(--font-sans);
}

/* Row action menu ----------------------------------------------------------*/

.row-menu {
  position: relative;
  display: inline-block;
}

.row-menu__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-badge);
  color: var(--ink-60);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  list-style: none;
  user-select: none;
}

/* Safari renders a disclosure triangle without this. */
.row-menu__toggle::-webkit-details-marker {
  display: none;
}

.row-menu__toggle:hover,
.row-menu[open] .row-menu__toggle {
  background: var(--canvas);
  color: var(--ink);
}

.row-menu__toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/*
 * Static by default: with no script the panel expands inside the row rather
 * than floating, which is plainer but is never clipped by the table's scroll
 * container. row-menu.js adds `js-row-menus` and takes over positioning.
 */
.row-menu__panel {
  /* Explicit, because the comment above depends on it. */
  position: static;
  display: flex;
  flex-direction: column;
  min-width: 170px;
  margin-top: 6px;
  padding: 5px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-button);
  background: var(--surface);
  box-shadow: 0 8px 24px rgba(16, 22, 28, 0.14);
  text-align: left;
}

.js-row-menus .row-menu__panel {
  position: fixed;
  z-index: 60;
  margin-top: 0;
}

.row-menu__item {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius-badge);
  background: none;
  text-align: left;
  font: 400 14px/1.3 var(--font-sans);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}

.row-menu__item:hover {
  background: var(--canvas);
  color: var(--ink);
}

.row-menu__item--danger {
  color: var(--status-archived);
}

.row-menu__item--danger:hover {
  background: rgba(208, 69, 59, 0.07);
}



.inline-form {
  display: inline;
  margin: 0;
}

.btn--xs {
  height: 28px;
  padding: 0 9px;
  font-size: 12px;
  border-radius: 7px;
}

.btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--ink-60);
}

.btn--ghost:hover {
  background: var(--canvas);
  color: var(--ink);
}

/* Editor --------------------------------------------------------------------*/

.editor__bar {
  height: 56px;
  border-bottom: 1px solid var(--hairline);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 22px;
  background: var(--surface);
}

.editor__identity {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
}

.editor__back {
  color: var(--ink-60);
  font-size: 18px;
}

.editor__title {
  font: 600 14px/1.2 var(--font-sans);
  color: var(--ink);
}

.editor__url {
  font: 400 11.5px/1.3 var(--font-mono);
  color: var(--ink-40);
}

.editor__actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.editor__role-note {
  max-width: 180px;
  text-align: right;
}

/*
 * Shown by unsaved.js beside Save draft once a field differs from what the
 * page loaded with. A statement, not a warning: Publish saves what it submits,
 * so nothing here is at risk — this says only that the draft on the server is
 * behind what is on screen.
 */
.editor__unsaved {
  font: 500 12px/1 var(--font-sans);
  color: var(--ink-60);
  white-space: nowrap;
}

.editor__layout {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr) 284px;
  align-items: start;
}

.editor__tree {
  border-right: 1px solid var(--hairline);
  padding: 12px 10px;
  min-height: 60vh;
}

.editor__main {
  background: var(--canvas);
  padding: 26px 30px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
  min-height: 60vh;
}

.editor__form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.card-panel--flush {
  padding: 0;
  gap: 0;
  overflow: hidden;
}

.tabs {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 42px;
  padding: 0 12px;
  border-bottom: 1px solid var(--hairline-soft);
  font: 500 13px/1 var(--font-sans);
  color: var(--ink-60);
}

.tabs__tab {
  padding: 6px 9px;
  border-radius: 6px;
}

.tabs__tab--active {
  background: var(--canvas);
  color: var(--ink);
}

.tabs__tab--disabled {
  color: #b9c0c6;
  cursor: not-allowed;
}

.tabs__note {
  margin-left: auto;
  font: 400 12px/1 var(--font-mono);
  color: var(--ink-40);
}

/*
 * The Collection editor's Pages panel.
 *
 * `--flush` strips the card's padding so the tab strip can span its full width;
 * this puts the padding back around the content beneath it, at the same rhythm
 * `.card-panel` uses.
 */
.panel-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px 22px;
}

.editor__body {
  width: 100%;
  border: 0;
  padding: 18px 20px;
  font: 400 14px/1.7 var(--font-mono);
  color: var(--ink);
  resize: vertical;
  min-height: 320px;
}

.editor__body:focus {
  outline: none;
  box-shadow: inset 0 0 0 2px rgba(44, 107, 237, 0.18);
}

.input--area {
  height: auto;
  padding: 11px 13px;
  line-height: 1.5;
  font-family: var(--font-sans);
  resize: vertical;
}

.editor__rail {
  border-left: 1px solid var(--hairline);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-height: 60vh;
}

.rail__group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rail__group--muted {
  opacity: 0.7;
}

.rail__url {
  margin: 0;
  font: 400 12.5px/1.5 var(--font-mono);
  color: var(--ink-60);
  word-break: break-all;
}

.rail__list {
  margin: 0;
  padding-left: 16px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  font: 400 12px/1.5 var(--font-sans);
  color: var(--ink-60);
}

.rail__list code {
  font-family: var(--font-mono);
  font-size: 11.5px;
}

/* Shared ------------------------------------------------------------------*/

.narrow {
  max-width: 620px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.form__actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.choice {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
  font: 400 14px/1.45 var(--font-sans);
  color: var(--ink-60);
}

.choice strong {
  color: var(--ink);
  font-weight: 600;
}

fieldset.field {
  border: 0;
  padding: 0;
  margin: 0;
}

@media (max-width: 1100px) {
  .content-layout,
  .editor__layout {
    grid-template-columns: 1fr;
  }

  .tree,
  .editor__tree,
  .editor__rail {
    border: 0;
    border-bottom: 1px solid var(--hairline);
    min-height: 0;
  }

  .table__head {
    display: none;
  }

  .table__row {
    grid-template-columns: 1fr;
    gap: 8px;
  }
}

/* ===========================================================================
   Images — public rendering and the admin media tab.
   =========================================================================== */

/*
 * The surface every public image sits on.
 *
 * Hero, collection cards, collection rows, home cards, related cards and the
 * gallery each need their own box — 240px, 64px, 138x184, 1:1 — but they are
 * all the same card, so they all show the same colour behind the picture.
 * They previously carried three different tints between them, which read as
 * three different materials once two of them appeared on one screen.
 *
 * This is the only place a public image container declares a background; none
 * of the rules below set one, so there is no order or specificity to reason
 * about, and a new container gets the surface by adding this class.
 *
 * Deliberately the card surface rather than the tints in the approved design
 * — see the note against the hero in the design project.
 */
.media-surface {
  background: var(--surface);
}

/* Public: fixed boxes with object-fit, reproducing the design's
   background-size behaviour while letting the browser reserve the space. */

.card__media {
  height: 240px;
  display: block;
}

.card__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.row__thumb {
  width: 64px;
  height: 64px;
  flex: none;
  border-radius: var(--radius-button);
  overflow: hidden;
}

/*
 * Contained even at 64px. `cover` filled the square by cutting away whatever
 * did not fit, which on a machine photographed end-on is most of the machine.
 * A thumbnail is small enough to be unhelpful already; one showing the middle
 * of the picture rather than the thing is worse than a small one showing all
 * of it. The box stays square so the rows stay aligned.
 */
.row__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.home-card__media {
  width: 138px;
  height: 184px;
  flex: none;
  border-radius: var(--radius-inner);
  overflow: hidden;
}

/*
 * The 138x184 frame is what keeps the Home cards a uniform shape, so it stays
 * fixed; only the crop goes. A landscape photo now sits letterboxed inside it
 * rather than being trimmed to a portrait slice of its middle.
 */
.home-card__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/*
 * Hero image.
 *
 * The band is the same white as `.shell`, so the image and the content below
 * it read as one card. Deliberately *not* the `--canvas` tint the approved
 * design specifies for this band — see the note in the design project.
 *
 * The image sizes itself from its own proportions rather than being poured
 * into a fixed frame: width and height stay `auto`, so the ratio is always the
 * image's own, and the two caps below can only ever shrink it. That means one
 * rule covers portrait, landscape and square, and an image smaller than the
 * card is never stretched to fill it.
 */
.hero {
}

/*
 * Centred with auto margins rather than flex on purpose: as a flex item the
 * image gets an automatic minimum size of its own content width, which
 * `max-width` cannot shrink, and a landscape photo then overflows the viewport.
 */
.hero__img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 300px;
  margin-inline: auto;
}

/*
 * The approved public design is mobile only (390 x 844), where the band is
 * 300px. Desktop has no approved screen to follow, so it keeps the same
 * proportions and simply spends the extra height available to it — which is
 * what stops a portrait image stranding the card in whitespace. The viewport
 * term keeps the hero from crowding out the title on a short laptop window.
 */
@media (min-width: 640px) {
  .hero__img {
    max-height: min(70vh, 520px);
  }
}

/*
 * Photos — one per row, each at its own proportions.
 *
 * This was a three-column grid of 1:1 boxes filled with `object-fit: cover`,
 * which is a crop: a portrait photo of someone using a machine lost their head
 * and their feet, and the three narrow columns left too little width for what
 * survived to be legible anyway.
 *
 * A single column costs nothing but scroll and lets each photo be the shape it
 * was taken in, so the same rule serves portrait, landscape and square with no
 * cropping in any of them.
 */
.gallery {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

.gallery__item {
  margin: 0;
}

/*
 * Sized from its own proportions — width and height stay `auto`, so the ratio
 * is always the file's own and the caps below can only ever shrink it. The
 * viewport cap keeps one tall photo from filling more than a screen; centred
 * with auto margins so a photo narrower than the card is not stranded left.
 */
.gallery__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 80vh;
  margin-inline: auto;
  border-radius: var(--radius-inner);
  border: 1px solid var(--hairline);
}

.gallery__caption {
  margin-top: 6px;
  font: 400 13px/1.4 var(--font-sans);
  color: var(--ink-40);
}

/* Admin: documents and relationships tabs -----------------------------------*/

.btn--icon {
  min-width: 34px;
  padding-inline: 10px;
  font-size: 15px;
  line-height: 1;
}

.btn--danger {
  border-color: rgba(208, 69, 59, 0.3);
  color: var(--status-archived);
  background: var(--surface);
}

.btn--danger:hover:not(:disabled) {
  border-color: var(--status-archived);
  background: rgba(208, 69, 59, 0.06);
}

.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.notice-bar--warn {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  color: var(--warn-ink);
}

.field__optional {
  font-weight: 400;
  color: var(--ink-40);
}

.doc-list,
.rel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.doc-item {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  background: var(--surface);
}

.doc-item__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.doc-item__text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.doc-item__title {
  font: 600 15px/1.3 var(--font-sans);
  color: var(--ink);
}

.doc-item__meta {
  font: 400 12px/1.4 var(--font-mono);
  color: var(--ink-40);
  overflow-wrap: anywhere;
}

.doc-item__order,
.rel-item__actions {
  display: flex;
  gap: 6px;
  flex: none;
}

.doc-item__form,
.doc-item__replace,
.rel-item__add,
.rel-search {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}

.doc-item__form .input {
  flex: 1 1 180px;
  min-width: 0;
}

.doc-item__actions {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  padding-top: 10px;
  border-top: 1px solid var(--hairline-soft);
}

.rel-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  background: var(--surface);
}

.rel-item__text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.rel-item__title {
  font: 600 15px/1.3 var(--font-sans);
  color: var(--ink);
}

.rel-item__meta {
  font: 400 12px/1.4 var(--font-sans);
  color: var(--ink-40);
}

/* Paired with the words "Not published" — never colour alone (design 1a). */
.rel-item__warn {
  font: 500 12px/1.4 var(--font-sans);
  color: var(--status-draft-ink);
}

.rel-search .input {
  flex: 1 1 220px;
}

/* Dashboard header + manufacturer import ------------------------------------*/

.dash-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.dash-head__actions {
  display: flex;
  gap: 10px;
  flex: none;
}

.import-form,
.import-review {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.import-review .card-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.import-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

.import-warnings {
  margin: 6px 0 0;
  padding-left: 18px;
  font: 400 13px/1.5 var(--font-sans);
}

.import-choice {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 400 14px/1.4 var(--font-sans);
  color: var(--ink);
  padding: 3px 0;
}

.import-candidates {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.import-candidate {
  display: flex;
  gap: 12px;
  padding: 10px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  background: var(--surface);
}

.import-candidate--flat {
  flex-direction: column;
  gap: 4px;
}

/* Remote thumbnails, shown once for review before anything is copied. */
.import-candidate__thumb {
  width: 96px;
  height: 96px;
  flex: none;
  object-fit: contain;
  border-radius: var(--radius-button);
  background: var(--thumb-bg);
}

.import-candidate__text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.import-candidate__meta {
  font: 400 12px/1.4 var(--font-mono);
  color: var(--ink-40);
}

.import-candidate__url {
  font: 400 11px/1.4 var(--font-mono);
  color: var(--ink-40);
  overflow-wrap: anywhere;
}

.import-source-text,
.import-prompt {
  font: 400 13px/1.6 var(--font-mono);
  resize: vertical;
}

.import-prompt-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.import-note {
  padding: 10px 12px;
  border-radius: var(--radius-button);
  background: var(--canvas);
}

/* Import workflow steps and summary -----------------------------------------*/

.import-steps {
  list-style: none;
  margin: 0;
  padding: 0 0 14px;
  border-bottom: 1px solid var(--hairline);
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  font: 400 13px/1.4 var(--font-sans);
  color: var(--ink-40);
}

.import-steps__step {
  display: flex;
  align-items: center;
  gap: 7px;
}

.import-steps__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: 10px;
  border: 1px solid var(--hairline-strong);
  font: 500 11px/1 var(--font-mono);
}

/* The current step is the only one carrying weight, matching the admin nav. */
.import-steps__step--current {
  color: var(--ink);
  font-weight: 500;
}

.import-steps__step--current .import-steps__number {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.import-steps__step--done {
  color: var(--ink-60);
}

.import-steps__step--done .import-steps__number {
  background: var(--canvas);
  color: var(--ink-60);
}

.import-confirm {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.import-summary {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px 20px;
}

.import-summary > div {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.import-summary dt {
  font: 500 11px/1 var(--font-mono);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-40);
}

.import-summary dd {
  margin: 0;
  font: 400 14px/1.4 var(--font-sans);
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* Downloads -----------------------------------------------------------------*/

/* Design 1b/04b: 52px rows, title left, "PDF · 4.2 MB" right. */
.downloads {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.download {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--tap);
  padding: 8px 16px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-button);
  background: var(--surface);
  color: inherit;
}

.download:hover {
  color: inherit;
  border-color: var(--ink-40);
}

.download__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.download__title {
  font: 600 16px/1.25 var(--font-sans);
  color: var(--ink);
}

.download__desc {
  font: 400 13px/1.4 var(--font-sans);
  color: var(--ink-60);
}

/* Geist Mono, per the design's rule that sizes and IDs are monospace. */
.download__meta {
  flex: none;
  font: 400 13px/1.2 var(--font-mono);
  color: var(--ink-40);
  white-space: nowrap;
}

.download__icon {
  flex: none;
  color: var(--ink-40);
}

/* Related items -------------------------------------------------------------*/

/*
 * "Accessories that fit" — one card per row.
 *
 * A deliberate departure from design 1b/04b, which draws this as a horizontal
 * strip three across. Three columns inside a 640px sheet leave each card about
 * 190px wide, and no fixed box that narrow holds an accessory without either
 * cropping it or shrinking it past recognition — which defeats the one thing
 * the block is for, since an accessory is identified by its shape. Every other
 * image on a public page now runs one to a row at its own proportions, and
 * this is the last place that did not.
 */
.related-strip {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.related-card {
  display: block;
  height: 100%;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  overflow: hidden;
  background: var(--surface);
  color: inherit;
}

.related-card:hover {
  color: inherit;
  border-color: var(--hairline-strong);
}

/*
 * No fixed box, so no crop. The picture takes its own height, the same way a
 * gallery photo and the hero do; the cap keeps a portrait accessory from
 * taking a whole screen when this block is something you scan on the way past,
 * not the subject of the page.
 */
.related-card__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 300px;
  margin-inline: auto;
}

.related-card__title {
  display: block;
  padding: 9px 10px;
  font: 600 14px/1.25 var(--font-sans);
  color: var(--ink);
}

/* ===========================================================================
   Content Library — Images

   No approved design screen exists for this: `1c` draws A1–A6 and the Image
   Library is not among them. So every component here is one A3 or A6 already
   uses — the two-column `content-layout`, the `card-panel` surface, `tabs`,
   `badge`, `btn`, `input` — and the only new rules are the grid the cards sit
   in and the chips that carry a category and a tag.
   =========================================================================== */

/* The content-type strip. `tabs__tab` is A4's pill, reused; this is only the
   bar it sits in, which spans the whole workspace rather than a card. */
.content-nav {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 44px;
  padding: 0 30px;
  border-bottom: 1px solid var(--hairline);
  background: var(--surface);
  font: 500 13px/1 var(--font-sans);
  color: var(--ink-60);
}

/* The Images › Library|Intake level, in the same bar rather than a second one.
   Two stacked 44px strips would cost more height than the distinction is
   worth, and "Images › Intake" reads as the path it is. */
.content-nav__divider {
  color: var(--ink-40);
  padding: 0 2px;
}

.content-nav__count {
  display: inline-block;
  min-width: 18px;
  padding: 1px 5px;
  border-radius: 9px;
  background: var(--status-draft);
  color: #fff;
  font: 600 11px/1.45 var(--font-sans);
  text-align: center;
}

.library-rail {
  border-right: 1px solid var(--hairline);
  min-height: 60vh;
  padding: 18px 16px;
}

/* Intake ------------------------------------------------------------------- */

.intake-review {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/*
 * The toolbar sticks, because the decision buttons belong beside the work.
 * Reviewing twenty images means scrolling, and a Keep button that scrolled away
 * with the first row would mean scrolling back for every batch.
 */
.intake-toolbar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
}

.intake-toolbar__select,
.intake-toolbar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.library-card--intake {
  position: relative;
}

/* A generous target over the corner of the thumbnail: the checkbox is the
   control this screen is operated with, not an afterthought beside a label. */
.library-card__pick {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  cursor: pointer;
}

.library-card__pick input {
  width: 16px;
  height: 16px;
  cursor: pointer;
}

.intake-decision {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.library-filters {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.library-filters__actions {
  display: flex;
  gap: 8px;
}

.library-upload {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  padding: 14px 18px;
}

.library-upload[open] {
  padding-bottom: 20px;
}

.library-upload__summary {
  font: 500 13.5px/1 var(--font-sans);
  color: var(--ink);
  cursor: pointer;
}

.library-upload[open] .library-upload__summary {
  margin-bottom: 16px;
}

/*
 * 200px, not the 180px of the editor's grid.
 *
 * A library card carries a name, a description, a category, its tags, its
 * dimensions and its usage. At 180px the tag row wrapped to three lines on
 * anything with more than two tags and the card stopped being scannable, which
 * is the only thing it is for.
 */
.library-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 18px;
}

.library-card {
  display: flex;
  flex-direction: column;
  gap: 7px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  padding: 12px;
  min-width: 0;
}

/* The whole picture-and-name block is one target, so the click that opens an
   image is the obvious one rather than a link buried under it. */
.library-card__link {
  display: flex;
  flex-direction: column;
  gap: 8px;
  color: var(--ink);
  min-width: 0;
}

.library-card__thumb {
  display: block;
}

.library-card__name {
  font: 600 13.5px/1.35 var(--font-sans);
  overflow-wrap: anywhere;
}

.library-card__alt {
  margin: 0;
  font: 400 12.5px/1.45 var(--font-sans);
  color: var(--ink-60);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.library-card__gap {
  color: var(--status-draft-ink);
}

.library-card__labels {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.library-card__facts {
  font: 400 11.5px/1.4 var(--font-mono);
  color: var(--ink-40);
  overflow-wrap: anywhere;
}

.library-card__usage {
  margin-top: auto;
}

.library-card__state {
  margin: 0;
  font: 500 12px/1.4 var(--font-sans);
  color: var(--status-published-ink);
}

/* An image already on this page is dimmed rather than hidden: hiding it would
   make the picker disagree with the editor about what exists. */
.library-card--attached {
  background: var(--canvas);
}

.library-pager {
  display: flex;
  align-items: center;
  gap: 12px;
}

.chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--radius-badge);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  font: 500 11.5px/1.5 var(--font-sans);
  color: var(--ink-60);
  overflow-wrap: anywhere;
}

.chip--category {
  background: rgba(44, 107, 237, 0.08);
  border-color: rgba(44, 107, 237, 0.18);
  color: var(--ink);
}

/* One image ---------------------------------------------------------------- */

.image-detail {
  padding: 26px 30px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.image-detail__layout {
  display: grid;
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}

.image-detail__preview {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
}

.image-detail__frame {
  aspect-ratio: auto;
  max-height: 460px;
}

.image-detail__frame .media-thumb__img {
  max-height: 460px;
}

.image-detail__panels {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.usage-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.usage-list__item {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 10px;
  border: 1px solid var(--hairline);
  border-radius: 9px;
}

.usage-list__title {
  font: 500 13px/1.4 var(--font-sans);
  color: var(--ink);
  min-width: 0;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .image-detail__layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Admin media tab -----------------------------------------------------------*/

/* Choose from library / Upload new image. A row, because the editor offers two
   ways to fill a slot and never a third. */
.media-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.media-panel {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.media-section {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.media-empty {
  margin: 0;
  padding: 14px;
  border: 1px dashed var(--hairline-strong);
  border-radius: 9px;
  color: var(--ink-40);
  font: 400 13.5px/1.5 var(--font-sans);
}

.media-thumb {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 9px;
  overflow: hidden;
  background: var(--thumb-bg);
  border: 1px solid var(--hairline);
}

.media-thumb--hero {
  max-width: 220px;
}

.media-thumb__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.media-hero {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

.media-hero__meta {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.media-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
}

.media-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.media-item__alt {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.media-item__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.media-item__caption {
  display: flex;
  gap: 6px;
}

.input--file {
  height: auto;
  padding: 10px 12px;
}

.upload {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.upload__status {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.upload__bar {
  height: 6px;
  border-radius: 3px;
  background: var(--canvas);
  overflow: hidden;
}

.upload__bar > span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 150ms ease-out;
}

.upload__message--error {
  color: #9c2b23;
}

/* Video --------------------------------------------------------------------*/

.video-section {
  padding-top: 20px;
  gap: 12px;
}

.video-player {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-media);
  background: var(--ink);
  display: block;
  object-fit: contain;
}

.video-embed {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-media);
  overflow: hidden;
  background: var(--ink);
}

.video-embed__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.media-thumb__blank {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 400 12px/1 var(--font-sans);
  color: var(--ink-40);
}

.media-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.media-list__item {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 10px;
  border: 1px solid var(--hairline);
  border-radius: 9px;
}

.media-list__item .t-caption {
  flex: 1;
  min-width: 0;
}

/* ===========================================================================
   QR labels — design 1c/A5, and the QR rail in 1c/A4.
   =========================================================================== */

.labels {
  display: grid;
  grid-template-columns: 330px minmax(0, 1fr);
  align-items: start;
  min-height: 60vh;
}

.labels__controls {
  border-right: 1px solid var(--hairline);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.labels__list {
  border: 1px solid var(--hairline-strong);
  border-radius: 10px;
  overflow: hidden;
  max-height: 260px;
  overflow-y: auto;
}

.labels__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--hairline-soft);
  cursor: pointer;
}

.labels__item:last-child {
  border-bottom: 0;
}

.labels__item:hover {
  background: var(--canvas);
}

.labels__item-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.labels__item-title {
  font: 400 14px/1.2 var(--font-sans);
  color: var(--ink);
}

.labels__item-path {
  font-size: 11px;
}

/* Stock, grouped: durable first, then the office labels that merely work.
   One card per row rather than a row of three — each carries a size, a count
   and a line saying what it is for, and a third of a rail is not enough width
   to read that in. */

.labels__group + .labels__group {
  margin-top: 14px;
}

.labels__group-heading {
  margin: 0 0 6px;
}

.labels__sizes {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.labels__size {
  min-height: 60px;
  border-radius: 9px;
  border: 1px solid var(--hairline-strong);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  cursor: pointer;
  padding: 8px;
  text-align: center;
}

.labels__size--active {
  border: 1.5px solid var(--accent);
  background: rgba(44, 107, 237, 0.05);
}

.labels__size-name {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  font: 600 13px/1.3 var(--font-sans);
  color: var(--ink);
}

.labels__size-meta {
  font-size: 11px;
}

.labels__size-note {
  font: 400 12px/1.4 var(--font-sans);
  color: var(--ink-60);
  text-wrap: pretty;
}

/* Home page entrance label. Below the sheet rather than inside it: this is not
   one of the pages, it is the front door, and reaching for it is a different
   job from labelling a rack of machines. One cell is previewed, not a sheet —
   every copy is identical, so twenty-four of them would show nothing the first
   does not. */

.labels__home {
  border-top: 1px solid var(--hairline);
  padding: 22px 24px;
  background: var(--surface);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px 24px;
  align-items: start;
}

.labels__home > .t-overline,
.labels__home > .t-caption,
.labels__home > .notice-bar {
  grid-column: 1 / -1;
  margin: 0;
}

.labels__home-form,
.labels__home-preview {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.labels__home-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.labels__home-preview .sheet__cell {
  aspect-ratio: auto;
}

.labels__home-url {
  margin: 0;
  font-size: 12px;
  color: var(--ink-60);
  overflow-wrap: anywhere;
}

/* Already inside a padded section, so it keeps the rule and drops the inset. */
.labels__home .labels__download {
  grid-column: 1 / -1;
  padding: 16px 0 0;
  background: none;
}

/* The site initials, standing in for a logo that cannot be embedded. Sized in
   the cell the same way the PDF sizes it in the label. */
.sheet__initials {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font: 600 clamp(10px, 2.6vw, 22px) / 1 var(--font-sans);
  color: var(--ink);
  letter-spacing: -0.02em;
}

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

.labels__start {
  margin: 0;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: var(--canvas);
  font: 500 13px/1 var(--font-sans);
  color: var(--ink);
}

.labels__actions {
  display: flex;
  gap: 8px;
}

.labels__preview {
  background: #edeeef;
  padding: 26px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

.labels__download {
  border-top: 1px solid var(--hairline);
  padding: 18px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  background: var(--surface);
  flex-wrap: wrap;
}

.labels__download-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.labels__download-text p {
  margin: 0;
}

/* Read at the moment the file is downloaded, which is the last point before a
   sheet of adhesive stock goes through a printer. The PDF asks for the same
   settings itself, but a viewer may ignore the hint and a dialogue can always
   override it. */
.labels__print-note {
  font: 400 12.5px/1.45 var(--font-sans);
  color: var(--warn-ink);
  text-wrap: pretty;
}

.labels__print-note strong {
  font-weight: 600;
}

/* Sheet preview ------------------------------------------------------------*/

/*
 * Label sheet preview.
 *
 * The proportions are A4 and therefore constant, so they live here rather than
 * being recomputed into the markup. Only the column and row counts vary — they
 * come from admin-editable label stock — and those arrive from a nonce'd
 * <style> the page emits, which is the one thing the CSP does authorise.
 */
.sheet {
  display: grid;
  gap: 4px;
  width: 100%;
  max-width: 520px;
  background: #fff;
  box-shadow: 0 3px 16px rgba(16, 22, 28, 0.14);
  padding: 16px 12px;
  aspect-ratio: 210 / 297;
  max-height: 141.4vh;
}

.sheet__cell {
  border-radius: 5px;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px;
  cursor: pointer;
  min-height: 0;
  overflow: hidden;
}

/*
 * A filled cell's columns are proportioned by the nonce'd <style> the page
 * emits, from the same ContentLayout the PDF draws from. The gap comes from
 * the template too, so `gap` here is overridden rather than added to.
 */
.sheet__cell--filled {
  gap: 0;
  align-items: stretch;
}

.sheet__slot {
  display: flex;
  min-width: 0;
  min-height: 0;
  align-items: center;
}

.sheet__slot--image {
  justify-content: center;
  /* White behind the picture, exactly as the renderer draws it: artwork with
     transparency must not pick up the sheet colour. */
  background: #fff;
}

.sheet__slot--text {
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 1px;
  padding: 0 4px;
}

.sheet__slot--qr {
  justify-content: center;
}

.sheet__image {
  max-width: 100%;
  max-height: 100%;
  /* Contain, never crop — the same rule the PDF applies. */
  object-fit: contain;
}

.sheet__cell--filled {
  border: 1px solid rgba(16, 22, 28, 0.1);
}

.sheet__cell--free {
  border: 1px dashed rgba(16, 22, 28, 0.12);
  background: #f7f8f8;
}

.sheet__cell--skipped {
  border: 1px dashed rgba(16, 22, 28, 0.08);
  background: repeating-linear-gradient(
    45deg,
    #f2f3f3,
    #f2f3f3 4px,
    #eaebec 4px,
    #eaebec 8px
  );
}

.sheet__cell:has(input:checked) {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.sheet__cell:has(input:focus-visible) {
  outline: 2px solid var(--accent);
}

.sheet__qr {
  width: 100%;
  aspect-ratio: 1;
  max-height: 100%;
  background:
    conic-gradient(from 0deg, #14171a 0 25%, #fff 0 50%, #14171a 0 75%, #fff 0);
}

.sheet__title {
  font: 600 9px/1.2 var(--font-sans);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.sheet__subtitle,
.sheet__instruction {
  font: 400 7px/1.2 var(--font-sans);
  color: var(--ink-60);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.sheet__instruction {
  color: var(--ink-40);
}

/* Fallback report ----------------------------------------------------------*/

.labels__fallbacks {
  width: 100%;
  max-width: 520px;
  margin-top: 16px;
  padding: 12px 14px;
  border-radius: 9px;
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
}

.labels__fallback-list {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 5px;
  font: 400 12px/1.35 var(--font-sans);
  color: var(--ink-60);
}

.labels__fallback-count {
  display: inline-block;
  min-width: 20px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--canvas);
  font: 600 11px/18px var(--font-mono);
  color: var(--ink);
  text-align: center;
}

.sheet__index {
  font: 400 9px/1 var(--font-mono);
  color: var(--ink-40);
  margin: auto;
}

/* QR rail ------------------------------------------------------------------*/

.qr-preview {
  display: flex;
  gap: 12px;
  align-items: center;
}

.qr-preview__image {
  width: 84px;
  height: 84px;
  flex: none;
  border: 1px solid var(--hairline-strong);
  border-radius: 8px;
  background: #fff;
}

.qr-preview__meta {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.qr-preview__meta p {
  margin: 0;
}

.qr-preview__url {
  font-size: 11.5px;
  word-break: break-all;
}

.qr-preview__actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

@media (max-width: 1100px) {
  .labels {
    grid-template-columns: 1fr;
  }

  .labels__controls {
    border-right: 0;
    border-bottom: 1px solid var(--hairline);
  }
}

/* ===========================================================================
   Site settings — design 1c/A6.

   A6 is a 210px rail of pill links beside a 660px column of white cards. The
   rail is the shell every future settings page plugs into, so its geometry
   lives here once and no page restates it.
   =========================================================================== */

.settings {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 10px;
  align-items: start;
}

.settings__nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px 0;
}

.settings__nav-item {
  min-height: 34px;
  display: flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: 8px;
  font: 400 14px/1.3 var(--font-sans);
  color: var(--ink-60);
}

.settings__nav-item:hover {
  color: var(--ink);
  background: rgba(16, 22, 28, 0.04);
}

.settings__nav-item--active {
  background: var(--surface);
  color: var(--ink);
  font-weight: 500;
  box-shadow: 0 1px 2px rgba(16, 22, 28, 0.06);
}

.settings__main {
  max-width: 660px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* 660px is A6's reading measure for a column of form fields. A data table is
   not that, and Users renders one through the same shell. */
.settings__main--wide {
  max-width: none;
}

.settings__head {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.settings__head .t-caption {
  margin: 0;
}

.settings-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.settings-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-inner);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.settings-card--row {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 22px;
}

.settings__actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  align-items: center;
}

/* Informational values — General. Read-only by design, so they are a
   description list rather than disabled inputs: a greyed-out field invites a
   click that will never do anything. */

.settings-facts {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.settings-facts__row {
  display: grid;
  grid-template-columns: 170px minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
}

.settings-facts__label {
  font: 500 13px/1.4 var(--font-sans);
  color: var(--ink);
}

.settings-facts__value {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font: 400 14px/1.4 var(--font-sans);
  color: var(--ink);
  min-width: 0;
  overflow-wrap: anywhere;
}

.settings-facts__value .field__hint {
  margin: 0;
}

.settings-keys {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font: 400 13px/1.5 var(--font-sans);
  color: var(--ink-60);
}

.settings-keys code {
  font: 400 12.5px/1.4 var(--font-mono);
}

/* Field-level validation. The summary above the form names every problem; this
   puts the same sentence beside the input that caused it, because a list at the
   top of a six-field form does not say which box to fix. */

.field__error {
  margin: 0;
  font: 400 13px/1.45 var(--font-sans);
  color: #9c2b23;
}

.input--invalid,
.input--invalid:focus {
  border-color: rgba(208, 69, 59, 0.55);
}

.input--area {
  height: auto;
  padding: 11px 13px;
  line-height: 1.5;
  resize: vertical;
  font-family: var(--font-sans);
}

/* Logo */

.logo-field {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.logo-preview {
  width: 52px;
  height: 52px;
  flex: none;
  border-radius: 11px;
  background: var(--ink);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font: 600 19px/1 var(--font-sans);
}

.logo-preview__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.logo-field__controls {
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
  flex: 1;
}

.logo-remove {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 400 13px/1.4 var(--font-sans);
  color: var(--ink-60);
  min-height: var(--tap-min);
}

/* Accent swatches */

.swatches {
  display: flex;
  gap: 9px;
  align-items: center;
  flex-wrap: wrap;
}

.swatch {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--swatch);
  cursor: pointer;
  flex: none;
  display: block;
}

/*
 * One rule per accent preset, keyed by the slug on ACCENT_PRESETS.
 *
 * `--swatch` used to be set by a style attribute, which the CSP drops — so
 * `background: var(--swatch)` resolved to nothing and the swatches rendered
 * colourless. The preset list is a fixed constant, so the colours belong here;
 * these values and ACCENT_PRESETS are asserted to agree in the settings tests.
 */
.swatch--blue {
  --swatch: #2c6bed;
}

.swatch--green {
  --swatch: #177a52;
}

.swatch--rust {
  --swatch: #b4462a;
}

.swatch--violet {
  --swatch: #5b4fcf;
}

.swatch--ink {
  --swatch: #14171a;
}

.swatch--active {
  box-shadow:
    0 0 0 2px var(--surface),
    0 0 0 4px var(--swatch);
}

.swatch__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.swatch:has(.swatch__input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.input--hex {
  width: 116px;
  height: 34px;
  padding: 0 12px;
  font: 400 13px/1 var(--font-mono);
  text-transform: uppercase;
}

/* Logo in the site mark. The tile keeps its shape; the image is contained
   inside it, so a non-square logo letterboxes rather than distorting. */

.mark--logo {
  background: var(--surface);
  border: 1px solid var(--hairline);
  overflow: hidden;
  padding: 2px;
}

.mark__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Home identity block ------------------------------------------------------

   Logo left, site name and introduction stacked to its right — one row,
   because they are one identity. See the note in templates/public/home.ts for
   why this departs from the design's stacked 34px mark.

   `--mark` drives both the logo box and the initials tile's type, so the two
   fallbacks stay the same size at every breakpoint. The public shell is capped
   at --shell-max, so these queries are about the *viewport*: past 640px the
   column stops widening and only the mark and gap still respond. */

.site-head {
  --mark: 64px;
  --mark-gap: 16px;
  display: flex;
  align-items: center;
  gap: var(--mark-gap);
}

/* Sized here rather than on .mark so the 34px tile the admin login still uses
   is untouched. */
.mark--home {
  width: var(--mark);
  height: var(--mark);
  border-radius: var(--radius-media);
  font-size: calc(var(--mark) * 0.34);
}

/* Releases the image from the width/height attributes, which exist only so the
   box has an aspect ratio before the bytes arrive. `auto` beats a presentation
   hint, so the logo renders at its own resolution and the max-* pair caps it:
   a 512px source fills the box, and a source smaller than the box sits at its
   own size rather than being upscaled into mush. */
.mark--home .mark__img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* min-width: 0 is what stops a long unbroken site name from pushing the row
   past the shell — a flex item's default min-width is its content. */
.site-head__text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* One line at 640px and up, a balanced two-line wrap on a phone, and never an
   overflow: the design's own home screen breaks this name across two lines at
   390px, so the wrap is the intended state and not a failure of the fit. */
.site-head__title {
  font-size: clamp(24px, 6.2vw, 32px);
  line-height: 1.15;
  text-wrap: balance;
  overflow-wrap: anywhere;
}

.site-head__sub {
  max-width: none;
}

@media (min-width: 640px) {
  .site-head {
    --mark: 78px;
    --mark-gap: 20px;
  }
}

@media (min-width: 1024px) {
  .site-head {
    --mark: 86px;
    --mark-gap: 22px;
  }
}

@media (max-width: 860px) {
  .settings {
    grid-template-columns: 1fr;
    gap: 16px;
  }

  .settings__nav {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .settings-card--row {
    flex-direction: column;
    align-items: stretch;
  }

  .settings-facts__row {
    grid-template-columns: 1fr;
    gap: 2px;
  }
}

/* Label stock — Settings → Labels ------------------------------------------
   Grouped by what the choice is between: durable stock for equipment, then
   office stock that merely prints correctly. Not by the manufacturer's product
   families — nobody is deciding between "Ultra Resistant" and "Address
   Labels", they are deciding how big the label is and how many are on a
   sheet. */

.stock-group__head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.stock-group__head .t-caption {
  margin: 0;
}

.stock-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
}

.stock-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
  border-top: 1px solid var(--hairline);
  flex-wrap: wrap;
}

.stock-item:first-child {
  border-top: 0;
  padding-top: 0;
}

.stock-item__text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1 1 260px;
}

.stock-item__title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font: 500 14px/1.3 var(--font-sans);
  color: var(--ink);
}

.stock-item__meta {
  font: 400 12.5px/1.4 var(--font-mono);
  color: var(--ink-60);
}

.stock-item__note {
  margin: 2px 0 0;
  font: 400 12.5px/1.45 var(--font-sans);
  color: var(--ink-60);
  text-wrap: pretty;
}

.stock-item__warn {
  margin: 2px 0 0;
  font: 400 12.5px/1.45 var(--font-sans);
  color: var(--warn-ink);
  text-wrap: pretty;
}

.stock-item__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
  flex-wrap: wrap;
}

.stock-item__actions form {
  margin: 0;
}

/* Geometry editor. Every field is a number read off a datasheet or measured
   with a rule — a two-column grid, not a canvas. */

.geometry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px 16px;
}

.geometry-grid .input {
  height: 40px;
}

@media (max-width: 720px) {
  .stock-item__actions {
    width: 100%;
  }
}

/* Manufacturer import settings ---------------------------------------------
   A prompt is prose an administrator reads and rewrites, so the editor is
   monospace and tall — the placeholders line up and a long instruction stays
   on one visual line. */

.prompt-editor {
  min-height: 420px;
  font: 400 13px/1.55 var(--font-mono);
  tab-size: 2;
}

.placeholder-ref {
  border: 1px solid var(--hairline);
  border-radius: 9px;
  padding: 12px 14px;
}

.placeholder-ref > summary {
  cursor: pointer;
  font: 500 13px/1 var(--font-sans);
  color: var(--ink);
}

.placeholder-ref[open] > summary {
  margin-bottom: 12px;
}

.placeholder-list {
  margin: 0 0 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.placeholder-list__row {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
}

.placeholder-list__row dt code {
  font: 400 12.5px/1.4 var(--font-mono);
  color: var(--ink);
}

.placeholder-list__row dd {
  margin: 0;
  font: 400 13px/1.45 var(--font-sans);
  color: var(--ink-60);
}

/* The two halves of the assembled prompt, labelled above the copyable text. */

.preview-split {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
}

.preview-split__part {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.preview-split__part .t-caption {
  margin: 0;
}

@media (max-width: 720px) {
  .placeholder-list__row {
    grid-template-columns: 1fr;
    gap: 2px;
  }
}

/* User management — 1c/A6 Users ---------------------------------------------
   A table, a form, and a page per account. Nothing here needs to scale past a
   couple of dozen rows, so there is no pagination and no virtualisation. */

.user-filters {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

.user-filters .field {
  flex: 1 1 150px;
  min-width: 0;
}

.user-filters .btn {
  flex: none;
}

.role-choice {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.user-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.user-actions form {
  margin: 0;
}

/* Sessions. Identified by a digest of the id, never the id — see
   listSessionsForUser in src/services/sessions.ts. */

.session-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
}

.session-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-top: 1px solid var(--hairline);
  flex-wrap: wrap;
}

.session-item:first-child {
  border-top: 0;
  padding-top: 0;
}

.session-item__text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.session-item__when {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font: 400 14px/1.4 var(--font-sans);
  color: var(--ink);
}

.session-item__meta {
  font: 400 12.5px/1.4 var(--font-mono);
  color: var(--ink-60);
}

.session-item form {
  margin: 0;
  flex: none;
}

@media (max-width: 720px) {
  .user-filters .field {
    flex-basis: 100%;
  }
}

/* Import from a webpage ----------------------------------------------------*/

/*
 * The candidate grid needs more width than a settings column and less than the
 * full workspace: each card carries a proxied thumbnail, where it was found and
 * its whole address, and at 660px the addresses wrapped to four lines each.
 */
.narrow--wide {
  max-width: 1080px;
}
