/* =========================================================================
   Paite Heritage — design system

   Direction: the puandum, the Paite ceremonial shawl. A black ground crossed
   by narrow rhythmic bands of hill green, brass yellow and madder red. That
   band sequence is the only ornament on this site, and it appears in exactly
   three places: the loom rail down the page edge, the section dividers, and
   the rating meters. Everything else stays quiet.

   The sequence holds fixed proportions — black 60%, green 10%, yellow 10%,
   red 20% — and is mirrored on the red, so the red sits at the centre of each
   repeat as a single solid band with green and yellow flanking it on both
   sides. See `.band` for the arithmetic.

   Type: a humanist calligraphic serif for display (Hoefler/Palatino lineage),
   a neutral UI sans for body, and one rule that carries meaning — Paite words
   are always set in display italic, wherever they appear.
   ========================================================================= */

:root {
  /* Ground: the indigo-black warp of the shawl. */
  --indigo-900: #0e141c;
  --indigo-800: #131b26;
  --indigo-700: #1b2735;
  --indigo-600: #24344a;

  /* The band colours, in the order they sit on the cloth. */
  --madder: #c8402f;
  --madder-lift: #e05a45;
  --weave: #f0ece2;
  --moss: #55876a;
  --moss-lift: #74ab89;

  /* Ochre from the brass and bead work. Carries the yellow of the band, and
     doubles as the membership/consensus accent. */
  --gold: #d9a441;

  /* The band's ground. Set a shade under --indigo-900 so the rail reads as its
     own object against the page rather than dissolving into it. */
  --band-black: #070b0f;

  /* Ridge haze: the blue-grey of the Chin Hills at distance. */
  --haze: #93a6b5;
  --haze-dim: #6b7d8c;

  --paper: #e6e3da;
  --paper-dim: #cfccc1;

  --band-height: 3px;
  --rail-width: 14px;
  --radius: 3px;
  --measure: 58ch;

  --shell: min(1240px, 100% - 3rem);

  --font-display: 'Hoefler Text', 'Iowan Old Style', 'Palatino Linotype', Palatino,
    'URW Palladio L', 'Book Antiqua', serif;
  --font-body: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  --step--1: clamp(0.78rem, 0.76rem + 0.1vw, 0.84rem);
  --step-0: clamp(1rem, 0.97rem + 0.16vw, 1.09rem);
  --step-1: clamp(1.2rem, 1.12rem + 0.4vw, 1.45rem);
  --step-2: clamp(1.5rem, 1.32rem + 0.9vw, 2.1rem);
  --step-3: clamp(2rem, 1.6rem + 2vw, 3.1rem);
  --step-4: clamp(2.6rem, 1.7rem + 4.4vw, 5.4rem);

  --ease: cubic-bezier(0.2, 0.6, 0.2, 1);
}

/* ---------------------------------------------------------------- base --- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The site header is sticky, so a fragment jump that lands the target at y=0
     lands it *underneath* the header. Reserving its height plus a little air
     means "#collections" arrives with its heading actually visible. Applies to
     the skip link and every in-page anchor, not just the nav. */
  scroll-padding-top: 5.25rem;
}

body {
  margin: 0;
  background: var(--indigo-900);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img,
video {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: inherit;
}

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

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--gold);
  color: var(--indigo-900);
  padding: 0.75rem 1.25rem;
  font-weight: 600;
}
.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

.shell {
  width: var(--shell);
  margin-inline: auto;
}

/* Paite-language words are always set this way, sitewide. */
.paite {
  font-family: var(--font-display);
  font-style: italic;
  letter-spacing: 0.01em;
}

.eyebrow {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--haze);
  margin: 0 0 1rem;
  font-weight: 600;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* --------------------------------------------------- the band + loom rail - */

/* The puandum sequence, expressed once and reused everywhere.

   Proportions of one repeat: black 60% · green 10% · yellow 10% · red 20%.

   The pattern is mirrored on the red, so red is the axis of symmetry: its two
   halves meet as a single solid centre band, and every other colour appears
   twice, once on each side. The repeat therefore reads

       black · green · yellow · RED · yellow · green · black

   with each flanking colour carrying half its share. Over a 160px repeat that
   is black 2x48, green 2x8, yellow 2x8, red 1x32 — 96/16/16/32, exactly
   60/10/10/20. The ground carries the slack: green and yellow grow inward at
   black's expense, so red keeps both its 20% share and its exact position.
   Keep any change to the repeat length divisible by 20 so the 10% bands stay
   on whole pixels. */
.band {
  height: var(--band-height);
  border: 0;
  margin: 0;
  background: repeating-linear-gradient(
    90deg,
    var(--band-black) 0 48px,
    var(--moss) 48px 56px,
    var(--gold) 56px 64px,
    var(--madder) 64px 96px,
    var(--gold) 96px 104px,
    var(--moss) 104px 112px,
    var(--band-black) 112px 160px
  );
}

/* Signature element: a fixed vertical run of the same weave down the page
   edge. It is the spine the whole layout hangs off. */
/* Same 60/10/10/20 mirrored sequence, run vertically over a 200px repeat:
   black 2x60, green 2x10, yellow 2x10, red 1x40. The rail is the longest run
   of the pattern on the page, so it gets the roomier repeat. */
.loom-rail {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  width: var(--rail-width);
  z-index: 60;
  background: repeating-linear-gradient(
    180deg,
    var(--band-black) 0 60px,
    var(--moss) 60px 70px,
    var(--gold) 70px 80px,
    var(--madder) 80px 120px,
    var(--gold) 120px 130px,
    var(--moss) 130px 140px,
    var(--band-black) 140px 200px
  );
  border-inline-end: 1px solid rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

body {
  padding-inline-start: var(--rail-width);
}

/* --------------------------------------------------------------- header --- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(14, 20, 28, 0.82);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(147, 166, 181, 0.16);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: 68px;
}

.wordmark {
  font-family: var(--font-display);
  font-size: var(--step-1);
  letter-spacing: 0.02em;
  text-decoration: none;
  margin-inline-end: auto;
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
}

.wordmark__mark {
  color: var(--madder);
}

.site-nav {
  display: flex;
  gap: 1.75rem;
  align-items: center;
}

.site-nav a {
  text-decoration: none;
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--haze);
  padding-block: 0.5rem;
  border-bottom: 2px solid transparent;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}

.site-nav a:hover,
.site-nav a[aria-current='page'] {
  color: var(--paper);
  border-bottom-color: var(--madder);
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid rgba(147, 166, 181, 0.3);
  color: var(--paper);
  border-radius: var(--radius);
  padding: 0.5rem 0.7rem;
  font-size: var(--step--1);
  cursor: pointer;
}

/* --------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0.85rem 1.6rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 140ms var(--ease), background-color 160ms var(--ease),
    border-color 160ms var(--ease), color 160ms var(--ease);
}

.btn:hover {
  transform: translateY(-1px);
}

.btn--primary {
  background: var(--madder);
  color: var(--weave);
}
.btn--primary:hover {
  background: var(--madder-lift);
}

.btn--ghost {
  border-color: rgba(240, 236, 226, 0.35);
  color: var(--paper);
}
.btn--ghost:hover {
  border-color: var(--paper);
  background: rgba(240, 236, 226, 0.07);
}

.btn--small {
  padding: 0.55rem 1rem;
  letter-spacing: 0.06em;
}

/* ------------------------------------------------------------------ hero -- */

.hero {
  position: relative;
  padding-block: clamp(4rem, 9vw, 8.5rem) clamp(3rem, 6vw, 5rem);
  overflow: hidden;
}

/* Receding ridgelines — the Chin Hills at distance, built from layered
   radial washes rather than an image so it costs nothing to serve. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 70% at 12% 100%, rgba(85, 135, 106, 0.22), transparent 60%),
    radial-gradient(90% 60% at 88% 0%, rgba(200, 64, 47, 0.16), transparent 62%),
    linear-gradient(180deg, var(--indigo-800), var(--indigo-900));
  z-index: -1;
}

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: end;
}

.hero__title {
  font-family: var(--font-display);
  font-size: var(--step-4);
  line-height: 0.94;
  letter-spacing: -0.02em;
  margin: 0 0 1.5rem;
  font-weight: 400;
}

.hero__title em {
  color: var(--madder-lift);
  font-style: italic;
}

.hero__lede {
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--paper-dim);
  max-width: var(--measure);
  margin: 0 0 2.25rem;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
}

.hero__note {
  margin: 1.5rem 0 0;
  font-size: var(--step--1);
  color: var(--haze-dim);
}

/* Stat rail sits in the hero's second column on desktop, below on mobile. */
.stat-rail {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: rgba(147, 166, 181, 0.18);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
  overflow: hidden;
}

.stat {
  background: var(--indigo-800);
  padding: 1.4rem 1.25rem;
}

.stat__figure {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: 1;
  display: block;
  color: var(--weave);
}

.stat__label {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--haze-dim);
  margin-top: 0.45rem;
  display: block;
}

/* -------------------------------------------------------------- sections -- */

.section {
  padding-block: clamp(3.5rem, 7vw, 6.5rem);
}

.section--raised {
  background: var(--indigo-800);
}

.section--paper {
  background: var(--paper);
  color: var(--indigo-900);
}

.section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.25rem 2rem;
  margin-bottom: 2.75rem;
}

.section__title {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 0;
  font-weight: 400;
}

.section__note {
  font-size: var(--step--1);
  color: var(--haze);
  max-width: 42ch;
  margin: 0;
}

.section--paper .section__note {
  color: #5c6670;
}

/* ---------------------------------------------- administrator-edited copy

   Landing copy is stored as Markdown and rendered through the shared renderer,
   which wraps a paragraph in <p>. Several slots that used to *be* a <p> are now
   a container holding one, so the inner paragraph has to inherit the slot's
   typography and drop its own margins — otherwise every edited section grows a
   blank line it never had.

   The last-child rule keeps a multi-paragraph field readable: the gaps between
   paragraphs stay, only the trailing margin goes. */
.landing-copy .hero__lede > p,
.landing-copy .hero__note > p,
.landing-copy .section__note > p,
.landing-copy .empty-state > p,
.landing-copy .steps p,
.landing-copy .consensus__body > p {
  font: inherit;
  color: inherit;
  max-width: none;
  margin-top: 0;
}

.landing-copy .hero__lede > p:last-child,
.landing-copy .hero__note > p:last-child,
.landing-copy .section__note > p:last-child,
.landing-copy .empty-state > p:last-child,
.landing-copy .steps p:last-child,
.landing-copy .consensus__body > p:last-child {
  margin-bottom: 0;
}

/* In landing copy an italic *is* a Paite word — that is what the asterisk
   convention in the editor means — so it gets the sitewide treatment rather
   than a generic oblique. Scoped here so article prose is unaffected. */
.landing-copy em {
  font-family: var(--font-display);
  font-style: italic;
  letter-spacing: 0.01em;
}

.section__link {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 650;
  color: var(--madder-lift);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
  white-space: nowrap;
}

.section--paper .section__link {
  color: var(--madder);
}

