:root {
  --maroon: #7b241c;
  --maroon-dark: #4a140e;
  --gold: #d4ac0d;
  --cream: #fdf2e3;
  --band: #fbeee6;
  --ink: #2b2b2b;
  --muted: #6b6b6b;
  --border: #e0d6cd;
  --danger: #b3261e;
  --warning: #8a5a00;
  /* Liquid/glossy gold - more color stops than a simple 3-tone gradient needs, so the bands
     read as flowing/wavy rather than a flat stripe (chasing the look of a reference photo the
     user liked, without the readability/licensing/crop-fragility problems an actual photo
     texture would bring behind button text - see conversation). Reserved for a small number
     of focal points (logo rings, primary buttons) on purpose - see the comment above
     button.primary below for why this isn't applied to every gold accent in the app.
     background-size is oversized (220%) specifically so button.primary's hover state (below)
     can slide background-position across it for a shimmer-sweep effect, rather than an
     instant color swap - "liquid" implies motion, a static gradient alone can't give that. */
  --gold-gloss: linear-gradient(
    120deg,
    #f9e6a8 0%, #f0d878 10%, #d4ac0d 20%, #a8790a 30%, #8a6508 40%,
    #c49a0a 50%, #f5e08a 60%, #d4ac0d 70%, #8a6508 80%, #a8790a 90%, #d4ac0d 100%
  );
  /* A bright inset top edge + dark inset bottom edge reads as a beveled, pressed-medallion
     surface rather than a flat ring - the same trick behind every embossed coin/seal look.
     Two sizes: the login/committee rings are thicker (3px) than the small header one (2px),
     so the bevel needs to scale down with it or it looks like a smudge instead of an edge. */
  --ring-emboss: inset 0 1px 2px rgba(255, 255, 255, 0.55), inset 0 -2px 3px rgba(0, 0, 0, 0.3);
  --ring-emboss-thin: inset 0 1px 1px rgba(255, 255, 255, 0.5), inset 0 -1px 1px rgba(0, 0, 0, 0.3);
}

* { box-sizing: border-box; }

/* Scrollbar theming - covers the page itself and every .table-scroll (both axes). Firefox
   uses the standard scrollbar-color/-width properties; Chrome/Edge/Safari use the older
   ::-webkit-scrollbar pseudo-elements - both are included since neither alone covers all
   browsers. Thumb is semi-transparent maroon rather than solid - stays on-brand but reads as
   light/subtle instead of a bold, heavy bar (a plain gray would look more "generic app" and
   less tied to the rest of the styling). */
* { scrollbar-width: thin; scrollbar-color: rgba(123, 36, 28, 0.4) var(--band); }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: var(--band); }
::-webkit-scrollbar-thumb {
  background-color: rgba(123, 36, 28, 0.4);
  border-radius: 7px;
}
::-webkit-scrollbar-thumb:hover { background-color: rgba(123, 36, 28, 0.65); }
/* Removes the browser's default arrow-click-to-scroll buttons at each end - unstyled, plain
   gray boxes that clashed with the smooth custom-colored track/thumb around them. */
::-webkit-scrollbar-button { display: none; }

html {
  /* Stops the browser's "rubber-band" overscroll bounce past the top/bottom of the page -
     without this, scrolling past the end briefly shifts the whole viewport (trackpads/touch
     especially), which makes even a correctly sticky header look like it's moving too. */
  overscroll-behavior-y: none;
}

body {
  margin: 0;
  /* User's explicit choice, after being flagged that it's a serif font not bundled as a free
     web font (ships with MS Office on Windows) - falls back to Georgia/Times New Roman/serif
     on devices that don't have it installed, rather than an unpredictable generic fallback. */
  font-family: "Bookman Old Style", Georgia, "Times New Roman", serif;
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--cream);
}

h1, h2, h3, h4 { color: var(--maroon-dark); margin: 0.4em 0; font-weight: 600; }
h1 { font-size: 22px; }  /* login screen only - a one-time "primary header" moment */
h2 { font-size: 19px; }  /* main section title per tab */
h3 { font-size: 15px; }  /* subsection heading */
h4 { font-size: 13px; }  /* small supporting label */

.muted { color: var(--muted); }
.small { font-size: 0.85em; }

/* Real browsers give form controls their own default OS font via the user-agent stylesheet
   and DON'T inherit the body font automatically (a very well-known gotcha - unlike headings/
   tables/divs, which inherit normally). Without this, every button/input/select/datalist
   would silently stay on the browser's default UI font regardless of what's set on body. */
