/* Garry's List — rebrand reskin (design handoff v1, 2026-08-18).
 *
 * Loaded by the layouts via stylesheet_link_tag(Current.outlet.stylesheet),
 * after tailwind.css and application.css, so re-pointing a Tailwind theme
 * variable here reskins every utility class that reads it. That is the whole
 * trick: `text-earth`, `bg-fog` and `border-fog-dark` stay in the templates
 * and change color because the variable underneath them changed.
 *
 * Scoped to this outlet on purpose — Root Access keeps the old palette.
 *
 * The token values, their contrast ratios and the four places this diverges
 * from the handoff are in app/assets/tailwind/application.css and in the
 * admin doc at /admin/docs/brand.
 */

/* ===========================================================================
   1. The reskin — legacy names re-pointed
   =========================================================================== */
:root {
  /* Color. Left column is what the templates say; right is what it now means. */
  --color-bridge: var(--color-brand-red);         /* was #a5432e */
  --color-bridge-dark: var(--color-brand-red-hover); /* was #8a3a28 */
  --color-earth-dark: var(--color-ink);           /* was #2f2c27 */
  --color-earth: var(--color-ink-soft);           /* was #4a4840 */
  /* The handoff maps this to brand rose, with the caveat: check what it was
   * carrying, and if it was text, move it darker. It was carrying text —
   * avatar initials and secondary labels — so it takes the readable rose
   * (4.6:1) rather than the decorative one (4.3:1). */
  --color-earth-light: var(--color-brand-rose-text); /* was #6b6355 */
  --color-hills: var(--color-ink-soft);           /* was #666260 */
  /* Same caveat as earth-light below: check what it carries, and if it is
   * text, move it darker. It carries text — the forum's vote arrows (16px),
   * the metadata separators (12px), comment bylines and placeholders — all
   * well under the 24px that makes ink-muted legal. At #977d77 that is 3.5:1
   * and fails AA at every one of those sizes; 61 of the 61 contrast failures
   * on staging traced here. It takes the readable companion instead. */
  --color-water: var(--color-ink-muted-text);     /* was #9e9a94 */
  --color-water-light: var(--color-ink-faint);    /* was #b5b0a9 */
  --color-fog: var(--color-sand);                 /* was #e2dfda — bands go from gray to warm */
  --color-fog-dark: var(--color-ink-faint);       /* was #c9c5c0 */

  /* Nothing on this site is #ffffff — everything is warm. Re-pointing
   * Tailwind's own `white` moves every `bg-white` card and every
   * `text-white` label reversed out of ink onto paper, without touching
   * the hundred templates that say `bg-white`. */
  --color-white: var(--color-paper);              /* was #ffffff */

  /* Type. Everything that was Fraunces becomes Newsreader — same job, same
   * mixed-case reading, no markup change. Big Shoulders is the poster face
   * and is applied by name (.gl-poster*), never inherited, because it is set
   * uppercase by design and would wreck any heading not drawn for it.
   *
   * The roles are named for the job, not the face: templates say
   * `--font-display` and `--font-poster`, so swapping a typeface again is a
   * two-line change here and nowhere else. */
  --font-sans: var(--font-figtree);
  --font-display: var(--font-newsreader);
  --font-poster: var(--font-big-shoulders);

  /* Untracked caps read as a bug, and the current labels are set at about a
   * quarter of the tracking this system uses. Re-pointing Tailwind's own
   * tracking scale upgrades every existing `tracking-wide` caps label at once
   * — the cheapest and most visible half of the rebrand. */
  --tracking-wide: 0.08em;   /* quiet caps: section headings, tags, bylines */
  --tracking-wider: 0.10em;  /* UI caps: buttons, metadata, eyebrows */
  --tracking-widest: 0.14em; /* poster caps: banner and masthead lettering */
}

/* The page is warm off-white, not white. */
body {
  background-color: var(--color-paper);
  color: var(--color-ink-soft);
  font-family: var(--font-figtree);
  font-weight: 500;
  /* Figtree has no ss01/ss02/cv01/cv02 — clearing the inherited setting keeps
   * the browser from doing anything surprising with unknown features. */
  font-feature-settings: normal;
}

/* Every rung below states its colour through :where(), which has zero
 * specificity. That still beats inheritance — a declaration that matches the
 * element always does — but it loses to any colour utility on the element, so
 * `text-white` on a headline over a photograph just works. Without this the
 * rung outranks the utility (both are one class, and this file loads last) and
 * silently paints ink on a dark tile.
   =========================================================================== */

