:root {
  /* AFRICOM Technologies brand colors, sampled directly from the company logo. */
  --sd-primary: #003ea3;
  --sd-primary-dark: #002c75;
  --sd-primary-light: #eaeff7;
  --sd-accent: #00ad4c;
  --sd-success: #2f9e6e;
  --sd-warning: #d9a72b;
  --sd-danger: #c94c4c;
  --sd-sidebar-bg: #172a3a;
  --sd-sidebar-hover: #22405a;
  --sd-sidebar-active: var(--sd-primary);
  --sd-body-bg: #f4f6f9;

  --sd-heading: #1c2b3a;
  --sd-body-text: #33475a;
  --sd-muted: #667085;
  --sd-border: #e6eaf0;
  --sd-border-strong: #d7dde5;

  --sd-card-radius: 0.75rem;
  --sd-radius-sm: 0.5rem;
  --sd-radius-lg: 1rem;
  --sd-sidebar-width: 250px;

  --sd-shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.04);
  --sd-shadow-sm: 0 2px 6px rgba(16, 24, 40, 0.06);
  --sd-shadow-md: 0 8px 24px rgba(16, 24, 40, 0.1);
  --sd-shadow-lg: 0 16px 40px rgba(16, 24, 40, 0.16);

  --sd-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --sd-transition-fast: 0.15s var(--sd-ease);
  --sd-transition: 0.2s var(--sd-ease);

  /* Display/body face: IBM Plex Sans -- calm, institutional, deliberately not the
     Inter/Plus-Jakarta-Sans look every generic SaaS dashboard reaches for.
     Data/utility face: IBM Plex Mono -- for anything that's a value to be scanned
     precisely (IDs, timestamps, confidence %) rather than prose to be read. */
  --sd-font-sans: "IBM Plex Sans", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, sans-serif;
  --sd-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;

  /* Confidence-ring signature scale -- shared by the OCR review screen, the
     student-forms list, and the dashboard pipeline aggregate. */
  --sd-confidence-none: #c7d3e0;
  --sd-confidence-low: var(--sd-danger);
  --sd-confidence-mid: var(--sd-warning);
  --sd-confidence-high: var(--sd-accent);

  --bs-primary: var(--sd-primary);
  --bs-primary-rgb: 0, 62, 163;
  --bs-body-color: var(--sd-body-text);
  --bs-link-color: var(--sd-primary);
  --bs-link-hover-color: var(--sd-primary-dark);
  --bs-border-radius: var(--sd-radius-sm);
  --bs-border-radius-sm: 0.4rem;
  --bs-border-radius-lg: var(--sd-card-radius);
  --bs-focus-ring-color: rgba(0, 62, 163, 0.25);

  /* Bootstrap's bg-primary-subtle / text-primary-emphasis / border-primary-subtle utilities
     are precompiled to Bootstrap's stock blue and don't derive from --bs-primary at runtime,
     so they need their own override to actually match the brand blue. */
  --bs-primary-bg-subtle: var(--sd-primary-light);
  --bs-primary-border-subtle: #99b2da;
  --bs-primary-text-emphasis: var(--sd-primary-dark);
}

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

/* ---------- Base / typography ---------- */
html {
  scroll-behavior: smooth;
}

body {
  background-color: var(--sd-body-bg);
  color: var(--sd-body-text);
  font-family: var(--sd-font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 0.9375rem;
  line-height: 1.55;
}

/* Utility face for values meant to be scanned precisely: IDs, timestamps,
   confidence percentages, counts. Tabular figures keep columns of numbers
   aligned instead of jittering as digits change width. */
.font-data {
  font-family: var(--sd-font-mono);
  font-variant-numeric: tabular-nums;
}

h1, h2, h3, h4, h5, h6 {
  color: var(--sd-heading);
  font-weight: 700;
  letter-spacing: -0.01em;
}

h3 { font-size: 1.4rem; }
h4 { font-size: 1.2rem; }
h5 { font-size: 1.05rem; }
h6 { font-size: 0.9rem; }

.text-muted {
  color: var(--sd-muted) !important;
}

a {
  transition: color var(--sd-transition-fast);
}

::selection {
  background: rgba(0, 62, 163, 0.2);
}

/* Consistent, tasteful keyboard-focus ring on interactive elements */
a:focus-visible,
button:focus-visible,
.btn:focus-visible,
.nav-link:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible {
  outline: 2px solid var(--sd-primary);
  outline-offset: 2px;
}

/* Subtle one-time entrance for freshly-rendered page content.
   Opacity-only and no fill-mode: a lingering non-"none" transform (e.g. from
   fill-mode "both") on an .app-content ancestor would create a new containing
   block for any position:fixed descendant -- including Bootstrap modals nested
   anywhere inside the page -- breaking their full-viewport positioning. */
@media (prefers-reduced-motion: no-preference) {
  .app-content > * {
    animation: sd-fade-in 0.25s var(--sd-ease);
  }
  @keyframes sd-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

/* ---------- App shell ---------- */
.app-shell {
  display: flex;
  min-height: 100vh;
}

.app-sidebar {
  width: var(--sd-sidebar-width);
  flex-shrink: 0;
  background-color: var(--sd-sidebar-bg);
  color: #cdd8e3;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  overflow-y: auto;
  z-index: 1030;
  transition: transform var(--sd-transition) ease-in-out;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}

.app-sidebar::-webkit-scrollbar {
  width: 6px;
}

.app-sidebar::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.18);
  border-radius: 3px;
}

