/* ============================================================================
   Schoo — component layer

   Loaded after tokens.css and after Bootstrap. Everything here reads from
   tokens; there is not a single hex value in this file, and there should never
   be one. If a colour is missing, add it to tokens.css.

   Scope is deliberately narrow: the shared furniture that appears on every
   screen of all three portals — surfaces, tables, buttons, forms, chips,
   sidebar. Per-module layout stays in the module's own stylesheet.
   ========================================================================= */

/* --- Foundations --------------------------------------------------------- */

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
  color: var(--text);
  font-weight: 600;
  letter-spacing: -0.011em;
  text-wrap: balance;
}

a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-hover); }

/* One focus treatment everywhere. Browsers disagree about default outlines and
   Bootstrap removes several of them; keyboard users should never have to guess
   where they are. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
}

/* Digits that sit in a column must line up, or a fee table reads as noise. */
.table td, .table th,
.text-end, .num, [class*="amount"], [class*="total"] {
  font-variant-numeric: tabular-nums;
}

/* --- Cards and panels ---------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}

/* style.css gave card headers a navy ground with white text. Setting the ground to
   white here without also setting the text colour left white on white, and every card
   title on the fee, cash-report and bad-debt screens vanished. Light header with dark
   text is the standard now — both properties, always together. */
/* :first-child to match the specificity of Bootstrap's own rule, which sets a 9px
   radius and leaves a sliver of the 16px card showing at each top corner. */
.card > .card-header:first-child { border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0; }

.card-header {
  background: var(--surface);
  color: var(--text);
  border-bottom: 1px solid var(--border);

  padding: var(--sp-4) var(--sp-5);
  font-weight: 600;
  font-size: 1.05rem;
  border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
}
/* Plain links only. A bare `.card-header a` also matches every button rendered as an
   anchor, and at (0,1,1) it outranks the .btn-* rules — which is how an outline
   warning button in a card header ended up accent green on an amber band. */
.card-header a:not(.btn) { color: var(--accent); }

/* The dark page header the app uses above most tables. */
.card > .card-header.bg-primary,
.card > .card-header[class*="bg-dark"] {
  background: var(--surface-inverse) !important;
  color: var(--text-inverse);
  border-bottom: 0;
  border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
}
.card > .card-header.bg-primary h1,
.card > .card-header.bg-primary h2,
.card > .card-header.bg-primary h3,
.card > .card-header.bg-primary h4,
.card > .card-header.bg-primary h5 { color: var(--text-inverse); margin: 0; }

.card-body { padding: var(--sp-5); }

/* --- Tables -------------------------------------------------------------- */

.table {
  --bs-table-bg: transparent;
  color: var(--text);
  border-color: var(--border);
  margin-bottom: 0;
}

.table > thead > tr > th {
  background: var(--surface-sunken);
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--border-strong);
  padding: var(--sp-3) var(--sp-4);
  white-space: nowrap;
}

.table > tbody > tr > td {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.table > tbody > tr:hover > td { background: var(--accent-subtle); }
.table > tbody > tr:last-child > td { border-bottom: 0; }

/* A wide table scrolls inside its own container; the page never scrolls sideways. */
.table-responsive { overflow-x: auto; border-radius: var(--r-md); }

/* --- Buttons ------------------------------------------------------------- */

.btn {
  --bs-btn-border-radius: var(--r-md);
  font-weight: 500;
  padding: 0.5rem 1rem;
  transition: background-color var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast);
}

.btn-primary {
  --bs-btn-bg: var(--accent);
  --bs-btn-border-color: var(--accent);
  --bs-btn-hover-bg: var(--accent-hover);
  --bs-btn-hover-border-color: var(--accent-hover);
  --bs-btn-active-bg: var(--accent-hover);
  --bs-btn-color: var(--text-on-brand);
  --bs-btn-hover-color: var(--text-on-brand);
  box-shadow: var(--shadow-xs);
}