/* ---------------------------------------------------------- media ranking - */

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: 1.5rem;
}

/* The two highest-ranked items take a double cell — the ranking is the
   layout, not just the order. */
.gallery__item--lead {
  grid-column: span 2;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--indigo-700);
  border: 1px solid rgba(147, 166, 181, 0.14);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  height: 100%;
  transition: transform 200ms var(--ease), border-color 200ms var(--ease);
}

.card:hover {
  transform: translateY(-3px);
  border-color: rgba(200, 64, 47, 0.55);
}

.card__frame {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--indigo-600);
  overflow: hidden;
  /* So the plate's icon can be sized against the frame it sits in — the lead
     tiles are twice the width of the rest, and one fixed glyph size cannot
     serve both. Inline-size only: the height still comes from aspect-ratio. */
  container-type: inline-size;
}

.gallery__item--lead .card__frame {
  aspect-ratio: 16 / 9;
}

.card__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 600ms var(--ease);
}

.card:hover .card__frame img {
  transform: scale(1.04);
}

/* Media with no picture of its own gets a plate: an icon for the kind above
   the word, so the card still reads as something at gallery scale. */
/* Flex, not grid: a centred grid whose content outgrows the frame lets its rows
   collide, and a linked item's plate carries four things — icon, kind, host,
   title. Long titles were printing straight through the icon. */
.card__plate {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  text-align: center;
  padding: 1.25rem;
  overflow: hidden;
  background: linear-gradient(150deg, var(--indigo-600), var(--indigo-800));
}

/* The title is the one part with no upper bound, so it is the part that gets
   cut. The card body repeats it in full underneath. */
.card__plate .paite {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-size: var(--step--1);
  line-height: 1.35;
  overflow-wrap: anywhere;
}

/* Sized off the card, not the text: the plate is the card's whole picture, and
   a glyph pinned to a font size looks lost in the lead tiles. */
.card__plate-icon {
  width: clamp(1.75rem, 14cqi, 3.25rem);
  height: auto;
  color: var(--haze);
  opacity: 0.65;
  transition: opacity 300ms var(--ease), color 300ms var(--ease);
}

.card:hover .card__plate-icon {
  color: var(--madder-lift);
  opacity: 1;
}

.card__plate-kind {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--haze);
}

/* Where a linked item actually lives. Set in the body face and left in the
   host's own lower case: it is a domain the reader may recognise, not a label
   of ours to style into a heading. */
.card__plate-source {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-size: var(--step--1);
  color: var(--haze-dim);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card__plate-source-icon {
  width: 0.85em;
  height: 0.85em;
  flex: none;
}

.card__rank {
  position: absolute;
  top: 0.85rem;
  left: 0.85rem;
  background: rgba(14, 20, 28, 0.86);
  color: var(--weave);
  font-family: var(--font-display);
  font-size: var(--step--1);
  padding: 0.2rem 0.6rem;
  border-radius: 2px;
  letter-spacing: 0.06em;
}

.card__body {
  padding: 1.15rem 1.25rem 1.35rem;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  flex: 1;
}

.card__title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.2;
  margin: 0;
  font-weight: 400;
}

.card__meta {
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin: 0;
}

.card__meta a {
  color: var(--haze);
  text-decoration: none;
  border-bottom: 1px solid rgba(147, 166, 181, 0.35);
}

.card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: auto;
  padding-top: 0.35rem;
}

.tag {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 650;
  color: var(--moss-lift);
  border: 1px solid rgba(85, 135, 106, 0.45);
  border-radius: 999px;
  padding: 0.2rem 0.6rem;
  text-decoration: none;
}

.tag:hover {
  background: rgba(85, 135, 106, 0.16);
}

/* Rating shown as five woven threads rather than stars — same language as
   the loom rail and the consensus marks. */
.threads {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

.threads__track {
  display: flex;
  gap: 3px;
}

.threads__seg {
  width: 18px;
  height: 5px;
  border-radius: 1px;
  background: rgba(240, 236, 226, 0.16);
}

.threads__seg--on {
  background: var(--madder);
}

.threads__seg--half {
  background: linear-gradient(90deg, var(--madder) 50%, rgba(240, 236, 226, 0.16) 50%);
}

.threads__value {
  font-size: var(--step--1);
  color: var(--haze);
  font-variant-numeric: tabular-nums;
}

/* Only the paper variant had a prompt rule; the item page rates on the dark
   ground too. */
.threads__prompt {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--haze-dim);
  font-weight: 650;
}

/* Says why the control is a readout rather than buttons. */
.threads__note {
  font-size: var(--step--1);
  color: var(--haze-dim);
  font-style: italic;
}

.empty-state {
  border: 1px dashed rgba(147, 166, 181, 0.35);
  border-radius: var(--radius);
  padding: clamp(2rem, 5vw, 3.5rem);
  text-align: center;
  color: var(--haze);
}

.empty-state h3 {
  font-family: var(--font-display);
  font-size: var(--step-2);
  margin: 0 0 0.6rem;
  color: var(--paper);
  font-weight: 400;
}

.empty-state p {
  margin: 0 auto;
  max-width: 46ch;
}

/* ---------------------------------------------------------- collections --- */

.collections {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 230px), 1fr));
  gap: 1px;
  background: rgba(147, 166, 181, 0.16);
  border: 1px solid rgba(147, 166, 181, 0.16);
  border-radius: var(--radius);
  overflow: hidden;
}

.collection {
  background: var(--indigo-900);
  padding: 1.6rem 1.4rem;
  text-decoration: none;
  color: inherit;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 160px;
  transition: background-color 180ms var(--ease);
}

.collection:hover {
  background: var(--indigo-700);
}

.collection__name {
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin: 0;
  font-weight: 400;
}

.collection__desc {
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin: 0;
  flex: 1;
}

.collection__count {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--moss-lift);
  font-weight: 650;
}

/* Standing note under the collections grid on /collections. */
.collections__total {
  margin-top: 1.25rem;
  max-width: none;
}

/* ------------------------------------------------------------- consensus -- */

.consensus {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

.consensus__body p {
  color: var(--paper-dim);
  max-width: var(--measure);
}

/* Five marks because five endorsements are literally required — the count is
   the content. */
.vouches {
  display: flex;
  gap: 0.75rem;
  margin: 2rem 0 1.25rem;
}

.vouch {
  flex: 1;
  height: 64px;
  border-radius: 2px;
  background: linear-gradient(180deg, rgba(217, 164, 65, 0.85), rgba(217, 164, 65, 0.35));
  position: relative;
}

.vouch::after {
  content: attr(data-step);
  position: absolute;
  inset-inline: 0;
  bottom: -1.6rem;
  text-align: center;
  font-size: var(--step--1);
  color: var(--haze-dim);
}

.vouch--empty {
  background: rgba(240, 236, 226, 0.1);
}

.steps {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  display: grid;
  gap: 1.4rem;
}

.steps li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

.steps__marker {
  width: 4px;
  align-self: stretch;
  background: var(--gold);
  border-radius: 2px;
}

.steps h3 {
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin: 0 0 0.3rem;
  font-weight: 400;
}

.steps p {
  margin: 0;
  font-size: var(--step--1);
  color: var(--haze);
}

/* -------------------------------------------------------------- articles -- */

.articles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: 2rem;
}

.article-card {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  text-decoration: none;
  color: inherit;
  padding-top: 1.5rem;
  border-top: 2px solid var(--indigo-900);
}

.article-card__title {
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.15;
  margin: 0;
  font-weight: 400;
}

.article-card:hover .article-card__title {
  color: var(--madder);
}

.article-card__summary {
  margin: 0;
  color: #4d565f;
  font-size: var(--step-0);
}

.article-card__meta {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #6b747d;
  margin: auto 0 0;
}

/* ---------------------------------------------------------------- footer -- */

.site-footer {
  background: var(--indigo-900);
  padding-block: 3.5rem 2.5rem;
  border-top: 1px solid rgba(147, 166, 181, 0.16);
}

.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(auto-fit, minmax(140px, 1fr));
  gap: 2.5rem;
  margin-bottom: 2.5rem;
}

.footer-grid h2 {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--haze-dim);
  margin: 0 0 1rem;
}

.footer-grid ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}

.footer-grid a {
  text-decoration: none;
  color: var(--paper-dim);
  font-size: var(--step--1);
}

.footer-grid a:hover {
  color: var(--madder-lift);
}

.footer-blurb {
  color: var(--haze-dim);
  font-size: var(--step--1);
  max-width: 40ch;
  margin: 0.75rem 0 0;
}

.footer-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--step--1);
  color: var(--haze-dim);
  border-top: 1px solid rgba(147, 166, 181, 0.14);
  padding-top: 1.75rem;
}

/* ----------------------------------------------------------------- forms -- */

.form-shell {
  max-width: 460px;
  margin: clamp(2rem, 6vw, 5rem) auto;
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
  padding: clamp(1.75rem, 4vw, 2.5rem);
}

.form-shell h1 {
  font-family: var(--font-display);
  font-size: var(--step-2);
  margin: 0 0 0.5rem;
  font-weight: 400;
}

.field {
  display: grid;
  gap: 0.4rem;
  margin-bottom: 1.15rem;
}

.field label {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--haze);
  font-weight: 650;
}

.field input,
.field select,
.field textarea {
  font: inherit;
  font-size: var(--step-0);
  background: var(--indigo-900);
  color: var(--paper);
  border: 1px solid rgba(147, 166, 181, 0.28);
  border-radius: var(--radius);
  padding: 0.7rem 0.85rem;
  width: 100%;
}

/* Radios and checkboxes are not text fields. The shared rule above stretches
   them to the full row and gives them a text field's padding and border, which
   puts the control in the middle of the row and pushes its label off the end —
   visible on the composer's "who can read this" choice and on any other control
   sitting inside a .field. */
.field input[type='checkbox'],
.field input[type='radio'] {
  width: auto;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--madder);
  outline: none;
}

.field__hint {
  font-size: var(--step--1);
  color: var(--haze-dim);
}

.alert {
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  font-size: var(--step--1);
  margin-bottom: 1.25rem;
  border: 1px solid;
}

.alert--error {
  border-color: rgba(200, 64, 47, 0.6);
  background: rgba(200, 64, 47, 0.12);
  color: #f2b6ac;
}

.alert--ok {
  border-color: rgba(85, 135, 106, 0.6);
  background: rgba(85, 135, 106, 0.14);
  color: #a8d5ba;
}

/* ------------------------------------------------------- archive browsing - */

/* Filtering is a plain GET form, so the toolbar is styled form controls
   rather than a bespoke widget. */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1rem;
  padding: 1.25rem;
  margin-bottom: 1.5rem;
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
}

.toolbar__field {
  display: grid;
  gap: 0.35rem;
  flex: 1 1 190px;
  min-width: 0;
}

.toolbar__field label {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--haze);
  font-weight: 650;
}

.toolbar__field select {
  font: inherit;
  font-size: var(--step-0);
  background: var(--indigo-900);
  color: var(--paper);
  border: 1px solid rgba(147, 166, 181, 0.28);
  border-radius: var(--radius);
  padding: 0.6rem 0.75rem;
  width: 100%;
}

