/* PolitiSizeIt design tokens.
 *
 * Light, not dark. The previous surface was an app-console dark; colleagues found
 * it heavy, and for a page whose job is to be read quickly by people who are not
 * software users, light is the easier read.
 *
 * Palette taken from moxy.live: crimson, navy and sky over white and a blush
 * tint, set in Poppins.
 *
 * ON PARTISANSHIP — this matters for a political product. Crimson alone reads
 * Republican and blue alone reads Democrat. Moxy avoids that by always pairing
 * them, and so do we: crimson carries actions, navy carries structure, and the
 * two appear together often enough that neither claims the page. Nothing is
 * ever red-on-its-own or blue-on-its-own at a scale that could read as a side.
 *
 * Every page loads this BEFORE its own stylesheet, so a token change here moves
 * the whole product.
 */

:root {
  /* --- brand ---------------------------------------------------------- */
  --crimson:      #A31D4E;   /* ours — wine-leaning */
  --crimson-deep: #83183F;
  --crimson-soft: #FFF1F5;
  --navy:         #1B3566;   /* ours — indigo-leaning */
  --navy-soft:    #EEF2F8;
  --sky:          #03A9F4;   /* moxy accent */
  --sky-soft:     #E8F0FE;

  /* --- surfaces ------------------------------------------------------- */
  /* Not stark white. A trace of the gradient's own warmth carries the palette
     into the working areas of every page without putting text on colour. */
  --bg:         #FAF7F9;
  --bg-tint:    #FFF8FB;     /* moxy blush — section banding */
  --surface:    #FFFFFF;
  --surface-2:  #F7F8FA;
  --surface-3:  #EFF1F4;

  /* --- ink ------------------------------------------------------------ */
  --text:       #212529;     /* moxy body */
  --text-dim:   #55606B;
  /* Darkened from #8A929B, which was 2.96:1 against a white card — under the
     4.5:1 normal text needs. Hints, timestamps and footnotes were the quietest
     text in the app and also the least readable, which is the wrong pairing:
     quiet is a matter of weight and size, not of being hard to see. 4.8:1. */
  --text-faint: #666E78;
  --on-dark:    #FFFFFF;

  /* --- lines ---------------------------------------------------------- */
  --line:        rgba(31, 55, 91, 0.12);
  --line-strong: rgba(31, 55, 91, 0.22);

  /* --- onyx ----------------------------------------------------------- */
  /* The black the app wears where it wants to look like equipment rather than
     stationery: the bottom bar, the chat composer, the story tools. It is not
     pure #000 — it carries the same trace of navy the rest of the palette
     does, so black next to the gradient reads as part of it. */
  --onyx:        #0A0F1C;
  --onyx-raised: #141B2C;    /* one step up, for a control sitting on onyx */
  --onyx-line:   rgba(255, 255, 255, 0.13);
  --onyx-text:   #EEF1F6;
  --onyx-dim:    rgba(238, 241, 246, 0.52);

  /* --- roles ---------------------------------------------------------- */
  /* accent = the thing you click. accent-2 = the thing you read as information. */
  /*
   * Our gradient, deliberately adjacent to Moxy's rather than identical.
   *
   * Theirs is linear-gradient(161deg, #AE1C41 5%, #1F375B 99%). Colour itself is
   * not ownable and a gradient is not a trademark, so copying it exactly carries
   * no real legal risk. The problem is commercial: Moxy is in this same sector.
   * Being visually indistinguishable from another political-tech company is a
   * positioning mistake, not a legal one — the first person to notice will
   * conclude we are the derivative product, and they will be describing what
   * they see.
   *
   * So: same family, our own values. The crimson moves toward wine, the navy
   * toward indigo. Side by side they read as different companies with similar
   * taste, which is exactly the relationship we want.
   */
  --grad: linear-gradient(161deg, #A31D4E 4%, #1B3566 55%, #16294F 99%);

  /* Type that sits ON the gradient rather than on a white card. */
  --on-grad:       #FFFFFF;
  --on-grad-dim:   rgba(255, 255, 255, 0.82);
  /* .62 was 3.68:1 against the crimson end of the gradient — the quietest ink
     in the app, on the page where most of it lives. .76 clears 4.5:1 there and
     is still plainly quieter than --on-grad-dim. */
  --on-grad-faint: rgba(255, 255, 255, 0.76);
  --on-grad-line:  rgba(255, 255, 255, 0.26);

  --accent:      var(--crimson);
  --accent-2:    var(--navy);
  --accent-glow: rgba(163, 29, 78, 0.28);

  --ok:     #1B7F5A;
  --danger: #C0264A;
  --warn:   #A8620A;

  /* --- shape and motion ----------------------------------------------- */
  --radius:    12px;
  --radius-lg: 18px;
  --ease: cubic-bezier(0.22, 0.9, 0.3, 1);

  --shadow-sm: 0 1px 2px rgba(31, 55, 91, 0.06), 0 1px 3px rgba(31, 55, 91, 0.08);
  --shadow-md: 0 4px 6px -1px rgba(31, 55, 91, 0.07), 0 10px 24px -8px rgba(31, 55, 91, 0.12);
  --shadow-lg: 0 12px 28px -10px rgba(31, 55, 91, 0.18), 0 2px 6px rgba(31, 55, 91, 0.06);

  /* --- type ----------------------------------------------------------- */
  --sans: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, system-ui, sans-serif;
  /* Poppins carries headings too: one family reads calmer than a display pairing,
     and this audience is scanning, not admiring. */
  --serif: var(--sans);

  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

/* --- primitives shared by every page ------------------------------------ */

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* The dark theme's ambient wash and grid do not belong on a light surface. */
.ambient, .grid-veil { display: none !important; }

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

.primary-btn, .go-btn, .psv-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 48px; padding: 12px 22px; border: 0; border-radius: var(--radius);
  cursor: pointer; font: inherit; font-size: 15.5px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--on-dark); text-decoration: none;
  background: var(--crimson);
  box-shadow: var(--shadow-sm);
  transition: background .16s var(--ease), transform .16s var(--ease), box-shadow .16s var(--ease);
}
.primary-btn:hover:not(:disabled), .go-btn:hover:not(:disabled), .psv-btn:hover:not(:disabled) {
  background: var(--crimson-deep); transform: translateY(-1px); box-shadow: var(--shadow-md);
}
.primary-btn:disabled, .go-btn:disabled, .psv-btn:disabled {
  background: var(--surface-3); color: var(--text-faint); cursor: not-allowed;
  box-shadow: none; transform: none;
}