/* ===========================================================================
   2. The type ladder
   ---------------------------------------------------------------------------
   Leading is barbell-shaped and there is no page-level line-height: display
   type is set TIGHTER than its own size (0.82-1.00x), reading type LOOSER
   than convention (1.50-1.78x). Both halves are the brand.

   These classes set face, weight, leading and tracking but not size. Size
   stays on the utility the template already carries, so the reskin changes
   how the page reads without moving anything.
   =========================================================================== */

/* --- Display · Big Shoulders Display. Uppercase, and set tighter than its own size.
 *
 * WEIGHTS ARE NOT PORTABLE BETWEEN TYPEFACES. The handoff measured Balboa at
 * 600-900, and those numbers were carried across unchanged when the face was
 * swapped in Aug 2026 — which rendered the article H1 thin, because Big
 * Shoulders is drawn on a lighter axis than Balboa at the same numeric weight.
 * The poster tier is +200 from the measured Balboa values, capped at 900. The
 * relative hierarchy is unchanged: -article and -loose stay one step below
 * -poster and -section.
 *
 * Size stays with whatever utility the template already carries — the reskin
 * changes the face, the weight, the leading and the tracking, not the layout.
 * Uppercase lives here rather than in the string: a screen reader spells out
 * shouted text, and a reader who copies a headline should get what the editor
 * typed. --- */
.gl-poster {
  font-family: var(--font-poster);
  font-weight: 900;
  line-height: 0.92;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* The article headline — bigger, so the ratio relaxes toward 1. */
.gl-poster-article {
  font-family: var(--font-poster);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0;
  text-transform: uppercase;
}

/* The section masthead. Uppercase has no descenders to collide, so the
 * lines can interlock and it reads as printed. */
.gl-poster-section {
  font-family: var(--font-poster);
  font-weight: 900;
  line-height: 0.82;
  letter-spacing: 0;
  text-transform: uppercase;
  /* Ink by default; the section's own color when the element carries a
   * `category-*` class — a category masthead is set in its category color. */
}

/* The one display role set loose — it wraps to 2-4 short lines in a narrow
 * rail, where sub-1.0 leading would collide. */
.gl-poster-loose {
  font-family: var(--font-poster);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

/* --- Headlines · Newsreader. Cards and lists, 14-20px.
 *
 * No text-transform. The handoff annotates this rung "capitalize"; CSS
 * capitalize on a database headline produces "Sf's Nonprofit Housing
 * Machine". Sentence case is an editorial rule, applied when the headline is
 * written, not a transform applied when it is displayed. --- */
.gl-serif-headline {
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.375;
  letter-spacing: 0.02em;
}

/* --- Sans display · Figtree caps --- */
.gl-eyebrow {
  font-family: var(--font-figtree);
  font-weight: 800;
  line-height: 1.22;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

/* "Most Read", "Latest", "Top Stories" — usually paired with an 18x18 ink
 * square. The square is decoration: the heading beside it already says the
 * name, so it is hidden from assistive tech rather than announced. */
.gl-section-heading {
  font-family: var(--font-figtree);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Ink by default; a section's own color when the element carries a
   * `category-*` class. The square beside it always takes the base. */
  display: flex;
  align-items: center;
  gap: 12px;
}

.gl-section-heading::before {
  content: "";
  width: 0.7em;
  height: 0.7em;
  background: var(--category-base, var(--color-ink));
  flex: none;
}

/* A section header: the heading, whatever sits opposite it (a date, a "Read
 * more" link), and the hairline that closes it off.
 *
 * Rules are how this design separates things. Sections are told apart by a
 * 1px line under their heading, not by a gap — which is why the spacing below
 * a heading (16px) is tighter than the spacing above one (48px). */
.gl-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 8px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--color-ink-faint);
}

/* --- Reading · Figtree, leading above 1.5 --- */
.gl-dek {                                                /* 18 / 28 = 1.56 */
  font-family: var(--font-figtree);
  font-weight: 600;
  line-height: 1.56;
  letter-spacing: 0;
  color: var(--color-ink-muted-text);
}

.gl-excerpt {                                            /* 12 / 18 = 1.50 */
  font-family: var(--font-figtree);
  font-weight: 400;
  line-height: 1.5;
  color: var(--color-ink-soft);
}

/* --- Furniture --- */
.gl-metadata {
  font-family: var(--font-figtree);
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--color-ink);
}

/* The handoff sets the byline in ink-faint, which is 1.9:1 on sand — and its
 * own rules say ink-faint is never text. It takes brand-rose-text instead,
 * which is the quiet accent the rose was named for. */
.gl-byline {
  font-family: var(--font-figtree);
  font-weight: 700;
  font-size: 10px;
  line-height: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-brand-rose-text);
}

