/*
 * Spirit of Christmas -- base styles.
 * Every color/spacing value here is a CSS custom property from themes.css
 * (Section 7: no hardcoded values, no inline styles anywhere in the app).
 * Layout is responsive-first and touch-friendly -- volunteers use this on
 * a tablet on the warehouse floor (Section 15).
 */

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  background: var(--color-bg);
  color: var(--color-fg);
}

.wrap {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a {
  color: var(--color-link);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.02em;
  margin: 0 0 0.6rem;
}
h1 { font-size: 1.9rem; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.1rem; }

/* Visible focus indicator on every interactive element (Section 15: WCAG 2.1 AA) */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--color-focus-ring);
  outline-offset: 2px;
}

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

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--color-primary);
  color: var(--color-primary-fg);
  padding: 0.75rem 1rem;
  z-index: 100;
}
.skip-link:focus {
  left: 0.5rem;
  top: 0.5rem;
}

/* ---------- Header / nav ---------- */

.app-header {
  background: var(--brand-teal-color);
  color: var(--color-header-fg);
  padding: 0 1.25rem;
  min-height: 58px;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  z-index: 900;
  box-shadow: var(--shadow-md);
  border-bottom: 3px solid var(--color-header-bg);
}

.app-header__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  font-family: var(--font-display);
  font-size: 1.4rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-decoration: none;
  color: #000000;
  text-shadow: 0 0 6px #ffffff, 0 0 12px #ffffff;
  white-space: nowrap;
  padding: 0.6rem 0;
}
.app-header__brand:hover {
  text-decoration: none;
}
/* Matches .login-logo span's accent color for "Christmas" on the login page. */
.app-header__brand-accent {
  color: var(--color-accent);
}

/* The logo file has its own white canvas baked in (not transparent), so it
   gets a fixed-white chip to sit on regardless of theme -- every other
   surface in this app is theme-adaptive (and dark in half the themes),
   which would otherwise clip or clash with the logo's own background. */
.brand-logo-chip {
  display: block;
  background: #ffffff;
  border: 1px solid var(--brand-ink-color);
  border-radius: var(--radius-sm);
  padding: 3px 8px;
  line-height: 0;
}
.brand-logo-chip img {
  display: block;
  height: 30px;
  width: auto;
}
.brand-logo-chip--lg {
  padding: 8px 18px;
  border-radius: var(--radius-md);
  margin: 0 auto 1rem;
  width: fit-content;
}
.brand-logo-chip--lg img {
  height: 52px;
}

/* Active program year badge -- a fixed light chip (like .brand-logo-chip)
   rather than the dark #theme-select/.user-menu__btn treatment, so it reads
   distinctly against those and stays legible regardless of which of the 8
   header colors is active. Text/border colors are fixed, not theme-driven
   (--color-fg/--color-header-fg would flip to light-on-dark in dark
   themes, wrong for a permanently white chip). */
.app-header__program-year {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.2);
  border-radius: var(--radius-sm);
  padding: 0.25rem 0.85rem;
  line-height: 1.15;
}
.app-header__program-year-label {
  font-family: var(--font-display);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--brand-ink-color);
}
.app-header__program-year-value {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 800;
  color: #000000;
}

.app-nav {
  display: flex;
  gap: 2px;
  flex: 1;
  flex-wrap: wrap;
}

/* Hamburger toggle for .app-nav -- hidden here, shown only under the
   max-width: 768px rule below where .app-nav itself collapses to a panel. */
.nav-toggle-btn {
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.15);
  border: 1px solid rgba(255, 255, 255, 0.3);
  color: #ffffff;
  font-size: 1.25rem;
  line-height: 1;
  padding: 0.4rem 0.7rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.nav-toggle-btn:hover {
  background: rgba(255, 255, 255, 0.25);
}

/* Solid white, not the old per-theme translucent white -- the header
   background is a fixed --brand-teal-color in every theme now, so one
   plain, fully-opaque color reads clearly against it everywhere. */
.app-nav a {
  font-family: var(--font-display);
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #ffffff;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-sm);
  transition: background 0.15s ease, color 0.15s ease;
}

.app-nav a:hover {
  color: var(--color-nav-highlight);
  background: rgba(255, 255, 255, 0.1);
  text-decoration: none;
}

/* Current-page highlight is a fixed dark gray in every theme (not
   theme-driven like hover above), so it reads the same regardless of which
   of the 8 header colors is active. */
.app-nav a[aria-current="page"] {
  color: var(--color-nav-highlight);
  background: #333333;
  text-decoration: none;
}

.app-header__user {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-left: auto;
}

/* Theme select -- kept a fixed very-light-gray regardless of the header's
   own background (Header is --brand-teal-color; theme-driven text/border
   colors would flip to light-on-light in some themes, wrong for a
   permanently light control), matching the .user-menu__btn treatment below. */
.theme-select-wrap {
  position: relative;
}
/* appearance: none below strips the native dropdown arrow entirely, so this
   stands in for it -- sized to sit in the padding-right the select already
   reserves, and non-interactive so clicks still reach the select under it. */
.theme-select-wrap::after {
  content: '\25BE';
  position: absolute;
  top: 50%;
  right: 0.65rem;
  transform: translateY(-50%);
  color: #333333;
  font-size: 0.75rem;
  pointer-events: none;
}
#theme-select {
  appearance: none;
  width: auto;
  height: 37.6px; /* matches .user-menu__btn's rendered height, which is set by its 26px avatar circle */
  min-height: 0; /* overrides the generic select{} 44px touch-target floor below so height above actually wins */
  background: #d9d9d9;
  border: 1px solid rgba(0, 0, 0, 0.2);
  color: #1a1a1a;
  font-family: var(--font-body);
  font-size: 0.8rem;
  padding: 0.35rem 1.6rem 0.35rem 0.6rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
