/* ── The Riff List — main.css ────────────────────────────────────────────── */

/* :root holds only non-color tokens — layout, type, geometry. These never
   change between themes. Color tokens live in the three [data-theme] blocks
   below so a single attribute swap on <html> re-themes the whole app. */
:root {
  --date-col-width:   56px;
  --card-gap:         2px;
  --card-pad:         10px 14px 10px 12px;

  --type-scale:       1.12;       /* global text size multiplier — change this one value to resize all text */
  --font-display:     'Metal Mania', cursive;
  --font-gothic:      'UnifrakturMaguntia', cursive;
  --font-body:        'Inter', system-ui, sans-serif;

  --nav-height:       52px;
  /* Fixed-header content offsets — real rendered heights (calendar-header:
     ~141px w/ search field, news-feed-header: ~90px without), rounded up
     with a small buffer for cross-browser font-metric variance. */
  --calendar-header-height: 145px;
  --news-header-height:     94px;
  /* profile-header's markup (hamburger/theme-toggle/logo/subtitle, no
     search field) is identical to news-feed-header's — same value. */
  --profile-header-height:  94px;
  /* The List/The Scene tab bar (§ News/Scene tab split) — a second fixed
     strip directly below news-feed-header, own token so it doesn't grow
     --news-header-height itself (which profile-header also shares). */
  --feed-tabs-height:       44px;
}

/* ── Theme: original — the V3.3 warm-black / aged-gold / blood-red look,
   preserved verbatim as the dormant revert target. ─────────────────────── */
[data-theme="original"] {
  --bg:               #0e0d0b;
  --shell:            #0a0906;

  --gold:             #b8962e;
  --venue-text:       rgba(184, 150, 46, 0.48);
  --time-text:        rgba(184, 150, 46, 0.40);
  --attending-names:  #b8962e; /* .attending-names previously hardcoded var(--gold) — preserved verbatim */
  --hex-stroke:       rgba(184, 150, 46, 0.20);

  --parchment:        rgba(224, 220, 210, 0.85);

  --date-col-bg:      rgba(184, 150, 46, 0.03);
  --date-col-bg-att:  rgba(120, 15, 15, 0.26);
  --date-col-border-att: rgba(180, 30, 30, 0.38);

  --card-bg:          rgba(255, 255, 255, 0.022);
  --card-bg-att:      rgba(115, 12, 12, 0.34);

  /* Attending card edge/outline — no-op here so the pre-migration look
     never grows a left-edge accent bar it never had (addendum v1.1 §2.3) */
  --attend-edge-width:     0px; /* intentional no-op: attending edge is day/night-only, not original; do not remove */
  --attend-edge:           transparent;
  --attend-outline-top:    rgba(184, 150, 46, 0.09);
  --attend-outline-bottom: rgba(0, 0, 0, 0.35);

  --hatch-h:          rgba(255, 255, 255, 0.016);
  --hatch-v:          rgba(255, 255, 255, 0.010);
  --vignette:         rgba(0, 0, 0, 0.60);

  --card-border-top:    rgba(184, 150, 46, 0.09);
  --card-border-bottom: rgba(0, 0, 0, 0.35);
  --card-dash:          rgba(184, 150, 46, 0.10);

  /* News feed row separators only — same hue as --card-border-top/bottom
     (the calendar's .show-card divider) but ~20% more opaque and rendered
     at 2px, not 1px, since the feed has no left-edge date badge doing the
     row-to-row separation work the calendar gets for free. Scoped to its
     own variables rather than bumping --card-border-top/bottom directly,
     so the calendar and Bands/Venues list pages (which also read those)
     stay exactly as they are. */
  --feed-divider-top:    rgba(184, 150, 46, 0.11);
  --feed-divider-bottom: rgba(0, 0, 0, 0.42);

  --logo-glow:        rgba(184, 150, 46, 0.22);

  --nav-bg:           #0c0b09;
  --nav-border:       rgba(184, 150, 46, 0.15);
  --nav-gold:         rgba(184, 150, 46, 0.45);
  --nav-gold-active:  #b8962e;

  /* Previously-hardcoded gold-trim values, tokenized so they follow theme */
  --border-faint:        rgba(184, 150, 46, 0.12);
  --border-hairline:     rgba(184, 150, 46, 0.20);
  --border-focus:        rgba(184, 150, 46, 0.50);
  --border-focus-strong: rgba(184, 150, 46, 0.55);
  --border-chip:         rgba(184, 150, 46, 0.35);
  --border-medium:       rgba(184, 150, 46, 0.45);
  --border-icon:         rgba(184, 150, 46, 0.22);
  --border-headline:     rgba(184, 150, 46, 0.18);
  --border-success:      rgba(184, 150, 46, 0.28);
  --tint-subtle:         rgba(184, 150, 46, 0.10);
  --tint-hover:          rgba(184, 150, 46, 0.06);
  --tint-active:         rgba(184, 150, 46, 0.08);
  --badge-text:          rgba(184, 150, 46, 0.60);
  --surface-tint:        rgba(255, 255, 255, 0.04);
  --placeholder-search:  rgba(224, 220, 210, 0.28);
  --placeholder-login:   rgba(224, 220, 210, 0.22);
  --accent-on:           #0e0d0b;
  --accent-hover:        #c9a83a;
}

/* ── Theme: night — "Gunmetal". Blackened cool-steel base, chrome-white band
   names, molten-orange accent, acid-green attending. ────────────────────── */
[data-theme="night"] {
  --bg:               #14171a;
  --shell:            #101317;

  --gold:             #e0611c;
  --venue-text:       #8b959d;
  --time-text:        #79838b;
  --attending-names:  #c3cbd0;
  --hex-stroke:       rgba(224, 97, 28, 0.30);

  --parchment:        #f2f5f7;

  --date-col-bg:      rgba(224, 97, 28, 0.03);
  --date-col-bg-att:  #232f18;
  --date-col-border-att: #679b24;

  --card-bg:          rgba(255, 255, 255, 0.022);
  --card-bg-att:      #232f18;

  /* Attending card edge/outline — addendum v1.1 §2, dialed back ~40%, then
     separator dialed back a further ~10% and card body matched to date-col */
  --attend-edge-width:     5px;
  --attend-edge:           #679b24;
  --attend-outline-top:    #3c561e;
  --attend-outline-bottom: #3c561e;

  --hatch-h:          transparent;
  --hatch-v:          transparent;
  --vignette:         transparent;

  --card-border-top:    rgba(224, 97, 28, 0.14);
  --card-border-bottom: rgba(0, 0, 0, 0.35);
  --card-dash:          rgba(224, 97, 28, 0.16);

  /* News feed row separators only — see original theme's comment above. */
  --feed-divider-top:    rgba(224, 97, 28, 0.17);
  --feed-divider-bottom: rgba(0, 0, 0, 0.42);

  --logo-glow:        transparent;

  --nav-bg:           #10141a;
  --nav-border:       rgba(224, 97, 28, 0.20);
  --nav-gold:         #78828a;
  --nav-gold-active:  #e0611c;

  --border-faint:        rgba(224, 97, 28, 0.12);
  --border-hairline:     rgba(224, 97, 28, 0.20);
  --border-focus:        rgba(224, 97, 28, 0.50);
  --border-focus-strong: rgba(224, 97, 28, 0.55);
  --border-chip:         rgba(224, 97, 28, 0.45);
  --border-medium:       rgba(224, 97, 28, 0.45);
  --border-icon:         rgba(224, 97, 28, 0.30);
  --border-headline:     rgba(224, 97, 28, 0.22);
  --border-success:      rgba(224, 97, 28, 0.28);
  --tint-subtle:         rgba(224, 97, 28, 0.10);
  --tint-hover:          rgba(224, 97, 28, 0.08);
  --tint-active:         rgba(224, 97, 28, 0.10);
  --badge-text:          rgba(224, 97, 28, 0.70);
  --surface-tint:        rgba(255, 255, 255, 0.04);
  --placeholder-search:  #79838b;
  --placeholder-login:   #6c757c;
  --accent-on:           #160d06;
  --accent-hover:        #ff7a35;
}

/* ── Theme: day — "Cold-Rolled Steel". Pale brushed-silver base, etched-black
   band names, molten-orange accent, acid-green attending. Sun-legible. ──── */
[data-theme="day"] {
  --bg:               #c9cccf;
  --shell:            #b7babd;

  --gold:             #c24e12;
  --venue-text:       #5c656c;
  --time-text:        #6f767c;
  --attending-names:  #4b555d;
  --hex-stroke:       rgba(194, 78, 18, 0.30);

  --parchment:        #131519;

  --date-col-bg:      rgba(194, 78, 18, 0.03);
  --date-col-bg-att:  rgba(78, 147, 16, 0.16);
  --date-col-border-att: rgba(78, 147, 16, 0.55);

  --card-bg:          rgba(0, 0, 0, 0.018);
  --card-bg-att:      rgba(78, 147, 16, 0.20);

  /* Attending card edge/outline — same 5px separator bar as night,
     tuned to day's existing attend-green family */
  --attend-edge-width:     5px;
  --attend-edge:           #4e9310;
  --attend-outline-top:    #a9c07f;
  --attend-outline-bottom: #a9c07f;

  --hatch-h:          transparent;
  --hatch-v:          transparent;
  --vignette:         transparent;

  --card-border-top:    rgba(194, 78, 18, 0.12);
  --card-border-bottom: rgba(20, 22, 25, 0.15);
  --card-dash:          rgba(194, 78, 18, 0.14);

  /* News feed row separators only — see original theme's comment above. */
  --feed-divider-top:    rgba(194, 78, 18, 0.14);
  --feed-divider-bottom: rgba(20, 22, 25, 0.18);

  --logo-glow:        transparent;

  --nav-bg:           #c3c6c9;
  --nav-border:       rgba(194, 78, 18, 0.20);
  --nav-gold:         #7a828a;
  --nav-gold-active:  #c24e12;

  --border-faint:        rgba(194, 78, 18, 0.12);
  --border-hairline:     rgba(194, 78, 18, 0.22);
  --border-focus:        rgba(194, 78, 18, 0.55);
  --border-focus-strong: rgba(194, 78, 18, 0.60);
  --border-chip:         rgba(194, 78, 18, 0.45);
  --border-medium:       rgba(194, 78, 18, 0.45);
  --border-icon:         rgba(194, 78, 18, 0.32);
  --border-headline:     rgba(194, 78, 18, 0.22);
  --border-success:      rgba(194, 78, 18, 0.30);
  --tint-subtle:         rgba(194, 78, 18, 0.08);
  --tint-hover:          rgba(194, 78, 18, 0.07);
  --tint-active:         rgba(194, 78, 18, 0.09);
  --badge-text:          rgba(194, 78, 18, 0.75);
  --surface-tint:        rgba(0, 0, 0, 0.035);
  --placeholder-search:  #6f767c;
  --placeholder-login:   #7a8288;
  --accent-on:           #fbeee6;
  --accent-hover:        #a83f0d;
}

/* ── Reset ─────────────────────────────────────────────────────────────── */

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

body {
  background: var(--bg);
  color: var(--parchment);
  font-family: var(--font-body);
  font-size: calc(14px * var(--type-scale));
  line-height: 1.4;
  min-height: 100dvh;
}

/* ── Phone shell (desktop preview wrapper) ──────────────────────────────── */