/* Real italic — the kit serves it, so ask for it. A synthesized oblique is
 * visibly wrong next to the real thing. */
.gl-caption {                                            /* 14 / 26 = 1.86 */
  font-family: var(--font-figtree);
  font-weight: 400;
  font-style: italic;
  font-size: 14px;
  line-height: 26px;
  letter-spacing: 0.02em;
  color: var(--color-brand-rose-text);
}

/* The standfirst under an article headline carries a short thick ink bar on
 * its left — the design's mark for "this is the summary, not the story". */
.gl-standfirst {
  border-left: 6px solid var(--color-ink);
  padding-left: 24px;
}

/* The article column is 653px at 18px Figtree — about 80 characters, exactly
 * the WCAG 1.4.8 ceiling. A ceiling, not a target: if it widens, the size
 * drops or the column narrows. */
.gl-measure {
  max-width: 653px;
}

/* Tracking tiers, for caps runs that are not one of the rungs above. */
/* A block of running text that happens to be in caps — legal boilerplate,
 * mostly. The 0.08em label tier is too much for a paragraph; the sans-label
 * tier is the right amount, and all-caps running text reads better with a
 * little tracking than with none. */
.gl-caps-block { letter-spacing: 0.03em; }

.caps-quiet { text-transform: uppercase; letter-spacing: 0.08em; }
.caps-ui { text-transform: uppercase; letter-spacing: 0.10em; }
.caps-poster { text-transform: uppercase; letter-spacing: 0.14em; }

/* ===========================================================================
   3. Category colors
   ---------------------------------------------------------------------------
   Three custom properties per section. `base` is the identity — the square,
   the chip border, the section bar. `tint` is the pastel a chip fills with on
   hover. `text` is the label.

   The handoff says base and label are always the same value. Three of the six
   are too light to read: techno 4.0:1 on sand, state 3.2:1, merit 2.3:1 — and
   merit-gold fails even the large-text threshold at every size, so setting it
   bigger does not rescue it. So the rule narrows: the base owns everything
   you look at, the text companion owns what you read, and each companion also
   clears 4.5:1 on its own hover tint so the fill cannot hide the label. On
   the other three, base and text are the same value and the rule holds as
   written.
   =========================================================================== */
.category-techno-optimism {
  --category-base: var(--color-techno-green);
  --category-tint: var(--color-techno-green-tint);
  --category-text: var(--color-techno-green-text);
}
.category-state-capacity {
  --category-base: var(--color-state-orange);
  --category-tint: var(--color-state-orange-tint);
  --category-text: var(--color-state-orange-text);
}
.category-public-safety {
  --category-base: var(--color-safety-teal);
  --category-tint: var(--color-safety-teal-tint);
  --category-text: var(--color-safety-teal-text);
}
.category-public-education {
  --category-base: var(--color-education-magenta);
  --category-tint: var(--color-education-magenta-tint);
  --category-text: var(--color-education-magenta-text);
}
.category-ca-politicians {
  --category-base: var(--color-politicians-purple);
  --category-tint: var(--color-politicians-purple-tint);
  --category-text: var(--color-politicians-purple-text);
}
.category-merit-excellence {
  --category-base: var(--color-merit-gold);
  --category-tint: var(--color-merit-gold-tint);
  --category-text: var(--color-merit-gold-text);
}

/* The label, and the 8x8 square that identifies the section. */
.gl-category-label {
  font-family: var(--font-figtree);
  font-weight: 700;
  /* Size stays on the utility the template carries — 14px on the hero, 12px in
   * the rails. Both clear AA: the text companions were derived to. */
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--category-text, var(--color-brand-red));
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.gl-category-label::before {
  content: "";
  width: 8px;
  height: 8px;
  background: var(--category-base, var(--color-brand-red));
  flex: none;
}