#theme-select option {
  background: #d9d9d9;
  color: #1a1a1a;
}

/* User menu (avatar + nickname, dropdown with sign out) */
.user-menu {
  position: relative;
}
.user-menu__btn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: #d9d9d9;
  border: 1px solid rgba(0, 0, 0, 0.2);
  color: #1a1a1a;
  font-family: var(--font-body);
  font-size: 0.82rem;
  padding: 0.3rem 0.7rem 0.3rem 0.3rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.user-menu__btn:hover {
  background: #c9c9c9;
}
.user-menu__avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--color-accent);
  color: #fff;
  font-family: var(--font-display);
  font-size: 0.85rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.user-menu__dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 190px;
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 5px;
  display: none;
  z-index: 950;
}
.user-menu__dropdown.is-open {
  display: block;
}
.user-menu__dropdown-label {
  padding: 6px 10px 4px;
  font-size: 0.78rem;
  color: var(--color-fg-muted);
  white-space: nowrap;
}
.user-menu__dropdown hr {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 4px 0;
}
.user-menu__dropdown button,
.user-menu__dropdown a {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 0;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  border: none;
  background: none;
  color: var(--color-fg);
  font-size: 0.85rem;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.user-menu__dropdown button:hover,
.user-menu__dropdown a:hover {
  background: var(--color-bg-inset);
}

main {
  flex: 1;
  max-width: 1300px;
  width: 100%;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 1.5rem;
}

.page-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.page-heading h1 {
  margin: 0;
}

.app-footer {
  text-align: center;
  padding: 1.25rem;
  background: var(--brand-teal-color);
  color: var(--color-header-fg);
  font-size: 0.82rem;
  border-top: 1px solid var(--color-border);
}

/* ---------- Buttons & form controls (min 44px touch target -- Section 15) ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 44px;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-bg-elevated);
  color: var(--color-fg);
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s ease, filter 0.15s ease;
}

.btn:hover {
  filter: brightness(0.97);
  text-decoration: none;
}

.btn-primary {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-fg);
}
.btn-primary:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  filter: none;
}

.btn-danger {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: #ffffff;
}

.btn-sm {
  min-height: 36px;
  padding: 0.35rem 0.75rem;
  font-size: 0.82rem;
}

.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.btn-full {
  width: 100%;
}

label {
  display: block;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-fg-muted);
  margin-bottom: 0.3rem;
}

input[type="text"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="search"],
input[type="email"],
select,
textarea {
  width: 100%;
  min-height: 44px;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg-elevated);
  color: var(--color-fg);
  font-size: 1rem;
  font-family: var(--font-body);
}

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

textarea {
  min-height: 80px;
}

.field {
  margin-bottom: 1rem;
}
.field-inline {
  margin-bottom: 0;
}

/* Puts two or more .field elements side by side in a stacked form (e.g. a
   modal) -- unlike .inline-form, which shrinks every control to its own
   content width, a plain .field-row child keeps filling its share of the
   row (flex: 1) so First/Last name or City split evenly. Give a field
   .field-row__fixed when it should stay its input's own natural width
   instead (e.g. Zip next to City) -- pair that with .input-narrow/
   .input-medium on the input itself to size it. */
.field-row {
  display: flex;
  gap: 0.75rem;
}
.field-row .field {
  flex: 1 1 0;
  min-width: 0;
}
.field-row .field-row__fixed {
  flex: 0 0 auto;
}

/* Donor Add/Edit modal: tighter vertical rhythm than the site-wide form
   defaults above, scoped to #donor-modal only (not a global .field/input
   change) since this is the one form dense enough -- 7 rows across 11
   fields -- that showing more of it at once without scrolling is worth
   trading some breathing room for. */
#donor-modal .modal-hdr {
  padding: 0.6rem 1.25rem;
}
#donor-modal .modal-body {
  padding: 0.75rem 1.25rem;
}
#donor-modal .modal-ftr {
  padding: 0.5rem 1.25rem;
}
#donor-modal .field-hint {
  margin: 0 0 0.5rem;
}
#donor-modal .field {
  margin-bottom: 0.5rem;
}
#donor-modal label {
  margin-bottom: 0.1rem;
}
#donor-modal input[type="text"],
#donor-modal input[type="email"] {
  min-height: 34px;
  padding: 0.3rem 0.6rem;
}
#donor-modal textarea {
  min-height: 44px;
  padding: 0.3rem 0.6rem;
}
#donor-modal .alert {
  margin-bottom: 0.5rem;
}

.field-hint {
  font-size: 0.8rem;
  color: var(--color-fg-subtle);
  margin-top: 0.25rem;
}

.checkbox-field {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.checkbox-field input {
  width: 22px;
  height: 22px;
  min-height: 0;
  accent-color: var(--color-accent);
}
.checkbox-field label {
  margin: 0;
  text-transform: none;
  font-weight: 500;
}

/* Groups OR SIMILAR + Requires assembly (audit page, per-wish row) onto one
   line with a vertical rule between them, instead of each .checkbox-field
   stacking as its own block. */
.checkbox-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.checkbox-row .checkbox-field + .checkbox-field {
  padding-left: 0.75rem;
  border-left: 1px solid var(--color-border);
}

/* Ethnicity checkboxes (audit page): fixed 3-column grid so the 6 options
   lay out as 3 across, 2 down rather than each .checkbox-field stacking as
   its own block. */
.checkbox-grid-3col {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.4rem 0.75rem;
}

/* Stacks a handful of .checkbox-inline labels vertically, left-aligned, in
   one table cell (Intake's Received/Requires assembly pair) instead of
   .inline-form's default of laying every child out in a single row. */
.checkbox-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}