.phone-shell {
  display: flex;
  justify-content: center;
  background: var(--shell);
  background-image:
    repeating-linear-gradient(
      90deg,
      var(--hatch-h) 0px, var(--hatch-h) 1px,
      transparent 1px, transparent 4px
    ),
    repeating-linear-gradient(
      0deg,
      var(--hatch-v) 0px, var(--hatch-v) 1px,
      transparent 1px, transparent 4px
    );
  min-height: 100dvh;
  padding: 0;
}

.screen {
  position: relative;
  width: 100%;
  max-width: 430px;
  background: var(--bg);
  background-image:
    radial-gradient(ellipse 85% 85% at 50% 50%,
      transparent 35%, var(--vignette) 100%),
    repeating-linear-gradient(
      90deg,
      var(--hatch-h) 0px, var(--hatch-h) 1px,
      transparent 1px, transparent 4px
    ),
    repeating-linear-gradient(
      0deg,
      var(--hatch-v) 0px, var(--hatch-v) 1px,
      transparent 1px, transparent 4px
    );
}

/* ── App header ─────────────────────────────────────────────────────────── */

.app-header {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 24px 16px 10px;
  border-bottom: 1px solid var(--border-faint);
}

/* News feed and calendar — shared .app-header's divider removed for these
   two pages specifically (submit/profile still use the plain .app-header
   border, untouched). Calendar's is the line below the search box, right
   above the first month/day row — same divider element as the header
   itself, not a separate one. */
.app-header.news-feed-header,
.app-header.calendar-header {
  border-bottom: none;
}

/* Anchored to the top of the viewport, same technique as .bottom-nav
   (fixed + left/right 0 + matching max-width/margin to stay centered
   within the phone-shell column, same z-index tier). Opaque background
   so the scrolling list/feed underneath never shows through.
   .profile-header and .submit-header share this fixed/sticky behavior
   (their headers should stick identically to Calendar/News) but
   deliberately stay OUT of the border-bottom:none rule above — their
   existing divider is part of current visual appearance and isn't part
   of this fix. Submit's header markup (hamburger/theme-toggle/logo/
   subtitle, no search field) is identical to Profile's/News's, so no
   new height token was needed — see --profile-header-height reuse on
   .submit-content below. */
.app-header.news-feed-header,
.app-header.calendar-header,
.app-header.profile-header,
.app-header.submit-header,
.app-header.help-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  max-width: 430px;
  margin: 0 auto;
  z-index: 15;
  background: var(--bg);
  /* iOS standalone mode (viewport-fit=cover + black-translucent status
     bar) renders content underneath the notch/status-bar area unless the
     fixed header explicitly reserves that space. env() resolves to 0 on
     browsers/devices without a notch, so max() just falls back to the
     original 24px there — no effect outside standalone iOS. */
  padding-top: max(24px, env(safe-area-inset-top));
}

/* ── Hamburger trigger ──────────────────────────────────────────────────── */

.hamburger {
  position: absolute;
  /* Absolute-positioned children measure `top` from the containing
     block's padding edge, not its content edge — the fixed header's own
     padding-top: max(24px, env(safe-area-inset-top)) fix (see
     .app-header.news-feed-header etc.) never moved this. Same max()
     pattern, same baseline-vs-inset logic, so on a notched device this
     lands at the same offset as the header's own content instead of
     drifting into the unreachable notch/status-bar area above it. */
  top: max(20px, env(safe-area-inset-top));
  left: 14px;
  width: 36px;
  height: 36px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 4px;
}

/* ── Theme toggle (sun/moon) ─────────────────────────────────────────────── */

.theme-toggle {
  position: absolute;
  /* Same fixed-header safe-area fix as .hamburger above — absolute
     positioning isn't affected by the header's own padding-top, so it
     needs the identical max() offset to stay aligned and out of the
     notch/status-bar area on notched devices in standalone mode. */
  top: max(18px, env(safe-area-inset-top));
  right: 14px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--gold);
  cursor: pointer;
  padding: 4px;
}

.theme-toggle svg {
  display: block;
}

.hamburger span {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--gold);
  opacity: 0.70;
}

.hamburger:hover span {
  opacity: 1;
}

.logo {
  font-family: var(--font-display);
  font-size: calc(34px * var(--type-scale));
  color: var(--gold);
  letter-spacing: 0.02em;
  line-height: 1;
  text-shadow: 0 0 22px var(--logo-glow), 0 2px 8px rgba(0,0,0,0.60);
  text-decoration: none;
  cursor: pointer;
}

.logo:hover {
  opacity: 0.85;
}

.subtitle {
  font-family: var(--font-body);
  font-size: calc(11px * var(--type-scale));
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--venue-text);
  margin-top: 4px;
}

/* ── Calendar search bar ────────────────────────────────────────────────── */

.search-wrap {
  position: relative;
  width: 100%;
  margin-top: 12px;
}

.search-input {
  width: 100%;
  background: var(--surface-tint);
  border: 1px solid var(--border-hairline);
  color: var(--parchment);
  font-family: var(--font-body);
  font-size: calc(14px * var(--type-scale));
  font-weight: 400;
  padding: 9px 38px 9px 12px; /* right pad reserves space under the ✕ */
  border-radius: 0;
  outline: none;
  transition: border-color 0.15s;
  -webkit-appearance: none;
}

.search-input::placeholder {
  color: var(--placeholder-search);
}

.search-input:focus {
  border-color: var(--border-focus);
}

.search-clear {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--venue-text);
  font-size: calc(15px * var(--type-scale));
  line-height: 1;
  cursor: pointer;
}

.search-clear:hover {
  color: var(--gold);
}

/* ── Calendar feed ──────────────────────────────────────────────────────── */

.calendar {
  /* Top: clears the fixed header+search area (see .calendar-header).
     Both edges add the iOS standalone-mode safe-area inset — see
     .app-header's padding-top / .bottom-nav's height comments — env()
     is 0 outside standalone iOS so this is a no-op elsewhere. */
  padding: calc(var(--calendar-header-height) + env(safe-area-inset-top)) 0 calc(var(--nav-height) + env(safe-area-inset-bottom) + 24px);
}

/* ── Archive (§ Archive — read-only mirror of the calendar for past items) ── */

/* Fixed strip anchored directly below .calendar-header, same technique as
   .feed-tabs below .news-feed-header. The archive header carries no search
   bar (unlike Calendar's), so this sits right under it with no extra gap —
   which means it must measure off --news-header-height (the shorter,
   no-search-row header height Archive's actual markup matches), not
   --calendar-header-height (§ mid-page start fix: using the taller
   Calendar-page variable here left a ~51px dead gap between the real
   header and the tab bar, which read as the archive content starting in
   the vertical middle of the page). Confirmed via real HTTP against the
   deployed process: rendered .calendar-header height was ~93px, matching
   --news-header-height (94px), not --calendar-header-height (145px). */
.archive-tabs {
  position:         fixed;
  top:              calc(var(--news-header-height) + env(safe-area-inset-top));
  left:             0;
  right:            0;
  max-width:        430px;
  margin:           0 auto;
  height:           var(--feed-tabs-height);
  display:          flex;
  background:       var(--bg);
  border-bottom:    1px solid var(--border-faint);
  z-index:          15;
}

/* Same as .calendar's own top-clearing padding, plus the tab bar's own
   height — the two .calendar containers on the archive page (one per tab
   panel) both need this, not just .calendar's plain (Calendar-page) rule.
   --news-header-height, not --calendar-header-height — see .archive-tabs
   above. */
.calendar--archive {
  padding-top:      calc(var(--news-header-height) + var(--feed-tabs-height) + env(safe-area-inset-top));
}

.empty-state {
  padding: 40px 24px;
  text-align: center;
  color: var(--venue-text);
  font-size: calc(13px * var(--type-scale));
}

/* ── Month break slab ───────────────────────────────────────────────────── */

.month-break {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
}

.month-label {
  /*font-family: var(--font-gothic);
  font-size: calc(15px * var(--type-scale)); */
  font-family: var(--font-display);   /* Metal Mania, same as the logo */
  font-size:   calc(20px * var(--type-scale));
  color: var(--gold);
  white-space: nowrap;
  flex-shrink: 0;
}

.month-rule {
  flex: 1;
  height: 1px;
  background: var(--hex-stroke);
}

/* ── Day row ────────────────────────────────────────────────────────────── */

.day-row {
  display: flex;
  align-items: stretch;
  margin-bottom: var(--card-gap);
}

/* ── Date column ────────────────────────────────────────────────────────── */

.date-col {
  width: var(--date-col-width);
  flex-shrink: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px 0 10px;
  background: var(--date-col-bg);
  border-right: 1px solid var(--hex-stroke);
}

.day-row.attending .date-col {
  background: var(--date-col-bg-att);
  border-right-color: var(--date-col-border-att);
}

/* ── Hexagon date marker ─────────────────────────────────────────────────── */

.hex-wrapper {
  position: relative;
  width: 42px;
  height: 46px;
}

.hex-svg {
  display: block;
}

.hex-svg path {
  fill: none;
  stroke: var(--hex-stroke);
  stroke-width: 1.5;
}

/* hex stroke is identical in both states — §6.4 */

.hex-text {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
}

.day-abbr {
  font-family: var(--font-body);
  font-size: calc(9px * var(--type-scale));
  font-weight: 700;
  letter-spacing: 0.10em;
  color: var(--gold);
  line-height: 1;
}

.date-num {
  font-family: var(--font-body);
  font-size: calc(17px * var(--type-scale));
  font-weight: 900;
  color: var(--parchment);
  line-height: 1;
}

/* date number is identical in both states — §8 */

/* ── Today marker — hex badge only ─────────────────────────────────────────
   Deliberately hardcodes the OPPOSITE theme's literal token values (copied
   verbatim from the [data-theme="night"]/[data-theme="day"] blocks above)
   instead of referencing var() — the whole point is to break the current
   theme's cascade for this one element, so it must not follow it. Scoped to
   .hex-wrapper only; .date-col's background/border and .rock-hands are left
   alone so the rest of the card stays normal for the active theme. */
[data-theme="night"] .hex-today .hex-svg path {
  fill: #c9cccf;                        /* day --bg */
  stroke: rgba(194, 78, 18, 0.30);      /* day --hex-stroke */
}
[data-theme="night"] .hex-today .day-abbr {
  color: #c24e12;                       /* day --gold */
}
[data-theme="night"] .hex-today .date-num {
  color: #131519;                       /* day --parchment */
}

[data-theme="day"] .hex-today .hex-svg path {
  fill: #14171a;                        /* night --bg */
  stroke: rgba(224, 97, 28, 0.30);      /* night --hex-stroke */
}
[data-theme="day"] .hex-today .day-abbr {
  color: #e0611c;                       /* night --gold */
}
[data-theme="day"] .hex-today .date-num {
  color: #f2f5f7;                       /* night --parchment */
}

.rock-hands {
  position: absolute;
  top: 58px; /* padding-top 10px + hex height 46px + 2px clearance */
  left: 50%;
  transform: translateX(-50%);
  font-size: calc(14px * var(--type-scale));
  line-height: 1;
}

/* ── Shows column ───────────────────────────────────────────────────────── */

.shows-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Single card in a day: fill shows-col so its background covers the full row.
   Without this, a short card (release, minimal show) leaves a dark gap at the
   bottom of shows-col when the date-col is taller (~66px intrinsic vs ~58px card). */
