/* ==========================================================================
   Aditya University — CSE Permission & Leave Tracking System
   Design tokens aligned with digitalservices.adityauniversity.in
   ========================================================================== */

/* ---------- Branding assets ----------
   aditya-logo.png   3134x1168 RGBA wordmark with a transparent background.
                     Always shown whole via object-fit: contain -- never cropped.
   aditya-crest.png  512x512   circular sun emblem, for square slots only.

   The wordmark is gold, which reads on white surfaces but not on the dark blue
   top bar. Rather than framing it in a white box, the bar renders it through
   .au-wordmark--invert (brightness(0) invert(1)), which maps every opaque pixel
   to white and leaves transparency untouched. */

.au-wordmark {
  display: block;
  height: auto;
  object-fit: contain;   /* guarantees the full mark is visible */
}

.au-wordmark--invert { filter: brightness(0) invert(1); }

.au-wordmark--side   { width: 152px; }

/* The sign-in brand panel. max-width keeps it fluid: on a phone the mark scales
   down to the panel instead of overflowing, so no fixed mobile override is
   needed. */
.au-wordmark--card   { width: 520px; max-width: 100%; }
.au-wordmark--letter { width: 252px; }

.au-crest {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 50%;
}

/* ---------- Group permission member picker ---------- */

/* The search box and the chosen chips sit side by side on a wide form and stack
   on a narrow one, because the list of chosen students grows downwards and a
   fixed two-column layout would make the form jump as it fills. */