.checkbox-inline {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
  font-weight: 500;
  white-space: nowrap;
}
.checkbox-inline input {
  width: 20px;
  height: 20px;
  min-height: 0;
  accent-color: var(--color-accent);
}

.radio-inline {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 500;
  font-size: 0.85rem;
}
.radio-inline input {
  width: auto;
  height: auto;
  min-height: 0;
  accent-color: var(--color-accent);
}

fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 1rem;
  margin: 0 0 1.25rem;
}

/* Separates the Pant/Shirt/Shoe groupings within the Sizes fieldset (audit
   page) -- plain <hr>, no border/box-model surprises to work around. */
.field-group-divider {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 0 0 1rem;
}

/* Audit page's Siblings fieldset: query container (not the viewport) so
   the 2-column grid below responds to the fieldset's own available width
   -- it lives in .grid-2col's left (form) column, which is a hard 50% of
   the page (see that rule's comment), so what matters is this column's
   width, not the window's. Same technique .pdf-frame-wrap already uses
   for the Source PDF sizing. */
.siblings-fieldset {
  container-type: inline-size;
}
/* 2 columns x 4 rows for its fixed 8 slots (ChildRepository::
   loadSiblingsForReview() is always padded out to 8 in the template)
   rather than one slot per row -- down to 1 column below the container
   query threshold, where even First name's 15ch floor (see
   .sibling-cell__fields below) plus Child # can't fit two across without
   the right column running under the Source PDF panel.
   minmax(0, 1fr), not bare 1fr: same fix as .grid-2col above and for the
   same reason -- a bare 1fr track still won't shrink below its content's
   min-content size, so the second column was rendering ~314-324px wide
   (blowing well past the fieldset's own ~600px box once padding and the
   column gap were added in) regardless of the 15ch floor on First name,
   which only constrains the flex item inside, not the grid track it
   sits in. */
.siblings-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.5rem 1rem;
}
/* Each cell stacks its First name/Child # row above the "Child Not Found"
   badge (when present) instead of running all three inline -- the 2-column
   grid's .grid-2col fix (minmax(0, 1fr), see that rule's comment) capped
   this fieldset at a hard 50% of the page instead of letting it push the
   Source PDF column narrower, so there's less width here now than the
   fields' old fixed sizes assumed; column-stacking the badge separately
   keeps the fields row itself uncluttered and free to use all of that
   narrower width. */
.siblings-grid .inline-form {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}
/* Vertical rule between the two columns -- on the left column's cells
   (odd positions: 1st, 3rd, ...) rather than a pseudo-element on the grid
   container, so it only spans each row's own height instead of running
   the full fieldset height through the row-gap too. */
.siblings-grid .inline-form:nth-child(odd) {
  border-right: 1px solid var(--color-border);
  padding-right: 0.6rem;
}
/* nowrap plus letting First name flex to fill whatever's left after
   Child #'s fixed width keeps both fields on one line at any cell width,
   same reasoning as the fields-vs-badge stacking above. */
.siblings-grid .sibling-cell__fields {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.5rem;
  width: 100%;
}
.siblings-grid .sibling-cell__fields input[name*="firstName"] {
  flex: 1 1 auto;
  /* Floor, not a fixed size: grows past this whenever the column has
     room, but won't shrink tighter than 15 characters of typing room
     even under the tightest width the 2-column layout allows just above
     the container query's 1-column collapse. */
  min-width: 15ch;
}
/* Placed after every unconditional .siblings-grid rule above (same
   selector specificity throughout this section, so source order decides
   the winner) -- an earlier draft of this query block sat above the
   :nth-child(odd) divider rule and silently lost to it at every
   container width, divider included, since @container doesn't add
   specificity of its own. */
@container (max-width: 500px) {
  .siblings-grid {
    grid-template-columns: 1fr;
  }
  .siblings-grid .inline-form:nth-child(odd) {
    border-right: none;
    padding-right: 0;
  }
}
legend {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 0.4rem;
  color: var(--color-fg-muted);
}

/* ---------- Layout utilities ---------- */

/* minmax(0, 1fr), not bare 1fr: a bare 1fr track still won't shrink below
   its content's min-content width, so when the Siblings matrix (2 columns
   of First name + Child #) made the left column's own min-content wider
   than a true 50%, the grid quietly gave it more than its 1fr share and
   shrank the Source PDF column (right, audit page only) below its
   original width to compensate. minmax(0, ...) makes the 1fr split a hard
   50/50 regardless of either side's content, restoring (and guaranteeing
   a floor on) the PDF column's original width. */
.grid-2col {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}

.inline-form {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  flex-wrap: wrap;
}
.inline-form--end {
  align-items: end;
}
/* Form controls default to width:100% (fills a stacked .field); inside a
   flex row that fights the layout, so each control gets its own
   content-sized width here, and the input-* utilities set an explicit one. */
.inline-form select,
.inline-form input {
  width: auto;
  flex: 0 0 auto;
}
.inline-form .field {
  margin-bottom: 0;
}

/* Admin > Users action buttons (Edit/Password/Lock/Delete): kept on one line
   rather than wrapping to a second row -- the Actions column doesn't have
   room for all 4 at once, so without this the last button (Delete) drops
   below the others. .table-wrap already scrolls horizontally, so letting
   this row grow past the column's fitted width just triggers that instead
   of wrapping. Scoped to this table only -- .inline-form's wrap behavior is
   still wanted everywhere else it's used (actual multi-field forms). */