button, input, select, textarea, datalist { font: inherit; }

/* One gold focus-visible ring for every interactive element - buttons, inputs, selects,
   links - rather than relying on the browser's default (usually blue) outline, which was
   only ever overridden for the login inputs and the header logo link. Keyboard users tabbing
   through the rest of the app (every Transactions/Garage/Reports form field, every button)
   would otherwise hit a blue ring that clashes with the maroon/gold theme everywhere else. */
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, a:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* Login - the form now lives inside #tab-login, an ordinary panel in the persistent shell
   (see index.html), so it just needs centering within that panel rather than the whole
   viewport a standalone login screen would need. The soft gold-tinted glow behind the card is
   subtle on purpose - depth, not a background color swap. */
.login-box-wrap {
  display: flex;
  justify-content: center;
  /* Kept deliberately modest (not the 3rem+ a standalone full-viewport login screen could
     afford) - this sits below the always-present sticky header now, so generous padding here
     is what pushes the page taller than a shorter viewport (or DevTools eating vertical
     space), forcing a scrollbar for what's a fairly small form. */
  padding: 1rem;
  background: radial-gradient(circle at 50% 35%, rgba(212, 172, 13, 0.12), transparent 60%);
}
.login-box {
  background: white;
  padding: 1.5rem;
  border-radius: 10px;
  border-top: 4px solid var(--gold);
  box-shadow: 0 4px 20px rgba(212, 172, 13, 0.18), 0 4px 16px rgba(0, 0, 0, 0.06);
  width: min(90vw, 380px);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  /* position+overflow set up the one-time shine sweep below (::after) - overflow:hidden
     clips it to the card's rounded corners without affecting the card's own outer shadow,
     which paints outside the clip region regardless. */
  position: relative;
  overflow: hidden;
}
/* A single light streak sweeps across the card once, shortly after it appears - like a
   spotlight catching a medallion's edge. Plays once (no infinite loop) so it reads as a
   one-time flourish, not a distracting ongoing animation. */
.login-box::after {
  content: "";
  position: absolute;
  top: 0;
  left: -75%;
  width: 45%;
  height: 100%;
  background: linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.55) 50%, transparent 100%);
  transform: skewX(-20deg);
  animation: login-shine-sweep 1.3s ease-out 0.5s 1;
  pointer-events: none;
}
@keyframes login-shine-sweep {
  from { left: -75%; }
  to { left: 130%; }
}
@media (prefers-reduced-motion: reduce) {
  .login-box::after { display: none; }
}
/* Shorter viewports (small laptop screens, or DevTools eating half the window) get a
   further-compressed version - smaller logo/heading/gaps - rather than just accepting a
   scrollbar for a form this small. */
@media (max-height: 700px) {
  .login-box-wrap { padding: 0.5rem; }
  .login-box { padding: 1rem; gap: 0.4rem; }
  .club-logo { width: 56px; height: 56px; }
  .login-box h1 { font-size: 17px; }
  .login-tagline { margin-bottom: 0.2em; }
}
.login-box label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.9em; }
.login-box input { padding: 0.5em; border: 1px solid var(--border); border-radius: 6px; font-size: 1em; }
.login-box input:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(212, 172, 13, 0.25);
}
/* Gradient ring via padding + background rather than a real `border` - CSS's gradient-capable
   border-image ignores border-radius entirely (square corners poking out of a round logo),
   so a padding gap + background gradient underneath is what actually renders as a round,
   glossy medallion ring. Works because object-fit sizes the image within the content-box,
   leaving the padding ring for the gradient to show through, both clipped to a circle by the
   shared border-radius. */
.club-logo {
  width: 72px;
  height: 72px;
  object-fit: contain;
  align-self: center;
  border-radius: 50%;
  padding: 3px;
  background: var(--gold-gloss);
  box-shadow: 0 2px 8px rgba(212, 172, 13, 0.35), var(--ring-emboss);
}
.login-box h1 { text-align: center; font-size: 19px; line-height: 1.25; margin-bottom: 0.1em; }
.login-tagline {
  text-align: center;
  color: var(--gold);
  font-weight: 600;
  letter-spacing: 0.08em;
  margin: 0 0 0.3em;
  font-size: 0.95em;
}