.app-sidebar .brand {
  padding: 1.25rem 1.25rem 1rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.app-sidebar .brand-logo-badge {
  background: #fff;
  border-radius: 0.5rem;
  padding: 0.35rem 0.6rem;
  display: inline-flex;
  align-items: center;
  box-shadow: var(--sd-shadow-sm);
}

.app-sidebar .brand-logo-img {
  height: 26px;
  width: auto;
  display: block;
}

.app-sidebar .brand-product-name {
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  color: #fff;
}

.app-sidebar .nav-section-title {
  text-transform: uppercase;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  color: #7c93a8;
  padding: 1.1rem 1.5rem 0.4rem;
}

.app-sidebar .nav {
  padding: 0 0.75rem;
}

.app-sidebar .nav-section-title {
  padding-left: 0.75rem;
  padding-right: 0.75rem;
}

.app-sidebar .nav-link {
  color: #cdd8e3;
  padding: 0.55rem 0.85rem;
  margin: 0.1rem 0;
  border-radius: var(--sd-radius-sm);
  display: flex;
  align-items: center;
  gap: 0.65rem;
  font-size: 0.92rem;
  transition: background-color var(--sd-transition-fast), color var(--sd-transition-fast), transform var(--sd-transition-fast);
}

.app-sidebar .nav-link:hover {
  background-color: var(--sd-sidebar-hover);
  color: #fff;
  transform: translateX(2px);
}

.app-sidebar .nav-link.active {
  background-color: var(--sd-sidebar-active);
  color: #fff;
  font-weight: 600;
  box-shadow: var(--sd-shadow-sm);
}

.app-sidebar .nav-link.active:hover {
  transform: none;
}

.app-sidebar .nav-link i {
  font-size: 1.05rem;
  width: 1.25rem;
  text-align: center;
  flex-shrink: 0;
}

.app-main {
  margin-left: var(--sd-sidebar-width);
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.app-topbar {
  background-color: #fff;
  border-bottom: 1px solid var(--sd-border);
  padding: 0.65rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 1020;
  gap: 0.75rem;
}

.app-topbar .user-chip {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-width: 0;
}

.app-topbar .user-avatar {
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: var(--sd-primary-light);
  color: var(--sd-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.05rem;
  flex-shrink: 0;
}

.app-topbar .user-meta {
  min-width: 0;
  line-height: 1.25;
}

.app-topbar .user-email {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 200px;
}

.app-content {
  padding: 1.5rem;
  flex-grow: 1;
}

.app-footer {
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--sd-border);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  color: var(--sd-muted);
  font-size: 0.8rem;
}

.app-footer-logo {
  height: 16px;
  width: auto;
  opacity: 0.85;
}

/* Quiet on purpose: the build number matters when you're checking a deployment
   and should be invisible the rest of the time. */
.app-footer-sep {
  color: var(--sd-border-strong);
}

.app-footer-version {
  font-size: 0.75rem;
  color: var(--sd-muted);
}

.sidebar-toggle-btn {
  display: none;
}

.sidebar-backdrop {
  display: none;
}

@media (max-width: 991.98px) {
  .app-sidebar {
    transform: translateX(-100%);
    box-shadow: var(--sd-shadow-lg);
  }
  .app-sidebar.show {
    transform: translateX(0);
  }
  .app-main {
    margin-left: 0;
  }
  .sidebar-toggle-btn {
    display: inline-flex;
  }
  .sidebar-backdrop.show {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 32, 0.45);
    z-index: 1025;
    animation: sd-fade-in-backdrop 0.15s ease-in-out both;
  }
  @keyframes sd-fade-in-backdrop {
    from { opacity: 0; }
    to { opacity: 1; }
  }
  .app-topbar .user-email {
    max-width: 120px;
  }
}

@media (max-width: 575.98px) {
  .app-content {
    padding: 1rem;
  }
  .app-topbar {
    padding: 0.6rem 1rem;
  }
  .app-topbar .role-badges {
    display: none;
  }
}

/* ---------- Buttons ---------- */
.btn {
  border-radius: var(--sd-radius-sm);
  font-weight: 500;
  touch-action: manipulation;
  transition: color var(--sd-transition-fast), background-color var(--sd-transition-fast),
    border-color var(--sd-transition-fast), box-shadow var(--sd-transition-fast),
    transform var(--sd-transition-fast);
}

.btn:hover {
  transform: translateY(-1px);
}

.btn:active {
  transform: translateY(0) scale(0.98);
}

.btn:disabled,
.btn.disabled {
  transform: none;
  opacity: 0.5;
}

.btn-primary,
.btn-success,
.btn-danger {
  box-shadow: var(--sd-shadow-xs);
}

.btn-primary:hover,
.btn-success:hover,
.btn-danger:hover {
  box-shadow: var(--sd-shadow-sm);
}

.btn-sm {
  border-radius: 0.4rem;
}

/* Bootstrap's .btn-sm (~31px tall) reads fine visually on desktop but is
   under the 44px touch-target minimum. Keep it compact for mouse use and
   only grow the tap area on touch/coarse-pointer devices, so dense admin
   tables don't get visually bloated on desktop. */
@media (hover: none), (pointer: coarse) {
  .btn-sm {
    min-height: 44px;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    display: inline-flex;
    align-items: center;
  }
}

/* Loading state, toggled by site.js on form submit */
.btn.is-loading {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}

.btn.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1rem;
  height: 1rem;
  margin: -0.5rem 0 0 -0.5rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  color: inherit;
  opacity: 0.85;
  animation: sd-spin 0.6s linear infinite;
}

.btn-primary.is-loading::after,
.btn-success.is-loading::after,
.btn-danger.is-loading::after {
  color: #fff;
}

.btn-outline-primary.is-loading::after,
.btn-outline-secondary.is-loading::after,
.btn-outline-success.is-loading::after,
.btn-outline-danger.is-loading::after {
  color: var(--sd-primary);
}

@keyframes sd-spin {
  to { transform: rotate(360deg); }
}

/* ---------- Forms ---------- */
.form-label {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--sd-heading);
  margin-bottom: 0.35rem;
}

/* Add class="form-label required" to mark a field mandatory */
.form-label.required::after {
  content: " *";
  color: var(--sd-danger);
  font-weight: 700;
}

.form-control,
.form-select {
  border-color: var(--sd-border-strong);
  transition: border-color var(--sd-transition-fast), box-shadow var(--sd-transition-fast);
}

.form-control:hover:not(:disabled):not(:focus),
.form-select:hover:not(:disabled):not(:focus) {
  border-color: #b9c3d0;
}

.form-control:focus,
.form-select:focus {
  border-color: var(--sd-primary);
  box-shadow: 0 0 0 0.2rem rgba(0, 62, 163, 0.15);
}

