/* ==========================================================================
   Tribe — shared wireframe stylesheet (T-009)
   ==========================================================================

   THIS FILE SURVIVES INTO PRODUCTION. Tribe's real UI is server-rendered HTML
   via maud (see ARCHITECTURE.md), so the wireframe is the template modulo
   interpolation. Class names chosen here are the class names that ship. Name
   things for what they are in THIS product (.event-card, .why, .standing-row,
   .org-chip), never generically (.card, .subtitle, .row).

   Every wireframe under wireframe/ links this one file. Do not fork the
   palette per screen — the whole point is that these become one stylesheet
   served from crates/tribe-server/.

   ---------------------------------------------------------------------------
   THE DESIGN BRIEF, because it is a product commitment and not taste
   ---------------------------------------------------------------------------

   Risk #2 in CLAUDE.md is clubhouse capture: if Tribe looks recognizably like
   "the crunchy co-op app," a large share of Stevens Point correctly identifies
   it as not-for-them in about four seconds and never returns. The test to
   design against is whether Hirschner's would feel comfortable listed here or
   judged.

   So the visual target is CIVIC INFRASTRUCTURE PLAIN — the aesthetic of a bus
   schedule, a library catalog, a posted public notice. Concretely:

     - Institutional blue as the only accent. No sage, terracotta, mustard, or
       cream: that palette reads co-op newsletter and does the exact
       identity-signaling the risk warns about.
     - Hairline borders instead of soft shadows. Shadows read consumer-app
       warm; 1px rules read document.
     - Near-zero corner radius (3px). Pills and big rounded cards read
       friendly-startup; crisp edges read posted schedule.
     - System UI type at a modest scale. No display serif, no rounded sans.
     - Tabular figures on every time and date, so a column of times aligns
       like a printed timetable.

   Restraint here is not blandness — it is the thing that lets a conventional
   local business see itself in the product.

   ---------------------------------------------------------------------------
   TWO RULES EVERY SCREEN INHERITS (MVP_SCOPE.md, not decoration)
   ---------------------------------------------------------------------------

   1. Every event card carries .why — the ranking explanation ("because you
      follow Portage County Public Library"). Opacity is the actual complaint
      about Facebook's feed; a published inspectable ranking rule is also the
      procedural-legitimacy answer to Risk #3. A card without .why is a bug.
   2. Recurrence is collapsed. "Trivia every Tuesday" is ONE row carrying
      .recurrence, not fourteen rows.

   Accessibility rule that shapes the tokens: status is NEVER encoded in color
   alone. Every state badge carries a text label, because "cancelled" vs
   "no longer listed" is a distinction with legal and trust weight (Risk #4 —
   never assert cancellation from absence).

   ========================================================================== */

/* ==========================================================================
   1. TOKENS
   ========================================================================== */

