/* ============================================================================
   premium.css — the 2026 refinement layer
   ----------------------------------------------------------------------------
   Loaded LAST, after components.css and layout.css.

   WHY A SEPARATE FILE RATHER THAN EDITS IN PLACE
   components.css is 184KB covering forty screens. A visual refresh spread
   through it is impossible to review, impossible to revert, and guarantees
   that the next person cannot tell which rules are the design system and
   which are one screen's exception. This file is the refresh, in one place:
   delete it and the application returns exactly to where it was.

   It only ever RAISES the finish — surfaces, edges, type, motion, focus. It
   changes no layout that a screen module measures, and adds no new class a
   module has to know about.

   ORDER
     1. Canvas & chrome        5. Data surfaces
     2. Type                   6. Motion & entrance
     3. Controls               7. Scrollbars & focus
     4. Cards & panels         8. Reduced motion
   ========================================================================== */

/* ============================================================================
   1. CANVAS & CHROME
   ========================================================================== */

/* Ambient light behind the whole application. Fixed, so it does not travel
   with the content and give away that it is a gradient. */
.app-shell {
  background-image: var(--canvas-glow);
  background-attachment: fixed;
}

/* ---- Sidebar ------------------------------------------------------------
   Its own surface, not the same white as the cards beside it. Without this
   the shell is one undifferentiated field and the eye has no anchor for
   "where am I" versus "what am I looking at". */
.sidebar {
  background: var(--rail-bg);
  border-right: 1px solid var(--rail-border);
  box-shadow: 1px 0 0 var(--hairline);
}

.sidebar-brand {
  padding: var(--space-5) var(--space-4) var(--space-4);
  border-bottom: 1px solid var(--divider);
}

/* The product monogram, when no school crest has been configured. A flat
   brand square is the most dated object in an interface; this is a lit tile
   with a real bevel. */
.sidebar-logo:not(.has-image) {
  background: var(--brand-fill);
  box-shadow: var(--edge-top), var(--shadow-brand);
  font-family: var(--font-hero);
  font-size: 1.25rem;
  font-weight: 400;
  line-height: 1;
  padding-bottom: 2px;
  border-radius: var(--radius-lg);
}

.sidebar-brand-name {
  font-size: var(--text-md);
  letter-spacing: var(--tracking-tight);
}

/* A wide school crest takes the row and the PRODUCT name sits beneath it as
   an eyebrow. components.css hides the name in wordmark mode on the reasoning
   that the artwork already says it — true of the school, not of the CRM, and
   with it hidden nothing on the screen said which product this was. */
.sidebar-brand.is-wordmark .sidebar-brand-name {
  display: block;
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--text-muted);
}
.sidebar-brand.is-wordmark .sidebar-brand-sub { display: none; }

.nav-group-label {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.13em;
  color: var(--text-faint);
  padding-bottom: var(--space-1);
}

/* Nav items: a soft pill, a brand-tinted active state with its own lit edge,
   and an icon that gains colour before the label does. */
.nav-item {
  padding: 7px var(--space-3);
  border-radius: var(--radius-md);
  transition: background-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
.nav-item .ic { transition: color var(--dur-2) var(--ease-out); }
.nav-item:hover { transform: translateX(1px); }

.nav-item.active {
  background: var(--brand-wash);
  color: var(--brand-ink);
  box-shadow: var(--edge-inner);
}
.nav-item.active .ic { color: var(--brand); }

/* The active rail marker: a short brand bar with a glow, so a glance finds
   the current screen without reading a single label. */
.nav-item::before {
  left: -2px;
  width: 3px;
  top: 22%;
  height: 56%;
  background: var(--brand);
  box-shadow: 0 0 10px 0 hsl(var(--brand-h) var(--brand-s) var(--brand-l) / 0.6);
  transition: transform var(--dur-3) var(--ease-spring);
}

.nav-badge {
  box-shadow: 0 1px 4px rgba(196, 32, 32, 0.4);
  font-variant-numeric: tabular-nums;
}

/* ---- Command palette trigger ---- */
.palette-trigger {
  background: var(--surface-inset);
  border-color: transparent;
  box-shadow: var(--shadow-ring);
  transition: var(--transition-colors), box-shadow var(--dur-2) var(--ease-out);
}
.palette-trigger:hover {
  background: var(--surface-elevated);
  box-shadow: var(--shadow-card);
}
.palette-kbd {
  padding: 2px 5px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border-subtle);
  background: var(--surface-elevated);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-faint);
  line-height: 1.4;
}