/* Disabled = not applicable right now (muted, blocked cursor). Readonly =
   still valid data the user just can't edit here (kept legible, not grayed
   out) -- these are semantically different states and should look different. */
.form-control:disabled,
.form-select:disabled {
  background-color: #eef0f2;
  color: var(--sd-muted);
  cursor: not-allowed;
}

.form-control[readonly] {
  background-color: #fbfcfd;
  color: var(--sd-body-text);
  cursor: default;
}

.form-control::placeholder {
  color: #a3adba;
}

.form-text {
  font-size: 0.8rem;
}

@media (hover: none), (pointer: coarse) {
  .form-control,
  .form-select {
    min-height: 44px;
  }
}

/* Password show/hide toggle */
.password-toggle-wrap {
  position: relative;
}

.password-toggle-wrap .form-control {
  padding-right: 2.75rem;
}

.password-toggle-btn {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--sd-muted);
  cursor: pointer;
  transition: color var(--sd-transition-fast);
}

.password-toggle-btn:hover {
  color: var(--sd-primary);
}

.password-toggle-btn:focus-visible {
  outline: 2px solid var(--sd-primary);
  outline-offset: -2px;
  border-radius: var(--sd-radius-sm);
}

.form-check-input {
  transition: background-color var(--sd-transition-fast), border-color var(--sd-transition-fast), box-shadow var(--sd-transition-fast);
}

.form-check-input:focus {
  box-shadow: 0 0 0 0.2rem rgba(0, 62, 163, 0.15);
}

.form-check-label,
label.form-check-label {
  cursor: pointer;
}

.invalid-feedback,
.text-danger.small {
  font-size: 0.8rem;
}

/* ---------- Step indicator (registration: record details -> documents) ---------- */
.step-indicator {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.step-indicator-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--sd-muted);
  font-size: 0.85rem;
  font-weight: 500;
}

.step-indicator-number {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--sd-border);
  color: var(--sd-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
  flex-shrink: 0;
}

.step-indicator-item.is-done .step-indicator-number {
  background: var(--sd-accent);
  color: #fff;
}

.step-indicator-item.is-current .step-indicator-number {
  background: var(--sd-primary);
  color: #fff;
}

.step-indicator-item.is-current {
  color: var(--sd-heading);
}

.step-indicator-connector {
  width: 2rem;
  height: 1px;
  background: var(--sd-border);
  flex-shrink: 0;
}

/* ---------- Sectioned record forms (Student/Employee Create & Edit) ---------- */
.record-form {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 1.5rem;
  align-items: start;
}

@media (max-width: 991.98px) {
  .record-form {
    grid-template-columns: 1fr;
  }
}

.record-form-nav {
  position: sticky;
  top: calc(1.5rem + 61px); /* app-topbar height + its own top offset */
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.record-form-nav-link {
  padding: 0.45rem 0.75rem;
  border-radius: var(--sd-radius-sm);
  color: var(--sd-muted);
  font-size: 0.85rem;
  font-weight: 500;
  text-decoration: none;
  border-left: 2px solid transparent;
  transition: background-color var(--sd-transition-fast), color var(--sd-transition-fast), border-color var(--sd-transition-fast);
}

.record-form-nav-link:hover {
  background-color: var(--sd-primary-light);
  color: var(--sd-primary);
}

.record-form-nav-link.active {
  color: var(--sd-primary);
  border-left-color: var(--sd-primary);
  font-weight: 600;
  background-color: var(--sd-primary-light);
}

.record-form-sections {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  min-width: 0;
}

.form-section-card {
  background: var(--sd-surface, #fff);
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-card-radius);
  padding: 1.5rem;
  scroll-margin-top: calc(1.5rem + 61px);
}

.form-section-title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--sd-heading);
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--sd-border);
}

/* Repeating rows: guardians, siblings, previous schools, pick-up authorization.
   Each row is a bordered block rather than bare inputs, because these sections stack several
   rows of the same fields and without the boundary it is genuinely hard to see where one
   guardian's details end and the next one's begin. */
.repeating-rows {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.repeating-row {
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-card-radius);
  padding: 0.85rem;
  background: var(--sd-surface-muted, #fbfcfd);
}

/* The label above each input in a repeating row is a reminder, not a heading -- the fields
   repeat, so full-size labels on every row turn the section into a wall of text. */
.repeating-row .form-label {
  font-size: 0.78rem;
  margin-bottom: 0.2rem;
  color: var(--sd-muted);
}

.repeating-row .btn-outline-danger {
  /* Lines the delete button up with the inputs beside it rather than their labels. */
  margin-bottom: 0.1rem;
}

@media (max-width: 767.98px) {
  /* Stacked one per line on a phone, the delete button reads as belonging to the field above
     it unless it is pushed to the full width and clearly separated. */
  .repeating-row .btn-outline-danger {
    width: 100%;
    margin-top: 0.5rem;
  }
}

/* A field that differs from its value when the Edit page loaded. Left border +
   tint rather than a loud color, since a form full of these should still read
   as calm -- it's information, not an alarm. */
.form-field {
  position: relative;
  transition: padding-left var(--sd-transition);
}

.form-field--changed {
  padding-left: 0.6rem;
  border-left: 2px solid var(--sd-warning);
}

.form-field-changed-badge {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--sd-warning);
  background: rgba(217, 167, 43, 0.14);
  border-radius: 50rem;
  padding: 0.1rem 0.45rem;
  margin-left: 0.4rem;
  vertical-align: middle;
}

.record-form-actions {
  position: sticky;
  bottom: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: var(--sd-surface, #fff);
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-card-radius);
  padding: 0.85rem 1.25rem;
  margin-top: 1.25rem;
  box-shadow: 0 -4px 12px rgba(16, 24, 40, 0.05);
}