/* The one deliberate exception to "gloss only where it has room to show" (see --gold-gloss
   above) - buttons are exactly the handful of focal points a glossy sheen was reserved for
   (Sign in, Save, Add row/member, Login), each with real surface area for the gradient bands
   to actually read as metallic rather than just a duller flat color. */
button.primary {
  background: var(--gold-gloss);
  background-size: 220% 220%;
  background-position: 0% 50%;
  color: var(--maroon-dark);
  border: none;
  padding: 0.6em 1.1em;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.95em;
  font-weight: 600;
  box-shadow: 0 2px 8px rgba(212, 172, 13, 0.35);
  transition: background-position 0.6s ease, box-shadow 0.15s ease;
}
button.primary:hover {
  background-position: 100% 50%;
  box-shadow: 0 3px 12px rgba(212, 172, 13, 0.45);
}
/* Motion is purely decorative here (the color change on hover already communicates
   interactivity on its own) - respect the OS-level "less motion" preference by cutting
   straight to the end state instead of sweeping through it. */
@media (prefers-reduced-motion: reduce) {
  button.primary { transition: box-shadow 0.15s ease; }
}
/* Explicit color + a maroon (not just var(--border) gray) border - without these, a ghost
   button has no author-set text color at all (just whatever the browser's UA default button
   color happens to be) and a border faint enough to nearly disappear against the white
   header, so next to the gold Login/Save buttons it can read as a blank box rather than a
   deliberate secondary action. */
button.ghost {
  background: transparent;
  color: var(--maroon-dark);
  border: 1px solid var(--maroon);
  padding: 0.5em 1em;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
  transition: background-color 0.15s ease;
}
button.ghost:hover { background: var(--band); }
.error { color: var(--danger); font-size: 0.9em; }

/* Every tab's own <h2> gets the same small gold underline flourish - one consistent "brand
   signature" across public and logged-in tabs alike, cheap enough not to compete with the
   data-dense screens (Transactions/Garage/Reports) the way a card treatment would. */
main .tab-panel > h2 {
  display: inline-block;
  padding-bottom: 0.2em;
  border-bottom: 3px solid var(--gold);
}

/* About & Committee - both are now ordinary tab panels in the persistent shell (see
   index.html), public or not, so they just need their own content layout - no separate
   full-page header/logo/back-link chrome, since the app header above already shows that.
   These two are the public "showcase" pages, so their cards carry the same gold signature as
   the login card (top accent stripe, soft shadow, hover lift) - a bit more polish than the
   purely functional logged-in tabs get (see .tx-form/.chart-box below for their lighter
   version of the same idea - accent + shadow, no hover lift, since a data-entry form or a
   chart isn't something you "browse" the way a card grid is).
   (A tiled paisley-pattern watermark was tried behind these panels and removed - it read as a
   recognizable paisley in isolation, but tiled at a size small enough to stay subtle, the
   curl/tail detail disappeared and all that was left was a grid of plain circles, which
   looked like a rendering glitch rather than a decoration.)
   Cards also get a staggered fade+rise entrance - animation (not transition, same reasoning
   as .tab-panel.active above) with `both` fill mode so each card stays invisible until its
   own animation-delay (set inline per-card in app.js) elapses, rather than all flashing in
   at once and then un-staggering themselves. */
.about-card, .committee-card, .announcement-card, .gallery-card, .list-row {
  animation: card-enter 0.4s ease both;
}
@keyframes card-enter {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .about-card, .committee-card, .announcement-card, .gallery-card, .list-row { animation: none; }
}

/* Shimmering skeleton shapes shown while assets/about.json or committee.json is still
   fetching (see skeletonCardsHtml in app.js) - reuses the same card shell as the real cards
   so there's no layout jump when the fetch resolves and replaces this. */