#users-tbl .inline-form {
  flex-wrap: nowrap;
}
#users-tbl .inline-form > * {
  flex-shrink: 0;
}

/* Donors action buttons (Edit/Lists/Delete): same fix, same reason -- see
   #users-tbl above. */
#donors-tbl .inline-form {
  flex-wrap: nowrap;
}
#donors-tbl .inline-form > * {
  flex-shrink: 0;
}

/* Donors grid: fixed, percentage-sized columns (widths chosen from each
   field's typical content -- Phone/State/ZIP are short and fixed-format,
   Company/Email/Name are open text and can run long) so a single oversized
   value -- e.g. a long company name -- wraps within its own cell instead of
   stretching the table past its container. Without table-layout: fixed a
   nowrap cell just grows the column to fit its content, which pushed the
   Actions column off the visible edge of .table-wrap's scroll area. Actions
   itself stays unwrapped (see .inline-form above) and gets a wide enough
   share of the row for all three buttons on one line. */
#donors-tbl {
  table-layout: fixed;
}
#donors-tbl col.col-select { width: 3%; }
#donors-tbl col.col-name { width: 12%; }
#donors-tbl col.col-company { width: 14%; }
/* Email/City are ch (character-count) widths, not percentages, so they
   hold their floor -- at least 25 and 20 characters respectively -- no
   matter the viewport. table-layout: fixed only sizes a column from its
   own <col> width (min-width on the td/th itself is not honored for
   column sizing in fixed layout, unlike table-layout: auto), so the floor
   has to live here rather than as a min-width override below. The
   remaining columns stay percentage-based; if these fixed-width columns
   plus those percentages add up to more than the container's width, the
   table grows past .table-wrap's 100% and scrolls horizontally (already
   how this table handles overflow -- see the comment above) instead of
   any of them ever shrinking below their floor. Actions isn't touched, so
   its existing nowrap/flex-shrink:0 buttons keep their full 32% share
   regardless.

   Phone is a measured px width, not ch: ch is defined by the width of the
   "0" glyph specifically, but Inter's digits aren't tabular -- measured
   via canvas.measureText() against this cell's actual computed font
   (13.92px Inter), "4" is the widest digit at 129.18px for
   "+1 (444) 444-4444" (vs. "0" at 127.07px), not the ~2px gap that alone
   would suggest: across the other columns' rounding, plain 17ch already
   wrapped a number containing several wide digits (steadily 4/6/8/9 all
   sit within ~2px of each other and above "0"). 129.18px content + 12.8px
   padding each side = 154.78px minimum border-box width; 160px leaves a
   ~5px buffer. Re-measure and adjust if the font ever changes. */
#donors-tbl col.col-phone { width: 160px; }
#donors-tbl col.col-email { width: 25ch; }
#donors-tbl col.col-city { width: 20ch; }
#donors-tbl col.col-state { width: 6%; }
#donors-tbl col.col-actions { width: 32%; }
#donors-tbl td[data-col] {
  white-space: normal;
  overflow-wrap: break-word;
}

/* Donor picker modal (Section 10.9's donor-assignment page): same
   fixed-column/wrap treatment as the Donors grid above -- a long company
   name wraps instead of forcing the narrower modal to scroll horizontally. */
#donor-picker-search {
  margin-bottom: 0.6rem;
  max-width: none;
}
#donor-picker-tbl {
  table-layout: fixed;
}
#donor-picker-tbl col.col-name { width: 42%; }
#donor-picker-tbl col.col-company { width: 38%; }
#donor-picker-tbl col.col-actions { width: 20%; }
#donor-picker-tbl td[data-col] {
  white-space: normal;
  overflow-wrap: break-word;
}

/* Staging page (templates/reporting/picking-list.php): columns pinned to a
   tight width so the row -- Child #, Name, Sex, Age, Ethnicity, Color, and
   all 3 clothing size/category pairs -- stays compact, matching the same
   "fixed columns, don't let one field's content push the row wider than
   intended" approach as the Donors grid/donor picker above. Name/Pant/
   Shirt/Shoe are sized in ch (character count) rather than px, since their
   widths are specified in characters, not a measured pixel value -- unlike
   Age/Sex/Color, which don't need to grow or shrink in response to content. */
#staging-tbl {
  table-layout: fixed;
}
#staging-tbl col.col-child-number { width: 90px; }
#staging-tbl col.col-name { width: 7ch; }
#staging-tbl col.col-gender { width: 36px; }
#staging-tbl col.col-age { width: 34px; }
#staging-tbl col.col-ethnicity { width: 130px; }
#staging-tbl col.col-fav-color { width: 90px; }
#staging-tbl col.col-size-category { width: 13ch; }
#staging-tbl col.col-staging-action { width: 235px; }
#staging-tbl th, #staging-tbl td {
  white-space: nowrap;
  overflow: hidden;
}
#staging-tbl td.truncate {
  text-overflow: ellipsis;
}
/* Pant/Shirt/Shoe are explicitly allowed to wrap (unlike every other
   column here) -- a long "12 to 18 Months Infant/Toddler"-style value was
   otherwise being silently clipped rather than fully shown. */
#staging-tbl td.size-category {
  white-space: normal;
  overflow-wrap: break-word;
}

.actions-row {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}

/* Tag-qualified (not just .input-narrow) so this beats the generic
   input[type="date"]/select { width: 100% } rule above on specificity --
   a bare class selector would lose that fight and silently render at 100%. */