.shows-col > .show-card:only-child {
  flex: 1;
}

/* ── Show card ──────────────────────────────────────────────────────────── */

.show-card {
  position: relative;
  background: var(--card-bg);
  padding: var(--card-pad);
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-top: 1px solid var(--card-border-top);
  border-bottom: 1px solid var(--card-border-bottom);
  cursor: pointer;
}

.show-card::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 1px;
  background-image: repeating-linear-gradient(
    90deg,
    var(--card-dash) 0px, var(--card-dash) 2px,
    transparent 2px, transparent 5px
  );
}

.show-card.attending {
  background: var(--card-bg-att);
  border-left: var(--attend-edge-width) solid var(--attend-edge);
  border-top-color: var(--attend-outline-top);
  border-bottom-color: var(--attend-outline-bottom);
}

/* ── Release cards ──────────────────────────────────────────────────────── */

.release-card:hover {
  background: var(--surface-tint);
}

.band-name {
  font-family: var(--font-body);
  font-size: calc(14px * var(--type-scale));
  font-weight: 900;
  color: var(--parchment);
  line-height: 1.3;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.venue-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.venue-name {
  font-size: calc(12px * var(--type-scale));
  color: var(--gold);
  line-height: 1.3;
}

.tickets-btn {
  flex-shrink: 0;
  font-size: calc(10px * var(--type-scale));
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold);
  text-decoration: none;
  border: 1px solid var(--border-chip);
  padding: 3px 7px;
  border-radius: 2px;
  line-height: 1.3;
}

.tickets-btn:hover {
  background: var(--tint-subtle);
}

.time-row {
  display: flex;
  min-width: 0;
  overflow: hidden;
  font-size: calc(11px * var(--type-scale));
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--time-text);
  line-height: 1.3;
}

.time-support {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex-shrink: 1;
}

.time-dot,
.time-fixed {
  flex-shrink: 0;
  white-space: nowrap;
}

.attending-names {
  font-size: calc(11px * var(--type-scale));
  color: var(--attending-names);
  line-height: 1.4;
}

/* ── Show Popup ─────────────────────────────────────────────────────────── */

/* Token set — scoped to popup; ALWAYS Cold-Rolled Steel, invariant across
   [data-theme]. The popup is a deliberate "break" surface — never bind
   these to the app-wide theme tokens (addendum v1.1 §1). */
.show-popup {
  /* CSS custom properties scoped here so children inherit them */
  --pop-surface:    #e2e4e6;
  --pop-surface-2:  #d3d6d8;
  --pop-ink:        #131519;
  --pop-secondary:  #4b555d;
  --pop-muted:      #5c656c;
  --pop-accent:     #c24e12;
  --pop-accent-on:  #fbeee6;
  --pop-going:      #4e9310;
  --pop-border:     #b6babd;
  --pop-close:      #131519;

  /* Full-screen dim backdrop — panel floats centered within it, both axes */
  position:         fixed;
  inset:            0;
  z-index:          50;
  background:       rgba(0, 0, 0, 0.65);
  display:          flex;
  align-items:      center;
  justify-content:  center;
  overflow:         hidden;
  overscroll-behavior: none;
}

body.popup-open {
  overflow:         hidden;
}

.show-popup[hidden] { display: none; }

/* Both popup types now hide the shared static-top header title (class
   always set on open — see calendar_mobile.html) — identity sits at the
   very top of both instead of "Show Details"/"New Release". */
.show-popup.popup-header-hidden .popup-header { display: none; }

/* Centered floating card — fixed at ~90% of viewport width/height, capped
   at the existing 430px desktop column width so it never exceeds that
   column. height (not max-height): the card always renders at exactly
   90dvh regardless of how little content a given tab panel holds — e.g.
   the Comments tab's short "Coming Soon" placeholder must not shrink the
   card to hug it. .popup-body below still scrolls internally if content
   exceeds that fixed height. Shadow reads as "floating" without a
   border-radius — this design language is deliberately sharp-edged
   (border-radius: 0 everywhere else), so square corners stay consistent
   here rather than introducing rounding as a side effect of this resize. */
.popup-panel {
  width:            90vw;
  max-width:        430px;
  height:           90dvh;
  background:       var(--pop-surface);
  display:          flex;
  flex-direction:   column;
  overflow:         hidden;
  position:         relative;
  box-shadow:       0 12px 40px rgba(0, 0, 0, 0.5);
}

/* profile.html's Reset Attendance confirm dialog reuses .popup-panel
   wholesale (same shell/CSS as the event/release popups) but is a short
   message + Cancel/Confirm footer, not a content-rich detail view — it
   shouldn't be forced to the fixed 90dvh height above, which was written
   for (and only makes sense for) the tab-panel popups. Same class-vs-ID
   collision #popup-body's scroll-region fix already had to work around:
   this dialog's own id is on the outer .show-popup wrapper, not on
   .popup-panel itself, so scope the override to a descendant selector
   rather than touching the shared class rule. Back to shrink-to-fit
   (max-height, capped at the same 90dvh so it still can't exceed the
   viewport on a very small screen), which is what this dialog actually
   had before the event popup's fixed-size rule was introduced. */
#reset-attendance-popup .popup-panel {
  height:           auto;
  max-height:       90dvh;
}

/* Paper grain on the panel surface only */
.popup-panel::before {
  content:          '';
  position:         absolute;
  inset:            0;
  pointer-events:   none;
  z-index:          0;
  background-image: repeating-linear-gradient(
    -38deg,
    transparent,
    transparent 4px,
    rgba(0,0,0,0.012) 4px,
    rgba(0,0,0,0.012) 5px
  );
}

/* Fixed header — never scrolls */
.popup-header {
  position:         relative;
  z-index:          2;
  flex-shrink:      0;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  padding:          14px 20px;
  border-bottom:    1px solid var(--pop-border);
  background:       var(--pop-surface);
}

/* Header label — centered now that X is gone. Metal Mania + gold-toned
   accent, same display font and size as the main logo (.logo). */
.popup-header-label {
  font-family:      var(--font-display);
  font-size:        calc(34px * var(--type-scale));
  letter-spacing:   0.02em;
  color:            var(--pop-accent);
}

/* Scrollable middle zone — the generic case (e.g. profile.html's
   reset-attendance confirm dialog, whose content sits directly in
   .popup-body with no static-top/scroll-region split). Thin, low-contrast
   scroll indicator rather than a hidden scrollbar: the browser only paints
   it when scrollHeight > clientHeight, so short content shows nothing at
   all — no JS overflow-detection needed. Not a drag target in practice
   (primary users are on mobile and scroll via swipe), just a "there's more
   below" cue, so it's kept thin and on the invariant --pop-border token
   rather than a bold native scrollbar look.

   The event/release popup fragments (fetched into the specific
   #popup-body element — see calendar_mobile.html) override this below:
   they split into a static top and a single scrolling region, so only
   .popup-scroll-region should scroll/show the indicator, not the whole
   box. See #popup-body and .popup-scroll-region.

   iOS Safari note: WebKit on iOS doesn't honor ::-webkit-scrollbar sizing/
   color the way desktop Chrome/Safari do — it always shows its own thin
   translucent overlay scrollbar that appears during (and briefly after) a
   scroll gesture and fades at rest, regardless of these rules. That's a
   platform limitation, not a bug here: it still satisfies "only visible
   when there's more content" (arguably more strictly, since it hides at
   rest too), just without a persistent indicator on that platform. Desktop
   Chrome/Edge/Safari and Firefox (via scrollbar-width/scrollbar-color) get
   the persistent thin indicator described above. */
.popup-body {
  position:         relative;
  z-index:          1;
  flex:             1;
  overflow-y:       auto;
  padding:          0 20px 16px;
  scrollbar-width:  thin;
  scrollbar-color:  var(--pop-border) transparent;
  overscroll-behavior: none;
}

.popup-body::-webkit-scrollbar {
  width:            4px;
}

.popup-body::-webkit-scrollbar-track {
  background:       transparent;
}

.popup-body::-webkit-scrollbar-thumb {
  background:       var(--pop-border);
  border-radius:    2px;
}

/* Event/release popup fragments split .popup-body's content into a static
   top (identity/Get Tickets/tabs) and a scroll region below the tabs —
   but .popup-body itself was still the one element with overflow-y:auto,
   so its native scrollbar painted across its own FULL height (including
   the static top), not just the genuinely-scrollable portion beneath it.
   Turns that off (ID beats the .popup-body class rule above) and lays
   the two children out as a column instead, so only .popup-scroll-region
   scrolls and shows the indicator. */
#popup-body {
  display:          flex;
  flex-direction:   column;
  overflow-y:       hidden;
}

/* .popup-identity / .popup-details / .popup-attendance each carry their
   own border-bottom as a between-section divider — whichever one ends up
   last (varies: .popup-attendance for the event popup; .popup-details if
   a release has a Note, else .popup-identity) leaves a trailing divider
   line directly above the Close footer. Strip only the last child's, so
   every other section-to-section divider is untouched. */
.popup-body > *:last-child {
  border-bottom:    none;
}

/* Pinned close bar — parchment surface, thin dark top rule */
.popup-footer {
  position:         relative;
  z-index:          2;
  flex-shrink:      0;
  height:           var(--nav-height);
  background:       var(--pop-surface);
  border-top:       1px solid var(--pop-border);
  display:          flex;
}

.popup-close-bar {
  flex:             1;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  background:       transparent;
  border:           none;
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  color:            var(--pop-close);
  cursor:           pointer;
}

.popup-close-bar:hover {
  background:       rgba(0, 0, 0, 0.05);
}

/* Confirm half of a two-button footer (Cancel/Confirm dialogs, e.g. Reset
   Attendance) — sits alongside .popup-close-bar, same flex:1 split, divided
   by a hairline, colored with the popup accent to read as the primary action. */
.popup-confirm-bar {
  flex:             1;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  background:       transparent;
  border:           none;
  border-left:      1px solid var(--pop-border);
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  color:            var(--pop-accent);
  cursor:           pointer;
}

.popup-confirm-bar:hover {
  background:       rgba(0, 0, 0, 0.05);
}

/* Plain confirmation copy (Reset Attendance dialog) — no existing text
   pattern in .popup-body fit a static paragraph, so this is intentionally
   minimal: same body font, popup ink tokens, no section divider. */
.popup-confirm-body {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  line-height:      1.5;
  color:            var(--pop-secondary);
  padding-top:      16px;
  margin:           0;
}

.popup-confirm-warning {
  color:            var(--pop-ink);
  font-weight:      700;
}

/* ── Show Identity block (§6.4) ─────────────────────────────────────────── */

.popup-identity {
  padding:          22px 0 20px;
  border-bottom:    1px solid var(--pop-border);
}

/* Band/venue (event popup) and band/album (release popup) centered now
   that they sit at the very top of the static section, in both popups —
   scoped via .popup-header-hidden (always set on open now — see
   calendar_mobile.html) rather than the bare .popup-identity class,
   which is shared between the two fragments. */
.show-popup.popup-header-hidden .popup-identity {
  text-align:       center;
}

.popup-band-name {
  font-family:      var(--font-body);
  font-size:        calc(28px * var(--type-scale));
  font-weight:      900;
  letter-spacing:   -0.01em;
  text-transform:   uppercase;
  color:            var(--pop-ink);
  line-height:      1.05;
  margin-bottom:    7px;
}