.gl-category-label:hover {
  color: var(--category-text, var(--color-brand-red));
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ===========================================================================
   4. Buttons
   ---------------------------------------------------------------------------
   Fully rounded, uppercase Figtree bold, 6px vertical and 20px horizontal
   padding. The label's line box is pinned to its font size, which is what
   holds the pill to its 24px height — left at `normal` it grows to 29px.

   24px is exactly the WCAG 2.5.8 minimum target with nothing to spare, so
   under a thumb the pill grows to 44px. Legal on a desktop is still a bad
   target on a phone.
   =========================================================================== */
/* These live in @layer components so every Tailwind utility outranks them.
 * Unlayered rules beat every layered rule regardless of specificity, and this
 * file is unlayered and loads last — so an unlayered `.gl-button { display:
 * inline-flex }` beat `.hidden` from @layer utilities, and the apply form's
 * "Continue with email" button could never be hidden by its own Stimulus
 * controller. Same failure the story-tile headlines hit with text-white. */
@layer components {
  .gl-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-figtree);
    font-weight: 700;
    font-size: 12px;
    line-height: 12px;
    letter-spacing: 0.10em;
    text-transform: uppercase;
    padding: 6px 20px;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  }

  .gl-button--solid {
    background: var(--color-brand-red);
    border: 1px solid var(--color-brand-red);
    color: var(--color-paper);  /* 5.0:1 */
  }

  .gl-button--solid:hover {
    background: var(--color-brand-red-hover);
    border-color: var(--color-brand-red-hover);
  }

  .gl-button--outline {
    background: transparent;
    border: 1.5px solid var(--color-ink);
    color: var(--color-ink);
  }

  .gl-button--outline:hover {
    background: var(--color-ink);
    color: var(--color-paper);
  }

  /* A full-width form CTA. The design only draws small pills, but the rule is
   * "buttons are fully rounded, uppercase Figtree bold" — a form submit is a
   * button. Same treatment, stretched, with a taller target since it is the
   * primary action on the page. */
  .gl-button--block {
    width: 100%;
    justify-content: center;
    padding: 12px 24px;
  }

  @media (pointer: coarse) {
    .gl-button {
      min-height: 44px;
      padding-block: 14px;
    }
  }
}

/* ===========================================================================
   4b. The vote control
   ---------------------------------------------------------------------------
   A ballot box, not an upvote arrow — this is a members' board for a civic
   union and "vote" is the word that belongs. The mark carries no arrow in
   either direction: up is an upvote in costume, down reads as a downvote to
   anyone who has used an aggregator, and there is no downvote in this design.

   Unvoted the body is an outline and the lid is solid. Voted it fills whole
   and turns brand-red, and the label goes past tense — a ballot box you have
   used stays used. The fill switch is here rather than in the Stimulus
   controller so the controller never rewrites SVG markup.

   The count is normal-size text and takes ink-muted-text (4.6:1), never
   ink-muted. The label hides on phones where the row is tight; the button
   keeps its aria-label at every size, so nothing is lost to a screen reader.
   =========================================================================== */
