/* ============================================================
 * v19 (Edit #22 — Brent 2026-07-18): Three refinements.
 *
 * 1. Resize recent activity + connected nodes panels to fit half-page
 *    each, side-by-side, more compact.
 *    - .two-col: changed grid from 1.5fr 1fr → 1fr 1fr (true 50/50 split)
 *    - .two-col .panel: padding 1.25rem → .85rem (tighter)
 *    - .two-col .panel-head h2: 1.25rem → 1.05rem
 *    - .two-col .panel-head margin: 1rem → .55rem
 *    - .two-col .panel-lede: .9rem → .78rem
 *    - .activity-row / .node-row: padding tightened, font 0.85→0.78rem
 *    Mobile fallback (column-count reductions at <720px) preserved.
 *
 * 2. Lower opacity on module accent colors (Brent: "too bright").
 *    Brent asked for color-mix transparency (~75%) so the hue is
 *    preserved but the saturation against the dark glass is softer.
 *    Implemented via color-mix(in srgb, var(--mod-X) 75%, transparent)
 *    which is supported in Chrome 111+, Firefox 113+, Safari 16.2+.
 *    - .mod-letter: background now color-mix 75% alpha
 *    - .activity-row .mod-pill[data-letter="A-G"]: 0.85 → 0.65 alpha
 *    - .node-row .mod-badge[data-module="A-G"]: per-module 0.75 alpha
 *
 * 3. Match colors between module cards and connected nodes.
 *    Added data-module="A-G" attribute on each .node-row (JS-side, in
 *    preview-v13.js renderNodes). CSS now mirrors per-letter accents:
 *    same color palette as the module letter badge and activity pill.
 *
 * v18 features preserved: production --mod-X palette (blue/green/
 * purple/amber/red/teal/cyan), white description, separator, min-height
 * 2-line station reservation, QRZ.com links, recent-activity black-on-
 * saturated module pills.
 * ============================================================ */
/* XLX159 preview — Dashboard prototype
 * Preserves the dark/glass look from xlx159.whiskey7.site but rebuilt
 * with a single-page dashboard layout. Background image IS visible —
 * body.hero-bg loads assets/images/hero.jpg with dark gradient overlay.
 * NO CSS leaks into the live site (this file is local to /var/www/xlx159-preview/).
 *
 * v17 changes (Edit #17 — Brent: recent-activity module pill colors):
 *   - .activity-row .mod-pill now tints by module letter, matching the
 *     letter badge color on the matching module card above. So a
 *     station on Module C shows an orange "C" pill, Module B shows
 *     indigo "B", Module E shows mid-blue "E", etc.
 *   - Implemented via data-letter attribute (emitted by renderRecent in
 *     preview-v13.js, line 353 — el() helper already supports dataset).
 *   - Base .mod-pill rule kept its layout (padding, font, border-radius,
 *     min-width); hardcoded cyan background/color/border removed and
 *     replaced by per-letter rules using the same --mod-X variables
 *     defined in :root (lines 798-807). Same color palette = same
 *     per-letter WCAG AA contrast as the module letter badges above.
 *   - Tint pattern: ~12% bg + colored border + colored text, mirroring
 *     the existing pill aesthetic (tinted chip, not solid color block).
 *     Stays visually subtle so 7×N rows don't create color noise in the
 *     activity feed; the saturated letter badge stays as the anchor in
 *     the module cards above.
 *   - All v16 features preserved (description wrap fix, E/F colors,
 *     16ch station chips, count row, no border-top divider).
 *
 * v16 changes (Edit #16 — Brent: description wrapping to 2nd line):
 *   - Root cause: preview-v13.js renders the bottom-of-card description
 *     as <p class="mod-name">m.name</p> (line 323), but the CSS only
 *     had a rule for .mod-description. So white-space:nowrap + ellipsis
 *     never applied and long names like "Wheels N Keels TGIF 11159
 *     Allstar 678350" wrapped to a 2nd line.
 *   - Fix: added a .mod-name rule that mirrors .mod-description exactly.
 *     Long names now truncate with "…" on one line, short names display
 *     unchanged. CSS-only fix — no JS change, no markup change.
 *   - All v15 features preserved: state pill colors, station list 16ch
 *     chips, count row, no border-top divider, E/F letter accents.
 *
 * v14 changes (Edit #14 — Brent: module card tweaks after v13 eyeball):
 *   - --mod-e: slate #94a3b8 → mid blue #3b82f6 (Tailwind blue-500,
 *     5.94:1 contrast on dark glass — WCAG AA)
 *   - --mod-f: pink #f472b6 → red #ef4444 (Tailwind red-500, 5.36:1
 *     contrast on dark glass — WCAG AA)
 *   - New --mod-idle: #94a3b8 (slate). Was reusing --mod-e for the idle
 *     state left stripe; now decoupled so the E letter can be blue
 *     without turning every idle module's left stripe blue.
 *   - .mod-card: gap .35 → .3rem, padding .55/.7/.6 → .5/.7/.35rem.
 *     Tighter bottom padding removes the empty line below the description.
 *   - .mod-card[data-state="idle"]: border-left-color now var(--mod-idle)
 *     (was var(--mod-e)).
 *   - .mod-card[data-letter="E"] .mod-station: text tint #cbd5e1 → #93c5fd
 *     (blue-300 — matches the new blue accent).
 *   - .mod-description: now ONE LINE only. font-size .78 → .68rem,
 *     white-space: nowrap, overflow hidden + text-overflow ellipsis.
 *     Removed -webkit-line-clamp:2 + display:-webkit-box. Long names
 *     truncate with "…". min-width:0 added so ellipsis works inside the
 *     flex card column.
 *   - WCAG: --accent-2 (amber TX) is unchanged. State pill colors are
 *     unchanged. The E (mid blue) and F (red) accents stay well clear
 *     of state-pill hues (no overlap with Connected green, TX amber,
 *     or Active sky).
 *
 * v6 changes (Edit #2 + #3):
 *   - Module A-G cards compacted (less padding, smaller font, less min-height)
 *   - New .mod-count line showing connected stations/nodes count per module
 *   - PayPal donate button styled with PayPal brand yellow (#FFC439) +
 *     dark blue text (#003087) + bold + subtle shadow
 *   (DMR/YSF/NXDN/P25 protocol code lines are stripped in preview-v6.js,
 *    not here — CSS has no .mod-codes block visible in cards anymore.)
 */
/*
 * v11 changes (Edit #11 — Brent: hero layout polish after v10 eyeball):
 *   - .hero-text h1: dropped `max-width: 22ch`. The 22ch ceiling forced
 *     "One Reflector. Six Digital Modes." to wrap to a second line on
 *     desktop because the left grid column was being squeezed by the
 *     wider right column (3 buttons). h1 now flows naturally and stays
 *     on ONE line at viewport widths >= ~1100px; still wraps gracefully
 *     on phones (<540px) where the grid collapses to one column.
 *   - .hero-text column-gap: 1.25rem → 0.85rem. The wide gap left a
 *     conspicuous gap of dead space between the end of h1 and the start
 *     of "Get connected". Buttons now sit close to h1 as Brent intended.
 *   - .btn-row.btn-row-centered > .donate-btn: dropped `max-width: 11rem`.
 *     The PayPal button (PP mark + heart SVG + "Support the network"
 *     text) needed more horizontal room than the .btn siblings. The .btn
 *     rule keeps the 11rem cap (Get connected / Module guides fit fine).
 *     donate-btn now sizes to its own content with `flex: 0 1 auto` +
 *     `white-space: nowrap` (already set in v9).
 *   - NO JS changes — pure CSS fix (preview-v9.js still serves).
 *   - h1 font-size UNCHANGED (still clamp(1.5rem, 2.8vw, 2rem)).
 *   - PayPal colors UNCHANGED (yellow #FFC439, blue #003087).
 *   - Mobile (<540px): all rules above are scoped to desktop grid;
 *     phone layout still reverts to single-column stacked buttons.
 *
 * v10 changes (Edit #10 — Brent: buttons UP and to the RIGHT, shorter hero):
 *   - .hero-text is now a 2-column grid: row 1 is "title (left, 1fr) | buttons
 *     (right, auto)", row 2 is "lede (spans full width)". Buttons no longer
 *     sit centered below the title — they cluster top-right, beside the h1.
 *   - .hero-card padding tightened: .9/.65rem (was .9/1rem). Reclaims ~12px
 *     of vertical real estate so the modules panel starts higher on the page.
 *     h1 font-size UNCHANGED (still clamp(1.5rem, 2.8vw, 2rem)).
 *   - .btn-row.btn-row-centered: justify-content center → flex-end (right-
 *     aligned). Buttons are now content-width (flex 0 1 auto, was 1 1 0) with
 *     a smaller max-width cap (11rem, was 13rem) so they fit cleanly in the
 *     right column. PayPal button still pops yellow #FFC439 — color untouched.
 *   - .proto-tag.proto-tag-corner: moved from top-right to BOTTOM-right of
 *     .hero-card (top: .55rem → bottom: .5rem) so it doesn't fight with the
 *     buttons now occupying the top-right corner. Still stripped at promotion.
 *   - Mobile (<540px): .hero-text reverts to single-column stack (title →
 *     buttons → lede); buttons go full-width equal-sized (matches v9 feel).
 *
 * v9 changes (Edit #9 — Brent: hero slim + symmetric CTAs):
 *   - REMOVED .hero-stats / .stat / .stat-num / .stat-lbl rules. The 3 tiles
 *     (module links / endpoints / XLX peer) are gone from the hero card;
 *     JS no longer writes to data-stat-* nodes either.
 *   - HERO CARD is now single-column (the 2fr/1fr split was text + stats).
 *     Padding tightened (1rem → .9rem top, → 1rem bottom) so the modules
 *     panel starts higher on the page. h1 size + button sizes unchanged.
 *   - .btn-row gets a new .btn-row-centered modifier: equal-width, centered,
 *     gap-spaced flex. Each CTA gets flex: 1 1 0 with a max-width cap so
 *     they share width perfectly without becoming unwieldy on wide screens.
 *   - .proto-tag moved out of .btn-row to a corner badge (.proto-tag-corner)
 *     so it doesn't disrupt button symmetry. Stripped at promotion time.
 *
 * v8 changes (Edit #8 — Brent: condense header):
 *   - REMOVED the full-width health strip (.health-strip / .health-row)
 *     from above the main grid. That space was crowding the hero card.
 *   - ADDED .health-fresh-top — a 1-line sliver directly under the sticky
 *     site-header containing ONLY the "Updated Xs ago" timestamp.
 *   - ADDED .health-info-footer — a new panel at the BOTTOM of the page
 *     (above the prototype banner) that holds the rest of the health info:
 *     ONLINE pill, Uptime, Version, TX now, Refresh button. Compact
 *     5-column grid, same dark-glass panel aesthetic.
 *   - .health-pill / .health-meta / .refresh-btn rules kept (they're
 *     still used by the footer block), but the .health-row layout is gone.
 */

