/* ==========================================================================
   DIGISCHOOL: App Shell
   Top bar + content region. Navigation lives in an off-canvas drawer at every
   width — there is no permanent sidebar. The dashboards carry the primary
   navigation as a module grid; the drawer is the same permission-filtered
   list, reachable from the header hamburger on any page.

   Shell state lives on <html> as data attributes, so CSS can react to it
   without JS touching individual element styles:
     data-theme="light" | "dark"
   Drawer visibility is a class on <body>: .ec-drawer-open
   ========================================================================== */

/* --- Reset / base -------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

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

body {
  margin: 0;
  font-family: var(--ec-font-sans);
  font-size: var(--ec-text-md);
  line-height: var(--ec-leading-normal);
  color: var(--ec-text);
  background-color: var(--ec-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings take the display face and the sheet's negative tracking. The
   sizes stay wherever the component that owns the heading puts them — this
   sets the voice, not the scale. */
h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--ec-font-display);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-leading-tight);
  letter-spacing: var(--ec-headline-md-track);
  color: var(--ec-text);
}

p { margin: 0; }

a {
  color: var(--ec-accent);
  text-decoration: none;
  transition: color var(--ec-transition-fast);
}

a:hover { color: var(--ec-accent-hover); }

ul, ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

button {
  font: inherit;
  color: inherit;
}

img, svg { vertical-align: middle; }

/* Numbers must not jitter when they update, and columns of figures must
   align. Applied wherever a value is rendered.

   cv05 joins tnum because the design sheet asks for it by name: it is
   Inter's straight-tailed lowercase l, which is what stops "l" being read as
   "1" in a matriculation number or a class code. */
.ec-num {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'tnum', 'cv05';
}

/* The sheet's tabular-numeric role, whole: the figure style for marks, money,
   dates, clock times and student IDs. .ec-num is the feature switch on its
   own, for a figure that must keep the size it already has. */
.ec-figure {
  font-size: var(--ec-tabular);
  font-weight: var(--ec-weight-medium);
  line-height: var(--ec-tabular-lh);
  letter-spacing: var(--ec-tabular-track);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'tnum', 'cv05';
}

/* One consistent focus ring everywhere, keyboard-only.

   The colour is --ec-focus rather than --ec-accent directly. The accent is
   now the brand green, and the ring is drawn 2px outside the element, so on
   every ordinary surface it contrasts fine — but anything *painted* the
   accent (the primary button, the active sidebar row, an active chip) would
   be ringed in its own fill. The block at the foot of this file re-keys
   --ec-focus on exactly those elements. */
:focus-visible {
  outline: 2px solid var(--ec-focus);
  outline-offset: 2px;
  border-radius: var(--ec-radius-sm);
}

.ec-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A <table> ignores the `width: 1px` above, and the transcript tables that
   sit beside every dashboard chart therefore laid out at their natural
   ~390px. Being absolutely positioned they are outside the content column's
   own clipping, so they dragged the document 53px wider than the phone it
   was on: every signed-in dashboard — the school's, the teacher's, the
   parent's, the pupil's — scrolled sideways because of a table nobody can
   see.

   Two declarations, and both are needed. `white-space` is the one that
   matters: the nowrap above cascades into the <caption>, whose min-content
   width is then the whole unbroken sentence, and a table can never be
   narrower than its own min-content — which is why `width: 1px` was
   ignored. Letting it wrap, `table-layout: fixed` makes the declared width
   apply. Nothing about what a screen reader announces changes. */
table.ec-sr-only,
table.ec-sr-only caption {
  white-space: normal;
}

table.ec-sr-only {
  max-width: 1px;
  table-layout: fixed;
}

/* The attribute means "not relevant", and a component that sets its own
   display must not outrank it. Without this, a .ec-badge or a .ec-btn that
   script hides with `hidden` keeps its inline-flex and stays on screen —
   which is why the photograph screen's "chosen and not yet saved" counter
   was visible before anything had been chosen. Normalize's rule; the
   marketing sheet already carries its own copy. */
[hidden] { display: none !important; }

/* Skip link: first tab stop, hidden until focused. */
.ec-skip-link {
  position: absolute;
  top: var(--ec-space-2);
  left: var(--ec-space-2);
  z-index: 2000;
  padding: var(--ec-space-2) var(--ec-space-4);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  border-radius: var(--ec-radius-sm);
  transform: translateY(-200%);
  transition: transform var(--ec-transition);
}

.ec-skip-link:focus {
  color: var(--ec-accent-on);
  transform: translateY(0);
}

/* --- Scrollbars ---------------------------------------------------------- */

.ec-scroll {
  scrollbar-width: thin;
  scrollbar-color: var(--ec-border-strong) transparent;
}

.ec-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
.ec-scroll::-webkit-scrollbar-track { background: transparent; }

.ec-scroll::-webkit-scrollbar-thumb {
  background: var(--ec-border-strong);
  border-radius: var(--ec-radius-full);
}

.ec-scroll::-webkit-scrollbar-thumb:hover { background: var(--ec-text-subtle); }

/* ==========================================================================
   NAVIGATION RAIL
   One element, two behaviours. Below 1200px it is the off-canvas drawer it
   has always been: closed by default, slid in over the page by
   .ec-drawer-open on <body>. From 1200px it is pinned open beside the
   content and the scrim, the toggle and the transform all stand down.

   1200 and not 1024: the rail is 260px, and the dashboards behind it are
   three columns wide. At 1024 the pinned rail left 764px for those three,
   which is narrower than the two-column layout the same page uses on a
   tablet — the rail has to earn its width out of surplus, not out of the
   table.
   ========================================================================== */