.toolbar__field select:focus {
  border-color: var(--madder);
  outline: none;
}

.toolbar__clear {
  font-size: var(--step--1);
  color: var(--haze);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
}

.toolbar__clear:hover {
  color: var(--madder-lift);
}

.result-count {
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin: 0 0 1.5rem;
}

/* The card is an <article> on the archive, so the anchors are inside it. */
.card__link {
  text-decoration: none;
  color: inherit;
  display: block;
}

.card__title .card__link:hover {
  color: var(--madder-lift);
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(147, 166, 181, 0.16);
}

.pagination__status {
  font-size: var(--step--1);
  color: var(--haze-dim);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.pagination__spacer {
  min-width: 1px;
}

/* Interactive rating: the woven segments become buttons, in the same visual
   language as the read-only meter. */
.threads--interactive .threads__track {
  gap: 4px;
}

.threads__seg--btn {
  border: 0;
  padding: 0;
  height: 14px;
  width: 22px;
  cursor: pointer;
  background: rgba(240, 236, 226, 0.16);
  border-radius: 1px;
  transition: background-color 140ms var(--ease), transform 140ms var(--ease);
}

.threads__seg--btn:hover {
  transform: translateY(-1px);
  background: var(--madder-lift);
}

.threads__seg--btn.threads__seg--on {
  background: var(--madder);
}

.threads--interactive[data-busy='true'] {
  opacity: 0.6;
  pointer-events: none;
}

/* ---------------------------------------------------------------- upload - */

.uploader {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}

.uploader__intro .section__title {
  margin-bottom: 1rem;
}

.uploader__facts {
  list-style: none;
  margin: 1.75rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.75rem;
}

.uploader__facts li {
  font-size: var(--step--1);
  color: var(--haze);
  padding-left: 1rem;
  border-left: 3px solid var(--moss);
}

.uploader__facts strong {
  color: var(--paper);
  font-weight: 650;
}

/* The form is wider than the auth forms, and left-aligned in its column. */
.uploader__form {
  max-width: none;
  margin: 0;
}

.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr));
  gap: 0 1rem;
}

.field fieldset,
fieldset.field {
  border: 0;
  padding: 0;
  margin: 0 0 1.15rem;
}

fieldset.field legend {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--haze);
  font-weight: 650;
  padding: 0;
  margin-bottom: 0.4rem;
}

.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 170px), 1fr));
  gap: 0.5rem 1rem;
  margin-top: 0.6rem;
}

.checkbox {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-size: var(--step--1);
  color: var(--paper-dim);
  cursor: pointer;
  line-height: 1.45;
}

.checkbox input {
  margin: 0.25rem 0 0;
  accent-color: var(--madder);
  flex-shrink: 0;
}

.checkbox:hover {
  color: var(--paper);
}

fieldset.field .checkbox + .checkbox {
  margin-top: 0.6rem;
}

.field input[type='file'] {
  padding: 0.6rem;
  cursor: pointer;
}

.field input[type='file']::file-selector-button {
  font: inherit;
  font-size: var(--step--1);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: var(--indigo-600);
  color: var(--paper);
  border: 0;
  border-radius: var(--radius);
  padding: 0.5rem 0.9rem;
  margin-right: 0.9rem;
  cursor: pointer;
}

.field input[type='file']::file-selector-button:hover {
  background: var(--madder);
}

.upload-progress {
  display: grid;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.upload-progress label {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--haze);
  font-weight: 650;
}

.upload-progress progress {
  width: 100%;
  height: 10px;
  appearance: none;
  border: 0;
  border-radius: 2px;
  background: rgba(240, 236, 226, 0.14);
  overflow: hidden;
}

.upload-progress progress::-webkit-progress-bar {
  background: rgba(240, 236, 226, 0.14);
}

.upload-progress progress::-webkit-progress-value {
  background: var(--madder);
  transition: width 200ms var(--ease);
}

.upload-progress progress::-moz-progress-bar {
  background: var(--madder);
}

.upload-progress__status {
  font-size: var(--step--1);
  color: var(--haze);
  font-variant-numeric: tabular-nums;
}

.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

/* ---------------------------------------------------------------- shared - */

.pill {
  display: inline-block;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 650;
  color: var(--gold);
  border: 1px solid rgba(217, 164, 65, 0.45);
  border-radius: 999px;
  padding: 0.12rem 0.55rem;
}

.section--paper .pill {
  color: #8a6516;
  border-color: rgba(138, 101, 22, 0.45);
}

.profile-section {
  font-size: var(--step-2);
  margin: 3rem 0 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(147, 166, 181, 0.16);
}

.plain-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1px;
  background: rgba(147, 166, 181, 0.16);
  border: 1px solid rgba(147, 166, 181, 0.16);
  border-radius: var(--radius);
  overflow: hidden;
}

.plain-list li {
  background: var(--indigo-800);
  padding: 1rem 1.25rem;
  display: grid;
  gap: 0.25rem;
}

/* The row's own link is the title, set in the display face. A button that
   happens to be an anchor is not a title — without the exclusion it inherits
   the serif and the larger step, and an "Edit" control ends up bigger than the
   destructive one beside it. */
.plain-list a:not(.btn) {
  font-family: var(--font-display);
  font-size: var(--step-1);
  text-decoration: none;
}

.plain-list a:not(.btn):hover {
  color: var(--madder-lift);
}

.plain-list__meta {
  font-size: var(--step--1);
  color: var(--haze-dim);
}

/* -------------------------------------------------------- owner actions - */

/* Edit and remove, wherever a member meets something they contributed. Kept
   visually quiet: these sit next to the work, not above it, and the destructive
   one only takes on weight once it has been armed. */
.owner-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

/* Edit is an anchor and Remove is a button, so they disagree about line-height:
   the anchor inherits the surrounding text's, the button takes the UA's
   `normal`. Left alone the pair sits 3-4px apart in height. Fixed here rather
   than on `.btn` itself, which would re-space every button on the site. */
.owner-actions .btn {
  line-height: 1.2;
}

.owner-actions--inline {
  margin-top: 0.5rem;
}

/* Inside the card body, pinned to the bottom so a row of cards with titles of
   different lengths still lines its controls up. `margin-top: auto` works
   because .card__body is a flex column with `flex: 1`. */
.owner-actions--card {
  margin-top: auto;
  padding-top: 0.85rem;
}

.btn--danger {
  color: var(--madder-lift);
  background: transparent;
  border-color: rgba(200, 64, 47, 0.5);
}

.btn--danger:hover {
  color: var(--paper);
  background: rgba(200, 64, 47, 0.16);
  border-color: var(--madder);
}

/* Armed: one more click removes it. The colour is the warning, so it has to be
   unmistakably different from the resting state rather than a shade of it. */
.btn--danger.is-armed {
  color: var(--paper);
  background: var(--madder);
  border-color: var(--madder);
}

.btn--danger[disabled] {
  opacity: 0.6;
  cursor: default;
}

/* Initials plate — members have no avatars, so the monogram is the identity. */
.monogram {
  display: grid;
  place-content: center;
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  border-radius: 2px;
  background: linear-gradient(150deg, var(--indigo-600), var(--indigo-800));
  border: 1px solid rgba(147, 166, 181, 0.2);
  font-family: var(--font-display);
  font-size: var(--step-0);
  letter-spacing: 0.04em;
  color: var(--paper);
}

.monogram--large {
  width: 88px;
  height: 88px;
  font-size: var(--step-2);
}

.toolbar__field input[type='search'] {
  font: inherit;
  font-size: var(--step-0);
  background: var(--indigo-900);
  color: var(--paper);
  border: 1px solid rgba(147, 166, 181, 0.28);
  border-radius: var(--radius);
  padding: 0.6rem 0.75rem;
  width: 100%;
}

.toolbar__field input[type='search']:focus {
  border-color: var(--madder);
  outline: none;
}

/* -------------------------------------------------------------- articles - */

.article-list {
  display: grid;
  gap: 0;
}

.article-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 1rem 2.5rem;
  align-items: baseline;
  padding: 1.75rem 0;
  border-top: 1px solid rgba(20, 26, 32, 0.16);
  text-decoration: none;
  color: inherit;
}

.article-row:last-child {
  border-bottom: 1px solid rgba(20, 26, 32, 0.16);
}

.article-row__title {
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.15;
  margin: 0 0 0.4rem;
  font-weight: 400;
}

.article-row:hover .article-row__title {
  color: var(--madder);
}

.article-row__summary {
  margin: 0;
  color: #4d565f;
  max-width: 62ch;
}

.article-row__meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.3rem;
  font-size: var(--step--1);
  color: #6b747d;
  text-align: right;
  white-space: nowrap;
}

.empty-state--paper {
  border-color: rgba(20, 26, 32, 0.25);
  color: #4d565f;
}

.empty-state--paper h3 {
  color: var(--indigo-900);
}

.pagination--paper {
  border-top-color: rgba(20, 26, 32, 0.16);
}

.pagination--paper .btn--ghost {
  border-color: rgba(20, 26, 32, 0.3);
  color: var(--indigo-900);
}

.pagination--paper .btn--ghost:hover {
  border-color: var(--indigo-900);
  background: rgba(20, 26, 32, 0.06);
}

.pagination--paper .pagination__status {
  color: #6b747d;
}

/* Reader: a single measured column, because that is what reading needs. */
.article-reader {
  max-width: 72ch;
}

.article-reader__back {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}

.article-reader .eyebrow {
  color: #6b747d;
}

.article-reader__title {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 0 0 1rem;
  font-weight: 400;
}

.article-reader__standfirst {
  font-size: var(--step-1);
  line-height: 1.5;
  color: #3f4851;
  margin: 0 0 1.25rem;
}

.article-reader__byline {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #6b747d;
  margin: 0 0 2rem;
}

.article-reader__byline a {
  color: var(--madder);
  text-decoration: none;
}

.prose {
  font-size: var(--step-1);
  line-height: 1.7;
  color: #23292f;
  margin-top: 2.5rem;
}

.prose > * + * {
  margin-top: 1.4em;
}

.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.2;
  margin-top: 2.2em;
  color: var(--indigo-900);
}

.prose h2 { font-size: var(--step-2); }

/* h3 sat at --step-1, the same size as body text, so a `##` subheading read as
   a plain paragraph. It needs its own step between body and h2. */
.prose h3 { font-size: clamp(1.32rem, 1.19rem + 0.62vw, 1.72rem); }
.prose h4 { font-size: var(--step-1); }