@layer components {
  .gl-vote {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 0;
    border: 0;
    background: none;
    color: var(--color-ink-muted-text);
    cursor: pointer;
    text-decoration: none;
    transition: color 0.15s ease;
  }

  .gl-vote__mark { flex: none; }
  .gl-vote__lid  { fill: currentColor; }
  .gl-vote__body { fill: none; stroke: currentColor; stroke-width: 1.5; }

  .gl-vote__count {
    font-family: var(--font-figtree);
    font-weight: 700;
    font-size: 13px;
    line-height: 1;
  }

  .gl-vote__label {
    display: none;
    font-family: var(--font-figtree);
    font-weight: 700;
    font-size: 11px;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  @media (min-width: 640px) {
    .gl-vote__label { display: inline; }
  }

  /* Comment votes: same mark, smaller, never a label. */
  .gl-vote--sm { gap: 4px; }
  .gl-vote--sm .gl-vote__mark { width: 15px; height: 15px; }
  .gl-vote--sm .gl-vote__count { font-size: 12px; }

  .gl-vote:hover { color: var(--color-ink); }

  .gl-vote[data-voted="true"] { color: var(--color-brand-red); }
  .gl-vote[data-voted="true"] .gl-vote__body { fill: currentColor; }
  .gl-vote[data-voted="true"]:hover { color: var(--color-brand-red-hover); }

  /* Legal on a desktop is still a bad target under a thumb. */
  @media (pointer: coarse) {
    .gl-vote {
      min-height: 44px;
      min-width: 44px;
    }
  }

  /* The quiet actions on a meta line — Share, Discuss, Reply, Report. Small
   * uppercase Figtree, ink-muted-text so it clears AA at 11px. Deliberately
   * not pill-shaped: nothing in a row should look pressable except the things
   * that are, and a row of filled pills makes the eye sort affordance from
   * decoration on every item. */
  .gl-meta-action {
    font-family: var(--font-figtree);
    font-weight: 700;
    font-size: 11px;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-ink-muted-text);
    text-decoration: none;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    transition: color 0.15s ease;
  }

  .gl-meta-action:hover {
    color: var(--color-ink);
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  /* The comment count is the one meta item that is a destination rather than
   * an action, so it carries the link colour and stays sentence case. */
  .gl-meta-action--count {
    color: var(--color-brand-red);
    text-transform: none;
    letter-spacing: 0.01em;
    font-size: 12px;
  }

  .gl-meta-action--count:hover { color: var(--color-brand-red-hover); }

  .gl-meta-action--on { color: var(--color-brand-red); }

  @media (pointer: coarse) {
    .gl-meta-action {
      min-height: 44px;
    }
  }
}

/* ===========================================================================
   4c. The forum row
   ---------------------------------------------------------------------------
   Swiss: hierarchy from scale, weight, alignment and space. No card, no
   shadow, no rule between rows. Rows sit 48px apart on an 8px rhythm and
   share one left edge with the thread page and the article — that alignment
   running the length of the page is what replaces the lines.

   Wide screens get a three-column grid: a rank gutter, the body, and the art.
   Below 640px the gutter collapses (55px is 15% of a 375px viewport for two
   glyphs, and the rank moves inline as quiet metadata) and the art is dropped
   outright rather than stacked — the list is scan-and-choose, and a 200px
   image per row buys no decision.
   =========================================================================== */
@layer components {
  .gl-forum-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
  }

  /* A hairline between rows after all. Space alone was tried and, on a
   * phone, the list dissolved: each row twice as tall as its content and
   * nothing saying where one stopped. The rule is ink-faint at 60% — a
   * boundary you feel more than see — and the padding either side of it is
   * the rhythm: 20px on phones, 24px from sm up. */
  .gl-row { padding-block: 20px; }
  .gl-row:first-child { padding-top: 0; }
  .gl-row + .gl-row { border-top: 1px solid rgb(202 177 171 / 0.6); }

  .gl-row {
    position: relative;
    display: grid;
    grid-template-columns: 40px minmax(0, 1fr);
    column-gap: 12px;
  }

  .gl-row--faded { opacity: 0.55; }

  /* Gutter rank. Hidden below sm — .gl-row__rank-inline carries it there. */
  .gl-row__rank {
    display: block;
    margin-bottom: 8px;
    font-family: var(--font-figtree);
    font-size: 11px;
    line-height: 1.4;
    color: var(--color-ink-muted-text);
    margin: 2px 0 0;
  }

  .gl-row__kind {
    font-family: var(--font-figtree);
    font-weight: 700;
    font-size: 11px;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-ink-muted-text);
    margin: 0 0 8px;
    display: flex;
    align-items: baseline;
    gap: 10px;
  }

  /* The kicker's anchor: the same 8px square gl-category-label draws, in the
   * row's section colour when it has one, brand-red otherwise. */
  .gl-row__mark {
    display: inline-block;
    width: 8px;
    height: 8px;
    flex: none;
    background: var(--category-base, var(--color-brand-red));
  }

  /* The vote is the row's primary act and the comp draws it at ballot-box
   * scale. In the gutter it gets room: a 24px mark and a count you can read
   * from the headline. */
  .gl-row__gutter .gl-vote {
    flex-direction: column;
    align-items: center;
    gap: 3px;
    min-width: 32px;
  }
  .gl-row__gutter .gl-vote__mark { width: 22px; height: 22px; }
  @media (min-width: 640px) { .gl-row__gutter .gl-vote__mark { width: 24px; height: 24px; } }
  .gl-row__gutter .gl-vote__count {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-ink);
  }

  /* The "…" menu, top-right of the row. A <details>: the list is the open
   * state, on paper with a shadow, no border — the comp's popover without a
   * line around it. */
  .gl-row__menu {
    position: absolute;
    top: -6px;
    right: 0;
    z-index: 2;
  }
  .gl-row__menu-button {
    list-style: none;
    cursor: pointer;
    padding: 6px 8px;
    font-size: 16px;
    line-height: 1;
    letter-spacing: 0.1em;
    color: var(--color-ink-muted-text);
    border-radius: 4px;
  }
  .gl-row__menu-button::-webkit-details-marker { display: none; }
  .gl-row__menu-button:hover { color: var(--color-ink); background: var(--color-sand); }
  .gl-row__menu-list {
    position: absolute;
    right: 0;
    top: 100%;
    min-width: 160px;
    padding: 6px 0;
    background: var(--color-paper);
    box-shadow: 0 8px 24px rgb(33 14 3 / 0.14);
    border-radius: 6px;
  }
  .gl-row__menu-item,
  .gl-row__menu-item button {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 14px;
    font-family: var(--font-figtree);
    font-size: 13px;
    font-weight: 600;
    color: var(--color-ink-soft);
    background: none;
    border: 0;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
  }
  .gl-row__menu-item:hover, .gl-row__menu-item button:hover { color: var(--color-ink); background: var(--color-sand); }
  /* Art starts below the menu's line, as in the comp. */
  .gl-row--with-art .gl-row__art { margin-top: 22px; }

  /* The domain is a kicker like EDITORIAL and DISCUSSION, and reads in the
   * same register: no parentheses, no brand-red. Where a link goes is worth
   * knowing before you click; it is not worth more than the headline. */
  .gl-row__domain {
    color: var(--color-ink-muted-text);
  }

  /* Matches the gutter rank's casing so the label does not change wording
   * across the breakpoint — it is the same datum in a different place. */
  .gl-row__rank-inline { display: none; }

  /* The gutter is a column at every width. A strip under the row was tried
   * on phones and doubled every row's height; 40px of rail (10% of a 375px
   * screen) holds the rank and a stacked vote and keeps the row compact. */
  .gl-row__gutter { grid-column: 1; grid-row: 1; }
  .gl-row__body { grid-column: 2; grid-row: 1; }

  @media (min-width: 640px) {
    .gl-row { grid-template-columns: 56px minmax(0, 1fr); column-gap: 24px; padding-block: 24px; }
    .gl-row--with-art { grid-template-columns: 56px minmax(0, 1fr) 190px; }
  }

  .gl-row__title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 20px;
    line-height: 1.25;
    color: var(--color-ink);
    margin: 0;
  }

  .gl-row__title a {
    color: inherit;
    text-decoration: none;
  }

  .gl-row__title a:hover {
    color: var(--color-brand-red);
  }

  .gl-row__meta {
    font-family: var(--font-figtree);
    font-size: 12px;
    line-height: 1.4;
    color: var(--color-ink-muted-text);
    margin: 8px 0 0;
  }

  .gl-row__author { font-weight: 600; color: var(--color-ink-soft); }

  .gl-row__excerpt {
    font-family: var(--font-figtree);
    font-size: 14px;
    line-height: 1.6;
    color: var(--color-ink-soft);
    margin: 8px 0 0;
    max-width: 62ch;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* Phones keep the art: an 88px thumbnail at the right of the body. Hiding
   * it below 640px was meant to save scroll, and read instead as a page with
   * its images missing. */
  .gl-row--with-art { grid-template-columns: 40px minmax(0, 1fr) 88px; }
  .gl-row__art {
    display: block;
    grid-column: 3;
    grid-row: 1;
    height: 66px;
    overflow: hidden;
    background: var(--color-sand);
  }
  .gl-row__art img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: saturate(0.5);
  }

  /* One line, joined: "by GARRY TAN · 3h ago · SHARE · DISCUSS". The comp
   * reads as a sentence about the row; 20px gaps with nothing between them
   * read as four loose controls that happen to share a line. The middots are
   * drawn by CSS so the markup stays a list of links. */
  .gl-row__actions {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-top: 10px;
    flex-wrap: wrap;
  }

  .gl-row__actions > * + *::before {
    content: "·";
    margin-right: 8px;
    color: var(--color-ink-faint);
  }

  /* Flag sits with the rest. Pushed to the right edge it reads as a control
   * belonging to the page rather than to this row, and on a row with art it
   * collides with the thumbnail column. */
  .gl-row__flag { margin-left: 0; }

  /* Front page only: how many people and how recently. Reads as a statement
   * about the row, not another thing to press — same size as the actions
   * beside it, no underline, no hover. */
  .gl-row__activity {
    font-size: 12px;
    letter-spacing: 0.04em;
    color: var(--color-ink-soft);
  }

  /* A pin is the newsroom's claim on a slot, so it reads in brand-red and says
   * how long it holds. The dot is a mark, not a chip — nothing on a row is
   * pill-shaped except things you can press. */
  .gl-row__pin {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--color-brand-red);
    letter-spacing: 0.08em;
  }

  /* The callout above the board (8/28 comp, #1175). Sand panel; the medallion
   * breaks its top edge. Stacks on phones: medallion, text, buttons. */
  .gl-callout {
    position: relative;
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    margin: 12px 0 40px;
    padding: 20px 24px 24px;
    background: var(--color-sand);
  }
  .gl-callout__medallion {
    width: 96px;
    height: auto;
    margin-top: -32px;
  }
  .gl-callout__heading {
    font-family: var(--font-figtree);
    font-weight: 800;
    font-size: 18px;
    line-height: 1.1;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-ink);
    margin: 0 0 6px;
  }
  .gl-callout__copy {
    font-family: var(--font-display);
    font-size: 16px;
    line-height: 1.4;
    color: var(--color-ink-soft);
    max-width: 48ch;
  }
  .gl-callout__copy p { margin: 0; }
  .gl-callout__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
  }
  @media (min-width: 768px) {
    .gl-callout {
      grid-template-columns: 120px minmax(0, 1fr) auto;
      align-items: center;
      column-gap: 20px;
      padding: 20px 28px;
    }
    .gl-callout__medallion { width: 120px; margin-top: -40px; }
    .gl-callout__actions { justify-content: flex-end; flex-wrap: nowrap; }
  }

  /* Toolbar: search left, SUBMIT POST right. */
  .gl-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 28px;
  }
  .gl-search {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1 1 auto;
    max-width: 320px;
    padding: 0 14px;
    height: 40px;
    background: var(--color-sand);
    color: var(--color-ink-soft);
  }
  .gl-search__icon { flex: none; }
  .gl-search__input {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    border: 0;
    background: transparent;
    font-family: var(--font-figtree);
    font-size: 14px;
    color: var(--color-ink);
  }
  .gl-search__input::placeholder { color: var(--color-ink-muted-text); }
  .gl-search__input:focus { outline: none; }
  .gl-search:focus-within { outline: 2px solid var(--color-brand-red); outline-offset: 1px; }
  .gl-button--ink {
    background: var(--color-ink);
    color: var(--color-paper);
    white-space: nowrap;
  }
  .gl-button--ink:hover { background: var(--color-brand-red); color: var(--color-paper); }

  /* The strip: kinds left, SORT BY right. */
  .gl-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    justify-content: space-between;
    gap: 16px 24px;
    margin-bottom: 32px;
  }
  .gl-sort {
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--font-figtree);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-ink-muted-text);
  }
  .gl-sort__select {
    appearance: none;
    -webkit-appearance: none;
    border: 1px solid var(--color-ink-faint);
    border-radius: 999px;
    padding: 6px 28px 6px 12px;
    background: var(--color-paper) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5l3 3 3-3' fill='none' stroke='%2377655a' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat right 10px center / 12px 12px;
    font: inherit;
    color: var(--color-ink);
    cursor: pointer;
  }

  /* Kind filter — what you are looking at, so it carries more weight than the
   * sort beside it. Still type and space: the active state is ink against
   * ink-muted-text plus the red square, with no underline bar and no rule
   * under the strip. */
  .gl-kinds {
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
  }

  .gl-kind {
    position: relative;
    font-family: var(--font-figtree);
    font-weight: 700;
    font-size: 13px;
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-ink-muted-text);
    text-decoration: none;
    padding-bottom: 9px;
    transition: color 0.15s ease;
  }

  .gl-kind:hover { color: var(--color-ink); }
  .gl-kind--active { color: var(--color-ink); }

  /* Active state is a 2px brand-red bar the width of the word. The comp's
   * 7px square read as a stray dot at phone size ("what is that little dot
   * under ALL"); a bar under the word is unmistakably "this one". */
  .gl-kind--active::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: var(--color-brand-red);
  }

  @media (pointer: coarse) {
    .gl-kind {
      min-height: 44px;
      display: inline-flex;
      align-items: center;
      padding-bottom: 0;
    }
    .gl-kind--active::after { bottom: 10px; }
  }

  /* Sort / filter tabs. Active state is weight and colour plus the small red
   * square the comps mark it with — no underline bar, and no rule beneath the
   * strip. The square is a mark; a full-width line would be a divider. */
  .gl-tab {
    position: relative;
    font-family: var(--font-figtree);
    font-weight: 700;
    font-size: 11px;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-ink-muted-text);
    text-decoration: none;
    padding-bottom: 8px;
    transition: color 0.15s ease;
  }

  .gl-tab:hover { color: var(--color-ink); }

  .gl-tab--active { color: var(--color-ink); }

  .gl-tab--active::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: var(--color-brand-red);
  }

  @media (pointer: coarse) {
    .gl-tab {
      min-height: 44px;
      display: inline-flex;
      align-items: center;
      padding-bottom: 0;
    }
    .gl-tab--active::after { bottom: 10px; }
  }

  @media (min-width: 640px) {
    .gl-row__title { font-size: 22px; }

    /* Restated here because the phone-width --with-art template above sits
     * later in the file than the first desktop block and would win. */
    .gl-row--with-art { grid-template-columns: 56px minmax(0, 1fr) 190px; }
    .gl-row__art {
      display: block;
      grid-column: 3;
      grid-row: 1;
      height: 124px;
      overflow: hidden;
      background: var(--color-sand);
    }

    .gl-row__art img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      filter: saturate(0.5);
      transition: filter 0.3s ease;
    }

    .gl-row:hover .gl-row__art img { filter: saturate(1); }
  }
}