input.input-tiny, select.input-tiny      { width: 50px; }
input.input-narrow, select.input-narrow  { width: 80px; }
input.input-medium, select.input-medium  { width: 140px; }
input.input-wide, select.input-wide      { width: 190px; }
/* A measured px width, not ch -- ch is pinned to the "0" glyph's width,
   but Inter's digits aren't tabular, so a ch value doesn't actually
   guarantee every digit combination fits (confirmed by real wrapping:
   see #donors-tbl col.col-phone's longer comment above for the
   measurement method). Measured against this input's own computed font
   (16px Inter): "4" is the widest digit at 148.48px for
   "+1 (444) 444-4444". 148.48px content + 9.6px padding each side + 1px
   border each side = 169.68px minimum border-box width; 180px leaves a
   ~10px buffer. Used by the donor Add/Edit modal's Phone field. */
input.input-phone, select.input-phone    { width: 180px; }
/* Sized for the Siblings fieldset's Child # field -- real values run 4-5
   digits (e.g. "3011"); reduced from 120px so it and First name (which
   flexes to fill the rest -- see .sibling-cell__fields) both fit within
   the Siblings grid's now-halved column width without either being
   clipped or pushed under the Source PDF panel. */
input.input-childnum                     { width: 70px; }

/* Intake page's wish item fulfillment rows: only the detail fields matching
   the row's currently-selected Method show at once -- Tracking # for
   Shipped (tracked), Bags/Boxes/Shelf # for Facility drop-off. .hidden's
   !important wins over this when wish-fulfillment.js (or the
   server-rendered initial state) toggles it off. */
.wish-method-fields {
  display: inline-flex;
  gap: 0.6rem;
  align-items: center;
}

/* !important is intentional: a true utility class -- "this element is
   hidden" must win regardless of what other classes (e.g. .tbl-toolbar's
   own display: flex) happen to be on the same element or how CSS source
   order between them falls out. Caught by the donor bulk-action bar
   reusing .tbl-toolbar + .hidden together, where .tbl-toolbar's later
   source position was silently winning without this. */
.hidden { display: none !important; }
.inline { display: inline; }
.tbl-empty { padding: 1rem; text-align: center; }
.text-center { text-align: center; }

.mt-1 { margin-top: 1rem; }
.mb-1 { margin-bottom: 1rem; }
.ml-auto { margin-left: auto; }

/* ---------- Cards & panels ---------- */

.card {
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.25rem;
  margin-bottom: 1.25rem;
}

/* A plain-form card that shouldn't stretch full-width (e.g. change-password) -- same idea as .login-card's own max-width, for a card in normal page flow rather than the bare login layout. */
.card-narrow {
  max-width: 420px;
}

.card-title {
  margin: 0 0 0.85rem;
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-fg-muted);
  border-bottom: 2px solid var(--color-accent);
  padding-bottom: 0.45rem;
}

/* ---------- PDF-style child form (review/show.php) ---------- */
/* Mirrors the layout of the actual intake PDF (sample-forms/*.pdf) --
   bordered sections in the paper form's own order, and fields drawn as an
   underline with a small caption below rather than a boxed input, matching
   how the paper form presents "write on the line, label under it." The
   read-only Auditor equivalent (templates/review/audit.php) intentionally
   does NOT use these classes -- it keeps the older boxed-field look, since
   it's a data-vs-source comparison tool, not a form facsimile. */

.pdf-sheet {
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.1rem 1.75rem;
  /* Wide enough for Name/P-number/Gender/Age/checkboxes to share one row
     even after tightening Gender and Age to their real content widths --
     see .pdf-field--wide and .pdf-field--tiny below. */
  max-width: 960px;
}

/* No divider line between sections -- each section's own title underline
   (below) is enough separation, and skipping it lets sections sit closer
   together so more of the record shows without scrolling. */
.pdf-section {
  margin-bottom: 0.75rem;
}
.pdf-section:last-of-type {
  margin-bottom: 0;
}

/* Title text on the left, the parenthetical/context <small> (e.g. "(at the
   time of Christmas)") right after it on the same line, one space over,
   instead of wrapping to its own line below or being pushed to the far
   right -- one line per section title instead of two. */
.pdf-section-title {
  display: flex;
  align-items: baseline;
  justify-content: flex-start;
  gap: 0.4rem;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-fg);
  border-bottom: 2px solid var(--brand-teal-color);
  padding-bottom: 0.25rem;
  margin-bottom: 0.6rem;
}
.pdf-section-title small {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.72rem;
  text-transform: none;
  letter-spacing: normal;
  color: var(--color-fg-subtle);
  white-space: nowrap;
}

.pdf-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.65rem 1.5rem;
  align-items: end;
  margin-bottom: 0.6rem;
}
.pdf-row:last-child {
  margin-bottom: 0;
}

.pdf-field {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 110px;
  margin-bottom: 0.5rem;
}
.pdf-row > .pdf-field {
  margin-bottom: 0;
}
.pdf-field input,
.pdf-field select {
  border: none;
  border-bottom: 1px solid var(--color-fg);
  border-radius: 0;
  background: transparent;
  padding: 0.15rem 0.1rem;
  min-height: 28px;
}
.pdf-field input:focus,
.pdf-field select:focus {
  border-bottom-color: var(--color-accent);
  box-shadow: none;
  outline: none;
}
.pdf-field input:disabled {
  color: var(--color-fg-muted);
  border-bottom-style: dotted;
}
/* Holds one badge per selected value (e.g. Ethnicity, which can have
   several) -- wraps to as many lines as needed instead of a single-line
   input silently clipping anything past its width. */