.ghost-btn {
  display: inline-flex; align-items: center; min-height: 42px; padding: 0 16px;
  border-radius: var(--radius); font-size: 14px; font-weight: 500;
  color: var(--navy); text-decoration: none;
  background: var(--surface); border: 1px solid var(--line-strong);
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.ghost-btn:hover { background: var(--navy-soft); border-color: var(--navy); }

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

.control {
  width: 100%; min-height: 48px; padding: 11px 14px;
  font: inherit; font-size: 15px; color: var(--text);
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.control:focus-visible {
  outline: none; border-color: var(--navy);
  box-shadow: 0 0 0 3px var(--sky-soft);
}
.control::placeholder { color: var(--text-faint); }
.control:disabled { background: var(--surface-2); color: var(--text-faint); }

.field-label, .field > span:first-child { color: var(--text-dim); font-weight: 500; }

/* --- focus -------------------------------------------------------------- */
/* One visible ring everywhere, because keyboard users should never be lost. */
:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }

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


/* Shared footer for the app pages. Lives here rather than in legal.css because
   that file carries a body rule that would win by load order and restyle the
   whole application. */
.legal-foot {
  border-top: 1px solid var(--line);
  padding: 22px clamp(16px, 4vw, 40px) 40px;
  /* --text-faint here gave 2.96:1 against the page, under the 4.5:1 a body of
     text needs. The separators between these links were the thing that failed
     — small grey punctuation on near-white. */
  font-size: 13px; color: var(--text-dim); text-align: center;
}
/* inline-block with padding: the links keep sitting in a line, but each one
   becomes something you can hit rather than a 20px-tall word. The horizontal
   6px was margin until short labels — "Home", "Terms" — measured under 44px
   wide. Moving it inside the anchor widens the target and changes nothing
   about how the row looks. */
.legal-foot a {
  display: inline-block;
  padding: 13px 6px;
  color: var(--text-dim); text-decoration: none;
}
.legal-foot a:hover { color: var(--accent); }

/* The page you are on, marked in the nav. Every page now offers the same three
   destinations, so without this you can reach everywhere and know nowhere. */
.ghost-btn[aria-current="page"] {
  background: var(--on-grad);
  color: var(--crimson);
  border-color: var(--on-grad);
  font-weight: 650;
}

/* ------------------------------------------------------------------- topbar */

/*
 * The wordmark, which is also the way home.
 *
 * It was styled only in landing.css while ten other pages use the same markup,
 * so everywhere else it was a bare anchor 17px tall — a link you had to aim at.
 * The colour override for the landing page's gradient topbar stays there; the
 * shape belongs here, with the rest of the shared chrome.
 */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  flex: none;
  min-height: 44px;
  text-decoration: none;
  color: inherit;
}