/* ========== Design tokens (mirror live site's palette) ========== */
:root {
  --bg-deep:    #0a0e14;
  --bg-card:    rgba(15, 23, 42, 0.45);  /* lightened — matches live site glass; 55% photo bleeds through */
  --bg-card-2:  rgba(15, 23, 42, 0.55);  /* was 0.78 — too opaque, hid the tower photo */
  --border:     rgba(148, 163, 184, 0.18);
  --border-strong: rgba(148, 163, 184, 0.35);
  --text:       #e2e8f0;
  --text-dim:   #94a3b8;
  --text-mute:  #64748b;
  --accent:     #38bdf8;    /* sky-400 — same as live site */
  --accent-2:   #f59e0b;    /* amber-500 — used for "On Air" */
  --ok:         #22c55e;    /* green-500 */
  --warn:       #fbbf24;    /* amber-400 */
  --bad:        #ef4444;    /* red-500 */
  /* PayPal brand colors — donate button only */
  --pp-yellow:  #FFC439;
  --pp-yellow-2: #F5BB36;
  --pp-yellow-hover: #FFD15F;
  --pp-blue:    #003087;
  --pp-blue-light: #0070BA;

  --bg:            #0f172a;
  --bg-elev:       #131c2f;
  --bg-elev-2:     #1a253d;
  --font:          system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --radius:        10px;
  --radius-lg:     14px;
  --shadow-3:      0 18px 48px rgba(0, 0, 0, 0.55);
  --danger:        #ef4444;
  --text-faint:    #475569;
  --mono: ui-monospace, "SFMono-Regular", "Cascadia Mono", "Menlo", monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* ========== Reset ========== */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
img { max-width: 100%; display: block; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ========== Background image (HERO) ========== */
body.hero-bg {
  min-height: 100vh;
  font-family: var(--sans);
  color: var(--text);
  /* Background photo + dark gradient overlay — same image as live site */
  background-color: var(--bg-deep);
  background-image:
    /* Lightened overlay so PNW tower photo bleeds through (live-site style).
       Was 0.72→0.88→0.95 (photo only 5-28% visible). Now 0.35→0.45→0.55
       (photo 45-65% visible). */
    linear-gradient(180deg, rgba(10,14,20,0.35) 0%, rgba(10,14,20,0.45) 60%, rgba(10,14,20,0.55) 100%),
    url("../images/hero.jpg");
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
  background-attachment: fixed, fixed;
}
@media (max-width: 900px) {
  body.hero-bg { background-attachment: scroll, scroll; }
}

.skip-link {
  position: absolute; top: -100px; left: 1rem;
  background: var(--accent); color: var(--bg-deep);
  padding: .5rem .9rem; border-radius: 999px; font-weight: 600;
  z-index: 200;
}
.skip-link:focus { top: 1rem; }

/* ========== Layout containers ========== */
.container {
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 1.25rem;
}
.main-grid {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding-top: 1rem;
  padding-bottom: 3rem;
}

/* ========== Header ========== */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: linear-gradient(180deg, rgba(10,14,20,0.92), rgba(10,14,20,0.78));
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .75rem 1.25rem;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: .65rem;
  color: var(--text);
  font-weight: 700;
}
.brand:hover { text-decoration: none; }
.brand-mark {
  display: inline-flex;
  align-items: center;
  gap: .65rem;
}
.brand-mark img {
  border-radius: 50%;
  background: rgba(56,189,248,0.12);
  padding: 2px;
}
.brand-text {
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  display: block;
  line-height: 1.15;
}
.brand-stack {
  display: inline-flex;
  flex-direction: column;
  gap: .1rem;
  line-height: 1.1;
}
.header-tagline {
  display: block;
  font-size: .72rem;
  font-weight: 500;
  color: var(--text-dim);
  letter-spacing: .04em;
  text-transform: uppercase;
  line-height: 1.1;
}
.badge {
  display: inline-block;
  margin-left: .35rem;
  padding: .12rem .5rem;
  background: rgba(56,189,248,0.15);
  color: var(--accent);
  border: 1px solid rgba(56,189,248,0.35);
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  vertical-align: middle;
}
.site-nav {
  display: flex;
  align-items: center;
  gap: .25rem;
  flex-wrap: wrap;
}
.site-nav a {
  padding: .45rem .8rem;
  border-radius: .45rem;
  color: var(--text);
  font-size: 1rem;
  font-weight: 600;
}
.site-nav a:hover { background: rgba(56,189,248,0.08); color: var(--text); text-decoration: none; }
.site-nav a[aria-current="page"] { background: rgba(56,189,248,0.15); color: var(--accent); }
.site-nav a.ext {
  border: 1px solid var(--border-strong);
  color: var(--accent);
}

/* ========== Health: TOP sliver + FOOTER panel (v8) ==========
   v8 split the old single .health-strip into two pieces:
     1. .health-fresh-top — a 1-line sliver directly under the sticky
        site-header showing ONLY "Updated Xs ago". Reclaims the rest of
        the vertical real estate that used to be the health strip.
     2. .health-info-footer — a panel at the bottom of the page (above
        the prototype banner) holding the rest: ONLINE pill, Uptime,
        Version, TX now, and the Refresh button. Compact 5-column grid.

   The .health-pill / .health-meta / .refresh-btn base rules below are
   reused in the footer block (kept verbatim from v7).
   The old .health-strip / .health-row rules are removed. */

/* --- TOP sliver (only "Updated Xs ago" at the top) --- */
.health-fresh-top {
  display: flex;
  justify-content: flex-end;     /* right-aligned — feels like a meta chip */
  align-items: center;
  padding: .35rem 1.25rem;
  border-top: 1px solid rgba(148,163,184,0.08);
  font-size: .78rem;
  color: var(--text-mute);
  background: linear-gradient(180deg, rgba(10,14,20,0.78), rgba(10,14,20,0.62));
}
.health-fresh-top .health-meta {
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
}

/* --- FOOTER panel --- */
.health-info-footer .panel-head { margin: 0 0 .75rem 0; }
.health-info-footer .panel-head h2 { font-size: 1.1rem; }
.health-info-footer .panel-lede code {
  font-family: var(--mono);
  background: rgba(56,189,248,0.1);
  padding: .05rem .35rem;
  border-radius: .25rem;
  font-size: .9em;
  color: var(--accent);
}
.health-info-grid {
  display: grid;
  grid-template-columns: 1fr;                /* mobile: stack */
  gap: .65rem;
}
@media (min-width: 600px) {
  .health-info-grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 980px) {
  /* 5-column desktop grid: status | uptime | version | tx | action */
  .health-info-grid { grid-template-columns: 1.1fr 1.4fr 1.5fr 1.2fr auto; align-items: end; }
}
.health-info-cell {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  padding: .55rem .7rem;
  background: rgba(10,14,20,0.55);
  border: 1px solid var(--border);
  border-radius: .55rem;
  min-width: 0;
}
.health-info-cell.health-info-action {
  /* match other health-info-cells: same dark bg + border. Refresh button is inside. */
  justify-content: flex-end;
  background: rgba(10,14,20,0.55);
  border: 1px solid var(--border);
  padding: .55rem .7rem;
}
.health-info-lbl {
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-mute);
}
.health-info-val {
  font-size: .9rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Base pill (reused inside footer cell) --- */
.health-pill {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .25rem .75rem;
  border-radius: 999px;
  background: rgba(34,197,94,0.12);
  border: 1px solid rgba(34,197,94,0.35);
  color: #86efac;
  font-weight: 600;
  font-size: .82rem;
  align-self: flex-start;
}
.health-pill[data-state="warn"] {
  background: rgba(251,191,36,0.12);
  border-color: rgba(251,191,36,0.4);
  color: #fde68a;
}
.health-pill[data-state="bad"] {
  background: rgba(239,68,68,0.12);
  border-color: rgba(239,68,68,0.4);
  color: #fecaca;
}
.health-pill[data-state="loading"] {
  background: rgba(148,163,184,0.12);
  border-color: rgba(148,163,184,0.4);
  color: var(--text-dim);
}
.health-pill .dot {
  width: .55rem; height: .55rem; border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 0 currentColor;
  animation: pulse 2.2s ease-out infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(34,197,94,0.55); }
  70%  { box-shadow: 0 0 0 .55rem rgba(34,197,94,0); }
  100% { box-shadow: 0 0 0 0 rgba(34,197,94,0); }
}
.health-meta { color: var(--text-dim); }