.pdf-tag-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  min-height: 28px;
  padding: 0.15rem 0.1rem;
  border-bottom: 1px dotted var(--color-fg);
}
.pdf-tag-list__empty {
  color: var(--color-fg-muted);
  font-size: 0.9rem;
}
.pdf-field-label {
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-fg-subtle);
  margin: 0;
}
.pdf-field--grow {
  flex: 1;
  min-width: 180px;
}
/* Fixed width, not max-width -- .pdf-field's own min-width:110px would
   otherwise win the conflict and this would never actually shrink. Sized
   for a full 10-digit unmasked P-number, not the masked "**********" --
   digits render wider than asterisks in this font (a worst-case run of
   "0000000000" needs ~101px, vs ~80px for 10 asterisks), so sizing for the
   mask alone clipped the real value for roles with full access. */
.pdf-field--narrow {
  width: 118px;
  min-width: 0;
  flex: 0 0 auto;
}
/* The "AGE" label (uppercase, ~23px) is wider than any real age value
   ("17" is ~16px) -- this fits the label, the actual binding constraint. */
.pdf-field--tiny {
  max-width: 36px;
  min-width: 0;
}
/* Sized so "Toiletries: fragrance/dye-free?" (the longest .pdf-field-label
   on the read-only child page, ~207px on one line) never wraps, while still
   fitting alongside County of placement/Ethnicity/Favorite color in the
   same row -- see review/show.php. */
.pdf-field--medium {
  flex: 1;
  min-width: 220px;
}
/* Sized for the longest genders.label on file ("Male that identifies as
   female" / "Female that identifies as male", 30 chars, measures ~226px
   rendered) -- same read-only rationale as .pdf-field--medium above. */
.pdf-field--wide {
  flex: 1;
  min-width: 236px;
}

.pdf-checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
  align-items: center;
}
.pdf-checkbox-group--stacked {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

.pdf-siblings-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem 1.5rem;
}
@media (max-width: 820px) {
  .pdf-siblings-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
.pdf-sibling-pair {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.pdf-wish-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.55rem;
}
.pdf-wish-row:last-child {
  margin-bottom: 0;
}
.pdf-wish-number {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.15rem;
  color: var(--brand-teal-color);
  flex-shrink: 0;
}
.pdf-wish-row .pdf-field {
  flex: 1;
  margin-bottom: 0;
}
.pdf-wish-row .checkbox-field {
  flex-shrink: 0;
}

/* ---------- Tables (excel-style grid) ---------- */

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.87rem;
}

thead th {
  background: var(--color-bg-inset);
  position: sticky;
  top: 0;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-fg-muted);
  border-bottom: 2px solid var(--color-border);
}

th, td {
  text-align: left;
  padding: 0.55rem 0.8rem;
  border-bottom: 1px solid var(--color-border-muted);
  white-space: nowrap;
}

/* Intake page: the Item cell is the only thing left in its column now that
   Method/Details moved into the per-row form, so it gets to run larger
   than the table's base 0.87rem instead of competing for space. */
.wish-item-cell {
  font-size: 1.05rem;
}

/* Intake page: the Child #/Donor summary line, run larger than the
   default paragraph text since it's the page's key identifying info. */
.intake-summary-line {
  font-size: 1.15rem;
}

tbody tr:last-child td {
  border-bottom: none;
}

tbody tr:hover {
  background: var(--color-bg-inset);
}

/* Sortable column headers (excel-like grid, e.g. the Donors page) */
th.sortable {
  cursor: pointer;
  user-select: none;
}
th.sortable:hover {
  color: var(--color-fg);
}
th.sortable .sort-indicator {
  margin-left: 0.25rem;
  opacity: 0.4;
  font-size: 0.68rem;
}
th.sortable.sorted-asc .sort-indicator,
th.sortable.sorted-desc .sort-indicator {
  opacity: 1;
  color: var(--color-accent);
}

/* Toolbar above a grid: free-text search + primary action */
.tbl-toolbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
  flex-wrap: wrap;
}
.tbl-search {
  flex: 1;
  min-width: 200px;
  max-width: 340px;
}