/*
 * Four items rather than three, which is what pushed this to two rows: the
 * shared topbar leaves .topbar-actions as a block, so the buttons simply
 * wrapped. One row, scrollable rather than truncated — on a narrow phone the
 * last item can be reached by swiping instead of disappearing.
 */
.topbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  /*
   * Right-aligned via auto margin rather than justify-content: flex-end.
   * With flex-end, content that overflows spills off the START edge, where it
   * sits under the wordmark and cannot be scrolled back to — the first nav item
   * was half-hidden behind the brand. An auto margin on the first child gives
   * the same alignment and overflows to the right, where scrolling reaches it.
   */
  justify-content: flex-start;
}

.topbar-actions > :first-child { margin-left: auto; }
.topbar-actions::-webkit-scrollbar { display: none; }
.topbar-actions .ghost-btn { flex: 0 0 auto; }

@media (max-width: 640px) {
  /* The brand keeps its name; the section sub-label is what gives way first. */
  .brand-sub { display: none; }

  /*
   * The account link becomes a glyph. Measured: four text labels need 331px
   * beside a 75px wordmark, and a 375px phone has 272px to give them. Something
   * has to shrink, and the account link is the one whose destination is obvious
   * from position alone.
   */
  #nav-account { font-size: 0; padding: 0; width: 38px; justify-content: center; }
  #nav-account::before {
    content: '\1F464';
    font-size: 16px;
    line-height: 1;
  }
}


/* ---------------------------------------------------------- secondary button

   This lived in landing.css and campaign.css and nowhere else, in two versions
   that had already drifted apart. Any page loading neither — the feed did —
   rendered a browser-default button, which is how a "Sign in" pill ended up as
   a thin grey bar. A class used across the site belongs in the file every page
   loads. */
.second-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 18px;
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--accent-2);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  transition: border-color .18s var(--ease), background .18s var(--ease), color .18s var(--ease);
}

.second-btn:hover:not(:disabled) {
  border-color: var(--accent-2);
  background: var(--navy-soft);
}

.second-btn:disabled { opacity: .6; cursor: default; }

/* A destructive action offered beside a real button: no border, no fill, so it
   never competes with the thing you probably meant to press. Still 44px tall,
   because a quiet control is not a small target. */
.text-btn {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 10px;
  border: 0;
  background: none;
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-dim);
  transition: color .18s var(--ease), background .18s var(--ease);
}
.text-btn:hover:not(:disabled) { color: var(--danger); background: var(--crimson-soft); }
.text-btn:disabled { opacity: .6; cursor: default; }

/* A hero's call to action: wide enough to read as the main thing to do, and
   the same height whether it is the loud one or the quiet one. */
.hero-btn {
  width: auto;
  min-width: 200px;
  min-height: 50px;
  margin: 24px auto 0;
}

/* The gap above belongs to the group, not to each button — otherwise stacking
   them on a phone drops the space between the text and the first button. */
.hero-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 28px;
}

.hero-actions .hero-btn { margin-top: 0; }

/* The quieter of a pair sitting on the gradient. Inherits .second-btn's shape
   and only replaces the colours, which is why it must come after it. */
.hero-btn-alt {
  background: rgba(255, 255, 255, .10);
  border: 1px solid var(--on-grad-line);
  color: var(--on-grad);
}
.hero-btn-alt:hover:not(:disabled) {
  background: rgba(255, 255, 255, .20);
  border-color: var(--on-grad);
  color: var(--on-grad);
}

@media (max-width: 480px) {
  .hero-actions { flex-direction: column; align-items: stretch; }
  .hero-actions .hero-btn { width: 100%; }
}