/* ===========================================================================
   5. Rules, shape and photography
   =========================================================================== */

/* Every rule is 1px #cab1ab at FULL opacity — not a tinted ink, not 40% of
 * something darker. The old site set hr to 60% of fog-dark; that lands as a
 * different color, not a lighter one. */
hr,
.gl-rule {
  border: 0;
  border-top: 1px solid var(--color-ink-faint);
  opacity: 1;
}

/* Photography has square corners — at every size, in every module. Radius
 * belongs to interface chrome only. */
.prose img,
article img,
.hero-card img,
.featured-card img,
figure img {
  border-radius: 0;
}

/* Headline links inherit the rung they sit in and darken to brand red on
 * hover. Red is a darkening, so this only works on light grounds — on the
 * hero's red wedge it would land dark-red-on-red, which is why the hero
 * headline is excluded. */
.gl-headline a,
.gl-headline-compact a,
.gl-display-article a,
.gl-display-lead a {
  color: inherit;
  text-decoration: none;
}

.gl-headline a:hover,
.gl-headline-compact a:hover,
.gl-display-article a:hover,
.gl-display-lead a:hover {
  color: var(--color-brand-red);
}

/* ===========================================================================
   6. Long-form content
   ---------------------------------------------------------------------------
   .prose and .trix-content are styled in application.css against the legacy
   variables, so they reskin for free. What they cannot inherit is the new
   leading and the new heading face, which is what this section sets.
   =========================================================================== */
