:root {
  color-scheme: light;
  --fg: #3f3f3f;
  --bg: #ffffff;
  --page-bg: #f8f9fa;
  --muted: #666;
  --border: #ddd;
  --primary: #2c3e50;   /* navy: headings, primary accents */
  --accent: #3498db;    /* blue: links, buttons, focus */
  --secondary: #e74c3c; /* red: errors/emphasis, used sparingly */
  --pill-bg: #ffe6a1;   /* cream/amber: status & warning callouts */
  --pill-fg: #a84b00;
  --success-bg: #d7f0e0; /* soft green: positive status badges/messages */
  --success-fg: #1e7d4f;
  --error-bg: #fbe2e0;   /* soft red: paired with --secondary for error messages */
  --font-body: Georgia, 'Times New Roman', serif;
  --font-mono: 'IBM Plex Mono', 'Courier New', monospace;
}

body {
  font-family: var(--font-body);
  color: var(--fg);
  background: var(--page-bg);
  margin: 0;
}

.dymo-label {
  background: #b71c1c;
  color: #fff;
  border-radius: 0;
  padding: 0.22em 1.15em 0.18em 1.15em;
  font-family: 'Courier New', monospace;
  font-size: 1.22rem;
  font-weight: bold;
  letter-spacing: 0.11em;
  text-shadow: 0 2px 2px #7b1010, 0 -1px 1px #7b1010;
  display: inline-block;
  line-height: 1.2;
  border: 2px solid #a84b00;
}
/* Narrow enough that the full-size wordmark plus the navbar-toggler
   hamburger no longer fit on one line -- confirmed live at 375px width,
   where the label alone (312px) left the toggler (56px) no room and
   wrapped to a second row. Smaller font/padding/letter-spacing only
   below this width; unchanged everywhere wider, including tablet. */
@media (max-width: 576px) {
  .navbar-brand .dymo-label {
    padding: 0.2em 0.6em 0.16em 0.6em;
    font-size: 0.92rem;
    letter-spacing: 0.05em;
  }
}

/* Manila-folder-tab style divider between nav links -- reuses the same
   cream/amber pair as the .warnings pill so it reads as part of the same
   "manila folder" visual language, not a new color introduced just here. */
.nav-divider {
  width: 5px;
  align-self: stretch;
  margin: 0.35rem 1rem;
  background: var(--pill-bg);
  border: 1px solid var(--pill-fg);
  border-radius: 2px;
  opacity: 0.85;
}

/* --- Cassette buttons --- */
/* Used on just two buttons (homepage submit, meeting page's "Copy link to
   current time") -- the rewind-to-a-moment metaphor fits those, not every
   button on the page. */
.cassette-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  background: #fff;
  color: #222426;
  border: 2px solid #222;
  border-radius: 7px;
  font-family: 'Courier New', monospace;
  font-size: 1.13rem;
  font-weight: bold;
  padding: 0.54rem 1.3rem;
  cursor: pointer;
  transition: background 0.16s, color 0.16s, border 0.16s;
}
.cassette-btn:hover, .cassette-btn:focus {
  background: #f3f3f3;
  color: #111;
  border-color: #111;
}
.cassette-label {
  font-family: inherit;
  font-size: 1.08rem;
  letter-spacing: 1.5px;
}
.cassette-reel {
  display: flex;
  align-items: center;
}
.cassette-reel circle { transition: filter 0.2s; }
@keyframes reel-spin { 100% { transform: rotate(360deg); } }
.cassette-btn:hover .cassette-reel,
.cassette-btn:active .cassette-reel {
  animation: reel-spin 0.8s linear infinite;
  transform-origin: 50% 50%;
}
/* Ambient loading state (the "please wait" fetch message) -- slower
   than the hover flourish above since this can run for up to ~20s and
   a quick 0.8s spin reads frantic sustained that long. */
.cassette-reel.spinning {
  animation: reel-spin 1.6s linear infinite;
  transform-origin: 50% 50%;
}