.skeleton-card { background: white; border: 1px solid var(--border); border-radius: 10px; padding: 1rem; }
.skeleton-line {
  height: 14px;
  border-radius: 4px;
  margin-bottom: 0.6em;
  background: linear-gradient(90deg, var(--band) 25%, #f3e6d2 37%, var(--band) 63%);
  background-size: 400% 100%;
  animation: skeleton-shimmer 1.4s ease infinite;
}
.skeleton-line:last-child { margin-bottom: 0; }
.skeleton-image { height: 120px; }
@keyframes skeleton-shimmer {
  0% { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton-line { animation: none; }
}

.about-content { max-width: 900px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem; }
/* Announcements and Events (see app.js) reuse this exact stacked image+text card shape - same
   .about-content container class too - rather than a near-duplicate ruleset, since visually
   they're the same "dated or not, image + heading + prose" block About already established. */
.about-card, .announcement-card {
  background: white;
  border: 1px solid var(--border);
  border-top: 3px solid var(--gold);
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.about-card:hover, .announcement-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(212, 172, 13, 0.15), 0 4px 12px rgba(0, 0, 0, 0.07);
}
.about-card-image { width: 220px; max-width: 100%; height: 180px; object-fit: cover; flex-shrink: 0; }
.about-card-body { padding: 1rem 1.2rem; flex: 1; min-width: 200px; }
.about-card-body h3 { margin-top: 0; }
.about-card-body p { margin: 0; }
/* Small pill above the title - shared by Announcements' date and Events' date/"Upcoming"
   badges (the latter reuses .badge itself, already defined for the read-only indicator). */
.date-badge {
  display: inline-block;
  background: var(--band);
  color: var(--maroon-dark);
  padding: 0.2em 0.6em;
  border-radius: 10px;
  font-size: 0.8em;
  font-weight: 600;
  margin: 0 0.5em 0.4em 0;
}

/* Committee is a roster (name + role + photo per person), not prose - a card grid instead of
   About's stacked image+text blocks. */
.committee-content {
  max-width: 900px;
  margin: 0 auto;
  display: grid;
  /* Wider than the 160px an image+name+role card alone would need - description text (below)
     is prose, and wraps badly in a column that narrow. */
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1.2rem;
}
.committee-card {
  background: white;
  border: 1px solid var(--border);
  border-top: 3px solid var(--gold);
  border-radius: 10px;
  padding: 1.2rem 1rem;
  text-align: center;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.committee-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(212, 172, 13, 0.15), 0 4px 12px rgba(0, 0, 0, 0.07);
}
.committee-card-image {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  margin: 0 auto 0.8rem;
  display: block;
  /* Same padding+gradient ring technique as .club-logo - see its comment. */
  padding: 3px;
  background: var(--gold-gloss);
  box-shadow: 0 2px 8px rgba(212, 172, 13, 0.35), var(--ring-emboss);
}
.committee-card h3 { margin: 0 0 0.2em; font-size: 15px; }
.committee-card-role { margin: 0; color: var(--muted); font-size: 0.85em; }
.committee-card-description { margin: 0.6em 0 0; font-size: 0.85em; text-align: left; }

/* Photo Gallery - a grid of photo+caption only (no name/role/description like Committee's
   roster cards), so a narrower minimum column width reads better. */
.gallery-content {
  max-width: 900px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
}
.gallery-card { margin: 0; text-align: center; }
.gallery-card-image {
  width: 100%;
  height: 160px;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--border);
  display: block;
}
.gallery-card figcaption { margin-top: 0.5rem; font-size: 0.85em; color: var(--muted); }

/* Shared by Documents and Events (see dateWindowRowsHtml in app.js) - a plain list of rows
   rather than cards, since there's no photo to showcase and a list scans faster than a card
   grid for "find the one thing I need". */
.list-content { max-width: 900px; margin: 0 auto; display: flex; flex-direction: column; gap: 0.8rem; }
.list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: white;
  border: 1px solid var(--border);
  border-top: 3px solid var(--gold);
  border-radius: 10px;
  padding: 1rem 1.2rem;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  transition: opacity 0.15s ease;
}
/* status: "Closed" - dims the whole row, not just its badge, so a closed item reads as
   inactive at a glance even before reading any text. Grayscaling the thumbnail too was tried
   and reverted - it made the artwork itself look broken/washed out rather than just
   "inactive", which wasn't the intent. */
.list-row-closed { opacity: 0.45; }
/* Optional - most rows won't have one (image is optional, same convention as About/
   Announcements), so this only renders when a row actually sets one. */