.btn-outline-primary {
  --bs-btn-color: var(--accent);
  --bs-btn-border-color: var(--accent-border);
  --bs-btn-hover-bg: var(--accent-subtle);
  --bs-btn-hover-border-color: var(--accent);
  --bs-btn-hover-color: var(--accent-hover);
}

.btn-secondary {
  --bs-btn-bg: var(--surface);
  --bs-btn-border-color: var(--border-strong);
  --bs-btn-color: var(--text);
  --bs-btn-hover-bg: var(--surface-sunken);
  --bs-btn-hover-border-color: var(--border-strong);
  --bs-btn-hover-color: var(--text);
}

/* Success takes a lighter stop than primary rather than --ok itself.
   --ok aliases the brand green, which is right for a status chip but not for a button:
   the admin puts a .btn-primary and a .btn-success side by side (Filter next to Excel)
   and in one identical green they read as one wide control. */
.btn-success {
  /* background-color as well as the Bootstrap variable: style.css also styles this
     button and sets background-color outright, so overriding only --bs-btn-bg here
     changes nothing. ui.css loads last, so this is the one that wins. */
  --bs-btn-bg: var(--brand-500);
  --bs-btn-border-color: var(--brand-500);
  background-color: var(--brand-500);
  border-color: var(--brand-500);
}
.btn-success:hover { background-color: var(--brand-600); border-color: var(--brand-600); }
.btn-danger  { --bs-btn-bg: var(--danger); --bs-btn-border-color: var(--danger); }
.btn-warning { --bs-btn-bg: var(--warn);   --bs-btn-border-color: var(--warn); --bs-btn-color: var(--text-inverse); }

/* The disabled state has to be bridged separately.
   Bootstrap gives every button variant its own --bs-btn-disabled-bg, compiled to that
   variant's own colour, and `.btn:disabled` is a more specific selector than
   `.btn-primary` — so a disabled button ignores every override above it and paints in
   Bootstrap's stock palette. That went unnoticed while the brand was also a blue; the
   moment it became green, every disabled button in the admin was still Bootstrap blue.

   Disabled means "not available", so all variants share one flat, quiet treatment
   rather than a faded version of each colour. */
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
  --bs-btn-disabled-bg: var(--surface-sunken);
  --bs-btn-disabled-border-color: var(--border);
  --bs-btn-disabled-color: var(--text-muted);
  --bs-btn-disabled-opacity: 1;
  box-shadow: none;
}
/* The other outline variants. Without these an outline button keeps whatever colour it
   inherits — the dashboard's "Manage Overlaps" sat on a warning band in the accent green
   at 1.9:1. Each takes its own -text stop so it is legible on a tinted band as well as
   on paper. */
.btn-outline-warning {
  --bs-btn-color: var(--late-text);        --bs-btn-border-color: var(--late-border);
  --bs-btn-hover-bg: var(--late-tint);     --bs-btn-hover-color: var(--late-text);
  --bs-btn-hover-border-color: var(--late);
  color: var(--late-text); border-color: var(--late-border);
}
.btn-outline-danger {
  --bs-btn-color: var(--absent-text);      --bs-btn-border-color: var(--absent-border);
  --bs-btn-hover-bg: var(--absent-tint);   --bs-btn-hover-color: var(--absent-text);
  --bs-btn-hover-border-color: var(--absent);
  color: var(--absent-text); border-color: var(--absent-border);
}
.btn-outline-success {
  --bs-btn-color: var(--present-text);     --bs-btn-border-color: var(--present-border);
  --bs-btn-hover-bg: var(--present-tint);  --bs-btn-hover-color: var(--present-text);
  --bs-btn-hover-border-color: var(--present);
  color: var(--present-text); border-color: var(--present-border);
}
.btn-outline-secondary {
  --bs-btn-color: var(--text-muted);       --bs-btn-border-color: var(--border-strong);
  --bs-btn-hover-bg: var(--surface-sunken);--bs-btn-hover-color: var(--text);
  color: var(--text-muted); border-color: var(--border-strong);
}