.status-loading {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}

/* Input group pairing (URL textbox + cassette submit button fused into
   one control), matching round-1's real input-group/cassette-btn sizing. */
.input-group > .form-control,
.input-group > .cassette-btn {
  height: 48px;
  min-height: 48px;
  font-size: 1.05rem;
}
.input-group > .form-control {
  border-radius: 7px 0 0 7px;
}
.input-group > .cassette-btn {
  border-radius: 0 7px 7px 0;
}

.center-page {
  max-width: 640px;
  margin: 10vh auto;
  padding: 0 1.5rem;
  text-align: center;
}

.subtitle { color: var(--muted); }

.error { color: var(--secondary); }
.status { color: var(--muted); margin: 1rem 0; }

.meeting-page {
  max-width: 860px;
  margin: 2rem auto;
  padding: 0 1.5rem;
}

/* Desktop-only: video pinned in a narrow sticky left column, transcript
   scrolling in a wider right column -- so the video never has to be
   scrolled back up to, without needing to scroll a full-width video off
   past the transcript to read it. Deliberately narrow, not a large
   video column: most viewers here are deep-linking to a specific
   moment and just need audio plus a visual confirmation of who's
   speaking, not a large frame for reading slides -- someone who does
   need to read a presentation would open the source video fullscreen
   directly rather than use this tool's transcript view at all. Below
   this breakpoint (comfortably above .meeting-page's own 860px max
   width + padding, so it only activates when there's real room to
   spare) everything stays single-column/stacked, matching mobile's
   existing behavior unchanged.

   #videoColumn wraps #videoSection together with the report-problem and
   transcribe-request toggles/forms into a single sticky unit -- these
   used to be separate grid items sharing #videoSection's grid row via
   explicit row-span tricks, but a sticky element's "stick range" is
   bounded by its own row, and two independently-sized sticky siblings
   in the same row fought/overlapped each other as the page scrolled
   (real bug from live review: the report-problem block would ride up
   and crash over the video). Wrapping everything in one element removes
   that whole class of problem -- there's only one sticky box now, sized
   to its own real content, and simple column-only auto-placement (no
   explicit grid-row numbers needed) is enough since #videoColumn and
   #transcriptColumn are just two plain siblings in the same implicit
   row. */
@media (min-width: 900px) {
  .meeting-page {
    display: grid;
    grid-template-columns: minmax(220px, 300px) 1fr;
    column-gap: 1.75rem;
    align-items: start;
  }
  #meta { grid-column: 1 / -1; }
  #statusMessage { grid-column: 1 / -1; }
  #videoColumn {
    grid-column: 1;
    position: sticky;
    top: 1rem;
  }
  #transcriptColumn { grid-column: 2; }
  /* The toolbar's own independent sticky-to-top-of-viewport (below) was
     built for the single-column mobile layout, where the video scrolls
     away but the controls above the transcript shouldn't. Here the
     *entire* #videoColumn (toolbar + video + report-problem + transcribe
     request, together) already sticks as one unit, so the toolbar
     keeping its own separate sticky positioning would nest two
     independent sticky contexts and fight/jitter against each other. */
  .toolbar { position: static; }
}

.meta h1 { margin-bottom: 0.2rem; color: var(--primary); }
/* margin-bottom wasn't overridden here before -- the browser's default
   paragraph spacing (~1em) was the real cause of "too much space"
   between the jurisdiction/date line and "View original source" below
   it, not .source-link's own (already tight) margin. */
.meta p { color: var(--muted); margin: 0 0 0.25rem; }
.source-link { margin: 0.15rem 0; }
.source-link a {
  color: var(--muted);
  font-size: 0.85rem;
  text-decoration: none;
}
.source-link a:hover { color: var(--accent); text-decoration: underline; }