/* --- Refresh button (lives inside footer cell now) --- */
.refresh-btn {
  padding: .25rem .75rem;
  background: rgba(56,189,248,0.1);
  color: var(--accent);
  border: 1px solid rgba(56,189,248,0.4);
  border-radius: 999px;
  font-size: .88rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  transition: background .15s;
  min-height: 38px;             /* matches .btn min-height so it lines up */
}
.refresh-btn:hover { background: rgba(56,189,248,0.2); }
.refresh-btn:disabled { opacity: .55; cursor: progress; }

/* ========== Hero card ========== */
/* v6: tightened (was padding 1.5rem, gap 1.25rem) so modules panel + the
   start of recent activity fit above the fold at 1366x768. Brent's goal:
   recent activity must start showing on first screen. */
/* v10: hero card collapsed — h1 on the left, 3 CTA buttons clustered top-right
   (no longer centered), and the card itself is much shorter vertically. Layout
   uses a 2-column grid inside .hero-text:
     row 1: "title" (left col, 1fr) | "buttons" (right col, auto)
     row 2: "lede"  (spans full width)
   Padding tightened (.9/.65rem instead of .9/1rem) to reclaim real estate so
   the modules panel starts higher. h1 font-size UNCHANGED per Brent — keep
   clamp(1.5rem, 2.8vw, 2rem). Button sizes slightly smaller (max-width 13rem
   → 11rem, content-width instead of flex:1 1 0) so they fit cleanly in the
   right column without crowding the h1. On phones (<540px) the grid reverts
   to a single column with buttons stacked full-width below the title. */
.hero-card {
  display: block;
  position: relative;
  padding: .7rem 1.15rem .65rem;       /* v10: was .9rem 1.15rem 1rem — reclaim ~12px vertical */
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: .85rem;
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
}
/* v10: 2-column grid — h1 left, buttons right; lede spans full width below. */
.hero-text {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "title   buttons"
    "lede    lede";
  align-items: start;
  column-gap: .85rem;                 /* v11: was 1.25rem — tighten gap between h1 and buttons */
  row-gap: .15rem;
}
.hero-text h1 {
  grid-area: title;
  margin: 0;                            /* v10: was 0 0 .4rem 0 — no bottom margin now, grid handles spacing */
  font-size: clamp(1.5rem, 2.8vw, 2rem);/* UNCHANGED — Brent's explicit ask */
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-weight: 700;
  /* v11: dropped max-width: 22ch so h1 flows to its natural width and
     stays on ONE line ("One Reflector. Six Digital Modes.") instead of
     wrapping when the left column gets squeezed by the right column's
     buttons. Phones (<540px) still wrap gracefully via grid stack. */
}
.hero-text .accent { color: var(--accent); }
/* v10: lede now spans the full width below the title/buttons row. */
.lede {
  grid-area: lede;
  margin: .1rem 0 0 0;                  /* v10: was 0 0 .75rem 0 — flush with the row above */
  color: var(--text-dim);
  font-size: .9rem;                     /* v10: was .95rem — slight shrink to match the tighter card */
  line-height: 1.35;                    /* v10: was 1.4 — tighten */
  max-width: 60ch;
}
.lede code {
  font-family: var(--mono);
  background: rgba(56,189,248,0.1);
  padding: .05rem .35rem;
  border-radius: .25rem;
  font-size: .9em;
  color: var(--accent);
}
.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: center;
}
/* v10: .btn-row-centered — now lives in the right column of the hero-text grid
   (via grid-area: buttons). Buttons are right-aligned (not centered) and
   content-sized with a slightly smaller max-width so they cluster neatly in
   the top-right corner without crowding the h1. The .proto-tag has been
   moved to the bottom-right of the hero card so it doesn't fight with the
   buttons in the top-right (was top-right in v9). */
.btn-row.btn-row-centered {
  grid-area: buttons;
  justify-content: flex-end;       /* v10: was center — now right-aligned */
  align-self: start;               /* v10: anchor to the top of the grid row */
}
.btn-row.btn-row-centered > .btn {
  flex: 0 1 auto;                  /* v10: was 1 1 0 — content-width, no longer forced equal */
  min-width: 0;
  justify-content: center;         /* center the label/icon inside each button */
  max-width: 11rem;                /* v10: was 13rem — slight shrink so they fit in the right column */
}
/* v11: PayPal button needs more room than .btn siblings (PP mark + heart SVG
   + "Support the network" text overflowed at 11rem). Drop the max-width cap
   and let .donate-btn size to its content. white-space: nowrap (from .donate-btn
   base rule) keeps the label on one line. */
.btn-row.btn-row-centered > .donate-btn {
  flex: 0 1 auto;
  min-width: 0;
  justify-content: center;
  /* max-width intentionally omitted — donate-btn shrinks to its content */
}
@media (max-width: 540px) {
  /* On phones, fall back to single column with full-width stacked buttons. */
  .hero-text {
    grid-template-columns: 1fr;
    grid-template-areas:
      "title"
      "buttons"
      "lede";
  }
  .btn-row.btn-row-centered {
    justify-content: stretch;
  }
  .btn-row.btn-row-centered > .btn,
  .btn-row.btn-row-centered > .donate-btn {
    flex: 1 1 0;                   /* phones get the equal-width v9 feel back */
    max-width: none;
  }
}
.btn {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .55rem 1rem;
  border-radius: .5rem;
  font-size: .92rem;
  font-weight: 600;
  font-family: inherit;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
}
.btn:hover { text-decoration: none; }
.btn-primary {
  background: var(--accent);
  color: var(--bg-deep);
  border-color: var(--accent);
}
.btn-primary:hover { background: #7dd3fc; border-color: #7dd3fc; }
.btn-secondary {
  background: rgba(56,189,248,0.08);
  color: var(--accent);
  border-color: rgba(56,189,248,0.35);
}
.btn-secondary:hover { background: rgba(56,189,248,0.18); }
.proto-tag {
  display: inline-block;
  padding: .3rem .65rem;
  background: rgba(251,191,36,0.12);
  border: 1px solid rgba(251,191,36,0.4);
  border-radius: 999px;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .06em;
  color: #fde68a;
}
/* v10: corner badge variant — moved from top-right to bottom-right of
   .hero-card so it doesn't overlap with the 3 CTA buttons now clustered
   in the top-right (v9 had it top-right, but v10 reclaimed that corner
   for the buttons per Brent's Edit #10 directive). Stripped at promotion
   time per the skill runbook. */
.proto-tag.proto-tag-corner {
  position: absolute;
  bottom: .5rem;                  /* v10: was top: .55rem */
  right: .75rem;                   /* unchanged */
  margin: 0;
  z-index: 1;
}
@media (max-width: 540px) {
  /* On phones, the corner can clip if title wraps to 3 lines; hide it. */
  .proto-tag.proto-tag-corner { display: none; }
}
/* v9: REMOVED .hero-stats / .stat / .stat-num / .stat-lbl rules. The 3 tiles
   are gone from the hero card and from the markup. The corresponding
   updateStats() function in preview-v9.js was also removed (it wrote to
   data-stat-active/nodes/heard nodes that no longer exist). */

/* ===== PayPal donate button (Edit #3: brand yellow + dark blue + bold + shadow)
   PayPal yellow #FFC439 with PayPal dark blue #003087 text — WCAG AA 7.5:1
   contrast, easily passes. Stands out against the dark glass hero. ===== */
.donate-btn {
  order: 99;
  display: inline-flex;
  white-space: nowrap;
  align-items: center;
  gap: .45rem;
  padding: .55rem 1rem;                  /* v7: harmonized to match .btn */
  background: linear-gradient(180deg, var(--pp-yellow) 0%, var(--pp-yellow-2) 100%);
  color: var(--pp-blue);
  border: 1px solid #E5B431;            /* slight darker yellow rim */
  border-radius: .5rem;                  /* v7: harmonized to match .btn */
  font-size: .92rem;                     /* v7: harmonized to match .btn */
  font-weight: 700;                      /* v7: slightly reduced from 800 to align with .btn-secondary */
  letter-spacing: .01em;
  text-decoration: none;
  min-height: 38px;                      /* v7: ensures all header buttons share baseline */
  /* Subtle shadow + warm glow so it pops off the dark glass card */
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.35) inset,
    0 2px 8px rgba(255, 196, 57, 0.35),
    0 0 0 1px rgba(0, 48, 135, 0.08);
  transition: background .15s, transform .12s, box-shadow .15s;
}
.donate-btn:hover {
  background: linear-gradient(180deg, var(--pp-yellow-hover) 0%, var(--pp-yellow) 100%);
  border-color: var(--pp-yellow);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.45) inset,
    0 4px 14px rgba(255, 196, 57, 0.5),
    0 0 0 1px rgba(0, 48, 135, 0.12);
  text-decoration: none;
}
.donate-btn:active {
  transform: translateY(1px);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.25) inset,
    0 1px 4px rgba(255, 196, 57, 0.4);
}
.donate-btn:focus-visible {
  outline: 2px solid var(--pp-blue);
  outline-offset: 2px;
}
.donate-btn svg { display: block; flex-shrink: 0; color: var(--pp-blue); }
.donate-btn .pp-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.55rem;
  height: 1.55rem;
  border-radius: 50%;
  background: #ffffff;
  color: var(--pp-blue);
  font-weight: 800;
  font-style: italic;
  font-size: .85rem;
  letter-spacing: -.02em;
  line-height: 1;
  flex-shrink: 0;
  /* small inner shadow so the PP mark sits cleanly on the yellow */
  box-shadow: 0 0 0 1px rgba(0, 48, 135, 0.15);
}

