@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Tamil:wght@700;800&family=Poppins:wght@400;500;600;700;800&display=swap');

:root {
  --sheet-w: 1073px;   /* the ledger sheet footprint: its table renders 1071px (1070 + a collapsed cell border) + the 2px card border. ONE number for the title, header, filter card, table card (and the செலவு / வரவு card in expenses.css) so they always flush with the table. */
  --bg: #fff8f1;
  --surface: #ffffff;
  --surface-2: #fff2e4;
  --text: #2a1a10;
  --muted: #8a6f5c;
  --border: #f2ddc7;
  --accent: #ea580c;
  --accent-2: #dc2626;
  --accent-soft: #ffe8d4;
  --accent-text: #ffffff;
  --credit: #15803d;
  --expense: #b91c1c;
  --radius: 16px;
  --shadow: 0 1px 2px rgba(120, 53, 15, 0.06), 0 12px 28px -10px rgba(234, 88, 12, 0.28);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; color-scheme: light; overflow-anchor: none; }

input[type="number"] { -moz-appearance: textfield; }
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

body {
  margin: 0;
  font-family: 'Poppins', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  overflow-x: hidden;
  /* overflow-anchor:none on the individual shrinking elements (header etc.)
     wasn't enough — the browser's scroll anchoring picks its "anchor node"
     from anywhere in the scrollport, not necessarily the element that's
     actually changing size, so it kept compensating for the members.html
     scroll-compact shrink by silently reducing scrollY to match (confirmed
     by logging scrollY alongside body.scrollHeight — they fell in
     lockstep). Disabling it here, on the actual scroll container, is what's
     needed for that animation to be intentional rather than fought. */
  overflow-anchor: none;
}