.popup-venue-line {
  font-family:      var(--font-body);
  font-size:        calc(15px * var(--type-scale));
  font-weight:      500;
  letter-spacing:   0.01em;
  color:            var(--pop-secondary);
  margin-bottom:    16px;
}

.popup-core-four {
  display:          grid;
  grid-template-columns: 1fr 1fr;
  gap:              12px;
  margin-bottom:    18px;
}

.popup-core-label {
  font-family:      var(--font-body);
  font-size:        calc(8px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.18em;
  text-transform:   uppercase;
  color:            var(--pop-secondary);
  margin-bottom:    3px;
}

.popup-core-value {
  font-family:      var(--font-body);
  font-size:        calc(16px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   -0.01em;
  color:            var(--pop-ink);
}

.popup-tickets-btn {
  display:          inline-flex;
  align-items:      center;
  padding:          9px 18px;
  background:       var(--pop-accent);
  color:            var(--pop-accent-on);
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      900;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  text-decoration:  none;
  border-radius:    0;
  border:           none;
}

/* Album art — release popup only; ~60% width, square, centered */
.popup-album-art {
  display:      block;
  width:        60%;
  height:       auto;
  margin-bottom:18px;
}

/* DATE/SHOW + Get Tickets — relocated from .popup-identity into the
   Details tab panel (§ popup restructure). Same 12px-top/bottom-plus-
   divider rhythm as .popup-details/.popup-attendance below, so it reads
   as a consistent first "section" of the panel regardless of whether
   Doors/Support/Tickets rows follow it. .popup-core-four/.popup-tickets-
   btn themselves are unchanged — only this wrapper is new. */
.popup-show-core {
  padding:          12px 0 12px;
  border-bottom:    1px solid var(--pop-border);
}

/* ── Event popup tabs (Details / Comments) ─────────────────────────── */
/* Same visual pattern as .submit-tabs/.submit-tab, restyled onto the popup's
   own color tokens (pop-* instead of gold/venue-text) since this bar lives
   inside the popup card, not the Submit page. */

.popup-tabs {
  display:          flex;
  border-bottom:    1px solid var(--pop-border);
}

/* Static top section (identity + tabs) — a plain non-scrolling flex child
   of #popup-body now (see #popup-body above), not a sticky-positioned
   element within a scrolling box: it never moves regardless of how much
   Details/Attendance content is below it, and — unlike the earlier
   sticky-within-one-scroll-box approach — the scrollbar for the content
   below no longer paints across this section too. flex-shrink:0 keeps it
   from being compressed by .popup-scroll-region's flex:1 below. Both the
   event and release popup fragments use this same structure. */
.popup-static-top {
  flex-shrink:      0;
  background:       var(--pop-surface);
}

/* The one genuinely scrolling region in the event/release popups — see
   #popup-body above for why #popup-body itself no longer scrolls.
   min-height:0 is required for a flex child to actually shrink/scroll
   instead of stretching to fit its content. Padding-top reuses the exact
   16px value #popup-body's own bottom padding already gives the Close
   footer, so the spacing matches top and bottom. Scrollbar styling
   mirrors .popup-body's above (same thin/low-contrast/--pop-border
   treatment, same iOS caveat) since this is the actual scroll box now.
   Padding-right on top of #popup-body's own 20px side padding: right-
   aligned Details values (Doors/Support/Tickets, via .popup-detail-row's
   justify-content:space-between) otherwise sit flush against this box's
   right edge, same edge the 4px scrollbar draws on — overlapping it by
   a few px whenever the indicator is visible. Left edge is untouched;
   this only pulls the right side in. */
.popup-scroll-region {
  flex:             1;
  min-height:       0;
  overflow-y:       auto;
  padding-top:      16px;
  padding-right:    8px;
  scrollbar-width:  thin;
  scrollbar-color:  var(--pop-border) transparent;
  overscroll-behavior: none;
}

.popup-scroll-region::-webkit-scrollbar {
  width:            4px;
}

.popup-scroll-region::-webkit-scrollbar-track {
  background:       transparent;
}

.popup-scroll-region::-webkit-scrollbar-thumb {
  background:       var(--pop-border);
  border-radius:    2px;
}

.popup-tab {
  flex:             1;
  padding:          10px 0;
  background:       none;
  border:           none;
  border-bottom:    2px solid transparent;
  margin-bottom:    -1px;
  color:            var(--pop-accent);
  opacity:          0.55;
  font-family:      var(--font-display);
  font-size:        calc(19px * var(--type-scale));
  letter-spacing:   0.02em;
  cursor:           pointer;
  transition:       opacity 0.15s, border-color 0.15s;
}

.popup-tab.active {
  opacity:          1;
  border-bottom-color: var(--pop-accent);
}

/* Non-interactive "Details" header — the release popup's single section
   label now that Comments was removed (§ release Comments removal), no
   longer a two-way switcher. Copies .popup-tab.active's visual treatment
   (metal font, orange, underline) for consistency with the established
   look, but is a plain element — no cursor:pointer, no ARIA tab roles,
   and never queried by initPopupTabs (that call is dropped entirely for
   the release popup now — see openReleasePopup in base.html — so this
   label is never wired up as clickable). */
.popup-section-label {
  flex:             1;
  padding:          10px 0;
  text-align:       center;
  border-bottom:    2px solid var(--pop-accent);
  margin-bottom:    -1px;
  color:            var(--pop-accent);
  font-family:      var(--font-display);
  font-size:        calc(19px * var(--type-scale));
  letter-spacing:   0.02em;
}

.popup-tab-panel {
  display:          none;
}

.popup-tab-panel.active {
  display:          block;
}

/* Same trailing-divider strip as .popup-body > *:last-child, but scoped one
   level deeper now that Details/Attendance live inside a tab panel instead
   of directly in .popup-body. */
.popup-tab-panel > *:last-child {
  border-bottom:    none;
}

/* .popup-attendance's own 14px bottom padding (its "space after the list,
   before the next section") used to stack with #popup-body's 16px bottom
   padding, giving ~30px above the Close footer instead of the intended
   16px — not matched by anything visible, since its border-bottom is
   already stripped above. Zeroed here (only when it's the panel's last
   child, which in practice it always is) so #popup-body's own 16px
   padding is the sole contributor, matching .popup-scroll-region's
   16px top gap exactly. Scoped to .popup-attendance specifically, not
   the generic *:last-child rule above, so the Comments tab's
   .empty-state placeholder keeps its own unrelated padding. */
.popup-tab-panel > .popup-attendance:last-child {
  padding-bottom:   0;
}

/* Same trailing-divider strip as .popup-tab-panel > *:last-child above,
   for the release popup's content — no longer wrapped in a .popup-tab-
   panel now that its Comments tab (and the tab structure entirely) was
   removed (§ release Comments removal), so .popup-show-core/.popup-
   details sit directly in .popup-scroll-region instead. Inert for the
   event popup: its .popup-scroll-region's direct children are the
   .popup-tab-panel wrappers themselves, which carry no border-bottom of
   their own. */
.popup-scroll-region > *:last-child {
  border-bottom:    none;
}

/* ── Details block (§6.5) ───────────────────────────────────────────────── */

.popup-details {
  /* Bottom trimmed from 18px to 12px (reuses the existing 12px rhythm
     already used elsewhere in this popup) — tightens the Support-line →
     TAP IF YOU'RE GOING gap.
     Top trimmed from 18px to 12px too (was left untouched by that
     earlier fix, back when 18px was legitimately reserving space above
     the since-removed "Details" label) — now matches .popup-attendance's
     own 12px top padding exactly, so the first Details row (Doors/
     Support/Tickets, whichever is first) starts at the same vertical
     position "TAP IF YOU'RE GOING" sits at when Details is empty and
     .popup-attendance is the panel's first child instead. */
  padding:          12px 0 12px;
  border-bottom:    1px solid var(--pop-border);
}

.popup-section-title {
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.16em;
  text-transform:   uppercase;
  color:            var(--pop-ink);
  margin-bottom:    12px;
}

.popup-detail-row {
  display:          flex;
  align-items:      baseline;
  justify-content:  space-between;
  padding:          6px 0;
  border-bottom:    1px solid var(--pop-border);
}

.popup-detail-row:last-child {
  border-bottom:    none;
}

/* When Support is the last detail row (no ticket price shown), its own
   border-bottom is stripped above, so the visible divider below it is
   .popup-details' border-bottom — 6px (this row's padding-bottom) + 12px
   (.popup-details' padding-bottom) = 18px away. That left the text only
   6px from the divider above it. Match padding-top to 18px so the text
   sits centered between both dividers. Doesn't apply when Support isn't
   last (e.g. ticket price follows it) — that case is already symmetric. */
.popup-detail-row--support:last-child {
  padding-top:      18px;
}

/* The actual remaining fix from the previous session's Details-top-
   spacing pass: .popup-details' own container padding-top (12px) was
   correctly matched to .popup-attendance's, and the two CONTAINERS do
   start at the same position — but .popup-detail-row's base 6px own
   top padding (and --support:last-child's 18px above, when Support is
   the ONLY row and therefore both first and last) still pushed the
   actual visible text further down than .popup-attendance-header's
   text, which has no padding of its own at all. Reference (Details
   empty) has zero extra padding before "TAP IF YOU'RE GOING"; the
   first Details row needs the same. Bumped specificity (via the
   .popup-details ancestor) so this correctly wins over
   --support:last-child specifically when a row is both first and
   last — the 18px "centered between two dividers" treatment above is
   only correct when Support is last but NOT first (i.e. there's a
   divider above it from a preceding row), which this selector, being
   scoped to :first-child, never touches. */
.popup-details .popup-detail-row:first-child {
  padding-top:      0;
}

.popup-detail-key {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  font-weight:      500;
  letter-spacing:   0.01em;
  color:            var(--pop-muted);
}

.popup-detail-value {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  font-weight:      600;
  letter-spacing:   0.01em;
  color:            var(--pop-muted);
}

/* ── Attendance block (§6.6) ────────────────────────────────────────────── */

.popup-attendance {
  /* Top trimmed from 18px to 12px (reuses the existing 12px rhythm) —
     tightens the divider → TAP IF YOU'RE GOING gap. Bottom (space after
     the scrollable list, before the next section) is untouched. */
  padding:          12px 0 14px;
  border-bottom:    1px solid var(--pop-border);
}

/* Scoped override, not a global .popup-section-title change — tightens
   TAP IF YOU'RE GOING → pinned row (and WHO'S GOING → first list row)
   without touching the unrelated DETAILS → first-detail-row spacing,
   which also uses the shared .popup-section-title class. Reuses the
   existing 8px rhythm (same value as .popup-member-row's own padding). */
.popup-attendance .popup-section-title {
  margin-bottom:    8px;
}

/* Header row pairing "TAP IF YOU'RE GOING" (left, aligned above the
   name) with the new "TAP IF YOU" label (right, aligned above where the
   drive button sits on the row below) — one row, two labels, not two
   stacked header lines. Both children keep .popup-section-title's own
   font/size/weight/letter-spacing/color/margin-bottom untouched; this
   wrapper only adds the side-by-side layout. */
.popup-attendance-header {
  display:          flex;
  align-items:      baseline;
  justify-content:  space-between;
  gap:              12px;
}

/* "TAP IF YOU" hidden until the pinned row is going. Previously used
   :has() (.popup-attendance:has(.popup-member-row--pinned.going)) since
   the label lives in a sibling wrapper BEFORE the pinned row in the DOM
   and a plain descendant/sibling selector can't reach backward to it.
   Switched away from :has() after the owner's real-device report (iOS
   Safari only, not reproducible on Android): the label stayed visible
   after going was toggled off. WebKit has a documented history of
   :has() invalidation bugs specifically for "selector re-evaluating
   when a referenced SIBLING's class changes" (fixed only as late as
   Safari 27 beta — see WebKit blog release notes), which is exactly
   this rule's shape. JS now toggles an explicit .pinned-going class on
   the shared .popup-attendance ancestor in the same handler that
   toggles .going on the row — ordinary descendant-selector reactivity,
   not :has(), for both the initial server-render AND every live toggle
   while the popup stays open. */
.popup-drive-label {
  opacity:          0;
  transition:       opacity 0.12s;
}

.popup-attendance.pinned-going .popup-drive-label {
  opacity:          1;
}

/* § driver UI parked pending redesign — see .popup-drive-btn's own
   display:none rule further down for the full rationale; same
   treatment here. Placed after this element's own opacity-only rules
   (neither of which touches display, so order isn't load-bearing here
   the way it was for .popup-drive-btn) — kept adjacent for locality. */
.popup-drive-label {
  display:          none;
}

/* Redundant safety net for the pinned row's own button, driven by the
   SAME explicit .pinned-going class as the label above — not the
   :has()-implicated mechanism. Agrees with, doesn't replace, the shared
   .popup-member-row.going .popup-drive-btn rule below (used by every
   row, pinned and WHO'S GOING alike) — the two rules always agree in
   practice since JS keeps .going and .pinned-going in sync together, so
   this only adds a second, independent, :has()-free code path backing
   up the pinned button's visibility specifically. */
.popup-attendance.pinned-going .popup-member-row--pinned .popup-drive-btn {
  opacity:          1;
  pointer-events:   all;
}

/* "WHO'S GOING" sub-header — same 12px rhythm as before it was removed
   in the attendance-section merge. Only relevant when the current user
   HASN'T tapped going yet (other_going_members renders below the pinned
   "prompt to go" row and needs its own label); hidden once they have,
   since the main header itself becomes "WHO'S GOING" at that point and
   a second identical label directly above the same now-merged list
   would be redundant. See the template comment for why this is CSS-
   driven off .pinned-going rather than template-gated at render time. */
.popup-who-going-title {
  margin-top:       12px;
}

.popup-attendance.pinned-going .popup-who-going-title {
  display:          none;
}

.popup-member-row {
  display:          flex;
  align-items:      center;
  padding:          8px 0;
  border-bottom:    1px solid var(--pop-border);
}

.popup-member-row:last-child {
  border-bottom:    none;
}

.popup-member-row[data-tappable="true"] {
  cursor:           pointer;
}

/* Pinned current-user row (§ attendance reorder) — size is the ONLY
   distinction from a normal row, no color/icon/badge added. Its own
   border-bottom (inherited from .popup-member-row above) doubles as the
   divider line above the scrollable roster below it — reuses the same
   hairline already used between every other row/section in this popup,
   no separate divider element needed. "Brah!" matches the enlarged name
   size (was left at the smaller base size originally — pinned row only,
   other rows' "Brah!" is untouched). */
.popup-member-row--pinned .popup-member-name {
  font-size:        calc(22px * var(--type-scale));
}

.popup-member-row--pinned .popup-brah {
  font-size:        calc(22px * var(--type-scale));
}

/* Roster below the pinned row/divider — previously its own ~190px-capped
   internally-scrolling box, which nested a second scroll boundary inside
   #popup-body's own scroll (double-scroll bug: reaching the end of
   #popup-body's scroll didn't guarantee the rest of WHO'S GOING was
   visible, since it needed a separate scroll gesture over just this
   list). Collapsed to plain flow so the roster is just more content
   inside #popup-body's single scroll region, same as Details/Attendance
   above it. */
.popup-member-name {
  font-family:      var(--font-body);
  font-size:        calc(17px * var(--type-scale));
  font-weight:      400;
  letter-spacing:   0.01em;
  color:            var(--pop-secondary);
  flex-shrink:      0;
  transition:       color 0.12s, font-weight 0.12s;
}

.popup-member-row.going .popup-member-name {
  font-weight:      700;
  color:            var(--pop-ink);
}

/* Acid-green "Brah!" — matches the calendar's attending signal */
.popup-brah {
  font-family:      var(--font-body);     /* Inter, same as member names */
  font-weight:      700;
  font-size:        calc(17px * var(--type-scale));
  color:            var(--pop-going);
  margin-left:      6px;
  flex-shrink:      0;
  opacity:          0;
  transition:       opacity 0.12s;
}

.popup-member-row.going .popup-brah {
  opacity:          1;
}

/* Passive driving indicator (§ driving indicator) — separate element from
   the parked .popup-drive-btn entirely (that button/its label stay
   display:none, untouched). Reuses .driving purely as a display trigger
   — that class is already correctly wired to real is_driver regardless
   of which mechanism set it (old toggle, comment-flagging, or staff
   admin override), so this "just works" for all three with no new
   server-side logic. Same opacity-reserves-space pattern as .popup-brah
   above (not display:none) — always in the DOM so a live class toggle
   (syncDriving() in base.html, after a driving comment posts/deletes)
   only has to flip .driving, no code path to insert/remove a whole
   element. Read-only: no cursor:pointer, no click handling anywhere. */
.popup-driving-tag {
  font-size:        calc(17px * var(--type-scale));
  margin-left:      4px;
  flex-shrink:      0;
  opacity:          0;
  transition:       opacity 0.12s;
}

.popup-member-row.driving .popup-driving-tag {
  opacity:          1;
}

.popup-member-row--pinned .popup-driving-tag {
  font-size:        calc(22px * var(--type-scale));
}

.popup-member-spacer {
  flex:             1;
}

/* Drive button — hidden until going; lit via weight + fill (§6.6, §10) */
.popup-drive-btn {
  display:          flex;
  align-items:      center;
  padding:          4px 9px;
  font-family:      var(--font-body);
  font-size:        calc(12px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.04em;
  color:            var(--pop-accent);
  border:           1px solid var(--pop-accent);
  background:       transparent;
  border-radius:    0;
  cursor:           pointer;
  white-space:      nowrap;
  flex-shrink:      0;
  opacity:          0;
  pointer-events:   none;
  transition:       color 0.12s, border-color 0.12s, background 0.12s, opacity 0.12s;
}

/* § driver UI parked pending redesign — owner's decision, temporary and
   deliberate, NOT a bug. Hides the CAN DRIVE button and the "TAP IF
   YOU" label completely, in every state, via display:none — placed
   AFTER the base .popup-drive-btn rule above specifically (same
   specificity, so source order decides the winner) rather than earlier
   in the file, where an identical rule was silently overridden by that
   base rule's own display:flex. Overrides, without deleting, the
   opacity/pointer-events-driven show/hide mechanism used by the
   .going/.driving variants below — those still fully apply to the
   underlying elements, they just no longer matter once display:none
   wins. The toggle-driving JS handler, the /shows/toggle-driving/
   view, and the driving model fields are all left completely
   untouched — this is a visual hide only. A future session re-enables
   this by removing this one rule (and .popup-drive-label's own,
   further up) once the owner's redesign is ready. */
.popup-drive-btn {
  display:          none;
}

.popup-member-row.going .popup-drive-btn {
  opacity:          1;
  pointer-events:   all;
}

/* Fill color fix — was --pop-accent (orange), now --pop-going (the same
   green already used for "Brah!"'s going-state fill) so the driver
   button's active/toggled-on fill matches the going-state green instead
   of clashing with it. Border switched to match too, so the button
   reads as one cohesive green state (border + fill together) rather
   than a green fill inside an orange border. Applies everywhere this
   button appears — pinned row AND every row in WHO'S GOING, since this
   selector isn't scoped to .popup-member-row--pinned. */
.popup-member-row.driving .popup-drive-btn {
  color:            var(--pop-accent-on);
  border-color:     var(--pop-going);
  background:       var(--pop-going);
  font-weight:      900;
}

/* ── Setlist (§ Archive Phase 3 — archived attended shows only) ──────────── */

.popup-setlist {
  padding:          12px 0 12px;
  border-bottom:    1px solid var(--pop-border);
}

.popup-setlist-list {
  margin:           0 0 10px;
  padding-left:     0;
  list-style:       none;
  counter-reset:    setlist-track;
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  color:            var(--pop-ink);
}

.popup-setlist-list li {
  position:         relative;
  padding:          3px 0 3px 32px;
  counter-increment: setlist-track;
}

/* Custom counter replaces the native <ol> marker (§ setlist number
   clipping fix) — outside-position native markers were losing their
   tens digit on two-digit tracks (10/11/12 rendered as ".0"/".1"/".2"),
   and ::marker isn't reliably stylable for width/alignment across
   browsers (notably Safari). Absolutely positioned at the li's own
   left:0, inside the li's padding-left reservation, so wrapped
   second lines on a long title hang under the same indent rather than
   the title's own left edge. Width sized for two digits comfortably;
   any realistic setlist stays well under 99 tracks. */
.popup-setlist-list li::before {
  content:          counter(setlist-track) ".";
  position:         absolute;
  left:             0;
  width:            24px;
  text-align:       left;
}

.popup-setlist-byline {
  margin-bottom:    10px;
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  color:            var(--pop-muted);
}

.popup-setlist-add-btn,
.popup-setlist-edit-btn,
.popup-setlist-save-btn {
  padding:          9px 16px;
  background:       var(--pop-accent);
  color:            var(--pop-accent-on);
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      900;
  letter-spacing:   0.1em;
  text-transform:   uppercase;
  border:           none;
  border-radius:    0;
  cursor:           pointer;
}

.popup-setlist-cancel-btn {
  padding:          9px 16px;
  background:       none;
  color:            var(--pop-muted);
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.1em;
  text-transform:   uppercase;
  border:           1px solid var(--pop-border);
  border-radius:    0;
  cursor:           pointer;
}

.popup-setlist-textarea {
  display:          block;
  width:            100%;
  min-height:       140px;
  background:       var(--pop-surface-2);
  border:           1px solid var(--pop-border);
  color:            var(--pop-ink);
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  font-weight:      400;
  padding:          9px 12px;
  border-radius:    0;
  outline:          none;
  resize:           vertical;
  margin-bottom:    10px;
  transition:       border-color 0.15s;
}

.popup-setlist-textarea::placeholder {
  color:            var(--pop-muted);
}

.popup-setlist-textarea:focus {
  border-color:     var(--pop-accent);
}

.popup-setlist-form-actions {
  display:          flex;
  gap:              8px;
}

/* ── Comments (§ comment system) ─────────────────────────────────────────── */

.popup-comment-list {
  display:          flex;
  flex-direction:   column;
}

.popup-comment {
  padding:          12px 0;
  border-bottom:    1px solid var(--pop-border);
}

.popup-comment:last-child {
  border-bottom:    none;
}

.popup-comment-head {
  display:          flex;
  align-items:      baseline;
  gap:              8px;
  margin-bottom:    4px;
}

.popup-comment-author {
  font-family:      var(--font-body);
  font-size:        calc(12px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.01em;
  color:            var(--pop-ink);
}

.popup-comment-time {
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      500;
  color:            var(--pop-muted);
}

.popup-comment-delete {
  margin-left:      auto;
  background:       none;
  border:           none;
  padding:          0 2px;
  font-family:      var(--font-body);
  font-size:        calc(15px * var(--type-scale));
  line-height:      1;
  color:            var(--pop-muted);
  cursor:           pointer;
}

/* Lightweight two-tap confirm — first tap arms it (shrinks to a text
   "Sure?" prompt in the accent color), second tap within the JS-side
   timeout actually deletes. Avoids a native confirm() dialog for a
   single short comment while still guarding against a stray mobile
   mis-tap on a small × control. */
.popup-comment-delete.confirm-armed {
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.06em;
  text-transform:   uppercase;
  color:            var(--pop-accent);
}

.popup-comment-text {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  font-weight:      400;
  line-height:      1.4;
  color:            var(--pop-secondary);
  word-break:       break-word;
}

.popup-comment-text a {
  color:            var(--pop-accent);
}

/* Resolved @mention (comment.mentions, set at save time) — reuses the same
   accent token as comment links above, just bolded to read as a name
   rather than a URL. An unmatched @word never gets this class (§
   @mentions). */
.popup-mention {
  color:            var(--pop-accent);
  font-weight:      700;
}

/* Pinned input bar (§ comment input pin) — sibling of .popup-static-top/
   .popup-scroll-region inside #popup-body's flex column, not part of the
   scrollable Comments panel: flex-shrink:0 keeps it fixed-height so
   .popup-scroll-region's flex:1 absorbs the remaining space above it.
   Hidden by default (matches .popup-tab-panel's own default-hidden
   state); toggled in sync with tab switches by the same activate() that
   already drives tab-panel visibility (see initPopupTabs in base.html),
   not a separate mechanism. border-top reads as the same "divider before
   fixed chrome" pattern .popup-tabs/.popup-header already use; #popup-
   body's own bottom padding (unchanged) supplies the gap below this bar,
   before the Close footer — same gap that used to sit below
   .popup-scroll-region when the form lived inside it. */
.popup-comment-bar {
  display:          none;
  flex-shrink:      0;
  padding-top:      14px;
  border-top:       1px solid var(--pop-border);
}

.popup-comment-bar.active {
  display:          block;
}

/* Quick-fill template row — one button ("I can drive") today, more
   planned; flex-wrap so additional templates added later just wrap onto
   a second line rather than overflowing or requiring a layout change. */
.popup-comment-templates {
  display:          flex;
  flex-wrap:        wrap;
  gap:              8px;
  margin-bottom:    10px;
}

.popup-comment-template-btn {
  padding:          6px 12px;
  background:       var(--pop-surface-2);
  border:           1px solid var(--pop-border);
  color:            var(--pop-secondary);
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.02em;
  border-radius:    0;
  cursor:           pointer;
}

.popup-comment-template-btn:active {
  background:       var(--pop-surface);
}

.popup-comment-input-row {
  display:          flex;
  gap:              8px;
}

.popup-comment-input {
  flex:             1;
  min-width:        0;
  background:       var(--pop-surface-2);
  border:           1px solid var(--pop-border);
  color:            var(--pop-ink);
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  font-weight:      400;
  padding:          9px 12px;
  border-radius:    0;
  outline:          none;
  transition:       border-color 0.15s;
}

.popup-comment-input::placeholder {
  color:            var(--pop-muted);
}

.popup-comment-input:focus {
  border-color:     var(--pop-accent);
}

.popup-comment-submit {
  padding:          9px 16px;
  background:       var(--pop-accent);
  color:            var(--pop-accent-on);
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      900;
  letter-spacing:   0.1em;
  text-transform:   uppercase;
  border:           none;
  border-radius:    0;
  cursor:           pointer;
}

.popup-comment-counter {
  margin-top:       6px;
  text-align:       right;
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  color:            var(--pop-muted);
}

.popup-comment-counter--limit {
  color:            var(--pop-accent);
  font-weight:      700;
}

/* ── News page tabs — The List / The Scene (§ News/Scene tab split) ──────── */

/* Second fixed strip, anchored directly below news-feed-header — same
   fixed/left/right/max-width/margin technique as the header and bottom-nav
   (see .app-header.news-feed-header, .bottom-nav). Not merged into the
   header itself: news-feed-header's rendered height is shared with
   profile-header via --news-header-height, so growing it here would push
   Profile's layout too. */
.feed-tabs {
  position:         fixed;
  /* Mirrors news-feed-header's own padding-top formula
     (max(24px, env(safe-area-inset-top)), see .app-header.news-feed-header
     above) instead of anchoring off --news-header-height — that token is a
     flat buffered constant measured where the inset is 0 (normal browser/
     Android), so adding the real env(safe-area-inset-top) on top of it
     double-counted the inset on notched iOS standalone (a ~24px gap
     above this bar there, .screen's own textured background showing
     through) while UNDER-counting it if the double-count was naively
     just deleted instead (this bar would sit too high, overlapping the
     header's real content on notched iOS). 66px is the header's own
     content+bottom-padding portion, back-derived from
     --news-header-height's documented ~90px measurement (90 - the 24px
     floor it assumed = 66) — added to the SAME max(24px, env(...))
     expression the header itself uses, so this scales correctly with
     the real inset on any device instead of guessing a device-specific
     number. */
  top:              calc(max(24px, env(safe-area-inset-top)) + 66px);
  left:             0;
  right:            0;
  max-width:        430px;
  margin:           0 auto;
  height:           var(--feed-tabs-height);
  display:          flex;
  background:       var(--bg);
  border-bottom:    1px solid var(--border-faint);
  z-index:          15;
}

/* Same active-indicator approach as .popup-tab (metal-font label, bottom
   border sweeps in on .active) but bound to the app's own theme-aware
   --nav-gold/--nav-gold-active tokens instead of the popup's
   theme-invariant --pop-accent — this tab bar lives on a normal themed
   page, not inside .show-popup's deliberate "break" surface. */
.feed-tab {
  flex:             1;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  background:       none;
  border:           none;
  border-bottom:    2px solid transparent;
  margin-bottom:    -1px;
  color:            var(--nav-gold);
  opacity:          0.75;
  font-family:      var(--font-display);
  font-size:        calc(17px * var(--type-scale));
  letter-spacing:   0.02em;
  cursor:           pointer;
  transition:       opacity 0.15s, border-color 0.15s, color 0.15s;
}

.feed-tab.active {
  opacity:          1;
  color:            var(--nav-gold-active);
  border-bottom-color: var(--nav-gold-active);
}

/* Both tab panels stay in the DOM at all times (JS just toggles which is
   visible) — same approach as .popup-tab-panel. */
.feed-tab-panel {
  display:          none;
}

.feed-tab-panel.active {
  display:          block;
}

/* ── Activity Feed ──────────────────────────────────────────────────────── */

.activity-feed {
  /* Top: clears the fixed header AND the fixed tab bar below it (see
     .news-feed-header, .feed-tabs), plus the 8px breathing room this page
     already had before the first row. Both edges add the iOS standalone
     safe-area inset — see .app-header/.bottom-nav comments; env() is 0
     outside standalone iOS. */
  padding: calc(var(--news-header-height) + var(--feed-tabs-height) + env(safe-area-inset-top) + 8px) 0 calc(var(--nav-height) + env(safe-area-inset-bottom) + 24px);
}

.activity-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 12px 16px;
  border-bottom: 2px solid var(--feed-divider-bottom);
}

/* Card rows — News / Release / Show, all rendered via _activity_card.html.
   Same plain dark-card treatment as the calendar's normal (non-attending)
   .show-card — var(--card-bg) + var(--card-border-top) — instead of an
   invented orange tint, so this row type matches the calendar's
   established look rather than its own separate visual language. */
.activity-row.card {
  background: var(--card-bg);
  border-top: 2px solid var(--feed-divider-top);
}

/* § whole-row tap target — the row itself is the tap target now (no
   visible buttons), rendered as an <a> when a real URL exists, a
   <button> otherwise (Release rows — no release detail page/route
   exists). cursor:pointer is the visual affordance; rows with no
   destination don't get this class and stay the default cursor. */
.activity-tappable {
  cursor: pointer;
}

/* Reset the UA default appearance both tags carry that .activity-row's
   own rules never had to override before (plain <div> rows didn't need
   this) — <a> would otherwise render its contents in the browser's
   default link blue/underline, <button> in the OS's default button text
   color; neither matches the theme. color:inherit walks back up to
   body's var(--parchment) same as every other row's text always has,
   text-decoration:none on the ancestor is required (a child re-declaring
   none doesn't stop an ancestor's underline from being drawn through it).
   :visited is reset too so a previously-tapped row doesn't turn purple. */
a.activity-row,
button.activity-row {
  color: inherit;
  text-decoration: none;
}

a.activity-row:visited {
  color: inherit;
}

button.activity-row {
  display: flex;
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  box-sizing: border-box;
}

/* Quiet rows — going / driving / not-going / not-driving. No background
   tint at all (plain, same as the page background) — .faint (not-going,
   not-driving) is distinguished only by the absence of an emoji, not by
   shading. Shorter vertical padding than the card rows above, further
   de-emphasizing this group. Single child now (no separate time header
   row), so no gap needed. */
.activity-row.quiet {
  border-top: 2px solid var(--feed-divider-top);
  padding: 6px 16px;
}

.activity-row.quiet .activity-sentence {
  font-family: var(--font-body);
  font-size: calc(13px * var(--type-scale));
  font-weight: 400;
  color: var(--parchment);
  line-height: 1.4;
}

/* Inline time for quiet rows — flows in normal text with the sentence
   (e.g. "🤘 Brant is going to Melvins. · 5 hours ago") rather than being
   anchored top-right like the card component's .activity-time, so the row
   wraps as a unit only if it genuinely doesn't fit, instead of the time
   competing with short sentence text for its own line. */
.activity-time-inline {
  font-family: var(--font-body);
  font-size: calc(10px * var(--type-scale));
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--venue-text);
  white-space: nowrap;
}

/* Card rows' top-right time (News/Release/Show — unchanged, still anchored
   via .feed-badge-row's flex layout below). */
.activity-time {
  font-family: var(--font-body);
  font-size: calc(10px * var(--type-scale));
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--venue-text);
  margin-left: auto;
}

.feed-badge-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.feed-badge {
  font-family: var(--font-body);
  font-size: calc(14px * var(--type-scale));
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--gold);
}