:root {
  /* --- Type ------------------------------------------------------------- */
  --font-sans:
    ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono:
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* A deliberately short scale. Signage has few sizes; so does this. */
  --text-2xs: 0.6875rem; /* 11px — metadata, source attribution */
  --text-xs: 0.75rem; /* 12px — .why, badges, tags */
  --text-sm: 0.8125rem; /* 13px — secondary rows, venue lines */
  --text-base: 0.9375rem; /* 15px — body, event titles */
  --text-md: 1.0625rem; /* 17px — card titles on detail screens */
  --text-lg: 1.375rem; /* 22px — screen headings */
  --text-xl: 1.75rem; /* 28px — the one big number / event page title */

  --leading-tight: 1.25;
  --leading-normal: 1.45;
  --leading-loose: 1.6;

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-semibold: 600;

  /* --- Space: 4px base -------------------------------------------------- */
  --space-0: 0;
  --space-1: 0.25rem; /*  4px */
  --space-2: 0.5rem; /*  8px */
  --space-3: 0.75rem; /* 12px */
  --space-4: 1rem; /* 16px */
  --space-5: 1.5rem; /* 24px */
  --space-6: 2rem; /* 32px */
  --space-7: 3rem; /* 48px */

  /* --- Shape ------------------------------------------------------------ */
  --radius: 3px;
  --radius-sm: 2px;
  --border-width: 1px;

  /* Deliberately minimal. One shadow, used only for things that float above
     the page (sheets, the install prompt). Cards never get one. */
  --shadow-overlay: 0 2px 12px rgb(16 20 26 / 18%);

  /* --- Color: light ----------------------------------------------------- */
  /* Semantic names only. No screen should reference a hex value directly. */
  --c-page: #f5f6f8; /* app background */
  --c-surface: #ffffff; /* cards, sheets, inputs */
  --c-surface-sunken: #eceef1; /* wells, disabled fields, skeletons */
  --c-hairline: #d9dde3; /* the workhorse border */
  --c-hairline-strong: #b9c0ca; /* input borders, focus-adjacent */

  --c-ink: #161a1f; /* primary text */
  --c-ink-muted: #5b6472; /* venue lines, metadata */
  --c-ink-faint: #838c99; /* .why, timestamps, attribution */

  --c-accent: #1d4f7c; /* institutional blue — the only accent */
  --c-accent-hover: #17405f;
  --c-accent-ink: #ffffff; /* text on accent */
  --c-accent-wash: #e8eff6; /* selected rows, followed chips */

  --c-focus: #2d7ab8; /* focus ring — brighter than accent on purpose */

  /* Status. Each pairs with a text label; never color alone. */
  --c-cancelled: #9c2620; /* upstream said STATUS:CANCELLED */
  --c-cancelled-wash: #fbeceb;
  --c-changed: #7a5200; /* time/venue moved */
  --c-changed-wash: #fdf3e0;
  --c-unlisted: #5b6472; /* "no longer listed" — NOT cancelled (Risk #4) */
  --c-unlisted-wash: #eceef1;

  /* --- Motion ----------------------------------------------------------- */
  --ease: cubic-bezier(0.2, 0, 0.3, 1);
  --duration-fast: 90ms;
  --duration: 160ms;

  /* --- Layout ----------------------------------------------------------- */
  --measure: 34rem; /* single-column reading width */
  --measure-wide: 60rem; /* submission tables, merge queue */
  --tap-target: 44px; /* iOS minimum; every control meets it */

  color-scheme: light dark;
}

/* --- Color: dark ------------------------------------------------------- */
/* Tribe is an installable PWA people check on a phone at night, so dark is a
   real surface, not a nicety. KEEP THESE TWO BLOCKS IN SYNC — CSS cannot
   share one declaration block between a media query and an attribute
   selector, so the dark palette is written twice on purpose. Only the color
   tokens change; type, space and shape are theme-independent. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --c-page: #12151a;
    --c-surface: #1a1f26;
    --c-surface-sunken: #232932;
    --c-hairline: #2f3640;
    --c-hairline-strong: #454e5a;

    --c-ink: #e9ecf0;
    --c-ink-muted: #a3acb9;
    --c-ink-faint: #7d8794;

    --c-accent: #7fb3e3;
    --c-accent-hover: #9bc6ec;
    --c-accent-ink: #12151a;
    --c-accent-wash: #1c2b3a;

    --c-focus: #7fb3e3;

    --c-cancelled: #f0918a;
    --c-cancelled-wash: #33201f;
    --c-changed: #d9ab5a;
    --c-changed-wash: #2d2618;
    --c-unlisted: #a3acb9;
    --c-unlisted-wash: #232932;

    --shadow-overlay: 0 2px 14px rgb(0 0 0 / 50%);
  }
}

:root[data-theme="dark"] {
  --c-page: #12151a;
  --c-surface: #1a1f26;
  --c-surface-sunken: #232932;
  --c-hairline: #2f3640;
  --c-hairline-strong: #454e5a;

  --c-ink: #e9ecf0;
  --c-ink-muted: #a3acb9;
  --c-ink-faint: #7d8794;

  --c-accent: #7fb3e3;
  --c-accent-hover: #9bc6ec;
  --c-accent-ink: #12151a;
  --c-accent-wash: #1c2b3a;

  --c-focus: #7fb3e3;

  --c-cancelled: #f0918a;
  --c-cancelled-wash: #33201f;
  --c-changed: #d9ab5a;
  --c-changed-wash: #2d2618;
  --c-unlisted: #a3acb9;
  --c-unlisted-wash: #232932;

  --shadow-overlay: 0 2px 14px rgb(0 0 0 / 50%);
}

/* ==========================================================================
   2. RESET + BASE
   ========================================================================== */

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

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