header {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: 30px 16px;
  text-align: center;
  border-bottom: 1px solid var(--border);
  background: linear-gradient(120deg, #fb923c, #ea580c 55%, #b91c1c);
  box-shadow: 0 6px 20px -8px rgba(234, 88, 12, 0.55);
  transition: padding 0.2s ease;
  /* Without this, the browser's scroll anchoring "compensates" for this
     element shrinking by silently reducing scrollY to match — which drops
     it below the is-scrolled threshold, un-triggers the shrink, and the
     element snaps back to full size while scroll position stays stuck near
     0. Confirmed by logging scrollY alongside the shrink: they fell in
     lockstep until scrollY bottomed out. This animation is intentional, not
     an accidental content-shift the browser needs to protect against. */
  overflow-anchor: none;
}
/* index.html only (.header-logo-main marks its header, same scoping
   technique as the is-scrolled override below) — no left/right padding,
   top/bottom 30px unchanged, by explicit request. */
header:has(.header-logo-main) { padding-left: 0; padding-right: 0; }

/* Shared by index.html and members.html — each page's own JS toggles
   body.is-scrolled once the page is scrolled a bit, shrinking the pinned
   header/pills/table-header stack so more of the sheet is visible while
   browsing. 30px vertical padding is a fixed value regardless of how many
   lines the title wraps to — the banner's total height isn't set directly
   anywhere, it's just padding + content, so it naturally grows to fit 2-3
   wrapped lines on a narrow screen without needing separate rules per line
   count (h1's own negative margin-bottom, computed live in JS from its
   actual rendered height, already accounts for however tall the title
   ends up being — see syncHeaderTitleCollapse in shared.js). */
body.is-scrolled header { padding: 30px 16px; }
/* index.html only — its header has a logo image (.header-logo-main;
   members.html's header is text-only), used here to scope this override
   without needing a body/page-level class. Tighter vertical padding once
   scrolled than the shared 30px above, by explicit request — independent
   of the title's own height-driven shrink (h1's transform:scale() +
   negative margin-bottom above), which still applies on top of this. */
body.is-scrolled header:has(.header-logo-main) { padding: 5px 0; }
/* HEART LOGIC — do not change without asking (transform, not font-size).
   Deliberately NOT font-size here — shrinking font-size continuously changes
   how many words fit per line, and the exact moment the title's text
   re-wraps from 2 lines to 1 is a discrete layout event (not a continuous
   one), so it can't be smoothed by a transition — it snaps, no matter how
   short the duration. Measured this directly: frame-by-frame height deltas
   were a steady ~5-10px/frame until one single frame dropped ~59px, then
   resumed smooth. transform:scale() shrinks the already-laid-out text
   visually instead (its font-size, and therefore its line-wrapping, never
   changes) — pure compositor work, so it's smooth by construction — and
   --header-h1-margin-collapse (set in members.js from a one-time real
   measurement of the title's rendered height) pulls the freed-up space
   closed to actually shrink the header, not just the text inside it. */
body.is-scrolled header h1 {
  transform: scale(0.6);
  margin-bottom: var(--header-h1-margin-collapse, 0px);
}

header h1 {
  margin: 0 0 4px;
  font-family: 'Noto Sans Tamil', 'Poppins', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 2.3rem;
  font-weight: 800;
  color: #fff;
  letter-spacing: -0.01em;
  text-shadow: 0 2px 10px rgba(0,0,0,0.15);
  transform-origin: top center;
  transition: transform 0.2s ease, margin-bottom 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

/* HEART LOGIC — do not change without asking (one shared sticky container).
   members.html only — header, the type-filter pills, and the spreadsheet's
   own column-title/filter row all need to scroll-pin and shrink together as
   one visual unit (see body.is-scrolled rules below). Giving them one shared
   sticky container instead of each being independently sticky (with the
   pills bar's/table header's own `top` computed via JS from the elements
   above them) avoids a real, measured two-stage stutter: a plain CSS class
   change on one piece started shrinking at t=0, but a sibling's position
   only updated once JS re-measured — often a whole transition-duration
   later — so it sat frozen while its neighbor shrank, then snapped into
   place. Inside one sticky wrapper, everything below the first child is
   back in normal flow, always correctly adjacent with zero timing
   dependency and zero per-frame JS cost, regardless of page size. */
.sticky-top-stack {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
  overflow-anchor: none;
}
.sticky-top-stack header { position: static; }
/* <main>'s own top padding existed to space it from the sticky header —
   redundant now that the header (and everything meant to sit flush below
   it) lives inside .sticky-top-stack instead, ending exactly where <main>
   should begin. */
.sticky-top-stack + main { padding-top: 0; }

/* HEART LOGIC — do not change without asking (.sticky-top-inner shares
   <main>'s exact container mechanism, not a lookalike).
   Wraps the pills bar + table header (not <header> itself, which stays
   full-bleed edge-to-edge) using the exact same max-width/margin/padding
   numbers as <main> below it — not just similar values, the same
   mechanism — so the pills bar and table header are structurally
   guaranteed to line up with .members-table-card, rather than each
   computing its own separate centering math that only coincidentally
   lands on the same numbers. An earlier version had .members-thead-wrap
   compute its own max-width via calc()/min() directly; that measured
   correctly in every automated check (multiple browser engines, many
   widths) but still intermittently rendered flush against the left edge
   on a real device, fixed by a reload — sharing one container instead of
   keeping two independent calculations in sync removes that failure mode
   entirely rather than chasing it. */
.sticky-top-inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 16px;
}
@media (max-width: 900px) {
  .sticky-top-inner { padding: 0 12px; }
}

.header-logo { height: 2.4em; width: auto; flex-shrink: 0; margin: -0.55em 0; }
/* index.html's main app header — same logo as the password gate (already
   has the full name baked in), sized to fill the header banner instead of
   sitting as a small icon next to separate heading text. */
.header-logo-main { height: 90px; width: auto; max-width: 100%; }
/* Separate containers for the logo and title text, by explicit request —
   each is its own flex item inside header h1 (already display:flex), so
   they can be independently targeted/adjusted without touching the other. */
.header-logo-container {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.header-title-text {
  display: inline-block;
}

header p { margin: 0; color: rgba(255,255,255,0.85); font-size: 0.9rem; }

.header-link-btn {
  position: absolute;
  top: 50%;
  right: 20px;
  transform: translateY(-50%);
  font-size: 0.85rem;
  font-weight: 700;
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.18);
  border: 1px solid rgba(255, 255, 255, 0.4);
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.15s ease;
}

.header-link-btn:hover { background: rgba(255, 255, 255, 0.3); }

@media (max-width: 600px) {
  .header-link-btn { position: static; display: inline-block; transform: none; margin-bottom: 10px; }
}

main {
  max-width: 1320px;
  margin: 0 auto;
  padding: 28px 16px 90px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  width: 100%;
}

.totals-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 25;
  height: 46px;
  background: linear-gradient(120deg, #fb923c, #ea580c 55%, #b91c1c);
  box-shadow: 0 -6px 20px -8px rgba(234, 88, 12, 0.55);
}

/* Moved out of <header> — the banner's own columns (see .totals-track) only
   start at சந்தா (col 5), so the space to their left is otherwise empty.
   The two buttons (members-link / ← Back, and logout) share one flex
   container (.banner-left-group), so only ONE element needs to be
   position:absolute + JS-tracked, and the logout button naturally sits
   right next to its sibling without needing its own left/right/transform.
   Specificity here beats .header-link-btn's own mobile rule (position:static)
   so this stays positioned at every screen width. left:16px is just the default: both pages centre the group
   (.totals-banner-simple below for members.html, .totals-banner-two in expenses.css for the ledger). */
.totals-banner .banner-left-group {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: 26;
}
/* Individual buttons inside the group flow naturally — no absolute
   positioning per-button, no per-button transforms. */
.totals-banner .banner-left-group .header-link-btn {
  position: static;
  transform: none;
  margin: 0;
}

/* Buttons inherit their look from .header-link-btn; .logout-btn only needs
   the extras a real <button> (vs an <a>) needs to look/feel like the link
   next to it: form font-family reset and an explicit cursor. */
.totals-banner .logout-btn,
.totals-banner .master-lock-btn {
  cursor: pointer;
  font-family: inherit;
}

/* Master lock lives in the members.html banner as a single global guard
   against accidental edits — when engaged, every per-cell 🔒 button is
   hidden and every Avoid checkbox + the new-member row are inert. The
   emoji is the only content, so the padding is tighter than the text
   buttons next to it (which need room for a full Tamil word). */
.totals-banner .master-lock-btn {
  padding: 6px 10px;
  line-height: 1;
  font-size: 0.95rem;
  white-space: nowrap;   /* icon + "Edit" / "Lock" stay on one line */
}

/* Edit mode (members.js adds body.editing while the page is unlocked): the page behind the table turns grey so it is
   obvious edits are live. The sticky stack paints its own background, so it needs the same grey. The cancel button
   (x, next to the lock) only exists while editing. */
body.editing { background: #d4d4d8; }
body.editing .sticky-top-stack { background: #d4d4d8; }
body:not(.editing) #master-cancel-btn { display: none; }

/* master-locked = read-only page mode. Cell inputs are set to readonly
   (see setCellsLocked in members.js) so no typing is possible; Avoid
   checkboxes and the new-member row also visibly disable so the
   read-only intent is obvious to the user at a glance. */
body.master-locked #members-body .avoid-check {
  pointer-events: none;
  opacity: 0.45;
}
body.master-locked #new-member-row input,
body.master-locked #new-member-row select {
  opacity: 0.6;
}

@media (max-width: 600px) {
  .totals-banner .header-link-btn { font-size: 0.75rem; padding: 6px 12px; }
  .totals-banner .banner-left-group { gap: 6px; }
}

/* members.html only — its footer banner has no .totals-track (no ledger
   numbers to leave room for, unlike the main Ledger Sheet page that shares
   this same .totals-banner markup), so the button group can sit centered
   across the whole bar instead of pinned to the left edge. Scoped to this
   extra class rather than changing .totals-banner .banner-left-group
   itself, since that rule's left:16px is deliberately left-anchored
   specifically to leave room for the Ledger Sheet's totals-track columns. */
.totals-banner-simple .banner-left-group {
  left: 50%;
  transform: translate(-50%, -50%);
}

.totals-track { position: relative; width: 100%; height: 100%; }

.totals-col {
  position: absolute;
  top: 0;
  height: 100%;
  box-sizing: border-box;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

/* The மொத்தம் column's total is the Grand Total, written "Grand Total = 3,333" on one line: it starts at the left edge
   of that column and runs on to the right, over the empty குறிப்பு footer (!important beats the width the JS sets). */
.totals-col-grand { width: auto !important; white-space: nowrap; justify-content: flex-start; }

.totals-col::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: rgba(255, 255, 255, 0.4);
}

.totals-value { color: #fff; font-size: 1.05rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.totals-grand { color: #ffe8d4; }

.totals-shine {
  display: inline-block;
  animation: totals-shine-pulse 3.5s ease-in-out infinite;
}

@keyframes totals-shine-pulse {
  0%, 50%, 100% {
    text-shadow: none;
    transform: scale(1);
  }
  65% {
    color: #fff;
    text-shadow: 0 0 8px #fff, 0 0 20px #ffe8d4, 0 0 34px #ffe8d4;
    transform: scale(1.08);
  }
}

@media (max-width: 600px) {
  .totals-banner { height: 38px; }
  .totals-col { padding: 0 4px; }
  .totals-value { font-size: 0.8rem; }
  .totals-col-grand { padding-left: 10px; }  /* keeps the word clear of the previous column's number */
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px;
}

.card h2 { margin: 0 0 14px; font-size: 1.05rem; font-weight: 700; color: #9a3412; }

/* Was independently position:sticky with top:var(--header-height) — now a
   plain normal-flow child of .sticky-top-inner (see .sticky-top-stack's own
   HEART LOGIC comment), so it needs no position/top of its own; the shared
   stack's own background (var(--bg) on .sticky-top-stack) is what keeps
   scrolled-past table rows from showing through, same as the rest of the
   stack's children. */
/* HEART LOGIC — do not change without asking (outer #sheet-title never
   transforms/resizes; .sheet-title-inner below is the part that scales).
   An earlier version put transform:scale() directly on #sheet-title
   itself, sized to min(--sheet-w (1073px),100%)/0.7 so the scaled-down result would
   still visually fill the available width — that part worked, but it
   also meant #sheet-title's own width no longer matched its siblings'
   plain --sheet-w (1073px), so its margin:auto centering (below) computed a DIFFERENT
   offset than .sheet-filter-card/.sheet-thead-wrap/.sheet-table-card get,
   and the title's left edge drifted away from the spreadsheet's — visible
   specifically on desktop once scrolled, confirmed by direct measurement
   (margin-left computed to 0 instead of matching the siblings' ~74px,
   because a box wider than its container can't be auto-centered normally).
   Keeping #sheet-title itself fixed at plain width:--sheet-w (1073px)/max-width:100%,
   identical to its siblings, guarantees its left edge is ALWAYS pixel-
   identical to theirs, in every state, on every screen width — no
   coincidence of matching numbers to maintain, structurally guaranteed
   the same way .sticky-top-inner guarantees it for the others. */
#sheet-title {
  font-size: 1.75rem;
  font-weight: 800;
  color: #c2410c;
  padding: 4px 0 4px 5px;
  margin: 12px auto 0;
  width: var(--sheet-w);
  max-width: 100%;
  letter-spacing: -0.01em;
  transition: margin-top 0.2s ease, margin-bottom 0.2s ease;
}
body.is-scrolled #sheet-title {
  margin-top: 0;
  margin-bottom: var(--sheet-title-margin-collapse, 0px);
}
/* Same transform:scale() treatment as header h1/.sheet-filter-card (see
   their own HEART LOGIC comments) — not font-size, since this title's text
   length varies (different function names) and could wrap, and animating
   font-size on wrapping text causes a discrete, unsmoothable jump exactly
   when it collapses from 2 lines to 1. SCALE here must match
   syncSheetTitleCollapse's own constant in app.js. */
.sheet-title-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  /* top left, not top center — unlike header h1/.sheet-filter-card, this
     title is left-aligned, not centered; scaling from center would shift
     its left edge sideways as it shrinks. */
  transform-origin: top left;
  transition: transform 0.2s ease;
}
body.is-scrolled .sheet-title-inner {
  transform: scale(0.7);
  /* transform:scale(0.7) shrinks what's RENDERED, but the box still
     reserves its full pre-scale width in the layout — leaving real,
     unused visual space to the right of the scaled content that the
     overflow-x:auto scroll/fade below never accounts for (scrollWidth/
     clientWidth are both measured in the pre-scale coordinate system,
     unaffected by this element's own transform), so content that would
     actually fit in that wasted space got faded/hidden instead. 100%/0.7
     — relative to #sheet-title above, which (unlike this element) never
     moves or resizes — makes this exactly wide enough that scaling by 0.7
     lands the visible result flush with #sheet-title's own right edge,
     not short of it, without disturbing #sheet-title's own position. */
  width: calc(100% / 0.7);
}
/* Title+year stay on one row; the குடும்பத் தலைவர்கள் button can drop to its
   own row below once compact if there's no room for both, by explicit
   request — flex-wrap:wrap above is the base rule's own value, un-
   overridden (an earlier version forced nowrap on the whole row, keeping
   the button stuck alongside the title always). The title text itself
   still never wraps mid-sentence — see the span rule below, which is
   where nowrap + the horizontal swipe/scroll + fade-mask fallback live,
   scoped to just the text instead of the whole row, for the rare case a
   function name alone is too long for one line even with the button out
   of the way. */