/* ---- User card ---- */
.sidebar-footer { border-top: 1px solid var(--divider); }
.user-card {
  border: 1px solid transparent;
  transition: var(--transition-colors), box-shadow var(--dur-2) var(--ease-out);
}
.user-card:hover {
  background: var(--surface-elevated);
  border-color: var(--border-subtle);
  box-shadow: var(--shadow-ring);
}

/* ---- Topbar --------------------------------------------------------------
   Translucent, so content scrolling underneath is visibly BEHIND it rather
   than stopping at an opaque strip. The border is a hairline plus a soft
   fade, which is what makes a sticky bar read as a layer. */
.topbar {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--glass-border);
  position: relative;
}
.topbar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 14px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(15, 23, 42, 0.045), transparent);
}
:root[data-theme="dark"] .topbar::after,
:root:not([data-theme="light"]) .topbar::after {
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.22), transparent);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .topbar::after {
    background: linear-gradient(to bottom, rgba(15, 23, 42, 0.045), transparent);
  }
}

/* The bell's unread dot should pulse once when it arrives, not for ever —
   a permanently animated badge is ignored within a day. */
.notif-badge {
  box-shadow: 0 0 0 2px var(--surface-elevated),
              0 0 10px hsl(0 70% 50% / 0.5);
}

/* ============================================================================
   2. TYPE — where the product gets a voice
   ========================================================================== */

/* Page titles in the display serif. This is the single change that does most
   of the work: it is on every screen, it costs nothing, and it is the reason
   the application no longer looks like a bootstrap admin theme. */