body {
  margin: 0;
  background: var(--c-page);
  color: var(--c-ink);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  letter-spacing: -0.01em;
}

h1 {
  font-size: var(--text-lg);
}
h2 {
  font-size: var(--text-md);
}
h3 {
  font-size: var(--text-base);
}

p {
  margin: 0 0 var(--space-3);
}
p:last-child {
  margin-bottom: 0;
}

a {
  color: var(--c-accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

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

button {
  font: inherit;
  color: inherit;
}

/* One focus treatment everywhere. Keyboard-only, so it never nags a
   touch user, but it is never removed. */
:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

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

/* ==========================================================================
   3. SHELL — the frame every screen sits in
   ========================================================================== */

.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--c-page);
}

.app-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--c-surface);
  border-bottom: var(--border-width) solid var(--c-hairline);
  position: sticky;
  top: 0;
  z-index: 10;
}

/* Wordmark. Plain, lowercase, no logotype flourish — a utility masthead. */
.app-header__brand {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  color: var(--c-ink);
  text-decoration: none;
  margin-right: auto;
}

/* The place name earns its own element: this product is specifically about
   one town, and saying so is part of the pitch. */
.app-header__place {
  font-size: var(--text-xs);
  font-weight: var(--weight-normal);
  color: var(--c-ink-faint);
  letter-spacing: 0;
}

.app-main {
  flex: 1;
  width: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-4);
}

.app-main--wide {
  max-width: var(--measure-wide);
}

/* Bottom tab bar (PWA). Four destinations maximum. */
.app-nav {
  position: sticky;
  bottom: 0;
  display: flex;
  background: var(--c-surface);
  border-top: var(--border-width) solid var(--c-hairline);
  padding-bottom: env(safe-area-inset-bottom);
}

.app-nav__item {
  flex: 1;
  min-height: var(--tap-target);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: var(--space-2) var(--space-1);
  background: none;
  border: 0;
  font-size: var(--text-2xs);
  color: var(--c-ink-muted);
  text-decoration: none;
  cursor: pointer;
}

.app-nav__item[aria-current="page"] {
  color: var(--c-accent);
  font-weight: var(--weight-medium);
  box-shadow: inset 0 2px 0 var(--c-accent);
}

/* --- Sections ---------------------------------------------------------- */

.section {
  margin-bottom: var(--space-5);
}

.section__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.section__title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-ink-muted);
}

/* "things you follow" vs "popular near you" — MVP_SCOPE Risk #1 says label
   which is which, so the section heading carries a count and a source. */
.section__count {
  font-size: var(--text-xs);
  color: var(--c-ink-faint);
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}

/* Day divider in the ranked feed. */
.day-rule {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-ink-muted);
}

.day-rule::after {
  content: "";
  flex: 1;
  height: var(--border-width);
  background: var(--c-hairline);
}

/* ==========================================================================
   4. EVENT CARD — the core object of the entire product
   ========================================================================== */

.event-card {
  display: grid;
  grid-template-columns: 4.25rem 1fr;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3);
  background: var(--c-surface);
  border: var(--border-width) solid var(--c-hairline);
  border-radius: var(--radius);
  margin-bottom: var(--space-2);
}

/* Whole card is a link target on the feed; the inner title stays a real
   <a> for accessible naming. */
.event-card--link {
  cursor: pointer;
  transition: border-color var(--duration) var(--ease);
}

.event-card--link:hover {
  border-color: var(--c-hairline-strong);
}

/* Time column. Tabular figures so a stack of times aligns like a timetable —
   this is the single most "civic" detail in the whole stylesheet. */
.event-card__when {
  grid-column: 1;
  grid-row: 1 / span 2;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--c-ink);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
}