body.is-scrolled .sheet-title-inner > span {
  white-space: nowrap;
  overflow-x: auto;
  max-width: 100%;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
body.is-scrolled .sheet-title-inner > span::-webkit-scrollbar { display: none; }
body.is-scrolled .sheet-title-inner > span.has-overflow {
  mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
}

.members-toggle {
  font-size: 0.85rem;
  font-weight: 700;
  padding: 6px 14px;
  min-height: 32px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--accent-soft);
  color: var(--accent);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.members-toggle:hover:not(:disabled) { background: var(--accent); color: #fff; }
.members-toggle:disabled { opacity: 0.6; cursor: not-allowed; }

.members-toggle.active {
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  border-color: transparent;
  color: #fff;
  box-shadow: 0 6px 16px -4px rgba(234,88,12,0.55);
}

form { display: flex; flex-direction: column; gap: 10px; }

label { font-size: 0.85rem; color: var(--muted); display: flex; flex-direction: column; gap: 4px; }

input, select, textarea {
  font: inherit;
  font-size: 16px;
  padding: 11px 12px;
  min-height: 44px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  width: 100%;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

button {
  font: inherit;
  font-weight: 700;
  padding: 12px 18px;
  min-height: 44px;
  border-radius: 12px;
  border: none;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  color: var(--accent-text);
  cursor: pointer;
  box-shadow: 0 8px 18px -6px rgba(234,88,12,0.5);
  transition: transform 0.12s ease, opacity 0.15s ease;
}

button:hover { opacity: 0.94; transform: translateY(-1px); }
button:active { transform: translateY(0); }
button.secondary { background: transparent; border: 1px solid var(--border); color: var(--text); box-shadow: none; }

.status { font-size: 0.85rem; min-height: 1.2em; }
.status.error { color: var(--expense); }
.status.ok { color: var(--credit); }

/* contain:layout stops this element's (possibly much wider, horizontally
   scrolling) content from counting toward ancestors' scrollWidth — without
   it, overflow-x:auto visually clips fine but <html>'s true content width
   still leaks through, which real mobile browsers use to decide the whole
   page needs auto-zooming out to fit. That silently breaks every
   position:sticky/fixed element's visual pinning on phones (not
   reproducible in desktop devtools/emulation, which don't have that
   auto-zoom heuristic) even though nothing was actually scrollable. Applies
   to both the main Ledger Sheet and the members table. */
.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; contain: layout; }

table { width: 100%; border-collapse: collapse; font-size: 0.88rem; margin-top: 4px; }
table.wide { min-width: 480px; }
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); }
th { color: var(--muted); font-weight: 600; }
.amt-credit { color: var(--credit); font-weight: 600; }
.amt-expense { color: var(--expense); font-weight: 600; }

#gate {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: linear-gradient(160deg, #fb923c, #ea580c 45%, #7c2d12);
}

#gate .card { width: 100%; max-width: 460px; text-align: center; }
.gate-logo { width: 400px; max-width: 100%; height: auto; display: block; margin: 0 auto 12px; }
.gate-subtitle { color: var(--muted); margin: -6px 0 8px; font-size: 2rem; }
/* #gate-status is a <p>, which carries a default browser margin — visible
   as extra space below the Unlock button even while the status text is
   empty (min-height:1.2em above already reserves room for the message
   itself, so this margin was pure excess, not needed for the no-layout-jump
   behavior that min-height exists for). */
#gate .status { margin: 0; }

.hidden { display: none !important; }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Tablet ---------- */
@media (max-width: 900px) {
  main { padding: 22px 12px 84px; }
  .card { padding: 16px; }
  table.sheet td input { padding: 8px 6px; font-size: 0.9rem; }
  table.sheet thead th { padding: 10px 6px; font-size: 0.8rem; }
  table.sheet thead th:nth-child(n+2) { font-size: 0.95rem; }
  .santha-cell { padding: 0 6px; gap: 5px; }
  #sheet-title { font-size: 1.5rem; }
}