.page-title {
  font-family: var(--font-hero);
  font-weight: 400;
  font-size: clamp(1.75rem, 2.4vw, 2.125rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

.page-subtitle {
  font-size: var(--text-base);
  color: var(--text-secondary);
  max-width: 72ch;
}

.page-header {
  margin-bottom: var(--space-7);
  padding-bottom: var(--space-5);
  border-bottom: 1px solid var(--divider);
  align-items: center;
}

.card-title, .section-title {
  font-family: var(--font-hero);
  font-weight: 400;
  font-size: var(--text-lg);
  letter-spacing: -0.014em;
}
.section-title { font-size: var(--text-xl); }

/* Big figures — KPI tiles, hero stats — take the serif and tabular numerals.
   The serif has ONE weight, so size has to carry the emphasis that a bold
   would have carried: a semibold grotesque at 26px and a regular serif at
   26px do not read as equally important, and the figure is the whole point
   of the tile.

   Tabular is not decoration either: a column of counts that shifts sideways
   as the digits change is genuinely harder to compare down. */
.stat-value,
.kpi-value,
.metric-value {
  font-family: var(--font-hero);
  font-weight: 400;
  font-size: var(--text-3xl);
  letter-spacing: -0.026em;
  line-height: 0.94;
  font-variant-numeric: tabular-nums lining-nums;
  margin-top: var(--space-4);
}
.stat-value .unit { font-size: var(--text-lg); }
.stat-hero .stat-value { font-size: var(--text-4xl); }

/* Everything that is a number in a row stays on lining tabular figures. */
.num, td .num, .mono, .stat-delta { font-variant-numeric: tabular-nums lining-nums; }

::selection {
  background: hsl(var(--brand-h) var(--brand-s) var(--brand-l) / 0.22);
  color: var(--text-primary);
}

/* ============================================================================
   3. CONTROLS
   ========================================================================== */

.btn {
  border-radius: var(--radius-md);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.004em;
  transition: background var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
/* A press that moves. One pixel is enough — the point is that the control
   acknowledges the click before the network does. */
.btn:active:not(:disabled) { transform: translateY(1px); }

.btn-primary {
  background: var(--brand-fill);
  box-shadow: var(--edge-top), var(--shadow-brand);
  border-color: transparent;
}
.btn-primary:hover:not(:disabled) {
  background: var(--brand-fill-hover);
  box-shadow: var(--edge-top),
              0 1px 2px hsl(var(--brand-h) var(--brand-s) 30% / 0.32),
              0 10px 26px -8px hsl(var(--brand-h) var(--brand-s) 40% / 0.5);
}

.btn-secondary,
.btn-ghost,
.btn-icon {
  transition: background var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
.btn-secondary { box-shadow: var(--shadow-xs); }
.btn-secondary:hover:not(:disabled) { box-shadow: var(--shadow-sm); }

.btn-icon {
  border-radius: var(--radius-md);
}
.btn-icon:hover { background: var(--surface-hover); }

/* Inputs: a soft inset so a field reads as a well rather than a rectangle,
   and a focus state that is a ring and a colour, not just a colour. */
.input, .select, .textarea, select, textarea,
input[type="text"], input[type="email"], input[type="tel"],
input[type="number"], input[type="search"], input[type="date"],
input[type="time"], input[type="password"], input[type="url"] {
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-inset);
  transition: border-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out);
}
.input:hover:not(:disabled), .select:hover:not(:disabled),
.textarea:hover:not(:disabled) { border-color: var(--border-strong); }

.input:focus, .select:focus, .textarea:focus,
select:focus, textarea:focus,
input:focus-visible {
  box-shadow: var(--shadow-inset), var(--shadow-focus);
}

.field-label {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Pills and filters */
.filter-pill {
  transition: var(--transition-colors), box-shadow var(--dur-2) var(--ease-out);
}
.filter-pill:hover { box-shadow: var(--shadow-xs); }
.filter-pill.active {
  background: var(--brand-fill);
  border-color: transparent;
  color: var(--on-brand);
  box-shadow: var(--edge-top), var(--shadow-brand);
}

.view-toggle {
  background: var(--surface-inset);
  border-color: transparent;
  box-shadow: var(--shadow-ring);
  padding: 2px;
  border-radius: var(--radius-md);
  gap: 2px;
}
.view-btn {
  background: transparent;
  border-radius: var(--radius-sm);
  transition: var(--transition-colors), box-shadow var(--dur-2) var(--ease-out);
}
.view-btn + .view-btn { border-left: 0; }
.view-btn.active {
  background: var(--surface-elevated);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}

/* ============================================================================
   4. CARDS & PANELS
   ========================================================================== */

.card, .panel, .widget {
  border-color: transparent;
  box-shadow: var(--shadow-card);
  transition: box-shadow var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-out);
}
.card-hoverable:hover, .card-hoverable:focus-within {
  box-shadow: var(--shadow-float);
  border-color: transparent;
}

.card-header { padding-bottom: var(--space-4); border-bottom-color: var(--hairline); }

/* ---- KPI tiles ----------------------------------------------------------
   The figure is the object; everything else is annotation. So: the label
   recedes to a small tracked cap, the number takes the display serif at
   hero size, and the tile itself gets a faint brand wash that pools at the
   top-right behind the icon — enough to separate six tiles from the white
   page without drawing six boxes. */
.stat {
  border-color: transparent;
  box-shadow: var(--shadow-card);
  padding: var(--space-5) var(--space-5) var(--space-5);
  transition: box-shadow var(--dur-3) var(--ease-out),
              transform var(--dur-3) var(--ease-out);
}
.stat::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(60% 90% at 100% 0%,
    hsl(var(--brand-h) var(--brand-s) var(--brand-l) / 0.07), transparent 70%);
}
.stat > * { position: relative; z-index: 1; }

/* A two-line label ("Applications submitted") must not push its figure down
   while the tile beside it keeps a one-line label — the whole value of a KPI
   row is that six numbers sit on one line and can be read across. The icon
   already sets a 30px floor; this reserves the second line of label too. */
.stat-head { min-height: 34px; align-items: flex-start; }
.stat-label { line-height: 1.35; }

.stat:hover {
  border-color: transparent;
  box-shadow: var(--shadow-float);
  transform: translateY(-2px);
}

.stat-label { letter-spacing: 0.11em; color: var(--text-muted); }

/* The icon gets a tile of its own. At 0.7 opacity on the bare surface it
   read as a rendering artefact rather than as an icon. */
.stat-icon {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--brand-soft);
  color: var(--brand);
  opacity: 1;
}
.stat-icon .ic { width: 15px; height: 15px; }