.event-card__when small {
  display: block;
  font-size: var(--text-2xs);
  font-weight: var(--weight-normal);
  color: var(--c-ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.event-card__body {
  grid-column: 2;
  min-width: 0; /* lets long titles wrap instead of blowing out the grid */
}

.event-card__title {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  margin: 0 0 var(--space-1);
}

.event-card__title a {
  color: inherit;
  text-decoration: none;
}

.event-card__title a:hover {
  text-decoration: underline;
}

/* Venue + org line. */
.event-card__where {
  font-size: var(--text-sm);
  color: var(--c-ink-muted);
  margin: 0;
}

.event-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* Emphasis variants. Both are neutral in shape and differ by a left rule
   plus a label — not by color alone, and not by size, because a "popular
   near you" item must not look like a downgraded version of a followed one. */
.event-card--followed {
  border-left: 3px solid var(--c-accent);
}

.event-card--popular {
  border-left: 3px solid var(--c-hairline-strong);
}

/* Cancelled / changed cards stay legible and keep their place in the feed;
   they are information, not errors. */
.event-card--cancelled .event-card__title {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  color: var(--c-ink-muted);
}

/* ==========================================================================
   5. .why — THE RANKING EXPLANATION (required on every card)
   ==========================================================================
   MVP_SCOPE.md: "every card shows why it's there." This is the product's
   differentiator against Facebook's feed and half of the procedural-
   legitimacy answer to Risk #3, so it gets a first-class class and is
   never conditional. */

.why {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
  color: var(--c-ink-faint);
}

/* A small leading rule rather than an icon: no emoji, no sparkle, nothing
   that implies an opaque algorithm is being cute at you. */
.why::before {
  content: "";
  flex: none;
  width: 10px;
  height: var(--border-width);
  background: var(--c-hairline-strong);
  transform: translateY(-0.3em);
}

/* The org or rule being cited, e.g. "Portage County Public Library". */
.why__cause {
  color: var(--c-ink-muted);
  font-weight: var(--weight-medium);
}

/* Link to the published ranking rule. Every explanation is inspectable —
   that is the point. */
.why__rule {
  color: var(--c-ink-faint);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --- The published ranking rule ----------------------------------------
   The other half of the Risk #3 answer. .why explains ONE card; this
   discloses the whole scoring rule, inspectable by anyone. A transparent
   linear score with weights in config is a governance commitment, so it gets
   a real surface in the UI rather than a help-center page. */

.rank-rule {
  margin-top: var(--space-3);
  border: var(--border-width) solid var(--c-hairline);
  border-radius: var(--radius);
  background: var(--c-surface);
}

.rank-rule > summary {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--c-ink-muted);
  cursor: pointer;
  min-height: var(--tap-target);
  display: flex;
  align-items: center;
}

.rank-rule[open] > summary {
  border-bottom: var(--border-width) solid var(--c-hairline);
}

.rank-rule__body {
  padding: var(--space-3);
  font-size: var(--text-sm);
  color: var(--c-ink-muted);
}

/* Weight table. Tabular and right-aligned so the numbers read as a spec. */
.rank-rule__weights {
  margin: var(--space-3) 0;
}

.rank-rule__weights li {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  border-bottom: var(--border-width) solid var(--c-hairline);
  font-size: var(--text-xs);
}

.rank-rule__weights li:last-child {
  border-bottom: 0;
}

.rank-rule__weights span {
  flex: 1;
}

.rank-rule__weights b {
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-semibold);
  color: var(--c-ink);
}

/* ==========================================================================
   6. RECURRENCE — collapsed by default (required)
   ==========================================================================
   "Trivia every Tuesday" is one row, not fourteen. The badge states the
   cadence; the disclosure reveals the individual dates. */

.recurrence {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--c-ink-muted);
  background: var(--c-surface-sunken);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
  white-space: nowrap;
}

.recurrence__count {
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-faint);
  font-weight: var(--weight-normal);
}

/* The "show the 14 dates" disclosure. */
.recurrence-dates {
  margin-top: var(--space-2);
  padding-left: var(--space-3);
  border-left: var(--border-width) solid var(--c-hairline);
}

.recurrence-dates li {
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-muted);
  padding: 2px 0;
}

/* ==========================================================================
   7. ORG CHIP — the followable unit
   ==========================================================================
   Onboarding follows ORGS, not categories (MVP_SCOPE.md). The chip is
   therefore a control, not a tag: it has a pressed state and a real
   tap target. */

.org-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-3);
  background: var(--c-surface);
  border: var(--border-width) solid var(--c-hairline-strong);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  color: var(--c-ink);
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease),
    border-color var(--duration-fast) var(--ease);
}