/* ===== Visually-hidden helper (a11y: keep h1 for screen readers, hide visually) ===== */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ===== Header responsive: stack tagline below brand on narrow screens ===== */
@media (max-width: 720px) {
  .header-tagline { font-size: .65rem; }
}

/* ========== Panels (cards) ========== */
/* Note: backdrop-filter blur is already applied below (14px saturate 140% — stronger
   than the spec'd 10px, so we keep it rather than downgrade). */
.panel {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: .85rem;
  padding: 1.25rem;
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  min-width: 0;  /* critical: prevents overflow in grid/flex */
}
/* Defensive fallback: in case any markup uses .card instead of .panel,
   apply the same glass treatment so the photo bleeds through consistently. */
.card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: .85rem;
  padding: 1.25rem;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  min-width: 0;
}
.panel-head {
  margin: 0 0 1rem 0;
  display: flex;
  flex-direction: column;
  gap: .25rem;
}
/* v6: tighten modules panel head so the panel itself is more compact above the fold */
.modules-panel .panel-head { margin: 0 0 .65rem 0; }
.modules-panel .panel-head h2 { font-size: 1.1rem; }
.panel-head h2 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.panel-lede {
  margin: 0;
  font-size: .9rem;
  color: var(--text-dim);
  line-height: 1.45;
}
.panel-foot {
  margin: 1rem 0 0 0;
  font-size: .82rem;
}

/* ========== Two-column grid ========== */
.two-col {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}
@media (min-width: 980px) {
  .two-col { grid-template-columns: 1fr 1fr; }
}

/* v19: tighter padding for the two-column panels so they fit half-page each */
.two-col > .panel {
  padding: .85rem 1rem;
}
.two-col > .panel > .panel-head {
  margin: 0 0 .55rem 0;
  gap: .15rem;
}
.two-col > .panel > .panel-head h2 {
  font-size: 1.05rem;
}
.two-col > .panel > .panel-lede {
  font-size: .78rem;
  line-height: 1.35;
}
.two-col > .panel > .panel-foot {
  margin: .55rem 0 0 0;
  font-size: .75rem;
}

/* ========== Modules grid (A–G) — v6: COMPACT cards ==========
   Edit #2: smaller padding, smaller font, less min-height. No more .mod-codes
   row (DMR/YSF/NXDN/P25 port lines moved out — they're stripped in preview-v6.js).
   New .mod-count line shows connected stations/nodes per module. */