.member-picker { display: grid; gap: 1.1rem; grid-template-columns: 1fr; }
@media (min-width: 62rem) {
  .member-picker { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.member-picker__search { position: relative; }

.member-picker__results {
  position: absolute;
  z-index: 20;
  left: 0; right: 0; top: calc(100% + .25rem);
  margin: 0;
  padding: .25rem;
  list-style: none;
  max-height: 15rem;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--au-border);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgba(16, 24, 40, .14);
}
.member-result {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .45rem .6rem;
  border-radius: 7px;
  cursor: pointer;
  font-size: .88rem;
}
.member-result:hover, .member-result:focus { background: var(--au-primary-soft); }
.member-result.is-added { opacity: .5; cursor: default; }
.member-result__pin { font-weight: 600; }
.member-result__name { color: var(--au-text-muted); font-size: .8rem; }

.member-chips {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0 0 .5rem;
  padding: 0;
  list-style: none;
}
.member-chip {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .3rem .3rem .3rem .7rem;
  background: var(--au-primary-soft);
  border: 1px solid rgba(0, 78, 146, .18);
  border-radius: 20px;
  font-size: .82rem;
}
.member-chip__pin { font-weight: 600; }
.member-chip__name { color: var(--au-text-muted); font-size: .76rem; }
.member-chip__remove {
  border: 0;
  background: transparent;
  color: var(--au-text-muted);
  line-height: 1;
  padding: .15rem .3rem;
  border-radius: 50%;
}
.member-chip__remove:hover { background: rgba(0, 78, 146, .14); color: var(--au-primary-dark); }

:root {
  /* Brand */
  --au-primary: #004e92;
  --au-primary-dark: #003666;
  --au-primary-soft: #e8f0fe;
  --au-gradient-primary: linear-gradient(140deg, #000428, #013c6e, #000428);

  /* Surfaces */
  --au-page-bg: #f2f5f9;
  --au-surface: #ffffff;
  --au-surface-panel: #f8fafc;
  --au-surface-alt: #fbfcfe;

  /* Text */
  --au-text: #16202f;
  --au-text-muted: #5b6879;
  /* The lightest text still in use. It is deliberately still below --au-text-muted
     so the hierarchy survives, but it was #8b96a5, which measured 3.0:1 on the
     light surface -- under the 4.5:1 text needs, and it carries the row numbers,
     the page footer and the sign-in notes. Raised until it clears the bar. */
  --au-text-faint: #6b7789;

  /* Lines + state */
  --au-border: #e2e8f0;
  --au-border-strong: #cbd5e1;
  --au-success: #0f9d6e;
  --au-success-soft: #d8f5e9;
  --au-success-text: #046c4e;
  --au-warning: #d97706;
  --au-warning-soft: #fdf0d5;
  --au-warning-text: #92400e;
  --au-danger: #dc2626;
  --au-danger-soft: #fde5e5;
  --au-danger-text: #991b1b;
  --au-info: #2563eb;

  /* Geometry */
  --au-radius-sm: 6px;
  --au-radius-md: 10px;
  --au-radius-lg: 14px;
  --au-radius-xl: 20px;
  --au-radius-pill: 9999px;
  --au-max-width: 1440px;
  --au-sidebar-w: 284px;

  /* Sidebar: restrained light surface, one flat active state */
  --au-side-bg: #f6f8fa;
  --au-side-text: #334155;
  --au-side-muted: #7c8ba1;
  --au-side-hover: #eaeef3;
  --au-side-active: #e6eef7;
  --au-side-accent: #004e92;
  --au-side-line: #e2e8f0;
  --au-shadow-xs: 0 1px 2px rgba(16, 24, 40, .05);
  --au-shadow-sm: 0 1px 3px rgba(16, 24, 40, .07), 0 1px 2px rgba(16, 24, 40, .04);
  --au-shadow: 0 4px 12px rgba(16, 24, 40, .08);
  --au-shadow-lg: 0 16px 40px rgba(16, 24, 40, .16);

  /* ---------- Frosted filter-bar palette (light theme) ----------

     Kept beside the other tokens rather than declared on the bar, so dark mode
     has one place to restate it. In light mode this is a deep navy glass: over
     a white page it reads unmistakably as a different plane, and the table
     scrolling underneath stays legible as blurred context.

     --glass-muted is the small print inside the bar. It is deliberately not a
     low-alpha white: at .68 it lands near 4:1 against the blended background,
     below the 4.5:1 small text needs, and the complaint that started all this
     was that the bar was hard to read. */
  --glass-bg: rgba(10, 24, 46, .74);
  --glass-line: rgba(255, 255, 255, .14);
  --glass-text: #f4f8fe;
  --glass-muted: rgba(238, 244, 253, .84);
  --glass-field: rgba(255, 255, 255, .11);
  --glass-field-line: rgba(255, 255, 255, .26);
  --glass-field-hover: rgba(255, 255, 255, .18);
  --glass-focus-ring: rgba(255, 255, 255, .5);
  --glass-shadow: 0 16px 36px -16px rgba(2, 8, 20, .7);
  /* The wash that fades the content out below the bar. */
  --glass-fade: rgba(10, 24, 46, .55);

  --au-font: 'Google Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;
  --au-mono: 'SF Mono', 'Cascadia Mono', 'Roboto Mono', Consolas, monospace;

  --bs-body-font-family: var(--au-font);
  --bs-primary: #004e92;
  --bs-primary-rgb: 0, 78, 146;
  --bs-link-color: #004e92;
  --bs-link-hover-color: #003666;
}

[data-bs-theme='dark'] {
  --au-page-bg: #0c1220;
  --au-surface: #141b2b;
  --au-surface-panel: #1b2333;
  --au-surface-alt: #171f2e;
  --au-primary: #4a9fe0;
  --au-primary-soft: #16283f;
  --au-text: #e8edf5;
  --au-text-muted: #a3b0c2;
  --au-text-faint: #8492a6;
  --au-border: #2a3446;
  --au-border-strong: #3a4659;
  --au-success: #34d399;   --au-success-soft: #0d3b2e; --au-success-text: #6ee7b7;
  --au-warning: #fbbf24;   --au-warning-soft: #453110; --au-warning-text: #fcd34d;
  --au-danger: #f87171;    --au-danger-soft: #451a1a;   --au-danger-text: #fca5a5;
  --au-info: #60a5fa;
  --au-side-bg: #141b2b;
  --au-side-text: #c3ccdb;
  --au-side-muted: #8593a8;
  --au-side-hover: #1c2434;
  --au-side-active: #1d3251;
  --au-side-accent: #6fb0ea;
  --au-side-line: #2a3446;
  --au-shadow-xs: 0 1px 2px rgba(0, 0, 0, .3);
  --au-shadow-sm: 0 1px 3px rgba(0, 0, 0, .4);
  --au-shadow: 0 4px 12px rgba(0, 0, 0, .45);
  --au-shadow-lg: 0 16px 40px rgba(0, 0, 0, .6);

  /* Frosted bar, dark theme.

     The light-theme navy is nearly the same value as --au-page-bg here, so on a
     dark page the bar would have flattened into the background with no visible
     edge and nothing for the blur to read against. This is a lighter, cooler
     slate instead, and light enough to clear the page by a clear margin --
     measured at 1.5:1 against --au-page-bg, where a darker glass came out at
     1.3:1 and read as part of the page rather than as a bar over it. The
     border and shadow carry the rest. */
  --glass-bg: rgba(48, 64, 92, .78);
  --glass-line: rgba(255, 255, 255, .18);
  --glass-text: #f2f7ff;
  --glass-muted: rgba(226, 235, 248, .84);
  --glass-field: rgba(255, 255, 255, .12);
  --glass-field-line: rgba(255, 255, 255, .26);
  --glass-field-hover: rgba(255, 255, 255, .18);
  --glass-focus-ring: rgba(255, 255, 255, .55);
  --glass-shadow: 0 18px 40px -18px rgba(0, 0, 0, .9);
  --glass-fade: rgba(12, 18, 32, .62);
}

html { scroll-behavior: smooth; }

body {
  background-color: var(--au-page-bg);
  color: var(--au-text);
  font-family: var(--au-font);
  font-size: .9375rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5 { font-weight: 600; letter-spacing: -.011em; color: var(--au-text); }
a { color: var(--au-primary); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--au-primary);
  outline-offset: 2px;
  border-radius: 4px;
}

::selection { background: var(--au-primary); color: #fff; }

/* ========================== App shell ========================== */

.app-topbar {
  background: var(--au-gradient-primary);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 1030;
  box-shadow: 0 1px 0 rgba(255, 255, 255, .08), 0 2px 12px rgba(0, 0, 0, .22);
}

.app-topbar__inner {
  max-width: var(--au-max-width);
  margin-inline: auto;
  padding: .5rem 28px;
  display: flex;
  align-items: center;
  gap: 1rem;
}

/* The header is a single row for every role: the drawer toggle on the left,
   the controls on the right. The wordmark and the links live in the navigation
   column, so nothing here repeats them. */
.app-topbar__spacer { margin-left: auto; }

.app-user {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding-left: .9rem;
  border-left: 1px solid rgba(255, 255, 255, .16);
}

/* Sign-out as a real form so it cannot be triggered by a link. */
.signout-form { display: inline-flex; }

.icon-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(255, 255, 255, .08);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: background-color .14s ease;
}

.icon-btn:hover { background: rgba(255, 255, 255, .18); color: #fff; }

/* ========================== App shell ========================== */

.au-app { min-height: 100vh; display: flex; flex-direction: column; }

.au-app--sidebar { flex-direction: row; }

/* Desktop: the sidebar is a real flex sibling, not an overlay.

   It is deliberately NOT position:fixed with a margin offset on the main
   column. A fixed sidebar plus `margin-left` relies on the two staying the same
   width forever, and below the lg breakpoint the sidebar becomes an off-canvas
   drawer while that margin would still apply, leaving an empty strip. As a real
   flex item the two meet exactly, with no offset to get out of step. */
@media (min-width: 992px) {
  .au-app--sidebar .au-sidebar-wrap {
    position: sticky !important;   /* Bootstrap forces position:static at lg+ */
    top: 0;
    align-self: flex-start;
    height: 100vh;
    /* !important because Bootstrap's .offcanvas-lg sets width:auto !important
       at this breakpoint, and on mobile it uses --bs-offcanvas-width (400px). */
    flex: 0 0 var(--au-sidebar-w) !important;
    width: var(--au-sidebar-w) !important;
    max-width: var(--au-sidebar-w) !important;
    min-width: var(--au-sidebar-w) !important;
    overflow: hidden;
    background: var(--au-side-bg);
    /* No border and no shadow: the column must sit flush against the content
       so there is no seam or gutter between navigation and page. */
    border: 0;
    box-shadow: none;
    transform: none !important;
    visibility: visible !important;
    z-index: 1020;
  }

  /* The main column must occupy exactly the space the sidebar leaves, starting
     at its right edge. `flex: 1 1 0` rather than `auto` pins the basis to zero
     so the column cannot pick up slack of its own, and the explicit background
     means no strip of <body> colour can show between the column and the
     navigation. */
  .au-app--sidebar .au-app__main {
    margin-left: 0;
    padding-left: 0;
    width: auto;
    flex: 1 1 0;
    min-width: 0;
    background-color: var(--au-page-bg);
  }

  .au-app--sidebar .offcanvas-header { display: none; }

  /* Content fills the whole column the sidebar leaves. The auto margins and the
     max-width cap exist to centre a page that owns the full viewport; once a
     sidebar owns the left edge they leave a band of empty background between
     the column and the first element, and on a wide monitor they cap the page
     well short of the window. Both go. The padding stays on every side so the
     content breathes instead of touching the column. */
  .au-app--sidebar .au-content,
  .au-app--sidebar .au-foot,
  .au-app--sidebar .page-wrap,
  .au-app--sidebar .page-wrap-narrow {
    max-width: none;
    margin-inline: 0;
  }

  /* The header row is capped and centred like the content it sits above, so
     without this it stops matching the content's left edge on a wide screen. */
  .au-app--sidebar .app-topbar__inner {
    max-width: none;
    margin-inline: 0;
  }
}

/* Mobile drawer: Bootstrap drives the width, so set the variable it reads. */
.au-app--sidebar .au-sidebar-wrap {
  --bs-offcanvas-width: 288px;
  --bs-offcanvas-height: 100vh;
}

/* ---------- Main column ---------- */

.au-app__main { display: flex; flex-direction: column; min-width: 0; flex: 1; }

.au-side {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  /* The column is a fixed width (--au-sidebar-w) and .au-side is its only
     child, but it sits in a row flex container, so it would otherwise size to
     its content and leave a band of bare background between the navigation and
     the main column. Filling the row is what closes that gap. */
  width: 100%;
  background: var(--au-side-bg);
  color: var(--au-side-text);
}

/* The wordmark lives here, on the navigation, and nowhere else in the app.
   Gold on the light navigation, white once dark mode turns it dark. */
.au-side__top {
  padding: 1.15rem 1.15rem 1.05rem;
  border-bottom: 1px solid var(--au-side-line);
}

.au-side__top .au-wordmark--side { width: 100%; max-width: 190px; }

[data-bs-theme='dark'] .au-side__top .au-wordmark--side {
  filter: brightness(0) invert(1);
}

.au-sidebar-wrap .offcanvas-body {
  display: flex;
  /* `height`, not `min-height`, for the same reason as .au-side below: the
     column is 100vh with overflow hidden, so a min-height lets the contents
     grow past the viewport where they are clipped rather than scrolled. */
  height: 100%;
  min-height: 0;
}

/* Bounded to the column so a long navigation scrolls *inside* the sidebar
   instead of overflowing it. This is not cosmetic: at lg+ the column is
   `height: 100vh; overflow: hidden`, so a sidebar taller than the window was
   simply cut off with no way to reach the items below the fold -- and an HOD's
   sidebar, which now carries the administrative panel and the teaching panel,
   is exactly the height where that happens. */
.au-app--sidebar .au-side { height: 100%; }

.au-side__nav {
  flex: 1;
  /* A flex item defaults to min-height:auto, which refuses to shrink below its
     content and so silently disables overflow-y. Without this the scrolling
     below never engages however tall the viewport is. */
  min-height: 0;
  padding: 1rem .7rem .6rem;
  overflow-y: auto;
  /* Keeps a scroll gesture inside the sidebar: without it, reaching the end of
     the list hands the page over and the whole document jumps. */
  overscroll-behavior: contain;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  /* The scrollbar is hidden but the scrolling is not. It is a permanent strip
     down the edge of a fixed column, and a permanently visible track there reads
     as a second scrollbar for the page rather than for the navigation. Anyone
     using a pointer rather than a wheel still scrolls; keyboard and screen
     reader scrolling is unaffected. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.au-side__nav::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

.au-side__grouphead {
  margin: 0;
  padding: 1.05rem .8rem .45rem;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--au-side-muted);
}

.au-side__grouphead:first-child { padding-top: .25rem; }
.au-side__list { list-style: none; margin: 0; padding: 0; }

.au-side__item {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .55rem .8rem;
  border-left: 3px solid transparent;
  border-radius: 0 var(--au-radius-sm) var(--au-radius-sm) 0;
  color: var(--au-side-text);
  font-size: .9rem;
  font-weight: 400;
  text-decoration: none;
  transition: background-color .12s ease, color .12s ease;
}

.au-side__item i {
  font-size: 1rem;
  width: 1.1rem;
  text-align: center;
  flex-shrink: 0;
  color: var(--au-side-muted);
}

.au-side__item:hover { background: var(--au-side-hover); color: var(--au-side-text); }
.au-side__item:hover i { color: var(--au-side-text); }

.au-side__item.is-active {
  background: var(--au-side-active);
  border-left-color: var(--au-side-accent);
  color: var(--au-side-accent);
  font-weight: 600;
}

.au-side__item.is-active i { color: var(--au-side-accent); }

.au-side__item:focus-visible {
  outline: 2px solid var(--au-side-accent);
  outline-offset: -2px;
}

/* ---- Footer ---- */

.au-side__foot {
  padding: .6rem .7rem .8rem;
  border-top: 1px solid var(--au-side-line);
}

.au-side__who {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  padding: .4rem .8rem;
  border-radius: var(--au-radius-sm);
  color: var(--au-side-text);
  text-decoration: none;
}

.au-side__who:hover { background: var(--au-side-hover); color: var(--au-side-text); }

.au-side__who-name {
  font-size: .8rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.au-side__who-role {
  font-size: .66rem;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--au-side-muted);
  font-weight: 600;
}

.au-side__out {
  display: flex;
  align-items: center;
  gap: .6rem;
  width: 100%;
  margin-top: .2rem;
  padding: .5rem .8rem;
  border: 0;
  border-radius: var(--au-radius-sm);
  background: transparent;
  color: var(--au-side-muted);
  font-size: .875rem;
  text-align: left;
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}

.au-side__out:hover { background: var(--au-side-hover); color: var(--au-side-text); }
.au-side__out:focus-visible { outline: 2px solid var(--au-side-accent); outline-offset: -2px; }
/* ---------- Main column ---------- */

/* ---------- Main column ---------- */

.au-app__main { display: flex; flex-direction: column; min-width: 0; flex: 1; }

.au-content {
  flex: 1;
  width: 100%;
  /* min-width:0 lets a wide table scroll inside .au-table-wrap instead of
     stretching this flex item (and therefore the whole column). */
  min-width: 0;
  max-width: var(--au-max-width);
  margin-inline: auto;
  padding: 24px 28px 8px;
}

/* The sign-in split is edge-to-edge and exactly one viewport tall, so it has to
   escape the centred, padded content column that every other page uses. */
.au-content--flush {
  max-width: none;
  margin-inline: 0;
  padding: 0;
}

.au-flashes { margin-bottom: .5rem; }

.au-foot {
  max-width: var(--au-max-width);
  margin-inline: auto;
  width: 100%;
  padding: 24px 28px 32px;
  color: var(--au-text-faint);
  font-size: .8rem;
}

/* Standalone content columns for pages that set their own <main class>. Every
   page is sized by the browser rather than by a cap, so nothing is stranded in
   the middle of a wide window; a sidebar page opts out of the centring in the
   block above. */
.page-wrap { max-width: none; margin-inline: 0; padding: 24px 28px 8px; }
.page-wrap-narrow { max-width: none; margin-inline: 0; padding: 24px 28px 8px; }
.page-foot { max-width: var(--au-max-width); margin-inline: auto; padding: 24px 28px 32px; color: var(--au-text-faint); font-size: .8rem; }

@media (max-width: 991px) {
  .au-content, .au-foot,
  .page-wrap, .page-wrap-narrow { padding-inline: 16px; }
}

@media (max-width: 767px) {
  .au-content { padding: 16px 14px 8px; }
  .au-foot { padding: 16px 14px 24px; }
  .page-wrap, .page-wrap-narrow { padding: 16px 14px 8px; }
}

@media (max-width: 767px) {
  .au-content { padding: 16px 14px 8px; }
  .au-foot { padding: 16px 14px 24px; }
}

/* ========================== Page header ========================== */

.page-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.page-head__title { font-size: 1.4rem; font-weight: 600; margin: 0 0 .2rem; }
.page-head__sub { color: var(--au-text-muted); font-size: .85rem; margin: 0; }
.page-head__actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* ========================== Cards ========================== */

.card-au {
  background: var(--au-surface);
  border: 1px solid var(--au-border);
  border-radius: var(--au-radius-lg);
  box-shadow: var(--au-shadow-xs);
}

.card-au__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
  padding: .85rem 1.15rem;
  border-bottom: 1px solid var(--au-border);
  background: var(--au-surface-panel);
  border-radius: var(--au-radius-lg) var(--au-radius-lg) 0 0;
}

.card-au__title {
  font-size: .82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .055em;
  color: var(--au-text-muted);
  margin: 0;
  display: flex;
  align-items: center;
  gap: .5rem;
}

.card-au__body { padding: 1.15rem; }

/* ========================== Stat tiles ========================== */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: .85rem;
  margin-bottom: 1.5rem;
}

.stat {
  background: var(--au-surface);
  border: 1px solid var(--au-border);
  border-radius: var(--au-radius-lg);
  box-shadow: var(--au-shadow-xs);
  padding: .95rem 1.1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  position: relative;
  overflow: hidden;
  transition: box-shadow .16s ease, transform .16s ease;
}

.stat::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--au-primary);
}