.org-chip:hover {
  border-color: var(--c-accent);
}

/* Followed state: wash + accent border + a checkmark supplied by the markup.
   Three signals, only one of which is color. */
.org-chip[aria-pressed="true"] {
  background: var(--c-accent-wash);
  border-color: var(--c-accent);
  color: var(--c-accent);
  font-weight: var(--weight-medium);
}

.org-chip__count {
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-faint);
}

/* Grid of chips during onboarding. 10rem, not 11: at a 390px phone width the
   content box is ~356px, and 11rem tracks need 360px — so two-up missed by
   4px and every chip fell to its own row. Onboarding shows ~8 orgs at once,
   where one-per-row is a scroll instead of a scan. */
.org-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  gap: var(--space-2);
}

/* ==========================================================================
   7b. FILTER ROW — tag filters on the public calendar (T-053)
   ==========================================================================
   Links, not buttons: every filtered view is a real URL (?tag=kids) so it can
   be texted to someone. One row that scrolls sideways rather than wrapping —
   on a phone, three wrapped rows of chips push the first event below the
   fold. The page itself must never scroll sideways; only this row does. */

.filter-row {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-2);
}

.filter-row::-webkit-scrollbar {
  display: none;
}

.filter-chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  padding: 0 var(--space-3);
  background: var(--c-surface);
  border: var(--border-width) solid var(--c-hairline-strong);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  color: var(--c-ink);
  text-decoration: none;
  white-space: nowrap;
}

.filter-chip:hover {
  border-color: var(--c-accent);
}

/* Current filter: same three-signal rule as .org-chip — wash, border, weight. */
.filter-chip[aria-current="page"] {
  background: var(--c-accent-wash);
  border-color: var(--c-accent);
  color: var(--c-accent);
  font-weight: var(--weight-medium);
}

/* Inline, non-interactive org attribution inside a card. */
.org-ref {
  color: var(--c-ink-muted);
  text-decoration: none;
  border-bottom: var(--border-width) dotted var(--c-hairline-strong);
}

/* ==========================================================================
   8. STANDING ROW — "your Thursdays"
   ==========================================================================
   The standing-schedule view: what recurs in your life, by weekday. Reads as
   a printed timetable on purpose. */

.standing-row {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: var(--border-width) solid var(--c-hairline);
}

.standing-row:last-child {
  border-bottom: 0;
}

.standing-row__day {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-ink-muted);
  padding-top: 2px;
}

.standing-row__items {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.standing-row__item {
  display: flex;
  gap: var(--space-3);
  font-size: var(--text-sm);
}

.standing-row__time {
  flex: none;
  width: 4.25rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-muted);
}

/* A weekday with nothing standing. Shown, not hidden — the empty Thursday is
   information too. */
.standing-row--empty .standing-row__items {
  font-size: var(--text-sm);
  color: var(--c-ink-faint);
  font-style: italic;
}

/* ==========================================================================
   9. BADGES — status, always with a text label
   ==========================================================================
   Risk #4: never assert "cancelled" from absence. .badge--unlisted says
   "no longer listed" and is visually distinct from .badge--cancelled, which
   is only ever used when upstream sent STATUS:CANCELLED. */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--c-surface-sunken);
  color: var(--c-ink-muted);
  white-space: nowrap;
}

.badge--cancelled {
  background: var(--c-cancelled-wash);
  color: var(--c-cancelled);
}

.badge--changed {
  background: var(--c-changed-wash);
  color: var(--c-changed);
}

.badge--unlisted {
  background: var(--c-unlisted-wash);
  color: var(--c-unlisted);
}

.badge--free {
  background: var(--c-accent-wash);
  color: var(--c-accent);
}

/* Normalized tag vocabulary (T-008). Quiet — tags are for scanning and
   filtering, never the loudest thing on a card. */
.tag {
  display: inline-block;
  font-size: var(--text-xs);
  color: var(--c-ink-faint);
  padding: 1px var(--space-2);
  border: var(--border-width) solid var(--c-hairline);
  border-radius: var(--radius-sm);
}

/* Source attribution. Shown because provenance is part of the trust story. */
.source-ref {
  font-size: var(--text-2xs);
  color: var(--c-ink-faint);
}