.record-form-status {
  font-size: 0.8rem;
  color: var(--sd-muted);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.record-form-status.is-dirty {
  color: var(--sd-warning);
}

.record-form-last-updated {
  background: var(--sd-primary-light);
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-radius-sm);
  padding: 0.6rem 0.9rem;
  font-size: 0.8rem;
  color: var(--sd-body-text);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.record-form-last-updated i {
  color: var(--sd-primary);
}

/* ---------- Cards & summary tiles ---------- */
.card {
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-card-radius);
  box-shadow: var(--sd-shadow-xs);
  transition: box-shadow var(--sd-transition), transform var(--sd-transition), border-color var(--sd-transition);
}

.card-header {
  background-color: #fafbfc;
  border-bottom: 1px solid var(--sd-border);
  font-weight: 600;
  color: var(--sd-heading);
}

.card-title {
  font-weight: 700;
  color: var(--sd-heading);
}

/* Opt-in class for cards that act like interactive controls (radio-card pickers, etc.) */
.card-hoverable {
  cursor: pointer;
}

.card-hoverable:hover {
  box-shadow: var(--sd-shadow-md);
  transform: translateY(-2px);
  border-color: #c7d3e0;
}

.card-hoverable:active {
  transform: translateY(-1px) scale(0.99);
}

/* ---------- Dashboard ---------- */
.dash-section-title {
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--sd-muted);
}

.dash-provider-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.85rem;
  background: #fff;
  border: 1px solid var(--sd-border);
  border-radius: 50rem;
  font-size: 0.85rem;
  color: var(--sd-body-text);
  transition: border-color var(--sd-transition-fast), box-shadow var(--sd-transition-fast);
}

.dash-provider-chip:hover {
  border-color: var(--sd-border-strong);
  box-shadow: var(--sd-shadow-xs);
  color: var(--sd-body-text);
}

.dash-provider-chip i {
  color: var(--sd-primary);
}

.dash-kpi {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 1rem;
  background: #fff;
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-card-radius);
  box-shadow: var(--sd-shadow-xs);
  height: 100%;
  transition: box-shadow var(--sd-transition), transform var(--sd-transition), border-color var(--sd-transition);
}

.dash-kpi:hover {
  box-shadow: var(--sd-shadow-sm);
  transform: translateY(-2px);
}

.dash-kpi-icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;
  flex-shrink: 0;
  background: var(--sd-primary-light);
  color: var(--sd-primary);
}

.dash-kpi-icon--lg {
  width: 3rem;
  height: 3rem;
  font-size: 1.35rem;
  color: #fff;
}

.dash-kpi--info .dash-kpi-icon { background: var(--sd-primary-light); color: var(--sd-primary); }
.dash-kpi--success .dash-kpi-icon { background: rgba(47, 158, 110, 0.12); color: var(--sd-success); }
.dash-kpi--danger .dash-kpi-icon { background: rgba(201, 76, 76, 0.12); color: var(--sd-danger); }
.dash-kpi--muted .dash-kpi-icon { background: #f1f3f5; color: var(--sd-muted); }

.dash-kpi-value {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--sd-heading);
  font-variant-numeric: tabular-nums;
}

.dash-kpi-label {
  color: var(--sd-muted);
  font-size: 0.8rem;
}

.dash-pipeline-bar {
  display: flex;
  height: 0.65rem;
  border-radius: 50rem;
  overflow: hidden;
  background: var(--sd-border);
}

.dash-pipeline-bar > span {
  transition: flex-grow var(--sd-transition);
}

.dash-action-card {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 1rem;
  background: #fff;
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-card-radius);
  text-decoration: none;
  color: inherit;
  height: 100%;
  box-shadow: var(--sd-shadow-xs);
  transition: box-shadow var(--sd-transition), transform var(--sd-transition), border-color var(--sd-transition);
}

.dash-action-card:hover {
  box-shadow: var(--sd-shadow-sm);
  transform: translateY(-2px);
  border-color: var(--sd-border-strong);
  color: inherit;
}

.dash-action-card:active {
  transform: translateY(-1px) scale(0.99);
}

@media (prefers-reduced-motion: no-preference) {
  .dash-spin {
    display: inline-block;
    animation: sd-spin-slow 2.5s linear infinite;
  }
}

@keyframes sd-spin-slow {
  to { transform: rotate(360deg); }
}

@media (max-width: 575.98px) {
  .dash-kpi-value { font-size: 1.25rem; }
}

.bg-icon-primary { background-color: var(--sd-primary); }
.bg-icon-accent { background-color: var(--sd-accent); }
.bg-icon-success { background-color: var(--sd-success); }
.bg-icon-warning { background-color: var(--sd-warning); }
.bg-icon-danger { background-color: var(--sd-danger); }

/* ---------- Tables ---------- */
.table-modern {
  --bs-table-bg: transparent;
  font-size: 0.9rem;
}

.table-modern thead th {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--sd-muted);
  background-color: #fafbfc;
  border-bottom: 1px solid var(--sd-border);
  white-space: nowrap;
  padding: 0.85rem 1rem;
}

.table-modern td {
  vertical-align: middle;
  padding: 0.8rem 1rem;
  border-bottom-color: var(--sd-border);
}

.table-modern tbody tr {
  transition: background-color var(--sd-transition-fast);
}

.table-modern tbody tr:hover {
  background-color: var(--sd-primary-light);
}

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

.table-modern a {
  text-decoration: none;
}

.table-modern th a:hover {
  color: var(--sd-primary);
}