.feed-card-title {
  font-weight: 700;
}

/* Posted-by attribution (§ posted-by attribution) — News/Show/Release
   cards only, sits between the title and the description. Smaller/more
   muted than .news-description since it's secondary metadata, not body
   content. */
.feed-posted-by {
  font-family: var(--font-body);
  font-size: calc(11px * var(--type-scale));
  font-weight: 500;
  color: var(--venue-text);
}

/* Responsive 2:1 crop (was a fixed 400x200px — overflowed narrow
   viewports, most visibly on iOS Safari's rubber-band overscroll, since
   nothing here or on any ancestor constrained width at all). No separate
   max-width safeguard needed for desktop: .screen already caps the whole
   app at max-width:430px (this is a fixed-width "phone shell" layout
   even on wide viewports, not a fluid one), and .activity-row's own 16px
   side padding leaves at most 430-32=398px of available width here —
   already narrower than the original 400px, so this renders effectively
   identically to before on every screen this app has ever actually
   rendered at, while now correctly shrinking instead of overflowing on
   anything narrower. */
.news-thumbnail {
  width: 100%;
  aspect-ratio: 2 / 1;
  object-fit: cover;
  object-position: top;
  border-radius: 6px;
}

.news-thumbnail-link {
  display: inline-block;
  cursor: pointer;
}