/* ------------------------------------------------------ iOS zoom on focus

   Safari on iPhone zooms the whole page in when you focus a form control whose
   font is under 16px, and does not zoom back out. Every field on the landing
   page, the audience builder, the campaign builder and the directory was set
   between 13px and 15.5px, so tapping any of them threw the layout sideways —
   on the very first field a new visitor touches.

   16px is the threshold, so this sets the floor and leaves everything else
   alone. The visual difference from 15px is half a pixel of x-height; the
   difference in behaviour is the page staying still. */
@media (max-width: 820px) {
  input,
  select,
  textarea {
    /* !important is deliberate and this is the case it exists for. Every page
       stylesheet loads after this one and sets its own control font-size, so a
       plain rule here loses to all of them. This is not a style preference
       being forced on a page — it is a platform behaviour that no page is
       allowed to opt out of by accident. */
    font-size: max(16px, 1rem) !important;
  }
}

/* ------------------------------------------------------ the top bar on a phone

   One owner for the nav sizing. This lived in landing.css AND builder.css with
   different numbers, and the landing page kept the wider pair — so when the bar
   grew from two items to four, the last one ran off the screen edge there and
   nowhere else.

   The doubled class is a specificity bump, not a typo: theme.css loads before
   every page stylesheet, so a plain `.topbar` here loses to the `.topbar` in
   landing.css no matter what it says. Doubling it wins without !important. */
@media (max-width: 640px) {
  .topbar.topbar { padding: 12px 12px; gap: 8px; }
  .topbar .topbar-actions { gap: 5px; }
  .topbar .ghost-btn { font-size: 12.5px; padding: 0 9px; min-height: 40px; }
  .topbar #nav-account { min-width: 44px; min-height: 40px; }
}

/* ------------------------------------------------------- out-of-counts offer

   Shown where somebody runs out, so the way forward is under their thumb
   rather than on a page they would have to go looking for. */

.tok-offer {
  text-align: left;
}

.tok-offer-head {
  margin: 0 0 6px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.4;
}

.tok-offer-sub {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-dim);
}

.tok-offer-rate {
  margin: 0 0 12px;
  padding: 9px 13px;
  border-radius: 9px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: 13.5px;
  color: var(--text-dim);
}

.tok-offer-rate strong {
  color: var(--crimson);
  font-size: 15px;
  font-weight: 700;
}

.tok-offer-packs {
  display: grid;
  /* auto-fit rather than a fixed count: the introductory pack makes four for a
     new account and three for everybody else, and a hardcoded 3 left the fourth
     stranded on its own row. */
  grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
  gap: 8px;
}

.tok-pack {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 14px 8px 12px;
  border-radius: 12px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  cursor: pointer;
  font: inherit;
  transition: border-color .15s, transform .15s, box-shadow .15s;
}

.tok-pack:hover:not(:disabled) {
  border-color: var(--crimson);
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(27, 53, 102, .12);
}

.tok-pack:disabled { opacity: .6; cursor: progress; }

.tok-pack-price {
  font-size: 19px;
  font-weight: 700;
  color: var(--navy);
  line-height: 1.1;
}

/* The figure that actually matters to somebody who just ran out. */
.tok-pack-n {
  font-size: 11.5px;
  line-height: 1.3;
  text-align: center;
  color: var(--text-dim);
}

.tok-pack-deal { border-color: var(--crimson); }

.tok-pack-save {
  position: absolute;
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--crimson);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
}

.tok-offer-btn { width: 100%; margin-top: 4px; }

.tok-offer-err {
  margin: 10px 0 0;
  font-size: 13.5px;
  color: var(--crimson);
}

.tok-offer-fine {
  margin: 12px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-faint);
}

/* The host elements are styled as error lines. Carrying an offer they need to
   read as a panel instead — and the doubled class is needed because the error
   styling lives in a page stylesheet that loads after this one. Running out of
   counts is not a failure, and painting it red says it was. */
.go-error.go-error-offer,
.count-note.count-note-offer,
.go-error-offer,
.count-note-offer {
  display: block;
  padding: 16px 18px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text);
  text-align: left;
}

@media (max-width: 420px) {
  .tok-offer-packs { grid-template-columns: 1fr; }
  .tok-pack { flex-direction: row; justify-content: space-between; padding: 12px 14px; }
  .tok-pack-save { position: static; transform: none; margin-left: 8px; }
}