.ec-sidebar {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: var(--ec-z-sidebar);
  display: flex;
  flex-direction: column;
  width: var(--ec-sidebar-w);
  background: var(--ec-sidebar-bg);
  border-inline-end: 1px solid var(--ec-sidebar-border);
  transform: translateX(-100%);
  transition: transform var(--ec-transition);
}

.ec-drawer-open .ec-sidebar {
  transform: translateX(0);
  box-shadow: var(--ec-shadow-lg);
}

.ec-sidebar__brand {
  display: flex;
  flex-shrink: 0;
  gap: var(--ec-space-3);
  align-items: center;
  min-height: var(--ec-header-h);
  padding: var(--ec-space-3) var(--ec-space-4);
}

/* The mark's tile.
   Unpainted: a surface with a hairline, not a filled swatch. brand_mark()
   now returns Digisol's two-colour mark — a cyan bracket closed by an indigo
   D — and a gradient behind it would flatten both fills into a silhouette.
   Because it is stated in role tokens it is correct in all four places it
   appears: lifted navy inside the chrome, white on the marketing header, and
   the dark-theme equivalents of each. */
.ec-sidebar__logo {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 6px;
  color: var(--ec-accent);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-sm);
}

/* The school's own identity, two lines: its name, and what kind of school it
   is. Both truncate rather than wrap — a rail whose head is three lines tall
   on one school and one on another is not a fixed piece of chrome. */
.ec-sidebar__ident {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: var(--ec-leading-tight);
}

.ec-sidebar__school {
  overflow: hidden;
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-md);
  font-weight: var(--ec-weight-bold);
  letter-spacing: var(--ec-headline-md-track);
  color: var(--ec-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-sidebar__kind {
  overflow: hidden;
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-sidebar-text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-sidebar__wordmark {
  overflow: hidden;
  font-family: var(--ec-font-display);
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-bold);
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.ec-sidebar__wordmark span { color: var(--ec-accent); }

/* The academic-year card under the head. A tinted tray rather than another
   row of the nav, because it is not somewhere you can go. */
.ec-sidebar__year {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  padding: var(--ec-space-2) var(--ec-space-3);
  margin: 0 var(--ec-space-3) var(--ec-space-1);
  background: var(--ec-sidebar-hover-bg);
  border-radius: var(--ec-radius-md);
}

.ec-sidebar__year-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.ec-sidebar__year-label {
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-medium);
  line-height: var(--ec-label-sm-lh);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-sidebar-text-muted);
}

.ec-sidebar__year-value {
  font-size: var(--ec-body-sm);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-body-sm-lh);
  color: var(--ec-accent);
  font-variant-numeric: tabular-nums lining-nums;
}

.ec-sidebar__nav {
  flex: 1;
  padding: var(--ec-space-3) var(--ec-space-3) var(--ec-space-6);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* --- Rail foot: the connection --------------------------------------- */

.ec-sidebar__foot {
  flex-shrink: 0;
  padding: var(--ec-space-3);
}

.ec-railstat {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-2) var(--ec-space-3);
  background: var(--ec-sidebar-hover-bg);
  border-radius: var(--ec-radius-md);
}

.ec-railstat__dot {
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  background: var(--ec-success);
  border-radius: var(--ec-radius-full);
}

.ec-railstat__name {
  flex: 1;
  overflow: hidden;
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-railstat__state {
  flex-shrink: 0;
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-medium);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-sidebar-text-muted);
}

/* Painted by assets/js/offline.js when the signal goes. */
.ec-railstat.is-offline .ec-railstat__dot { background: var(--ec-text-subtle); }
.ec-railstat.is-waiting .ec-railstat__dot { background: var(--ec-warning); }

/* One collapsible group per nav section. See the note in includes/layout.php:
   sixty-eight items across nine sections made the drawer a single scroll of
   everything the school owns. */
.ec-nav-group > summary.ec-nav-section {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  cursor: pointer;
  border-radius: var(--ec-radius-md);

  /* Both are needed: Safari still draws the triangle from its own pseudo
     element rather than from list-style. */
  list-style: none;
  -webkit-user-select: none;
  user-select: none;
}

.ec-nav-group > summary.ec-nav-section::-webkit-details-marker { display: none; }

.ec-nav-group > summary.ec-nav-section:hover {
  color: var(--ec-sidebar-text);
  background: var(--ec-sidebar-hover-bg);
}

.ec-nav-section__label { flex: 1; }

/* The count sits on the heading as well as on the link, so a folded section
   still reports what is waiting inside it. Reset the uppercase letter-spacing
   the heading carries, or the digits are drawn apart. */
.ec-nav-section__badge {
  letter-spacing: normal;
  text-transform: none;
}

.ec-nav-group[open] > summary .ec-nav-link__chevron { transform: rotate(90deg); }

/* Sentence case at label size. Nine headings in tracked 10px capitals read as
   one grey stripe; at 12px in the case they are written in, "Finance & HR"
   and "Communication" can be told apart at a glance. */
.ec-nav-section {
  padding: var(--ec-space-4) var(--ec-space-3) var(--ec-space-2);
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-sidebar-section);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* The heading is inside a <details> now, so :first-child no longer matches it
   against the nav — it matches against its own group, where it is always
   first. The group is what has to be asked. */
.ec-nav-group:first-of-type > .ec-nav-section { padding-top: 0; }