.table-modern th a.sort-link {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.table-modern th a.sort-link i {
  font-size: 0.7rem;
}

.table-modern th[aria-sort="ascending"] a.sort-link,
.table-modern th[aria-sort="descending"] a.sort-link {
  color: var(--sd-primary);
}

/* Consistent empty-state treatment for table bodies with no rows */
.table-empty-state {
  text-align: center;
  padding: 2.5rem 1rem;
  color: var(--sd-muted);
}

.table-empty-state i {
  display: block;
  font-size: 1.75rem;
  margin-bottom: 0.5rem;
  color: #c7d3e0;
}

.table-empty-state .table-empty-title {
  font-weight: 600;
  color: var(--sd-heading);
  margin-bottom: 0.15rem;
}

/* ---------- DataTables ---------- */
.dataTables_wrapper > .row:first-child {
  padding: 1rem 1rem 0.75rem;
  margin: 0;
}

.dataTables_wrapper > .row:last-child {
  padding: 0.75rem 1rem 1rem;
  margin: 0;
  align-items: center;
}

.dataTables_wrapper .dataTables_filter input {
  margin-left: 0.4rem;
  min-width: 200px;
}

.dataTables_wrapper .dataTables_length select {
  margin: 0 0.4rem;
}

.dataTables_wrapper .dataTables_info {
  color: var(--sd-muted);
  font-size: 0.85rem;
}

.dataTables_wrapper .dataTables_paginate .pagination {
  margin-bottom: 0;
}

.dataTables_wrapper table.dataTable thead th {
  outline: none;
}

@media (max-width: 575.98px) {
  .dataTables_wrapper > .row:first-child,
  .dataTables_wrapper > .row:last-child {
    text-align: center;
  }

  .dataTables_wrapper .dataTables_length,
  .dataTables_wrapper .dataTables_filter {
    margin-bottom: 0.5rem;
  }

  .dataTables_wrapper .dataTables_filter input {
    width: 100%;
    margin-left: 0;
    margin-top: 0.35rem;
  }
}

/* ---------- Badges ---------- */
.badge {
  font-weight: 600;
  letter-spacing: 0.01em;
  border-radius: 50rem;
  padding: 0.4em 0.75em;
}

/* Status colors are chosen by what they mean, not just alternating hues:
   neutral = waiting on the system, blue = system is actively working,
   teal = system is done and a human needs to act, red = broken, green = human-confirmed.
   Completed previously rendered in warning-yellow, which reads as "needs
   attention/problem" when it actually just means "OCR finished, ready to review." */
.badge-status-Pending { background-color: #e9ecef; color: #495057; }
.badge-status-Processing { background-color: #cfe2ff; color: #084298; }
.badge-status-Completed { background-color: #cff4fc; color: #055160; }
.badge-status-Failed { background-color: #f8d7da; color: #842029; }
.badge-status-Verified { background-color: #d1e7dd; color: #0f5132; }

/* ---------- Pagination ---------- */
.pagination .page-link {
  border-color: var(--sd-border);
  color: var(--sd-body-text);
  transition: background-color var(--sd-transition-fast), color var(--sd-transition-fast), border-color var(--sd-transition-fast);
}

.pagination .page-link:hover {
  background-color: var(--sd-primary-light);
  color: var(--sd-primary);
}

.pagination .page-item.active .page-link {
  box-shadow: var(--sd-shadow-xs);
}

/* ---------- Alerts ---------- */
.alert {
  border: 1px solid transparent;
  border-radius: var(--sd-radius-sm);
}

/* asp-validation-summary always renders the container -- with class
   "validation-summary-valid" and an empty (but present) list -- even when
   there are no errors. Without this rule, any form using the common
   `class="alert alert-danger py-2"` pattern (this one included) shows an
   empty red box on every clean page load. */
.validation-summary-valid {
  display: none;
}

/* ---------- Auth pages ---------- */
.auth-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, var(--sd-primary-dark), var(--sd-primary));
}

/* A quiet radial glow for depth, not decoration -- reinforces "calm and
   secure" rather than competing with the form for attention. */
.auth-shell::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 20% 15%, rgba(255, 255, 255, 0.1), transparent 45%),
    radial-gradient(circle at 85% 90%, rgba(0, 173, 76, 0.16), transparent 40%);
  pointer-events: none;
}

.auth-card {
  width: 100%;
  max-width: 400px;
  background: #fff;
  border-radius: var(--sd-card-radius);
  padding: 2rem;
  box-shadow: var(--sd-shadow-lg);
  position: relative;
}

@media (prefers-reduced-motion: no-preference) {
  .auth-card {
    animation: sd-auth-card-in 0.4s var(--sd-ease) backwards;
  }
  @keyframes sd-auth-card-in {
    from { opacity: 0; transform: translateY(10px) scale(0.98); }
    to { opacity: 1; transform: translateY(0) scale(1); }
  }
}

/* The validation-summary tag helper fully replaces its own inner content
   (the error <ul>), so an icon can't live inside it -- it lives beside it in
   this wrapper instead, shown only once the summary actually has errors. */
.auth-alert-wrap {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.auth-alert-wrap > i {
  display: none;
  color: var(--sd-danger);
  margin-top: 0.7rem;
  flex-shrink: 0;
}

.auth-alert-wrap:has(.validation-summary-errors) > i {
  display: block;
}

@media (prefers-reduced-motion: no-preference) {
  .auth-alert-wrap:has(.validation-summary-errors) {
    animation: sd-auth-error-in 0.3s var(--sd-ease);
  }
  @keyframes sd-auth-error-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: translateY(0); }
  }
}

.auth-demo-hint {
  background: var(--sd-primary-light);
  border: 1px solid var(--sd-primary-border-subtle, var(--sd-border));
  border-radius: var(--sd-radius-sm);
  padding: 0.6rem 0.75rem;
  font-size: 0.8rem;
  color: var(--sd-body-text);
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
}

.auth-demo-hint i {
  color: var(--sd-primary);
  margin-top: 0.15rem;
}

.auth-logo {
  height: 48px;
  width: auto;
}

.auth-footer-credit {
  text-align: center;
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.8rem;
  margin-top: 1.5rem;
  position: relative;
}

/* ---------- Detail pages (Student/Employee Details) ---------- */
.detail-tabs {
  border-bottom: 1px solid var(--sd-border);
  gap: 0.25rem;
}

.detail-tabs .nav-link {
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--sd-muted);
  font-weight: 500;
  padding: 0.6rem 0.25rem;
  margin-right: 1.25rem;
  transition: color var(--sd-transition-fast), border-color var(--sd-transition-fast);
}

.detail-tabs .nav-link:hover {
  color: var(--sd-primary);
  border-color: var(--sd-border-strong);
}

.detail-tabs .nav-link.active {
  color: var(--sd-primary);
  background: transparent;
  border-color: var(--sd-primary);
  font-weight: 600;
}