.prose,
.trix-content {
  max-width: 653px;
}

.prose p,
.trix-content p,
.prose li,
.trix-content li {
  font-family: var(--font-figtree);
  font-weight: 500;
  font-size: 18px;
  line-height: 32px;  /* 1.78 — the loose half of the barbell */
  color: var(--color-ink-soft);
}

/* In-article headings are the one display role set loose — they wrap to two
 * to four short lines in a narrow column, where sub-1.0 leading would collide.
 * Size is left to whatever the element already had, so a 28px article heading
 * and a 20px heading on a legal page both keep their place in the page. */
/* Long-form rhythm on the ladder. application.css sets these at 1.75rem (28px)
 * and 2.5rem (40px), neither of which is a step — 24px between paragraphs and
 * 48px above a heading are. */
.prose p,
.trix-content p,
.prose li,
.trix-content li,
.prose ul,
.prose ol,
.trix-content ul,
.trix-content ol,
.prose blockquote,
.trix-content blockquote {
  margin-bottom: 24px !important;
}

.prose h2,
.trix-content h2,
.prose h3,
.trix-content h3 {
  margin-top: 48px !important;
  margin-bottom: 16px !important;
}

.prose h2,
.trix-content h2 {
  font-family: var(--font-poster);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: var(--color-ink);
}