.ec-nav-link {
  position: relative;
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-2) var(--ec-space-3);
  margin-block-end: 2px;
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-sidebar-text);
  cursor: pointer;
  border-radius: var(--ec-radius-md);
  transition: background var(--ec-transition-fast), color var(--ec-transition-fast);
}

.ec-nav-link:hover {
  color: var(--ec-sidebar-text);
  background: var(--ec-sidebar-hover-bg);
}

/* Sign out is a <button> in a form (it POSTs), and a button centres its
   text by default, so its label sat in the middle of the row while every
   link above it sat left. */
button.ec-nav-link { text-align: start; }

/* A wash of the accent and a bar down the leading edge — the rule, Digisol's
   one ornament — rather than a solid cyan slab. The slab was the loudest thing
   on every screen, louder than the page it pointed at. The console rails
   already used a wash; now all four agree, and their ::before colours (below)
   finally have a base rule to colour. */
.ec-nav-link.is-active {
  color: var(--ec-sidebar-active-text);
  background: var(--ec-sidebar-active-bg);
}

.ec-nav-link.is-active::before {
  position: absolute;
  inset-block: 8px;
  inset-inline-start: 0;
  width: 3px;
  content: '';
  background: var(--ec-accent);
  border-radius: 0 var(--ec-radius-full) var(--ec-radius-full) 0;
}

.ec-nav-link.is-active .ec-nav-link__icon { opacity: 1; color: var(--ec-accent); }

.ec-nav-link__icon {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  opacity: 0.75;
}

/* Wraps rather than clips: "Inscriptions et réinscriptions" is longer than
   the rail, and a label cut to "Inscriptions et ré" names nothing. */
.ec-nav-link__label {
  flex: 1;
  min-width: 0;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.ec-nav-link__chevron {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  opacity: 0.6;
  transition: transform var(--ec-transition);
}

.ec-nav-link[aria-expanded='true'] .ec-nav-link__chevron {
  transform: rotate(90deg);
}

.ec-nav-link__badge {
  flex-shrink: 0;
  padding: 1px 7px;
  font-size: var(--ec-text-2xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent-on);
  background: var(--ec-danger);
  border-radius: var(--ec-radius-full);
}

/* Submenu: height animated via max-height so it works without JS measuring */
.ec-nav-sub {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--ec-transition);
}

.ec-nav-sub.is-open { grid-template-rows: 1fr; }

.ec-nav-sub > div { overflow: hidden; }

.ec-nav-sub__link {
  display: block;
  padding: var(--ec-space-2) var(--ec-space-3) var(--ec-space-2) calc(var(--ec-space-3) + 30px);
  font-size: var(--ec-text-sm);
  color: var(--ec-sidebar-text-muted);
  border-radius: var(--ec-radius-sm);
  transition: color var(--ec-transition-fast), background var(--ec-transition-fast);
}

.ec-nav-sub__link:hover {
  color: var(--ec-sidebar-text);
  background: var(--ec-sidebar-hover-bg);
}

.ec-nav-sub__link.is-active {
  font-weight: var(--ec-weight-medium);
  color: var(--ec-accent);
}

/* ==========================================================================
   MAIN COLUMN
   ========================================================================== */

.ec-main {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

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

.ec-header {
  position: sticky;
  top: 0;
  z-index: var(--ec-z-header);
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  height: var(--ec-header-h);
  padding-inline: var(--ec-page-margin);
  background: var(--ec-header-bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-block-end: 1px solid var(--ec-header-border);
}

.ec-header__spacer { flex: 1; }

/* The header's search: a white pill on the canvas, the same shape as the
   header's other controls. The accent border on focus says where the caret
   is. */
/* It takes the room the year pill left, up to a comfortable line length,
   and has a floor so a crowded header narrows the spacer, never the field:
   squeezed below its input's own minimum, it used to draw over the pill
   beside it. */
.ec-header .ec-search {
  flex: 1 1 360px;
  min-width: 7rem;
  max-width: 520px;
}

.ec-header .ec-search .ec-input {
  height: 40px;
  background: var(--ec-surface);
  border-color: var(--ec-border);
  border-radius: var(--ec-radius-full);
}

.ec-header .ec-search .ec-input:hover { border-color: var(--ec-border-strong); }
.ec-header .ec-search .ec-input:focus { border-color: var(--ec-accent); }

.ec-header__divider {
  width: 1px;
  height: 24px;
  background: var(--ec-border);
}

/* The period pills: which academic year, and where inside it. A tinted tray
   with an icon, not a status badge — nothing here is a state that can go
   wrong, so nothing here is coloured like one. The second takes the accent
   tint because it is the one that changes six times a year. */
.ec-ctxpill {
  display: inline-flex;
  flex-shrink: 0;
  gap: var(--ec-space-2);
  align-items: center;
  height: 30px;
  padding-inline: var(--ec-space-3);
  font-size: var(--ec-label-md);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-md-track);
  color: var(--ec-text);
  white-space: nowrap;
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-md);
}

.ec-ctxpill svg {
  flex-shrink: 0;
  color: var(--ec-text-muted);
}

.ec-ctxpill--accent {
  color: var(--ec-primary-700);
  background: var(--ec-accent-soft);
}

.ec-ctxpill--accent svg { color: var(--ec-accent); }

[data-theme='dark'] .ec-ctxpill--accent { color: var(--ec-primary-300); }

/* Square icon button used across header, cards, and tables */
.ec-icon-btn {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  color: var(--ec-text-muted);
  cursor: pointer;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--ec-radius-md);
  transition: background var(--ec-transition-fast), color var(--ec-transition-fast);
}