/* Clean key/value display for read-only record details -- replaces ad hoc
   dl.row markup with a consistent label/value rhythm across every detail card. */
.info-list {
  display: flex;
  flex-direction: column;
}

.info-list-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--sd-border);
}

.info-list-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.info-list-row:first-child {
  padding-top: 0;
}

.info-list-label {
  color: var(--sd-muted);
  font-size: 0.8rem;
  flex-shrink: 0;
}

.info-list-value {
  color: var(--sd-text-primary, var(--sd-heading));
  font-weight: 500;
  text-align: right;
}

/* A calmer empty-state treatment for cards with nothing to show yet, reused
   from the table empty-state pattern (the styling isn't table-specific). */
.card-empty-state {
  text-align: center;
  padding: 1.5rem 1rem;
  color: var(--sd-muted);
}

.card-empty-state i {
  display: block;
  font-size: 1.5rem;
  margin-bottom: 0.4rem;
  color: #c7d3e0;
}

/* ---------- File upload dropzone ---------- */
.dropzone {
  position: relative;
  border: 2px dashed var(--sd-border-strong);
  border-radius: var(--sd-radius-sm);
  background: var(--sd-body-bg);
  padding: 1.5rem 1rem;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--sd-transition-fast), background-color var(--sd-transition-fast);
}

.dropzone:hover {
  border-color: var(--sd-primary);
}

.dropzone.is-dragover {
  border-color: var(--sd-primary);
  background: var(--sd-primary-light);
}

.dropzone input[type="file"] {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.dropzone-empty i {
  font-size: 1.5rem;
  color: var(--sd-muted);
  display: block;
  margin-bottom: 0.4rem;
}

.dropzone-empty-text {
  color: var(--sd-muted);
  font-size: 0.85rem;
}

.dropzone-preview {
  display: none;
  align-items: center;
  gap: 0.75rem;
  text-align: left;
}

.dropzone.has-file .dropzone-empty {
  display: none;
}

.dropzone.has-file .dropzone-preview {
  display: flex;
}

.dropzone-thumb {
  width: 44px;
  height: 44px;
  border-radius: var(--sd-radius-sm);
  object-fit: cover;
  background: #fff;
  border: 1px solid var(--sd-border);
  flex-shrink: 0;
}

.dropzone-thumb-icon {
  width: 44px;
  height: 44px;
  border-radius: var(--sd-radius-sm);
  background: #fff;
  border: 1px solid var(--sd-border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3rem;
  color: var(--sd-primary);
  flex-shrink: 0;
}

.dropzone-file-info {
  min-width: 0;
  flex-grow: 1;
}

.dropzone-file-name {
  font-weight: 600;
  color: var(--sd-heading);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dropzone-file-size {
  color: var(--sd-muted);
  font-size: 0.8rem;
}

.dropzone-remove {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
}

/* ---------- Document viewer ---------- */
.doc-viewer-frame {
  width: 100%;
  height: 100%;
  min-height: 420px;
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-radius-sm);
  background: #fff;
}

.doc-preview-body {
  height: 70vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
}

.doc-preview-body img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* Bootstrap's .modal-content is already a flex column (header/body/footer) --
   in full screen it just needs to fill the viewport and let the preview body
   (rather than the fixed 70vh) grow into the freed-up space. */
.modal-content:fullscreen,
.modal-content:-webkit-full-screen {
  height: 100vh;
  border-radius: 0;
}

.modal-content:fullscreen .doc-preview-body,
.modal-content:-webkit-full-screen .doc-preview-body {
  height: auto;
  flex: 1 1 auto;
  min-height: 0;
}

/* ---------- OCR review cockpit (Student Forms Review) ----------
   Two panes that each own their scrolling, plus one action bar pinned to the
   viewport. The reviewer's loop is "read the paper -> fix the field -> commit",
   and every part of that has to be reachable without a scroll trip: the scan
   stays pinned on the left, the fields scroll on the right, and Save/Verify sit
   together in the bar rather than at opposite ends of a 34-field form. */
:root {
  --sd-topbar-h: 61px;
  --sd-review-bar-h: 4.5rem;
}

.ocr-review--has-actions {
  padding-bottom: calc(var(--sd-review-bar-h) + 1rem);
}

.ocr-review-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

/* Nothing shares the rail with the scan -- the paper is the reference the whole
   screen exists to serve, so it gets every pixel between the topbar and the
   action bar. */
.ocr-review-rail {
  position: sticky;
  top: calc(var(--sd-topbar-h) + 1.5rem);
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--sd-topbar-h) - 3rem - var(--sd-review-bar-h));
  min-height: 420px;
}