.btn-outline-primary:disabled, .btn-outline-primary.disabled {
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--border);
}

/* --- Forms --------------------------------------------------------------- */

.form-control, .form-select {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  color: var(--text);
  padding: 0.5rem 0.75rem;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}

.form-control:focus, .form-select:focus {
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}

.form-control::placeholder { color: var(--text-subtle); }
.form-label { color: var(--text-muted); font-size: 0.875rem; font-weight: 500; margin-bottom: var(--sp-1); }

/* Every filter row in the app: fields and their buttons on one baseline. The
   height mismatch this fixes came from the inputs carrying a bottom margin the
   buttons did not. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-3);
}
.filter-bar .filter-field { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 180px; flex: 1 1 180px; }
.filter-bar .filter-field > .form-control,
.filter-bar .filter-field > .form-select { margin-bottom: 0; }
.filter-bar .filter-actions { display: flex; gap: var(--sp-2); align-items: flex-end; }

/* One height for everything standing on a filter row. Fields and buttons compute their
   own height from their own padding and never match, so both are floored to the same
   token. Scoped to .filter-bar so ordinary forms, small controls and textareas keep the
   sizing they already have. */
.filter-bar .filter-field > .form-control,
.filter-bar .filter-field > .form-select,
.filter-bar .filter-actions .btn {
  min-height: var(--control-h);
}
.filter-bar .filter-actions .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* --- Status chips and alerts --------------------------------------------- */

.badge {
  border-radius: var(--r-full);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  padding: 0.3em 0.7em;
}

/* Text on a tint takes the darker *-text stop, not the fill colour. --late and --danger
   are picked to read as a solid bar; set as words on their own pale tint they fall to
   3.2:1. The -text stops exist for exactly this pairing. */
.badge.bg-success, .badge.text-bg-success { background: var(--present-tint) !important; color: var(--present-text) !important; }
.badge.bg-warning, .badge.text-bg-warning { background: var(--late-tint) !important;    color: var(--late-text) !important; }
.badge.bg-danger,  .badge.text-bg-danger  { background: var(--absent-tint) !important;  color: var(--absent-text) !important; }
.badge.bg-info,    .badge.text-bg-info    { background: var(--info-surface) !important; color: var(--info) !important; }

/* Same rule for inline status words and alert bodies. */
.text-warning { color: var(--late-text) !important; }
.text-danger  { color: var(--absent-text) !important; }
.text-success { color: var(--present-text) !important; }
.alert-warning { background: var(--late-tint); border-color: var(--late-border); color: var(--late-text); }
.alert-danger  { background: var(--absent-tint); border-color: var(--absent-border); color: var(--absent-text); }
.alert-success { background: var(--present-tint); border-color: var(--present-border); color: var(--present-text); }
/* A link inside a tinted band cannot keep the accent — green on amber is 1.9:1. */
/* An inline link inside a coloured alert should take the alert's tone. A button that
   happens to be an anchor should not: it carries its own colour, and inheriting the
   alert's left it painting its label in exactly its own background — a solid block with
   no readable text. Buttons are excluded rather than the rule narrowed, so ordinary
   links keep behaving as they did. */
.alert-warning a:not(.btn),
.alert-danger  a:not(.btn),
.alert-success a:not(.btn) { color: inherit; text-decoration: underline; }
.badge.bg-primary, .badge.text-bg-primary { background: var(--accent-subtle) !important;  color: var(--accent) !important; }
.badge.bg-secondary                       { background: var(--surface-sunken) !important; color: var(--text-muted) !important; }

.alert { border-radius: var(--r-md); border-width: 1px; padding: var(--sp-4); }
.alert-success { background: var(--ok-surface);     border-color: var(--ok-border);     color: var(--ok); }
.alert-warning { background: var(--warn-surface);   border-color: var(--warn-border);   color: var(--warn); }
.alert-danger  { background: var(--danger-surface); border-color: var(--danger-border); color: var(--danger); }
.alert-info    { background: var(--info-surface);   border-color: var(--info-border);   color: var(--info); }