.list-row-image {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.list-row-text h3 { margin: 0 0 0.2em; }
.list-row-text p { margin: 0; font-size: 0.9em; color: var(--muted); }
/* venue/time line (see dateWindowRowsHtml in app.js) - between the badges and the description,
   so "where and when" reads before "what it's about". Needs the extra .list-row-text prefix
   to out-specificity the plain "p" rule above, which would otherwise win and mute this. */
.list-row-text .list-row-meta { margin: 0.3em 0; font-size: 0.85em; font-weight: 600; color: var(--maroon-dark); }
/* Same look as button.ghost (see below), just applied to an <a> - that selector is scoped to
   `button` specifically, so a download link needs its own rule rather than just the class.
   Documents-only - Events has no action at all. */
.document-download {
  flex-shrink: 0;
  background: transparent;
  color: var(--maroon-dark);
  border: 1px solid var(--maroon);
  padding: 0.5em 1em;
  border-radius: 6px;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 0.15s ease;
}
.document-download:hover { background: var(--band); }
/* Reuses .badge for a row's "status" tag (Upcoming/Ongoing/Closed) - "Closed" specifically
   gets this muted variant instead, so an inactive Document or past Event doesn't still look
   "live" at a glance. Shared by both tabs. */
.status-badge-closed { background: var(--border); color: var(--muted); }
/* Replaces .document-download when status is "Upcoming" or "Closed" - plain muted text, not a
   button, since there's nothing to click in either case. */
.document-unavailable { flex-shrink: 0; color: var(--muted); font-size: 0.85em; font-style: italic; white-space: nowrap; }
/* Narrow screens: .list-row's default [image | text | action] flex row (align-items: center)
   squeezes the text column so tight that wrapped description lines make the row very tall, and
   the image/button - vertically centered against that whole tall row - end up stranded in the
   middle of the card instead of sitting with the text. Stacking top-to-bottom avoids that. */
@media (max-width: 640px) {
  .list-row { flex-direction: column; align-items: stretch; }
  .list-row-image { width: 100%; height: 180px; }
  .document-download, .document-unavailable { align-self: flex-start; }
}

/* Public-content "Community" grouping menu, shown only once logged in (restricted-tab, no
   admin-only - see index.html). Deliberately styled as a plain tab, not the gold-gloss pill
   the since-removed Admin menu used - that treatment fit Admin because admin is a genuinely
   elevated permission tier, but Community is just a grouping of ordinary public pages, and a
   shiny CTA-style button here visually outranked every other tab (even Save/Log out), which
   was misleading given how minor this menu actually is. The caret is the only visual hint
   left that it opens something. */
.community-menu { position: relative; }
.community-menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  background: transparent;
  border: none;
  padding: 0.5em 0.9em;
  border-radius: 6px;
  cursor: pointer;
  color: var(--ink);
  transition: background-color 0.15s ease, color 0.15s ease;
}
.community-menu-trigger:hover { background: var(--band); }
.community-menu-caret { font-size: 0.75em; transition: transform 0.15s ease; }
.community-menu-trigger[aria-expanded="true"] .community-menu-caret { transform: rotate(180deg); }
.community-menu-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 0.5rem;
  min-width: 180px;
  background: white;
  border-top: 3px solid var(--gold);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  overflow: hidden;
  z-index: 10;
  animation: tab-fade-in 0.15s ease;
}
@media (prefers-reduced-motion: reduce) {
  .community-menu-dropdown { animation: none; }
}
.community-menu-item { display: block; width: 100%; text-align: left; padding: 0.65em 1em; border-radius: 0; }

/* App shell */
.app-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: white;
  border-bottom: 3px solid var(--gold);
  padding: 0.7rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
/* Row 1: logo/name on the left, Save/Log out/Login on the right. flex-wrap is the same
   last-resort safety net .header-nav-row below already has - normally both sides fit on one
   line, but logged in (badge + Save + Log out, not just a single Login button) on a narrow
   phone alongside a club name long enough to wrap to two lines, this keeps the actions from
   getting crushed instead of just falling to a second line. */
.header-top-row { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }
/* Row 2: tabs, full width to themselves - flex-wrap stays as a last-resort safety net for
   extremely narrow windows, but with a whole row (not a three-way split) to work with, it
   shouldn't ever actually trigger at normal desktop widths, admin's extra tab included. */
.header-nav-row { display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; }
/* Thinner ring than .club-logo's (2px vs 3px) - proportional to the smaller size, same
   padding+gradient technique. */
.header-logo {
  width: 36px;
  height: 36px;
  object-fit: contain;
  border-radius: 50%;
  flex-shrink: 0;
  padding: 2px;
  background: var(--gold-gloss);
  box-shadow: var(--ring-emboss-thin);
}
/* white-space: nowrap here (removed) was forcing the full club name onto one line no matter
   how narrow the screen was - on mobile that line is wider than the viewport, which pushed
   the whole page into horizontal overflow (the bug: header background not reaching the edge,
   peach body background showing through on the side). Letting it wrap fixes this at the root. */