/* Below h4 the display face has no room left, so these read as labels. */
.prose h5,
.prose h6 {
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.prose a {
  color: var(--madder);
  text-underline-offset: 3px;
}

.prose blockquote {
  margin-inline: 0;
  padding-left: 1.25rem;
  border-left: 3px solid var(--moss);
  color: #4d565f;
  font-style: italic;
}

.prose code {
  font-size: 0.9em;
  background: rgba(20, 26, 32, 0.07);
  padding: 0.1em 0.35em;
  border-radius: 2px;
}

.prose ul,
.prose ol {
  padding-left: 1.4em;
}

.prose li + li {
  margin-top: 0.5em;
}

/* A nested list is part of its parent item, so it keeps the item's rhythm
   rather than the 1.4em gap `.prose > * + *` gives top-level blocks. */
.prose li > ul,
.prose li > ol {
  margin-top: 0.5em;
}

.prose li > p + p {
  margin-top: 0.8em;
}

.prose ul ul { list-style-type: circle; }
.prose ul ul ul { list-style-type: square; }

/* Task lists carry their own marker, so the bullet would be a second one. */
.prose .task-list {
  list-style: none;
  padding-left: 0.2em;
}

.prose .task-list__item input {
  margin-right: 0.5em;
  accent-color: var(--moss);
}

.prose pre {
  background: var(--indigo-900);
  color: var(--paper);
  padding: 1rem 1.15rem;
  border-radius: var(--radius);
  overflow-x: auto;
  font-size: 0.86em;
  line-height: 1.55;
}

/* Inside a code block the inline-code chip would double the background. */
.prose pre code {
  background: none;
  padding: 0;
  font-size: inherit;
  color: inherit;
}

/* Tables scroll inside their own box: a wide table must never make the page
   scroll sideways on a phone. */
.prose__table {
  overflow-x: auto;
}

.prose table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.94em;
}

.prose th,
.prose td {
  border: 1px solid rgba(20, 26, 32, 0.18);
  padding: 0.5em 0.75em;
  text-align: left;
  vertical-align: top;
}

.prose thead th {
  background: rgba(20, 26, 32, 0.06);
  font-family: var(--font-display);
  font-weight: 400;
}

.prose tbody tr:nth-child(even) {
  background: rgba(20, 26, 32, 0.03);
}

.prose img {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
}

.prose hr {
  border: 0;
  border-top: 1px solid rgba(20, 26, 32, 0.2);
  margin-block: 2.2em;
}

.prose del {
  color: #6c757d;
}

.prose blockquote > :first-child {
  margin-top: 0;
}

.article-reader__foot {
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(20, 26, 32, 0.16);
}

.article-reader__rating {
  font-size: var(--step--1);
  color: #6b747d;
  margin: 0;
}

.threads--onpaper .threads__prompt {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #6b747d;
  font-weight: 650;
  margin-right: 0.25rem;
}

.threads--onpaper .threads__seg {
  background: rgba(20, 26, 32, 0.16);
}

.threads--onpaper .threads__value {
  color: #6b747d;
}

/* -------------------------------------------------------------- composer - */

/* Writing column on the left, reference panel on the right.

   The composer is worked rather than read, so it follows `body.compose` (see
   the dense layer near the end of this file) rather than the editorial rhythm
   of the archive pages. The writing column takes the room; everything
   explanatory lives in the panel, where it can be glanced at without displacing
   the text. */
.composer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 21rem;
  gap: 1.5rem;
  align-items: start;
}

.composer__main {
  display: grid;
  gap: 0.75rem;
  min-width: 0;
}

/* Title and the primary actions share one line: the actions are wanted at the
   top, where they are reachable without scrolling past a long article. */
.composer__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}

.composer__heading {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 400;
  letter-spacing: 0;
  margin: 0;
}

.composer__head-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Title, standfirst and visibility on one row — three short controls that do
   not each deserve a full-width band above the editor. */
.composer__meta {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.4fr) auto;
  gap: 0.75rem 1rem;
  align-items: end;
}

.field--flush {
  margin-bottom: 0;
}

.composer__visibility {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.9rem;
}

.composer__visibility legend {
  flex-basis: 100%;
  margin-bottom: 0.15rem;
}

.composer__visibility .checkbox {
  align-items: center;
  white-space: nowrap;
}

fieldset.field.composer__visibility .checkbox + .checkbox {
  margin-top: 0;
}

.composer__body {
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.6;
  resize: vertical;
  /* Fills the window rather than a fixed box: the editor is the page. */
  min-height: clamp(20rem, calc(100vh - 20rem), 70rem);
}

/* ---- the reference panel ---- */

.composer__guide {
  position: sticky;
  top: calc(var(--compose-header, 4.5rem) + 0.75rem);
  min-width: 0;
}

.composer__guide-inner {
  display: grid;
  gap: 1.1rem;
  max-height: calc(100vh - var(--compose-header, 4.5rem) - 2rem);
  overflow-y: auto;
  padding: 1rem 1.1rem;
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.2);
  border-radius: var(--radius);
}

/* ---- the assistant ---- */

.assist {
  padding-bottom: 1.1rem;
  border-bottom: 1px solid rgba(147, 166, 181, 0.18);
}

.assist__intro {
  margin-top: 0;
}

/* Which service is answering, and on whose account. Dimmer than the intro: it
   is provenance, needed once and then ignored. */
.assist__service {
  margin: 0.5rem 0 0;
  font-size: var(--step--1);
  color: var(--haze-dim);
}

.assist__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.7rem;
}

.assist__btn[disabled] {
  opacity: 0.5;
  cursor: progress;
}

.assist__status {
  margin: 0.6rem 0 0;
  font-size: var(--step--1);
  line-height: 1.45;
  color: var(--gold);
  min-height: 1.2em;
}

.assist__results {
  display: grid;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

.assist__item {
  padding: 0.65rem 0.75rem;
  background: rgba(147, 166, 181, 0.06);
  border: 1px solid rgba(147, 166, 181, 0.16);
  border-radius: var(--radius);
  font-size: var(--step--1);
}

.assist__item .btn {
  margin-top: 0.5rem;
  line-height: 1.2;
}

.assist__item .btn[disabled] {
  opacity: 0.55;
  cursor: default;
}

.assist__text,
.assist__note {
  margin: 0;
  line-height: 1.45;
  color: var(--paper);
}

.assist__note {
  color: var(--paper-dim);
}

/* The change itself, so the writer judges the edit rather than the advice:
   struck-through original above, proposed replacement below. */
.assist__before,
.assist__after {
  margin: 0.4rem 0 0;
  padding-left: 0.55rem;
  border-left: 2px solid rgba(147, 166, 181, 0.3);
  line-height: 1.4;
  font-size: 0.94em;
  overflow-wrap: anywhere;
}

.assist__before {
  color: var(--haze-dim);
  text-decoration: line-through;
}

.assist__after {
  color: var(--moss-lift);
  border-left-color: var(--moss);
}

.guide-block__title {
  font-family: var(--font-body);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--haze);
  font-weight: 650;
  margin: 0 0 0.5rem;
}

.guide-block__text {
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--paper-dim);
  margin: 0.5rem 0 0;
}

.guide-block__text strong {
  color: var(--paper);
}

.guide-list,
.guide-syntax {
  margin: 0;
  font-size: var(--step--1);
  line-height: 1.5;
}

.guide-list dt {
  color: var(--paper);
  font-weight: 650;
  margin-top: 0.45rem;
}

.guide-list dt:first-child {
  margin-top: 0;
}

.guide-list dd {
  margin: 0;
  color: var(--paper-dim);
}

/* Name on the left, syntax on the right: a lookup table, so it is read by
   scanning down one column rather than across pairs. */
.guide-syntax {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.3rem 0.9rem;
  align-items: baseline;
}

.guide-syntax dt {
  color: var(--paper-dim);
}

.guide-syntax dd {
  margin: 0;
  text-align: right;
}

.guide-syntax code {
  font-size: 0.82em;
  color: var(--gold);
  background: rgba(217, 164, 65, 0.09);
  border-radius: 2px;
  padding: 0.05em 0.35em;
  white-space: nowrap;
}

/* Rich-text composer.

   The surface is a view over the Markdown textarea; exactly one of the two is
   visible at a time. Until editor.js sets data-ready the rich chrome stays
   hidden, so a reader without JavaScript gets the plain textarea rather than a
   toolbar that does nothing. */

.composer__editor {
  display: grid;
  gap: 0;
}

.composer__modes,
.composer__toolbar {
  display: none;
}

.composer__editor[data-ready='true'] .composer__modes {
  display: flex;
  gap: 0.25rem;
  margin-bottom: -1px;
}

.composer__mode {
  font: inherit;
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--haze);
  background: transparent;
  border: 1px solid rgba(147, 166, 181, 0.24);
  border-bottom-color: transparent;
  border-radius: var(--radius) var(--radius) 0 0;
  padding: 0.4rem 0.9rem;
  cursor: pointer;
}

.composer__mode[aria-selected='true'] {
  color: var(--paper);
  background: var(--indigo-700);
  border-color: rgba(147, 166, 181, 0.24);
}

.composer__editor[data-ready='true'][data-mode='rich'] .composer__toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  background: var(--indigo-700);
  border: 1px solid rgba(147, 166, 181, 0.24);
  border-radius: var(--radius) var(--radius) 0 0;
  /* The surface is as tall as the window, so on a long article the toolbar
     would scroll out of reach exactly when it is being used. Pinned under the
     sticky site header instead — hence the offset rather than 0. */
  position: sticky;
  top: var(--compose-header, 4.5rem);
  z-index: 20;
}

.composer__group {
  display: flex;
  gap: 0.15rem;
}

.composer__group + .composer__group {
  border-left: 1px solid rgba(147, 166, 181, 0.2);
  padding-left: 0.75rem;
}

.composer__tool {
  min-width: 2rem;
  height: 2rem;
  padding: 0 0.45rem;
  font: inherit;
  font-size: var(--step--1);
  line-height: 1;
  color: var(--haze);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}

.composer__tool:hover {
  color: var(--paper);
  background: rgba(147, 166, 181, 0.12);
}

.composer__tool:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}

.composer__tool[aria-pressed='true'] {
  color: var(--indigo-900);
  background: var(--paper-dim);
  border-color: var(--paper-dim);
}

/* The surface reuses .prose so the writer sees the reader's typography, which
   means it must also reuse the reader's *ground*: .prose hard-codes a near-black
   ink for the paper background of .section--paper, and on the dark form
   background that is ink on ink. Paper here is both the fix and the point — the
   article page renders inside .section--paper, so this is what the reader gets. */
.composer__surface {
  display: none;
  /* Matches .composer__body so switching modes does not resize the page. */
  min-height: clamp(20rem, calc(100vh - 20rem), 70rem);
  max-width: none;
  margin-top: 0;
  padding: 1.5rem 1.75rem;
  overflow-y: auto;
  background: var(--paper);
  color: var(--indigo-900);
  border: 1px solid rgba(147, 166, 181, 0.24);
  border-radius: 0 0 var(--radius) var(--radius);
  overflow-wrap: break-word;
}

.composer__editor[data-ready='true'][data-mode='rich'] .composer__surface {
  display: block;
}

.composer__editor[data-ready='true'][data-mode='rich'] .composer__body {
  display: none;
}

.composer__surface:focus {
  outline: none;
  border-color: var(--gold);
}

/* A caret in an empty document needs something to sit against. */
.composer__surface p:only-child:empty::before {
  content: 'Start writing…';
  color: #7c848c;
}