.link-button {
  background: none;
  border: none;
  padding: 0;
  color: var(--muted);
  font-size: 0.85rem;
  text-decoration: underline;
  cursor: pointer;
}
.link-button:hover { color: var(--accent); }
.report-problem-toggle { margin: 0.4rem 0 0; }
.report-problem-form {
  margin: 0.75rem 0 1.5rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  max-width: 420px;
}
.report-problem-form label {
  display: block;
  font-size: 0.85rem;
  color: var(--muted);
  margin-bottom: 0.75rem;
}
.report-problem-form select,
.report-problem-form textarea {
  display: block;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font-family: inherit;
  font-size: 0.9rem;
}
.report-problem-actions { display: flex; align-items: center; gap: 1rem; }
/* Status message pill -- matches .warnings' shape/weight (below) instead
   of a smaller, plain-colored-text treatment, so a "your report was
   sent" confirmation reads as part of the same status-message visual
   language as everything else on the page, not a separate smaller
   system. Empty by default (no text yet), so no visible pill until a
   real status message is set. */
.report-problem-status,
.transcribe-status {
  display: inline-block;
  border-radius: 7px;
  padding: 0.35rem 0.85rem;
  font-weight: bold;
  margin: 0.6rem 0 0;
}
.report-problem-status:empty,
.transcribe-status:empty { display: none; }
.report-problem-status.success,
.transcribe-status.success { background: var(--success-bg); color: var(--success-fg); }
.report-problem-status.error,
.transcribe-status.error { background: var(--error-bg); color: var(--secondary); }

.transcribe-toggle { margin: 0.4rem 0 0; }
.nyc-crosslink { margin: 0.75rem 0 0; font-size: 0.9rem; }
.transcribe-form {
  margin: 0.75rem 0 1.5rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  max-width: 420px;
}
.transcribe-form label {
  display: block;
  font-size: 0.85rem;
  color: var(--muted);
  margin-bottom: 0.75rem;
}
.transcribe-form input[type="email"] {
  display: block;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font-family: inherit;
  font-size: 0.9rem;
}

/* Cream/amber "choose wisely" pill treatment for status & warning
   messages -- the manila-folder half of the red-tape/manila-folder vibe. */
.warnings {
  display: inline-block;
  background: var(--pill-bg);
  color: var(--pill-fg);
  border-radius: 7px;
  padding: 0.35rem 0.85rem;
  font-weight: bold;
  margin-bottom: 0.75rem;
}
.warnings:empty { display: none; }
.warnings a, .error a { color: inherit; text-decoration: underline; }
.warnings a:hover, .error a:hover { color: var(--accent); }
.transcribe-inline-trigger {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.transcribe-inline-trigger:hover { color: var(--accent); }

.video-section { margin: 1.5rem 0; }
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
  /* On long transcripts with auto-scroll on, the page keeps scrolling the
     transcript into view, making it hard to scroll back up to turn
     auto-scroll off (real complaint from live review). Sticking the
     toolbar to the top keeps it reachable at all times. */
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--page-bg);
  padding: 0.5rem 0;
}
/* Auto-scroll toggle + "Go to time" -- moved below the video (real
   feedback: cramming these into the same narrow toolbar row as the
   share button, in a ~220-300px sticky column, left them squeezed/hard
   to notice). A plain wrapping row, not sticky itself -- only the share
   button above needs to stay reachable at all times.

   Stacked (not side-by-side) and stretched to the column's full width --
   in the narrow sticky video column these two controls wrapped
   unevenly when laid out as a wrapping row, leaving their left/right
   edges misaligned depending on how much text/space each happened to
   take. Stretching both to 100% width guarantees their edges always
   line up, regardless of column width. */
.video-subtoolbar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
/* Target the auto-scroll toggle specifically, not .btn generally -- the
   seek-form's own submit button also carries the .btn class, and a
   blanket ".video-subtoolbar .btn { width: 100% }" rule fought with the
   seek-form's flex layout (button demanding 100% width on top of
   flex-shrink: 0 squashed the input to almost nothing and pushed the
   form past the column's right edge). */