.ocr-review-scan {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.ocr-review-scan .card-body {
  min-height: 0;
  flex: 1 1 auto;
}

.ocr-review-fields {
  min-width: 0;
}

/* Fixed rather than sticky: sticky is bounded by its containing block, so a bar
   at the end of the page would only float for the last few pixels of scroll.
   Offset to clear the fixed sidebar, matching .app-main's margin. */
.ocr-action-bar {
  position: fixed;
  left: var(--sd-sidebar-width);
  right: 0;
  bottom: 0;
  z-index: 1015;
  background: #fff;
  border-top: 1px solid var(--sd-border);
  box-shadow: 0 -4px 16px rgba(16, 24, 40, 0.07);
  padding: 0.7rem 1.5rem;
}

.ocr-action-bar-inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.ocr-progress {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.ocr-progress-text {
  font-size: 0.85rem;
  color: var(--sd-heading);
  font-weight: 600;
}

.ocr-save-status {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.82rem;
  color: var(--sd-muted);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.ocr-save-status.is-dirty { color: var(--sd-warning); }
.ocr-save-status.is-saved { color: var(--sd-success); }
.ocr-save-status.is-error { color: var(--sd-danger); }

.ocr-action-bar-buttons {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
  margin-left: auto;
}

/* Below lg the sidebar is off-canvas, so the bar spans the full width. The rail
   stops being sticky too -- a pinned half-height scan on a phone would eat the
   screen the fields need. */
@media (max-width: 991.98px) {
  .ocr-review-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .ocr-review-rail {
    position: static;
    height: auto;
    min-height: 0;
  }
  .ocr-review-scan .card-body {
    height: 55vh;
  }
  .ocr-action-bar {
    left: 0;
    padding: 0.6rem 1rem;
  }
}

/* Once the label text is dropped the buttons stop being the widest thing in the
   bar, so the counter and status can share the remaining row. */
@media (max-width: 575.98px) {
  .ocr-progress-text { font-size: 0.8rem; }
  .ocr-save-status { flex-basis: 100%; order: 3; }
}

.field-review-group {
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-radius-sm);
  padding: 1rem;
  margin-bottom: 1rem;
  scroll-margin-top: calc(var(--sd-topbar-h) + 1.5rem);
  transition: border-color var(--sd-transition-fast);
}

.field-review-group:hover {
  border-color: var(--sd-border-strong);
}

.field-review-group h6 {
  text-transform: uppercase;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--sd-muted);
  margin-bottom: 0.75rem;
}

/* OCR-sourced field, not yet reviewed by a human -- a blue tint (distinct from
   the amber "you changed this" indicator used on Student/Employee Edit) that
   fades away the moment the reviewer touches the field, since editing it IS
   the review. */
.field-ocr-pending {
  padding-left: 0.6rem;
  border-left: 2px solid var(--sd-primary);
  background: var(--sd-primary-light);
  border-radius: 0 var(--sd-radius-sm) var(--sd-radius-sm) 0;
  padding-top: 0.4rem;
  padding-bottom: 0.4rem;
  transition: background-color var(--sd-transition), border-color var(--sd-transition), padding var(--sd-transition);
}

.ocr-field-badge {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--sd-primary);
  background: #fff;
  border-radius: 50rem;
  padding: 0.1rem 0.45rem;
  margin-left: 0.4rem;
  vertical-align: middle;
}

.field-label-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.25rem;
}

/* Edited-but-unsaved. Amber left border deliberately matches the "Changed" idiom
   on Student/Employee Edit -- same meaning, so it should look the same. Mutually
   exclusive with .field-ocr-pending in practice: editing a field clears that. */
.ocr-field {
  padding-left: 0.6rem;
  border-left: 2px solid transparent;
  border-radius: 0 var(--sd-radius-sm) var(--sd-radius-sm) 0;
  transition: border-color var(--sd-transition), background-color var(--sd-transition);
}

.ocr-field--dirty {
  border-left-color: var(--sd-warning);
}

.ocr-field-flag {
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--sd-warning);
  background: rgba(217, 167, 43, 0.14);
  border-radius: 50rem;
  padding: 0.1rem 0.45rem;
  white-space: nowrap;
}

.ocr-field-flag[hidden] {
  display: none;
}

.ocr-field--rejected .form-control,
.ocr-field--rejected .form-select {
  border-color: var(--sd-danger);
}

/* A save with nothing to show for it feels like nothing happened. One quiet
   green wash per saved field is the receipt -- it names exactly which edits
   landed, which a toast at the top of the page cannot. */
@media (prefers-reduced-motion: no-preference) {
  .ocr-field--just-saved {
    animation: sd-field-saved 0.9s var(--sd-ease);
  }
  @keyframes sd-field-saved {
    0% { background-color: rgba(47, 158, 110, 0.18); border-left-color: var(--sd-success); }
    100% { background-color: transparent; border-left-color: transparent; }
  }
}

/* ---------- Confidence ring (signature element) ----------
   A donut built from a conic-gradient track + an inset surface-colored circle
   cut out of the middle -- avoids SVG for something this small and reused this
   often. --pct is 0-100, set inline per instance. Registering it with @property
   lets the browser transition the fill in on load instead of snapping; browsers
   without @property support just render the final value immediately, which is
   still correct. */
@property --sd-ring-pct {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

.confidence-ring {
  --sd-ring-pct: 0;
  --sd-ring-color: var(--sd-confidence-none);
  width: var(--sd-ring-size, 20px);
  height: var(--sd-ring-size, 20px);
  border-radius: 50%;
  flex-shrink: 0;
  position: relative;
  background: conic-gradient(var(--sd-ring-color) calc(var(--sd-ring-pct) * 1%), var(--sd-border) 0);
}

.confidence-ring::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--sd-surface, #fff);
}

@media (prefers-reduced-motion: no-preference) {
  .confidence-ring {
    transition: --sd-ring-pct 0.6s var(--sd-ease);
  }
}

.confidence-ring-label {
  font-family: var(--sd-font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--sd-muted);
}

/* ---------- Motion utilities: staggered entrance ---------- */
@media (prefers-reduced-motion: no-preference) {
  /* fill-mode "backwards" (not "both"): we need the pre-delay "from" state so
     staggered items don't flash visible before their turn, but we must NOT
     hold a non-"none" transform after the animation ends -- a lingering
     transform of any kind creates a new containing block for position:fixed
     descendants (e.g. a Bootstrap modal nested inside a staggered card),
     which would trap it instead of positioning against the viewport. Once
     the animation completes without "forwards", the element reverts to its
     normal static styles, which already equal the "to" keyframe anyway. */
  .stagger-item {
    animation: sd-stagger-in 0.35s var(--sd-ease) backwards;
    animation-delay: calc(var(--stagger-i, 0) * 60ms);
  }
  @keyframes sd-stagger-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: translateY(0); }
  }
}

/* ---------- htmx swap polish ---------- */
.htmx-swapping {
  opacity: 0;
  transition: opacity 120ms ease-out;
}

.htmx-settling {
  opacity: 1;
}

/* ---------- Misc responsiveness ---------- */
@media (max-width: 575.98px) {
  h3 { font-size: 1.2rem; }
  .btn { font-size: 0.9rem; }
}

/* ==========================================================================
   User Activity report
   ========================================================================== */

.activity-range-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.75rem;
  background: #fff;
  border: 1px solid var(--sd-border-strong);
  border-radius: 50rem;
  box-shadow: var(--sd-shadow-xs);
  font-size: 0.85rem;
  color: var(--sd-body-text);
}

.activity-range-chip i { color: var(--sd-primary); }