.composer__surface > :first-child {
  margin-top: 0;
}

/* Cell edges have to be visible while editing even though the reader's table
   has no grid, or the writer cannot tell which cell the caret is in. */
.composer__surface table {
  width: 100%;
  border-collapse: collapse;
}

.composer__surface th,
.composer__surface td {
  border: 1px solid rgba(35, 41, 47, 0.22);
  padding: 0.4rem 0.55rem;
}

.composer__surface hr {
  border: 0;
  border-top: 2px solid rgba(35, 41, 47, 0.28);
}

/* Task boxes are `disabled` in the reader's output; in the composer they are
   the writer's own control, so they must not look inert. */
.composer__surface input[type='checkbox'] {
  accent-color: var(--madder);
  margin-right: 0.4rem;
}

.composer__status {
  margin: 0.5rem 0 0;
  min-height: 1.2em;
  font-size: var(--step--1);
  color: var(--gold);
}

/* --------------------------------------------------------------- members - */

.member-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: 1px;
  background: rgba(147, 166, 181, 0.16);
  border: 1px solid rgba(147, 166, 181, 0.16);
  border-radius: var(--radius);
  overflow: hidden;
}

.member-card {
  display: flex;
  gap: 1rem;
  padding: 1.35rem 1.25rem;
  background: var(--indigo-800);
  text-decoration: none;
  color: inherit;
  transition: background-color 180ms var(--ease);
}

.member-card:hover {
  background: var(--indigo-700);
}

.member-card__body {
  display: grid;
  gap: 0.25rem;
  min-width: 0;
}

.member-card__name {
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.2;
}

.member-card__handle,
.member-card__origin,
.member-card__counts {
  font-size: var(--step--1);
  color: var(--haze-dim);
}

.member-card__origin {
  color: var(--moss-lift);
}

.member-card__roles {
  margin-top: 0.35rem;
  display: flex;
  gap: 0.3rem;
  flex-wrap: wrap;
}

.profile-head {
  display: flex;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  align-items: flex-start;
  padding-bottom: 2rem;
  border-bottom: 1px solid rgba(147, 166, 181, 0.16);
}

.profile-head__body {
  min-width: 0;
}

.profile-head__origin {
  font-size: var(--step-1);
  color: var(--moss-lift);
  margin: 0 0 0.75rem;
}

.profile-head__bio {
  color: var(--paper-dim);
  max-width: var(--measure);
}

.profile-head__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin: 1.25rem 0;
}

.profile-head__stats strong {
  color: var(--paper);
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin-right: 0.25rem;
}

/* ------------------------------------------------- endorsement queue --- */

.application-list {
  display: grid;
  gap: 1.5rem;
}

.application {
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-left: 3px solid var(--gold);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 3vw, 1.85rem);
}

.application__head {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.application__name {
  font-family: var(--font-display);
  font-size: var(--step-2);
  margin: 0;
  font-weight: 400;
}

.application__origin {
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin: 0.15rem 0 0;
}

.application__age {
  margin-left: auto;
  font-size: var(--step--1);
  color: var(--haze-dim);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* The applicant's own words are the point of this screen, so they get the
   largest type on the card. */
.application__statement {
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.6;
  color: var(--paper);
  margin: 1.5rem 0;
  padding-left: 1.25rem;
  border-left: 2px solid rgba(147, 166, 181, 0.3);
}

.application__tally {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.vouches--compact {
  margin: 0;
  gap: 0.4rem;
  max-width: 220px;
  flex: 0 0 auto;
}

.vouches--compact .vouch {
  height: 26px;
}

.vouches--compact .vouch::after {
  content: none;
}

.application__count {
  font-size: var(--step--1);
  color: var(--haze);
  font-variant-numeric: tabular-nums;
}

.application__actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  align-items: center;
}

.application__done {
  font-size: var(--step--1);
  color: var(--moss-lift);
  margin: 0;
}

.application__error {
  font-size: var(--step--1);
  color: #f2b6ac;
  margin: 0;
  width: 100%;
}

/* --------------------------------------------------------------- account - */

/* ------------------------------------------------- account: dense layout

   The account panels are six independent cards of very different heights — the
   writing assistant runs four or five times the height of "signed-in devices".
   In a two-column split that mismatch became dead space: every short panel left
   a hole beside it the length of whatever sat opposite.

   Columns rather than grid, deliberately. CSS grid aligns rows, so a row is as
   tall as its tallest cell and the shortfall shows under everything else;
   multi-column *packs by height*, which is the actual goal. The browser
   balances the columns, so the tall assistant takes one and the four short
   panels stack beside it with no gaps.

   The cost is that reading order runs down a column before moving across. That
   is the right trade here — these panels are unrelated to each other, so nobody
   reads them in sequence — and it is why the same treatment is not applied to
   pages whose blocks form an argument. */
.account-panels {
  column-gap: 1.25rem;
}

.account-panels > .panel {
  /* A panel split across a column boundary would put half a form in each. */
  break-inside: avoid;
  margin-bottom: 1.25rem;
  /* Must stay block-level and in flow: `column-span: all` is ignored on an
     inline-block, which silently kept the assistant panel trapped in a single
     column after it had been told to span them all. */
  display: block;
  width: 100%;
}

@media (min-width: 62rem) {
  .account-panels {
    columns: 2;
  }
}

@media (min-width: 86rem) {
  .account-panels {
    columns: 3;
  }
}

/* The page is a working screen, not a document, so it takes the width it can
   get rather than the reading measure the rest of the site is set to. */
body.account .shell {
  width: min(1560px, 100% - 3rem);
}

body.account .panel {
  padding: 1.1rem 1.25rem;
}

body.account .panel__title {
  font-size: var(--step-0);
  margin-bottom: 0.85rem;
}

body.account .panel__note {
  margin-bottom: 0.85rem;
  max-width: none;
}

body.account .field {
  margin-bottom: 0.85rem;
  /* Grid rows stretch, so a field beside one carrying a hint line inherits the
     extra height and puts the slack in its input — the taller-box-in-a-pair
     bug the console hit. Pack to the top instead. */
  align-content: start;
}

body.account .panel--form .field:last-of-type {
  margin-bottom: 1rem;
}

/* Identity fields sit three-up where there is room, one-up where there is not.
   160px is about where a name field stops being usable. */
body.account .field-row {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: 0 0.75rem;
}

/* The writing assistant is the one panel that does not belong in a column: on
   its own it is taller than the other five together, and left in the flow it
   made the browser pile everything into the first columns and leave the last
   one empty. Spanning the full width, its three parts sit side by side and the
   panel becomes one of the shortest on the page. */
body.account .panel--assistant {
  column-span: all;
}

body.account .assist-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 0 1.75rem;
  align-items: start;
}

/* Each part keeps its own vertical rhythm; the grid only decides where they
   sit. Without this the last field's bottom margin lands unevenly between
   columns of different lengths. */
body.account .assist-col > *:last-child {
  margin-bottom: 0;
}

/* Permissions are a scannable index, not a list to read — two or three columns
   turns twenty lines into seven. */
body.account .panel__perms {
  /* `.panel__perms` is a grid, and `columns` is inert on a grid container — it
     has to go back to block flow before multi-column means anything. */
  display: block;
  columns: 2;
  column-gap: 1rem;
  font-size: var(--step--1);
}

body.account .panel__perms li {
  padding-bottom: 0.4rem;
}

body.account .panel__list > div {
  padding-bottom: 0.45rem;
}

/* The contribution lists below the panels: same rows, less air. */
body.account .profile-section {
  font-size: var(--step-1);
  margin: 2rem 0 1rem;
  padding-top: 1.25rem;
}

body.account .plain-list li {
  padding: 0.6rem 0.9rem;
  /* Title, meta and controls on one line while there is room, so a list of
     drafts is a list rather than a stack of three-line blocks. */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.25rem 1rem;
}

body.account .plain-list a:not(.btn) {
  font-size: var(--step-0);
}

body.account .plain-list__meta {
  grid-column: 1;
}

body.account .owner-actions--inline {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
}

.panel {
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 3vw, 1.85rem);
}

.panel--form .field:last-of-type {
  margin-bottom: 1.5rem;
}

.panel--danger {
  border-color: rgba(200, 64, 47, 0.4);
}

.panel__title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  margin: 0 0 1.25rem;
  font-weight: 400;
}

.panel__note {
  font-size: var(--step--1);
  color: var(--haze);
  margin: 0 0 1.25rem;
  max-width: 52ch;
}

/* Sub-headings inside a panel that has more than one thing to do — the
   assistant panel choosing a service, listing keys and adding one. */
.panel__subtitle {
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-weight: 400;
  margin: 1.75rem 0 0.85rem;
}

.field__legend {
  font-size: var(--step--1);
  color: var(--haze);
  margin-bottom: 0.5rem;
  padding: 0;
}

.field__optional {
  color: var(--haze-dim);
  font-weight: 400;
}

/* Radio rows for choosing an AI service. Flex rather than grid so the label
   text sets the row height and the radio sits on the first line of it.
   These are <label>s inside a .field, so the uppercase caps-and-tracking
   treatment for field labels applies and has to be undone: these rows are
   sentences and model ids, not field names, and `mistral-medium-latest` set in
   spaced capitals is unreadable. The selector has to out-specify `.field label`
   (0,1,1) to do it, hence the element qualifier. */
.field label.ai-choice {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid rgba(147, 166, 181, 0.12);
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--step-0);
  font-weight: 400;
  color: inherit;
}

.ai-choice:last-of-type {
  border-bottom: none;
  margin-bottom: 1rem;
}

.ai-choice input {
  flex: none;
}

.ai-choice > span {
  display: block;
}

.ai-choice__note {
  display: block;
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin-top: 0.15rem;
}

.ai-keys {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.ai-keys__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid rgba(147, 166, 181, 0.12);
}

.ai-keys__name {
  font-weight: 600;
}

/* The masked hint. It is a <code>, so it is already monospace and `••••a1b2`
   lines up between rows; the margin pushes the buttons to the far edge so the
   hint cannot be mistaken for one. */
.ai-keys__hint {
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin-right: auto;
}

/* Each control is its own form (they post to different routes), so they need
   to stop behaving like block-level siblings. */
.ai-keys__action {
  display: inline;
}

/* ------------------------------------------------ front-page editor

   One card per landing section. The move/hide controls sit opposite the title
   so the eye can run down the column and see the page's order at a glance,
   which is the thing this screen is really for. */
.landing-edit {
  margin-bottom: 1.5rem;
}

/* A hidden section stays legible but visibly out of the running — dimming the
   whole card would make the very text you came to edit hard to read. */
.landing-edit--hidden {
  border-style: dashed;
  opacity: 0.75;
}

.landing-edit__head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: start;
  gap: 1rem;
}

.landing-edit__head .panel__title {
  margin-bottom: 0.35rem;
}

/* Each control is its own form posting to its own route, so they must sit on
   one line rather than stacking as block siblings. */
.landing-edit__controls {
  display: flex;
  gap: 0.4rem;
}