#toggleAutoScrollBtn { width: 100%; }
.seek-form { display: flex; width: 100%; gap: 0.4rem; }
.seek-form input {
  flex: 1;
  min-width: 0;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.9rem;
}
.seek-form button { flex-shrink: 0; }
/* Positioning context for the floating .copy-toast bubble below --
   wraps just the button (not the whole .toolbar, whose own position
   flips between sticky/static across breakpoints) so the toast always
   anchors to the button itself regardless of layout. */
.copy-control {
  position: relative;
  display: inline-block;
}
/* Small fading confirmation that floats above the share button (real
   feedback: below/beside it went unnoticed or crowded the row) --
   replaces the previous behavior of swapping the button's own label
   text to "Copied!", since the label now shows a live timestamp instead.
   An absolutely-positioned overlay, not in-flow, so it never shifts
   surrounding layout when it appears. Shown via a .visible class
   (opacity transition), removed by JS after a few seconds. */
.copy-toast {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 0.5rem;
  white-space: nowrap;
  background: var(--success-bg);
  color: var(--success-fg);
  border-radius: 6px;
  padding: 0.3rem 0.7rem;
  font-size: 0.8rem;
  font-weight: bold;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}
.copy-toast.visible { opacity: 1; }
.video-wrapper {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 8px;
  overflow: hidden;
}
video { width: 100%; height: 100%; display: block; }

.youtube-player-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
/* YT.Player sets explicit pixel width/height attributes on the iframe it
   creates -- override with !important so it still fills the responsive
   16:9 wrapper instead of the fixed default size. */
.youtube-player-container iframe {
  width: 100% !important;
  height: 100% !important;
}

/* Viebit's embed page is iframed directly (see viebit.py's module
   docstring for why this isn't native <video>/hls.js) -- this element IS
   the iframe, unlike .youtube-player-container above (a <div> YT.Player
   fills with its own iframe), so the sizing rule applies straight to it,
   no child selector needed. */
.viebit-player-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: none;
}

.big-play-button {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: transform 0.15s ease;
}
.big-play-button:hover { transform: translate(-50%, -50%) scale(1.08); }
.big-play-button svg { display: block; filter: drop-shadow(0 2px 6px rgba(0,0,0,0.4)); }

.transcript-section h2 { margin-bottom: 0.5rem; color: var(--primary); }
.agenda-section { margin-bottom: 1.5rem; }
.agenda-section h2 { margin-bottom: 0.5rem; color: var(--primary); }
/* Shorter than the main transcript list's 60vh -- real feedback: a long
   agenda pushed the "Transcript" heading below the fold. The agenda is
   secondary/reference material here, so it gets its own scrollable box
   rather than claiming the same generous height as the transcript. */
.agenda-section .transcript-list { max-height: 220px; }

.transcript-heading-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.transcript-heading-row h2 { margin-bottom: 0.5rem; }
.transcript-language-picker {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.85rem;
}
.transcript-language-picker select {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.85rem;
}
.transcript-export {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.85rem;
}

/* Fills the space where the transcript would be when there isn't one --
   a live playhead readout so it's clear we're still tracking the exact
   moment, not just a dead end, plus a prominent way to grab a link to
   it (the toolbar's "Copy link to current time" does the same thing,
   but easy to miss when there's nothing else to look at down here). */
.no-transcript-timestamp {
  text-align: center;
  padding: 2.5rem 1rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
}
.no-transcript-hint {
  color: var(--muted);
  max-width: 32rem;
  margin: 0 auto 1.25rem;
}
.no-transcript-time {
  font-family: var(--font-mono);
  font-size: 3.5rem;
  font-weight: bold;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  margin-bottom: 1.25rem;
}
.no-transcript-manual-input {
  display: block;
  width: 12rem;
  margin: 0 auto 1.25rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 1.5rem;
  text-align: center;
}