/* Phones: 8 columns can't stay legible at any width squeeze, so this table
   alone gets a minimum width and scrolls horizontally within its own card
   (table-wrap already has overflow-x:auto) — the rest of the page doesn't
   scroll sideways, just this one element. */
@media (max-width: 600px) {
  table.sheet { min-width: 720px; }
  .santha-cell { gap: 5px; min-height: 44px; }
  .santha-amount { font-size: 0.9rem; }
  main { padding-bottom: 70px; }
}

@media (max-width: 480px) {
  header h1 { font-size: 1.8rem; }
  /* index.html only (members.html's header has no logo image) — at 140px
     fixed height, the logo + long title together are wider than a phone
     viewport; header h1's flex row doesn't wrap or shrink on its own, so
     the combined content overflowed centered (bleeding off both left and
     right edges), forcing real horizontal scroll on the whole page.
     Bumped from 45px to 75px, by explicit request — the title wraps to
     two lines at this width, and 45px read as visibly small/undersized
     next to that taller two-line block; this only touches the rest
     (expand) state's mobile size, scoped by this same media query, so
     desktop's own 90px (base rule above) is untouched. The shrunk/
     scrolled state's mobile logo size isn't set independently — it's
     derived from this value via body.is-scrolled header h1's own
     transform:scale(0.6) — so it gets proportionally bigger too as a
     side effect of this change, not a separate decision. */
  .header-logo-main { height: 75px; }
  main { padding: 18px 12px 52px; gap: 16px; }
  .card { padding: 18px; }
  .totals-banner { height: 34px; }
}

/* index.html only — narrower than the 480px breakpoint above. The title
   wraps to THREE lines at this width (confirmed by measurement: ~129.6px
   tall) instead of two, making the 480px breakpoint's 75px logo look
   small again by the same comparison that motivated bumping it from 45px
   there — bumped further here to better match. Also nudges the logo off
   the screen's left edge: header's own left/right padding is 0 at every
   width (see header:has(.header-logo-main) above, by earlier explicit
   request), which reads fine for the centered title text but left the
   logo flush against the edge with no breathing room — margin-left here
   (not header padding) keeps that padding:0 decision intact for
   everything else and only nudges the logo specifically.
   height:80px/margin-left:8px, not a bigger jump — header h1 is
   flex-wrap:nowrap with justify-content:center and no explicit width on
   the title text, so it's the two of them sharing one fixed budget:
   images don't shrink below their intrinsic size in a flex row (the
   min-width:auto quirk), so past a certain logo size there's no longer
   enough left for the Tamil title's own minimum practical wrap width,
   and centering an overflowing nowrap row bleeds off BOTH edges at once
   (confirmed directly — a 105px attempt put the logo at a NEGATIVE left
   position, off-screen, not just tight). Checked this exact combination
   across the realistic device range (360–480px, several logo/margin
   pairs) — this is the largest size with zero overflow at every one of
   them, including 360px specifically (a real, common Android width, not
   just a legacy outlier), rather than one that merely looked fine at the
   two widths first tried. */
@media (max-width: 400px) {
  .header-logo-main { height: 80px; }
  .header-logo-container { margin-left: 8px; }
  /* Phone-width overflow fix (measured, not guessed). The row is logo 80px + 8px margin + 10px gap, leaving the title
     (vw - 98px). Its longest Tamil word, "ஸ்ரீ வீரமாத்தி அம்மன் துணை", is about 10.1em wide, so at the fixed 1.8rem (28.8px) it needs
     291px: it overflows at every width under ~390px (360px: sideways scroll of 15px, logo cut off at the left).
     Scaling the font with the screen keeps the word at 0.69 x vw, which fits down to ~320px. This is a static size
     at rest, NOT the scroll-shrink animation (that stays transform:scale on h1, see Heart Logic rule 3).
     Scoped by :has(.header-logo-main) so members.html's text-only header is untouched. */
  header:has(.header-logo-main) h1 { font-size: min(1.8rem, 6.8vw); }
}

/* ---------- Ledger spreadsheet ---------- */

/* index.html only — same architecture as members.html's
   .members-thead-wrap/.members-tbody-wrap/.members-table-card (see that
   file's HEART LOGIC index for the full reasoning): header + pills +
   #sheet-title + this thead all share .sticky-top-stack/.sticky-top-inner
   instead of each independently computing a sticky offset from
   --header-height, and the table itself is now part of the page's own
   scroll instead of its own boxed max-height:74vh/overflow-y:auto area —
   that internal scroll box was incompatible with the shared-stack shrink
   approach (the "scroll" that should trigger the shrink is the PAGE's
   scroll, not a scroll happening inside a small fixed-height box).
   Unlike members.html's table, this one already stretches to fill its
   container (table.sheet's own width:100% + this table's percentage
   columns, not literal fixed px) — so .sheet-thead-wrap and
   .sheet-table-card below don't need explicit width-matching numbers, just
   width:auto within the same max-width:1320px/margin:auto/padding
   container (.sticky-top-inner / <main>) that guarantees alignment. */
/* width explicitly matches the table's own fixed 1250px + 2px border
   footprint (--sheet-w (1073px)), same as .sheet-table-card and .sheet-filter-card
   below — without this, all three just filled their shared container
   (up to 1288px wide), leaving each one's border/shadow/rounded-corner box
   visibly wider than the actual table/pills content centered inside it, on
   any screen wider than --sheet-w (1073px). Same fix members.html needed for
   .members-thead-wrap/.members-filter-card. */
.sheet-thead-wrap {
  overflow: hidden;
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 14px 14px 0 0;
  box-shadow: var(--shadow);
  contain: layout;
  overflow-anchor: none;
  width: var(--sheet-w);
  max-width: 100%;
  margin: 22px auto 0;
  transition: margin-top 0.2s ease;
}
body.is-scrolled .sheet-thead-wrap { margin-top: 0; }

.sheet-tbody-wrap {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--border);
  border-radius: 0 0 14px 14px;
  box-shadow: var(--shadow);
  contain: layout;
}

/* No card framing around the ledger sheet's own card wrapper — its border/
   shadow/rounding now lives directly on .sheet-thead-wrap/.sheet-tbody-wrap
   instead (same "no container" treatment as members.html's spreadsheet).
   width/margin match .sheet-thead-wrap's own footprint — see its comment
   above. */
.sheet-table-card {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  width: var(--sheet-w);
  max-width: 100%;
  margin: 0 auto;
}

/* Keeps its .card.compact framing (unlike .sheet-thead-wrap/
   .sheet-table-card above) — width still explicitly matches the table's
   own --sheet-w (1073px) footprint (see .sheet-thead-wrap's comment above for why),
   just with the card's usual background/border/shadow/padding restored.
   No transform here (unlike .members-filter-card) — transform:scale()
   shrinks an element's rendered width along with everything else, which
   would pull this card's visible width away from the table's below it the
   moment it's scrolled. The shrink instead happens on .pills-inline (its
   only real content, below) while this outer box's own width/padding stay
   fixed, so its box keeps matching the spreadsheet's width in every state. */