.landing-edit__controls form {
  display: flex;
  gap: 0.4rem;
}

.landing-edit__actions {
  margin-top: 1.25rem;
}

.landing-edit__footer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid rgba(147, 166, 181, 0.12);
}

/* Marks a field an administrator has overridden, so "what have we changed from
   the original?" is answerable by scanning rather than by remembering. */
.landing-edit__flag {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gold);
  margin-left: 0.4rem;
}

.landing-edit textarea {
  font-family: inherit;
  line-height: 1.5;
  resize: vertical;
}

.panel__list {
  margin: 0;
  display: grid;
  gap: 0.75rem;
}

.panel__list > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  border-bottom: 1px solid rgba(147, 166, 181, 0.12);
  padding-bottom: 0.6rem;
}

.panel__list dt {
  font-size: var(--step--1);
  color: var(--haze-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.panel__list dd {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-1);
  text-align: right;
}

.panel__perms {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
  font-size: var(--step--1);
  color: var(--haze);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------- moderation & admin - */

/* Dense tabular data. Wide tables scroll inside their own container so the
   page body never scrolls sideways on a phone. */
.mod-table-wrap {
  overflow-x: auto;
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
  margin-bottom: 2rem;
  -webkit-overflow-scrolling: touch;
}

.mod-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--step--1);
  min-width: 720px;
}

.mod-table thead th {
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
  color: var(--haze-dim);
  background: var(--indigo-900);
  padding: 0.8rem 1rem;
  border-bottom: 1px solid rgba(147, 166, 181, 0.2);
  white-space: nowrap;
}

.mod-table td {
  padding: 0.85rem 1rem;
  border-bottom: 1px solid rgba(147, 166, 181, 0.1);
  vertical-align: top;
  background: var(--indigo-800);
}

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

.mod-table a {
  color: var(--paper);
  text-decoration: none;
  border-bottom: 1px solid rgba(147, 166, 181, 0.4);
}

.mod-table a:hover {
  color: var(--madder-lift);
}

.mod-table code {
  font-size: 0.82rem;
  color: var(--moss-lift);
  word-break: break-all;
}

.mod-body {
  color: var(--haze);
  max-width: 40ch;
}

.mod-kind {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--haze-dim);
}

.mod-actions {
  white-space: nowrap;
}

.mod-actions .btn {
  margin-right: 0.35rem;
  margin-bottom: 0.25rem;
}

.mod-danger {
  border-color: rgba(200, 64, 47, 0.5);
  color: #f2b6ac;
}

.mod-danger:hover {
  border-color: var(--madder);
  background: rgba(200, 64, 47, 0.12);
}

/* Status is the one thing a moderator scans for, so it is colour-coded. */
.mod-status {
  display: inline-block;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 650;
  border-radius: 2px;
  padding: 0.15rem 0.5rem;
  border: 1px solid;
}

.mod-status--published,
.mod-status--visible,
.mod-status--active {
  color: var(--moss-lift);
  border-color: rgba(85, 135, 106, 0.5);
  background: rgba(85, 135, 106, 0.12);
}

.mod-status--quarantined,
.mod-status--hidden,
.mod-status--suspended {
  color: var(--gold);
  border-color: rgba(217, 164, 65, 0.5);
  background: rgba(217, 164, 65, 0.12);
}

.mod-status--removed,
.mod-status--rejected {
  color: #f2b6ac;
  border-color: rgba(200, 64, 47, 0.5);
  background: rgba(200, 64, 47, 0.12);
}

.mod-status--pending,
.mod-status--draft,
.mod-status--processing {
  color: var(--haze);
  border-color: rgba(147, 166, 181, 0.4);
}

.mod-input,
.mod-select {
  font: inherit;
  font-size: var(--step--1);
  background: var(--indigo-900);
  color: var(--paper);
  border: 1px solid rgba(147, 166, 181, 0.28);
  border-radius: var(--radius);
  padding: 0.4rem 0.55rem;
  width: 100%;
  min-width: 120px;
}

.mod-select {
  width: auto;
  margin-right: 0.35rem;
}

.mod-input:focus,
.mod-select:focus {
  border-color: var(--madder);
  outline: none;
}

/* Reports are cards, not rows — each one is a judgement, not a data point. */
.mod-list {
  display: grid;
  gap: 1.25rem;
  margin-bottom: 2rem;
}

.mod-card {
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
  padding: clamp(1.1rem, 3vw, 1.6rem);
}

.mod-card--report {
  border-left: 3px solid var(--madder);
}

.mod-card[data-resolved='true'] {
  opacity: 0.55;
  border-left-color: var(--moss);
}

.mod-card__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.4rem;
}

.mod-card__title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 400;
}

.mod-card__meta {
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin: 0 0 0.9rem;
}

.mod-card__reason {
  margin: 0 0 1.1rem;
  padding-left: 1rem;
  border-left: 2px solid rgba(147, 166, 181, 0.3);
  color: var(--paper-dim);
  font-size: var(--step-0);
}

.mod-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* ----------------------------------------------------------- comments --- */

/* The discussion under an item. Server-rendered and posted with a plain form,
   so it reads and works with JavaScript blocked. Two grounds: the dark item
   page, and the paper of the article reader. */

.comments {
  margin-top: 3rem;
  padding-top: 2rem;
  border-top: 1px solid rgba(147, 166, 181, 0.18);
}

.comments__heading {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 400;
  margin: 0 0 1.25rem;
}

.comments__empty,
.comments__gate {
  color: var(--haze);
  font-size: var(--step--1);
  margin: 0 0 1.5rem;
  max-width: var(--measure);
}

.comments__list {
  list-style: none;
  margin: 0 0 2rem;
  padding: 0;
  display: grid;
  gap: 1.1rem;
  max-width: var(--measure);
}

.comment {
  border-left: 2px solid rgba(147, 166, 181, 0.25);
  padding-left: 1rem;
}

/* A comment linked from the moderation console is anchored; mark it so the
   moderator can see which one they were sent to. */
.comment:target {
  border-left-color: var(--madder);
  background: rgba(200, 64, 47, 0.06);
}

.comment__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 0.3rem;
}

.comment__author {
  font-weight: 650;
  color: var(--paper);
  text-decoration: none;
}

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

.comment__when {
  font-size: var(--step--1);
  color: var(--haze-dim);
}

/* Reporting is a fetch, so the control only exists when scripting does — a
   button that silently does nothing is worse than no button. */
.comment__report {
  display: none;
  margin-inline-start: auto;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: var(--step--1);
  color: var(--haze-dim);
  cursor: pointer;
  text-decoration: underline;
}

.js .comment__report {
  display: inline;
}

.comment__report:hover {
  color: var(--madder-lift);
}

.comment__body {
  margin: 0;
  white-space: pre-wrap;
}

.comments__form {
  max-width: var(--measure);
}

.comments__form textarea {
  resize: vertical;
}

/* Paper ground: the article reader. */
.comments--onpaper {
  border-top-color: rgba(35, 41, 47, 0.18);
}

.comments--onpaper .comments__heading,
.comments--onpaper .comment__author,
.comments--onpaper .comment__body {
  color: var(--indigo-900);
}

.comments--onpaper .comments__empty,
.comments--onpaper .comments__gate,
.comments--onpaper .comment__when,
.comments--onpaper .comment__report {
  color: #5c6670;
}

.comments--onpaper .comment {
  border-left-color: rgba(35, 41, 47, 0.2);
}

.comments--onpaper .field label {
  color: #5c6670;
}

.comments--onpaper .comments__form textarea {
  background: #fbfaf7;
  color: var(--indigo-900);
  border-color: rgba(35, 41, 47, 0.25);
}

/* ------------------------------------------------------ media detail --- */

/* The listing crops to 4:3 so the grid stays even. The item's own page must not
   — it is the one place the contribution is shown whole, and cropping a tall
   photograph here would hide the part someone uploaded it for. */
.media-detail__frame {
  margin-block: 1.5rem;
  aspect-ratio: auto;
  background: transparent;
  display: flex;
  justify-content: center;
}

.media-detail__frame img,
.media-detail__frame video {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 78vh;
  object-fit: contain;
}

.media-detail__frame audio {
  width: 100%;
}

.media-detail__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 2rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid rgba(147, 166, 181, 0.18);
}

.media-detail__desc {
  margin-top: 1.25rem;
  max-width: var(--measure);
}

/* --------------------------------------------------- contribute screen --- */

/* A working screen, not a reading one. The whole form should be visible at once
   on a laptop, so fields sit on a grid sized by what each one holds rather than
   in a single full-width column. */

.contribute .shell {
  max-width: min(1100px, 100% - 3rem);
}

.contribute__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem 2rem;
  margin-bottom: 1.25rem;
}

.contribute__title {
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.1;
  margin: 0.1rem 0 0;
  font-weight: 400;
}

.contribute__facts {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.5rem;
  margin: 0;
  padding: 0;
  font-size: var(--step--1);
  color: var(--haze);
}

.contribute__facts strong {
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}

.mode-switch {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  margin-bottom: 1.25rem;
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
}

.mode-switch__btn {
  font: inherit;
  font-size: var(--step--1);
  font-weight: 650;
  letter-spacing: 0.04em;
  padding: 0.4rem 0.9rem;
  border: 0;
  border-radius: 2px;
  background: none;
  color: var(--haze);
  cursor: pointer;
}

.mode-switch__btn.is-on {
  background: var(--madder);
  color: var(--weave);
}

.mode-switch__btn:not(.is-on):hover {
  color: var(--paper);
}

/* Two columns on a laptop; the fields that need room say so. */
.contribute__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: 0.75rem 1.25rem;
  align-items: start;
  background: var(--indigo-800);
  border: 1px solid rgba(147, 166, 181, 0.18);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
}

.contribute__grid .field {
  margin-bottom: 0;
  align-content: start;
}

.contribute__span-all {
  grid-column: 1 / -1;
}

.contribute__narrow input {
  max-width: 12rem;
}

.contribute__grid .field input,
.contribute__grid .field textarea {
  padding: 0.42rem 0.6rem;
  font-size: 0.9rem;
}

.contribute__grid legend {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--haze);
  font-weight: 650;
  padding: 0;
  margin-bottom: 0.4rem;
}

.contribute__grid legend .field__hint {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
}

/* Collections as chips rather than a checkbox column: ten of them fit on two
   rows instead of ten. */
.chip-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

/* A chip is a <label>, so it inherits the uppercase, letter-spaced, bold
   treatment `.field label` gives form labels. That is right for a field name
   and wrong for a value: it makes ten collection names unreadable and twice as
   wide as they need to be. Reset it back to plain text. */
/* `.field label` is (0,1,1) and would win over a bare `.chip` at (0,1,0), so
   the reset has to be at least as specific to take. */
.field label.chip,
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.4rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid rgba(147, 166, 181, 0.28);
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 150ms var(--ease), background-color 150ms var(--ease);

  font-size: var(--step--1);
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  text-align: left;
  color: var(--paper);
  /* Collection names are short. Wrapping inside a chip leaves it wider than its
     text and pushes the label off the checkbox; wrap the row instead. */
  white-space: nowrap;
}