/* Plain "we think we found this: <link>" lines on a best-effort result
   page -- deliberately normal weight/color, not the bold-on-tinted-
   background .warnings treatment, since nothing here is confirmed the
   way a real warning is. */
/* A real long media URL (no natural break points, e.g. a Swagit m3u8
   playlist link) confirmed live 2026-08-10 to overflow its column and
   visually collide with the Agenda column's text next to it -- neither
   .source-guess nor the <a> inside it wrapped without this. */
.source-guess { color: var(--fg); margin: 0 0 1rem; overflow-wrap: break-word; }
.source-guess a { color: inherit; text-decoration: underline; overflow-wrap: break-word; }
.source-guess a:hover { color: var(--accent); }
/* #agendaList stays empty when a best-effort result has no real
   agenda_items -- #agendaSection itself is now shown unconditionally
   for those results (see player.js), so an un-hidden empty box would
   otherwise render right below the agenda-guess line for nothing. */
.transcript-list:empty { display: none; }
.transcript-list {
  max-height: 60vh;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1rem;
  background: var(--bg);
  /* Always-visible scrollbar, not just on hover/scroll -- real feedback:
     this box not obviously reading as "a scrollable window" (most
     modern OS/browser combos use invisible-until-interacted overlay
     scrollbars by default). Firefox: */
  scrollbar-width: thin;
  scrollbar-color: var(--border) var(--bg);
}
/* Chrome/Safari/Edge: styling ::-webkit-scrollbar at all switches these
   browsers from overlay to always-reserved-space classic scrollbars. */
.transcript-list::-webkit-scrollbar { width: 10px; }
.transcript-list::-webkit-scrollbar-track { background: var(--bg); }
.transcript-list::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 5px;
}
.transcript-list::-webkit-scrollbar-thumb:hover { background: var(--muted); }
/* Grid, not plain inline flow -- a wrapped line of .segment-text
   previously fell back to the container's far-left edge (under the
   timestamp) instead of aligning under where the text itself started,
   confirmed hard to read on any real multi-line cue. Fixed-width
   timestamp/button columns (auto-sized, never wrap) plus a remaining-
   width text column (wraps within its own column only) keeps every
   wrapped line's left edge aligned under the first line's text. Also
   used by renderAgenda() for agenda_items rows -- the same shared
   .transcript-segment class -- so this fixes both without extra work. */
.transcript-segment {
  display: grid;
  /* Fixed (not auto) first column so the timestamp width doesn't vary row
     to row -- see archive/static/style.css's identical fix for the full
     explanation. Kept in sync with that file. */
  grid-template-columns: 8.5ch auto 1fr;
  column-gap: 0.4rem;
  align-items: start;
  margin-bottom: 0.75rem;
}
.transcript-segment.playing { background: rgba(52, 152, 219, 0.15); border-radius: 4px; }
.segment-timestamp {
  color: var(--accent);
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  font-family: var(--font-mono);
  white-space: nowrap;
}
.segment-timestamp:hover { text-decoration: underline; }
.segment-text { min-width: 0; }
/* renderAgenda()'s unreliableTimestamps case renders .segment-text as the
   sole child (no timestamp/button) when a source reports every agenda
   item at the same instant -- without this, the 3-column grid would
   confine that lone child to the first auto-sized column instead of the
   full row width. */
.transcript-segment > .segment-text:only-child { grid-column: 1 / -1; }