/* --------------------------------------------------- today's counts

   Shown on the pages where a count is about to be spent. Quiet while there is
   plenty left — it is a fact, not a call to action — and it gets louder as it
   runs down, because at two left the number IS the decision. */

.bal-slot { display: block; margin-top: 14px; }

.quota {
  padding: 13px 15px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  text-align: left;
}

.quota-head { margin: 0 0 8px; display: flex; align-items: baseline; gap: 2px; }

.quota-n {
  font-size: 24px;
  font-weight: 700;
  color: var(--navy);
  /* Tabular figures: this number changes while you watch it, and proportional
     digits make the words after it jump sideways when 10 becomes 9. */
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.quota-word { font-size: 14px; font-weight: 600; color: var(--text-dim); }

.quota-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}
.quota-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--navy);
  transition: width .3s var(--ease);
}

.quota-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 4px 12px;
  margin: 7px 0 0;
  font-size: 12.5px;
  color: var(--text-faint);
}
.quota-reset { white-space: nowrap; }

.quota-wallet { margin: 9px 0 0; font-size: 13px; color: var(--text-dim); }
.quota-wallet-link { color: var(--navy); font-weight: 600; text-decoration: none; }
.quota-wallet-link:hover { text-decoration: underline; }
.quota-worth { color: var(--text-faint); }

.quota-note { margin: 9px 0 0; font-size: 13px; line-height: 1.5; color: var(--text-dim); }

.quota-buy { margin-top: 11px; }

.quota-btn {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--navy);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  line-height: 42px;
  cursor: pointer;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.quota-btn:hover { border-color: var(--crimson); color: var(--crimson); }