.modules-grid {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: .65rem;
}
@media (min-width: 600px)  { .modules-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 900px)  { .modules-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1200px) { .modules-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ========== v13: Fix v12 module card issues (Edit #13 — Brent) ==========
 * Fixes:
 *   1. Left sliver is STATE-BASED (green Connected / amber TX / gray Idle)
 *      NOT per-module color (Brent's correction).
 *   2. Top row: letter + count + state pill properly spaced (center-aligned,
 *      justify-content: space-between for top-right state pill).
 *   3. Card size reduced (gap .35rem, padding .55/.7/.6rem — back to v11
 *      compactness).
 *   4. Per-module letter BADGE color KEPT (Brent likes those).
 *   5. Stations list: NO cap (handled in JS, see preview-v9.js cap removal).
 *
 * v12 (Edit #12 — partial; reverted by v13):
 *   - .mod-top-row → colored letter badge + stations list (NO CAP)
 *   - .mod-bottom-row → state pill + last heard
 *   - .mod-description → module name at bottom
 *
 * Per-module accent colors (WCAG AA on dark glass #0a0e14):
 *   A: cyan    #22d3ee   (~10.7:1)
 *   B: indigo  #818cf8   (~7.8:1)
 *   C: orange  #fb923c   (~9.7:1)  — shifted from amber #fbbf24 so it doesn't
 *                                   collide with the amber TX state pill
 *   D: teal    #2dd4bf   (~10.4:1) — shifted from emerald #34d399 so it doesn't
 *                                   collide with the green Connected state pill
 *   E: slate   #94a3b8   (~6.9:1)
 *   F: pink    #f472b6   (~7.0:1)
 *   G: violet  #a78bfa   (~8.4:1)
 *
 * Each accent cascades into 3 places:
 *   - .mod-letter background (saturated) + glyph color (white for contrast)
 *   - 4px left border stripe on the card (the colored bar at the leading edge)
 *   - .mod-stations-list glyph color (subtle tint on the callsigns)
 *
 * PRESERVED from v11:
 *   - State pill colors UNCHANGED: amber/TX, green/Connected, gray/Idle
 *   - .mod-codes is still hidden (defensive)
 *   - State-driven card background tint for TX (subtle amber wash) is kept
 *   - Last-heard row preserved
 *   - All breakpoint media queries (modules-grid 1/2/3/4 columns)
 */

/* ===== Per-module accent ===== */
:root {
  --mod-a: #2563eb;   /* blue (production) */   /* cyan   */
  --mod-b: #16a34a;   /* green (production) */   /* indigo */
  --mod-c: #9333ea;   /* purple (production) */   /* orange (NOT amber — TX already uses amber) */
  --mod-d: #d97706;   /* amber (production) */   /* teal   (NOT emerald — Connected already uses green) */
  --mod-e: #dc2626;   /* red (production) */   /* mid blue (v14: was slate #94a3b8) */
  --mod-f: #0d9488;   /* teal (production) - reverted from v44 yellow */
  --mod-g: #d946ef;   /* fuchsia (v46b: was lime, too close to B green/H yellow) */
  --mod-h: #facc15;   /* yellow (v45: changed from lime so H doesn't clash with E red) */
  --mod-idle: #94a3b8; /* v14: idle state stripe — kept slate; E no longer uses slate */
}

/* Default card — idle/unknown. Left stripe is muted gray; idle modules
   get the slate accent as a subtle hint, but only when actually idle. */
.mod-card {
  display: flex;
  flex-direction: column;
  gap: .3rem;                    /* v14: tightened from .35 for more vertical room */
  padding: .5rem .7rem .35rem;   /* v14: tightened bottom from .6 to .35 — removes empty line */
  background: rgba(10,14,20,0.55);
  border: 1px solid var(--border);
  border-left: 4px solid var(--mod-idle);  /* v14: use --mod-idle (slate), not --mod-e (now blue) */
  border-radius: .55rem;
  min-width: 0;
  overflow: hidden;
}

/* State-driven card chrome (left stripe + bg tint) — preserved from v11 */
.mod-card[data-state="active"]   { border-left-color: var(--accent); }
.mod-card[data-state="tx"]       { border-left-color: var(--accent-2); background: rgba(245,158,11,0.07); }
.mod-card[data-state="connected"]{ border-left-color: var(--ok); }
.mod-card[data-state="idle"]     { border-left-color: var(--mod-idle); }   /* v14: was --mod-e */
.mod-card[data-state="unknown"]  { border-left-color: var(--warn); }

/* Per-module accent overrides the default slate stripe when the card is
   idle/connected/tx — but the state colors (TX amber, Connected green)
   still get priority because that info is more important than identity. */
.mod-card[data-letter="A"] { --mod-color: var(--mod-a); }
.mod-card[data-letter="B"] { --mod-color: var(--mod-b); }
.mod-card[data-letter="C"] { --mod-color: var(--mod-c); }
.mod-card[data-letter="D"] { --mod-color: var(--mod-d); }
.mod-card[data-letter="E"] { --mod-color: var(--mod-e); }
.mod-card[data-letter="F"] { --mod-color: var(--mod-f); }
.mod-card[data-letter="G"] { --mod-color: var(--mod-g); }
.mod-card[data-letter="H"] { --mod-color: var(--mod-h); }  /* v46b: added H, was missing so fallback --mod-e (red) was being used */

/* Apply the per-module accent to the left stripe on idle/connected cards.
   For TX, the amber stripe wins (state > identity).
   For Connected, the per-letter color wins over the generic --ok. */
/* ===== Top row: letter badge (large, colorful, per-module) + stations list ===== */
.mod-top-row {
  display: flex;
  align-items: center;         /* center-align so letter + count + state pill line up */
  justify-content: space-between;
  gap: .65rem;
  min-width: 0;
}

.mod-letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.05rem; height: 2.05rem;       /* bumped up for the new "letter is the visual anchor" role */
  border-radius: 50%;
  background: color-mix(in srgb, var(--mod-color, var(--mod-e)) 75%, transparent);  /* v19: softer 75% alpha */
  color: #0a0e14;                        /* dark glyph on saturated accent — max contrast */
  font-weight: 800;
  font-size: 1.05rem;
  font-family: var(--mono);
  flex-shrink: 0;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.06) inset;  /* subtle inner ring */
}
/* State-driven glyph adjustments: TX keeps the saturated accent ring but
   flips the glyph to near-white for legibility against bright amber BG;
   state doesn't replace the module accent — identity always wins on the badge. */
.mod-card[data-state="tx"] .mod-letter {
  color: #1a1100;
}
.mod-card[data-state="connected"] .mod-letter {
  /* Connected state already implies busy — keep the letter color (dark on
     bright accent) for consistency with the badge itself. */
}

/* v15: count number + "stations" label with proper gap. Was unstyled (no class
   rule for .mod-count), so "4 stations" rendered with no space between. */
.mod-count {
  display: flex;
  align-items: baseline;
  gap: .55rem;
  min-width: 0;
}
.mod-count-num {
  font-family: var(--mono);
  font-size: 1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  letter-spacing: -.01em;
}
.mod-count-lbl {
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  font-weight: 600;
  color: var(--text-mute);
}


/* ===== Stations list: multi-line, ALL stations, NO cap =====
   This is the v12 override for .mod-stations-list (v11 defined it at the
   bottom of the file with a 9ch max-width + cap rule). The newer rule wins
   because we appended our module section AFTER it. */
.mod-card .mod-stations-list {
  display: flex;
  flex-wrap: wrap;            /* wrap to multiple lines as needed */
  align-items: flex-start;
  gap: .15rem .35rem;
  font-size: .72rem;
  color: var(--text-dim);
  font-family: var(--mono);
  line-height: 1.4;
  min-width: 0;
  margin: 0;                  /* reset — sits next to badge in .mod-top-row now */
  flex: 1 1 0;                /* take remaining width of the row */
  padding-top: .25rem;        /* nudge baseline to align with letter glyph */
  min-height: 2.6rem;         /* v18: reserve space for 2 lines even if empty/idle */
}
/* Each station chip — no more 9ch ellipsis, lets the full callsign render */
.mod-stations-list .mod-station {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 16ch;            /* v15: 13ch → 16ch so '678350' (6-char AllStar node) fits on one line */
  color: var(--text);
  background: rgba(255,255,255,0.04);
  padding: .05rem .4rem;
  border-radius: .25rem;
}
/* Subtle per-letter tint for visual identity hint */
.mod-card[data-letter="A"] .mod-station { color: #bfdbfe; }   /* v18: blue-200, contrasts with --mod-a #2563eb */
.mod-card[data-letter="B"] .mod-station { color: #bbf7d0; }   /* v18: green-200, contrasts with --mod-b #16a34a */
.mod-card[data-letter="C"] .mod-station { color: #e9d5ff; }   /* v18: purple-200, contrasts with --mod-c #9333ea */
.mod-card[data-letter="D"] .mod-station { color: #fde68a; }   /* v18: amber-200, contrasts with --mod-d #d97706 */
.mod-card[data-letter="E"] .mod-station { color: #fecaca; }   /* v18: red-200, contrasts with --mod-e #dc2626 */
.mod-card[data-letter="F"] .mod-station { color: #99f6e4; }   /* v18: teal-200, contrasts with --mod-f #0d9488 */
.mod-card[data-letter="G"] .mod-station { color: #a5f3fc; }   /* v18: cyan-200, contrasts with --mod-g #06b6d4 */

.mod-stations-list.mod-stations-empty {
  color: var(--text-mute);
  font-style: italic;
}
.mod-stations-list.mod-stations-empty .mod-stations-text {
  background: none;
  padding: 0;
}

/* ===== Description (m.name) — bottom of card ===== v14: ONE LINE only */
/* v16: added .mod-name alias rule — JS renders class="mod-name" but the
   original CSS only matched .mod-description. Without this rule the
   white-space:nowrap + ellipsis never applied and long names wrapped
   to a 2nd line. This rule mirrors .mod-description exactly. */
.mod-name {
  margin: 0;
  font-size: .78rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--text);
  margin-top: .35rem;
  padding-top: .25rem;
  border-top: 1px solid rgba(255,255,255,0.05);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.mod-description {
  margin: 0;
  font-size: .78rem;            /* v18: bigger so it stands out */
  font-weight: 700;              /* v18: bold */
  line-height: 1.2;
  color: var(--text);             /* v18: white-ish for visibility */
  margin-top: .35rem;
  padding-top: .25rem;
  border-top: 1px solid rgba(255,255,255,0.05);  /* v18: subtle separator from stations list */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* ===== Bottom row: state pill + last heard ===== */
.mod-bottom-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  min-width: 0;
  padding-top: .15rem;
  /* v15: removed border-top divider — the line under the stations list was visually a 'bottom line' Brent didn't want */
}

/* State pill — UNCHANGED from v11 (green Connected / amber TX / gray Idle) */
.mod-state-pill {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .15rem .5rem;
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  background: rgba(148,163,184,0.12);
  color: var(--text-dim);
  border: 1px solid rgba(148,163,184,0.25);
  white-space: nowrap;
  flex-shrink: 0;
}
.mod-state-pill::before {
  content: "●";
  font-size: .85em;
}
.mod-card[data-state="tx"]        .mod-state-pill { background: rgba(245,158,11,0.15); color: #fde68a; border-color: rgba(245,158,11,0.45); }
.mod-card[data-state="active"]    .mod-state-pill { background: rgba(56,189,248,0.15); color: #7dd3fc; border-color: rgba(56,189,248,0.45); }
.mod-card[data-state="connected"] .mod-state-pill { background: rgba(34,197,94,0.15);  color: #86efac; border-color: rgba(34,197,94,0.45); }

/* Last heard — same style as v11, sits on the right of the bottom row */
.mod-lastheard {
  font-size: .7rem;
  color: var(--text-dim);
  font-family: var(--mono);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 0 1 auto;
  text-align: right;
}
.mod-card[data-state="tx"] .mod-lastheard { color: var(--accent-2); font-weight: 600; }

/* v6 defensive: hide .mod-codes entirely — kept from v11 */
.mod-codes { display: none !important; }

/* ========== Activity table ========== */
.activity-table { overflow-x: auto; }
.activity-row, .activity-head {
  display: grid;
  grid-template-columns: 1.6fr 1fr 2.4fr 1.4fr;
  gap: .5rem;
  padding: .32rem .5rem;
  align-items: center;
  font-size: .78rem;
  min-width: 0;
}
.activity-head {
  border-bottom: 1px solid var(--border);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-mute);
}
.activity-row {
  border-bottom: 1px solid rgba(148,163,184,0.07);
}
.activity-row:last-child { border-bottom: 0; }
.activity-row > span, .activity-row > a {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.activity-row .call {
  font-family: var(--mono);
  font-weight: 600;
  color: var(--text);
}
.activity-row .via {
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--text-dim);
}
.activity-row .mod-pill {
  display: inline-block;
  padding: .08rem .35rem;
  background: rgba(148,163,184,0.10);                /* v17: neutral fallback tint (slate at 10%) */
  color: var(--text-dim);                            /* v17: neutral fallback text — overridden by data-letter */
  border: 1px solid rgba(148,163,184,0.22);          /* v17: neutral fallback border */
  border-radius: .3rem;
  font-family: var(--mono);
  font-size: .68rem;
  font-weight: 700;
  text-align: center;
  min-width: 1.6rem;
}
/* v17: per-letter accent for activity module pill — mirrors the module
   card's letter-badge color. Same --mod-X vars as :root. Tint pattern:
   ~12% bg + colored border + saturated text. Stays subtle so the feed
   doesn't drown in color; the saturated badge stays on the module card. */
.activity-row .mod-pill[data-letter="A"] { background: rgba(37, 99, 235, 0.65);   color: #0a0e14; border-color: rgba(37, 99, 235, 0.85); }
.activity-row .mod-pill[data-letter="B"] { background: rgba(22, 163, 74, 0.65);   color: #0a0e14; border-color: rgba(22, 163, 74, 0.85); }
.activity-row .mod-pill[data-letter="C"] { background: rgba(147, 51, 234, 0.65);  color: #0a0e14; border-color: rgba(147, 51, 234, 0.85); }
.activity-row .mod-pill[data-letter="D"] { background: rgba(217, 119, 6, 0.65);   color: #0a0e14; border-color: rgba(217, 119, 6, 0.85); }
.activity-row .mod-pill[data-letter="E"] { background: rgba(220, 38, 38, 0.65);   color: #0a0e14; border-color: rgba(220, 38, 38, 0.85); }
.activity-row .mod-pill[data-letter="F"] { background: rgba(13, 148, 136, 0.65);  color: var(--mod-f); border-color: rgba(13, 148, 136, 0.85); }
.activity-row .mod-pill[data-letter="G"] { background: rgba(6, 182, 212, 0.65);   color: #0a0e14; border-color: rgba(6, 182, 212, 0.85); }
.activity-row .mod-pill[data-letter="H"] { background: rgba(250, 204, 21, 0.65);   color: #0a0e14; border-color: rgba(250, 204, 21, 0.85); }  /* v67: added H rule - was missing so H fell back to default styling */
.activity-row .ts {
  font-family: var(--mono);
  color: var(--text-dim);
  font-size: .72rem;
}

/* ========== Nodes / Peers tables (compact) ========== */
.nodes-table, .peers-table {
  overflow-x: auto;
}
.node-row, .peer-row, .np-head {
  display: grid;
  gap: .4rem;
  padding: .32rem .5rem;
  align-items: center;
  font-size: .78rem;
  font-family: var(--mono);    /* v24: match .activity-row font */
  min-width: 0;
}
.node-row {
  /* v33: Station column narrower, Module column wider so the module
     badge isn't truncated at half-page width. Last heard shrunk slightly
     to free space for the Module badge. */
  grid-template-columns: 1fr 1fr 1.4fr .8fr 1fr;
}
.peer-row {
  grid-template-columns: 1.2fr 1.4fr 1.4fr 1fr .9rem;
}
.np-head {
  display: grid;
  /* v33: matches .node-row columns */
  grid-template-columns: 1fr 1fr 1.4fr .8fr 1fr;
  gap: .4rem;
  padding: .32rem .5rem;
  align-items: center;
  border-bottom: 1px solid var(--border);
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-mute);
}
.node-row + .node-row, .peer-row + .peer-row {
  border-top: 1px solid rgba(148,163,184,0.07);
}
.node-row > *, .peer-row > * {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.node-row .call, .peer-row .call {
  font-family: var(--mono);
  font-weight: 600;
  color: var(--text);
}
.mod-badge {
  display: inline-block;
  padding: .08rem .35rem;                          /* v19: tightened to match activity pill */
  background: rgba(148,163,184,0.10);              /* v19: neutral slate fallback (was cyan) */
  color: var(--text-dim);                          /* v19: neutral text (overridden per-module) */
  border: 1px solid rgba(148,163,184,0.22);        /* v19: neutral border */
  border-radius: .3rem;
  font-family: var(--mono);
  font-size: .68rem;                              /* v19: matched to activity pill */
  font-weight: 700;
  text-align: center;
  min-width: 1.6rem;
}
/* v19: per-module accent for .mod-badge — mirrors .mod-letter and
   .activity-row .mod-pill[data-letter] palette. Same --mod-X vars as :root.
   JS emits data-module="A-G" on each .node-row (preview-v13.js renderNodes). */
.node-row[data-module="A"] .mod-badge { background: color-mix(in srgb, var(--mod-a) 75%, transparent); color: #0a0e14; border-color: rgba(37, 99, 235, 0.85); }
.node-row[data-module="B"] .mod-badge { background: color-mix(in srgb, var(--mod-b) 75%, transparent); color: #0a0e14; border-color: rgba(22, 163, 74, 0.85); }
.node-row[data-module="C"] .mod-badge { background: color-mix(in srgb, var(--mod-c) 75%, transparent); color: #0a0e14; border-color: rgba(147, 51, 234, 0.85); }
.node-row[data-module="D"] .mod-badge { background: color-mix(in srgb, var(--mod-d) 75%, transparent); color: #0a0e14; border-color: rgba(217, 119, 6, 0.85); }
.node-row[data-module="E"] .mod-badge { background: color-mix(in srgb, var(--mod-e) 75%, transparent); color: #0a0e14; border-color: rgba(220, 38, 38, 0.85); }
.node-row[data-module="F"] .mod-badge { background: color-mix(in srgb, var(--mod-f) 75%, transparent); color: #0a0e14; border-color: rgba(13, 148, 136, 0.85); }
.node-row[data-module="G"] .mod-badge { background: color-mix(in srgb, var(--mod-g) 75%, transparent); color: #0a0e14; border-color: rgba(217, 70, 239, 0.85); }
.node-row[data-module="H"] .mod-badge { background: color-mix(in srgb, var(--mod-h) 75%, transparent); color: #0a0e14; border-color: rgba(250, 204, 21, 0.85); }
.proto-tag-sm {
  font-size: .72rem;
  padding: .1rem .4rem;
  background: rgba(148,163,184,0.1);
  border-radius: .3rem;
  color: var(--text-dim);
  font-family: var(--mono);
  white-space: nowrap;
}

/* ========== On-Air grid ========== */
.onair-grid {
  display: flex;
  flex-direction: column;
  gap: .5rem;
}
.onair-mod {
  display: flex;
  align-items: center;
  gap: .65rem;
  padding: .55rem .65rem;
  background: rgba(10,14,20,0.55);
  border: 1px solid var(--border);
  border-radius: .55rem;
  min-width: 0;
}
.onair-mod[data-active="1"] {
  background: rgba(245,158,11,0.1);
  border-color: rgba(245,158,11,0.4);
}
.onair-letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.85rem; height: 1.85rem;
  border-radius: 50%;
  background: rgba(56,189,248,0.15);
  color: var(--accent);
  font-family: var(--mono);
  font-weight: 700;
  font-size: .85rem;
  flex-shrink: 0;
}
.onair-mod[data-active="1"] .onair-letter {
  background: rgba(245,158,11,0.25); color: var(--accent-2);
  animation: glow 1.6s ease-in-out infinite alternate;
}
@keyframes glow {
  from { box-shadow: 0 0 0 0 rgba(245,158,11,0); }
  to   { box-shadow: 0 0 0 .4rem rgba(245,158,11,0.18); }
}
.onair-text {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  min-width: 0;
  flex: 1;
}
.onair-text .l1 {
  font-family: var(--mono);
  font-size: .9rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.onair-text .l2 {
  font-size: .78rem;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.onair-mod[data-active="0"] .onair-text .l1 { color: var(--text-dim); font-style: italic; }

/* ========== Links list ========== */
.links-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: .5rem;
  grid-template-columns: 1fr;
}
@media (min-width: 700px) {
  .links-list { grid-template-columns: 1fr 1fr; }
}
.links-list li {
  padding: .55rem .75rem;
  background: rgba(10,14,20,0.55);
  border: 1px solid var(--border);
  border-radius: .5rem;
  font-size: .88rem;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
}
.links-list strong {
  color: var(--accent);
  font-family: var(--mono);
  font-weight: 700;
  margin-right: .25rem;
}

/* ========== Proto banner + footer ========== */
.proto-banner {
  margin: .5rem 0 0 0;
  padding: 1rem 1.25rem;
  background: rgba(251,191,36,0.08);
  border: 1px dashed rgba(251,191,36,0.4);
  border-radius: .65rem;
  color: #fde68a;
  font-size: .88rem;
  line-height: 1.5;
}
.proto-banner strong { color: #fcd34d; }
.proto-banner code {
  font-family: var(--mono);
  background: rgba(0,0,0,0.35);
  padding: .05rem .35rem;
  border-radius: .25rem;
  font-size: .9em;
}
.muted { color: var(--text-mute); font-size: .82rem; }

.site-footer {
  background: rgba(10,14,20,0.85);
  border-top: 1px solid var(--border);
  margin-top: 1rem;
}
.site-footer .container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.25rem;
  font-size: .82rem;
  color: var(--text-dim);
  flex-wrap: wrap;
  gap: .5rem;
}

/* ========== Error state ========== */
.error-card {
  padding: 1rem 1.25rem;
  background: rgba(239,68,68,0.08);
  border: 1px dashed rgba(239,68,68,0.4);
  border-radius: .65rem;
  color: #fecaca;
  font-size: .88rem;
}
.error-card strong { color: #fca5a5; }

/* ========== Responsive table column reductions ========== */
@media (max-width: 720px) {
  .activity-row, .activity-head { grid-template-columns: 1.6fr 1fr 1.6fr; }
  .activity-row > .via, .activity-head > .via-h { display: none; }
  .node-row { grid-template-columns: 1.4fr 1fr .9rem; }
  .node-row > .linked-for, .node-row > .ts-n { display: none; }
  .peer-row { grid-template-columns: 1.4fr 1fr .9rem; }
  .peer-row > .linked-for, .peer-row > .ts-n { display: none; }
}

/* ===== v7: Header button row alignment =====
   .btn-row already uses flex + align-items: center. Make sure .site-nav links
   share the same baseline as the donate button + brand text. */
.site-header .header-row,
.site-header .btn-row {
  align-items: center;
}
.site-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 38px;                        /* v7: matches donate-btn + .btn min-height */
  padding: .5rem .9rem;                    /* v7: explicit padding so vertical centering is consistent */
}


/* v20: QRZ.com link button styling for callsigns in recent activity */
.activity-row .qrz-link {
  display: inline-block;
  padding: .08rem .45rem;
  border-radius: .3rem;
  font-family: var(--mono);
  font-size: .75rem;
  font-weight: 600;
  color: var(--accent);
  background: rgba(56, 189, 248, 0.08);
  border: 1px solid rgba(56, 189, 248, 0.3);
  text-decoration: none;
  transition: background .15s, color .15s, border-color .15s;
  white-space: nowrap;
}
.activity-row .qrz-link:hover {
  background: rgba(56, 189, 248, 0.18);
  color: #38bdf8;
  border-color: rgba(56, 189, 248, 0.6);
}
.activity-row .qrz-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}


/* v36: Production-style live TX glow on recent activity rows.
   When a station is currently transmitting, the upstream XLXD HTML
   adds <img src="./img/tx.gif"> to its row. parseLastheardHTML
   detects that and sets row.transmitting = true. We render a green
   dot before the callsign and a subtle green glow on the whole row. */
.activity-row.is-transmitting {
  background: rgba(34, 197, 94, 0.12);
  border-left: 3px solid rgb(34, 197, 94);
  padding-left: calc(.5rem - 3px);
}
/* v67: ensure transmitting row's mod-pill stays opaque and visible (not washed out by row bg) */
.activity-row.is-transmitting .mod-pill {
  position: relative;
  z-index: 1;
}
.activity-row.is-transmitting:hover {
  background: rgba(34, 197, 94, 0.20);
}
/* v21: Subdued "newest row" backstop highlight — fires only when
   the row is NOT already .is-transmitting (i.e., we're showing the
   latest entry but it's outside the 5min freshness window). Softer
   tint than the TX glow so it doesn't compete visually. */
.activity-row.is-newest {
  background: rgba(148, 163, 184, 0.06);
  border-left: 2px solid rgba(148, 163, 184, 0.45);
  padding-left: calc(.5rem - 2px);
}
.activity-row.is-newest:hover {
  background: rgba(148, 163, 184, 0.12);
}
.activity-row .tx-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgb(34, 197, 94);
  margin-right: .4rem;
  vertical-align: middle;
  box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.6);
  animation: tx-pulse 1.6s ease-in-out infinite;
}
@keyframes tx-pulse {
  0%   { box-shadow: 0 0 0 0   rgba(34, 197, 94, 0.6); }
  70%  { box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);   }
  100% { box-shadow: 0 0 0 0   rgba(34, 197, 94, 0);   }
}


/* v41: Panel-count badge — top-right corner of the connected nodes card.
   Same card size, just uses the existing real-estate. Big number for at-a-
   glance count, accent color for emphasis. */
.panel-count {
  position: absolute;
  top: .55rem;
  right: 1rem;
  font-family: var(--mono);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: -.02em;
  pointer-events: none;
  line-height: 1;
}
.panel {
  position: relative;
}

/* v47: PayPal donate pill — fixed top-right, matches production xlx159.whiskey7.site */
.donate-pill {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .55rem .95rem;
  background: linear-gradient(135deg, #FFC439 0%, #FFB100 100%);
  color: #142a52;
  border: 1px solid rgba(20, 42, 82, 0.35);
  border-radius: 999px;
  font-size: .85rem;
  font-weight: 700;
  text-decoration: none;
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.08) inset;
  transition: transform .15s, box-shadow .15s;
}
.donate-pill:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.12) inset;
}
.donate-pill:active { transform: translateY(0); }
.donate-pill svg { display: block; }
@media (max-width: 720px) {
  .donate-pill { top: auto; bottom: 1rem; right: 1rem; left: 1rem; justify-content: center; }
}

/* v47: UTC running clock — replaces the donate-btn in hero CTA row (3rd slot).
   Monospace pill, ticks every second, dark glassy surface to match the page. */
.utc-clock {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .55rem 1rem;
  background: rgba(56, 189, 248, 0.08);
  color: var(--accent);
  border: 1px solid rgba(56, 189, 248, 0.35);
  border-radius: .5rem;
  font-family: var(--mono);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: .02em;
  white-space: nowrap;
  cursor: default;
  user-select: none;
}
.utc-clock .clock-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  opacity: .85;
}
.utc-clock .clock-label {
  color: var(--text-dim);
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: .04em;
  margin-left: .15rem;
}


/* ===== Contact modal + button (mirrors production styles.css .contact-*) ===== */
.contact-modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(0.75rem, 3vw, 2rem);
}
.contact-modal[hidden] { display: none; }

.contact-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(2, 6, 12, 0.65);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  cursor: pointer;
}
.contact-modal-panel {
  position: relative;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  width: 100%;
  max-width: 36rem;
  max-height: calc(100vh - 4rem);
  max-height: calc(100dvh - 4rem);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: contact-modal-in 160ms ease-out;
}
@keyframes contact-modal-in {
  from { transform: translateY(8px) scale(0.98); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
.contact-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem 1.4rem 0.75rem;
  border-bottom: 1px solid var(--border);
}
.contact-modal-head h2 {
  margin: 0 0 0.3rem;
  font-size: 1.15rem;
  color: var(--text);
  font-family: var(--font);
}
.contact-modal-lede {
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-dim);
  line-height: 1.45;
}
.contact-modal-close {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text-dim);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.contact-modal-close:hover {
  background: var(--bg-elev-2);
  color: var(--text);
}
.contact-modal-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.contact-modal .contact-form {
  padding: 1rem 1.4rem 1.4rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.contact-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem 0.85rem;
}
.contact-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.contact-field-wide { grid-column: 1 / -1; }
.contact-field input,
.contact-field select,
.contact-field textarea {
  width: 100%;
  padding: 0.55rem 0.7rem;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-family: var(--font);
  font-size: 0.92rem;
  outline: none;
}
.contact-field input:focus,
.contact-field select:focus,
.contact-field textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.25);
}
.contact-field textarea { resize: vertical; min-height: 6.5rem; }
.contact-label {
  font-size: 0.78rem;
  color: var(--text-dim);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.contact-optional {
  color: var(--text-faint);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
}
.contact-help {
  font-size: 0.78rem;
  color: var(--text-faint);
  min-height: 1em;
}
.contact-actions {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex-wrap: wrap;
}
.contact-submit {
  padding: 0.7rem 1.1rem;
  background: var(--accent);
  color: #061018;
  border: none;
  border-radius: 999px;
  font-weight: 700;
  font-family: var(--font);
  cursor: pointer;
}
.contact-submit:hover { background: var(--accent-2); color: #061018; }
.contact-submit:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}
.contact-submit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.contact-note {
  font-size: 0.78rem;
  color: var(--text-faint);
  flex: 1 1 12rem;
}
.contact-result {
  font-size: 0.85rem;
  min-height: 1.2em;
}
.contact-result.is-success { color: var(--ok); }
.contact-result.is-error   { color: var(--danger); }

/* Contact button in the nav (production's button with blue outline) */
.site-nav button[data-contact-open] {
  color: #061018;
  background: var(--accent);
  font-weight: 600;
  padding: 0.5rem 1.1rem;
  border-radius: 999px;
  margin-left: 0.4rem;
}
.site-nav button[data-contact-open]:hover {
  background: var(--accent-2);
  color: #061018;
}
.site-nav button[data-contact-open]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}


/* ============================================================
 * XLX159 staging additions (Brent, 2026-07-21)
 * Moved from inline <style> in tmp-homepage.html for prod parity.
 * ============================================================ */

/* TEMP: Right-column stack — Connected Nodes + AllStar Nodes as separate cards */
.right-col-stack {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
  height: 100%;
}
.right-col-stack > #nodes { flex: 0 1 auto; }
.right-col-stack > #allstar { flex: 1 1 auto; }

/* TEMP: Condensed AllStar cards (v7) */
.allstar-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.6rem;
}
.allstar-node-card {
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--border);
  border-radius: 0.55rem;
  padding: 0.6rem 0.75rem;
  min-width: 0;
}
.allstar-head {
  margin-bottom: 0.4rem;
}
.allstar-title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.3rem;
}
.allstar-callsign {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--accent, #38bdf8);
  font-family: 'Courier New', monospace;
  line-height: 1;
}
.allstar-num {
  font-size: 0.7rem;
  color: var(--text-dim, #94a3b8);
}
.allstar-meta {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  font-size: 0.78rem;
  line-height: 1.35;
}
.meta-row {
  display: grid;
  grid-template-columns: 70px 1fr;
  gap: 0.4rem;
}
.meta-lbl {
  color: var(--text-dim, #94a3b8);
  text-transform: uppercase;
  font-size: 0.66rem;
  letter-spacing: 0.03em;
}
.meta-val {
  color: var(--text, #e2e8f0);
}
.allstar-dashboard-btn {
  display: inline-block;
  font-size: 0.74rem;
  color: var(--accent, #38bdf8);
  text-decoration: none;
  margin: 0.25rem 0 0.4rem;
}
.allstar-dashboard-btn:hover { text-decoration: underline; }
.allstar-peers { margin-top: 0.25rem; }
.peers-head {
  font-size: 0.66rem;
  color: var(--text-dim, #94a3b8);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.2rem;
}
.linked-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.5rem;
  padding: 0.15rem 0;
  font-size: 0.76rem;
  line-height: 1.3;
}
.linked-callsign {
  color: var(--accent, #38bdf8);
  font-family: 'Courier New', monospace;
  font-weight: 600;
}
.linked-meta {
  color: var(--text-dim, #94a3b8);
  font-size: 0.7rem;
}
.linked-empty {
  font-size: 0.74rem;
  color: var(--text-dim, #94a3b8);
  font-style: italic;
}

    
#nodes-total::after {
  content: " online";
  margin-left: 0.15em;
  opacity: 0.65;
}


/* TEMP: AllStar cards v8 — 2-column inside each card */
.allstar-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.6rem;
}
.allstar-node-card {
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--border);
  border-radius: 0.55rem;
  padding: 0.6rem 0.75rem;
  min-width: 0;
}
.allstar-2col {
  display: grid !important;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) !important;
  gap: 0.85rem;
  align-items: start;
  min-width: 0;
}
.allstar-left {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}
.allstar-right {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
}
.allstar-title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.allstar-callsign {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--accent, #38bdf8);
  font-family: 'Courier New', monospace;
  line-height: 1;
}
.allstar-num {
  font-size: 0.7rem;
  color: var(--text-dim, #94a3b8);
}
.allstar-meta {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  font-size: 0.78rem;
  line-height: 1.35;
}
.meta-row {
  display: grid;
  grid-template-columns: 70px 1fr;
  gap: 0.4rem;
  min-width: 0;
}
.meta-lbl {
  color: var(--text-dim, #94a3b8);
  text-transform: uppercase;
  font-size: 0.66rem;
  letter-spacing: 0.03em;
}
.meta-val {
  color: var(--text, #e2e8f0);
  overflow-wrap: anywhere;
}
.allstar-dashboard-btn {
  display: inline-block;
  font-size: 0.74rem;
  color: var(--accent, #38bdf8);
  text-decoration: none;
  margin-top: 0.1rem;
}
.allstar-dashboard-btn:hover { text-decoration: underline; }
.peers-head {
  font-size: 0.66rem;
  color: var(--text-dim, #94a3b8);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.25rem;
}
.linked-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4rem;
  padding: 0.1rem 0;
  font-size: 0.76rem;
  line-height: 1.25;
  min-width: 0;
}
.linked-callsign {
  color: var(--accent, #38bdf8);
  font-family: 'Courier New', monospace;
  font-weight: 600;
}
.linked-meta {
  color: var(--text-dim, #94a3b8);
  font-size: 0.7rem;
  overflow-wrap: anywhere;
}
.linked-empty {
  font-size: 0.74rem;
  color: var(--text-dim, #94a3b8);
  font-style: italic;
}
    

/* TEMP: Refresh button alignment — match other health cell baselines */
.health-info-grid { align-items: center; }
.health-info-action {
  display: flex;
  align-items: center;
  justify-content: center;
}
    

/* TEMP: TX NOW live indicator */
.tx-now-state {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 0.85rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-right: 0.35rem;
  background: rgba(34,197,94,0.15);
  color: #22c55e;
}
.tx-now-state.is-onair {
  background: rgba(239,68,68,0.18);
  color: #ef4444;
  animation: txnow-pulse 1.4s ease-in-out infinite;
}
.tx-now-state.is-onair::before {
  content: "●";
  margin-right: 0.3rem;
}
@keyframes txnow-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}
.tx-now-detail {
  font-size: 0.7rem;
  color: var(--text-dim, #94a3b8);
  font-family: 'Courier New', monospace;
}
    

/* TEMP: TX NOW detail styling */
.tx-detail-label {
  color: var(--text-dim, #94a3b8);
  font-size: 0.66rem;
  margin-right: 0.2rem;
}
.tx-detail-call {
  color: var(--text, #e2e8f0);
  font-family: 'Courier New', monospace;
  font-weight: 600;
  margin-right: 0.3rem;
}
.tx-detail-mod {
  color: var(--accent, #38bdf8);
  font-family: 'Courier New', monospace;
  font-size: 0.66rem;
  margin-right: 0.3rem;
}
.tx-detail-via {
  color: var(--text-dim, #94a3b8);
  font-size: 0.66rem;
  margin-right: 0.3rem;
}
.tx-detail-ts {
  color: var(--text-dim, #94a3b8);
  font-size: 0.62rem;
  opacity: 0.75;
}
    

/* TEMP: Recent Activity — comfortable row sizes (Brent prefers larger) */
#activity-table .activity-row,
#activity-table .activity-entry {
  padding: 0.32rem 0.5rem !important;
  font-size: 0.78rem !important;
  line-height: 1.3 !important;
  min-height: 0 !important;
}
#activity-table .activity-head {
  padding: 0.32rem 0.5rem !important;
  font-size: 0.72rem !important;
}
#activity-table .activity-row .qrz-link {
  padding: 0.08rem 0.45rem !important;
  font-size: 0.75rem !important;
}
#activity-table .activity-row .via,
#activity-table .activity-row .ts {
  font-size: 0.72rem !important;
}

/* TEMP: Country flag in recent activity row */
.activity-row .country-flag {
  display: inline-block;
  margin-right: 0.25rem;
  font-size: 0.85rem;
  line-height: 1;
  vertical-align: middle;
  font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
    

/* TEMP: Country column in Recent Activity */
.activity-table .activity-head {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.2fr) 50px 32px !important;
  gap: 0.45rem !important;
}
.activity-table .activity-row {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.2fr) 50px 32px !important;
  gap: 0.45rem !important;
  align-items: center !important;
}
.activity-table .country-cell {
  text-align: center;
  font-size: 0.85rem;
  line-height: 1;
  font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
.activity-table .activity-head .country-head {
  text-align: center;
  font-size: 0.66rem;
  text-transform: uppercase;
  color: var(--text-dim, #94a3b8);
  letter-spacing: 0.04em;
}
    

/* TEMP: Twemoji image sizing — fit the country cell */
.country-cell img.emoji,
.country-cell img.twemoji,
img.emoji,
img.twemoji {
  width: 1.1em !important;
  height: 1.1em !important;
  vertical-align: middle !important;
  display: inline-block !important;
}

/* TEMP: Unify all panel titles to match Recent activity (1.05rem, not bold) */
.panel-head h2 {
  font-size: 1.05rem !important;
  font-weight: normal !important;
  letter-spacing: 0 !important;
}

/* TEMP: F module — use BLACK text like other modules (consistent) */
.mod-card[data-letter="F"] .mod-letter,
[data-letter="F"] .mod-letter,
.mod-letter[data-letter="F"],
.activity-row .mod-pill[data-letter="F"] {
  color: #0a0e14 !important;
  font-weight: 700 !important;
}

/* TEMP: Compact module cards + connected nodes mod-badge to match activity row density */
.mod-card {
  padding: .45rem .6rem !important;
  gap: .25rem !important;
}

/* TEMP: Recent Activity card stretches to match right column bottom (dynamic).
   Right column = nodes + allstar, total height varies as AllStar peers connect.
   Activity table scrolls if it exceeds available height. */
.two-col > #activity {
  display: flex !important;
  flex-direction: column !important;
  height: 100% !important;
}
#activity-table {
  flex: 1 1 auto !important;
  min-height: 0 !important;
  overflow-y: auto !important;
}


/* TEMP: Title styling — server name as primary, node number as subtitle */
.allstar-title {
  display: flex !important;
  flex-direction: column !important;
  gap: 0.1rem !important;
}
.allstar-callsign {
  font-size: 1rem !important;
  font-weight: 700 !important;
  color: var(--accent, #38bdf8) !important;
  font-family: var(--mono, monospace) !important;
  line-height: 1.2 !important;
}
.allstar-num {
  font-size: 0.7rem !important;
  font-weight: 400 !important;
  color: var(--text-dim, #94a3b8) !important;
  font-family: var(--mono, monospace) !important;
  letter-spacing: 0.02em !important;
  line-height: 1.2 !important;
}

/* TEMP: Equalize allstar card heights so titles align across cards */
.allstar-node-card {
  display: flex !important;
  flex-direction: column !important;
  min-height: 130px !important;
}
.allstar-node-card > .allstar-2col {
  flex: 1 1 auto !important;
}

/* TEMP: Equalize allstar card heights so server names align top-to-bottom */
.allstar-node-card {
  display: flex !important;
  flex-direction: column !important;
  min-height: 130px !important;
}
.allstar-node-card > .allstar-2col {
  flex: 1 1 auto !important;
}



/* ============================================================
 * v37: Dstarusers link styling (Brent, 2026-07-21)
 * Originally added inline in temp-homepage.html. Moved here
 * for prod parity (clean staging → prod promotion).
 * Styles the "Last heard" link in the Reflector Health footer
 * to match the pill aesthetic of the surrounding cells.
 * ============================================================ */
.health-info-link {
  display: inline-flex !important;
  align-items: center !important;
  gap: 0.25rem !important;
  padding: 0.18rem 0.65rem !important;
  background: rgba(56, 189, 248, 0.10) !important;
  color: var(--accent, #38bdf8) !important;
  border: 1px solid rgba(56, 189, 248, 0.30) !important;
  border-radius: 999px !important;
  font-family: 'Courier New', monospace !important;
  font-size: 0.78rem !important;
  font-weight: 600 !important;
  text-decoration: none !important;
  transition: background 0.15s ease, border-color 0.15s ease !important;
}
.health-info-link:hover {
  background: rgba(56, 189, 248, 0.18) !important;
  border-color: rgba(56, 189, 248, 0.55) !important;
}


/* ============================================================
 * Footer alignment fix (Brent, 2026-07-21)
 * Override grid alignment so all health cells start at the same Y.
 * Labels align across cells, values align below them.
 * ============================================================ */
@media (min-width: 980px) {
  .health-info-grid {
    align-items: start !important;
  }
  /* Force consistent min-height so the row is uniform */
  .health-info-cell {
    min-height: 56px !important;
    justify-content: flex-start !important;
  }
  /* The action cell (Refresh button) should align its button with the values */
  .health-info-cell.health-info-action {
    justify-content: flex-end !important;
    padding-top: 1.6rem !important; /* aligns button with value row of others */
  }
}


/* ============================================================
 * Site footer — 4-column grid (Brent, 2026-07-21)
 * Originally in styles.css (used by wiki/peers/bubble). Copied here
 * for modules.html (which uses preview-v46.css). Renders XLX159 /
 * Quick links / Connection / Resources columns + footer-bottom.
 * ============================================================ */
.site-footer {
  background: rgba(10, 14, 20, 0.55);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  padding: 3rem 0 2rem;
  margin-top: 4rem;
}
.site-footer > .container {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: 2rem;
  align-items: start;
}
@media (max-width: 760px) {
  .site-footer > .container { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 480px) {
  .site-footer > .container { grid-template-columns: 1fr; }
}
.site-footer h4 {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 0.85rem;
}
.site-footer ul {
  list-style: none;
  padding: 0;
  margin: 0;
}
.site-footer li {
  margin-bottom: 0.45rem;
}
.site-footer p {
  color: var(--text-dim);
  font-size: 0.9rem;
  margin: 0 0 0.85rem;
}
.footer-bottom {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  color: var(--text-faint);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
}