.club-name { font-weight: 700; color: var(--maroon-dark); }
.home-link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  border-radius: 6px;
  /* Flex items default to min-width:auto, which can silently block text from wrapping/
     shrinking below its unwrapped width even without white-space:nowrap - overriding this
     is what actually lets .club-name wrap inside this flex row. */
  min-width: 0;
}
.home-link:hover .club-name { text-decoration: underline; }

/* Shown only when printing (see @media print below) - the app header itself is hidden in
   print, so the report needs its own logo+name letterhead instead. */
.print-letterhead { display: none; }
.print-only { display: none; }
.garage-export-actions { margin-left: auto; display: flex; gap: 0.5rem; }
.tabs { display: flex; gap: 0.3rem; flex-wrap: wrap; }
.tab {
  background: transparent;
  border: none;
  padding: 0.5em 0.9em;
  border-radius: 6px;
  cursor: pointer;
  color: var(--ink);
  transition: background-color 0.15s ease, color 0.15s ease;
}
.tab:hover:not(.active) { background: var(--band); }
/* Solid maroon fill - the one place on screen maroon still shows up as a fill rather than
   just text, now that the buttons are gold (see conversation). It's the "you are here"
   indicator, visible on every single screen, so it's a meaningful enough spot to anchor the
   brand's other primary color rather than letting it recede to text-only. */
.tab.active { background: var(--maroon); color: white; font-weight: 600; }

.header-actions { margin-left: auto; display: flex; align-items: center; gap: 0.6rem; }
.badge { background: var(--gold); color: var(--maroon-dark); padding: 0.3em 0.7em; border-radius: 12px; font-size: 0.8em; }

main { padding: 1.2rem; max-width: 1100px; margin: 0 auto; }
.tab-panel { display: none; }
/* `animation`, not `transition` - a transition doesn't reliably fire across a display:none ->
   block change (there's no "before" state to interpolate from), but a browser does run an
   animation the moment an element starts being rendered. This is what turns switching tabs
   from an instant jump-cut into a quick fade+rise. */
.tab-panel.active { display: block; animation: tab-fade-in 0.2s ease; }
@keyframes tab-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .tab-panel.active { animation: none; }
}
/* Same class of bug as the old #login-screen[hidden] fix (see git history) - an author rule
   like .tab-panel.active always beats the browser's native [hidden] default, specificity
   aside, so a restricted panel that somehow gains .active (a stale/race-y login state, a
   future bug, anything) would render anyway despite still being `hidden`. This is the actual
   gate, independent of whatever adds/removes .active - login-gating still isn't a real
   security boundary this way (the Lambda is, see README), but it should never visually leak
   restricted content pre-login either. */
.restricted-tab[hidden] { display: none !important; }

.tx-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 0.5rem;
  background: white;
  padding: 1rem;
  border-radius: 8px;
  margin-bottom: 1rem;
  border: 1px solid var(--border);
  border-top: 3px solid var(--gold);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.tx-form input, .tx-form select {
  padding: 0.5em;
  border: 1px solid var(--border);
  border-radius: 6px;
  width: 100%;
}
.tx-form button { grid-column: -2 / -1; }
/* Native <input type="date"> doesn't support a placeholder like the text fields around it,
   and mobile browsers often show it with no visible hint at all (no calendar icon, no format
   text) unlike desktop - an explicit label avoids it looking like a blank, unlabeled box. */
.date-field { display: flex; flex-direction: column; gap: 0.2em; justify-content: flex-end; }
.date-field-label { font-size: 0.78em; color: var(--muted); }

/* overscroll-behavior-x stops horizontal scroll from "chaining" into the whole page once a
   table hits its left/right edge - the same rubber-band-bounce issue as the vertical one
   fixed on <html>, just on the horizontal axis and scoped to these scrollable tables. */
.table-scroll { overflow-x: auto; overscroll-behavior-x: contain; background: white; border-radius: 8px; border: 1px solid var(--border); }
table { border-collapse: collapse; width: 100%; font-size: 0.9em; }
th, td { padding: 0.5em 0.7em; border-bottom: 1px solid var(--border); text-align: left; white-space: nowrap; }
/* Not position:sticky here - the app header above is now page-level sticky (see .app-header),
   and a table header ALSO sticking at top:0 would fight it for the same spot and overlap.
   Tried offsetting it below the nav header instead (top: var(--header-height)) - in practice
   the header still ended up misplaced mid-table rather than pinned, even after fixing the
   border-collapse/position:sticky interaction, so this was reverted rather than debugged
   further. Revisit if sticky table headers come up again. */