.prose h3,
.trix-content h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.375;
  letter-spacing: 0.02em;
  color: var(--color-ink);
}

.prose blockquote,
.trix-content blockquote {
  border-left: 4px solid var(--color-brand-red);
  font-family: var(--font-figtree);
  font-style: italic;
  color: var(--color-ink-soft);
}

.prose figcaption,
.trix-content figcaption {
  font-family: var(--font-figtree);
  font-weight: 400;
  font-style: italic;
  font-size: 14px;
  line-height: 26px;
  letter-spacing: 0.02em;
  color: var(--color-brand-rose-text);
}

/* Rung colours live in the components layer so Tailwind's utilities — which
 * are in the later `utilities` layer — override them. This file is otherwise
 * unlayered, and unlayered styles beat every layered style whatever their
 * specificity, which is why `text-white` on a headline lost until now.
 * Layer beats specificity: :where() could not fix this on its own. */
@layer components {
  .gl-poster { color: var(--category-text, var(--color-ink)); }
  .gl-poster-article { color: var(--category-text, var(--color-ink)); }
  .gl-poster-section { color: var(--category-text, var(--color-ink)); }
  .gl-poster-loose { color: var(--category-text, var(--color-ink)); }
  .gl-serif-headline { color: var(--category-text, var(--color-ink)); }
  .gl-eyebrow { color: var(--category-text, var(--color-ink)); }
  .gl-section-heading { color: var(--category-text, var(--color-ink)); }
}

/* The wordmark's SVG box reserves headroom for the star, which rises above the
 * cap height. Box-centring therefore hangs the lettering low against the nav
 * links beside it — measured, the cap band centres about 7px below the menu
 * text at a 48px mark. Nudge it back toward the optical centre.
 *
 * A percentage, not a pixel value, so it holds at both logo sizes (h-8 mobile,
 * h-12 desktop) without a second magic number. */
.gl-masthead-logo {
  transform: translateY(-16%);
}