.stat-delta { font-variant-numeric: tabular-nums; }

/* Modals, drawers and menus float highest and get the full treatment. */
.modal, .drawer, .sheet, .menu, .popover, .dropdown-menu {
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-float);
}
.menu, .popover, .dropdown-menu {
  border-radius: var(--radius-lg);
  background: var(--glass-bg-deep);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}
/* Both of these are full-height panels anchored to the RIGHT viewport edge.
   Rounding all four corners puts two rounded corners against the edge of the
   screen, which shows as a sliver of page bleeding through the top-right and
   bottom-right of the panel. Only the two corners that face the page are
   rounded. */
.drawer, .sheet { border-radius: var(--radius-xl) 0 0 var(--radius-xl); }

.modal-backdrop, .drawer-backdrop, .scrim {
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

/* Empty states earn a little air — they are the first thing a new school
   sees on every screen, and a bare line of grey text reads as a fault. */
.empty-state {
  padding-block: var(--space-12);
}
.empty-state-title {
  font-family: var(--font-hero);
  font-weight: 400;
  font-size: var(--text-xl);
  letter-spacing: -0.015em;
}

/* ============================================================================
   5. DATA SURFACES
   ========================================================================== */

.table-wrap {
  border-color: transparent;
  box-shadow: var(--shadow-card);
}
/* A table nested inside a card must not draw a second card around itself —
   components.css already strips the border for that case; the shadow has to
   go with it or the seam shows as a grey halo inside the panel. */
.card > .table-wrap,
.panel > .table-wrap { box-shadow: none; }

/* Filters that stay put while their results scroll underneath. Opaque, they
   read as a lid the rows disappear behind; translucent, the rows are visibly
   passing under a layer, which is what is actually happening. */
.sticky-top {
  background: var(--glass-bg-deep);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}

.data-table thead th, table thead th {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--surface-raised);
}

.data-table tbody tr, table tbody tr {
  transition: background-color var(--dur-1) var(--ease-out);
}
.data-table tbody tr:hover, table tbody tr:hover {
  background: var(--surface-hover);
}

/* Badges: hairline ring instead of a solid border, so twelve of them in a
   column read as a set rather than as twelve separate boxes. */
.badge, .chip, .tag, .pill {
  border-radius: var(--radius-full);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.01em;
}

/* Meters get the lit edge, and nothing else. Their COLOUR is load-bearing —
   `.meter-fill.is-full` and `.is-over` step to warning and danger — so a
   blanket brand fill here would repaint the one signal a capacity bar
   exists to give. */
.progress-fill, .meter-fill, .bar-fill {
  box-shadow: var(--edge-top);
}

/* ---- Tabs ----
   The selected tab is marked by the BRAND, not the accent. Gold under a
   selected tab and brand green everywhere else meant the one control that
   says "you are here" was the only one not speaking the school's colour. */