/* ==========================================================================
   9b. PUBLIC EVENT PAGE — what a stranger sees from a pasted link
   ==========================================================================
   No account, never heard of us. Useful FIRST, invite SECOND: what, when,
   where, how to get there, add to calendar — all of it working logged-out.
   A signup wall here would break the acquisition strategy at exactly the
   point it is supposed to work. */

.event-hero {
  padding: var(--space-5) var(--space-4) var(--space-4);
  background: var(--c-surface);
  border-bottom: var(--border-width) solid var(--c-hairline);
}

/* The date, set large and tabular. A stranger's first question is "when",
   not "what" — they already clicked because the title interested them. */
.event-hero__date {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  color: var(--c-accent);
  margin-bottom: var(--space-2);
}

.event-hero__title {
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  letter-spacing: -0.02em;
  margin: 0 0 var(--space-2);
}

.event-hero__where {
  font-size: var(--text-base);
  color: var(--c-ink-muted);
  margin: 0;
}

/* Fact rows. Deliberately a table-ish layout rather than prose: this is
   reference information someone is scanning on a sidewalk. */
.event-detail {
  padding: var(--space-4);
}

.event-detail__row {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: var(--border-width) solid var(--c-hairline);
  font-size: var(--text-sm);
}

.event-detail__row:last-child {
  border-bottom: 0;
}

.event-detail__label {
  color: var(--c-ink-faint);
  text-transform: uppercase;
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  padding-top: 1px;
}

.event-detail__value {
  color: var(--c-ink);
  min-width: 0;
}

.event-actions {
  display: grid;
  gap: var(--space-2);
  padding: 0 var(--space-4) var(--space-4);
}

/* The invite, and it comes LAST. Everything above works without it. */
.invite {
  margin: var(--space-4);
  padding: var(--space-4);
  background: var(--c-accent-wash);
  border: var(--border-width) solid var(--c-accent);
  border-radius: var(--radius);
}

.invite__title {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-1);
}

.invite__body {
  font-size: var(--text-sm);
  color: var(--c-ink-muted);
  margin-bottom: var(--space-3);
}

/* ==========================================================================
   9c. OG SHARE CARD — 1200x630, rendered server-side to an image
   ==========================================================================
   PRODUCTION, not scaffolding. For most people who ever encounter Tribe,
   this thumbnail in a Facebook group post is the entire first impression —
   it replaces the digest as the acquisition surface. It is authored at full
   1200x630 and scaled for viewing by .wf-og-scale.

   Design constraint: it must be legible at ~250px wide in a feed, so there
   are at most four text elements and the smallest is large. */

.og-card {
  width: 1200px;
  height: 630px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 72px 80px;
  background: var(--c-surface);
  border-left: 16px solid var(--c-accent);
  font-family: var(--font-sans);
  color: var(--c-ink);
}

.og-card__kicker {
  font-size: 34px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--c-accent);
}

.og-card__title {
  font-size: 84px;
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.03em;
  margin: 32px 0 0;
  /* Three lines maximum; a longer title is ellipsised by the renderer
     rather than shrinking the type below feed-legibility. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* line-height 1.05 at 84px is tighter than the font's own line box, so
     `overflow: hidden` clips descenders on the final line — the tails of
     "Poetry Night" were being cut. Padding extends the clip edge without
     touching the type. */
  padding-bottom: 0.12em;
}

.og-card__where {
  font-size: 40px;
  color: var(--c-ink-muted);
  margin-top: 24px;
}

.og-card__foot {
  display: flex;
  align-items: baseline;
  gap: 16px;
  font-size: 32px;
  color: var(--c-ink-faint);
}

.og-card__brand {
  font-weight: 600;
  color: var(--c-ink);
  letter-spacing: -0.02em;
}

/* ==========================================================================
   10. PERSON / HOUSEHOLD
   ==========================================================================
   "Interests are per-person. Logistics are shared." Anything appearing in a
   household context has to say WHOSE it is. */

.person {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--c-ink-muted);
}

/* Initial-only avatar: no photo upload in v1, and initials sidestep the
   whole avatar-moderation surface. */
.person__initial {
  display: grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--radius-sm);
  background: var(--c-surface-sunken);
  border: var(--border-width) solid var(--c-hairline);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  color: var(--c-ink-muted);
}