.stat:hover { box-shadow: var(--au-shadow); transform: translateY(-1px); }

.stat--success::before { background: var(--au-success); }
.stat--warning::before { background: var(--au-warning); }
.stat--danger::before  { background: var(--au-danger); }
.stat--info::before    { background: var(--au-info); }

.stat__label {
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--au-text-faint);
}

.stat__value {
  font-size: 1.85rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

.stat__icon { font-size: 1.5rem; opacity: .22; }

/* ========================== Tables ========================== */

/* Wide tables scroll inside this wrapper rather than stretching the page.
   This must be a base rule: inside a max-width media query it leaves the
   document overflowing on desktop and mid-size screens. */
.au-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}

.card-au { min-width: 0; }

.table-au {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: .875rem;
}

.table-au thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--au-surface-panel);
  color: var(--au-text-muted);
  font-size: .69rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: .65rem 1rem;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1.5px solid var(--au-border-strong);
}

.table-au tbody td {
  padding: .7rem 1rem;
  border-bottom: 1px solid var(--au-border);
  vertical-align: middle;
  color: var(--au-text);
}

/* Zebra striping keeps long tables scannable. */
.table-au tbody tr:nth-child(even) { background: var(--au-surface-alt); }
.table-au tbody tr:hover { background: var(--au-primary-soft); }
.table-au tbody tr:last-child td { border-bottom: 0; }

.table-au__num { text-align: right; font-variant-numeric: tabular-nums; }
.table-au__center { text-align: center; }
.table-au__idx { color: var(--au-text-faint); font-variant-numeric: tabular-nums; width: 46px; }
.table-au__actions { text-align: right; white-space: nowrap; }
.table-au__strong { font-weight: 600; }

/* A row waiting on the HOD carries a tint so the actionable part of a long
   register reads at a glance. Kept to the first cell's edge rather than the
   whole row, so zebra striping still scans. */
.table-au__row--actionable td:first-child {
  box-shadow: inset 3px 0 0 var(--au-primary);
}

/* Section dividers inside a register: one line that says what the rows below
   it have in common, so the queue and the archive read as two lists. */
.table-au__section td {
  background: var(--au-surface-panel);
  font-weight: 700;
  font-size: .78rem;
  color: var(--au-text-muted);
  letter-spacing: .02em;
  border-bottom: 1.5px solid var(--au-border-strong);
}
.table-au__section--action td {
  background: var(--au-primary-soft);
  color: var(--au-primary-dark);
  border-bottom: 1.5px solid var(--au-primary);
}

/* The stage in words: "Waiting on you" is the HOD's own to-do and reads as
   one, "With faculty" is still travelling. The dot carries the meaning at a
   glance down a long list. */
.stage-now, .stage-wait { font-weight: 700; }
.stage-now { color: var(--au-primary); }
.stage-wait { font-weight: 600; color: var(--au-warning-text); }
.stage-now::before, .stage-wait::before {
  content: '';
  display: inline-block;
  width: .5em;
  height: .5em;
  border-radius: 50%;
  margin-right: .45em;
  vertical-align: baseline;
}
.stage-now::before { background: var(--au-primary); }
.stage-wait::before { background: var(--au-warning); }

/* A request reference is its own link: the fastest way to open a row. */
.req-link { text-decoration: none; }
.req-link:hover { text-decoration: underline; }

/* A row that opens its detail page on click shows the affordance. */
.table-au tr[data-href] { cursor: pointer; }

/* Event rows: rename, coordinator and save sit on one line; subs indent. */
.event-row { display: flex; gap: .4rem; align-items: center; }
.event-row__form { display: flex; gap: .4rem; align-items: center; flex: 1 1 auto; flex-wrap: wrap; }
.event-row__name { flex: 2 1 180px; font-weight: 600; }
.event-row__coord { flex: 1 1 160px; }
.event-subs { margin-top: .8rem; border-top: 1px dashed var(--au-border-strong); padding-top: .8rem; display: grid; gap: .6rem; }
.event-sub { display: flex; gap: .6rem; align-items: flex-start; }
.event-addsub { display: flex; gap: .4rem; margin-top: .8rem; flex-wrap: wrap; }
.event-addsub .form-control { flex: 2 1 200px; }
.event-addsub .form-select { flex: 1 1 180px; }

/* Two-line clamp for triage text: the screen shows a neat teaser with the
   full text on hover, while print shows everything (see the print rules). */
.clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-clamp: 2;
  overflow: hidden;
}

/* The register filter bar pins under the header while a long table scrolls.
   Top offset clears the fixed top bar; the card's own background keeps rows
   from showing through. */
.toolbar-sticky {
  position: sticky;
  top: 64px;
  z-index: 5;
}

/* ---------- Frosted filter bar ---------------------------------------------

   The bar is dark, translucent and blurred rather than an opaque panel. Rows
   scrolling underneath stay visible through it -- dimmed and out of focus --
   so the list still reads as a continuing context behind the controls, which is
   what makes the bar feel like it is floating over the table instead of
   cutting the page in half.

   Colours come from the --glass-* tokens in :root and the dark-theme block, so
   both themes are one declaration each rather than a pile of overrides here. */