.news-description {
  font-family: var(--font-body);
  font-size: calc(12px * var(--type-scale));
  /* Matches .feed-card-title's color exactly — that class sets no color
     of its own, it just inherits body's var(--parchment) (see
     a.activity-row/button.activity-row's own color:inherit rule above).
     Using the same variable here means this stays correct in both
     themes automatically, not just whichever one was eyeballed. Only
     the color changed — font-size/weight/line-height are untouched. */
  color: var(--parchment);
  line-height: 1.4;
}

/* ── Manage News (staff-only pending-approval queue) ─────────────────────
   Same .activity-feed card rows as The Scene tab, minus the feed-tabs bar
   (no tab strip on this page) — .manage-news-feed overrides just the
   top padding rather than reusing .activity-feed's alone. */
.manage-news-feed {
  padding: calc(var(--news-header-height) + env(safe-area-inset-top) + 8px) 0 calc(var(--nav-height) + env(safe-area-inset-bottom) + 24px);
}

.manage-news-item {
  border-bottom: 2px solid var(--feed-divider-bottom);
}

/* The card's own .activity-row border-bottom (shared with every other
   card type, incl. The Scene tab) would otherwise land between the
   description and the actions row below it, since actions renders as a
   sibling after the card, not inside it. .manage-news-item's own
   border-bottom above already serves as the item-to-item separator, so
   this is purely a redundant/misplaced line, scoped to Manage News only
   — The Scene tab's cards (no .manage-news-item ancestor) keep theirs. */