.ec-icon-btn:hover {
  color: var(--ec-text);
  background: var(--ec-surface-hover);
}

.ec-icon-btn--bordered {
  background: var(--ec-surface);
  border-color: var(--ec-border);
}

.ec-icon-btn__dot {
  position: absolute;
  top: 6px;
  right: 6px;
  display: grid;
  place-items: center;
  min-width: 16px;
  height: 16px;
  padding-inline: 4px;
  font-size: 10px;
  font-weight: var(--ec-weight-bold);
  line-height: 1;
  /* Ink on a filled chip, so it follows the fill. --ec-danger is a deep red
     on the light canvas and a lifted salmon on the band and in dark mode;
     white is 7.0:1 on the first and 2.0:1 on the second. */
  color: var(--ec-text-inverse);
  background: var(--ec-danger);
  border: 2px solid var(--ec-surface);
  border-radius: var(--ec-radius-full);
}

.ec-has-dot { position: relative; }

/* Hamburger: the drawer is the navigation at every width, so its toggle is
   always present. */
.ec-header__toggle { display: grid; }

/* Brand in the header — the drawer is closed most of the time, so the
   wordmark cannot live in it any more. */
.ec-header__brand {
  display: flex;
  flex-shrink: 0;
  gap: var(--ec-space-2);
  align-items: center;
  margin-inline-end: var(--ec-space-2);
}

/* --- Content ------------------------------------------------------------- */

.ec-content {
  flex: 1;
  width: 100%;
  max-width: var(--ec-content-max);
  padding: var(--ec-page-gutter) var(--ec-page-margin) var(--ec-space-12);
  margin-inline: auto;
}

/* Room for the assistant button, which is fixed to the bottom-right corner
   of every signed-in screen and therefore sits on top of whatever the page
   ends with. It was covering the last control on any page whose final row
   reaches the right edge — the Save on the photograph screen, the last file
   field, the last table row's action. The button is ~46px tall over a 14px
   offset; 6rem clears it with a margin, and :has() scopes the cost to the
   pages that actually carry the widget. */
body:has(.ec-ask) .ec-content { padding-block-end: 6rem; }

.ec-page-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-4);
  align-items: flex-start;
  justify-content: space-between;
  margin-block-end: var(--ec-space-6);
}

.ec-page-head__title {
  font-size: var(--ec-text-xl);
  letter-spacing: -0.02em;
}

.ec-page-head__sub {
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-text-md);
  color: var(--ec-text-muted);
}

.ec-page-head__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
}

.ec-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
  margin-block-end: var(--ec-space-2);
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-breadcrumb a { color: var(--ec-text-muted); }
.ec-breadcrumb a:hover { color: var(--ec-accent); }
.ec-breadcrumb__sep { color: var(--ec-text-subtle); }

/* --- Back ---------------------------------------------------------------- */

/*
 * Above the page head on every signed-in screen. See back_bar() in
 * includes/helpers.php for why it is not left to the browser: the manifest
 * ships `display: standalone`, and an installed window has no chrome to put a
 * back button in.
 *
 * Pulled left by its own inline padding so the label lines up with the
 * breadcrumb underneath it rather than sitting a few pixels in from everything
 * else on the page.
 */
.ec-backbar {
  margin-block-end: var(--ec-space-3);
}

.ec-backbar .ec-btn {
  padding-inline: var(--ec-space-2);
  margin-inline-start: calc(var(--ec-space-2) * -1);
}

/* --- Grid helpers -------------------------------------------------------- */
/* Bootstrap's grid handles most layout; these cover the two patterns it does
   awkwardly: equal auto-fit card rows and the dashboard's 12-col regions. */

.ec-grid {
  display: grid;
  gap: var(--ec-space-4);
}

.ec-grid--stats {
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}

/* Two up on a phone, not one.

   auto-fit against a 230px floor gives a single column below about 480px,
   which turns four figures into four full screens of scrolling — and the
   sheet's Android layout is explicit that they sit two by two. The floor is
   dropped rather than the track count fixed, so a 320px handset still gets
   two columns and nothing overflows. */
@media (max-width: 767.98px) {
  .ec-grid--stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Two panels side by side, collapsing to one column when there is no room.

   The term summary pairs first-three with last-three, and the statistics block
   with the term details; each pair is one comparison and only reads as one
   while the two halves sit level with each other. Without this modifier the
   bare .ec-grid above sets `display: grid` and no columns at all, so both
   pairs stacked full width and the comparison was lost.

   320px, because a panel holding three names and two figures stops being a
   table below that. */
.ec-grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}

/* Cards carrying a paragraph of explanation, not a number. Wider than the
   stats grid so the text does not set in a column three words across. */
.ec-grid--cards {
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

/* ==========================================================================
   DASHBOARD: the work, and a column beside it
   The "Academix" layout (docs/REDESIGN_PLAN.md, Part 2): figures, charts and
   tables in the main column; what falls due, the register, what is left to
   do and the notice board in a column on the right. Opened in
   includes/dashboards/_head.php and closed in _foot.php.

   One column below 1200px, where the right column's cards follow the work
   in a two-up grid and then stack on a phone. A view with nothing for the
   right column renders no aside, and :has() gives the main column the row.
   ========================================================================== */

.ec-dash {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--ec-space-6);
  align-items: start;
  margin-block-end: var(--ec-space-6);
}

/* The main column sizes its pairs to itself, not to the window: at 1440 it is
   about 760px, which is a tablet's width, and two charts side by side there
   are two cramped charts. */