.chip:hover {
  border-color: var(--haze);
}

.chip:has(input:checked) {
  border-color: var(--madder);
  background: rgba(200, 64, 47, 0.14);
}

.chip:has(input:focus-visible) {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.chip input {
  margin: 0;
  accent-color: var(--madder);
}

/* The visibility choice carries a sentence rather than a name, so it gets a
   rectangle and a cap on how wide it may run. */
/* The visibility choice carries a sentence, so it may wrap. */
.chip-grid--wide .chip {
  border-radius: var(--radius);
  max-width: 30rem;
  align-items: flex-start;
  white-space: normal;
}

.chip-grid--wide .chip input {
  margin-top: 0.2rem;
}

.contribute__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 1rem;
}

/* -------------------------------------------------------- pdf + embeds --- */

/* An iframe, not <object> — the CSP keeps object-src 'none', and that is not
   worth relaxing for a document viewer. */
.pdf-viewer {
  margin-block: 1.5rem;
}

.pdf-viewer__frame {
  width: 100%;
  height: min(82vh, 900px);
  border: 1px solid rgba(147, 166, 181, 0.25);
  border-radius: var(--radius);
  background: var(--indigo-800);
}

.pdf-viewer__fallback {
  margin: 0.5rem 0 0;
  font-size: var(--step--1);
  color: var(--haze-dim);
}

.embed {
  margin-block: 1.5rem;
}

/* 16:9 without a wrapper hack. */
.embed--video {
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
}

.embed__frame {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.embed--external {
  border: 1px dashed rgba(147, 166, 181, 0.35);
  border-radius: var(--radius);
  padding: 1.5rem;
  text-align: center;
}

.embed__host {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--moss-lift);
  font-weight: 650;
  margin: 0 0 0.35rem;
}

.embed__note {
  color: var(--haze);
  margin: 0 0 1rem;
}

/* Says where a linked item comes from, and what opening it means. */
.media-detail__source {
  font-size: var(--step--1);
  color: var(--haze-dim);
  margin: 0.5rem 0 0;
}

/* Marks a linked video in the listing. */
.card__frame {
  position: relative;
}

.card__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 2.4rem;
  color: rgba(240, 236, 226, 0.9);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6);
  pointer-events: none;
}

/* ------------------------------------------------------ dense compose --- */

/* Same argument as `body.console` below: the composer is worked, not read, so
   it gets the width and the tightened rhythm rather than the archive's
   editorial rests. The question here is "how much of my article can I see",
   which is answered by width above the fold and by the surface taking whatever
   height the window has left.

   Applied by the two composer routes in routes/pages.ts via `bodyClass`. */

body.compose {
  --shell: min(1720px, 100% - 2rem);
  /* Height of the sticky site header plus its band, measured on the deployed
     page (69px + 3px). Anything pinned inside the composer offsets by this. */
  --compose-header: 4.5rem;
}

body.compose .section {
  padding-block: 1.25rem 1.75rem;
}

/* The header and footer bands are chrome around a working surface; they do not
   need the full-page presence they have on an article. */
body.compose .site-header__inner {
  padding-block: 0.6rem;
}

body.compose .site-footer {
  padding-block: 1.5rem;
}

body.compose .field label,
body.compose fieldset.field legend {
  margin-bottom: 0.25rem;
}

body.compose .field input,
body.compose .field textarea {
  padding: 0.5rem 0.65rem;
}

/* ------------------------------------------------------ dense console --- */

/* The archive pages are editorial: generous rests, a big display serif, one
   idea at a time. The console is not read, it is worked — the question there is
   always "how many rows can I see at once".

   This layer takes its structure from the Cloudflare dashboard: a compact
   sans-serif interface, hairline-ruled tables, flat bordered cards with a
   header rule, sentence-case controls, and one warm accent reserved for the
   action that commits something. What it does *not* take is the light ground —
   the archive is dark, and a console that flipped to white would read as a
   different product. So the dashboard's structure sits on this site's indigo.

   Applied by the console routes in routes/pages.ts via `bodyClass`. Public
   pages are untouched. */

body.console {
  /* Tables carry up to six columns; give them the room the archive does not
     need. */
  --shell: min(1560px, 100% - 2rem);

  /* The accent. Cloudflare orange, used *only* for the primary action and the
     focus ring — the archive's madder stays the site's colour, and a console
     where several things compete for "look here" is a console where nothing
     does. */
  --cf-accent: #f6821f;
  --cf-accent-lift: #ff9838;

  /* One hairline value everywhere: card edges, table rules, header dividers.
     The dashboard's grid reads as a single system because every line in it is
     the same line. */
  --cf-line: rgba(147, 166, 181, 0.16);
  --cf-line-strong: rgba(147, 166, 181, 0.28);

  /* Surfaces, darkest to lightest: page, card, header/hover. */
  --cf-surface: var(--indigo-800);
  --cf-surface-alt: var(--indigo-700);

  --cf-radius: 4px;

  font-size: 0.8125rem; /* 13px, the dashboard's base */
}

/* Sans throughout. The display serif is the archive's editorial voice; in a
   settings table it is costume. This is the single biggest change of character
   in this layer, which is why it is stated once here rather than per rule. */
body.console h1,
body.console h2,
body.console h3,
body.console .section__title,
body.console .panel__title,
body.console .panel__subtitle,
body.console .collection__name,
body.console .mod-card__title,
body.console .empty-state h3 {
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: 0;
}

body.console .section {
  padding-block: 1.5rem 2.5rem;
}

body.console .section__head {
  margin-bottom: 1rem;
  gap: 0.4rem 1.5rem;
}

body.console .section__title {
  font-size: 1.35rem;
  line-height: 1.2;
}

body.console .section__note {
  max-width: 76ch;
  font-size: 0.8125rem;
}

body.console .eyebrow {
  margin-bottom: 0.2rem;
  font-size: 0.68rem;
}

/* Secondary headings inside a console page ("What each role can do"). */
body.console .profile-section {
  font-size: 0.72rem;
  margin-top: 1.5rem;
  margin-bottom: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--haze);
  font-family: var(--font-body);
  border-top: none;
  padding-top: 0;
}

/* ---- controls ---- */

/* Dashboard buttons are sentence case. Uppercase letter-spaced labels are an
   editorial device — they cost roughly a third more width per button, which at
   three controls a row is a column of table gone.

   Scoped to the page body, not the whole document: the site header is shared
   chrome, and restyling its sign-out button here would make the header change
   character as you walked between a console page and a public one. */
body.console .section .btn {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
  font-size: 0.8125rem;
  padding: 0.4rem 0.8rem;
  border-radius: var(--cf-radius);
}

/* No lift. A console is a dense field of controls; buttons that jump on hover
   make the whole page feel loose. */
body.console .section .btn:hover {
  transform: none;
}

body.console .section .btn--primary {
  background: var(--cf-accent);
  border-color: var(--cf-accent);
  color: #21150a;
  font-weight: 600;
}

body.console .section .btn--primary:hover {
  background: var(--cf-accent-lift);
  border-color: var(--cf-accent-lift);
}

body.console .section .btn--ghost {
  border-color: var(--cf-line-strong);
  color: var(--paper);
  background: transparent;
}

body.console .section .btn--ghost:hover {
  border-color: var(--haze);
  background: rgba(147, 166, 181, 0.08);
}

body.console .section .btn--small {
  padding: 0.22rem 0.55rem;
  font-size: 0.75rem;
  letter-spacing: 0;
}

/* One focus treatment for everything focusable, in the accent. Keyboard
   position must never be in doubt on a screen that can delete things. */
body.console .section .btn:focus-visible,
body.console .mod-input:focus,
body.console .mod-select:focus,
body.console .field input:focus,
body.console .field select:focus,
body.console .field textarea:focus {
  outline: 2px solid var(--cf-accent);
  outline-offset: 1px;
  border-color: var(--cf-accent);
}

/* ---- tabular data: the reason this layer exists ---- */

body.console .mod-table-wrap {
  margin-bottom: 1.25rem;
  border-color: var(--cf-line);
  border-radius: var(--cf-radius);
  background: var(--cf-surface);
}

body.console .mod-table {
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

/* Column headers: small, upper, quiet, on the lighter surface, separated from
   the body by the same hairline as everything else. */
body.console .mod-table thead th {
  padding: 0.45rem 0.7rem;
  font-size: 0.68rem;
  letter-spacing: 0.07em;
  background: var(--cf-surface-alt);
  border-bottom: 1px solid var(--cf-line);
  white-space: nowrap;
}

/* Row height is set by the cell's own line-height strut, not by the children's
   — the two-line "name over handle" cell renders two struts regardless of what
   the <strong> and <span> inside it declare. This is the line that actually
   shortens every row. */
body.console .mod-table td,
body.console .mod-table thead th {
  line-height: 1.3;
}

body.console .mod-table td {
  padding: 0.38rem 0.7rem;
  vertical-align: middle;
  border-bottom: 1px solid var(--cf-line);
}

body.console .mod-table tbody tr:last-child td {
  border-bottom: none;
}

/* Ruled rows, not banded. Zebra striping and hairlines answer the same question
   — "which row am I on" — and doing both makes a dense table look busy; the
   dashboard rules its rows and reserves fill for the row under the cursor. */
body.console .mod-table tbody tr:nth-child(even) {
  background: none;
}

body.console .mod-table tbody tr:hover {
  background: rgba(147, 166, 181, 0.07);
}

/* The two-line "name over handle" cell is the tallest thing in most rows.
   Pulling the second line up recovers height on every row at once. */
body.console .mod-table .mod-kind {
  font-size: 0.7rem;
  line-height: 1.2;
  color: var(--haze-dim);
}

body.console .mod-table td strong {
  line-height: 1.2;
  font-weight: 600;
}

body.console .mod-body {
  max-width: 46ch;
}

body.console .mod-actions {
  gap: 0.25rem;
}

/* Status badges: flat, small, one radius with everything else. The semantic
   colours are unchanged — green for live, amber for held, red for removed — as
   those are load-bearing for moderators. */
body.console .mod-status {
  padding: 0.08rem 0.4rem;
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  border-radius: 3px;
  font-weight: 600;
}

body.console .pill {
  padding: 0.06rem 0.4rem;
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  border-radius: 3px;
}

/* Reference chips: same shape, no accent. Used where a cell holds a dozen of
   them and the colour would be carrying no information. */
.pill--quiet {
  color: var(--haze);
  border-color: var(--cf-line-strong, rgba(147, 166, 181, 0.28));
  background: rgba(147, 166, 181, 0.06);
}

body.console .mod-body .pill {
  margin-bottom: 0.15rem;
}

body.console .mod-input,
body.console .mod-select {
  padding: 0.24rem 0.45rem;
  font-size: 0.8125rem;
  border-radius: var(--cf-radius);
  border-color: var(--cf-line-strong);
}

/* ---- forms, panels, cards ---- */

body.console .panel {
  padding: 0;
  margin-bottom: 1rem;
  border-color: var(--cf-line);
  border-radius: var(--cf-radius);
  background: var(--cf-surface);
}

/* A dashboard card is a titled box: the heading sits in its own ruled band and
   the body is inset below it. Padding therefore moves off the panel and onto
   its parts, which is why `.panel` above has none. */
body.console .panel__title {
  font-size: 0.9rem;
  margin: 0;
  padding: 0.65rem 0.9rem;
  border-bottom: 1px solid var(--cf-line);
  background: var(--cf-surface-alt);
  border-radius: var(--cf-radius) var(--cf-radius) 0 0;
}

/* The card's inset lives on its children, since the title band spans edge to
   edge. Buttons are excluded: they carry their own padding, and zeroing it
   inline left the label touching the button's border. */
body.console .panel > *:not(.panel__title):not(.btn) {
  padding-inline: 0.9rem;
}

body.console .panel > *:nth-child(2) {
  padding-top: 0.85rem;
}

body.console .panel > *:last-child {
  padding-bottom: 0.9rem;
}

body.console .panel__note {
  font-size: 0.8125rem;
  margin-bottom: 0.75rem;
  max-width: 70ch;
}

body.console .panel__subtitle {
  font-size: 0.8rem;
  margin: 1rem 0 0.5rem;
}

body.console .field {
  margin-bottom: 0.7rem;
  /* `.field` is a grid, and grid rows stretch by default. Side by side in a
     `.field-row`, a field whose neighbour carries a hint line gets stretched to
     match, and the slack lands in its input — which is why one box in a pair
     renders visibly taller than the other. Pack the rows to the top instead. */
  align-content: start;
  gap: 0.25rem;
}

/* Sentence case, not the archive's spaced capitals. A form of twelve fields is
   read by its labels, and spaced capitals are slower to read at 11px than the
   words they are setting. */
body.console .field label {
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--haze);
}