.toolbar-sticky {
  background: var(--glass-bg);
  border: 1px solid var(--glass-line);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  backdrop-filter: blur(16px) saturate(180%);
}

/* Full-bleed against the content below.

   The bar used to be a rounded card with a margin under it, and that gap was
   the problem: a strip of unblurred table ran between the bar and the first
   row, so the effect broke exactly where it was supposed to read. Sunk to the
   content, with the wash fading out beneath it, there is no seam for the eye
   to catch.

   The fade is a gradient rather than more blur because a backdrop-filter cannot
   fade -- its edge is a hard cut wherever the element stops. */
.toolbar-sticky--flush {
  /* No `position` here. `position: sticky` is set on .toolbar-sticky, and a
     modifier that restated it as `relative` would win on source order at equal
     specificity and silently stop the bar sticking -- which is the one thing
     this bar exists to do. Sticky already establishes a containing block for
     the absolutely positioned ::after below, so nothing is lost by leaving it
     alone. */
  margin-bottom: 0;
  border-bottom: 0;
  border-radius: var(--au-radius-lg) var(--au-radius-lg) 0 0;
}

.toolbar-sticky--flush::after {
  content: '';
  position: absolute;
  inset: 100% 0 auto;
  height: 1.75rem;
  background: linear-gradient(to bottom, var(--glass-fade),
              transparent 85%);
  pointer-events: none;
}

/* The content that used to sit in the margin now provides its own breathing
   room, so removing the bar's margin does not crowd the table against it. */
.toolbar-sticky--flush + * { margin-top: 1.1rem; }

/* Without backdrop-filter the blur does not happen, and at .74 the bar would
   sit over the table as a wash of grey with the text fighting whatever showed
   through. Near-opaque is the honest fallback: it loses the effect, not the
   legibility. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .toolbar-sticky {
    --glass-bg: rgba(10, 24, 46, .97);
    --glass-field: rgba(255, 255, 255, .13);
    --glass-field-line: rgba(255, 255, 255, .3);
  }

  [data-bs-theme='dark'] .toolbar-sticky {
    --glass-bg: rgba(24, 34, 52, .98);
  }
}

/* The card's own surface is what the glass replaces; the toolbar inside it must
   not paint over the blur. */
.toolbar-sticky .toolbar,
.toolbar-sticky .toolbar--standalone {
  background: transparent;
  border-bottom: 0;
  box-shadow: none;
}

.toolbar-sticky .toolbar,
.toolbar-sticky .filterbar__label,
.toolbar-sticky .toolbar__label,
.toolbar-sticky .filterchips__label,
.toolbar-sticky .filterbar__count {
  color: var(--glass-muted);
}

.toolbar-sticky .filterbar__count strong { color: var(--glass-text); }
.toolbar-sticky .filter-divider { background: var(--glass-field-line); }
.toolbar-sticky .filter-search__icon { color: var(--glass-muted); }

/* Fields inside the bar.

   These have to outrank `.form-control, .form-select` further down this file,
   which sets `background: var(--au-surface)` -- white in the light theme. That
   rule was written expecting to win, and it did win here, because it comes
   later at equal specificity.

   The result was a white field with near-black text sitting on the deep navy
   bar in light mode: unreadable, and invisible in dark mode only because
   `--au-surface` happens to be dark there. So the bug looked like a dark-theme
   accident and was invisible until the light theme was checked properly.

   `:where()` cannot fix this -- it contributes zero specificity, which is the
   whole reason the rule lost. The inner selectors are written out instead, so
   `.toolbar-sticky .form-select` is two classes and beats the one-class rule
   wherever either of them ends up in this file. */
.toolbar-sticky .form-control,
.toolbar-sticky .form-select {
  background: var(--glass-field);
  border-color: var(--glass-field-line);
  color: var(--glass-text);
}

.toolbar-sticky .form-control::placeholder,
.toolbar-sticky .form-select::placeholder {
  color: var(--glass-muted);
  opacity: 1;
}

.toolbar-sticky .form-control:hover,
.toolbar-sticky .form-select:hover {
  background: var(--glass-field-hover);
}

.toolbar-sticky .form-control:focus,
.toolbar-sticky .form-select:focus {
  background: var(--glass-field-hover);
  border-color: var(--glass-focus-ring);
  color: #fff;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .12);
}

/* A native select paints its option list with the system widget, not with the
   control's own background, so the options have to be given the dark fill
   separately or the menu opens as a white sheet over a dark bar. */
.toolbar-sticky .form-select option {
  background: #0b1a33;
  color: var(--glass-text);
}

/* The search field carries the composite control's own padding rules, which are
   more specific and set later in search.css -- so the left inset that keeps the
   text clear of the magnifier is restated here rather than left to the load
   order of two files. */
.toolbar-sticky .au-search__input { padding-left: .35rem; }

.toolbar-sticky .filter-search__clear {
  background: var(--glass-field-line);
  color: var(--glass-bg);
}

.toolbar-sticky .filter-search__clear:hover {
  background: var(--glass-focus-ring);
  color: var(--glass-bg);
}

.toolbar-sticky .filterchip {
  background: var(--glass-field);
  border-color: var(--glass-field-line);
  color: var(--glass-text);
}

.toolbar-sticky .filterchip__key { color: var(--glass-muted); }

.toolbar-sticky .filterchip:hover {
  background: var(--glass-field-hover);
  border-color: var(--glass-focus-ring);
  color: #fff;
}

/* The secondary button is the one control whose light-on-light colours the rest
   of the app gave it are wrong for a dark bar. */
.toolbar-sticky :where(.btn-outline-secondary) {
  --bs-btn-color: var(--glass-text);
  --bs-btn-border-color: var(--glass-field-line);
  --bs-btn-hover-bg: var(--glass-field-hover);
  --bs-btn-hover-border-color: var(--glass-focus-ring);
  --bs-btn-hover-color: #fff;
}

.table-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .7rem 1rem;
  border-top: 1.5px solid var(--au-border-strong);
  background: var(--au-surface-panel);
  font-size: .8rem;
  color: var(--au-text-muted);
}

.table-foot__stats { display: flex; gap: 1.1rem; flex-wrap: wrap; }
.table-foot__stat strong { color: var(--au-text); font-variant-numeric: tabular-nums; }

/* Toolbar above tables: filters + record counts. */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: .7rem 1.15rem;
  border-bottom: 1px solid var(--au-border);
  background: var(--au-surface);
  border-radius: var(--au-radius-lg) var(--au-radius-lg) 0 0;
}

.toolbar__label {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--au-text-faint);
  margin-right: .15rem;
}

/* A filter bar standing on its own rather than sitting on top of a table.
   The .toolbar skin is drawn as a table header -- a bottom rule and square
   bottom corners -- which reads as a cut-off card when the card ends there. */
.toolbar--standalone {
  border-bottom: 0;
  border-radius: var(--au-radius-lg);
  box-shadow: var(--au-shadow-sm);
}

/* Filter controls, one fitted row.

   Every control is the same height and sits on one baseline, so the bar reads
   as a single instrument rather than a row of mismatched boxes.

   Widths are flex shares with min-widths, not fixed widths. Fixed widths were
   sized to each control's longest value and added up to more than the content
   column, so the bar broke after the third dropdown and dropped the last one
   onto a row of its own -- an accident that looked like a mistake. Shares with
   a floor let the row always fit, widening the controls as space allows and
   wrapping only when the floors genuinely do not fit. */
.filterbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  width: 100%;
}

/* A plain search box: one control, its own border, an icon and a clear button.

   This is the shape most filter bars want, and it is what the demo-login page
   still uses. The composite below is a different thing and has its own class
   rather than redefining this one -- changing the shared class to fit one page
   is how the other page quietly loses its border. */
.filter-search {
  position: relative;
  flex: 1 1 15rem;
  min-width: 13rem;
  max-width: 27rem;
  display: flex;
}

.filter-search__icon {
  position: absolute;
  left: .6rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--au-text-faint);
  font-size: .85rem;
  pointer-events: none;
}

/* Selector is `.filterbar .filter-search .form-control` rather than a single
   class on purpose. The app's own `.form-control` rule sets `padding` as a
   shorthand later in this file, and at equal specificity the later rule wins --
   which quietly reset the left padding to .75rem and put the placeholder text
   underneath the icon. Three classes cannot be overridden by accident. */
.filterbar .filter-search .form-control {
  width: 100%;
  padding-left: 2rem;
  /* Room for the clear button, so the text never runs under it either. */
  padding-right: 2rem;
}

/* The search box and its scope dropdown, as one control.

   Separated by the bar's .5rem gap, the scope dropdown read as a fifth filter
   rather than part of the search: you had to already know that it chose what
   the box matched against. Sharing one border and one set of rounded corners,
   with a hairline between the halves, says "search, matching Class name"
   without spending a label row on it. */