.segment-link-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  border-radius: 4px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s ease, color 0.1s ease, background 0.1s ease;
}
.segment-link-btn svg { width: 17px; height: 17px; }
.transcript-segment:hover .segment-link-btn,
.segment-link-btn:focus-visible {
  opacity: 1;
}
.segment-link-btn:hover { color: var(--accent); background: rgba(52, 152, 219, 0.12); }
.segment-link-btn.copied { opacity: 1; color: #2f855a; }

/* While the video is at rest (paused), gently suggest that the current
   line is deep-linkable by showing its icon without needing a hover --
   but only the current line, and only at rest, so it doesn't flicker from
   segment to segment during normal playback/auto-scroll. */
body.video-at-rest .transcript-segment.playing .segment-link-btn {
  opacity: 0.6;
}
body.video-at-rest .transcript-segment.playing .segment-link-btn:hover {
  opacity: 1;
}

.transcript-search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}
.transcript-search input {
  flex: 1;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.9rem;
}
.transcript-search-count {
  color: var(--muted);
  font-size: 0.85rem;
  min-width: 3.5rem;
  text-align: right;
}

mark.search-match {
  background: #fef08a;
  color: #1a1a1a;
  border-radius: 2px;
  padding: 0 1px;
}
mark.search-match.current { background: #f97316; color: #fff; }

.calendar-candidate {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}
.calendar-candidate:last-child { border-bottom: none; }
.calendar-candidate a { color: var(--accent); text-decoration: none; font-weight: 500; }
.calendar-candidate a:hover { text-decoration: underline; }
.calendar-candidate-date { color: var(--muted); margin-left: 0.5rem; }

/* --- About page --- */
.about-page {
  max-width: 640px;
  margin: 3rem auto;
  padding: 0 1.5rem;
  line-height: 1.6;
}
.about-page h1 { color: var(--primary); }
.about-page h2 { color: var(--primary); margin-top: 2rem; }
.about-lede { font-weight: bold; font-size: 1.15rem; }
.about-actions { padding-left: 1.25rem; }
.about-actions li { margin-bottom: 0.5rem; }

/* --- Footer / newsletter signup --- */
.site-footer {
  margin-top: 3rem;
  padding: 2rem 1.5rem;
  border-top: 1px solid var(--border);
  background: var(--bg);
}
.footer-subscribe-prompt {
  max-width: 420px;
  margin: 0 auto;
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
}
.footer-links {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  margin: 0 auto 1.25rem;
  font-size: 0.9rem;
}
.footer-links a { color: var(--muted); }
.footer-links a:hover { color: var(--fg); }
.newsletter-form {
  max-width: 420px;
  margin: 0 auto;
  text-align: center;
}
.newsletter-input-group input {
  height: 48px;
  border: 1px solid var(--border);
  border-radius: 7px 0 0 7px;
  background: var(--bg);
  color: var(--fg);
  font-size: 1.05rem;
}
/* A new button, not cassette-btn -- "sign up" isn't a "rewind to a
   moment" action, so it gets its own but-consistent treatment: same
   bold mono/chunky-border family, solid navy instead of the reel-icon
   gimmick, sized to fuse with the input the same way cassette-btn does
   on the homepage. */
.newsletter-btn {
  height: 48px;
  padding: 0 1.3rem;
  background: var(--primary);
  color: #fff;
  border: 2px solid var(--primary);
  border-radius: 0 7px 7px 0;
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: bold;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: background 0.16s, border-color 0.16s;
}
.newsletter-btn:hover, .newsletter-btn:focus {
  background: #1c2a37;
  border-color: #1c2a37;
}
.newsletter-message {
  margin-top: 0.5rem;
  font-size: 0.85rem;
}
.newsletter-message-success { color: #2f855a; }
.newsletter-message-error { color: var(--secondary); }

/* Small brand callback on the dedicated /subscribe page -- reuses the
   dymo-label motif (otherwise only used for the site wordmark) as a
   secondary "section tag" rather than introducing a new visual idea
   just for this one page. */
.dymo-label-small {
  background: #b71c1c;
  color: #fff;
  font-family: 'Courier New', monospace;
  font-size: 0.8rem;
  font-weight: bold;
  letter-spacing: 0.11em;
  padding: 0.2em 0.8em;
  border: 2px solid #a84b00;
  display: inline-block;
  margin-bottom: 1rem;
}