.manage-news-item .activity-row {
  border-bottom: none;
}

/* Approve/Edit sit below the card as their own footer row, not inside it —
   the card above may already be an <a> or <button> (§ whole-row tap
   target), so a nested interactive element isn't an option. */
.manage-news-actions {
  display: flex;
  gap: 8px;
  padding: 0 16px 12px;
}

.manage-news-approve-form,
.manage-news-delete-form {
  flex: 1;
  display: flex;
}

.manage-news-approve-btn,
.manage-news-edit-link,
.manage-news-delete-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  font-family: var(--font-body);
  font-size: calc(11px * var(--type-scale));
  font-weight: 900;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: 0;
  cursor: pointer;
  transition: background 0.15s;
}

.manage-news-approve-btn {
  width: 100%;
  background: var(--gold);
  color: var(--accent-on);
  border: none;
}

.manage-news-approve-btn:hover {
  background: var(--accent-hover);
}

.manage-news-edit-link {
  background: transparent;
  color: var(--gold);
  border: 1px solid var(--border-medium);
}

.manage-news-edit-link:hover {
  background: var(--tint-active);
  border-color: var(--gold);
}

/* Destructive action — deliberately red, distinct from the gold Approve/
   Edit pair, same red already established for admin error states
   (AdminActionLog/Agent Log's #c62828). Solid fill (not outline, unlike
   Approve/Edit) so Delete reads as visually heavier — the fixed hex isn't
   theme-bound, so the white-on-red contrast holds in both day and night. */
.manage-news-delete-btn {
  width: 100%;
  background: #c62828;
  color: #fff;
  border: 1px solid #c62828;
}

.manage-news-delete-btn:hover {
  background: #a82121;
}

/* ── Simple list pages (Bands / Venues) ─────────────────────────────────── */

.simple-list {
  /* Bottom: clears the bottom nav, plus the iOS standalone safe-area
     inset (see .bottom-nav comment) — env() is 0 outside standalone iOS. */
  padding: 8px 0 calc(var(--nav-height) + env(safe-area-inset-bottom) + 24px);
}

.simple-list-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--card-border-bottom);
}

/* ── Login page ─────────────────────────────────────────────────────────── */

.login-page {
  display:          flex;
  justify-content:  center;
  background:       var(--shell);
  min-height:       100dvh;
}

.login-screen {
  width:            100%;
  max-width:        430px;
  background:       var(--bg);
  background-image:
    repeating-linear-gradient(
      90deg,
      var(--hatch-h) 0px, var(--hatch-h) 1px,
      transparent 1px, transparent 24px
    ),
    repeating-linear-gradient(
      0deg,
      var(--hatch-v) 0px, var(--hatch-v) 1px,
      transparent 1px, transparent 24px
    );
  display:          flex;
  flex-direction:   column;
  align-items:      center;
  justify-content:  center;
  padding:          0 32px 60px;
}

.login-logo {
  font-family:      var(--font-display);
  font-size:        calc(36px * var(--type-scale));
  color:            var(--gold);
  letter-spacing:   0.02em;
  line-height:      1;
  text-align:       center;
  margin-bottom:    8px;
}

.login-subtitle {
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  color:            var(--venue-text);
  text-align:       center;
  margin-bottom:    48px;
}

.login-form {
  width:            100%;
  display:          flex;
  flex-direction:   column;
  gap:              16px;
}

.login-field {
  display:          flex;
  flex-direction:   column;
  gap:              6px;
}

.login-label {
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  color:            var(--venue-text);
}

.login-input {
  width:            100%;
  background:       var(--surface-tint);
  border:           1px solid var(--border-hairline);
  color:            var(--parchment);
  font-family:      var(--font-body);
  font-size:        calc(15px * var(--type-scale));
  font-weight:      400;
  padding:          11px 14px;
  border-radius:    0;
  outline:          none;
  transition:       border-color 0.15s;
}

.login-input::placeholder {
  color:            var(--placeholder-login);
}

.login-input:focus {
  border-color:     var(--border-focus-strong);
}

.login-btn {
  width:            100%;
  margin-top:       8px;
  padding:          13px;
  background:       var(--gold);
  color:            var(--accent-on);
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      900;
  letter-spacing:   0.16em;
  text-transform:   uppercase;
  border:           none;
  border-radius:    0;
  cursor:           pointer;
  transition:       background 0.15s;
}

.login-btn:hover {
  background:       var(--accent-hover);
}

.login-error {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  color:            rgba(200,60,60,0.90);
  text-align:       center;
  margin-top:       -4px;
}

/* ── App header nav (Profile / Sign Out links) ──────────────────────────── */

.app-header-nav {
  display:          flex;
  gap:              20px;
  margin-top:       10px;
}

.app-header-nav a {
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.12em;
  text-transform:   uppercase;
  color:            var(--venue-text);
  text-decoration:  none;
}

.app-header-nav a:hover {
  color:            var(--gold);
}

/* ── Bottom nav ─────────────────────────────────────────────────────────── */

.bottom-nav {
  position:         fixed;
  bottom:           0;
  left:             0;
  right:            0;
  max-width:        430px;
  margin:           0 auto;
  /* Height grows by the iOS standalone safe-area inset (home-indicator
     gesture area) and padding-bottom eats exactly that much back out of
     the content box (box-sizing: border-box, set globally), so the tabs
     still get the original --nav-height to lay out in — this just
     extends the bar's background color down as a buffer strip instead of
     letting the tabs' icons/labels drift down into the gesture area.
     env() is 0 outside standalone iOS, so height/padding both collapse
     back to the original --nav-height there. */
  height:           calc(var(--nav-height) + env(safe-area-inset-bottom));
  padding-bottom:   env(safe-area-inset-bottom);
  background:       var(--nav-bg);
  background-image:
    repeating-linear-gradient(
      90deg,
      var(--hatch-h) 0px, var(--hatch-h) 1px,
      transparent 1px, transparent 4px
    ),
    repeating-linear-gradient(
      0deg,
      var(--hatch-v) 0px, var(--hatch-v) 1px,
      transparent 1px, transparent 4px
    );
  border-top:       1px solid var(--nav-border);
  display:          flex;
  z-index:          15;
}

.bottom-nav-tab {
  flex:             1;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  font-family:      var(--font-display);  /* Metal Mania, matches .feed-tab — below the documented 20px floor at this nav size, applied per owner's explicit call */
  font-size:        calc(17px * var(--type-scale));  /* matches .feed-tab's size exactly */
  font-weight:      normal;  /* .feed-tab leaves this unset (normal) — was 700, part of why this read bigger/heavier than the top nav at the same font-size */
  letter-spacing:   0.02em;  /* matches .feed-tab — was 0.14em, same reason */
  text-transform:   none;    /* matches .feed-tab — was uppercase, same reason */
  color:            var(--nav-gold);
  text-decoration:  none;
  position:         relative;
}

.bottom-nav-tab.active {
  color:            var(--nav-gold-active);
}

.bottom-nav-tab.active::before {
  content:          '';
  position:         absolute;
  top:              0;
  left:             0;
  right:            0;
  height:           2px;
  background:       var(--nav-gold-active);
}

/* ── Profile page ───────────────────────────────────────────────────────── */

.profile-content {
  /* Top: clears the now-fixed header (see .profile-header), plus the
     same 20px breathing room this page already had before the first
     field. Both edges add the iOS standalone safe-area inset (see
     .app-header/.bottom-nav comments); env() is 0 outside standalone iOS. */
  padding:          calc(var(--profile-header-height) + env(safe-area-inset-top) + 20px) 20px calc(var(--nav-height) + env(safe-area-inset-bottom) + 24px);
}