/* Per-column filter row rendered as a second row inside <thead> */
.filter-row th {
  padding: 0.35rem 0.5rem;
  background: var(--color-bg-elevated);
  border-bottom: 2px solid var(--color-border);
  position: sticky;
  top: 34px;
}
.filter-row input {
  width: 100%;
  min-height: 0;
  padding: 0.3rem 0.5rem;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

/* ---------- Badges ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.badge-success { background: var(--color-success-bg); color: var(--color-success); }
.badge-warning { background: var(--color-warning-bg); color: var(--color-warning); }
.badge-danger  { background: var(--color-danger-bg);  color: var(--color-danger);  }
.badge-neutral { background: var(--color-bg-inset);   color: var(--color-fg-muted); }

/* Admin > Users role badges (Section 14's 5 roles): fixed colors, not
   theme-driven like the badges above -- the point is a color that reliably
   means "this role" no matter which of the 8 themes is active, the same
   way a legend wouldn't change color depending on who's looking at it. */
.badge-role-volunteer     { background: #dcfce7; color: #166534; }
.badge-role-manager       { background: #ccfbf1; color: #115e59; }
.badge-role-administrator { background: #fee2e2; color: #991b1b; }
.badge-role-auditor       { background: #fef9c3; color: #854d0e; }
.badge-role-case-worker   { background: #dbeafe; color: #1e40af; }

/* ---------- Tabs ---------- */

.tab-bar {
  display: flex;
  gap: 1px;
  border-bottom: 2px solid var(--color-border);
  margin-bottom: 1.1rem;
  flex-wrap: wrap;
}
.tab-btn {
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.55rem 1.1rem;
  border: none;
  background: none;
  color: var(--color-fg-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
}
.tab-btn:hover {
  color: var(--color-fg);
}
.tab-btn.on {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}
.tab-pane {
  display: none;
}
.tab-pane.on {
  display: block;
}

/* ---------- Modals ---------- */

.overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
}
.overlay.is-open {
  display: flex;
}
.modal {
  background: var(--color-bg-elevated);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 540px;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  /* Without this, max-height on a flex column doesn't reliably constrain a
     flex-basis:0% child (.modal-body) in every engine -- the child's
     content can still force the column past 92vh instead of triggering
     .modal-body's own overflow-y: auto. overflow: hidden here is what
     actually makes max-height bite, so .modal-body is what ends up
     scrolling internally rather than content spilling past the modal. */
  overflow: hidden;
}
.modal-lg {
  max-width: 760px;
}
/* Every form-based modal wraps .modal-body/.modal-ftr in a <form> (CSRF
   field, POST target) that sits between them and .modal -- an unstyled
   <form> is just a block box, so without this it renders at its full
   content height regardless of .modal's max-height, and .modal-body's own
   flex/overflow rules (further down) never get a constrained box to act
   on. Making the form itself a flex column (matching .modal) restores the
   chain: .modal caps the total height, the form fills whatever's left
   after .modal-hdr, and .modal-body -- the form's own flex:1 child --
   is what ends up scrolling. Read-only modals with no <form> (e.g. the
   Lists modal) already have .modal-body as a direct child of .modal and
   don't need this. */
.modal > form {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
.modal-hdr {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.9rem 1.25rem;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.modal-title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 700;
}
.modal-x {
  background: none;
  border: none;
  color: var(--color-fg-muted);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  padding: 1px 5px;
  border-radius: var(--radius-sm);
}
.modal-x:hover {
  background: var(--color-bg-inset);
  color: var(--color-fg);
}
.modal-body {
  padding: 1.25rem;
  overflow-y: auto;
  flex: 1;
  /* Flex items default to min-height: auto, which lets a tall .modal-body
     grow past its flex share instead of shrinking to it -- without this,
     overflow-y: auto never actually engages, and .modal (which has no
     overflow of its own) lets content spill out below its rounded box
     instead of scrolling internally within max-height: 92vh. Caught by the
     Donor Add/Edit modal's field count once it grew past what fit. */
  min-height: 0;
}
.modal-ftr {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  padding: 0.75rem 1.25rem;
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}

/* ---------- Alerts / flash ---------- */

.alert {
  padding: 0.85rem 1.1rem;
  border-radius: var(--radius-md);
  margin-bottom: 1rem;
  border: 1px solid transparent;
  font-size: 0.9rem;
}
.alert-error   { background: var(--color-danger-bg);  color: var(--color-danger);  border-color: var(--color-danger); }
.alert-success { background: var(--color-success-bg); color: var(--color-success); border-color: var(--color-success); }
.alert-warning { background: var(--color-warning-bg); color: var(--color-warning); border-color: var(--color-warning); }

/* ---------- Dashboard stat tiles ---------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.85rem;
  margin-bottom: 1.5rem;
}
.stat-tile {
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 0.7rem 1rem;
  text-align: center;
}
.stat-tile__value {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 800;
  line-height: 1.1;
}
/* Only present on tiles that show a count alongside its share of some
   total (e.g. "48% of Assemblies Completed") -- sits directly below the
   big value, under the tile's label at the top. */
.stat-tile__sublabel {
  color: var(--color-fg-muted);
  font-size: 0.78rem;
  font-weight: 600;
  margin-top: 0.15rem;
}
.stat-tile__label {
  color: var(--color-fg-muted);
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.3rem;
}

/* ---------- Upload dropzone ---------- */

.dropzone {
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  padding: 2.5rem 1.5rem;
  text-align: center;
  color: var(--color-fg-muted);
  background: var(--color-bg-elevated);
  cursor: pointer;
}
.dropzone.is-dragover {
  border-color: var(--color-accent);
  background: var(--color-accent-bg);
  color: var(--color-fg);
}

/* ---------- Diff view (duplicate resolution) ---------- */

.diff-row {
  display: grid;
  grid-template-columns: 180px 1fr 1fr;
  gap: 0.75rem;
  padding: 0.6rem 0.2rem;
  border-bottom: 1px solid var(--color-border-muted);
  align-items: start;
}
.diff-row--head {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--color-fg-muted);
  border-bottom: 2px solid var(--color-border);
}
.diff-row__field { font-weight: 600; font-size: 0.85rem; }
.diff-old { color: var(--color-danger); text-decoration: line-through; }
.diff-new { color: var(--color-success); font-weight: 600; }

/* ---------- Filter bar (server-side search forms) ---------- */

.filter-bar {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  align-items: end;
}
/* Tighter than a plain .card's default 1.25rem padding/margin -- a search
   form doesn't need as much breathing room as a content card, and every
   server-side-paginated listing puts one directly above its table, so the
   saved space directly means more rows visible without scrolling. */
.filter-bar.card {
  padding: 0.85rem 1.25rem;
  margin-bottom: 0.75rem;
}
.filter-bar .field {
  margin-bottom: 0;
  min-width: 160px;
}

/* Admin's Audit Log / Application Log filter bars: more fields than a
   160px-per-field floor can fit on one row, but every field here already
   carries an explicit .input-narrow/.input-medium width, so the floor is
   just extra empty space -- drop it instead of letting fields wrap. */
.filter-bar--compact .field {
  min-width: 0;
}
/* The one free-text field earns back the room the fixed-width fields gave up. */
.field-grow {
  flex: 1 1 200px;
}

/* Numbered page-link row under a search/listing table (Wish Lists/Audit/
   Intake, Admin's Audit Log tab). */
.pagination-nav {
  margin-top: 1rem;
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Wraps .pagination-nav, the "Showing X - Y of Z" count, and the
   records-per-page dropdown -- every server-side-paginated listing (Wish
   Lists/Audit/Intake/Donor Assignment/Admin Audit Log) uses this same
   layout, at both the top and bottom of the list. A 3-column grid (not
   flex + space-between) so the count sits genuinely centered between the
   nav and the dropdown regardless of how wide either one is -- the nav's
   width varies with page count, so a flex layout's "space between" would
   only even out the gaps, not actually center the middle item. Either
   outer column can be empty (a single page has no nav; the top bar's
   count may briefly be absent) without breaking alignment, since each
   template always renders exactly 3 children here, substituting an empty
   <div> for whichever slot has nothing to show. */
.pagination-bar {
  margin-top: 0.4rem;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.75rem;
}
.pagination-bar .pagination-nav {
  margin-top: 0;
}
.pagination-count {
  margin: 0;
  text-align: center;
  justify-self: center;
}
.page-size-field {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--color-fg-muted);
  justify-self: end;
}

/* Admin > Application Log: message text is free-form and can be long
   (stack traces, JSON context), so it wraps within its own cell instead of
   forcing the table wider than its container -- same reasoning as the
   donors grid's wrapping columns. */
.log-message {
  white-space: normal;
  overflow-wrap: break-word;
  font-family: var(--font-mono, ui-monospace, 'Courier New', monospace);
  font-size: 0.82rem;
}

/* Admin > Audit Log's Details modal: raw before/after JSON, preformatted. */
.audit-details-json {
  background: var(--color-bg-inset);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.8rem;
  margin: 0 0 0.75rem;
  font-family: var(--font-mono, ui-monospace, 'Courier New', monospace);
  font-size: 0.82rem;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow-y: auto;
}

/* Audit page: the page-heading (child name/#/status/action buttons) sticks
   below the site header as the page scrolls, and the Source PDF panel
   sticks below both of them -- top offsets come from --sticky-header-height/
   --sticky-audit-heading-height, which audit-sticky.js measures and sets on
   :root. A fixed px guess would be wrong depending on role (an
   Administrator's extra Admin nav link can wrap .app-header to a second
   line that Manager/Auditor, who can also reach this page, don't get) and
   on viewport width. The px fallback values here only matter before that
   script's first paint. */
.audit-sticky-heading {
  position: sticky;
  top: var(--sticky-header-height, 58px);
  z-index: 800;
  background: var(--color-bg);
  padding-top: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--color-border);
}

/* Establishes the query container that .pdf-frame's cqw units below
   measure against -- has to be the iframe's own direct parent, not
   .grid-2col itself, since the iframe is only ~1fr of that grid (roughly
   half its width, not all of it). */
.pdf-frame-wrap {
  container-type: inline-size;
  position: sticky;
  top: calc(var(--sticky-header-height, 58px) + var(--sticky-audit-heading-height, 60px));
}
/* Every sample form's MediaBox is [0 0 612 792] -- exactly US Letter
   (8.5in x 11in @ 72pt/in). A plain aspect-ratio (width * 11/8.5) undersizes
   the frame: Chrome's built-in PDF viewer renders its own toolbar (page
   nav/zoom/download, ~48px) INSIDE the iframe, on top of the page canvas --
   that toolbar eats into the height aspect-ratio assumed was all page
   content, so the bottom of the page still needed a scrollbar. Sizing via
   cqw (the iframe's own container width, not the viewport) plus a fixed
   allowance for that toolbar fixes both: the ratio term keeps the page
   itself uncropped at any column width, and the +48px reserves room for
   the toolbar on top of it instead of stealing from the page area. */
.pdf-frame {
  width: 100%;
  height: calc(129.41cqw + 48px);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

/* ---------- Login page ---------- */

.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
  padding: 1.5rem;
}
.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-top: 4px solid var(--brand-teal-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 2.1rem;
}
.login-logo {
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 800;
  text-align: center;
  letter-spacing: 0.03em;
  margin-bottom: 0.2rem;
  color: var(--color-fg);
  /* Same glow as .app-header__brand's "Spirit of Christmas", in light gray instead of white. */
  text-shadow: 0 0 6px #d3d3d3, 0 0 12px #d3d3d3;
}
.login-logo span {
  color: var(--color-accent);
}
.login-sub {
  text-align: center;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  margin-bottom: 1.75rem;
}
.login-hint {
  text-align: center;
  margin-top: 1rem;
  margin-bottom: 0;
}

@media (max-width: 720px) {
  .diff-row {
    grid-template-columns: 1fr;
  }
  .grid-2col {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 768px) {
  .nav-toggle-btn { display: flex; }
  .app-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 2px;
    background: var(--brand-teal-color);
    padding: 0.5rem;
    box-shadow: var(--shadow-md);
    z-index: 890;
  }
  .app-nav.is-open { display: flex; }
  .app-nav a { text-align: center; }
  main { padding: 1rem; }
}

@media print {
  .app-header, .app-nav, .app-footer, .no-print {
    display: none !important;
  }
}