.tabs {
  gap: var(--space-1);
  border-bottom-color: var(--hairline);
}
.tab { border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.tab:hover { background: var(--surface-hover); }
.tab[aria-selected="true"], .tab.is-active {
  border-bottom-color: var(--brand);
  color: var(--brand-ink);
}
.tab[aria-selected="true"]::after, .tab.is-active::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: var(--brand);
  box-shadow: 0 0 10px hsl(var(--brand-h) var(--brand-s) var(--brand-l) / 0.55);
}

/* ---- Avatars ----
   A hairline ring and a lit top edge instead of a grey border, so a row of
   them reads as a set of tokens rather than as bordered boxes.

   The BACKGROUND is deliberately untouched. `.avatar-brand` and its
   siblings set a solid fill and white text; a blanket background here has
   the same specificity, wins on source order, and paints white letters onto
   a pale wash — every initial in the WhatsApp and inbox lists vanished. */
.avatar {
  border: 0;
  box-shadow: 0 0 0 1px var(--hairline), var(--edge-top);
}

/* ---- Toasts ----
   These land over whatever the user was reading, so they get the strongest
   elevation in the system and a translucent ground. */
.toast {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  background: var(--glass-bg-deep);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}

/* ---- Topbar controls ----
   The academic-year select is the one control in the topbar and it looked
   like a form field left behind in the chrome. As a quiet pill it reads as
   a scope switch, which is what it is. */
#year-switcher .select {
  border-color: transparent;
  background: var(--surface-inset);
  box-shadow: var(--shadow-ring);
  border-radius: var(--radius-full);
  padding-left: var(--space-4);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  transition: var(--transition-colors), box-shadow var(--dur-2) var(--ease-out);
}
#year-switcher .select:hover {
  background: var(--surface-elevated);
  box-shadow: var(--shadow-card);
}

/* ============================================================================
   6. MOTION & ENTRANCE
   ----------------------------------------------------------------------------
   ONE entrance, on the routed view. Not per-card animation: forty screens
   each animating their own contents is a product that feels slow, because
   every navigation costs the user the length of the longest animation before
   they can read anything.
   ========================================================================== */

@keyframes prm-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@keyframes prm-fade { from { opacity: 0; } to { opacity: 1; } }

#view > * {
  animation: prm-rise var(--dur-4) var(--ease-out) both;
}

/* A screen that owns the viewport height (the inbox, the WhatsApp chat —
   see `page-fill` in CLAUDE.md §10) gets the fade WITHOUT the translate. A
   transform on an element makes it the containing block for every fixed
   descendant inside it, and both of those screens position things against
   the viewport. Fading costs nothing and breaks nothing. */
#view > .page-fill {
  animation-name: prm-fade;
}

/* A refreshing screen dims very slightly instead of being replaced by a
   skeleton — see the "fetch first, swap once" rule in CLAUDE.md §10. */
.is-refreshing {
  opacity: 0.62;
  transition: opacity var(--dur-2) var(--ease-out);
}

/* ============================================================================
   7. SCROLLBARS & FOCUS
   ========================================================================== */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: var(--radius-full);
  border: 3px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); background-clip: content-box; }
*::-webkit-scrollbar-corner { background: transparent; }

/* One focus treatment everywhere, and it is a RING, not an outline that gets
   clipped by the first `overflow: hidden` ancestor it meets. */
:where(a, button, [role="button"], [tabindex]):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ============================================================================
   8. REDUCED MOTION
   The duration tokens already collapse to 0ms; these are the rules that carry
   their own timing or a transform, which the tokens cannot reach.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  #view > * { animation: none; }
  .nav-item:hover,
  .stat-card:hover,
  .kpi-card:hover,
  .btn:active:not(:disabled) { transform: none; }
}

/* Translucency is a finish, not information. Where the platform says the
   user wants solid, high-contrast chrome, give them exactly that. */
@media (forced-colors: active) {
  .topbar, .menu, .popover, .dropdown-menu {
    background: Canvas;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .app-shell { background-image: none; }
}