.filter-combo {
  display: flex;
  align-items: stretch;
  flex: 3 1 15rem;
  min-width: 15rem;
  max-width: 31rem;
  border-radius: var(--au-radius-sm, .5rem);
  /* The halves have to clip to the shared rounding, so the clipping is done
     here rather than left to each half's own border-radius. */
  overflow: hidden;
}

/* Inside the composite, the search box stops being a box of its own. */
.filter-combo .filter-search {
  flex: 1 1 auto;
  min-width: 0;
  max-width: none;
}

.filter-combo__scope {
  flex: 0 0 auto;
  width: 10.5rem;
  border-radius: 0;
  /* The hairline between the halves, drawn on the scope's own left edge so it
     cannot drift out of line with the rounded corners above and below it. */
  border-left: 1px solid var(--au-border-strong);
}

/* Each half draws no border of its own: the container's single border is the
   border, and two would show as a doubled line. */
.filter-combo :where(.form-control, .form-select) {
  height: 2.3rem;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.filter-combo :where(.form-control, .form-select):hover,
.filter-combo :where(.form-control, .form-select):focus {
  background: transparent;
  box-shadow: none;
}

/* Focus is drawn on the half that has focus, not as a ring around the whole
   box -- a ring around both would imply the pair is a single tab stop. */
.filter-combo :where(.form-control, .form-select):focus-visible {
  outline: 2px solid var(--au-focus, rgba(0, 78, 146, .35));
  outline-offset: -2px;
}

.filter-search__input::-webkit-search-cancel-button { display: none; }

.filter-search__clear {
  position: absolute;
  right: .45rem;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: var(--au-border-strong);
  color: var(--au-surface);
  font-size: .6rem;
  text-decoration: none;
  line-height: 1;
}

.filter-search__clear:hover {
  background: var(--au-text-muted);
  color: var(--au-surface);
}

.filterbar .form-control,
.filterbar .form-select { height: 2.3rem; padding-top: .3rem; padding-bottom: .3rem; }

/* A hairline between the search pair and the dropdown filters, so the row
   reads as two groups rather than five unrelated controls. Collapses away when
   the bar wraps, where a stray divider would float on its own line. */
.filter-divider {
  width: 1px;
  align-self: stretch;
  margin: .1rem .35rem;
  background: var(--au-border-strong);
}

.filter-select { flex: 1 1 0; min-width: 6.5rem; padding-right: 1.9rem; }
/* The lecturer list is the one whose longest value (a full name and an employee
   code) is worth the extra width, so it takes a larger share of the row. */
.filter-select--lecturer { flex-grow: 1.6; min-width: 9rem; max-width: 17rem; }

/* The three narrowing dropdowns travel as one group, so the bar breaks between
   "search" and "narrow by" rather than between two of the three. */
.filterbar__narrow {
  display: flex;
  flex: 0 1 auto;
  gap: .5rem;
  min-width: 0;
}

.filterbar__actions {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-left: auto;
}

.filterbar__count {
  font-size: .78rem;
  color: var(--au-text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  padding-right: .25rem;
}

.filterbar__count strong { color: var(--au-text); font-weight: 700; }

/* Only present before the page script runs, which removes it. See the template
   comment: without JS it is the only way to apply a filter, with JS it is a
   dead button at the loudest end of the bar. */
.filterbar__apply[hidden] { display: none; }

/* Chips naming the filters in force, each removable on its own. */
.filterchips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem;
  width: 100%;
  padding-top: .1rem;
}

.filterchips__label {
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--au-text-faint);
}

.filterchip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .18rem .5rem .18rem .55rem;
  border: 1px solid var(--au-border-strong);
  border-radius: 999px;
  background: var(--au-surface-panel);
  color: var(--au-text);
  font-size: .76rem;
  line-height: 1.35;
  text-decoration: none;
  white-space: nowrap;
}

.filterchip:hover {
  border-color: var(--au-primary);
  background: var(--au-primary-soft);
  color: var(--au-primary-dark);
  text-decoration: none;
}

.filterchip__key {
  font-weight: 700;
  color: var(--au-text-muted);
}

.filterchip__key::after { content: ':'; margin-right: .1rem; }