body.console .field__hint {
  font-size: 0.72rem;
}

/* The console's add-forms are four short fields, not a questionnaire. Flow them
   into columns so the form is a band across the top of the page rather than a
   screen of its own. `display: contents` lets the fields inside a `.field-row`
   join the same grid, so every field is laid out by one set of tracks. */
body.console .panel--form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
  gap: 0.5rem 0.9rem;
  align-items: start;
  padding: 0 0.9rem 0.9rem;
}

body.console .panel--form .panel__title {
  grid-column: 1 / -1;
  margin: 0 -0.9rem 0.85rem;
  /* The title band spans the card, so it has to escape the form's own inline
     padding — hence the negative margin rather than a wider column. */
}

body.console .panel--form > *:not(.panel__title):not(.btn) {
  padding-inline: 0;
}

body.console .panel--form > *:last-child {
  padding-bottom: 0;
}

body.console .panel--form .field-row {
  display: contents;
}

body.console .panel--form .field,
body.console .panel--form .field:last-of-type {
  margin-bottom: 0;
}

body.console .panel--form > .btn {
  grid-column: 1 / -1;
  justify-self: start;
}

body.console .field input,
body.console .field select,
body.console .field textarea {
  padding: 0.32rem 0.5rem;
  font-size: 0.8125rem;
  border-radius: var(--cf-radius);
  border-color: var(--cf-line-strong);
}

/* ---- toolbar, alerts, lists ---- */

body.console .toolbar {
  margin-bottom: 1rem;
  gap: 0.5rem;
  padding: 0.5rem 0.65rem;
  border-color: var(--cf-line);
  border-radius: var(--cf-radius);
  background: var(--cf-surface);
}

/* A search box or an action filter does not get better at 1300px wide. Cap the
   control and let the rest of the bar stay empty. */
body.console .toolbar__field {
  flex: 0 1 300px;
}

body.console .toolbar label {
  font-size: 0.72rem;
  text-transform: none;
  letter-spacing: 0;
}

/* Notices are a left-ruled band rather than a fully outlined box: at console
   density a four-sided border around a one-line message is another card
   competing with the real ones. */
body.console .alert {
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.85rem;
  font-size: 0.8125rem;
  border-radius: var(--cf-radius);
  border-width: 1px 1px 1px 3px;
}

body.console .mod-list {
  gap: 0.6rem;
  margin-bottom: 1.25rem;
}

body.console .mod-card {
  padding: 0.75rem 0.9rem;
  border-color: var(--cf-line);
  border-radius: var(--cf-radius);
}

body.console .mod-card__title {
  font-size: 0.9rem;
}

body.console .mod-card__head {
  margin-bottom: 0.25rem;
}

/* ---- the /admin hub ---- */

/* Links to the console's screens. As tall cards they push the last one under
   the fold for no reason; as compact tiles the whole console is visible at
   once. auto-fit rather than auto-fill, so an odd count does not leave an empty
   track that reads as a broken card. */
body.console .collections {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  gap: 0.75rem;
}

body.console .collection {
  min-height: 0;
  padding: 0.8rem 0.9rem;
  gap: 0.2rem;
  border-color: var(--cf-line);
  border-radius: var(--cf-radius);
  background: var(--cf-surface);
}

/* The accent again, and only here: a hub tile under the cursor is the one place
   in the console where "this is where you are going" is the whole message. */
body.console .collection:hover {
  border-color: var(--cf-accent);
}

body.console .collection__name {
  font-size: 0.9rem;
}

body.console .collection__desc {
  font-size: 0.78rem;
}

/* The moderation queue renders four of these at once when the archive is
   quiet. At editorial scale that is four screens of nothing; a console wants
   them to read as one quiet line each. */
body.console .empty-state {
  padding: 0.9rem 1rem;
  text-align: left;
  border-color: var(--cf-line);
  border-radius: var(--cf-radius);
}

body.console .empty-state h3 {
  font-size: 0.875rem;
  margin-bottom: 0.15rem;
}

body.console .empty-state p {
  margin: 0;
  font-size: 0.8125rem;
  max-width: none;
}

/* ---- the front-page editor ---- */

/* Its cards are `.panel`s, so they inherit the titled-box treatment above; only
   the parts that are its own need saying. */
body.console .landing-edit__head {
  padding: 0.65rem 0.9rem;
  border-bottom: 1px solid var(--cf-line);
  background: var(--cf-surface-alt);
  border-radius: var(--cf-radius) var(--cf-radius) 0 0;
  margin-bottom: 0;
}

body.console .landing-edit__head .panel__title {
  padding: 0;
  border-bottom: none;
  background: none;
}

body.console .landing-edit__head .panel__note {
  margin-bottom: 0;
  font-size: 0.75rem;
}

/* A section's fields are up to thirteen short strings. Stacked, each one is a
   1300px input holding an eyebrow of six words, and a single block runs several
   screens. Flowed into tracks the whole section fits in one. */
body.console .landing-edit > form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: 0.55rem 0.9rem;
  align-items: start;
  padding: 0.85rem 0.9rem 0;
}

body.console .landing-edit > form .field,
body.console .landing-edit > form .field:last-of-type {
  margin-bottom: 0;
}

body.console .landing-edit__actions {
  grid-column: 1 / -1;
}

/* Prose gets two tracks where there are tracks to give: a Markdown paragraph
   edited through a 240px slot is worse than no columns at all. Guarded by the
   breakpoint because `span 2` against a single column would open an implicit
   second one and push the layout off the card. */
@media (min-width: 62rem) {
  body.console .landing-edit > form .field:has(textarea) {
    grid-column: span 2;
  }
}

body.console .landing-edit__footer {
  padding: 0.75rem 0.9rem;
  margin-top: 0.75rem;
  border-top: 1px solid var(--cf-line);
}

body.console .landing-edit__actions {
  margin-top: 0.85rem;
}


/* ------------------------------------------------------------- responsive - */

@media (max-width: 960px) {
  .hero__grid,
  .consensus,
  .uploader,
  .composer {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Stacked, the panel is reference material below the work rather than
     something to pin — sticking it would cover the editor on the way past. */
  .composer__guide {
    position: static;
  }

  .composer__guide-inner {
    max-height: none;
    overflow-y: visible;
  }

  .composer__meta {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Without a sidebar the window height is the only budget left. */
  .composer__body,
  .composer__surface {
    min-height: clamp(18rem, calc(100vh - 24rem), 50rem);
  }

  .profile-head {
    flex-direction: column;
  }

  .article-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .article-row__meta {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
    gap: 0.75rem;
    text-align: left;
  }

  .gallery__item--lead {
    grid-column: span 1;
  }

  .gallery__item--lead .card__frame {
    aspect-ratio: 4 / 3;
  }
}

@media (max-width: 720px) {
  :root {
    --rail-width: 0px;
    --shell: min(1240px, 100% - 2rem);
  }

  /* The rail lays flat under the header on small screens rather than
     stealing horizontal space. The gradient has to turn with it: a vertical
     sequence sampled over 6px of height would show only the first 6px of the
     repeat, i.e. a solid bar of the ground colour. */
  .loom-rail {
    position: sticky;
    inset-block: auto;
    width: 100%;
    height: 6px;
    top: 0;
    background: repeating-linear-gradient(
      90deg,
      var(--band-black) 0 48px,
      var(--moss) 48px 56px,
      var(--gold) 56px 64px,
      var(--madder) 64px 96px,
      var(--gold) 96px 104px,
      var(--moss) 104px 112px,
      var(--band-black) 112px 160px
    );
    border-inline-end: 0;
  }

  .site-header {
    top: 6px;
  }

  .site-nav {
    display: none;
    position: absolute;
    top: 100%;
    inset-inline: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--indigo-800);
    border-bottom: 1px solid rgba(147, 166, 181, 0.2);
    padding: 0.5rem 1rem 1rem;
  }

  .site-nav[data-open='true'] {
    display: flex;
  }

  .site-nav a {
    padding-block: 0.85rem;
    border-bottom: 1px solid rgba(147, 166, 181, 0.12);
  }

  .nav-toggle {
    display: inline-flex;
  }

  .site-header__inner {
    position: relative;
  }

  .stat-rail {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vouches {
    gap: 0.4rem;
  }
}

@media (max-width: 420px) {
  .hero__actions .btn {
    width: 100%;
  }
}

/* --------------------------------------------------------------- motion --- */

/* One orchestrated entrance: the hero threads itself in, top to bottom. */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  animation: thread-in 700ms var(--ease) forwards;
}

.reveal[data-delay='1'] { animation-delay: 90ms; }
.reveal[data-delay='2'] { animation-delay: 180ms; }
.reveal[data-delay='3'] { animation-delay: 270ms; }
.reveal[data-delay='4'] { animation-delay: 360ms; }

@keyframes thread-in {
  to {
    opacity: 1;
    transform: none;
  }
}

/* Scroll reveal is opt-in via JS adding [data-inview]; without JS the content
   is visible by default, so the page never depends on script to be readable. */
.js .scroll-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 600ms var(--ease), transform 600ms var(--ease);
}

.js .scroll-reveal[data-inview='true'] {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.001ms !important;
  }

  .reveal,
  .js .scroll-reveal {
    opacity: 1;
    transform: none;
  }

  .card:hover,
  .btn:hover {
    transform: none;
  }
}