.ec-dash__main {
  min-width: 0;
  container-type: inline-size;
}

.ec-dash__rail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: var(--ec-space-4);
  align-content: start;
  min-width: 0;
}

/* The gap is the column's; a card's own bottom margin would double it. */
.ec-dash__rail > .ec-card { margin: 0 !important; }

/* Side by side only when there is something on both sides. A view whose
   cards all belong to the right column (Attendance & discipline, Deadlines
   & notices) draws no main column, and its cards take the width two up. */
@media (min-width: 1200px) {
  .ec-dash:has(> .ec-dash__main):has(> .ec-dash__rail) {
    grid-template-columns: minmax(0, 1fr) 340px;
  }

  .ec-dash__main + .ec-dash__rail { grid-template-columns: minmax(0, 1fr); }
}

@media (min-width: 1600px) {
  .ec-dash:has(> .ec-dash__main):has(> .ec-dash__rail) {
    grid-template-columns: minmax(0, 1fr) 380px;
  }
}

/* Four figures in a row inside the main column, not three and one left over. */
.ec-dash__main .ec-grid--stats {
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}

@media (max-width: 767.98px) {
  .ec-dash__main .ec-grid--stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A Bootstrap pair (col-xl-7 + col-xl-5) splits by the WINDOW's width, which
   in the main column is the wrong question. Under 900px of column the pair
   stacks; the grade spread and the marking table each get the full width. */
@container (max-width: 900px) {
  .ec-dash__main .row > [class*='col-xl-'] {
    flex: 0 0 100%;
    max-width: 100%;
  }
}

/* ==========================================================================
   DRAWER SCRIM  (all widths)
   ========================================================================== */

.ec-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--ec-z-overlay);
  background: var(--ec-overlay);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--ec-transition), visibility var(--ec-transition);
}

.ec-drawer-open .ec-overlay {
  opacity: 1;
  visibility: visible;
}

/* Body scroll lock while the drawer is open */
.ec-drawer-open { overflow: hidden; }

/* ==========================================================================
   THE PINNED RAIL  (1200px and up)

   Everything above describes the drawer. This block is what turns it into a
   fixed left-hand column: the transform stands down, the main column is
   indented by the rail's width, and the three controls that exist only to
   open and close a drawer — the hamburger, the scrim, the header's own copy
   of the brand — go away, because there is no drawer to open and the rail
   already names where you are.
   ========================================================================== */

@media (min-width: 1200px) {
  .ec-sidebar {
    transform: none;
    box-shadow: none;
  }

  .ec-main { margin-inline-start: var(--ec-sidebar-w); }

  .ec-header__toggle,
  .ec-header__brand { display: none; }

  /* The scrim never paints, so a stray .ec-drawer-open left on <body> by a
     resize cannot dim a page whose rail is already open. */
  .ec-overlay { display: none; }

  .ec-drawer-open { overflow: auto; }
}

/* Tablet: 24px margin, 16px gutter. */
@media (max-width: 1199.98px) {
  :root {
    --ec-page-margin: 1.5rem;
    --ec-page-gutter: 1rem;
  }
}

/* Crowded before it is narrow: the search box, the period pills and the
   switchers all want the same strip. The period pills go first — they are
   context, and the page under them repeats it. At 1200, not 992: below 1200
   the rail becomes a drawer and its toggle and the brand join the header, and
   at 1024 that pushed an admin's profile button 20px off the screen. */
@media (max-width: 1199.98px) {
  .ec-hide-md { display: none !important; }
}

/* Phones: 16px margin, 12px gutter, and the headline steps drop to the
   sheet's mobile sizes so a long course title stops wrapping mid-word.
   768 and not 576: the sheet's own phone breakpoint, and the width at which
   the family portal becomes the Android layout. */
@media (max-width: 767.98px) {
  :root {
    --ec-header-h: 56px;
    --ec-page-margin: 1rem;
    --ec-page-gutter: 0.75rem;

    --ec-headline-xl:       1.625rem;
    --ec-headline-xl-lh:    2.125rem;
    --ec-headline-xl-track: -0.01em;
    --ec-headline-lg:       1.25rem;
    --ec-headline-lg-lh:    1.75rem;
    --ec-headline-lg-track: -0.01em;
  }

  .ec-content {
    padding: var(--ec-page-gutter) var(--ec-page-margin) var(--ec-space-8);
  }

  .ec-header { padding-inline: var(--ec-page-margin); }

  .ec-page-head__title { font-size: var(--ec-headline-lg); }

  /* Header gets crowded fast; non-essential controls drop out. */
  .ec-hide-sm { display: none !important; }
}

/* The narrowest phones still in the field — a 320px SE, a folded Galaxy.

   Everything the 768px rule could drop is already gone by here, and what
   remains is six controls that cannot shrink: the hamburger, the brand, the
   language segment, the theme toggle, the bell and the avatar. Their widths
   came to 329px inside a 305px row, so the profile button hung 61px off the
   right-hand edge of every signed-in screen on those handsets — and it is
   the control that holds Sign out.

   Nothing is removed. The language switch in particular stays: this is a
   bilingual product sold into a bilingual country, and an office that works
   in French should not lose the switch because of the phone it owns. What
   gives instead is the air between the controls and around them, which is
   what there is too much of at this width. */
@media (max-width: 400px) {
  :root { --ec-page-margin: 0.75rem; }

  .ec-header { gap: var(--ec-space-2); }

  .ec-header .ec-icon-btn { width: 30px; height: 30px; }

  /* Scoped to the header so it outranks the base rule, which lives in
     components.css and therefore loads after this file. */
  .ec-header .ec-langseg__on,
  .ec-header .ec-langseg__off { padding-inline: 5px; }
}