.sheet-filter-card {
  width: var(--sheet-w);
  max-width: 100%;
  margin: 22px auto 0;
  transition: margin-top 0.2s ease, padding 0.2s ease;
}
body.is-scrolled .sheet-filter-card { margin-top: 0; padding: 4px 0; }

/* Same transform:scale() technique as .members-filter-card (see its own
   HEART LOGIC comment) — shrinks as one compositor-only unit instead of
   animating each pill's own padding/font-size — just scoped to this inner
   wrapper instead of .sheet-filter-card itself (see that rule's own
   comment for why). top left, not top center, since its content
   (வருடம்/விழா rows) is left-aligned, not centered — EXCEPT once scrolled
   (see body.is-scrolled override below), where the rows switch to
   centered and the scale origin switches with them; scaling centered
   content from a left origin would pull it visibly off-center (the right
   side shrinks toward the left corner more than the left side does). */
.pills-inline {
  transform-origin: top left;
  transition: transform 0.2s ease, margin-bottom 0.2s ease;
}
body.is-scrolled .pills-inline {
  gap: 4px;   /* tighter than the expanded 10px between the year row, divider and function row */
  transform: scale(0.75);   /* must match SHEET_FILTER_CARD_COMPACT_SCALE in app.js */
  transform-origin: top center;
  margin-bottom: var(--sheet-filter-card-margin-collapse, 0px);
}
/* Rows center horizontally once compact, by explicit request — matches
   removing .sheet-filter-card's left/right padding above. */
body.is-scrolled .pill-line { justify-content: center; }
/* வருடம்:/விழா: labels — dropped once compact, by explicit request; the
   pill buttons themselves already show the selected value, so the label
   is redundant once space is at a premium. */
body.is-scrolled .pill-label { display: none; }

/* margin-top:0 overrides the base table rule's 4px (see that rule's own
   comment) — same fix already applied to table.members-table for the same
   reason: .sheet-thead-wrap has no background of its own (transparent,
   showing the page's own background through), so that 4px gap between
   the wrapper's top edge and the table's actual top edge showed up as a
   visible sliver of page background above the header cells' own gradient
   — confirmed by direct measurement (a real 5px gap: 4px margin + 1px
   wrapper border) before this fix. */
table.sheet { width: 100%; table-layout: fixed; font-size: 1rem; font-variant-numeric: tabular-nums; margin-top: 0; }

table.sheet thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: linear-gradient(90deg, #ffe8d4, #ffddb8);
  border-bottom: 2px solid var(--border);
  padding: 12px 10px;
  font-size: 0.9rem;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: normal;
  word-break: break-word;
  color: #9a3412;
  transition: padding 0.2s ease, font-size 0.2s ease;
}

table.sheet thead th:nth-child(n+2) { font-size: 1.05rem; }

table.sheet td { border: 1px solid var(--border); padding: 0; vertical-align: middle; }
table.sheet td:nth-child(1) { text-align: center; padding: 10px 8px; }
/* HEART LOGIC — do not change without asking (explicit white, not
   transparent, for members.html's sake).
   Explicit rather than left transparent — members.html's table sits
   directly on the page background now (no card wrapper behind it, see
   .members-table-card above), which is a very faint cream (--bg), not true
   white; explicit white here keeps the white/cream zebra contrast crisp
   regardless of what's behind the table on any given page. */
table.sheet tbody tr:nth-child(odd) td { background: var(--surface); }
table.sheet tbody tr:nth-child(even) td { background: var(--surface-2); }
table.sheet tbody tr:hover td { background: var(--accent-soft); }

table.sheet td input {
  border: none;
  border-radius: 0;
  background: transparent;
  min-height: 48px;
  padding: 10px 12px;
  width: 100%;
  text-align: right;
  font-size: 1rem;
}

table.sheet td:nth-child(2) input, table.sheet td:nth-child(3) input, table.sheet td:nth-child(4) input, table.sheet td:nth-child(5) input, table.sheet td:nth-child(7) input { text-align: left; }
table.sheet input.f-name[readonly] { cursor: default; }
table.sheet input.f-name[readonly]:focus { outline: none; box-shadow: none; }
table.sheet td:nth-child(1) input { text-align: center; padding-left: 8px; padding-right: 8px; }
/* ---------- Ledger Sheet table widths — HEART LOGIC, do not change without
   asking ----------
   S.No. 70px / இரசீது எண் 80px / பெயர் 310px / ஊர் 170px / மொபைல் எண் 150px /
   தலைக்கட்டு வரி 120px / குறிப்பு 170px
   (= 1070px total.) Literal fixed pixel widths, identical on desktop/
   tablet/mobile — same "no stretch" approach as members.html's table, and
   for the same reason percentages were dropped here: table-layout:fixed
   does NOT resolve a column's % against the table's own width when other
   columns are literal px and the table's rendered width is constrained
   (e.g. by min-width on a narrow screen) — it instead treats the percentage
   values as relative WEIGHTS splitting whatever space is left after the
   fixed-px columns, which on a phone (where little space was left) measured
   as low as 13.7px for a 9% column — the actual money columns, unusably
   narrow. Fixed px for every column sidesteps that resolution question
   entirely. A responsive override for columns 1-4 used to exist at the
   903px breakpoint above, but it never actually applied at any width (same
   specificity, always beaten by this base rule's later position in the
   file, same category of bug as .members-filter-card's own max-width
   override once had) — removed rather than fixed, since fixed widths at
   every screen size is what's wanted now anyway. On a screen narrower than
   the table's total width, it scrolls horizontally within its own card,
   same technique used by members.html. */
table.sheet:not(.members-table) { width: 1070px; margin-left: auto; margin-right: auto; } /* centers it on the rare wide screen where the container (up to 1288px) is actually wider than the table itself */
table.sheet th:nth-child(1), table.sheet td:nth-child(1) { width: 70px; }
table.sheet th:nth-child(2), table.sheet td:nth-child(2) { width: 80px; }
table.sheet th:nth-child(3), table.sheet td:nth-child(3) { width: 310px; }
table.sheet th:nth-child(4), table.sheet td:nth-child(4) { width: 170px; }
table.sheet th:nth-child(5), table.sheet td:nth-child(5) { width: 150px; }
table.sheet th:nth-child(6), table.sheet td:nth-child(6) { width: 120px; }
table.sheet th:nth-child(7), table.sheet td:nth-child(7) { width: 170px; }

table.sheet td input:focus { outline: 2px solid var(--accent); outline-offset: -2px; background: var(--bg); }


table.sheet tr.row-saved td { background: rgba(21, 128, 61, 0.15); transition: background 1.2s ease; }

/* Row flash, see blinkRow() in shared.js: green = saved, red = could not save. Only the 50% keyframe is given, so the
   row eases from its own resting colour to the flash colour and back (no jump to transparent). Inputs are made
   see-through meanwhile, otherwise their own solid background would hide the colour. */