/* Out of counts is the one state where this stops being a quiet fact. */
.quota.is-out { border-color: var(--crimson); background: rgba(163, 29, 78, .04); }
.quota.is-out .quota-n { color: var(--crimson); }
.quota.is-out .quota-fill { background: var(--crimson); }
.quota.is-out .quota-btn {
  border-color: transparent;
  background: var(--crimson);
  color: #fff;
}
.quota.is-out .quota-btn:hover { background: var(--crimson-dark, #8d1839); color: #fff; }

.quota-packs { margin-top: 12px; }
/* Inside the panel the offer is already framed by the box around it. */
.quota-packs .tok-offer { padding: 0; border: 0; background: none; }

/* The one-time pack. Marked so it reads as an introduction rather than as the
   permanent cheapest option — it is neither, and looking like one would be a
   promise we take away on the second purchase. */
.tok-pack-intro {
  border-color: var(--navy);
  background: linear-gradient(180deg, rgba(27, 53, 102, .05), transparent);
}

.tok-pack-note {
  font-size: 10.5px;
  line-height: 1.25;
  text-align: center;
  color: var(--navy);
  font-weight: 600;
  letter-spacing: .01em;
}

/* -------------------------------------------------------------- tab bar

   Primary navigation on a phone. Five destinations do not fit across the top
   of a 375px screen, and the answer is not smaller text — it is the pattern
   every app people already use has settled on: bottom, thumb-reachable, and
   able to take a sixth destination without a redesign. */

.tabbar { display: none; }

@media (max-width: 760px) {
  .tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    display: grid;
    /* Not repeat(5): the bar takes however many destinations it is given, and
       hard-coding the count is how a sixth one ends up overflowing. */
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    /* The home indicator on an iPhone sits over the bottom of the screen. */
    padding-bottom: env(safe-area-inset-bottom, 0);

    /*
     * Near-black rather than white.
     *
     * The bar is chrome, not content, and a dark bar says so — it stops
     * competing with the cards above it and reads as part of the device. Not
     * pure #000: a hint of the brand navy in it keeps it from looking like a
     * hole cut in the page, and the frosted layer lets content show through
     * as it scrolls under.
     */
    background: rgba(10, 15, 28, .88);
    border-top: 1px solid rgba(255, 255, 255, .10);
    backdrop-filter: saturate(1.8) blur(18px);
    -webkit-backdrop-filter: saturate(1.8) blur(18px);
  }

  .tab {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 9px 2px 8px;
    min-height: 54px;
    color: rgba(255, 255, 255, .55);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: color .18s var(--ease);
  }

  .tab:active { transform: scale(.94); }

  .tab-ico-wrap { position: relative; display: block; line-height: 0; }
  .tab-ico { width: 23px; height: 23px; }

  .tab-label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .01em;
    /* Never two lines. A wrapped label makes one tab taller than the rest and
       the whole bar looks broken. */
    white-space: nowrap;
  }

  /* Six destinations on a 320px screen is 53px each, and "Strategist" is the
     longest word in the bar. It gets slightly smaller rather than clipped. */
  @media (max-width: 400px) {
    .tab { padding-left: 1px; padding-right: 1px; }
    .tab-label { font-size: 9.5px; letter-spacing: 0; }
  }

  /* The current tab lights up, and a short bar above it marks the position —
     colour alone is a weak signal on a dark ground, and unreadable to anyone
     who does not see red and grey as different. */
  .tab.is-here { color: #fff; }
  .tab.is-here .tab-ico { stroke-width: 2.1; }

  .tab.is-here::before {
    content: '';
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 26px;
    height: 2.5px;
    border-radius: 0 0 3px 3px;
    background: linear-gradient(90deg, var(--crimson), #E0537F);
    box-shadow: 0 0 12px rgba(224, 83, 127, .55);
  }

  /* Something waiting, without a number — a count on a tab bar is noise until
     it is large, and by then it is discouraging. */
  .tab-dot {
    position: absolute;
    top: -2px;
    right: -4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #FF4D7D;
    /* Ringed in the bar's own colour so it reads as sitting on the icon
       rather than floating above it. */
    box-shadow: 0 0 0 2px rgba(10, 15, 28, 1), 0 0 10px rgba(255, 77, 125, .7);
  }

  /*
   * Room for the bar, so nothing ends up permanently underneath it.
   *
   * By structure, not by class name. This used to name .legal-foot and
   * .site-foot — and the landing page's footer is .foot, so its last line sat
   * behind the bar with no way to scroll it clear. A page footer is a direct
   * child of body on every page here; the one nested <footer> in the app is
   * inside the story composer, which is a dialog and wants no such margin.
   */
  .has-tabbar .stage { padding-bottom: 84px; }
  body.has-tabbar > footer { margin-bottom: 62px; }

  /*
   * The whole nav moves to the bottom. Keeping the account glyph up top as
   * well would put the same destination in two places on one screen, which is
   * the kind of duplication that makes an interface feel unconsidered — "You"
   * already goes there.
   */
  .topbar-actions { display: none; }
  .topbar { justify-content: flex-start; }

  @media (prefers-reduced-motion: reduce) { .tab:active { transform: none; } }
}


/*
 * The page background must cover the viewport even when the content does not.
 *
 * body carries the gradient, so a short page — a signed-out state, an empty
 * inbox — left the html element showing through as white below the fold. Only
 * visible on a tall desktop window, which is exactly the sort of thing that
 * ships unnoticed.
 */
/* The page behind the page. Every public surface wears the gradient, and on a
   phone Safari shows this colour when the page rubber-bands or the keyboard
   pushes it: the gradient's own navy, never white. */
html { background: #16294F; }
body { min-height: 100dvh; }


/* ------------------------------------------------------------- place field

   Styling for the shared PSPlacePicker. It lived in landing.css, so when the
   same control was added to the builder the suggestions rendered as unstyled
   list items — the third time a shared component's CSS being in a page-specific
   file has broken a page. Component and its styles now travel together. */

.place-wrap { position: relative; }

.place-list {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20;
  margin: 0; padding: 6px; list-style: none; max-height: 320px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
}
.place-list[hidden] { display: none; }

.place-opt {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 10px 11px; border: 0; border-radius: 9px; cursor: pointer;
  background: transparent; font: inherit; font-size: 14.5px; color: var(--text);
  text-align: left;
}
.place-opt:hover, .place-opt.is-active { background: var(--crimson-soft); }
.place-name { font-weight: 500; }
.place-meta { margin-left: auto; font-size: 12px; color: var(--text-faint); white-space: nowrap; }

.place-empty { padding: 12px; font-size: 13.5px; color: var(--text-faint); text-align: center; }

.foot-links { display: flex; gap: 14px; }
.foot-links a {
  color: var(--on-grad-dim); text-decoration: none;
  border-bottom: 1px solid transparent;
}
.foot-links a:hover { color: var(--on-grad); border-bottom-color: var(--on-grad-line); }

/* --------------------------------------------------------------- avatars */

/* The member circle. Lives here rather than in feed.css because five pages draw
   a member now and only two of them load that file — the same mistake that once
   rendered a "Sign in" pill as a thin grey bar. */
.avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  border-radius: 50%;
  background: linear-gradient(145deg, #A31D4E, #1B3566);
  color: #fff;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: .02em;
  overflow: hidden;
}

.avatar-sm { width: 30px; height: 30px; font-size: 12px; }

/*
 * A member is drawn as a photo when there is one and initials when there is
 * not, by avatar.js. These two rules are what make the photo sit inside
 * whatever shape the surrounding avatar already is — the feed's circle, the
 * story ring's inner disc — without each of those having to know about images.
 */
.avatar-img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  /* Cover, not contain: a face should fill the circle rather than sit letter-
     boxed inside it, and the crop is centred where faces usually are. */
  object-fit: cover;
  object-position: center;
}

/* The initials background is a gradient. Behind a photo it would show through
   a transparent PNG, so it steps back to a plain surface. */
.avatar.has-photo,
.story-av.has-photo { background: var(--surface-3); }

/* ----------------------------------------------------------- profile photo */

.photo-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 18px;
}

.avatar-xl {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  background: linear-gradient(145deg, #A31D4E, #1B3566);
  color: #fff;
  font-weight: 700;
  font-size: 30px;
  letter-spacing: .02em;
  overflow: hidden;
}

/* min-width:0 so a long error message wraps instead of forcing the row wider
   than the panel — the flex default is min-width:auto, which does not shrink. */
.photo-side { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.photo-hint { margin: 0; font-size: 13px; color: var(--text-faint); line-height: 1.5; }
.photo-acts { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; margin-top: 4px; }
.photo-err { margin: 2px 0 0; font-size: 13px; color: var(--danger); }

@media (max-width: 420px) {
  .photo-row { gap: 13px; }
  .avatar-xl { width: 68px; height: 68px; font-size: 24px; }
}

/* --------------------------------------------------------------- switch row */
/* A checkbox with an explanation beside it. Used where a setting needs a
   sentence to be understood rather than a label. */

.switch-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px;
  align-items: start;
  margin-top: 20px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  cursor: pointer;
}
.switch-row input { margin-top: 3px; width: 18px; height: 18px; accent-color: var(--crimson); }
/* min-width:0 so a long explanation wraps rather than widening the row. */
.switch-text { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.switch-title { font-size: 14.5px; font-weight: 600; color: var(--text); }
.switch-sub { font-size: 13px; line-height: 1.5; color: var(--text-dim); }

.switch-row.is-off { opacity: .55; cursor: not-allowed; }

/* ------------------------------------------------------------------ back */

/*
 * Sits before the wordmark in the top bar, which is where every app people
 * already use puts it. Quiet — it is a way out, not an invitation.
 */
.back-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;
  margin-right: 4px;
  padding: 0 11px 0 8px;
  border-radius: 999px;
  border: 1px solid var(--on-grad-line);
  background: rgba(255, 255, 255, .10);
  color: var(--on-grad);
  font-size: 13.5px;
  font-weight: 600;
  text-decoration: none;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.back-btn:hover { background: rgba(255, 255, 255, .2); border-color: rgba(255, 255, 255, .45); }
.back-btn:focus-visible { outline: 2px solid var(--on-grad); outline-offset: 2px; }

/*
 * The wordmark stays next to the back button, not on the far side of the bar.
 *
 * The top bar is space-between, which is right for two children — brand on the
 * left, nav on the right. Adding a third at the start turned it into three
 * evenly spread items and threw the wordmark to the opposite edge. An auto
 * margin after the brand keeps back and brand together and pushes only the nav
 * away, which is the arrangement space-between was standing in for.
 */
.back-btn ~ .brand { margin-right: auto; }

/* On a phone the word costs more than it earns beside a wordmark and an
   account button. The arrow alone is understood. */
@media (max-width: 520px) {
  .back-btn span { display: none; }
  /* min-width, not padding: without the word it came out 38px wide, and an
     arrow you have to aim at is worse than no arrow. */
  .back-btn { padding: 0; min-width: 44px; justify-content: center; }
}

/* ---- the starfield ----
   A fixed canvas behind a page's content: above the gradient, below anything
   positioned, and it never takes a click. Drawn by /stars.js. */
.stars {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  display: block;
}