/* ==========================================================================
   MOTION / PRINT
   ========================================================================== */

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

@media print {
  .ec-sidebar,
  .ec-header,
  .ec-overlay,
  .ec-no-print {
    display: none !important;
  }

  .ec-main { margin: 0; }

  .ec-content { max-width: none; padding: 0; }

  .ec-card {
    break-inside: avoid;
    border: 1px solid #ddd;
    box-shadow: none;
  }

  body { background: #fff; }
}

/* ==========================================================================
   PLATFORM CONSOLE
   ==========================================================================
   The one screen outside the tenant boundary, and the only one whose user can
   suspend a school or read a record in any of them. It has to be recognisable
   at a glance: a console that looks exactly like the school portal makes
   "which side of the boundary am I on" a question you answer by reading the
   URL.

   The rail is tinted rather than restyled. Same widths, same type, same
   components — it is the same design system doing a different job, not a
   second product — but the accent edge and the warmer surface make the switch
   unmistakable in peripheral vision.
   ========================================================================== */

.ec-sidebar--owner {
  background: var(--ec-sidebar-owner-bg, var(--ec-sidebar-bg));
  border-inline-end: 2px solid var(--ec-warning, #d97706);
}

/* The accent runs behind the brand too, so the top of the rail reads as one
   surface rather than a tinted list under a normal header. */
.ec-sidebar--owner .ec-sidebar__brand {
  border-block-end-color: color-mix(in srgb, var(--ec-warning, #d97706) 35%, transparent);
}

.ec-sidebar--owner .ec-nav-section {
  color: color-mix(in srgb, var(--ec-warning, #d97706) 80%, var(--ec-text-muted));
}

.ec-sidebar--owner .ec-nav-link.is-active {
  color: var(--ec-warning, #d97706);
  background: color-mix(in srgb, var(--ec-warning, #d97706) 12%, transparent);
}

/* The active marker on the school rail is the accent; up here it is the
   warning colour, or the two disagree on what "you are here" looks like. */
.ec-sidebar--owner .ec-nav-link.is-active::before {
  background: var(--ec-warning, #d97706);
}

/* ==========================================================================
   SECRETARIAT CONSOLE

   The third scope: above a school, below the platform. A diocesan or
   convention education office reading aggregate figures for the schools it
   oversees, and able to change none of them.

   Blue, not the owner's amber, and that choice carries meaning rather than
   variety. Amber marks the one account that can suspend a school and read any
   record on the installation; handing a secretariat the same rail would tell
   everybody who ever glanced at the screen — the principal it is shown to, the
   bursar walking past — that the diocese holds operator powers. It does not.
   Blue is --ec-info, which is this system's colour for "this is a statement,
   not a warning", and that is exactly what the whole console is.

   Same widths, same type, same components. One design system, three jobs.
   ========================================================================== */

.ec-sidebar--secretariat {
  background: var(--ec-sidebar-secretariat-bg, var(--ec-sidebar-bg));
  border-inline-end: 2px solid var(--ec-info, #0e7490);
}

.ec-sidebar--secretariat .ec-sidebar__brand {
  border-block-end-color: color-mix(in srgb, var(--ec-info, #0e7490) 35%, transparent);
}

.ec-sidebar--secretariat .ec-nav-section {
  color: color-mix(in srgb, var(--ec-info, #0e7490) 80%, var(--ec-text-muted));
}

.ec-sidebar--secretariat .ec-nav-link.is-active {
  color: var(--ec-info, #0e7490);
  background: color-mix(in srgb, var(--ec-info, #0e7490) 12%, transparent);
}

.ec-sidebar--secretariat .ec-nav-link.is-active::before {
  background: var(--ec-info, #0e7490);
}

/* ==========================================================================
   MARKETER CONSOLE

   The fourth scope: the schools one salesperson brought, and nothing in any
   of them. It used to wear the owner's amber rail, so the two consoles
   outside the tenant boundary were told apart by reading the badge — and
   amber says "can suspend a school", which a marketer cannot. Violet is
   nobody else's colour here: not the operator's amber, not the secretariat's
   blue, not the brand green of a school rail. Same widths, same type, same
   components. One design system, four jobs.
   ========================================================================== */

.ec-sidebar--marketer {
  background: var(--ec-sidebar-marketer-bg, var(--ec-sidebar-bg));
  border-inline-end: 2px solid var(--ec-marketer, #6d28d9);
}

.ec-sidebar--marketer .ec-sidebar__brand {
  border-block-end-color: color-mix(in srgb, var(--ec-marketer, #6d28d9) 35%, transparent);
}

.ec-sidebar--marketer .ec-nav-section {
  color: color-mix(in srgb, var(--ec-marketer, #6d28d9) 80%, var(--ec-text-muted));
}

.ec-sidebar--marketer .ec-nav-link.is-active {
  color: var(--ec-marketer, #6d28d9);
  background: color-mix(in srgb, var(--ec-marketer, #6d28d9) 12%, transparent);
}

.ec-sidebar--marketer .ec-nav-link.is-active::before {
  background: var(--ec-marketer, #6d28d9);
}

/* ==========================================================================
   FOCUS RING: THE GREEN-ON-GREEN CASE

   The ring is --ec-focus, which defaults to --ec-accent, and is drawn 2px
   outside the element. Against a cream page, a white card or the dark
   theme's near-black that is a 5–6:1 edge and nothing more is needed.

   It stops working the moment the thing being ringed is itself painted the
   accent. This is not hypothetical: the marketing pages hit it first, where
   the ring landed on the green closing band and a keyboard user tabbing into
   the call to action saw nothing at all. Every accent-filled control in the
   application has the same shape.

   So on those, and only those, the ring turns into the ink that already
   works on that fill — --ec-accent-on, white in the light theme and a near
   black in the dark one — and moves *inside* the fill, where that ink is the
   guaranteed contrast. A negative offset rather than a positive one, because
   a white ring sitting 2px outside a green button lands on cream at 1.06:1,
   which is worse than the problem.

   The list is every accent-filled element a keyboard can reach. Anything
   painted with --ec-accent that is not focusable (the calendar's today, the
   sender's chat bubble, the unread dot) is deliberately absent.
   ========================================================================== */

/* The brand tile is deliberately not on this list. It is not painted at all
   any more — it is a surface with a hairline, holding a two-colour mark — so
   an inside ring would be drawn over the mark itself. It keeps the outside
   ring, which meets the rail and reads 6:1. */
body:not(.ec-mk) .ec-btn--primary:focus-visible,
body:not(.ec-mk) .ec-skip-link:focus-visible,
body:not(.ec-mk) .ec-page-btn.is-active:focus-visible,
body:not(.ec-mk) .ec-settings__tab.is-active:focus-visible,
body:not(.ec-mk) .ec-chip.is-active:focus-visible,
body:not(.ec-mk) .dropdown-item.active:focus-visible {
  --ec-focus: var(--ec-accent-on);
  outline: 2px solid var(--ec-focus);
  outline-offset: -4px;
}

/* The switch track and the register's four choice dots are the other half of
   the problem and want the opposite answer. They are 18px, so there is no
   room to draw a ring inside them, and their fill is a semantic colour
   rather than the accent — so an accent ring can still land next to a colour
   close to it. They keep the outside ring and get a 2px band of the surface
   painted underneath it, so the ring always meets the page and never the
   fill. --ec-ring, the soft glow these two used, was a 32% wash of the
   accent: on a filled control it was a green haze on green. */
body:not(.ec-mk) .ec-switch input:checked:focus-visible + .ec-switch__track,
body:not(.ec-mk) .ec-register__opt input:checked:focus-visible + span {
  outline: 2px solid var(--ec-focus);
  outline-offset: 4px;
  box-shadow: 0 0 0 2px var(--ec-surface);
}

/* The school rail's active row is a wash now, not a fill, so its ring goes
   outside in the accent, the same answer the console rails below give. */
body:not(.ec-mk) .ec-nav-link.is-active:focus-visible {
  outline: 2px solid var(--ec-focus);
  outline-offset: 2px;
}

/* The platform console's rail marks its active row with a 12% wash of the
   warning colour, not with a fill of the accent — so the rule above was
   drawing a white ring inside a near-white row, at 1.08:1. Measured, not
   guessed: it is the one row in the application where "the active nav link"
   and "painted the accent" are not the same statement.

   It goes back outside, in the colour this rail already uses to say you are
   here, so the ring agrees with the marker beside it. */
body:not(.ec-mk) .ec-sidebar--owner .ec-nav-link.is-active:focus-visible {
  --ec-focus: var(--ec-warning);
  outline: 2px solid var(--ec-focus);
  outline-offset: 2px;
}

/* The secretariat rail has the same problem for the same reason — a 12% wash
   rather than a fill — and needs the same answer in its own colour. Separate
   rule rather than a shared selector list, because each one sets --ec-focus to
   the colour ITS marker uses, and the whole point is that the ring agrees with
   the marker beside it. */
body:not(.ec-mk) .ec-sidebar--secretariat .ec-nav-link.is-active:focus-visible {
  --ec-focus: var(--ec-info);
  outline: 2px solid var(--ec-focus);
  outline-offset: 2px;
}

/* And the marketer rail, in its own colour, for the same reason. */
body:not(.ec-mk) .ec-sidebar--marketer .ec-nav-link.is-active:focus-visible {
  --ec-focus: var(--ec-marketer);
  outline: 2px solid var(--ec-focus);
  outline-offset: 2px;
}

/* ==========================================================================
   THE CHROME
   The rail, the header, the family portal's lite bar and its tab bar used
   to be a navy band in both themes, done by re-keying the role tokens on
   their subtree. The 2026 redesign (docs/REDESIGN_PLAN.md) lifted that:
   they now take the page theme like every other surface, so the rules
   below only adjust their shape, never their palette.
   ========================================================================== */

/* Both bars are opaque now, so the blur behind them has nothing to do and
   costs a compositing layer on every scroll — on a phone, on every scroll of
   the one screen a parent actually uses. */
.ec-header,
.ec-lite-bar {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* --- Rail head -------------------------------------------------------------
   The school's mark and name, with the collapse button at the far end. No
   rule under it: on a white rail the space before the nav is the divider. */
.ec-sidebar__brand {
  padding: var(--ec-space-5) var(--ec-space-4) var(--ec-space-3);
}

/* Shown from 1200px only, where the rail is pinned; below that the rail is
   a drawer and narrowing it would mean nothing. */
.ec-rail-toggle {
  display: none;
  flex-shrink: 0;
  margin-inline-start: auto;
}

/* --- Header controls on the canvas ------------------------------------------
   The header has no band any more, so its controls are round white chips on
   the grey field, the same white-on-grey the cards are. One family of shapes
   on one ground is what makes the page read as calm. */
.ec-header .ec-icon-btn,
.ec-lite-bar .ec-icon-btn {
  width: 40px;
  height: 40px;
  background: var(--ec-surface);
  border-color: var(--ec-border);
  border-radius: var(--ec-radius-full);
}

.ec-header .ec-icon-btn:hover,
.ec-lite-bar .ec-icon-btn:hover {
  background: var(--ec-surface-2);
}

.ec-header .ec-icon-btn:active,
.ec-lite-bar .ec-icon-btn:active { transform: scale(0.96); }

.ec-header .ec-ctxpill {
  height: 40px;
  padding-inline: var(--ec-space-4);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-full);
}

.ec-header .ec-ctxpill--accent {
  color: var(--ec-sidebar-active-text);
  background: var(--ec-accent-soft);
  border-color: transparent;
}

/* The theme switch as a two-state segment, sun and moon side by side with
   the current one filled, rather than one icon that names the OTHER state.
   It is still the one button shell.js and parent.js bind; both icons are
   simply always drawn, and the theme on <html> says which is lit. */
.ec-header [data-theme-toggle],
.ec-lite-bar [data-theme-toggle] {
  display: inline-flex;
  gap: 2px;
  align-items: center;
  width: auto;
  padding: 3px;
}

.ec-header [data-theme-toggle] svg,
.ec-lite-bar [data-theme-toggle] svg {
  box-sizing: content-box;
  padding: 7px;
  border-radius: var(--ec-radius-full);
  transition: background-color var(--ec-transition-fast), color var(--ec-transition-fast);
}

.ec-header [data-theme-toggle] svg[hidden],
.ec-lite-bar [data-theme-toggle] svg[hidden] { display: block !important; }

/* The sun is [data-theme-icon-dark] (the icon the dark theme used to show);
   it goes first, as it does on every OS switch. */
[data-theme-toggle] [data-theme-icon-dark] { order: -1; }

html:not([data-theme='dark']) [data-theme-toggle] [data-theme-icon-dark],
html[data-theme='dark'] [data-theme-toggle] [data-theme-icon-light] {
  color: var(--ec-accent-on);
  background: var(--ec-accent);
}

/* The school and role switchers and the profile: one white pill each. */
.ec-header .ec-profile-btn {
  min-height: 40px;
  padding: 3px var(--ec-space-3) 3px 3px;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-full);
}

/* Under 992px the round controls no longer fit in one row (at 820 the
   profile pill was pushed 33px off the screen). The theme switch goes back to
   one icon, the one for the other theme, and the switcher and profile pills
   drop their text to the avatar or icon. Their menus still name everything. */
@media (max-width: 991.98px) {
  .ec-header [data-theme-toggle] svg[hidden],
  .ec-lite-bar [data-theme-toggle] svg[hidden] { display: none !important; }

  html:not([data-theme='dark']) [data-theme-toggle] [data-theme-icon-dark],
  html[data-theme='dark'] [data-theme-toggle] [data-theme-icon-light] {
    color: inherit;
    background: none;
  }
}

/* Below 1200 the rail's toggle and the brand join the header, and the search
   box was left a word wide at 1024. The pills' text goes first so the search
   keeps its room; the avatar and the school icon stay, and each menu still
   names everything. */
@media (max-width: 1199.98px) {
  .ec-header .ec-profile-btn > .ec-hide-sm { display: none; }
  .ec-header .ec-profile-btn { padding-inline-end: 3px; }
}

/* --- The collapsed rail (1200px and up) -------------------------------------
   Icons only, 84px wide. Every section is opened by shell.js while it is
   collapsed, so the headings become thin rules between the groups of icons
   and each link carries its label as a title. */
@media (min-width: 1200px) {
  .ec-rail-toggle { display: grid; }

  :root.ec-rail-collapsed { --ec-sidebar-w: 84px; }

  .ec-rail-collapsed .ec-sidebar__brand {
    flex-direction: column;
    gap: var(--ec-space-3);
    padding-inline: 0;
  }

  .ec-rail-collapsed .ec-rail-toggle { margin-inline-start: 0; }
  .ec-rail-collapsed .ec-rail-toggle svg { transform: scaleX(-1); }

  .ec-rail-collapsed .ec-sidebar__ident,
  .ec-rail-collapsed .ec-sidebar__year,
  .ec-rail-collapsed .ec-nav-group > summary,
  .ec-rail-collapsed .ec-nav-link__label,
  .ec-rail-collapsed .ec-railstat__name,
  .ec-rail-collapsed .ec-railstat__state { display: none; }

  .ec-rail-collapsed .ec-nav-group + .ec-nav-group {
    padding-block-start: var(--ec-space-2);
    margin-block-start: var(--ec-space-2);
    border-block-start: 1px solid var(--ec-border);
  }

  .ec-rail-collapsed .ec-nav-link {
    justify-content: center;
    padding-inline: 0;
  }

  .ec-rail-collapsed .ec-nav-link__badge {
    position: absolute;
    inset-block-start: 2px;
    inset-inline-end: 12px;
  }

  .ec-rail-collapsed .ec-railstat { justify-content: center; }
}

/* The rail's group labels and its active row used to be restyled here, as
   tracked eyebrows and a dark-ink bar on a cyan fill. Both moved to the
   .ec-nav-section and .ec-nav-link.is-active rules near the top of this
   file in the 2026 redesign: sentence-case labels, and a cyan bar on a wash.
   The row is still found two ways, a colour and a shape, for a reader who
   cannot separate the hues. */