.profile-saved {
  font-family:      var(--font-body);
  font-size:        calc(12px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.10em;
  text-transform:   uppercase;
  color:            var(--gold);
  margin-bottom:    16px;
}

.profile-meta {
  margin-bottom:    20px;
}

.profile-username {
  font-family:      var(--font-display);
  font-size:        calc(28px * var(--type-scale));
  color:            var(--gold);
}

.profile-section-label {
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  color:            var(--gold);
  padding-top:      8px;
  border-top:       1px solid var(--tint-subtle);
}

.profile-section-subtitle {
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  color:            var(--gold);
  margin-top:       calc(-16px + 4px); /* pull back the login-form flex gap, keep 4px */
}

.profile-section-label:first-child {
  border-top:       none;
  padding-top:      0;
}

.profile-field-error {
  font-family:      var(--font-body);
  font-size:        calc(12px * var(--type-scale));
  color:            rgba(200, 60, 60, 0.90);
  margin-top:       4px;
}

.profile-action-link {
  display:          block;
  padding:          10px 0;
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  color:            var(--gold);
  text-decoration:  none;
}

.profile-action-link:hover {
  color:            var(--accent-hover);
}

.profile-logout-btn {
  display:          block;
  width:            100%;
  margin-top:       12px;
  padding:          13px;
  background:       transparent;
  color:            var(--gold);
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      900;
  letter-spacing:   0.16em;
  text-transform:   uppercase;
  text-decoration:  none;
  text-align:       center;
  border:           1px solid var(--border-medium);
  border-radius:    0;
  transition:       background 0.15s, border-color 0.15s;
}

.profile-logout-btn:hover {
  background:       var(--tint-active);
  border-color:     var(--gold);
}

/* ── Slide-out drawer ───────────────────────────────────────────────────── */

.drawer-backdrop {
  position:         fixed;
  inset:            0;
  background:       rgba(0, 0, 0, 0.65);
  z-index:          40;
  opacity:          0;
  pointer-events:   none;
  transition:       opacity 0.22s;
}

.drawer-backdrop.open {
  opacity:          1;
  pointer-events:   all;
}

.drawer {
  position:         fixed;
  top:              0;
  left:             0;
  bottom:           0;
  width:            260px;
  background:       var(--bg);
  background-image:
    repeating-linear-gradient(
      90deg,
      var(--hatch-h) 0px, var(--hatch-h) 1px,
      transparent 1px, transparent 4px
    ),
    repeating-linear-gradient(
      0deg,
      var(--hatch-v) 0px, var(--hatch-v) 1px,
      transparent 1px, transparent 4px
    );
  border-right:     1px solid var(--nav-border);
  z-index:          45;
  transform:        translateX(-100%);
  transition:       transform 0.25s ease;
  display:          flex;
  flex-direction:   column;
}

.drawer.open {
  transform:        translateX(0);
}

.drawer-top {
  display:          flex;
  align-items:      center;
  justify-content:  flex-end;
  padding:          14px 14px;
  border-bottom:    1px solid var(--nav-border);
  flex-shrink:      0;
}

.drawer-close {
  width:            34px;
  height:           34px;
  display:          flex;
  align-items:      center;
  justify-content:  center;
  background:       transparent;
  border:           1px solid var(--border-icon);
  color:            var(--venue-text);
  font-size:        calc(15px * var(--type-scale));
  font-weight:      300;
  font-family:      var(--font-body);
  line-height:      1;
  cursor:           pointer;
  border-radius:    0;
}

.drawer-close:hover {
  color:            var(--gold);
  border-color:     var(--border-medium);
}

.drawer-nav {
  flex:             1;
  display:          flex;
  flex-direction:   column;
  padding:          16px 0;
  overflow-y:       auto;
}

.drawer-link {
  display:          block;
  padding:          13px 24px;
  font-family:      var(--font-body);
  font-size:        calc(11px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  color:            var(--gold);
  text-decoration:  none;
}

.drawer-link:hover {
  background:       var(--tint-hover);
}

.drawer-link--muted {
  color:            var(--venue-text);
  pointer-events:   none;
  font-style:       italic;
  letter-spacing:   0.06em;
  text-transform:   none;
  font-weight:      400;
  font-size:        calc(12px * var(--type-scale));
}

.drawer-section-label {
  font-family:      var(--font-body);
  font-size:        calc(9px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.18em;
  text-transform:   uppercase;
  color:            var(--venue-text);
  padding:          20px 24px 6px;
  border-top:       1px solid var(--tint-subtle);
  margin-top:       12px;
}

/* ── Submit page ─────────────────────────────────────────────────────────── */

/* .login-label is shared with Login/Profile/Setup/Password-reset (dim gold by
   design there) — scope the brighter gold to submit's field labels only. */
.submit-panel .login-label {
  color: var(--gold);
}

/* Fixed-height column so the tracked list (below) can flex-fill the remaining
   space down to the bottom nav, identically across all three tabs — regardless
   of how tall that tab's own form fields are. */
.submit-screen {
  display:          flex;
  flex-direction:   column;
  height:           100dvh;
}

.submit-content {
  /* Top: clears the now-fixed header (see .submit-header), reusing
     --profile-header-height since Submit's header markup is identical
     to Profile's/News's (no new token needed), plus the same 16px
     breathing room this page already had before the tab bar. Both edges
     add the iOS standalone safe-area inset (see .app-header/.bottom-nav
     comments); env() is 0 outside standalone iOS. */
  padding:          calc(var(--profile-header-height) + env(safe-area-inset-top) + 16px) 20px calc(var(--nav-height) + env(safe-area-inset-bottom) + 16px);
  display:          flex;
  flex-direction:   column;
  gap:              0;
  flex:             1 1 auto;
  min-height:       0;
}

.submit-tabs {
  display:          flex;
  border-bottom:    1px solid var(--border-headline);
  margin-bottom:    20px;
}

.submit-tab {
  flex:             1;
  padding:          10px 0;
  background:       none;
  border:           none;
  border-bottom:    2px solid transparent;
  margin-bottom:    -1px;
  color:            var(--venue-text);
  font-family:      var(--font-body);
  font-size:        calc(10px * var(--type-scale));
  font-weight:      700;
  letter-spacing:   0.14em;
  text-transform:   uppercase;
  cursor:           pointer;
  transition:       color 0.15s, border-color 0.15s;
}

.submit-tab.active {
  color:            var(--gold);
  border-bottom-color: var(--gold);
}

.submit-form {
  display:          flex;
  flex-direction:   column;
  flex:             1 1 auto;
  min-height:       0;
}

.submit-panel {
  display:          none;
}

.submit-panel.active {
  display:          flex;
  flex-direction:   column;
  gap:              16px;
  flex:             1 1 auto;
  min-height:       0;
}

.submit-btn {
  margin-top:       20px;
}

/* ── Submit page — lower-half tracked list (information-only) ─────────────── */

.submit-list-section {
  display:          flex;
  flex-direction:   column;
  gap:              10px;
  flex:             1 1 auto;
  min-height:       0;
}

.submit-list-section .simple-list {
  flex:             1 1 auto;
  min-height:       0;
  overflow-y:       auto;
  padding:          0;
}

.submit-field-error {
  font-family:      var(--font-body);
  font-size:        calc(12px * var(--type-scale));
  color:            rgba(200, 60, 60, 0.90);
  margin-top:       4px;
}

.submit-success {
  background:       var(--tint-active);
  border:           1px solid var(--border-success);
  color:            var(--gold);
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  text-align:       center;
  padding:          14px 16px;
  margin-bottom:    16px;
}

/* ── Nav badge (admin-only pending count) ────────────────────────────────── */

/* Shared by both unread "(N)" badge locations — The Scene tab's count
   (activity_feed.html, inside .feed-tab) and the bottom-nav "News (N)"
   count (inside .bottom-nav-tab, every page's footer nav) — one class,
   one rule, so the two stay guaranteed-identical rather than drifting.
   Explicit font-family here is deliberate: without it this would inherit
   Metal Mania from whichever nav-tab parent it sits in (both .feed-tab
   and .bottom-nav-tab render in that font as of the recent font-matching
   work) — a thin decorative face that doesn't hold up as a plain count
   readout. calc(13px * var(--type-scale)) is ~76% of both parents'
   identical calc(17px * var(--type-scale)) label size — noticeably
   bigger than the old 10px, but still reads as a subordinate badge, not
   competing with the label. */
.nav-badge {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  font-weight:      700;
  color:            var(--badge-text);
  letter-spacing:   0.04em;
}

/* Staff-only pending-Submissions count (§ News unread badge) — same tab
   as the new per-user unread-News count above, so this modifier just
   dims it slightly when both render side by side, keeping the two counts
   visually distinct without inventing a second color. */
.nav-badge--submissions {
  opacity:          0.65;
}

/* ── Help page (Phase 1) ──────────────────────────────────────────────────── */

.help-content {
  /* Same --profile-header-height token as Submit — Help's header markup
     (hamburger/theme-toggle/logo/subtitle, no search) is identical, so
     no new height token was needed (see .app-header.help-header above). */
  padding:          calc(var(--profile-header-height) + env(safe-area-inset-top) + 20px) 20px calc(var(--nav-height) + env(safe-area-inset-bottom) + 24px);
}

.help-welcome-title {
  font-family:      var(--font-display);
  font-size:        calc(24px * var(--type-scale));
  color:            var(--gold);
  margin-bottom:    8px;
}

.help-welcome-body {
  font-family:      var(--font-body);
  font-size:        calc(14px * var(--type-scale));
  line-height:      1.5;
  color:            var(--parchment);
  margin-bottom:    24px;
}

/* No existing tap-to-expand/collapse pattern anywhere else in the
   codebase to reuse — this is a new, minimal accordion, same plain-JS/
   no-framework convention as everything else (see help.html's inline
   script). Multiple sections can be open at once; this is a reference
   page, not a strict either/or toggle like the popup's Details/Comments
   tabs. */
.help-accordion {
  display:          flex;
  flex-direction:   column;
}

.help-item {
  border-bottom:    1px solid var(--card-border-bottom);
}

.help-item-header {
  display:          flex;
  align-items:      center;
  justify-content:  space-between;
  width:            100%;
  padding:          14px 0;
  background:       none;
  border:           none;
  cursor:           pointer;
  text-align:       left;
}

.help-item-title-wrap {
  display:          inline-flex;
  align-items:      baseline;
  gap:              6px;
}

.help-item-title {
  font-family:      var(--font-body);
  font-size:        calc(14px * var(--type-scale));
  font-weight:      700;
  color:            var(--parchment);
}

/* Owner's manual asterisk-on-title replaced with a right-of-title star,
   colored independently via --gold (the app's existing orange accent
   token) rather than inheriting .help-item-title's parchment color. */
.help-item-star {
  font-family:      var(--font-body);
  font-size:        calc(14px * var(--type-scale));
  color:            var(--gold);
}

/* CSS-only chevron (border-right + border-bottom rotated) — closed
   points right, open points down. Cheaper than an SVG/icon font for one
   small rotating indicator. */
.help-item-chevron {
  flex-shrink:      0;
  width:            8px;
  height:           8px;
  margin-left:      12px;
  border-right:     2px solid var(--gold);
  border-bottom:    2px solid var(--gold);
  transform:        rotate(-45deg);
  transition:       transform 0.15s;
}

.help-item.open .help-item-chevron {
  transform:        rotate(45deg);
}

.help-item-panel {
  padding-bottom:   16px;
}

.help-item-panel p {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  line-height:      1.5;
  color:            var(--venue-text);
  margin:           0;
}

.help-item-panel p + .help-step-group {
  margin-top:       14px;
}

/* iOS/Android numbered walkthroughs (§ Add to Home Screen redesign) —
   stacked, not side-by-side: this page never renders wider than a
   phone viewport, so two columns would cramp both platforms rather
   than clarify either. A top border on the second group is the
   "divider," not a card/background treatment — consistent with this
   page's existing plain, no-decoration accordion look. */
.help-step-group + .help-step-group {
  margin-top:       16px;
  padding-top:      16px;
  border-top:       1px solid var(--card-border-bottom);
}

.help-step-group-title {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  font-weight:      700;
  color:            var(--gold);
  margin:           0 0 6px;
}

.help-step-list {
  font-family:      var(--font-body);
  font-size:        calc(13px * var(--type-scale));
  line-height:      1.5;
  color:            var(--venue-text);
  margin:           0;
  padding-left:     18px;
}

.help-step-list li {
  margin-bottom:    4px;
}

.help-step-list li:last-child {
  margin-bottom:    0;
}

/* Inline icon on the step naming the exact button to look for — small
   enough to sit mid-sentence, not a standalone graphic. currentColor +
   --gold (via color, set here) matches the header hamburger/theme-
   toggle icons' own stroke-based convention, not a new icon style. */
.help-step-icon {
  display:          inline-flex;
  align-items:      center;
  vertical-align:   -2px;
  width:            14px;
  height:           14px;
  margin-right:     3px;
  color:            var(--gold);
}

.help-item-panel p.help-step-outro {
  margin-top:       14px;
  font-style:       italic;
}