@keyframes row-blink-ok { 50% { background: rgba(21, 128, 61, 0.35); } }
@keyframes row-blink-error { 50% { background: rgba(220, 38, 38, 0.32); } }
table.sheet tr.row-blink-ok td { animation: row-blink-ok 0.35s ease-in-out 3; }
table.sheet tr.row-blink-error td { animation: row-blink-error 0.35s ease-in-out 3; }
table.sheet tr.row-blink-ok td input, table.sheet tr.row-blink-ok td select,
table.sheet tr.row-blink-error td input, table.sheet tr.row-blink-error td select { background: transparent; }

/* ---------- சந்தா: admin-fixed price, member just checks/unchecks ---------- */

.santha-cell {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 0 12px;
  min-height: 48px;
}

.santha-cell input[type="checkbox"] { width: 18px; height: 18px; min-height: auto; flex-shrink: 0; }

.santha-amount {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  transition: color 0.15s ease, opacity 0.15s ease;
}

.santha-amount.faded { color: var(--muted); opacity: 0.45; font-weight: 400; }

/* ---------- Year / function pills ---------- */

.pill-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.pill-row:first-of-type { margin-top: 4px; }
/* index.html's year/function rows, scrolled/compact state only, by
   explicit request — expanded state keeps the plain wrap behavior above.
   Same nowrap + horizontal swipe/scroll technique as members.html's
   .members-filter-card .pill-row (see its own HEART LOGIC comment there
   for the reasoning: wrapping splits a long pill onto its own line
   unevenly on narrow screens). */
