/* ===========================================================================
   EFF KSD Campaign Platform — design system
   Economic Freedom Fighters house colours: red, black, green, gold.
   =========================================================================== */

:root {
  /* Brand */
  --eff-red:        #E30613;
  --eff-red-dark:   #A8040E;
  --eff-red-deep:   #7A0309;
  --eff-gold:       #FFD500;
  --eff-gold-dark:  #D9B400;
  --eff-green:      #046A38;
  --eff-green-dark: #024527;

  /* Neutrals */
  --ink:        #0C0C0D;
  --ink-2:      #1C1C1F;
  --ink-3:      #34343A;
  --slate:      #5B5B66;
  --slate-2:    #8A8A96;
  --line:       #E2E2E8;
  --line-2:     #EEEEF2;
  --paper:      #FFFFFF;
  --canvas:     #F6F6F9;
  --canvas-2:   #FAFAFC;

  /* Status */
  --ok:      #0F7B3F;
  --ok-bg:   #E6F5EC;
  --warn:    #B45309;
  --warn-bg: #FEF3C7;
  --bad:     #C0161F;
  --bad-bg:  #FDEAEB;
  --info:    #1D4ED8;
  --info-bg: #E7EDFD;

  --radius:    14px;
  --radius-sm: 9px;
  --radius-lg: 22px;

  --shadow-sm: 0 1px 2px rgba(12,12,13,.06), 0 1px 3px rgba(12,12,13,.04);
  --shadow:    0 2px 4px rgba(12,12,13,.05), 0 8px 24px rgba(12,12,13,.07);
  --shadow-lg: 0 12px 48px rgba(12,12,13,.16);

  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          'Helvetica Neue', Arial, sans-serif;
  --mono: ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, Consolas, monospace;

  --sidebar-w: 252px;
  --header-h:  62px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  text-size-adjust: 100%;
}

body { min-height: 100vh; line-height: 1.5; }

h1, h2, h3, h4 { margin: 0; font-weight: 800; letter-spacing: -.021em; line-height: 1.18; }
h1 { font-size: 1.9rem; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.06rem; }
h4 { font-size: .94rem; }
p  { margin: 0 0 .85rem; }
a  { color: var(--eff-red); text-decoration: none; }
a:hover { text-decoration: underline; }

.muted     { color: var(--slate); }
.tiny      { font-size: .78rem; }
.small     { font-size: .86rem; }
.strong    { font-weight: 700; }
.mono      { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.nums      { font-variant-numeric: tabular-nums; }
.center    { text-align: center; }
.right     { text-align: right; }
.nowrap    { white-space: nowrap; }
.hide      { display: none !important; }
/* A hidden section between two white bands leaves them touching: one long
   white block with a doubled rule through it. Drop the second back to the
   page colour so the alternation still reads. */
.section.alt + .section.hide + .section.alt { background: transparent; border-block-color: transparent; }
.spacer    { flex: 1 1 auto; }
.stack     { display: flex; flex-direction: column; gap: .55rem; }
.row       { display: flex; align-items: center; gap: .6rem; }
.row-wrap  { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.mt0{margin-top:0}.mt1{margin-top:.5rem}.mt2{margin-top:1rem}.mt3{margin-top:1.6rem}
.mb0{margin-bottom:0}.mb1{margin-bottom:.5rem}.mb2{margin-bottom:1rem}.mb3{margin-bottom:1.6rem}

/* ------------------------------------------------------------------ brand */
/* No coloured strips anywhere (the user's call, 24 Sep 2026): the old
   green/red/black flag bar above the nav is gone; the logo carries the brand. */
.brand-bar { display: none; }

.logo-lockup { display: flex; align-items: center; gap: .7rem; }
/* The party logo, cut from the campaign poster, on a red tile. The "EFF" text
   stays in the markup for screen readers; it is only hidden visually. */
.logo-mark {
  width: 58px; height: 42px; flex: 0 0 58px;
  border-radius: 10px;
  background: var(--eff-red) url('../img/eff-logo.png') center / 86% auto no-repeat;
  color: transparent;
  display: grid; place-items: center;
  font-weight: 900; font-size: 0; letter-spacing: -.03em;
  box-shadow: inset 0 0 0 2px rgba(255,255,255,.18);
  overflow: hidden;
}
.logo-text { line-height: 1.1; }
.logo-text b { display: block; font-size: .98rem; font-weight: 800; letter-spacing: -.02em; }
.logo-text span { display: block; font-size: .7rem; color: var(--slate); font-weight: 600; }

/* ------------------------------------------------------------------ cards */
.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.card-pad { padding: 1.15rem 1.25rem; }
.card-head {
  display: flex; align-items: center; gap: .7rem;
  padding: .95rem 1.25rem;
  border-bottom: 1px solid var(--line-2);
}
.card-head h3 { flex: 1 1 auto; }
.card-body { padding: 1.15rem 1.25rem; }
.card-foot {
  padding: .8rem 1.25rem;
  border-top: 1px solid var(--line-2);
  background: var(--canvas-2);
  border-radius: 0 0 var(--radius) var(--radius);
}

/* ------------------------------------------------------------------ stats */
.stat-grid {
  display: grid; gap: .85rem;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}
.stat {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .95rem 1.05rem;
  box-shadow: var(--shadow-sm);
  position: relative;
  overflow: hidden;
}
/* A tile's colour lives in a dot beside its label, not in a strip down its
   edge. */
.stat { --stat-c: var(--eff-red); }
.stat.g { --stat-c: var(--eff-green); }
.stat.y { --stat-c: var(--warn); }
.stat.k { --stat-c: var(--ink); }
.stat-label::before {
  content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--stat-c); margin-right: .4rem; vertical-align: .08em;
}
.stat-label {
  font-size: .72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--slate);
}
.stat-value {
  font-size: 1.85rem; font-weight: 800; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; line-height: 1.15; margin-top: .12rem;
}
.stat-sub { font-size: .78rem; color: var(--slate); margin-top: .1rem; }

/* ----------------------------------------------------------------- badges */
.badge {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .17rem .5rem;
  border-radius: 999px;
  font-size: .73rem; font-weight: 700;
  background: var(--line-2); color: var(--ink-3);
  white-space: nowrap;
}
.badge.ok   { background: var(--ok-bg);   color: var(--ok); }
.badge.warn { background: var(--warn-bg); color: var(--warn); }
.badge.bad  { background: var(--bad-bg);  color: var(--bad); }
.badge.info { background: var(--info-bg); color: var(--info); }
.badge.red  { background: var(--eff-red); color: #fff; }
.badge.gold { background: var(--eff-gold); color: var(--ink); }

.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--slate-2); flex: 0 0 8px; }
.dot.live { background: var(--ok); box-shadow: 0 0 0 0 rgba(15,123,63,.5); animation: pulse 2s infinite; }
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(15,123,63,.5); }
  70%  { box-shadow: 0 0 0 7px rgba(15,123,63,0); }
  100% { box-shadow: 0 0 0 0 rgba(15,123,63,0); }
}

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  padding: .62rem 1.05rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: .9rem; font-weight: 700;
  cursor: pointer;
  background: var(--paper); color: var(--ink);
  border-color: var(--line);
  transition: background .13s ease, border-color .13s ease, transform .06s ease;
  text-decoration: none;
  line-height: 1.25;
}
.btn:hover  { background: var(--canvas); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-busy="true"] { opacity: .55; cursor: not-allowed; }

.btn-primary { background: var(--eff-red); color: #fff; border-color: var(--eff-red); }
.btn-primary:hover { background: var(--eff-red-dark); border-color: var(--eff-red-dark); }
.btn-dark    { background: var(--ink); color: #fff; border-color: var(--ink); }
.btn-dark:hover { background: var(--ink-2); }
.btn-green   { background: var(--eff-green); color: #fff; border-color: var(--eff-green); }
.btn-green:hover { background: var(--eff-green-dark); }
.btn-gold    { background: var(--eff-gold); color: var(--ink); border-color: var(--eff-gold); }
.btn-gold:hover { background: var(--eff-gold-dark); }
.btn-ghost   { background: transparent; border-color: transparent; color: var(--slate); }
.btn-ghost:hover { background: var(--line-2); color: var(--ink); }
.btn-lg  { padding: .82rem 1.4rem; font-size: 1rem; border-radius: var(--radius); }
.btn-sm  { padding: .38rem .7rem;  font-size: .8rem; }
.btn-block { width: 100%; }

/* ----------------------------------------------------------------- forms */
.field { margin-bottom: 1rem; }
.field > label {
  display: block;
  font-size: .8rem; font-weight: 700;
  color: var(--ink-3);
  margin-bottom: .32rem;
}
.field .hint { font-size: .76rem; color: var(--slate); margin-top: .28rem; }

.input, .select, .textarea {
  width: 100%;
  padding: .68rem .8rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: 1rem;
  background: var(--paper); color: var(--ink);
  transition: border-color .13s ease, box-shadow .13s ease;
  -webkit-appearance: none; appearance: none;
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--eff-red);
  box-shadow: 0 0 0 3.5px rgba(227,6,19,.13);
}
.textarea { min-height: 108px; resize: vertical; line-height: 1.5; }
.select {
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='%235B5B66' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .8rem center;
  padding-right: 2.1rem;
}
.input.big {
  font-size: 1.28rem; font-weight: 700; letter-spacing: .06em;
  font-family: var(--mono);
  padding: .78rem .8rem;
}
.input.ok   { border-color: var(--ok); }
.input.bad  { border-color: var(--bad); }

.field-msg { font-size: .8rem; margin-top: .32rem; font-weight: 600; display: none; }
.field-msg.show { display: block; }
.field-msg.ok  { color: var(--ok); }
.field-msg.bad { color: var(--bad); }
.field-msg.warn{ color: var(--warn); }

.check {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .7rem .8rem;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  cursor: pointer;
  background: var(--paper);
}
.check:hover { border-color: var(--slate-2); }
.check input { width: 19px; height: 19px; margin: 1px 0 0; accent-color: var(--eff-red); flex: 0 0 19px; }
.check-text b { display: block; font-size: .9rem; }
.check-text span { display: block; font-size: .78rem; color: var(--slate); }

.seg {
  display: inline-flex; padding: 3px; gap: 3px;
  background: var(--line-2); border-radius: 999px;
}
.seg button {
  border: 0; background: transparent; cursor: pointer;
  padding: .4rem .85rem; border-radius: 999px;
  font-family: inherit; font-size: .84rem; font-weight: 700; color: var(--slate);
}
.seg button.on { background: var(--paper); color: var(--ink); box-shadow: var(--shadow-sm); }

/* ---------------------------------------------------------------- tables */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.data { width: 100%; border-collapse: collapse; font-size: .88rem; }
table.data th {
  text-align: left; font-size: .72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--slate);
  padding: .6rem .7rem; border-bottom: 1.5px solid var(--line);
  white-space: nowrap; background: var(--canvas-2);
  position: sticky; top: 0; z-index: 1;
}
table.data td {
  padding: .62rem .7rem; border-bottom: 1px solid var(--line-2);
  vertical-align: middle;
}
table.data tbody tr:hover { background: var(--canvas-2); }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }

.rank {
  display: inline-grid; place-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--line-2); color: var(--ink-3);
  font-size: .78rem; font-weight: 800;
}
.rank.r1 { background: var(--eff-gold); color: var(--ink); }
.rank.r2 { background: #D8D8E0; }
.rank.r3 { background: #E8C9A8; }

.bar-track { height: 7px; background: var(--line-2); border-radius: 999px; overflow: hidden; min-width: 74px; }
.bar-fill  { height: 100%; background: var(--eff-red); border-radius: 999px; transition: width .4s ease; }
.bar-fill.g { background: var(--eff-green); }
.bar-fill.y { background: var(--eff-gold); }

/* --------------------------------------------------------------- notices */
.notice {
  padding: .8rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid;
  font-size: .88rem;
  display: flex; gap: .6rem; align-items: flex-start;
}
.notice b { display: block; margin-bottom: .1rem; }
.notice.ok   { background: var(--ok-bg);   border-color: #B8E0C8; color: #0A5A2E; }
.notice.warn { background: var(--warn-bg); border-color: #F5D68A; color: #7A3E06; }
.notice.bad  { background: var(--bad-bg);  border-color: #F2B8BC; color: #8E1017; }
.notice.info { background: var(--info-bg); border-color: #BCCDF7; color: #16389E; }

.empty { text-align: center; padding: 2.4rem 1.2rem; color: var(--slate); }
.empty-icon { font-size: 2rem; opacity: .4; margin-bottom: .5rem; }

.skel {
  background: linear-gradient(90deg, var(--line-2) 25%, #F4F4F8 50%, var(--line-2) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.3s infinite;
  border-radius: 6px;
}
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

/* -------------------------------------------------------------- app shell */
.app { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w); flex: 0 0 var(--sidebar-w);
  background: var(--ink);
  color: #fff;
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
}
.sidebar-head { padding: 1.1rem 1.1rem .9rem; border-bottom: 1px solid rgba(255,255,255,.09); }
.sidebar-head .logo-text b { color: #fff; }
.sidebar-head .logo-text span { color: rgba(255,255,255,.5); }
.nav { padding: .7rem .6rem; flex: 1 1 auto; overflow-y: auto; }
.nav-label {
  font-size: .67rem; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
  color: rgba(255,255,255,.35); padding: .8rem .6rem .3rem;
}
.nav a {
  display: flex; align-items: center; gap: .65rem;
  padding: .58rem .65rem; border-radius: 9px;
  color: rgba(255,255,255,.74); font-size: .88rem; font-weight: 600;
  text-decoration: none; margin-bottom: 1px;
}
.nav a:hover { background: rgba(255,255,255,.08); color: #fff; text-decoration: none; }
.nav a.on { background: var(--eff-red); color: #fff; }
.nav a .ico { width: 17px; text-align: center; flex: 0 0 17px; opacity: .95; }
.sidebar-foot { padding: .8rem 1rem; border-top: 1px solid rgba(255,255,255,.09); font-size: .8rem; }
.sidebar-foot .who { color: #fff; font-weight: 700; }
.sidebar-foot .role { color: rgba(255,255,255,.5); font-size: .74rem; }

.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  height: var(--header-h);
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: .8rem;
  padding: 0 1.3rem;
  position: sticky; top: 0; z-index: 40;
}
.topbar h1 { font-size: 1.12rem; }
.content { padding: 1.4rem 1.4rem 3.6rem; flex: 1 1 auto; }
.content-wide { padding: 1.4rem 1.4rem 3.6rem; }

.page-head { margin-bottom: 1.25rem; }
.page-head h2 { margin-bottom: .18rem; }

.grid { display: grid; gap: 1rem; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); }
.grid-side { grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr); }
/* A grid cell otherwise grows to fit a wide table inside it, and the whole page
   scrolls sideways on a phone instead of the table scrolling in its own box. */
.grid > * { min-width: 0; }

/* Opacity only — never a transform here, or fixed children get trapped. */
.fade-in { animation: fadeIn .22s ease; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* --------------------------------------------------------------- modals */
.modal-back {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(12,12,13,.55);
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
  backdrop-filter: blur(2px);
}
.modal {
  background: var(--paper); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 100%; max-width: 540px;
  max-height: 90vh; overflow-y: auto;
}
.modal-head {
  padding: 1.1rem 1.3rem; border-bottom: 1px solid var(--line-2);
  display: flex; align-items: center; gap: .7rem;
}
.modal-head h3 { flex: 1 1 auto; }
.modal-body { padding: 1.3rem; }
.modal-foot {
  padding: .95rem 1.3rem; border-top: 1px solid var(--line-2);
  display: flex; gap: .6rem; justify-content: flex-end;
  background: var(--canvas-2); border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* --------------------------------------------------------------- toasts */
.toast-wrap {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  z-index: 400; display: flex; flex-direction: column; gap: .5rem;
  width: min(430px, calc(100vw - 24px));
}
.toast {
  padding: .8rem 1rem; border-radius: var(--radius-sm);
  background: var(--ink); color: #fff;
  font-size: .89rem; font-weight: 600;
  box-shadow: var(--shadow-lg);
  animation: toastIn .2s ease;
}
.toast.ok  { background: var(--ok); }
.toast.bad { background: var(--bad); }
.toast.warn{ background: var(--warn); }
@keyframes toastIn { from { opacity: 0; } to { opacity: 1; } }

/* ---------------------------------------------------------- offline chip */
.net-chip {
  position: fixed; right: 14px; bottom: 14px; z-index: 300;
  /* It floats over the page and it is not a control. Without this it swallows
     the tap meant for whatever is underneath — on the capture form that was
     the "already registered" option, which sits exactly there. */
  pointer-events: none;
  display: flex; align-items: center; gap: .45rem;
  padding: .45rem .8rem; border-radius: 999px;
  font-size: .8rem; font-weight: 700;
  background: var(--ink); color: #fff;
  box-shadow: var(--shadow);
}
.net-chip.off { background: var(--warn); }
.net-chip.sync { background: var(--info); }
/* Online is ambient reassurance, not news. At full strength it sat on top of
   whatever card happened to be at the bottom of the page, so when there is
   nothing wrong it shrinks and steps back; anything the reader needs to act
   on — offline, syncing — comes forward at full size. */
.net-chip.idle {
  background: rgba(12,12,13,.55); font-size: .74rem;
  padding: .3rem .6rem; box-shadow: var(--shadow-sm);
  transition: background .18s ease;
}


/* =========================================================== PUBLIC SITE */
.pub-nav {
  background: var(--ink); color: #fff;
  position: sticky; top: 0; z-index: 50;
}
.pub-nav-in {
  max-width: 1220px; margin: 0 auto; padding: .8rem 1.3rem;
  display: flex; align-items: center; gap: 1.2rem;
}
.pub-nav a { color: rgba(255,255,255,.78); font-weight: 600; font-size: .9rem; }
/* A name, a link or a button label that wraps in a navigation bar reads as a
   fault. They stay on one line and the bar wraps around them instead.
   Only the bar's own children: the search results hang inside this nav, and
   a voting station called "NORTHCREST ASSEMBLY OF GOD" has to be free to
   wrap onto a second line rather than run off the edge of the panel. */
.pub-nav-in { flex-wrap: wrap; }
.pub-nav-in > a, .pub-nav-in > .btn, .pub-nav-in > button,
.pub-nav .logo-text b, .pub-nav .logo-text span {
  white-space: nowrap;
}
.ward-search-results a { white-space: normal; }
.pub-nav .logo-lockup { flex: 0 0 auto; }
.ward-search input { flex: 0 1 auto; max-width: 100%; }
.pub-nav a:hover, .pub-nav a.on { color: #fff; text-decoration: none; }
.pub-nav .logo-text b { color: #fff; }
.pub-nav .logo-text span { color: rgba(255,255,255,.5); }

.hero {
  background:
    radial-gradient(1100px 460px at 78% -8%, rgba(255,213,0,.20), transparent 62%),
    linear-gradient(150deg, var(--eff-red) 0%, var(--eff-red-dark) 58%, var(--eff-red-deep) 100%);
  color: #fff;
  padding: 2.6rem 1.3rem 2.8rem;
}
/* Fixed columns, centred as a pair: on a wide monitor a fluid split pushes the
   words and the poster to opposite edges with a field of empty red between. */
.hero-in {
  max-width: 1220px; margin: 0 auto;
  display: grid; gap: 2.6rem; justify-content: center;
  grid-template-columns: minmax(0, 620px) minmax(0, 320px);
  align-items: center;
}
.hero-stats { display: flex; flex-wrap: wrap; margin-top: 1.5rem; }
.hero-stats div { padding-right: 1.5rem; }
.hero-stats div + div { border-left: 1px solid rgba(255,255,255,.22); padding-left: 1.5rem; }
.hero-stats b { display: block; font-size: 1.7rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.hero-stats b.gold { color: var(--eff-gold); }
.hero-stats span { font-size: .74rem; opacity: .82; }
.hero h1 {
  font-size: clamp(2.1rem, 5.2vw, 3.5rem);
  line-height: 1.04; letter-spacing: -.035em;
  margin-bottom: .9rem;
}
.hero h1 .gold { color: var(--eff-gold); display: block; }
.hero p.lede { font-size: 1.08rem; opacity: .93; max-width: 54ch; margin-bottom: 1.5rem; }
.hero-eyebrow {
  display: inline-flex; align-items: center; gap: .45rem;
  background: rgba(0,0,0,.26);
  border: 1px solid rgba(255,255,255,.2);
  padding: .35rem .8rem; border-radius: 999px;
  font-size: .78rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .07em;
  margin-bottom: 1.1rem;
}

.poster-frame {
  background: var(--eff-red);
  border: 5px solid var(--eff-gold);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  max-width: 320px; margin-inline: auto;
  aspect-ratio: 3 / 4;
  display: grid; place-items: center;
  position: relative;
}
.poster-frame img { width: 100%; height: 100%; object-fit: contain; display: block; }
.poster-fallback { padding: 1.6rem; text-align: center; color: #fff; }
.poster-fallback .name {
  font-size: clamp(1.5rem, 3.4vw, 2.2rem); font-weight: 900;
  color: var(--eff-gold); letter-spacing: -.02em; line-height: 1.05;
}
.poster-fallback .role {
  font-size: .82rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .1em; margin-top: .5rem; opacity: .92;
}

.section { padding: 3rem 1.3rem; }
.section-in { max-width: 1220px; margin: 0 auto; }
.section.alt { background: var(--paper); border-block: 1px solid var(--line); }
.section-head { max-width: 62ch; margin-bottom: 1.8rem; }
.section-head .kicker {
  color: var(--eff-red); font-size: .78rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .09em; margin-bottom: .35rem;
}
.section-head h2 { font-size: clamp(1.6rem, 3.4vw, 2.15rem); margin-bottom: .5rem; }

.pillar {
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.3rem;
  box-shadow: var(--shadow-sm);
}
/* The step name is a red pill, where a red strip used to run along the top. */
.pillar .n {
  display: inline-block; font-size: .7rem; font-weight: 800;
  color: #fff; background: var(--eff-red); border-radius: 999px; padding: .22rem .65rem;
  text-transform: uppercase; letter-spacing: .08em; margin-bottom: .7rem;
}
.pillar h3 { margin-bottom: .45rem; }
.pillar p { font-size: .92rem; color: var(--slate); margin: 0; }
.pillar p a { overflow-wrap: anywhere; font-weight: 600; }

/* Between the hero and the reporting story: the one thing a resident wants in
   an election year, on its own line. */
.ward-band { background: var(--ink); color: #fff; padding: 1rem 1.3rem; }
.ward-band-in {
  max-width: 1220px; margin: 0 auto;
  display: flex; align-items: center; gap: .8rem 1.6rem; flex-wrap: wrap;
}
.ward-band-text { flex: 1 1 340px; min-width: 0; }
.ward-band-text b { display: block; font-size: 1.05rem; }
.ward-band-text span { font-size: .88rem; color: rgba(255,255,255,.72); }

/* A campaign member reading the public site keeps a way back to their own
   screen, so nobody has to sign out to look at Thetha Mhlali. */
.pub-nav .back-to-app {
  display: inline-flex; align-items: center; gap: .35rem;
  border: 1px solid rgba(255,255,255,.38); border-radius: 999px;
  padding: .28rem .75rem; font-size: .8rem; color: #fff; white-space: nowrap;
}
.pub-nav .back-to-app:hover { background: rgba(255,255,255,.12); }
.pub-nav .nav-home { display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap; }
/* The outline twin of the gold call to action, for a page that is itself the
   call to action and would otherwise leave the top bar with nothing in it. */
.pub-nav .nav-alt {
  background: transparent; color: #fff;
  border-color: rgba(255,255,255,.45); white-space: nowrap;
}
.pub-nav .nav-alt:hover { background: rgba(255,255,255,.12); color: #fff; }
.pub-nav .sign-out-btn {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.38); background: transparent; color: #fff;
  display: inline-grid; place-items: center; font-size: .95rem; cursor: pointer;
}
.pub-nav .sign-out-btn:hover { background: rgba(255,255,255,.12); }
.pub-nav .sign-out-btn:disabled { opacity: .5; }
.pub-nav .sign-out-btn svg { display: block; }
/* Icons we draw ourselves, because the symbols they replace have no glyph in
   the fonts Windows ships and rendered as an empty rectangle. */
.ico-svg { display: block; }
.nav .ico .ico-svg { margin: 0 auto; }
.only-sm { display: none; }

.mayor-photo {
  flex: 0 0 150px; width: 150px; aspect-ratio: 3 / 4;
  border-radius: var(--radius-sm); overflow: hidden;
  background: var(--eff-red); color: #fff;
  display: grid; place-items: center; text-align: center; padding: .5rem;
}
.mayor-photo img { width: 100%; height: 100%; object-fit: contain; display: block; }

.cta-band {
  background: var(--ink); color: #fff;
  padding: 2.6rem 1.3rem;
}
.cta-band-in {
  max-width: 1220px; margin: 0 auto;
  display: flex; align-items: center; gap: 1.6rem; flex-wrap: wrap;
}
.cta-band h2 { font-size: clamp(1.4rem, 3vw, 2rem); margin-bottom: .35rem; }
.cta-band p { color: rgba(255,255,255,.72); margin: 0; }

.pub-foot {
  background: var(--ink-2); color: rgba(255,255,255,.6);
  padding: 2.2rem 1.3rem 2.6rem; font-size: .85rem;
}
.pub-foot-in { max-width: 1220px; margin: 0 auto; }
.pub-foot a { color: rgba(255,255,255,.85); }

/* ------------------------------------------------------------ issue cards */
/* Each report must read as its own card: a card's green update strip used to
   run straight into the next card's photo, on a background barely darker than
   the card (and on the home page, the same white). Wider gaps, a firmer edge
   and a real shadow keep them apart. */
.issue-grid { gap: 2rem 1.5rem; }
/* White cards need a grey floor. The home page's "From the wall" band is a
   white .section.alt, which is how the cards melted into each other there. */
.section.alt:has(.issue-grid) { background: var(--canvas); }
.issue-card {
  background: var(--paper); border: 1px solid #D0D0DA;
  border-radius: var(--radius); overflow: hidden;
  box-shadow: var(--shadow);
  display: flex; flex-direction: column;
}

.issue-photo { aspect-ratio: 16/10; background: var(--line-2); overflow: hidden; }
.issue-photo { position: relative; }
.issue-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.issue-photo-count {
  position: absolute; right: .5rem; bottom: .5rem; background: rgba(0,0,0,.65); color: #fff;
  font-size: .74rem; font-weight: 700; padding: .15rem .5rem; border-radius: 999px;
}
.issue-body { padding: 1rem 1.1rem; flex: 1 1 auto; }
.issue-meta { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .55rem; }
.issue-body h3 { font-size: 1.05rem; line-height: 1.3; margin-bottom: .4rem; overflow-wrap: anywhere; }
/* The card is a teaser; the full text is one tap away ("Read the full
   report"). Five lines keeps a long report from filling a whole phone screen. */
.issue-body p {
  font-size: .9rem; color: var(--slate); margin: 0; line-height: 1.5;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 5; line-clamp: 5;
  overflow: hidden;
}
/* The team's update sits INSIDE the card as a rounded note, with white card
   below it. Full-bleed at the bottom, its green band read as the start of the
   next card. No side strip: the tint, the rounded box and its heading mark it. */
.issue-update {
  margin: 0 1.1rem 1.1rem;
  background: var(--ok-bg);
  border: 1px solid #BFE3CD;
  border-radius: 12px;
  padding: .75rem .9rem; font-size: .87rem; line-height: 1.45;
}
.issue-update b { color: var(--ok); display: block; font-size: .72rem;
  text-transform: uppercase; letter-spacing: .05em; margin-bottom: .25rem; }
.issue-update > span {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3;
  overflow: hidden;
}

.issue-update b::before {
  content: ''; display: inline-block; width: 14px; height: 14px; margin-right: .35rem; vertical-align: -2px;
  background: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230F7B3F' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'/%3E%3C/svg%3E");
}
.issue-photo-count { display: inline-flex; align-items: center; gap: .25rem; }
.issue-photo-count .gl { width: 13px; height: 13px; }
.empty-icon .gl { width: 34px; height: 34px; }

/* The whole card opens its issue (assets/js/issue-detail.js). */
.issue-card.is-link { cursor: pointer; transition: box-shadow .15s, border-color .15s; }
.issue-card.is-link:hover { border-color: var(--slate-2); box-shadow: var(--shadow-lg); }
.issue-card.is-link:focus-visible { outline: 3px solid var(--eff-gold); outline-offset: 2px; }
.issue-more { font-size: .78rem; font-weight: 700; color: var(--eff-red); margin-top: .6rem; }
.issue-card.is-link:hover .issue-more { text-decoration: underline; }

/* ...and what it opens into. pre-line keeps the paragraphs people typed. */
.issue-full { white-space: pre-line; font-size: .95rem; line-height: 1.55; margin: 0 0 .6rem; }
.issue-full-photo { display: block; width: 100%; max-height: 70vh; object-fit: contain;
                    background: var(--line-2); border-radius: 10px; margin: .4rem 0 .2rem; }
.issue-photo-strip { display: flex; gap: .4rem; flex-wrap: wrap; margin: .5rem 0 .2rem; }
.issue-photo-strip button {
  width: 64px; height: 64px; padding: 0; border: 2px solid transparent; border-radius: 8px;
  overflow: hidden; background: var(--line-2); cursor: pointer; opacity: .7;
}
.issue-photo-strip button.on { border-color: var(--eff-red); opacity: 1; }
.issue-photo-strip img { width: 100%; height: 100%; object-fit: cover; display: block; }

.timeline { position: relative; padding-left: 1.4rem; }
.timeline::before {
  content: ''; position: absolute; left: 5px; top: 6px; bottom: 6px;
  width: 2px; background: var(--line);
}
.tl-item { position: relative; padding-bottom: 1.1rem; }
.tl-item::before {
  content: ''; position: absolute; left: -1.4rem; top: 5px;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--eff-red); border: 2.5px solid var(--paper);
  box-shadow: 0 0 0 1.5px var(--eff-red);
}
.tl-item.done::before { background: var(--ok); box-shadow: 0 0 0 1.5px var(--ok); }
.tl-when { font-size: .74rem; color: var(--slate-2); font-weight: 700; }
.tl-body { font-size: .89rem; margin-top: .15rem; }
.tl-who  { font-size: .76rem; color: var(--slate); font-weight: 600; margin-top: .2rem; }

/* ---------------------------------------------------------------- charts */
.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart-legend { display: flex; gap: .9rem; flex-wrap: wrap; font-size: .8rem; margin-top: .6rem; }
.chart-legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; margin-right: .3rem; }

#map { height: 460px; border-radius: var(--radius); overflow: hidden; z-index: 1; }

/* -------------------------------------------------------------- printing */
/* Campaign screens are never printed as a web page — every report there is a
   proper PDF. If someone uses the browser's own Print, the page says where the
   PDF buttons are instead of printing screen layout. */
@media print {
  body:has(.app) > *, body:has(.cap-head) > * { display: none !important; }
  body:has(.app)::before, body:has(.cap-head)::before {
    content: "This screen is not printed. For a proper printable report use a Download PDF button: in the campaign console, or under My stats.";
    display: block; margin: 30mm 20mm;
    font: 600 14pt/1.5 Arial, Helvetica, sans-serif; color: #000;
  }
  .sidebar, .topbar, .btn, .net-chip, .toast-wrap, .no-print { display: none !important; }
  body, .content { background: #fff; padding: 0; }
  .card { box-shadow: none; border-color: #999; break-inside: avoid; }
}

/* ================================================== RESPONSIVE (last) === */
@media (max-width: 1080px) {
  .grid-side { grid-template-columns: 1fr; }
  /* The poster keeps its place beside the words down to a narrow tablet — it
     only has to get smaller. Dropping it underneath at 1080px left a field of
     empty red to the right of the paragraph on every laptop and tablet. */
  .hero-in { grid-template-columns: minmax(0, 1fr) minmax(0, 260px); gap: 1.8rem; }
  .poster-frame { max-width: 260px; }
}

@media (max-width: 760px) {
  .hero-in { grid-template-columns: 1fr; gap: 1.6rem; justify-items: center; }
  .hero-in > :first-child { justify-self: stretch; }
  .poster-frame { max-width: 300px; }
}

@media (max-width: 820px) {
  .app { flex-direction: column; }
  .sidebar {
    width: 100%; flex: none; height: auto; position: sticky; top: 0; z-index: 60;
    flex-direction: row; align-items: center; overflow-x: auto;
    padding: .4rem .6rem;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .sidebar::-webkit-scrollbar { display: none; }
  .sidebar-head, .sidebar-foot { display: none; }
  /* Twelve sections do not fit across a phone. Edge glows that appear only
     when there is something past the edge say so, and fade out at either end.
     The strip that actually scrolls is .nav, not the bar around it. */
  .nav {
    overflow-x: auto; scrollbar-width: none;
    background-image:
      linear-gradient(to right, var(--ink), rgba(12,12,13,0)),
      linear-gradient(to left,  var(--ink), rgba(12,12,13,0)),
      linear-gradient(to right, rgba(255,255,255,.30), rgba(255,255,255,0)),
      linear-gradient(to left,  rgba(255,255,255,.30), rgba(255,255,255,0));
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 22px 100%, 22px 100%, 13px 100%, 13px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav { display: flex; gap: .3rem; padding: .2rem; flex: 1 1 auto; }
  .nav-label { display: none; }
  .nav a { white-space: nowrap; padding: .5rem .8rem; font-size: .82rem; }
  .content, .content-wide { padding: 1rem .85rem; }
  .topbar { padding: 0 .85rem; }
  .topbar h1 { font-size: 1rem; }
  h1 { font-size: 1.5rem; }
  .stat-value { font-size: 1.55rem; }
  .modal-foot { flex-direction: column-reverse; }
  .modal-foot .btn { width: 100%; }
  #map { height: 340px; }

  /* Card headers carry filters and buttons on the admin screens. Let them
     stack instead of squeezing every control onto one unreadable line. */
  .card-head {
    flex-wrap: wrap;
    gap: .5rem;
    padding: .85rem .95rem;
  }
  .card-head h3 { flex: 1 1 100%; }
  .card-head .spacer { display: none; }
  .card-head .select,
  .card-head .input { flex: 1 1 auto; min-width: 0; width: auto; }
  .card-head .btn { flex: 0 0 auto; }

  .range-bar { gap: .45rem; padding: .7rem; }
  .range-bar .seg { width: 100%; justify-content: space-between; }
  .range-bar .seg button { flex: 1 1 0; padding: .45rem .3rem; }
  .range-bar .input[type="date"] { flex: 1 1 40%; }

  .seg { flex-wrap: wrap; }

  /* A table that scrolls sideways should look like it scrolls sideways. */
  .table-wrap {
    position: relative;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }
  .table-wrap::after {
    content: '';
    position: sticky; right: 0; top: 0;
    float: right;
    width: 26px; height: 100%;
    margin-left: -26px;
    background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.92));
    pointer-events: none;
  }
  table.data th, table.data td { padding: .55rem .55rem; }
  table.data th { font-size: .66rem; }
}

@media (max-width: 560px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat { padding: .8rem .85rem; }
  .stat-value { font-size: 1.4rem; }
  .section { padding: 2.2rem 1rem; }
  .hero { padding: 2.4rem 1rem 2.6rem; }
  .card-pad, .card-body, .card-head { padding-inline: .95rem; }
  .pub-nav-in { gap: .6rem; padding: .7rem 1rem; }
  .pub-nav .hide-sm { display: none; }
  .only-sm { display: inline; }
  /* A campaign member's way back into the console is a gear on a phone. The
     word beside it cost the search 60px, and the search is what the public
     came for. The link keeps its name for screen readers. */
  .pub-nav .back-to-app {
    width: 32px; height: 32px; padding: 0; justify-content: center; gap: 0;
  }
  .pub-nav .back-to-app .only-sm { display: none; }

  /* Thumb-friendly targets. Anything a canvasser taps at a gate in the rain
     needs to be at least 44px, and inputs must be 16px or iOS zooms the page
     on focus and leaves it zoomed. */
  .btn { min-height: 42px; }
  .btn-sm { min-height: 34px; }
  .input, .select, .textarea { font-size: 16px; }
  .check { padding: .8rem; }
  .check input { width: 22px; height: 22px; flex-basis: 22px; }

  .modal { max-height: 94vh; border-radius: 16px; }
  .modal-body { padding: 1rem; }
  .modal-head, .modal-foot { padding-inline: 1rem; }

  /* Stacked filter rows read better than a squeezed single line. */
  .card-head .select, .card-head .input { flex: 1 1 100%; }
  .filters { padding: .7rem; }
  .filters .select { flex: 1 1 100%; width: 100%; }

  .cta-band-in { flex-direction: column; align-items: stretch; }
  /* Stacked, a 340px flex-basis became 340px of empty height. */
  .cta-band-in > .cta-text { flex: 0 0 auto; }
  .cta-band .btn { width: 100%; }

  .hero .row-wrap .btn { width: 100%; }
  .hero-eyebrow { font-size: .7rem; }

  .grid-2, .grid-3 { grid-template-columns: 1fr; }
  .chart-legend { gap: .5rem; font-size: .74rem; }

  /* Issue cards on a phone: one per row, clearly apart, and every tap target
     big enough for a thumb. */
  .issue-grid { gap: 1.6rem; }
  .issue-card { border-radius: 16px; }
  .issue-photo { aspect-ratio: 16/9; }
  .issue-body { padding: .95rem 1rem .9rem; }
  .issue-body h3 { font-size: 1.08rem; }
  .issue-meta .badge { font-size: .76rem; padding: .22rem .6rem; }
  .issue-more {
    display: flex; align-items: center; min-height: 44px; margin: .3rem 0 -.4rem;
    font-size: .9rem;
  }
  .issue-update { margin: 0 1rem 1rem; }
  .issue-update .btn { width: 100%; }
}

/* Very narrow phones — the older Androids many canvassers will be carrying. */
@media (max-width: 380px) {
  .stat-grid { grid-template-columns: 1fr 1fr; gap: .5rem; }
  .stat-label { font-size: .64rem; }
  .stat-value { font-size: 1.25rem; }
  .today-cell .v { font-size: 1.2rem; }
  h1 { font-size: 1.3rem; }
  h2 { font-size: 1.15rem; }
  .btn-lg { padding: .75rem 1rem; font-size: .94rem; }
  .content, .content-wide { padding: .8rem .6rem; }
  .card-pad, .card-body, .card-head { padding-inline: .8rem; }
}

/* ============================================================ WARD MAP === */
.ward-label {
  font: 800 11px/16px var(--font);
  color: var(--ink);
  text-align: center;
  text-shadow: 0 0 3px #fff, 0 0 3px #fff, 0 0 2px #fff;
  pointer-events: none;
  white-space: nowrap;
}
.ward-legend {
  background: rgba(255,255,255,.94);
  border-radius: 8px;
  padding: .45rem .6rem;
  font: 600 11px/1.5 var(--font);
  color: var(--ink-3);
  box-shadow: 0 1px 4px rgba(0,0,0,.2);
}
.ward-legend i {
  display: inline-block; width: 14px; height: 10px;
  margin-right: .4rem; vertical-align: -1px; border-radius: 2px;
}
.leaflet-container { font-family: var(--font); }
@media (max-width: 560px) {
  .ward-legend { font-size: 10px; padding: .35rem .45rem; }
  .ward-label { font-size: 10px; }
}

/* ========================================================= WARD SEARCH === */
.ward-search { position: relative; min-width: 0; }
.ward-search input {
  width: 220px; padding: .45rem .8rem .45rem 2.05rem;
  border-radius: 999px; border: 1.5px solid rgba(255,255,255,.25);
  background: rgba(255,255,255,.1) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.6' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-4-4'/%3E%3C/svg%3E") .7rem center / 14px no-repeat;
  color: #fff; font: 600 .86rem var(--font);
  -webkit-appearance: none; appearance: none;
}
.ward-search input::placeholder { color: rgba(255,255,255,.7); }
.ward-search input:focus { outline: none; background-color: rgba(255,255,255,.18); border-color: var(--eff-gold); }
.ward-search-results {
  position: absolute; right: 0; top: calc(100% + 6px); width: min(370px, 92vw);
  max-height: 60vh; overflow-y: auto;
  background: var(--paper); color: var(--ink);
  border-radius: var(--radius); box-shadow: var(--shadow-lg); z-index: 80;
}
.ward-search-results a {
  display: flex; gap: .7rem; align-items: flex-start;
  padding: .6rem .85rem; color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--line-2);
}
.ward-search-results a:last-child { border-bottom: 0; }
.ward-search-results a:hover, .ward-search-results a.on { background: var(--canvas); text-decoration: none; }
.ward-search-results .kind {
  flex: 0 0 3.6rem; margin-top: .15rem;
  font-size: .64rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--eff-red);
}
/* Real voting stations are called things like "NORTHCREST ASSEMBLY OF GOD".
   The text column is a flex item, so without min-width:0 it refuses to shrink
   below its longest word and the name runs out past the edge of the panel and
   is cut off. It wraps instead — a station is something you have to read to
   recognise, so it is not a candidate for an ellipsis. */
.ward-search-results .txt { flex: 1 1 auto; min-width: 0; }
.ward-search-results .txt b { display: block; overflow-wrap: anywhere; line-height: 1.25; }
.ward-search-results .txt .tiny { overflow-wrap: anywhere; }
.ward-search-results .empty-note { padding: .85rem .95rem; color: var(--slate); font-size: .86rem; }

/* =========================================================== WARD PAGE === */
.ward-hero {
  background:
    radial-gradient(900px 380px at 80% -10%, rgba(255,213,0,.18), transparent 60%),
    linear-gradient(150deg, var(--eff-red) 0%, var(--eff-red-dark) 58%, var(--eff-red-deep) 100%);
  color: #fff; padding: 2.2rem 1.3rem 2.4rem;
}
/* The poster is a tall thing next to a short block of text. Keep it to a
   width where the two columns end up about the same height, or the hero is
   mostly empty red. */
.ward-hero-in {
  max-width: 1220px; margin: 0 auto; display: grid; gap: 2.6rem; align-items: center;
  /* Keep the text and the poster next to each other and centre the pair. On a
     wide monitor a 1fr column would push them to opposite edges with a field
     of empty red between them. */
  grid-template-columns: minmax(0, 660px) minmax(230px, 300px);
  justify-content: center;
}
.ward-hero h1 { font-size: clamp(1.9rem, 4.6vw, 3.1rem); line-height: 1.05; letter-spacing: -.03em; margin: .35rem 0 .6rem; }
.ward-hero .areas { opacity: .92; max-width: 62ch; margin: 0; }
.ward-facts { display: flex; gap: 1.6rem; flex-wrap: wrap; margin-top: 1.2rem; }
.ward-facts b { display: block; font-size: 1.75rem; line-height: 1.05; font-variant-numeric: tabular-nums; }
.ward-facts span { font-size: .78rem; opacity: .85; }
.candidate-card {
  background: var(--eff-red); border: 5px solid var(--eff-gold);
  border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-lg);
  max-width: 300px; justify-self: end; width: 100%;
}
/* A campaign poster is a designed thing — show all of it rather than cropping
   the candidate's head to fit a fixed shape. */
.candidate-card img {
  display: block; width: 100%; height: auto; max-height: 62vh;
  object-fit: contain; background: var(--eff-red-dark);
}
.candidate-logo { width: 120px; height: 84px; margin: 0 auto 1rem; }
.ward-map { height: 380px; border-radius: var(--radius); overflow: hidden; z-index: 1; }
.candidate-fallback {
  aspect-ratio: 3 / 4; display: grid; place-items: center; text-align: center; padding: 1.4rem; color: #fff;
  background: radial-gradient(420px 300px at 50% 20%, rgba(255,213,0,.22), transparent 70%), var(--eff-red);
}
.candidate-fallback .big { font-size: 2rem; font-weight: 900; color: var(--eff-gold); letter-spacing: -.02em; }
.candidate-cap { background: #fff; color: var(--ink); padding: .65rem 1rem .75rem; text-align: center; }
.candidate-cap .role { font-size: .68rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--eff-red); }
.candidate-cap .name { font-size: 1.2rem; font-weight: 900; letter-spacing: -.01em; margin-top: .1rem; }
.candidate-cap .vote { font-size: .78rem; font-weight: 800; color: var(--eff-green); margin-top: .2rem; letter-spacing: .04em; }
.station { display: flex; gap: .9rem; padding: .95rem 0; border-bottom: 1px solid var(--line-2); }
.station:last-child { border-bottom: 0; }
.station.hl { background: #FFF7E0; margin: 0 -1.25rem; padding-left: 1.25rem; padding-right: 1.25rem; }
.station .pinno, .station-pin {
  flex: 0 0 32px; width: 32px; height: 32px; border-radius: 50%;
  background: var(--eff-red); color: #fff; display: grid; place-items: center;
  font-weight: 800; font-size: .8rem; border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
.station-pin { width: 28px; height: 28px; font-size: .74rem; }
.ward-chooser { display: grid; gap: .65rem; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.ward-chooser a {
  display: block; padding: .8rem .95rem; background: var(--paper); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); text-decoration: none;
}
.ward-chooser a:hover { border-color: var(--eff-red); text-decoration: none; box-shadow: var(--shadow-sm); }
.ward-chooser b { display: block; color: var(--eff-red); }
.ward-issue {
  display: block; padding: .7rem 0; border-bottom: 1px solid var(--line-2);
  color: var(--ink); text-decoration: none;
}
.ward-issue:last-child { border-bottom: 0; }
.ward-issue:hover { text-decoration: none; }
.ward-issue:hover .strong { color: var(--eff-red); }

/* ================================================ ROLL & COUNCILLORS === */
.coverage-cell { display: flex; align-items: center; gap: .55rem; min-width: 150px; }
.coverage-cell .bar-track { flex: 1 1 auto; }
.coverage-cell span { min-width: 3.1rem; text-align: right; font-weight: 700; font-size: .82rem; font-variant-numeric: tabular-nums; }
.councillor-card .photo {
  aspect-ratio: 3 / 4; background: var(--line-2); display: grid; place-items: center;
  overflow: hidden; border-radius: var(--radius) var(--radius) 0 0; color: var(--slate);
}
.councillor-card .photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.upload-log { max-height: 320px; overflow-y: auto; }
.progress { height: 10px; background: var(--line-2); border-radius: 999px; overflow: hidden; }
.progress > div { height: 100%; background: var(--eff-green); width: 0; transition: width .3s ease; }

@media (max-width: 860px) {
  .ward-hero-in { grid-template-columns: 1fr; }
  .candidate-card { justify-self: start; max-width: 320px; }
}
/* ---- the home page on a phone: shorter hero, the numbers on one line, and
   only the three newest reports before the "see everything" button. */
@media (max-width: 700px) {
  .hero { padding: 1.7rem 1rem 2rem; }
  .hero h1 { font-size: clamp(1.8rem, 8vw, 2.4rem); margin-bottom: .55rem; }
  .hero p.lede { font-size: .97rem; line-height: 1.5; margin-bottom: 1.1rem; }
  .hero .row-wrap .btn { flex: 1 1 100%; }
  .hero-stats { margin-top: 1.2rem; }
  .hero-stats div { flex: 1 1 0; min-width: 0; padding: 0 .55rem; text-align: center; }
  .hero-stats div:first-child { padding-left: 0; }
  .hero-stats div + div { padding-left: .55rem; }
  .hero-stats b { font-size: 1.3rem; }
  .hero-stats span { display: block; font-size: .66rem; line-height: 1.25; }
  .poster-frame { max-width: 250px; }

  .ward-band { padding: .85rem 1rem; }
  .ward-band-text b { font-size: 1rem; }
  .ward-band-text span { font-size: .84rem; }
  .ward-band .btn { flex: 1 1 100%; }

  .section { padding: 1.9rem 1rem; }
  .section-head { margin-bottom: 1.1rem; }
  .pillar { padding: 1rem; }
  .cta-band { padding: 1.7rem 1rem; }
  .cta-band .btn { flex: 1 1 100%; }
}

/* ---- The top bar in one line needs about 1010px: the lockup, the written
   links, the gold button and a search box wide enough to read its own
   placeholder. Below that it wrapped mid-word — "Report a / problem" inside
   the button, "Issue / wall", the municipality's name broken over three
   lines — so below that it becomes two deliberate rows instead. */
@media (max-width: 1010px) {
  .pub-nav-in { flex-wrap: wrap; row-gap: .35rem; padding-block: .6rem;
    /* The second row (Get the app, the ward search) is put in by scripts.
       Without room held for it the first paint showed one row, then the
       whole page jumped down ~46px: the "ugly first view" of the installed
       app. Padding + row one (34px) + two row gaps (the break item is a row
       of its own) + the search (39px). */
    min-height: calc(1.2rem + 34px + .7rem + 39px); align-content: flex-start; }

  /* Exactly two rows. Everything that is a control sits on the first; the
     search gets the whole second one to itself. It used to share that row
     with Home, the console pill and sign-out, and on the issue wall there
     was so little left that "Find your ward" was cut off mid-word. The
     zero-height item at order 19 is what forces the break. */
  .pub-nav-in::after { content: ''; order: 19; flex: 1 0 100%; height: 0; }
  /* Row one is the logo and the one thing this site is for. Everything a
     person navigates with drops to row two, where the search takes whatever
     is left over — never less than it needs to show its own placeholder. */
  .pub-nav .btn-gold, .pub-nav .nav-alt { order: 5; flex: 0 0 auto; }
  .pub-nav .nav-home {
    order: 20; flex: 0 0 auto; padding: .3rem .55rem;
    border: 1px solid rgba(255,255,255,.3); border-radius: 999px;
  }
  .pub-nav .back-to-app { order: 21; flex: 0 0 auto; padding: .3rem .6rem; font-size: .76rem; }
  .pub-nav .sign-out-btn { order: 22; width: 32px; height: 32px; }
  .ward-search { order: 23; flex: 1 1 9rem; min-width: 0; }
  .ward-search input { width: 100%; font-size: 16px; padding-block: .5rem; }
}

/* ---- on a phone. Nearly everyone meets this page on one, standing
   somewhere, wanting a single thing: where do I vote. So: two short nav rows
   with the search across the second, and an answer that starts above the
   fold. */
@media (max-width: 700px) {
  /* The panel spans the bar, not just the input. On a phone the input shares
     its row with Home and, for a campaign member, two more buttons, which
     left a 223px column for names like "NORTHCREST ASSEMBLY OF GOD
     TABERNACLE" — three lines for one station. Anchoring it to the bar
     instead of the input gives it the full width and drops it below both
     rows. */
  .pub-nav-in { position: relative; }
  .ward-search { position: static; }
  .ward-search-results {
    left: 1rem; right: 1rem; width: auto; max-height: 68vh;
    top: calc(100% + 2px);
  }
  .ward-search-results a { padding: .7rem .85rem; align-items: center; }
  .ward-search-results .kind { flex-basis: 2.9rem; font-size: .6rem; margin-top: 0; }
  .pub-nav .logo-lockup { gap: .5rem; }
  .pub-nav .logo-mark { width: 44px; height: 32px; flex-basis: 44px; border-radius: 8px; }
  .pub-nav .logo-text b { font-size: .9rem; }

  .ward-hero { padding: 1.5rem 1rem 1.7rem; }
  .ward-hero-in { gap: 1.1rem; }
  .ward-hero h1 { font-size: clamp(1.55rem, 7vw, 2.1rem); margin: .3rem 0 .4rem; }
  .ward-hero .areas { font-size: .88rem; line-height: 1.45; }
  .ward-facts { gap: .3rem .9rem; margin-top: .85rem; }
  .ward-facts div { display: flex; align-items: baseline; gap: .3rem; }
  .ward-facts b { display: inline; font-size: 1.05rem; }
  .ward-facts span { font-size: .76rem; }
  .ward-hero .row-wrap { margin-top: 1.05rem; gap: .5rem; }
  .ward-hero .row-wrap .btn { flex: 1 1 100%; }

  /* The poster is why many people open this page, so it gets the full width
     of the phone. The "to be announced" placeholder has nothing to show, so
     it turns sideways instead of eating a screenful. */
  .candidate-card { max-width: 100%; border-width: 4px; }
  .candidate-card.has-photo img { max-height: 58vh; }
  .candidate-card.has-photo .candidate-cap { padding: .55rem .8rem .65rem; }
  .candidate-card.has-photo .candidate-cap .name { font-size: 1.05rem; }

  .candidate-card:not(.has-photo) { display: flex; }
  .candidate-card:not(.has-photo) .candidate-fallback {
    width: 40%; max-width: 132px; flex: 0 0 auto; padding: .7rem; aspect-ratio: 3 / 4;
  }
  .candidate-card:not(.has-photo) .candidate-logo { width: 62px; height: 44px; margin-bottom: .4rem; }
  .candidate-card:not(.has-photo) .candidate-fallback .big { font-size: 1.15rem; }
  .candidate-card:not(.has-photo) .candidate-cap {
    flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center;
    text-align: left; padding: .7rem .85rem;
  }
  .candidate-card:not(.has-photo) .candidate-cap .name { font-size: 1.05rem; }

  .station { gap: .7rem; padding: .7rem 0; }
  .station .pinno { flex: 0 0 26px; width: 26px; height: 26px; font-size: .72rem; }
  .station.hl { margin-inline: -.95rem; padding-inline: .95rem; }
  .ward-map { height: 240px; }

  .ward-chooser { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
  .ward-chooser a { padding: .6rem .7rem; }
  .ward-chooser .tiny { display: none; }
}

/* ===================================================== ROLL AT THE DOOR === */
.roll-box { margin-top: .7rem; }
.roll-box .notice { margin-top: .3rem; }
.roll-match {
  display: flex; gap: .7rem; align-items: flex-start; width: 100%; text-align: left;
  padding: .7rem .8rem; margin-top: .45rem;
  border: 1.5px solid var(--line); border-radius: var(--radius-sm);
  background: var(--paper); font: inherit; color: inherit; cursor: pointer;
}
.roll-match:hover { border-color: var(--slate-2); }
.roll-match.on { border-color: var(--eff-green); background: var(--ok-bg); box-shadow: 0 0 0 3px rgba(4,106,56,.12); }
.roll-match[disabled] { opacity: .6; cursor: not-allowed; }
.roll-match .tick {
  flex: 0 0 22px; height: 22px; margin-top: .1rem; border-radius: 50%;
  border: 2px solid var(--line); display: grid; place-items: center; font-size: .8rem; color: #fff;
}
.roll-match.on .tick { background: var(--eff-green); border-color: var(--eff-green); }
.roll-match b { display: block; font-size: .95rem; }
.roll-match .sub { display: block; font-size: .78rem; color: var(--slate); margin-top: .1rem; }

/* ===================================================== KEYBOARD & MOTION ===
   Inputs already show where they are. Buttons, links and the tiles people tab
   through did not, so somebody driving the console from a keyboard — or using
   a phone's switch access — had to guess where they were. :focus-visible
   leaves mouse users alone and draws the ring only for keyboard and
   assistive navigation.
   ========================================================================= */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
.roll-match:focus-visible,
.cat-tile:focus-visible {
  outline: 3px solid var(--eff-gold);
  outline-offset: 2px;
}
/* Gold reads on the black bars and on red. On a white card it needs a dark
   edge behind it or it washes out. */
.card a:focus-visible, .card button:focus-visible, .card [tabindex]:focus-visible,
.section a:focus-visible, .section button:focus-visible,
.roll-match:focus-visible, .cat-tile:focus-visible {
  outline-color: var(--ink);
  box-shadow: 0 0 0 5px rgba(255,213,0,.6);
}
.ward-search input:focus-visible { outline: 2px solid var(--eff-gold); outline-offset: 1px; }

/* An anchored section must not land underneath a sticky bar. */
:target,
h1[id], h2[id], h3[id], section[id] {
  scroll-margin-top: calc(var(--header-h) + 12px);
}

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

/* ============================================================== DENSITY ===
   These screens are read standing up, on a phone, between houses. Air between
   sections is not calm here, it is scrolling. The trims keep the rhythm of the
   design and take a screenful out of every long page.
   ========================================================================= */
@media (min-width: 821px) {
  .section { padding: 2.4rem 1.3rem; }
  .section-head { margin-bottom: 1.35rem; }
  .content, .content-wide { padding: 1.15rem 1.3rem 3rem; }
  .card-body, .card-pad { padding: 1rem 1.15rem; }
  .card-head { padding: .8rem 1.15rem; }
  .grid { gap: .85rem; }
}

/* Cards in a row are stretched to the tallest of them. Without this the
   shorter one keeps its content at the top and leaves a band of empty white
   underneath — most visibly under the registrations chart. */
.grid > .card { display: flex; flex-direction: column; }
.grid > .card > .card-body { flex: 1 1 auto; }
.chart-fill { display: flex; flex-direction: column; justify-content: center; min-height: 100%; }
.chart-fill > .chart { width: 100%; height: auto; }

/* ---------------------------------------------------------- footer links
   On a phone the top bar can only carry the search and one button, so the
   written links — the manifesto, the issue wall, your ward — live down here.
   Every public page carries the same set, because "Report a problem" was a
   page you could reach and then not leave. */
.pub-foot-nav {
  display: flex; flex-wrap: wrap; gap: .45rem 1.4rem;
  padding-top: 1.1rem; margin-top: 1.1rem;
  border-top: 1px solid rgba(255,255,255,.14);
}
.pub-foot-nav a { font-weight: 600; }
.pub-foot-nav a[aria-current="page"] { color: var(--eff-gold); }
.pub-foot-nav .spacer { flex: 1 1 auto; }
@media (max-width: 560px) {
  .pub-foot { padding: 1.6rem 1rem 2rem; }
  .pub-foot-nav { gap: .5rem 1.1rem; }
}

/* Nothing to report, nothing on the screen. The console asks for this; the
   capture form does not, because a canvasser at a gate wants to see that the
   registration is going through. */
.net-chip.quiet.idle { display: none; }

/* Six cards in a grid that fits four across leaves two empty cells staring
   back at the reader. Three across means the six published reports on the
   home page always fill their rows exactly. */
@media (min-width: 900px) {
  .grid-fill-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* Five manifesto pillars in three columns leave one cell empty on the
     second row. The last one widens to close it, which reads as a deliberate
     shape rather than a card that failed to load. */
  .grid-fill-3 > :last-child:nth-child(3n + 2) { grid-column: span 2; }
}

/* ============================================== NUMBERS, NOT PICTURES ===
   The overview used to open with a line chart, a doughnut and a bar chart.
   A ward coordinator reading it wants to know one thing — how many voters
   are left to reach and where — so it says that in figures instead.
   ========================================================================= */
.ov-split {
  display: grid; gap: .9rem;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.ov-split .stat-value { font-size: 1.75rem; }
.stat-value.ok, .num.ok, td.ok { color: var(--ok); }

/* One bar, two numbers: signed up against everybody still on the roll. */
.split-bar {
  height: 12px; border-radius: 999px; overflow: hidden;
  background: var(--line); display: flex;
}
.split-eff { background: var(--eff-green); height: 100%; }


/* ------------------------------------------------------- password reveal
   The eye sits inside the field rather than beside it, so the field keeps
   its full width on a phone. No transform: this is a positioned box and a
   transform would make it a containing block for anything fixed inside. */
.pw-field { position: relative; display: block; }
.pw-field .input { padding-right: 3rem; width: 100%; }
.pw-eye {
  position: absolute; right: .3rem; top: .3rem; bottom: .3rem;
  width: 2.4rem; padding: 0;
  border: 0; background: transparent; color: var(--slate);
  display: grid; place-items: center; cursor: pointer;
  border-radius: var(--radius-sm);
}
.pw-eye:hover { color: var(--ink); background: var(--line-2); }
.pw-eye[aria-pressed="true"] { color: var(--eff-red); }
.pw-eye svg { display: block; }


/* ----------------------------------------------------- the candidate's reel
   A band of the newest clips that runs on the front page, the issue wall and
   the ward pages. What turns over is the poster frame; no video is fetched
   until somebody presses play, and the button says what pressing it costs.

   Dark band on purpose: it reads as a break in the page rather than another
   card, and a poster frame sits better on black than on paper. */
.creel { background: var(--ink); color: #fff; }
.creel-in { max-width: 1220px; margin: 0 auto; padding: 1.6rem 1.15rem;
            display: grid; gap: 1.4rem; align-items: center;
            grid-template-columns: minmax(0, 1fr) minmax(0, 520px); }
.creel-side { min-width: 0; }
.creel-kicker { font-size: .72rem; font-weight: 800; letter-spacing: .12em;
                text-transform: uppercase; color: var(--eff-gold); margin-bottom: .5rem; }
.creel-date { font-size: .76rem; color: rgba(255,255,255,.6); margin-bottom: .15rem; }
.creel-title { font-size: 1.35rem; font-weight: 800;
               line-height: 1.2; margin-bottom: .3rem; }
.creel-cost { font-size: .78rem; font-weight: 700; color: rgba(255,255,255,.72); }
.creel-foot { display: flex; flex-wrap: wrap; gap: .9rem; align-items: center;
              margin-top: 1rem; }

/* The stage holds one clip's width and the track slides inside it. */
.creel-stage { position: relative; width: 100%; aspect-ratio: 16/9;
               border-radius: 14px; overflow: hidden; background: var(--ink-2); }
.creel-track { display: flex; height: 100%; width: 100%;
               transition: transform .45s cubic-bezier(.32,.72,.34,1); }
.creel-slide { flex: 0 0 100%; position: relative; display: block; height: 100%;
               border: 0; padding: 0; margin: 0; background: var(--ink-2);
               cursor: pointer; }
.creel-poster { width: 100%; height: 100%; object-fit: cover; display: block; opacity: .92; }
.creel-slide:hover .creel-poster { opacity: 1; }
.creel-shade { position: absolute; inset: 0; pointer-events: none;
               background: linear-gradient(to top, rgba(0,0,0,.45), rgba(0,0,0,0) 45%); }
.creel-play { position: absolute; inset: 0; display: flex; align-items: center;
              justify-content: center; pointer-events: none; }
.creel-play span { width: 66px; height: 66px; border-radius: 50%; background: var(--eff-red);
                   color: #fff; display: flex; align-items: center; justify-content: center;
                   font-size: 1.5rem; padding-left: 5px;
                   box-shadow: 0 8px 26px rgba(0,0,0,.5); }

/* Back and forward. Sized for a thumb, not a mouse pointer. */
.creel-arrow { position: absolute; top: 50%; transform: translateY(-50%);
               width: 42px; height: 42px; border-radius: 50%; border: 0;
               background: rgba(0,0,0,.55); color: #fff; font-size: 1.6rem; line-height: 1;
               display: flex; align-items: center; justify-content: center;
               cursor: pointer; padding: 0 0 3px; }
.creel-arrow:hover { background: rgba(0,0,0,.78); }
.creel-prev { left: .6rem; }
.creel-next { right: .6rem; }
/* One clip has nowhere to go. */
.creel-stage.one .creel-arrow { display: none; }

.creel-count { font-size: .74rem; color: rgba(255,255,255,.5); }

/* Asked to stop things moving: the track jumps instead of sliding, and the
   strip stops turning over on its own (see candidate-reel.js). */
@media (prefers-reduced-motion: reduce) {
  .creel-track { transition: none; }
}

.creel-dots { display: flex; gap: .35rem; }
.creel-dot { width: 9px; height: 9px; padding: 0; border: 0; border-radius: 50%;
             background: rgba(255,255,255,.28); cursor: pointer; }
.creel-dot.on { background: var(--eff-gold); }

/* the player */
.creel-view { position: fixed; inset: 0; background: rgba(0,0,0,.93); z-index: 90;
              display: flex; align-items: center; justify-content: center; padding: 1.2rem; }
.creel-fig { margin: 0; display: flex; flex-direction: column; align-items: center; }
.creel-video { max-width: 100%; max-height: 78vh; border-radius: 10px; background: #000; }
.creel-fig figcaption { color: #fff; text-align: center; margin-top: .9rem; max-width: 60ch;
                        display: flex; flex-direction: column; gap: .25rem; }
.creel-fig figcaption b { font-size: 1rem; }
.creel-fig figcaption span { font-size: .8rem; color: rgba(255,255,255,.65); }
.creel-fig figcaption a { font-size: .82rem; color: var(--eff-gold); margin-top: .3rem; }
.creel-close { position: absolute; top: 1rem; right: 1.15rem; background: none; border: 0;
               color: #fff; font-size: 2rem; line-height: 1; cursor: pointer; }

/* On a phone the picture goes first: it is the thing worth scrolling to. */
@media (max-width: 760px) {
  .creel-in { grid-template-columns: 1fr; }
  .creel-side { order: 2; }
  .creel-stage { order: 1; }
  .creel-title { font-size: 1.12rem; }
}

/* Every line icon (EFF.glyph) is text-sized unless a rule says otherwise;
   without this, one in a plain link filled its whole column. */
svg.gl { width: 1.05em; height: 1.05em; flex: 0 0 auto; vertical-align: -.16em; }

/* ------------------------------------------------ get the app (install.js) */
.install-bar {
  position: fixed; left: 50%; bottom: calc(12px + env(safe-area-inset-bottom)); z-index: 90;
  transform: translate(-50%, 140%); transition: transform .25s ease;
  width: min(560px, calc(100% - 20px));
  display: flex; align-items: center; gap: .75rem;
  background: var(--ink); color: #fff; border-radius: 16px; padding: .75rem .8rem;
  box-shadow: 0 12px 40px rgba(12,12,13,.35);
}
.install-bar.show { transform: translate(-50%, 0); }
.install-bar img { width: 44px; height: 44px; border-radius: 11px; flex: 0 0 44px; }
.install-t { flex: 1 1 auto; min-width: 0; }
.install-t b { display: block; font-size: .95rem; }
.install-t span { display: block; font-size: .8rem; color: rgba(255,255,255,.72); }
.install-a { display: flex; flex-direction: column; align-items: stretch; gap: .15rem; flex: 0 0 auto; }
.install-a .btn { min-height: 40px; }
.install-x { background: none; border: 0; color: rgba(255,255,255,.75); font: inherit; font-size: .78rem;
             font-weight: 700; padding: .3rem .4rem; cursor: pointer; }
@media (prefers-reduced-motion: reduce) { .install-bar { transition: none; } }

.ga section + section { margin-top: 1.3rem; padding-top: 1.1rem; border-top: 1px solid var(--line-2); }
.ga-h { margin: 0 0 .6rem; font-size: .78rem; font-weight: 800; text-transform: uppercase;
        letter-spacing: .06em; color: var(--slate); }
.ga .gl { width: 1.05em; height: 1.05em; vertical-align: -.16em; margin-right: .2rem; }
.ga-steps { margin: 0; padding: 0; list-style: none; counter-reset: ga; }
.ga-steps li { position: relative; padding: .15rem 0 .75rem 2.3rem; font-size: .95rem; counter-increment: ga; }
.ga-steps li::before { content: counter(ga); position: absolute; left: 0; top: 0;
  width: 26px; height: 26px; border-radius: 50%; background: var(--eff-red); color: #fff;
  display: grid; place-items: center; font-size: .8rem; font-weight: 800; }
.ga-done { background: var(--ok-bg); color: var(--ok); border-radius: 10px; padding: .7rem .85rem; font-size: .92rem; }
.ga-share { display: grid; gap: .5rem; }
.ga-share .btn { min-height: 46px; justify-content: center; }
.ga-wa { background: #25D366; border-color: #25D366; color: #fff; }
.ga-wa:hover { background: #1EBE5A; border-color: #1EBE5A; color: #fff; }
.ga-qr { display: flex; gap: 1rem; align-items: center; }
.ga-qr img { width: 140px; height: 140px; flex: 0 0 140px; border: 1px solid var(--line); border-radius: 10px;
             image-rendering: pixelated; background: #fff; }
.ga-qr b { display: block; margin-bottom: .2rem; }
@media (max-width: 420px) { .ga-qr { flex-direction: column; text-align: center; } }
.pub-foot-nav .ga-link { color: var(--eff-gold); font-weight: 800; }
.pub-foot-nav .ga-link .gl { width: 1em; height: 1em; vertical-align: -.14em; }
.pub-foot .ga-link { color: var(--eff-gold); font-weight: 800; }
.cta-actions { display: flex; gap: .6rem; flex-wrap: wrap; }
.cta-band .cta-app { background: transparent; border-color: rgba(255,255,255,.45); color: #fff; }
.cta-band .cta-app:hover { border-color: #fff; background: rgba(255,255,255,.08); color: #fff; }
@media (max-width: 560px) { .cta-actions { width: 100%; } .cta-actions .btn { width: 100%; } }

/* The way to the app (or to sharing it, once installed): in the top bar of
   every public page. On a phone it sits on the second row beside Home. */
.pub-nav .nav-app {
  display: inline-flex; align-items: center; gap: .35rem; flex: 0 0 auto;
  background: transparent; color: var(--eff-gold); border: 1.5px solid var(--eff-gold);
  border-radius: 999px; padding: .38rem .8rem; font: inherit; font-size: .85rem; font-weight: 800;
  cursor: pointer; white-space: nowrap; min-height: 34px;
}
.pub-nav .nav-app:hover { background: rgba(255,213,0,.12); }
.pub-nav .nav-app .gl { width: 16px; height: 16px; }
.pub-nav .nav-app-short { display: none; }
@media (max-width: 1010px) {
  .pub-nav .nav-app { order: 21; padding: .3rem .65rem; font-size: .8rem; min-height: 32px; }
}
@media (max-width: 700px) {
  .pub-nav .nav-app-long { display: none; }
  .pub-nav .nav-app-short { display: inline; }
}

/* ---- Recently reported: a rail that slides by itself (index.html) ------ */
.rail { position: relative; }
.rail-track {
  display: flex; gap: 1.2rem; overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  padding: .4rem .2rem 1rem; margin: 0 -.2rem;
}
.rail-track::-webkit-scrollbar { display: none; }
.rail-track > * { flex: 0 0 calc((100% - 2.4rem) / 3); scroll-snap-align: start; scroll-snap-stop: always;
                  min-width: 0; }
/* Slides are the same shape (see issueCard in index.html), so stretching
   them to the tallest costs at most a line or two. */
.rail-track { align-items: stretch; }
.rail-track .issue-card { height: 100%; }
.rail-track .issue-body h3 {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
.rail-track .issue-body p {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
.rail-track .issue-update > span {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
.rail-banner { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .55rem;
               background: #FFF4F4; color: var(--eff-red-dark); font-weight: 800; font-size: .95rem; }
.rail-banner .disc { width: 64px; height: 64px; border-radius: 50%; background: #FFE3E4; color: var(--eff-red);
                     display: grid; place-items: center; }
.rail-banner .disc .gl { width: 32px; height: 32px; }
.issue-update.is-none { background: var(--line-2); border-color: var(--line); color: var(--slate); }
.issue-update.is-none b { color: var(--slate); }
.issue-update.is-none b::before { filter: grayscale(1) opacity(.6); }
.rail-track .issue-card { display: flex; flex-direction: column; }
.rail-track .issue-body { flex: 1 1 auto; }
.rail-bar { display: flex; align-items: center; justify-content: center; gap: .8rem; margin-top: .6rem; }
.rail-btn {
  width: 42px; height: 42px; border-radius: 50%; border: 1.5px solid var(--line); background: var(--paper);
  color: var(--ink); display: grid; place-items: center; cursor: pointer; box-shadow: var(--shadow-sm);
}
.rail-btn:hover { border-color: var(--ink-3); }
.rail-btn .gl { width: 20px; height: 20px; }
.rail-dots { display: flex; gap: .4rem; flex-wrap: wrap; justify-content: center; max-width: 60%; }
.rail-dots button { width: 9px; height: 9px; border-radius: 50%; border: 0; padding: 0; cursor: pointer;
                    background: #CFCFD8; transition: background .2s, transform .2s; }
.rail-dots button.on { background: var(--eff-red); transform: scale(1.25); }
@media (max-width: 1000px) { .rail-track > * { flex-basis: calc((100% - 1.2rem) / 2); } }
@media (max-width: 640px) {
  .rail-track { gap: .8rem; }
  /* One report at a time, the full width, nothing of the next one showing. */
  .rail-track { gap: 1rem; padding-inline: 0; margin-inline: 0; }
  .rail-track > * { flex-basis: 100%; }
}
@media (prefers-reduced-motion: reduce) { .rail-track { scroll-behavior: auto; } }

/* ---- "A new version is ready" (core.js) --------------------------------- */
.update-bar {
  position: fixed; left: 50%; top: calc(10px + env(safe-area-inset-top)); z-index: 120;
  transform: translate(-50%, -160%); transition: transform .25s ease;
  width: min(520px, calc(100% - 20px));
  display: flex; align-items: center; gap: .6rem;
  background: var(--ok); color: #fff; border-radius: 14px; padding: .6rem .6rem .6rem .95rem;
  box-shadow: 0 10px 30px rgba(12,12,13,.3); font-size: .9rem;
}
.update-bar.show { transform: translate(-50%, 0); }
.update-t { flex: 1 1 auto; cursor: pointer; line-height: 1.3; }
.update-go { background: #fff; color: var(--ok); border: 0; border-radius: 999px; font: inherit;
             font-weight: 800; padding: .45rem .95rem; cursor: pointer; min-height: 38px; }
.update-later { background: none; border: 0; color: rgba(255,255,255,.85); font: inherit; font-size: .8rem;
                font-weight: 700; padding: .4rem; cursor: pointer; }
@media (prefers-reduced-motion: reduce) { .update-bar { transition: none; } }