thead th { background: var(--band); color: var(--maroon-dark); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
tfoot td { font-weight: 700; background: var(--band); }
tr.total td { font-weight: 700; border-top: 2px solid var(--gold); }
tr.section-header td { background: var(--gold); color: var(--maroon-dark); font-weight: 700; }
/* Zebra striping - excludes rows that already have their own distinct background (section
   headers, totals) so they aren't overridden by the alternating tint. */
tbody tr:not(.section-header):nth-child(even) td { background: var(--band); }

.row-delete { background: transparent; border: none; color: var(--danger); cursor: pointer; }

.warnings { color: var(--warning); font-size: 0.85em; margin-bottom: 0.5rem; }
.warnings p { margin: 0.2em 0; }

.tracking-month { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }

.pagination { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-top: 0.8rem; }
.pagination button:disabled { opacity: 0.4; cursor: default; }

/* Long descriptive text (e.g. transaction Particulars) should wrap, not force the whole
   table wider - overrides the general td { white-space: nowrap } above for just this cell. */
td.wrap { white-space: normal; min-width: 220px; }

/* flex-wrap + a min-width, same pattern as .rp-columns below - three equal columns with no
   wrap would get crushed to a sliver each on a narrow phone; this lets them stack instead. */
.signature-lines { display: flex; gap: 3rem; margin: 3rem 0 1rem; flex-wrap: wrap; }
.signature-lines > div { flex: 1; min-width: 150px; text-align: center; }
.sig-blank { display: block; border-bottom: 1px solid var(--ink); height: 2.5rem; }

.summary-table { width: 100%; max-width: 480px; }
.summary-table td { border-bottom: 1px solid var(--border); }

.rp-columns { display: flex; gap: 2rem; flex-wrap: wrap; }
.rp-columns > div { flex: 1; min-width: 260px; }

.report-controls { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }

/* Chart.js needs a container with an explicit HEIGHT (not just max-width) to size against
   responsively - without one, its canvas has nothing to bound it and can render enormous
   (this happened in practice - a pie chart filling almost the whole viewport). The padding
   below still works with that fixed height because of the global box-sizing:border-box (top
   of this file) - Chart.js just gets a slightly smaller content-box to draw into, not a box
   that overflows 340px. */
.chart-box {
  max-width: 700px;
  height: 340px;
  position: relative;
  margin-bottom: 2rem;
  background: white;
  padding: 1rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  border-top: 3px solid var(--gold);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}

/* Column proportions for the report tables - table-layout:fixed (set in @media print below)
   needs explicit widths or it just splits columns evenly, which is wrong for a table where
   Particulars/Status need far more room than Date/Debit/Credit. Applied generally, not just
   in print, so on-screen and printed layouts stay consistent. */
#report-ledger-table th:nth-child(1), #report-ledger-table td:nth-child(1) { width: 11%; }
#report-ledger-table th:nth-child(2), #report-ledger-table td:nth-child(2) { width: 10%; }
#report-ledger-table th:nth-child(3), #report-ledger-table td:nth-child(3) { width: 8%; }
#report-ledger-table th:nth-child(4), #report-ledger-table td:nth-child(4) { width: 41%; }
#report-ledger-table th:nth-child(5), #report-ledger-table td:nth-child(5) { width: 10%; }
#report-ledger-table th:nth-child(6), #report-ledger-table td:nth-child(6) { width: 10%; }
#report-ledger-table th:nth-child(7), #report-ledger-table td:nth-child(7) { width: 10%; }

#report-garage-table th:nth-child(1), #report-garage-table td:nth-child(1) { width: 22%; }
#report-garage-table th:nth-child(2), #report-garage-table td:nth-child(2) { width: 14%; }
#report-garage-table th:nth-child(3), #report-garage-table td:nth-child(3) { width: 12%; }
#report-garage-table th:nth-child(4), #report-garage-table td:nth-child(4) { width: 14%; }
#report-garage-table th:nth-child(5), #report-garage-table td:nth-child(5) { width: 14%; }
#report-garage-table th:nth-child(6), #report-garage-table td:nth-child(6) { width: 24%; }

@media print {
  .app-header, .report-controls, #save-btn, #logout-btn { display: none !important; }
  .tab-panel { display: none !important; }
  #tab-reports { display: block !important; }
  .no-print { display: none !important; }
  .print-only { display: block; }

  /* The Garage Fee Register's own "Download PDF" button prints that tab directly instead of
     the Reports tab - toggled via a body class rather than a tab switch, so the on-screen
     view doesn't jump when the user clicks it. */
  body.printing-garage #tab-reports { display: none !important; }
  body.printing-garage #tab-garage { display: block !important; }

  @page { margin: 14mm; }

  /* Named page (CSS Fragmentation's `page` property) rather than a JS-injected <style>
     toggling @page's `size` at print time - the injected-stylesheet version was unreliable
     (printed portrait regardless), because a browser's print pagination reads @page state
     before it reliably re-observes a <style> element added moments before window.print().
     Tying the landscape page directly to #tab-garage in the static sheet sidesteps that. */
  #tab-garage { page: garage-landscape; }
  @page garage-landscape { size: landscape; margin: 8mm; }

  /* Browsers strip background colors from print by default (ink-saving) - without this,
     every maroon/gold band below would just print as plain black-on-white regardless of
     what color rules exist. */
  * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }

  body {
    background: white;
    /* Same family the xlsm's PDF export uses (reportlab's Helvetica), for a matching look. */
    font-family: Helvetica, Arial, sans-serif;
    font-size: 9.5pt;
    color: #000;
  }
  h2, h3, h4 { font-family: Helvetica, Arial, sans-serif; }

  .print-letterhead {
    display: flex;
    align-items: center;
    gap: 0.7em;
    border-bottom: 3pt solid var(--gold);
    padding-bottom: 8pt;
    margin-bottom: 10pt;
  }
  .letterhead-logo { width: 48pt; height: 48pt; object-fit: contain; border-radius: 50%; }
  .print-letterhead h2 { margin: 0; font-size: 14pt; }
  h2 { font-size: 15pt; }
  h3 { font-size: 12pt; margin-top: 1.2em; }

  /* `overflow-x: auto` has no scrollbar in print - it just silently clips anything that
     doesn't fit (this is what was cutting off the Balance column). Let content flow instead,
     and constrain the table itself (below) so it actually fits the page. */
  .table-scroll { overflow: visible; border: none; border-radius: 0; }

  #report-ledger-table, #report-garage-table, .summary-table, #rp-receipts-table, #rp-payments-table {
    table-layout: fixed;
    width: 100%;
    font-size: 8.5pt;
  }
  #report-ledger-table th, #report-ledger-table td,
  #report-garage-table th, #report-garage-table td {
    padding: 3pt 5pt;
    white-space: normal;
    word-break: break-word;
    vertical-align: top;
  }

  /* Bank-statement look: solid maroon header band with white text, like the xlsm PDF. */
  thead th {
    background: var(--maroon) !important;
    color: white !important;
    position: static;
    font-size: 8.5pt;
  }
  #report-ledger-table tbody tr:nth-child(even),
  #report-garage-table tbody tr:nth-child(even) {
    background: var(--band) !important;
  }
  tfoot td {
    background: var(--gold) !important;
    color: var(--maroon-dark) !important;
  }

  .chart-box { max-width: 100%; page-break-inside: avoid; }
  .signature-lines { page-break-inside: avoid; }

  /* Garage Fee Register has ~23 columns (member details + 12 month cells + totals) - too
     dense for the report tables' font size, and the month cells are live <input>s that need
     to read as plain text rather than form fields once printed. */
  #garage-table { font-size: 7pt; }
  /* white-space:normal alone wraps at word boundaries ("Start" / "Mo."), which is fine at
     this density - word-break:break-word (tried initially) instead splits mid-word ("Star" /
     "t Mo.") whenever a column is too narrow, which reads as broken rather than just tight. */
  #garage-table th, #garage-table td { padding: 2pt 3pt; white-space: normal; }
  #garage-table thead th { font-size: 6.5pt; }
  #garage-table input {
    border: none;
    background: transparent;
    padding: 0;
    width: 100% !important;
    font-size: inherit;
    font-family: inherit;
    color: inherit;
  }
  #garage-table input[type="number"]::-webkit-inner-spin-button,
  #garage-table input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
  #garage-table input[type="checkbox"] { width: auto !important; }
}