body.is-scrolled .sheet-filter-card .pill-row {
  flex-wrap: nowrap;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
body.is-scrolled .sheet-filter-card .pill-row::-webkit-scrollbar { display: none; }
/* has-overflow set by syncPillRowOverflow (app.js) only when the row
   actually has more content than fits — fades the trailing edge into a
   "more content, scroll for it" cue instead of the last pill's rounded
   shape getting sliced off flat at the card's edge. Scrolled state only,
   same as the nowrap rule above — the JS sets/clears the class in both
   states, but it's inert in the expanded state since no CSS here matches
   without body.is-scrolled. */
body.is-scrolled .sheet-filter-card .pill-row.has-overflow {
  mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
}

.pill {
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  padding: 8px 16px;
  min-height: 36px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s ease, transform 0.1s ease, padding 0.2s ease, min-height 0.2s ease, font-size 0.2s ease;
}

.pill:hover { border-color: var(--accent); transform: translateY(-1px); }

.pill.active {
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  border-color: transparent;
  color: #fff;
  box-shadow: 0 6px 16px -4px rgba(234,88,12,0.55);
}

.hint { color: var(--muted); font-size: 0.8rem; margin: 8px 0 0; }

.card.compact { padding: 16px 20px; display: flex; flex-direction: column; gap: 2px; }
.card.compact h2 { margin: 0 0 8px; font-size: 0.95rem; }
.card.compact .pill-row { margin-top: 0; gap: 6px; }
.card.compact .pill { padding: 6px 14px; min-height: 30px; font-size: 0.82rem; }
.card.compact .hint { margin-top: 6px; }
.card.compact .status:empty { display: none; }

.pills-inline { display: flex; flex-direction: column; gap: 10px; }
.pill-divider { width: 100%; height: 1px; background: var(--border); }
.pill-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pill-label, .title-group-label { font-weight: 700; color: #9a3412; font-size: 0.95rem; white-space: nowrap; }
/* வரவு: / செலவு: groups in #sheet-title (app.js updateSheetTitle): வரவு stays with its own buttons; when a
   group is too wide for the screen its buttons wrap inside the group, not across the other group. */
.title-group { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Print / save as PDF. Accounts (print.js): last in the title row, pushed to its right end. */
.sheet-title-inner .print-btn { margin-left: auto; }
/* Members (members-print.js): the filter card holds just the Print button, kept at the right edge. */
/* .card.compact's own padding (more specific than .members-filter-card's) would otherwise pull the button 20px in from the
   table's right edge; only the sides are zeroed, the top and bottom keep the gap above the table. */
.members-filter-card.card { padding-left: 0; padding-right: 0; }
.members-filter-card .print-btn { display: block; margin-left: auto; }

/* The printout (print-table.js fills #print-sheet, a direct child of <body> on both pages): only it is printed,
   landscape, the column titles repeat on every page. */
#print-sheet { display: none; }
@media print {
  body > *:not(#print-sheet) { display: none !important; }
  body { background: #fff; color: #000; }
  #print-sheet { display: block; font-size: 10pt; line-height: 1.3; }
  #print-sheet h1 { font-size: 16pt; margin: 0; }
  #print-sheet h2 { font-size: 12pt; margin: 2pt 0 2pt; font-weight: 600; }
  #print-sheet .print-meta { margin: 0 0 8pt; color: #333; }
  #print-sheet table { width: 100%; min-width: 0; border-collapse: collapse; margin: 0; }
  #print-sheet thead { display: table-header-group; }
  #print-sheet tr { break-inside: avoid; }
  #print-sheet th, #print-sheet td { padding: 2pt 5pt; border-bottom: 1px solid #bbb; text-align: left; }
  #print-sheet th { border-bottom: 2px solid #000; color: #000; }
  #print-sheet .num { text-align: right; font-variant-numeric: tabular-nums; }
  #print-sheet th:first-child, #print-sheet td:first-child { width: 1%; white-space: nowrap; }   /* S.No: as narrow as its content */
  #print-sheet tfoot td { font-weight: 700; border-top: 2px solid #000; }
  @page { size: A4 landscape; margin: 12mm; }
}

/* ---------- Members page: per-cell lock/unlock ---------- */

.cell-flex { display: flex; align-items: center; gap: 2px; }
.cell-flex input { flex: 1; min-width: 0; }
.cell-input:read-only { cursor: default; color: var(--muted); }
.cell-input:read-only:focus,
.cell-input:read-only:hover {
  outline: none;
  box-shadow: none;
  border-color: transparent;
  background: transparent;
}
.cell-input.unlocked { color: var(--text); background: var(--accent-soft); }

table.sheet thead tr.filter-row th,
table.sheet thead tr.filter-row td {
  background: var(--surface);
  border-bottom: 2px solid var(--border);
  padding: 2px 6px;
  transition: padding 0.2s ease;
}

.filter-input {
  width: 100%;
  min-height: 18px;
  padding: 2px 8px;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--text);
  transition: padding 0.2s ease, min-height 0.2s ease, font-size 0.2s ease;
}

/* ---------- Members page table widths — HEART LOGIC, do not change without asking ----------
   S.No. 70px / Roll No. 76px / பெயர் 230px / Name 230px / ஊர் 180px / Mobile Number 170px / பிறந்த தேதி 140px / Avoid 80px
   (= 1176px total. The header frame (.members-thead-wrap) and the pill row (.members-filter-card)
   are 1178px = table + 2px border, so the table fills the frame with no gap on either side.)
   Literal fixed pixel widths, identical on desktop/tablet/mobile — the table
   does NOT stretch to fill its card (unlike table.sheet's default). On a
   screen narrower than the table's total width, it scrolls horizontally
   within its own card (table-wrap already supports this), same technique
   used for the main Ledger Sheet on phones — nothing else on the page moves. */
table.members-table { width: 1176px; margin-top: 0; }

/* The header (column titles + filter inputs) is a separate mini table from
   the data rows, pinned via position:sticky on this WRAPPING div rather than
   per-cell. Reason: any ancestor with overflow-x:auto (needed for the
   horizontal-scroll mobile fallback below) silently becomes the containing
   block for position:sticky on its descendants — so a per-cell sticky header
   nested inside the horizontally-scrolling box never tracks real page
   scroll, only that box's own (nonexistent) internal scroll. Splitting the
   header out from under that ancestor avoids the trap entirely. Its
   horizontal scroll position is mirrored from the body by members.js so
   columns still track left/right drags on narrow screens. */
/* contain:layout on both wrappers (found by measuring, not guessing — see
   commit notes) closes a real gap: overflow-x alone visually clips the
   1010px-wide table fine, but doesn't stop its true width from still
   counting toward the document root's scrollWidth. On real mobile browsers
   (not reproducible in desktop devtools/emulation) that phantom width made
   <html> report ~833px wide on a 390px phone, which is exactly the signal
   mobile Chrome/Safari use to decide the WHOLE PAGE needs to auto-zoom-out
   to "fit everything" — silently breaking every position:sticky/fixed
   element's visual pinning even though nothing was actually scrollable.
   contain:layout tells the browser this element's content can never affect
   anything outside it, closing the leak at its source. */
/* HEART LOGIC — do not change without asking (thead-wrap's position,
   separation from tbody, and width-matching to .members-filter-card).
   .members-thead-wrap now lives inside .sticky-top-stack (see members.html),
   right after the pills bar, instead of inside .members-table-card — a plain
   normal-flow child of an already-sticky ancestor needs no position/top of
   its own, so its screen position tracks the stack natively with zero JS
   measurement, however large the table body below it gets. It's still a
   separate element from .members-tbody-wrap (not one shared table) because
   any ancestor with overflow-x:auto (needed for the horizontal-scroll mobile
   fallback below) silently becomes the containing block for position:sticky
   on its descendants — a per-cell sticky header nested inside that
   horizontally-scrolling box would only track the box's own (nonexistent)
   internal scroll, never real page scroll. Its horizontal scroll position is
   mirrored from the body by members.js so columns still track left/right
   drags on narrow screens.
   Matches .members-filter-card's own 1038px footprint (1036px table + 2px
   border, no padding — see the "no container" note on .members-table-card
   below) and centers within .sticky-top-inner the same way — the same
   max-width:1320px/margin:auto/padding container <main> uses, so this is
   structurally guaranteed to line up with .members-table-card below rather
   than computing its own separate width math. On phones it shrinks below
   1038px (max-width:100% of a narrower .sticky-top-inner), which is what
   makes the table inside overflow and need the horizontal scroll-sync in
   the first place. */
.members-thead-wrap {
  /* overflow:hidden (not just overflow-x) clips the header cells' own
     square corners into this rounded shape — with border-collapse:collapse
     (the table default), border-radius directly on a <th> renders
     unreliably, so the corner-rounding happens here at the wrapper instead.
     Still fine for the horizontal scrollLeft sync in members.js — hidden
     still accepts a programmatic scrollLeft, it just has no visible
     scrollbar/drag, same as overflow-x:hidden did. */
  overflow: hidden;
  border-radius: 14px 14px 0 0;
  border: 1px solid var(--border);
  border-bottom: none; /* .members-tbody-wrap supplies its own top border instead — one single seam line, not a doubled-up pair */
  box-shadow: var(--shadow); /* same shadow every .card on the page uses — box-shadow paints outside the border-box, unaffected by this element's own overflow:hidden */
  contain: layout;
  overflow-anchor: none;
  width: 1178px; /* 1176px table + 2px border (1px each side) so the table still fits exactly, without the border clipping it */
  max-width: 100%;
  margin: 22px auto 0; /* top value is breathing room from the pills bar above; closes to 0 once scrolled, just like the pills bar's own margin does */
  transition: margin-top 0.2s ease;
}
body.is-scrolled .members-thead-wrap { margin-top: 0; }

/* contain:layout closes a real gap: overflow-x alone visually clips a
   wide table fine, but doesn't stop its true width from still counting
   toward the document root's scrollWidth. On real mobile browsers (not
   reproducible in desktop devtools/emulation) that phantom width made
   <html> report far wider than the actual phone screen, which is exactly
   the signal mobile Chrome/Safari use to decide the WHOLE PAGE needs to
   auto-zoom-out to "fit everything" — silently breaking every
   position:sticky/fixed element's visual pinning even though nothing was
   actually scrollable. contain:layout tells the browser this element's
   content can never affect anything outside it, closing the leak at its
   source. */
.members-tbody-wrap {
  overflow-x: auto; /* real user drag-scroll on narrow screens, unlike thead-wrap's hidden/JS-driven mirror — must stay auto, not hidden */
  overflow-y: hidden; /* clips the last row's own square bottom corners into this rounded shape, same reasoning as .members-thead-wrap above */
  -webkit-overflow-scrolling: touch;
  contain: layout;
  border-radius: 0 0 14px 14px;
  border: 1px solid var(--border); /* top edge doubles as the one seam line between this and .members-thead-wrap above (which has no bottom border of its own) */
  box-shadow: var(--shadow); /* same shadow every .card on the page uses */
}

/* HEART LOGIC — do not change without asking (no .card framing around the
   spreadsheet, by explicit request).
   No card framing (border/background/shadow/padding) around the spreadsheet
   at all — it sits directly on the page background, just the table itself
   with each cell supplying its own background color. Card hugs the table's
   fixed width instead of stretching to the page's max width, then centers
   itself horizontally — no empty space on either side. */
.members-table-card {
  align-self: center;
  width: fit-content;
  max-width: 100%;
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
}
.members-table-card .status { margin-top: 12px; }

/* No card framing (border/background/shadow/padding) — same "sits directly
   on the page" treatment as the spreadsheet below it (see
   .members-table-card). pill-row's own gap widens a bit (see further down)
   for breathing room now that there's no card edge to imply spacing.

   REVERT: restore width:1038px (was matching .members-thead-wrap's 1036px
   table + 2px border) and background:var(--surface); border:1px solid
   var(--border); border-radius:var(--radius); box-shadow:var(--shadow);
   padding:16px 20px (the .card.compact defaults) to go back to the boxed
   look. */
.members-filter-card {
  width: 1178px; /* always matches .members-thead-wrap's own width exactly, so the pill row's outer edges line up with the spreadsheet below it — the pill-row itself scrolls internally (see below) if its content doesn't fit at this width, rather than the card growing/shrinking to match content */
  max-width: 100%; /* of .sticky-top-inner's already-padded content box (see above) */
  margin: 28px auto 0; /* top value replicates <main>'s old header→pills gap, now that this card lives in .sticky-top-stack instead of <main> */
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  transform-origin: top center;
  transition: transform 0.2s ease, margin-top 0.2s ease, margin-bottom 0.2s ease;
}
/* margin-top eases down at the same breakpoints/rhythm main's own top
   padding used to. */
@media (max-width: 900px) {
  .members-filter-card { margin-top: 22px; }
}
@media (max-width: 480px) {
  .members-filter-card { margin-top: 18px; }
}

/* HEART LOGIC — do not change without asking (transform, not per-pill
   padding/font-size — same reasoning as header h1 above).
   Scaling the whole card visually (rather than animating each pill's own
   padding/font-size, ~10+ separate layout-triggering properties every
   frame) means everything inside — padding, buttons, text — rides along
   unscaled and un-animated, at zero extra layout cost. transform doesn't
   shrink the space the card reserves in the page, so a negative
   margin-bottom (computed in members.js from the card's actual rendered
   height, which transform doesn't affect) pulls that freed space closed. */
body.is-scrolled .members-filter-card {
  transform: scale(0.72);
  margin-top: 0;
  margin-bottom: var(--pills-card-margin-collapse, 0px);
}
/* Eight columns, 1176px: S.No 70 + Roll 76 + பெயர் 230 + Name 230 + ஊர் 180 + Mobile 170 + பிறந்த தேதி 140 + Avoid 80. */
table.members-table th:nth-child(1), table.members-table td:nth-child(1) { width: 70px; }
table.members-table th:nth-child(2), table.members-table td:nth-child(2) { width: 76px; }
table.members-table th:nth-child(3), table.members-table td:nth-child(3) { width: 230px; }
table.members-table th:nth-child(4), table.members-table td:nth-child(4) { width: 230px; }
table.members-table th:nth-child(5), table.members-table td:nth-child(5) { width: 180px; }
table.members-table th:nth-child(6), table.members-table td:nth-child(6) { width: 170px; }
table.members-table th:nth-child(7), table.members-table td:nth-child(7) { width: 140px; }
table.members-table th:nth-child(8), table.members-table td:nth-child(8) { width: 80px; }
table.members-table td:nth-child(2) { text-align: center; }
table.members-table td:nth-child(8) { text-align: center; }
/* பிறந்த தேதி (7th): a date box, left-aligned like the other text columns. */
table.members-table td:nth-child(7) input { text-align: left; }
/* ஊர் (5th): text, so left-aligned like the names (inputs are right-aligned by default). Also covers its filter and add-row boxes. */
table.members-table td:nth-child(5) input { text-align: left; }
/* Mobile column (6th): a fixed "+91" sits in front of the number, so people type only the 10 digits. Display only: the
   saved value stays the plain 10 digits. Widths are untouched (HEART LOGIC: literal px columns). */
table.members-table td:nth-child(6) { position: relative; }
table.members-table td:nth-child(6)::before {
  content: "+91";
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  font-weight: 600;
  pointer-events: none;
}
/* Inputs are right-aligned by default; left-align so the digits sit right after the +91. */
table.members-table td:nth-child(6) input { padding-left: 49px; text-align: left; }
table.members-table thead th:nth-child(3) { font-weight: 800; }
table.members-table thead th { padding: 4px 3px; }

table.members-table tr.row-avoided { opacity: 0.45; }

table.members-table tfoot td { background: rgba(21, 128, 61, 0.1); }
table.members-table tfoot td input:disabled,
table.members-table tfoot td select:disabled { background: transparent; color: var(--muted); opacity: 0.55; cursor: not-allowed; }
.new-member-num { text-align: center; color: #15803d; font-weight: 700; }

/* Custom-drawn (not the native tick) since accent-color can only recolor the
   browser's own checkmark glyph, not swap it for an X. */
table.sheet td input.avoid-check {
  appearance: none;
  -webkit-appearance: none;
  width: 25px;
  height: 25px;
  min-height: auto;
  padding: 0;
  margin: 0;
  border-radius: 2px;
  vertical-align: middle;
  border: 2px solid var(--border);
  background: var(--surface);
  cursor: pointer;
}

table.sheet td input.avoid-check:checked {
  background-color: var(--expense);
  border-color: var(--expense);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 5L19 19M19 5L5 19' stroke='white' stroke-width='3.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 65%;
}

/* Members page header font size — HEART LOGIC, do not change without asking:
   1.05rem (16.8px) for every column, independent of the main Ledger Sheet's
   own header sizing (which some columns here previously inherited by accident). */
table.members-table thead th:nth-child(1),
table.members-table thead th:nth-child(2),
table.members-table thead th:nth-child(3),
table.members-table thead th:nth-child(4),
table.members-table thead th:nth-child(5),
table.members-table thead th:nth-child(6),
table.members-table thead th:nth-child(7),
table.members-table thead th:nth-child(8) {
  font-size: 1.05rem;
}

table.members-table thead th { padding: 10px; transition: padding 0.2s ease, font-size 0.2s ease; }

/* Scroll-compact state (see body.is-scrolled header/.members-filter-card
   above) — shrinks the column-title row and the Filter inputs row too, once
   the user is actually scrolling. Doesn't touch their resting-state sizes
   (the 1.05rem heart logic above), only what applies while scrolled. */
body.is-scrolled table.members-table thead th { padding: 3px 6px; }
body.is-scrolled table.members-table thead th:nth-child(1),
body.is-scrolled table.members-table thead th:nth-child(2),
body.is-scrolled table.members-table thead th:nth-child(3),
body.is-scrolled table.members-table thead th:nth-child(4),
body.is-scrolled table.members-table thead th:nth-child(5),
body.is-scrolled table.members-table thead th:nth-child(6),
body.is-scrolled table.members-table thead th:nth-child(7),
body.is-scrolled table.members-table thead th:nth-child(8) {
  font-size: 0.8rem;
}
body.is-scrolled table.sheet thead tr.filter-row th,
body.is-scrolled table.sheet thead tr.filter-row td {
  padding: 1px 6px;
}
body.is-scrolled .filter-input { min-height: 14px; padding: 1px 6px; font-size: 0.72rem; }

/* index.html's own Ledger Sheet column-title row — same shrink treatment
   as table.members-table's above, just missed when the scroll-compact
   architecture was first ported to this page (the filter-row rule above,
   and .filter-input, already applied here since those are shared
   table.sheet selectors, not members-table-specific — this one wasn't). */
body.is-scrolled table.sheet:not(.members-table) thead th { padding: 3px 6px; font-size: 0.78rem; }

/* Centred title, logo at the left edge. The logo box is taken out of the row (absolute) so its width never pushes the title
   off-centre or steals line width on a phone; the title gets the full header width. min-height keeps the header as tall
   as the logo made it (90px; 75px at 480; 80px at 400, the same sizes as .header-logo-main above). */
header:has(.header-logo-main) h1 { position: relative; min-height: 90px; }
header:has(.header-logo-main) .header-logo-container { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); margin: 0; }
@media (max-width: 480px) { header:has(.header-logo-main) h1 { min-height: 75px; } }
@media (max-width: 400px) { header:has(.header-logo-main) h1 { min-height: 80px; } }