.activity-presets {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.25rem;
}

.activity-preset {
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--sd-border-strong);
  border-radius: 50rem;
  font-size: 0.8rem;
  text-decoration: none;
  color: var(--sd-body-text);
  background: #fff;
  transition: background var(--sd-transition-fast), border-color var(--sd-transition-fast), color var(--sd-transition-fast);
}

.activity-preset:hover {
  border-color: var(--sd-primary);
  color: var(--sd-primary);
}

.activity-preset.is-current {
  background: var(--sd-primary);
  border-color: var(--sd-primary);
  color: #fff;
}

/* A dot, not a word: "signed in now" appears beside a dozen names and needs to be
   scannable. Never the only carrier of the meaning -- every use is paired with a
   title attribute or adjacent text, since colour alone is not an indicator. */
.activity-live-dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--sd-success);
  box-shadow: 0 0 0 0.15rem rgba(47, 158, 110, 0.2);
  flex-shrink: 0;
}

/* ---------- daily bar strip ---------- */

.activity-bars {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 5rem;
  padding-top: 0.25rem;
}

.activity-bar {
  flex: 1 1 0;
  min-width: 3px;
  height: 100%;
  display: flex;
  align-items: flex-end;
  border-radius: 2px;
  background: var(--sd-primary-light);
  transition: background var(--sd-transition-fast);
}

.activity-bar:hover { background: #dde6f4; }
.activity-bar.is-weekend { background: #f1f3f5; }
.activity-bar.is-empty { background: #f1f3f5; }

.activity-bar > span {
  display: block;
  width: 100%;
  border-radius: 2px;
  background: var(--sd-primary);
  transition: height var(--sd-transition);
}

.activity-bar.is-weekend > span { background: #8ba6ce; }

/* ---------- table tone ---------- */

.activity-row--quiet td { color: var(--sd-muted); }
.activity-row--weekend td { background: #fbfcfd; }

/* ---------- drill-down timeline ---------- */

.activity-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 26rem;
  overflow-y: auto;
}

.activity-timeline li {
  display: flex;
  gap: 0.65rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--sd-border);
}

.activity-timeline li:last-child { border-bottom: 0; }

.activity-timeline-icon {
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: 0.45rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--sd-primary-light);
  color: var(--sd-primary);
  font-size: 0.8rem;
}

.activity-timeline-body { min-width: 0; }

/* ---------- the caveats block ---------- */

.activity-notes {
  border: 1px solid var(--sd-border);
  border-radius: var(--sd-card-radius);
  background: #fff;
  box-shadow: var(--sd-shadow-xs);
}

.activity-notes > summary {
  cursor: pointer;
  padding: 0.65rem 1rem;
  font-size: 0.875rem;
  color: var(--sd-muted);
  list-style: none;
}

.activity-notes > summary::-webkit-details-marker { display: none; }
.activity-notes > summary:hover { color: var(--sd-primary); }
.activity-notes[open] > summary { border-bottom: 1px solid var(--sd-border); }

.activity-notes-body {
  padding: 0.75rem 1rem 0.25rem;
  font-size: 0.875rem;
}

.activity-notes-body dl {
  display: grid;
  grid-template-columns: minmax(8rem, 12rem) 1fr;
  gap: 0.35rem 1rem;
  margin: 0;
}

.activity-notes-body dt {
  color: var(--sd-heading);
  font-weight: 600;
}

.activity-notes-body dd {
  margin: 0;
  color: var(--sd-muted);
}

@media (max-width: 575.98px) {
  .activity-notes-body dl { grid-template-columns: 1fr; gap: 0.1rem; }
  .activity-notes-body dd { margin-bottom: 0.6rem; }
  .activity-bars { height: 3.5rem; }
}

/* ==========================================================================
   Advanced filters (Students list)
   ========================================================================== */

/* Set apart from the everyday filters above it, so it reads as a second tier
   rather than as more of the same row. */
.advanced-filters {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--sd-border-strong);
}

/* Count of active advanced filters, on the toggle. The panel is closed by default,
   so without this a filtered list would give no clue why it is short -- the number
   is the only thing on screen pointing at the collapsed panel. */
.filter-count-badge {
  position: absolute;
  top: -0.35rem;
  right: -0.35rem;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 0.25rem;
  border-radius: 50rem;
  background: var(--sd-primary);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.1rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* The chevron-free toggle needs some other cue that it is a disclosure. */
.advanced-filters-toggle[aria-expanded="true"] {
  background: var(--sd-primary-light);
  border-color: var(--sd-primary);
  color: var(--sd-primary);
}

@media (max-width: 767.98px) {
  .filter-count-badge { top: -0.25rem; right: -0.25rem; }
}

/* ==========================================================================
   Roles and permissions
   ========================================================================== */

/* How much of the catalogue a role holds, at a glance. Paired with the "n / total"
   figure beside it, never on its own -- the bar alone would not say 22 of 26. */
.role-permission-bar {
  flex: 1 1 auto;
  min-width: 4rem;
  height: 0.4rem;
  border-radius: 50rem;
  background: var(--sd-border);
  overflow: hidden;
}

.role-permission-bar > span {
  display: block;
  height: 100%;
  border-radius: 50rem;
  background: var(--sd-primary);
  transition: width var(--sd-transition);
}

.permission-row {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--sd-border);
}

.permission-row:last-child { border-bottom: 0; }

.permission-row .form-check-input { margin-top: 0.3rem; }

.permission-row .form-check-label { cursor: pointer; }

.permission-name {
  display: block;
  font-weight: 600;
  color: var(--sd-heading);
}

.permission-description {
  display: block;
  font-size: 0.85rem;
  color: var(--sd-muted);
}

/* Set apart from the description: this is the sentence that should give someone pause
   before ticking the box, not more explanation of what the box does. */
.permission-warning {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.8rem;
  color: #8a6100;
}

.permission-warning i { color: var(--sd-warning); }

input[type="checkbox"]:disabled ~ .form-check-label .permission-name,
input[type="checkbox"]:disabled ~ .form-check-label .permission-description {
  opacity: 0.7;
}