/* A shared (household) object, as opposed to a personal one. */
.shared-marker {
  font-size: var(--text-xs);
  color: var(--c-ink-faint);
}

/* ==========================================================================
   11. CONTROLS
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-4);
  border: var(--border-width) solid var(--c-hairline-strong);
  border-radius: var(--radius);
  background: var(--c-surface);
  color: var(--c-ink);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  cursor: pointer;
  text-decoration: none; /* .btn is also used on <a> (GET actions) */
  transition:
    background-color var(--duration-fast) var(--ease),
    border-color var(--duration-fast) var(--ease);
}

.btn:hover {
  border-color: var(--c-accent);
}

.btn--primary {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: var(--c-accent-ink);
}

.btn--primary:hover {
  background: var(--c-accent-hover);
  border-color: var(--c-accent-hover);
}

/* Quiet action — "not now", "skip", "dismiss". Dismissal must be easy:
   dismissals are a ranking signal we actually want. */
.btn--quiet {
  border-color: transparent;
  background: none;
  color: var(--c-ink-muted);
  font-weight: var(--weight-normal);
}

.btn--quiet:hover {
  background: var(--c-surface-sunken);
  border-color: transparent;
}

.btn--block {
  width: 100%;
}

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

.btn:disabled:hover {
  border-color: var(--c-hairline-strong);
}

/* --- Fields ------------------------------------------------------------ */

.field {
  display: block;
  margin-bottom: var(--space-4);
}

.field__label {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--c-ink);
  margin-bottom: var(--space-1);
}

.field__hint {
  font-size: var(--text-xs);
  color: var(--c-ink-faint);
  margin-top: var(--space-1);
}

.input {
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-3);
  background: var(--c-surface);
  border: var(--border-width) solid var(--c-hairline-strong);
  border-radius: var(--radius);
  color: var(--c-ink);
  font-family: inherit;
  font-size: var(--text-base);
}

.input::placeholder {
  color: var(--c-ink-faint);
}

/* Phone + OTP entry. Tabular and wide-tracked so a 6-digit code is easy to
   check against an SMS. */
.input--code {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  letter-spacing: 0.25em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* --- Toggle row (the 4 cold-start priors) ------------------------------ */

.toggle-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-target);
  padding: var(--space-2) 0;
  border-bottom: var(--border-width) solid var(--c-hairline);
}

.toggle-row:last-child {
  border-bottom: 0;
}

.toggle-row__label {
  flex: 1;
  font-size: var(--text-base);
}

.toggle-row__note {
  display: block;
  font-size: var(--text-xs);
  color: var(--c-ink-faint);
}

/* ==========================================================================
   12. NOTICES + EMPTY STATES
   ==========================================================================
   The thin-week case is a designed state, not an accident: at ~60 events a
   week a user following four orgs WILL hit a week with two results, and thin
   personalization reads as broken unless the product says so out loud. */

.notice {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--c-surface);
  border: var(--border-width) solid var(--c-hairline);
  border-left: 3px solid var(--c-hairline-strong);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
}

.notice--accent {
  border-left-color: var(--c-accent);
  background: var(--c-accent-wash);
}

.notice--changed {
  border-left-color: var(--c-changed);
  background: var(--c-changed-wash);
}

.notice--cancelled {
  border-left-color: var(--c-cancelled);
  background: var(--c-cancelled-wash);
}

.empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--c-ink-muted);
  background: var(--c-surface);
  border: var(--border-width) dashed var(--c-hairline-strong);
  border-radius: var(--radius);
}

.empty__title {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--c-ink);
  margin-bottom: var(--space-1);
}

/* ==========================================================================
   13. UTILITIES — a short list on purpose
   ========================================================================== */

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

.u-muted {
  color: var(--c-ink-muted);
}
.u-faint {
  color: var(--c-ink-faint);
}
.u-tabular {
  font-variant-numeric: tabular-nums;
}
.u-nowrap {
  white-space: nowrap;
}

.u-stack > * + * {
  margin-top: var(--space-3);
}
.u-stack-tight > * + * {
  margin-top: var(--space-2);
}

.u-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.u-row--end {
  justify-content: flex-end;
}