/* --- Sidebar ------------------------------------------------------------- */

.sidebar, #sidebar, .admin-sidebar {
  background: var(--sidebar-bg);
  color: var(--sidebar-text);
}

.sidebar a, #sidebar a, .sidebar .nav-link {
  color: var(--sidebar-text);
  border-radius: var(--r-sm);
  transition: background-color var(--t-fast), color var(--t-fast);
}

.sidebar a:hover, #sidebar a:hover, .sidebar .nav-link:hover {
  background: var(--sidebar-active-bg);
  color: var(--sidebar-text-hover);
}

.sidebar .nav-link.active, .sidebar a.active {
  background: var(--sidebar-active-bg);
  color: var(--sidebar-text-hover);
  box-shadow: inset 3px 0 0 var(--sidebar-active-bar);
}

.sidebar .nav-section, .nav-section {
  color: var(--sidebar-section);
  font-size: 0.68rem;
  letter-spacing: 0.09em;
  font-weight: 600;
}

/* --- Brand lockup -------------------------------------------------------- */

.brand-lockup {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: -0.02em;
  text-decoration: none;
}
.brand-lockup img, .brand-lockup svg { width: 32px; height: 32px; border-radius: var(--r-sm); flex: none; }
.brand-lockup.on-dark { color: var(--text-inverse); }
.brand-lockup.on-light { color: var(--text); }

/* --- Modals, dropdowns, pagination --------------------------------------- */

.modal-content { border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-lg); }
.modal-header, .modal-footer { border-color: var(--border); }

.dropdown-menu {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  padding: var(--sp-1);
}
.dropdown-item { border-radius: var(--r-sm); padding: 0.5rem 0.75rem; }
.dropdown-item:hover { background: var(--accent-subtle); color: var(--accent); }

.page-link { color: var(--accent); border-color: var(--border); }
.page-item.active .page-link { background: var(--accent); border-color: var(--accent); }

/* --- Motion -------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* --- Student avatars ------------------------------------------------------
   The same disc the two portal apps use: a photograph where one exists, initials
   otherwise. Photographs are served by StudentPhotoController, which checks who is
   asking; the file is never reachable by URL alone. */
.disc {
  width: 34px; height: 34px; border-radius: var(--r-full); flex: none;
  background: var(--canvas); color: var(--text-muted);
  display: inline-grid; place-items: center; overflow: hidden;
  font-size: var(--t-sm); font-weight: 600; line-height: 1;
}
.disc img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar-cell { width: 46px; padding-right: 0 !important; }

/* --- Coloured cards ------------------------------------------------------ */

/*
 * A .card carrying a Bootstrap background utility was rendering as a blank box: the
 * colour lands on the card, but .card-body paints itself white while .text-white keeps
 * the text white, so the figure was being drawn in white on white. Letting the body show
 * the card's own colour through is the whole fix; the text colour was never the problem.
 */
.card.bg-primary   > .card-body,
.card.bg-success   > .card-body,
.card.bg-danger    > .card-body,
.card.bg-info      > .card-body,
.card.bg-warning   > .card-body,
.card.bg-secondary > .card-body {
  background-color: transparent;
}

/* --- Filter cards used as a status switcher ------------------------------ */

/*
 * When count cards are the filter, the card being viewed has to look like it, or the
 * list below reads as though it ignored the click. Tint it in its own accent and thicken
 * its rail, so the state carries without relying on colour alone.
 */
.admin-stat-card.is-active {
  background: var(--card-accent-bg);
  border-color: var(--card-accent);
  border-left-width: 6px;
  box-shadow: inset 0 0 0 1px var(--card-accent);
}
.admin-stat-card.is-active .stat-label { font-weight: 600; }