.filterchip__value {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.filterchip__drop {
  font-size: .95rem;
  line-height: 1;
  opacity: .6;
}

.filterchip:hover .filterchip__drop { opacity: 1; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .75rem;
  border-radius: var(--au-radius-pill);
  border: 1px solid var(--au-border-strong);
  background: var(--au-surface);
  color: var(--au-text-muted);
  font-size: .8rem;
  font-weight: 500;
  cursor: pointer;
  transition: all .14s ease;
}

.chip:hover { border-color: var(--au-primary); color: var(--au-primary); text-decoration: none; }
.chip.is-active { background: var(--au-primary); border-color: var(--au-primary); color: #fff; }
.chip__count {
  font-size: .7rem;
  font-weight: 700;
  background: rgba(0, 0, 0, .07);
  border-radius: var(--au-radius-pill);
  padding: 0 .4rem;
  font-variant-numeric: tabular-nums;
}
.chip.is-active .chip__count { background: rgba(255, 255, 255, .25); }

/* ========================== Status banner ========================== */

.status-banner {
  display: flex;
  align-items: center;
  gap: 1rem;
  border: 1.5px solid var(--au-border);
  border-left-width: 4px;
  border-radius: var(--au-radius-lg);
  padding: .9rem 1.15rem;
  margin-bottom: 1.25rem;
  background: var(--au-surface);
}

.status-banner__icon { font-size: 1.6rem; line-height: 1; }
.status-banner__label {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--au-text-faint);
}
.status-banner__value { font-size: 1.05rem; font-weight: 600; }
.status-banner__hint { font-weight: 400; font-size: .82rem; color: var(--au-text-muted); }

.status-banner--approved  { border-color: var(--au-success); border-left-color: var(--au-success); background: var(--au-success-soft); }
.status-banner--approved  .status-banner__icon,
.status-banner--approved  .status-banner__value { color: var(--au-success-text); }
.status-banner--rejected  { border-color: var(--au-danger); border-left-color: var(--au-danger); background: var(--au-danger-soft); }
.status-banner--rejected  .status-banner__icon,
.status-banner--rejected  .status-banner__value { color: var(--au-danger-text); }
.status-banner--pending   { border-color: var(--au-warning); border-left-color: var(--au-warning); background: var(--au-warning-soft); }
.status-banner--pending   .status-banner__icon,
.status-banner--pending   .status-banner__value { color: var(--au-warning-text); }
/* Recommended by the lecturer, waiting on the HOD. Deliberately not a success
   colour: the permission is not granted yet, and tinting it green would say
   otherwise on the one page a student reads to find out whether they may leave. */
.status-banner--awaiting_hod { border-color: var(--au-primary); border-left-color: var(--au-primary); background: var(--au-primary-soft); }
.status-banner--awaiting_hod .status-banner__icon,
.status-banner--awaiting_hod .status-banner__value { color: var(--au-primary-dark); }
.status-banner--cancelled { border-color: var(--au-border-strong); border-left-color: #64748b; background: var(--au-surface-panel); }
.status-banner--cancelled .status-banner__icon,
.status-banner--cancelled .status-banner__value { color: #475569; }
.status-banner--expired   { border-color: #334155; border-left-color: #334155; background: var(--au-surface-panel); }
.status-banner--expired   .status-banner__icon,
.status-banner--expired   .status-banner__value { color: #334155; }

/* ========================== Badges ========================== */

.badge-au {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .22rem .6rem;
  border-radius: var(--au-radius-pill);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .015em;
  white-space: nowrap;
  border: 1px solid transparent;
}

.badge-au--pending   { background: var(--au-warning-soft); color: var(--au-warning-text); border-color: color-mix(in srgb, var(--au-warning) 25%, transparent); }
.badge-au--approved  { background: var(--au-success-soft); color: var(--au-success-text); border-color: color-mix(in srgb, var(--au-success) 25%, transparent); }
.badge-au--rejected  { background: var(--au-danger-soft);  color: var(--au-danger-text);  border-color: color-mix(in srgb, var(--au-danger) 25%, transparent); }
.badge-au--cancelled { background: var(--au-border);        color: var(--au-text-muted); }
.badge-au--expired   { background: #334155; color: #f1f5f9; }
.badge-au--leave     { background: var(--au-primary-soft);  color: var(--au-primary); border-color: color-mix(in srgb, var(--au-primary) 22%, transparent); }
.badge-au--classroom { background: #e6f7ef; color: #0b7a55; border-color: #a7e3c9; }
.badge-au--role      { background: rgba(255, 255, 255, .14); color: #fff; }

[data-bs-theme='dark'] .badge-au--classroom {
  background: #0d3b2e; color: #6ee7b7; border-color: #1c5f49;
}

/* Roll number renders as a monospace identifier chip. */
.roll {
  display: inline-block;
  font-family: var(--au-mono);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .02em;
  background: var(--au-primary-soft);
  color: var(--au-primary);
  border-radius: var(--au-radius-sm);
  padding: .18rem .5rem;
  white-space: nowrap;
}

.roll--lg { font-size: 1rem; padding: .3rem .7rem; }

/* ========================== Forms ========================== */

.form-label {
  font-size: .8rem;
  font-weight: 600;
  color: var(--au-text);
  margin-bottom: .3rem;
}

.form-control, .form-select {
  border: 1px solid var(--au-border-strong);
  border-radius: var(--au-radius-sm);
  background: var(--au-surface);
  color: var(--au-text);
  padding: .5rem .75rem;
  font-size: .9rem;
}

.form-control:focus, .form-select:focus {
  border-color: var(--au-primary);
  box-shadow: 0 0 0 3px rgba(0, 78, 146, .14);
  background: var(--au-surface);
  color: var(--au-text);
}

.form-control:disabled {
  background: var(--au-surface-panel);
  color: var(--au-text-muted);
  border-color: var(--au-border);
  opacity: 1;
}

.form-control.is-invalid, .form-select.is-invalid { border-color: var(--au-danger); }

/* A select has to look like a menu.

   Bootstrap paints the arrow with a `background-image`, and the `background`
   shorthand in the rule above resets background-image to none -- so every
   <select> in the app has been rendering as a plain rounded box with nothing to
   say it opens a list.

   The arrow itself is NOT painted here. It used to be a `background-image`, which
   only worked as long as `background-repeat: no-repeat` won over the `background`
   shorthand in `.form-control, .form-select` above -- an ordering dependency,
   and losing it tiles the arrow across the control instead of drawing one.
   Selects are now wrapped in `.au-select` (static/css/search.css), which draws
   the arrow as an element. `appearance: none` here is the safety net for any
   select not yet wrapped: a bare box is far better than a row of chevrons. */
.form-select {
  appearance: none;
  -webkit-appearance: none;
  background-image: none;
  padding-right: 2rem;
}

.au-select .form-select { padding-right: 2.1rem; }

/* Secondary text, not decorative text. It was --au-text-faint, which is about
   3:1 on the light surface -- under the 4.5:1 that small text needs, and it is
   used for the hints people actually read (an employee number under a lecturer's
   name, a count beside a heading). --au-text-muted clears 5.9:1 on light and
   about 8:1 on dark, and still reads as secondary next to --au-text. */
.form-text { color: var(--au-text-muted); font-size: .78rem; }

/* ---------- Editing: the change preview and the amendment trail ---------- */

/* Old value struck through, new value in the accent colour. Struck-through is
   doing the work here: a column headed "Now" has to be read deliberately, while
   a struck value is recognised without reading it at all. */
.change-table td { vertical-align: top; }

.change-before {
  color: var(--au-text-muted);
  text-decoration: line-through;
  text-decoration-color: var(--au-danger);
  text-decoration-thickness: 1px;
}

.change-after {
  color: var(--au-text);
  font-weight: 600;
  background: var(--au-success-soft);
  padding: .05rem .3rem;
  border-radius: var(--au-radius-sm);
}

.change-after--clear {
  color: var(--au-danger-text);
  background: var(--au-danger-soft);
  font-weight: 500;
}

/* Each saved amendment, newest last. The rule between them is the only thing
   separating entries, so it is inset to line up with the text rather than
   running the full width of the card. */
.amendment {
  padding: .75rem 0;
  border-top: 1px solid var(--au-border);
}

.amendment:first-child { border-top: 0; padding-top: 0; }

.amendment__head {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  flex-wrap: wrap;
  margin-bottom: .35rem;
}

.amendment__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .25rem;
}

.amendment__list li {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  flex-wrap: wrap;
  font-size: .85rem;
}

.amendment__label {
  min-width: 9rem;
  color: var(--au-text-muted);
}

.amendment__arrow { color: var(--au-text-faint); font-size: .8rem; }

/* The value each field holds right now, shown beside the input on an edit
   form. Not a placeholder -- a placeholder disappears the moment you type,
   which is exactly when you want to check what you are replacing. */
.field-current {
  display: block;
  margin-top: .3rem;
  font-size: .76rem;
  color: var(--au-text-muted);
}

.field-current__label {
  color: var(--au-text-faint);
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: .68rem;
  font-weight: 700;
}

/* ---------- Leave application: period picker and status toasts ---------- */

/* The select-all control and the live count sit together on the right of the
   card head. `margin-left: auto` pushes them past the title regardless of how
   long the title is. */
.period-picker {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-left: auto;
}

.period-picker__count {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The count is the only thing that changes as boxes are ticked, so it gets a
   brief highlight -- otherwise the number changes silently and the button's
   own label is the only feedback. */
.period-picker__count.is-changing {
  animation: au-count-pulse .45s ease;
}

@keyframes au-count-pulse {
  from { color: var(--au-primary); }
  to   { color: var(--au-text-muted); }
}

/* Status toasts, bottom-right so they sit clear of the sticky filter bars and
   of the send button that produced them. */
.au-toast-stack {
  z-index: 1090;   /* above the sticky bars, below the modals */
  display: flex;
  flex-direction: column;
  gap: .6rem;
}

.au-toast {
  display: flex;
  align-items: flex-start;
  gap: .65rem;
  width: min(26rem, calc(100vw - 2rem));
  padding: .8rem .9rem;
  border: 1px solid var(--au-border-strong);
  border-left: 4px solid var(--au-info);
  border-radius: var(--au-radius-md);
  background: var(--au-surface);
  color: var(--au-text);
  box-shadow: var(--au-shadow-lg);
}

.au-toast--success { border-left-color: var(--au-success); }
.au-toast--danger  { border-left-color: var(--au-danger); }
.au-toast--warning { border-left-color: var(--au-warning); }
.au-toast--info    { border-left-color: var(--au-info); }

.au-toast__icon {
  font-size: 1.05rem;
  line-height: 1.35;
  flex-shrink: 0;
}

.au-toast--success .au-toast__icon { color: var(--au-success-text); }
.au-toast--danger  .au-toast__icon { color: var(--au-danger-text); }
.au-toast--warning .au-toast__icon { color: var(--au-warning-text); }
.au-toast--info    .au-toast__icon { color: var(--au-info); }

.au-toast__body {
  flex: 1;
  font-size: .88rem;
  line-height: 1.45;
}

.au-toast__close {
  flex-shrink: 0;
  border: 0;
  background: transparent;
  color: var(--au-text-faint);
  font-size: 1.25rem;
  line-height: 1;
  padding: 0 .15rem;
  cursor: pointer;
}

.au-toast__close:hover { color: var(--au-text); }

.form-section { display: grid; gap: .3rem; }

.definition { display: grid; grid-template-columns: minmax(120px, 38%) 1fr; gap: 0; margin: 0; }
.definition dt {
  font-size: .78rem;
  color: var(--au-text-muted);
  font-weight: 500;
  padding: .5rem 0;
  border-bottom: 1px solid var(--au-border);
}
.definition dd {
  margin: 0;
  font-size: .875rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--au-border);
  word-break: break-word;
}
.definition dt:last-of-type, .definition dd:last-of-type { border-bottom: 0; }

/* Radio cards for permission type. */
.pick-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: .85rem; }

.pick {
  border: 1.5px solid var(--au-border-strong);
  border-radius: var(--au-radius-lg);
  padding: 1rem 1.1rem;
  cursor: pointer;
  height: 100%;
  background: var(--au-surface);
  transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
}

.pick:hover { border-color: #9dc3e4; box-shadow: var(--au-shadow-xs); }

.btn-check:checked + .pick {
  border-color: var(--au-primary);
  background: var(--au-primary-soft);
  box-shadow: 0 0 0 1px var(--au-primary) inset;
}

.btn-check:focus-visible + .pick { outline: 2px solid var(--au-primary); outline-offset: 2px; }

.pick__icon { font-size: 1.5rem; color: var(--au-primary); }
.pick__title { font-weight: 600; font-size: .95rem; margin-top: .5rem; }
.pick__hint { font-size: .8rem; color: var(--au-text-muted); }

/* ========================== Buttons ========================== */

.btn {
  border-radius: var(--au-radius-sm);
  font-weight: 500;
  font-size: .875rem;
  padding: .45rem .95rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
}

.btn-pill { border-radius: var(--au-radius-pill); padding: .5rem 1.15rem; }
.btn-sm.btn-pill { padding: .3rem .8rem; }

.btn-primary {
  --bs-btn-bg: var(--au-primary);
  --bs-btn-border-color: var(--au-primary);
  --bs-btn-hover-bg: var(--au-primary-dark);
  --bs-btn-hover-border-color: var(--au-primary-dark);
  --bs-btn-active-bg: #00294d;
  --bs-btn-active-border-color: #00294d;
  --bs-btn-color: #fff;
  --bs-btn-hover-color: #fff;
  --bs-btn-disabled-bg: #8fb0cc;
  --bs-btn-disabled-border-color: #8fb0cc;
}

.btn-outline-primary {
  --bs-btn-color: var(--au-primary);
  --bs-btn-border-color: var(--au-border-strong);
  --bs-btn-hover-bg: var(--au-primary);
  --bs-btn-hover-border-color: var(--au-primary);
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: var(--au-primary-dark);
  --bs-btn-active-border-color: var(--au-primary-dark);
}

.btn-outline-secondary {
  --bs-btn-color: var(--au-text-muted);
  --bs-btn-border-color: var(--au-border-strong);
  --bs-btn-hover-bg: var(--au-surface-panel);
  --bs-btn-hover-border-color: var(--au-primary);
  --bs-btn-hover-color: var(--au-primary);
}

.btn-success { --bs-btn-bg: var(--au-success); --bs-btn-border-color: var(--au-success); --bs-btn-hover-bg: #0b7d56; --bs-btn-hover-border-color: #0b7d56; --bs-btn-color: #fff; }
.btn-danger  { --bs-btn-bg: var(--au-danger);  --bs-btn-border-color: var(--au-danger);  --bs-btn-hover-bg: #b91c1c; --bs-btn-hover-border-color: #b91c1c; --bs-btn-color: #fff; }

/* ========================== Alerts ========================== */

.alert {
  border-radius: var(--au-radius-md);
  font-size: .875rem;
  border: 1px solid transparent;
}

.alert-success { background: var(--au-success-soft); color: var(--au-success-text); border-color: color-mix(in srgb, var(--au-success) 30%, transparent); }
.alert-danger  { background: var(--au-danger-soft);  color: var(--au-danger-text);  border-color: color-mix(in srgb, var(--au-danger) 30%, transparent); }
.alert-warning { background: var(--au-warning-soft); color: var(--au-warning-text); border-color: color-mix(in srgb, var(--au-warning) 30%, transparent); }
.alert-info    { background: var(--au-primary-soft); color: var(--au-primary); border-color: color-mix(in srgb, var(--au-primary) 25%, transparent); }

/* ========================== Empty state ========================== */

.empty {
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--au-text-muted);
}

.empty__icon {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--au-surface-panel);
  border: 1px solid var(--au-border);
  display: grid;
  place-items: center;
  margin: 0 auto .85rem;
  font-size: 1.4rem;
  color: var(--au-text-faint);
}

.empty__title { font-weight: 600; color: var(--au-text); margin-bottom: .25rem; }
.empty__text { font-size: .85rem; max-width: 44ch; margin-inline: auto; }

/* ========================== Sign-in split layout ========================== */

.auth-split {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  min-height: 100vh;
}

/* Brand panel: wordmark, department and one line of copy, centred. */
.auth-split__art {
  background:
    radial-gradient(900px 460px at 14% -6%, rgba(4, 78, 146, .45), transparent 62%),
    var(--au-gradient-primary);
  color: #fff;
  padding: 2.5rem 2.75rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}

/* Wide enough for .au-wordmark--card, so the mark is not clipped by its own
   container. */
.auth-split__brand { max-width: 40rem; }

.auth-split__title {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 1.4rem 0 .4rem;
  color: #fff;
}

.auth-split__lede {
  font-size: .82rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, .72);
  /* The measure cap narrows the box to a readable line length, so the box itself
     has to be centred too -- centred text inside a left-aligned box still reads
     as left-aligned against the wordmark above it. */
  margin: 0 auto;
  max-width: 42ch;
}

/* Form panel */
.auth-split__form {
  background: var(--au-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2.5rem 2rem;
}

.auth-panel {
  width: min(400px, 100%);
}

.auth-panel__title {
  font-size: 1.5rem;
  font-weight: 600;
  margin: .35rem 0 .4rem;
  color: var(--au-text);
}

.auth-panel__sub {
  font-size: .875rem;
  color: var(--au-text-muted);
  margin: 0 0 1.5rem;
  line-height: 1.6;
}

.auth-panel__sub a { font-weight: 500; }

@media (max-width: 991px) {
  .auth-split { grid-template-columns: 1fr; }

  .auth-split__art {
    padding: 1.5rem 1.5rem 1.75rem;
  }

  .auth-split__title { font-size: .95rem; margin-top: 1rem; }
  .auth-split__lede { font-size: .78rem; }

  .auth-split__form { padding: 2rem 1.25rem 3rem; }
}

/* ========================== Auth screen (superseded by the split layout) ========================== */

.auth-screen {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 2rem 1rem;
  background:
    radial-gradient(900px 480px at 12% -8%, rgba(4, 78, 146, .40), transparent 62%),
    radial-gradient(700px 420px at 92% 108%, rgba(1, 60, 110, .40), transparent 62%),
    var(--au-gradient-primary);
}

.auth-card {
  width: min(452px, 100%);
  background: var(--au-surface);
  border-radius: var(--au-radius-xl);
  box-shadow: var(--au-shadow-lg);
  padding: 2.1rem 1.9rem;
}

.auth-card__brand { text-align: center; margin-bottom: 1.6rem; }

.auth-card__brand img {
  width: 200px;
  max-width: 100%;
  margin-bottom: .6rem;
}

.auth-card__title { font-size: 1.05rem; font-weight: 600; margin: 0 0 .15rem; }
.auth-card__sub { font-size: .8rem; color: var(--au-text-muted); margin: 0; }

.ms-signin {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  width: 100%;
  padding: .75rem 1rem;
  border-radius: var(--au-radius-sm);
  background: var(--au-gradient-primary);
  color: #fff;
  font-weight: 600;
  font-size: .92rem;
  border: 1px solid transparent;
  transition: filter .15s ease, box-shadow .15s ease;
}

.ms-signin:hover { color: #fff; filter: brightness(1.3); text-decoration: none; box-shadow: var(--au-shadow); }

.auth-divider {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin: 1.4rem 0 1.1rem;
  color: var(--au-text-faint);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.auth-divider::before, .auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--au-border);
}

.auth-note { font-size: .78rem; color: var(--au-text-faint); text-align: center; margin: 1rem 0 0; }
.auth-foot { text-align: center; font-size: .75rem; color: rgba(255, 255, 255, .6); margin-top: 1.25rem; }

/* Text that must stay legible on the dark sign-in backdrop. */
.btn-outline-light { color: rgba(255, 255, 255, .85); border-color: rgba(255, 255, 255, .4); }
.btn-outline-light:hover { background: rgba(255, 255, 255, .14); color: #fff; border-color: rgba(255, 255, 255, .6); }
.auth-foot { text-align: center; font-size: .75rem; color: rgba(255, 255, 255, .6); margin-top: 1.25rem; }

/* ========================== Proof viewer ========================== */

.proview {
  width: 100%;
  height: 340px;
  border: 1px solid var(--au-border);
  border-radius: var(--au-radius-md);
  background: var(--au-surface-panel);
}

/* ========================== Printed report ========================== */

.report-shell { background: #eef1f5; padding: 2rem 1rem; min-height: 100vh; }

.report-sheet {
  background: #fff;
  max-width: 850px;
  margin-inline: auto;
  padding: 2.5rem 2.25rem;
  border-radius: var(--au-radius-lg);
  box-shadow: var(--au-shadow);
}

.report-head {
  text-align: center;
  border-bottom: 2px solid var(--au-primary);
  padding-bottom: 1rem;
  margin-bottom: 1.5rem;
}

.report-head img { width: 185px; margin-bottom: .5rem; }
.report-head h1 { font-size: .98rem; font-weight: 700; margin: 0; letter-spacing: .01em; }
.report-head p { font-size: .8rem; margin: 0; color: #475569; }
.report-head h2 {
  font-size: .82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .12em;
  margin: .9rem 0 .3rem;
}

.report-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
.report-table th {
  background: #f1f5f9;
  text-align: left;
  font-size: .67rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: .5rem .6rem;
  border: 1px solid #cbd5e1;
}
.report-table td { padding: .45rem .6rem; border: 1px solid #e2e8f0; }
.report-table tbody tr:nth-child(even) { background: #f8fafc; }

.report-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  margin-top: 1rem;
  padding-top: .85rem;
  border-top: 1px solid #cbd5e1;
  font-size: .8rem;
}

.signatures { display: flex; gap: 3rem; margin-top: 3rem; }
.signatures > div { flex: 1; text-align: center; }
.signatures .line { border-top: 1px solid #334155; margin-bottom: .3rem; }
.signatures small { font-size: .75rem; color: #475569; }

/* ========================== Print ========================== */

/* The printed-document header lives in the markup of every shell page but
   must never appear on screen. */
.print-doc-head { display: none; }

@media print {
  .no-print, .no-print * { display: none !important; }

  /* A printed document is always black on white, whatever theme the screen
     is using. Declared after the dark block so it wins by source order. */
  :root { color-scheme: light; }
  [data-bs-theme='dark'] {
    --au-page-bg: #ffffff;
    --au-surface: #ffffff;
    --au-surface-panel: #f8fafc;
    --au-surface-alt: #fbfcfe;
    --au-primary: #004e92;
    --au-primary-dark: #003666;
    --au-primary-soft: #e8f0fe;
    --au-text: #16202f;
    --au-text-muted: #5b6879;
    --au-text-faint: #8b96a5;
    --au-border: #cbd5e1;
    --au-border-strong: #94a3b8;
    --au-success-soft: #d8f5e9; --au-success-text: #046c4e;
    --au-warning-soft: #fdf0d5; --au-warning-text: #92400e;
    --au-danger-soft: #fde5e5;  --au-danger-text: #991b1b;
    --bs-link-color: #004e92;
  }

  /* Every panel keeps its background fills: without this the printed sheet
     keeps the text and loses the status colours, the zebra rows and the
     badges -- the undesigned look the letter CSS already guards against. */
  *, *::before, *::after {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  body, .report-shell { background: #fff !important; padding: 0 !important; }

  /* Force readable text on paper. Screen themes may use light or white text
     that disappears on a white print sheet. */
  body * { color: #000 !important; }

  /* ---- App chrome goes; only the document stays ----
     Sidebar, top bar, footer, headings' action buttons, every form (filters,
     decisions, uploads -- all controls, no records), every button, flash
     messages, breadcrumbs, proof preview frames, the register's action
     column and in-table links (dead navigation on paper) have no meaning
     on paper. The sticky filter card would otherwise print as an empty box,
     since its only child is a hidden form. */
  .au-sidebar-wrap, .app-topbar, .au-foot,
  .page-head__actions, .toolbar, .toolbar-sticky, form,
  .btn, .btn-close, .breadcrumb, .alert, .au-flashes,
  .table-au__actions, iframe.proview,
  .member-picker__search, .stat__icon,
  input, select, textarea, button, .form-select, .form-control {
    display: none !important;
  }

  /* Event rows keep their names on paper: the name lives in the input, so
     the row form and its fields stay while every other control goes. */
  form.event-row__form,
  form.event-row__form input,
  form.event-row__form select,
  form.event-row__form textarea {
    display: revert !important;
  }
  .event-row__form .btn { display: none !important; }
  .event-row__name, .event-row__coord {
    border: 0 !important; background: none !important; padding: 0 !important;
    pointer-events: none; font-size: .85rem; color: #16202f !important;
  }
  .event-row__coord { appearance: none; }

  .au-app, .au-app__main { display: block; }
  .au-content, .page-wrap, .page-wrap-narrow {
    max-width: none; margin: 0; padding: 0 !important;
  }

  /* ---- The document header: centred like the formal letterhead ---- */
  .print-doc-head {
    display: block; text-align: center;
    padding-bottom: 4mm; margin-bottom: 5mm;
    border-bottom: 2.5px solid #b5730f;
  }
  .print-doc-head__logo { width: 20mm; margin: 0 auto 2mm; display: block; object-fit: contain; }
  .print-doc-head__univ { font-weight: 700; font-size: 1rem; color: #101c33; }
  .print-doc-head__dept { font-size: .8rem; color: #475569; }
  .print-doc-head__title { font-size: 1.25rem; font-weight: 700; color: #101c33; margin: .35rem 0 .1rem; }
  .print-doc-head__meta { font-size: .75rem; color: #6b7688; }

  /* The on-screen page heading would repeat the header title verbatim, and
     its subtitle is working copy, not record -- neither prints. */
  .page-head { margin-bottom: .4rem; }
  .page-head__title, .page-head__sub { display: none; }

  /* ---- Document tables: compact grid, full data ----
     Denser than the screen (more rows per sheet), with full grid lines, a
     shaded header that repeats on every sheet, and rows that never split.
     Triage clamps open up: paper gets the whole reason, not the teaser. */
  .au-table-wrap { overflow: visible !important; }
  .table-au { min-width: 0 !important; font-size: .75rem; }
  .table-au thead th {
    position: static; background: #eef2f7 !important; color: #0f172a !important;
    border: 1px solid #94a3b8; white-space: normal;
    padding: .45rem .6rem;
  }
  .table-au tbody td { border: 1px solid #cbd5e1; vertical-align: top; padding: .45rem .6rem; }
  .table-au tbody tr:nth-child(even) { background: #f8fafc !important; }
  .table-au tbody tr:hover { background: transparent !important; }
  .table-au__section td { background: #eef2f7 !important; color: #0f172a !important; }
  .table-au__section--action td { background: #e8f0fe !important; }
  .clamp-2 { display: block; }
  .table-foot { border-top: 2px solid #0f172a; font-size: .78rem; }

  /* ---- Document sections ---- */
  .card-au, .stat {
    box-shadow: none !important; border: 1px solid #cbd5e1; border-radius: 0;
    break-inside: avoid; margin-bottom: .6rem;
  }
  .stat-grid { gap: .5rem; margin-bottom: .6rem; }
  .stat { padding: .55rem .7rem; }
  .stat__value { font-size: 1.3rem; }
  .definition { display: block; }
  .definition dt, .definition dd {
    border-bottom: 1px dotted #cbd5e1; padding: .3rem 0;
  }
  .status-banner { break-inside: avoid; }
  canvas { max-width: 100% !important; }

  /* Links are dead on paper: keep the words, drop the chrome. */
  a { color: inherit !important; text-decoration: none !important; }

  .report-sheet { box-shadow: none; max-width: none; padding: 0; border-radius: 0; }
  .report-head { border-bottom: 2px solid #000; }
  .report-table tbody tr:nth-child(even) { background: #f1f5f9 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  .table-au thead { display: table-header-group; }
  .table-au tr { break-inside: avoid; }

  @page { margin: 14mm; size: A4; }
}

/* ========================== Responsive ========================== */

@media (max-width: 1200px) {
  .stat-grid { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
}

@media (max-width: 991px) {
  /* The navigation column is an off-canvas drawer here, so the header keeps
     only the toggle and the controls. Tighten the gaps, but keep the sign-out
     label: the control must never collapse to a bare, easy-to-miss icon. */
  #appNav { gap: .5rem; }
  #appNav .app-user { padding-left: .6rem; gap: .45rem; }
  #appNav .signout-form .btn { font-size: .8rem; padding: .25rem .7rem; }

  .page-head { align-items: flex-start; }
  .page-head__actions { width: 100%; }
  .page-head__actions .btn { flex: 1 1 auto; }

  .card-au__head { flex-direction: column; align-items: flex-start; gap: .6rem; }
  .card-au__head .form-select, .card-au__head .form-control { width: 100% !important; }
  .card-au__head form { width: 100%; }

  .toolbar { padding: .65rem .85rem; }
  .chip { font-size: .78rem; padding: .3rem .65rem; }

  .table-foot { flex-direction: column; align-items: flex-start; gap: .4rem; }
  .table-foot__stats { gap: .85rem; }

  /* Column widths below: wide tables scroll inside their wrapper. */
  .table-au { min-width: 640px; }
  .table-au--narrow { min-width: 480px; }

  .table-au thead th { position: static; }

  .btn-group-sm { flex-wrap: wrap; }

  .definition { grid-template-columns: 1fr; }
  .definition dt { padding-bottom: 0; border-bottom: 0; }
  .definition dd { padding-top: .15rem; }

  .letter-shell { padding: 1rem .5rem !important; }
  .report-shell { padding: 1rem .5rem; }
  .report-sheet, .letter-sheet { padding: 1.25rem 1rem; }
  .signatures { flex-direction: column; gap: 1.75rem; }
}

@media (max-width: 767px) {
  .page-wrap, .page-wrap-narrow { padding: 16px 14px 8px; }
  .page-foot { padding: 16px 14px 24px; }
  .app-topbar__inner { padding: .5rem 14px; }

  /* The wordmark is fluid (max-width: 100%), so it needs no width of its own
     here -- dropping the old fixed 300px keeps it as large as the panel allows
     instead of shrinking to a fixed size on every phone. */

  .stat-grid { grid-template-columns: repeat(2, 1fr); gap: .6rem; }
  .stat__value { font-size: 1.5rem; }
  .stat__label { font-size: .62rem; }
  .stat { padding: .7rem .8rem; }
  .stat__icon { font-size: 1.15rem; }

  .page-head__title { font-size: 1.15rem; }
  .pick-grid, .au-choice { grid-template-columns: 1fr; }
  .row > [class*='col-'] { margin-bottom: .75rem; }
  .row > [class*='col-']:last-child { margin-bottom: 0; }

  .btn { font-size: .82rem; padding: .45rem .8rem; }
  .page-head__actions .btn { width: 100%; }

  .auth-card { padding: 1.5rem 1.15rem; }
  .auth-screen { padding: 1rem .6rem; }
  .letter-tools .btn, .report-sheet .d-print-none .btn { flex: 1 1 auto; }
  .form-text { font-size: .74rem; }
  .chip { font-size: .75rem; }
}

@media (max-width: 400px) {
  .stat-grid { grid-template-columns: 1fr; }
}

/* Coarse pointers (phones/tablets) need larger tap targets. */
@media (pointer: coarse) {
  .icon-btn { width: 42px; height: 42px; }
  .btn { padding: .6rem 1rem; }
  .au-side__item { padding: .7rem .75rem; }
}

/* ========================== Motion ========================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}