/* ==========================================================================
   DIGISCHOOL: Components
   Everything that is not the shell. Each block is self-contained and reads
   only from tokens.css, so nothing here needs a dark-theme variant.
   ========================================================================== */

/* ==========================================================================
   BUTTONS
   ========================================================================== */

/* 40px default, 36px compact, label-md at weight 600, and the sheet's 4px
   corner. A button is the one thing on a card that is meant to be pressed, so
   it is the one thing on the card that is NOT rounded like the card. */
.ec-btn {
  display: inline-flex;
  gap: var(--ec-space-2);
  align-items: center;
  justify-content: center;
  padding: 0 var(--ec-space-4);
  height: 40px;
  font-size: var(--ec-label-md);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-md-track);
  white-space: nowrap;
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: var(--ec-radius-sm);
  transition: background var(--ec-transition-fast), color var(--ec-transition-fast),
    border-color var(--ec-transition-fast), box-shadow var(--ec-transition-fast);
}

.ec-btn svg { width: 18px; height: 18px; }

.ec-btn--primary {
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  box-shadow: var(--ec-shadow-sm);
}

.ec-btn--primary:hover {
  color: var(--ec-accent-on);
  background: var(--ec-accent-hover);
}

/* The sheet's secondary: a white face and a real edge, not a tinted block.
   --ec-border-strong rather than --ec-border because this edge IS the
   button — 1.4.11 asks a control boundary for 3:1 and the hairline that
   separates two table rows does not carry it. */
.ec-btn--outline {
  color: var(--ec-text);
  background: var(--ec-surface);
  border-color: var(--ec-border-strong);
}

.ec-btn--outline:hover {
  color: var(--ec-text);
  background: var(--ec-bg);
}

.ec-btn--ghost {
  color: var(--ec-text-muted);
  background: transparent;
}

/* The destructive one, and it was missing.

   Three buttons ask for it — the year rollover's "run it", and two on the
   timetable builder that clear a generated grid — and with no rule behind
   the modifier all three fell back to .ec-btn's own transparent face, which
   is the app's quietest control wearing the app's most consequential verb.
   An outline rather than a filled block: it is never the action the screen
   is for, and a red slab beside a primary button competes with it for the
   eye. The fill arrives on hover, when the pointer is already on it. */
.ec-btn--danger {
  color: var(--ec-danger);
  background: var(--ec-surface);
  border-color: color-mix(in srgb, var(--ec-danger) 45%, transparent);
}

.ec-btn--danger:hover {
  color: var(--ec-danger-on);
  background: var(--ec-danger);
  border-color: var(--ec-danger);
}

.ec-btn--danger:focus-visible { --ec-focus: var(--ec-danger); }

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

.ec-btn--sm {
  height: 36px;
  padding-inline: var(--ec-space-3);
  font-size: var(--ec-label-md);
}

.ec-btn--sm svg { width: 16px; height: 16px; }

.ec-btn:disabled,
.ec-btn[aria-disabled='true'] {
  pointer-events: none;
  opacity: 0.5;
}

/* ==========================================================================
   CARD
   ========================================================================== */

/*
 * The card sizes to its content.
 *
 * This was `height: 100%`, unconditionally, and that is right for the case it
 * was written for — one card alone in a Bootstrap column, next to another card
 * alone in the column beside it, the two wanting to end level with each other.
 * `.row` is `display: flex`, the columns stretch to the tallest, and the 100%
 * makes the cards fill the columns they were stretched to.
 *
 * It is wrong the moment a column holds TWO cards. They are not two halves of
 * the column: each one independently resolves 100% against the full stretched
 * height, so a card carrying a single homework row renders 912px tall beside a
 * results table that earned it, and the column overflows its own declared
 * height by the total of the extras. Three portal dashboards stack cards this
 * way — student, teacher and parent — and all three showed it.
 *
 * So the fill is kept, and narrowed to the case it was meant for: a card that
 * is the only child of its column. A stack sizes to content, which is what a
 * stack has always wanted. Nothing in any template had to change for this, and
 * `h-100` still forces the fill wherever a page asks for it by name.
 */
.ec-card {
  display: flex;
  flex-direction: column;
  height: auto;
  background: var(--ec-surface);
  /* White on the grey canvas is what separates a card now; the hairline is
     kept faint for the dark theme, where surface and canvas sit closer. */
  border: 1px solid color-mix(in srgb, var(--ec-border) 60%, transparent);
  border-radius: var(--ec-radius-lg);
}

[class*='col-'] > .ec-card:only-child,
.ec-card.h-100 {
  height: 100%;
}

.ec-card__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  /* No rule under the title: the title's weight and the space under it do
     that job, and a ruled head on every card stacks a dozen extra lines on
     a dashboard. Tables keep their own header row. */
  padding: var(--ec-space-5) var(--ec-space-5) var(--ec-space-2);
}

/* The sheet's headline-md, in the display face: the card's own name, one
   step above the body it sits over and clearly not part of it. */
.ec-card__title {
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-md);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-headline-md-lh);
  letter-spacing: var(--ec-headline-md-track);
}

.ec-card__body {
  flex: 1;
  padding: var(--ec-space-5);
}

/* Stacked blocks inside a body need a gap, and cannot get one from a margin on
   themselves. `p { margin: 0 }` is global, so four paragraphs in a body run
   together and an .ec-alert lands its border directly on the baseline of the
   text above it — which is what the import preview's summary card did.

   Done here rather than with an mb-* on each block because the LAST child
   varies: several of those paragraphs are conditional, so a blanket bottom
   margin lands on whichever happens to be last and leaves the body padded
   unevenly top against bottom. An adjacent-sibling rule cannot. */
.ec-card__body > p + p,
.ec-card__body > p + .ec-alert,
.ec-card__body > .ec-alert + p,
.ec-card__body > * + .ec-field { margin-block-start: var(--ec-space-3); }

.ec-card__body--flush { padding: 0; }

.ec-card__foot {
  padding: var(--ec-space-3) var(--ec-space-5);
  border-block-start: 1px solid var(--ec-border);
}

/* ==========================================================================
   STAT CARD
   ========================================================================== */

/*
 * The KPI card, re-laid out to the sheet without touching a line of markup.
 *
 * The contract is fixed and load-bearing — thirty-eight files and six
 * scripts emit `__icon + __body > __label + __value + __split`, so the DOM
 * is what it is and this block rearranges it rather than asking them to
 * change. Three moves do the whole job:
 *
 *   1. The icon is lifted OUT of the flow and pinned to the top right, as a
 *      small tinted square. It used to be a 46px block on the left, which
 *      made the first thing read on a row of four cards the decoration
 *      rather than the label.
 *   2. The label reserves room for it; the value and the footer do not, so
 *      the figure and its split run the full width of the card.
 *   3. The split becomes a tray — its own tinted strip at the foot — which
 *      is what separates "the number" from "how the number breaks down".
 *
 * The corner wash and the hover lift are gone. The sheet's elevation is an
 * outline and a shallow ambient layer; a card that rises 2px and carries a
 * coloured circle is a different design language, and on a grid of four it
 * was four circles competing with the four figures.
 */
.ec-stat {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--ec-space-5);
  background: var(--ec-surface);
  border: 1px solid color-mix(in srgb, var(--ec-border) 60%, transparent);
  border-radius: var(--ec-radius-lg);
}

/* The tile reads top to bottom: what kind of figure (the round icon), its
   name, the figure, and how it breaks down. The icon used to be pinned to the
   top right with a cyan bar in the opposite corner, two ornaments on a tile
   whose point is one number. Now it leads, in the tile's own --tint, and is
   the only colour on it.

   No resting shadow and no hover lift: a tile that is not a link must not
   move under the pointer as if it were one. .ec-stat--link keeps its own. */

.ec-stat__icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-block-end: var(--ec-space-4);
  color: var(--tint, var(--ec-accent));
  background: color-mix(in srgb, var(--tint, var(--ec-accent)) 14%, transparent);
  border-radius: var(--ec-radius-full);
}

.ec-stat__icon svg { width: 20px; height: 20px; }

.ec-stat__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

/* The figure's name, in the case it is written in. Tracked 10px capitals
   made four labels in a row a texture rather than four words; at label size
   "Teaching staff" is read, not decoded. */
.ec-stat__label {
  font-size: var(--ec-label-md);
  font-weight: var(--ec-weight-medium);
  line-height: var(--ec-label-md-lh);
  letter-spacing: var(--ec-label-md-track);
  color: var(--ec-text-muted);
}

.ec-stat__value {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: baseline;
  margin-block-start: var(--ec-space-2);
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-xl);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-headline-xl-lh);
  letter-spacing: var(--ec-headline-xl-track);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'tnum', 'cv05';
}

/* The gap above the tray, declared on the figure rather than on the tray:
   the tray's own top margin is `auto`, which is what pins it to the foot of
   a stretched card, and a second value there would cancel that. */
.ec-stat__value { margin-block-end: var(--ec-space-3); }

/* Anything that is not the figure itself — a unit, a scale, a qualifier —
   drops back to the sheet's label size and stops competing with it. */
.ec-stat__value small,
.ec-stat__value .ec-stat__unit {
  font-family: var(--ec-font-sans);
  font-size: var(--ec-label-md);
  font-weight: var(--ec-weight-normal);
  letter-spacing: var(--ec-label-md-track);
  color: var(--ec-text-muted);
}

/* The footer tray. margin-block-start:auto pins it to the bottom edge, so a
   row of four cards with sub-lines of different lengths still has its four
   trays in one line. */
.ec-stat__split {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2) var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  /* A hairline above, not a tinted tray around: the breakdown is a footnote
     to the figure, and a filled box made it the second-loudest thing on the
     tile. */
  padding-block-start: var(--ec-space-3);
  margin-block-start: auto;
  font-size: var(--ec-label-sm);
  line-height: var(--ec-label-sm-lh);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text-muted);
  border-block-start: 1px solid color-mix(in srgb, var(--ec-border) 70%, transparent);
}

.ec-stat__split b {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
  font-variant-numeric: tabular-nums lining-nums;
}

/* One item in the tray reads as a caption rather than a split, so it is set
   flush left instead of being pushed to both edges by space-between. */
.ec-stat__split > :only-child { margin-inline-end: auto; }

/* The bare caption: the same sentence the tray carries, without the tray.

   Two spellings are in the markup — __hint on the pupil, class and guardian
   screens, __sub on the report-card summary — and neither had a rule, so
   thirty-nine tiles across four screens set "46 on the roll today" in
   full-size body copy with the browser's own 16px paragraph margin under
   it, directly beneath the figure it was meant to be a footnote to. It is
   the loudest text in a card whose point is the number above it. */
.ec-stat__hint,
.ec-stat__sub {
  margin: 0;
  font-size: var(--ec-label-sm);
  line-height: var(--ec-label-sm-lh);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text-muted);
}

/* A row of small figures inside a card — the rollover's promoted / repeating
   / leaving / undecided split. Same .ec-stat parts, a quarter of the size:
   these are a breakdown, not four KPI cards. */
.ec-stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: var(--ec-space-3);
}

.ec-stat-row .ec-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--ec-space-3);
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

.ec-stat-row .ec-stat__label { padding-inline-end: 0; }

.ec-stat-row .ec-stat__value {
  margin-block: 0;
  font-size: var(--ec-headline-md);
}

/* Delta chip: direction sets the colour */
.ec-delta {
  display: inline-flex;
  gap: 3px;
  align-items: center;
  padding: 2px 7px;
  font-size: var(--ec-text-2xs);
  font-weight: var(--ec-weight-semibold);
  border-radius: var(--ec-radius-sm);
}

.ec-delta svg { width: 12px; height: 12px; }
.ec-delta--up   { color: var(--ec-success); background: var(--ec-success-soft); }
.ec-delta--down { color: var(--ec-danger);  background: var(--ec-danger-soft); }
.ec-delta--flat { color: var(--ec-text-muted); background: var(--ec-surface-2); }

/* ==========================================================================
   BADGES / PILLS
   ========================================================================== */

/* Square, always, and never a pill.
   The Stitch sheet asks for the full radius here, so that a state tag cannot
   be mistaken for the button beside it. The separation still has to happen —
   it just cannot happen with a pill, because a pill is the shape the other
   product ships and the two would be told apart by their hue alone.

   Digisol's own surfaces have no pill on them: state is drawn with square
   rules and tracked capitals. So the badge takes --ec-radius-sm (2px) and the
   button takes the same 2px, and what separates them is the tracked-out
   uppercase label and the 1px border a badge carries and a button does not.
   label-sm at weight 600, because a badge is scanned, not read. */
.ec-badge {
  display: inline-flex;
  gap: var(--ec-space-1);
  align-items: center;
  padding: 3px 10px;
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-label-sm-lh);
  letter-spacing: var(--ec-label-sm-track);
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: var(--ec-eyebrow-track);
  border: 1px solid transparent;
  border-radius: var(--ec-radius-sm);
}

/* The small one. Five files were already asking for it — the timetable's cover
   flag, the council's incident count, the honour roll's grades, the settings
   key indicators — and nothing defined it, so every one of them drew at full
   size inside a table cell that had no room for it.

   The type only drops to 11px: a badge is short and already the smallest text
   on the page, and taking it below that trades a legibility problem for a
   density one. What actually buys the space is the padding. */
.ec-badge--xs {
  padding: 1px 7px;
  font-size: var(--ec-text-2xs);
}

/* Each state carries a fill AND an edge a shade of its own colour, which is
   the sheet's status spectrum: the fills are pale enough that on the cream
   page several of them differ from it by under 1.2:1, and the edge is what
   makes the tag a tag. color-mix rather than six more tokens — the border is
   always the same colour as the text, at a quarter strength. */
.ec-badge--success {
  color: var(--ec-success);
  background: var(--ec-success-soft);
  border-color: color-mix(in srgb, var(--ec-success) 28%, transparent);
}

.ec-badge--warning {
  color: var(--ec-warning);
  background: var(--ec-warning-soft);
  border-color: color-mix(in srgb, var(--ec-warning) 28%, transparent);
}

.ec-badge--danger {
  color: var(--ec-danger);
  background: var(--ec-danger-soft);
  border-color: color-mix(in srgb, var(--ec-danger) 28%, transparent);
}

.ec-badge--info {
  color: var(--ec-info);
  background: var(--ec-info-soft);
  border-color: color-mix(in srgb, var(--ec-info) 28%, transparent);
}

.ec-badge--accent {
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-color: color-mix(in srgb, var(--ec-accent) 28%, transparent);
}
/* The marketer console's badge, in the colour its rail wears (tokens.css). */
.ec-badge--marketer { color: var(--ec-marketer); background: var(--ec-marketer-soft); }

/* The one badge whose fill does not identify it: --ec-surface-2 on the cream
   page is a 1.06:1 difference, so the border is the whole boundary and has to
   be seen. --ec-border, a hairline meant for a table rule, measured 1.22:1
   there. */
.ec-badge--neutral {
  color: var(--ec-text-muted);
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border-strong);
}

/* Leading status dot */
.ec-badge__dot {
  width: 6px;
  height: 6px;
  background: currentcolor;
  border-radius: 50%;
}

/* ==========================================================================
   AVATAR
   Initials-based. No image assets, no external requests, no licensing.
   Hue is derived from the name in JS and set via --hue.
   ========================================================================== */

/* The language switcher.

   .ec-icon-btn is a 36x36 grid box built for exactly one icon. This is the
   only control in the product carrying an icon AND a label, and grid
   auto-flow stacked them into two rows, pushing the "FR" out of the button.
   It opts out into a flex pill instead.

   Shared rather than marketing-local: the public header and the application's
   top bar both render it, and a switcher that looks like a different control
   either side of the sign-in page is two switchers. */
.ec-langbtn {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  width: auto;
  padding-inline: var(--ec-space-3);
  border-radius: var(--ec-radius-sm);
}

.ec-lang {
  font-size: var(--ec-text-2xs);
  font-weight: var(--ec-weight-semibold);
  line-height: 1;
}

/* The sheet's two-state segment: the language you are reading, lit, beside
   the one the link goes to. A globe with "FR" next to it never said which of
   the two you were currently in — the segment answers that before it is
   pressed, which is the whole reason it is drawn as two halves.

   It is one <a>, not two: only one of the pair is ever a destination, and a
   real segmented control here would be a button that does nothing half the
   time. */
.ec-langseg {
  display: inline-flex;
  flex-shrink: 0;
  gap: 2px;
  align-items: center;
  padding: 2px;
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-sm);
}

.ec-langseg:hover { color: inherit; }

.ec-langseg__on,
.ec-langseg__off {
  padding: 3px var(--ec-space-2);
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-label-sm-lh);
  letter-spacing: var(--ec-label-sm-track);
  border-radius: 3px;
}

.ec-langseg__on {
  color: var(--ec-accent);
  background: var(--ec-surface);
  box-shadow: var(--ec-shadow-sm);
}

.ec-langseg__off { color: var(--ec-text-muted); }

.ec-langseg:hover .ec-langseg__off { color: var(--ec-text); }

.ec-avatar {
  display: inline-grid;
  flex-shrink: 0;
  place-items: center;
  width: 38px;
  height: 38px;
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-semibold);
  /* 34% lightness looked even across the wheel and was not: HSL lightness is
     not perceptual, so at hue 60 the ink and the tint were 3.31:1 while at
     hue 250 they were comfortably clear. The initials are 11–13px, so they
     need 4.5:1 at *every* hue avatar() can produce, not the average one.
     28% is the lightest value where the worst hue still measures 4.63:1. */
  color: hsl(var(--hue, 250) 62% 28%);
  background: hsl(var(--hue, 250) 70% 92%);
  border-radius: var(--ec-radius-full);
  user-select: none;
}

[data-theme='dark'] .ec-avatar {
  color: hsl(var(--hue, 250) 70% 82%);
  background: hsl(var(--hue, 250) 40% 26%);
}

.ec-avatar--sm { width: 30px; height: 30px; font-size: var(--ec-text-xs); }
.ec-avatar--lg { width: 56px; height: 56px; font-size: var(--ec-text-base); }
.ec-avatar--xl { width: 88px; height: 88px; font-size: var(--ec-text-xl); }

.ec-avatar--square { border-radius: var(--ec-radius-md); }

/* A real photograph in the same box as the initials.

   It takes .ec-avatar's box so a list where some pupils are photographed and
   some are not still lines up — the sizes above apply unchanged. What it drops
   is the tinted background and the hue variable, which exist to make two
   letters legible and would show as a coloured ring around a face.

   `cover` crops to the box rather than letterboxing, which is what makes a
   circle of thirty different photographs read as one column. The faint ring is
   there so a light background in the photograph does not merge into the page.  */
.ec-avatar--photo {
  object-fit: cover;
  color: transparent;          /* an alt that fails should not print letters */
  background: var(--ec-surface-2);
  box-shadow: inset 0 0 0 1px var(--ec-border);
}

/* Overlapping stack */
.ec-avatar-stack {
  display: flex;
  align-items: center;
}

.ec-avatar-stack .ec-avatar {
  border: 2px solid var(--ec-surface);
}

.ec-avatar-stack .ec-avatar + .ec-avatar { margin-inline-start: -10px; }

.ec-avatar-stack__more {
  display: grid;
  place-items: center;
  min-width: 30px;
  height: 30px;
  padding-inline: 6px;
  margin-inline-start: -10px;
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  background: var(--ec-surface-2);
  border: 2px solid var(--ec-surface);
  border-radius: var(--ec-radius-full);
}

/* Name + sub-label pairing used in every table's first column */
.ec-person {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  min-width: 0;
}

.ec-person__name {
  overflow: hidden;
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-person__meta {
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

/* ==========================================================================
   TABLE
   ========================================================================== */

/* The scroller every wide table lives in, and — on the screen where that
   matters — a visible sign that it is one.

   A phone shows about a third of a fourteen-column register, and nothing in
   the old rule said the other two thirds existed: the table simply ended
   mid-column against the card's edge, which reads as a rendering fault
   rather than as an invitation to swipe. The two shadows below are painted
   by the scroller and the two covers by the content, so the left shadow
   appears only once you have scrolled away from the start and the right one
   disappears when you reach the end — the standard local/scroll background
   pair, and the only version of this that needs no script and no
   measurement. */
.ec-table-wrap {
  width: 100%;
  overflow-x: auto;

  overscroll-behavior-x: contain;
  background:
    linear-gradient(to right, var(--ec-surface) 40%, transparent) 0 0 / 28px 100% no-repeat,
    linear-gradient(to left,  var(--ec-surface) 40%, transparent) 100% 0 / 28px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgb(0 0 0 / 0.16), transparent) 0 0 / 14px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgb(0 0 0 / 0.16), transparent) 100% 0 / 14px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;

  /* Explicit, not left to default: overflow-x alone makes the UA compute
     overflow-y as auto too, turning this into a vertical scroll container as
     well as a horizontal one. That silently becomes the nearest scrolling
     ancestor for any position:sticky element inside it (the timetable's
     thead), so its `top` offset is measured against this box instead of the
     real page viewport — sticking the header at the wrong point and
     overlapping a row instead of sitting above the whole grid. */
  overflow-y: visible;

}

.ec-table {
  width: 100%;
  border-collapse: collapse;
  white-space: nowrap;
}

/* 12px by 16px, the sheet's figure: a register is read down a column, and
   the old 12/20 put so much air between the columns that two adjacent
   figures stopped reading as one row. */
.ec-table th,
.ec-table td {
  padding: var(--ec-space-3) var(--ec-space-4);
  text-align: left;
}

/* A cell the import preview could not make sense of. Tinted rather than
   outlined, because a bad row usually has several and a grid of red borders
   is harder to read than the values themselves. */
.ec-cell--bad {
  background: var(--ec-danger-soft);
  white-space: normal;
  min-width: 12rem;
}

/* The column head, as an eyebrow: the display face at 10px, uppercase, and
   tracked out to 0.22em. It sits on the page colour rather than on a tint of
   the card — a header band darker than the card it caps reads as a row of its
   own; the page colour reads as the edge of the data. One rule under it, none
   over: the card's own border is the top.

   The Stitch sheet asks for label-sm here. This is a step smaller and a good
   deal wider, which is the difference between a small heading and a legend —
   and it is the same treatment the rail's group labels and the KPI tiles take,
   so every scannable label in the product is set one way. */
.ec-table thead th {
  font-family: var(--ec-font-display);
  font-size: var(--ec-eyebrow);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-eyebrow-lh);
  letter-spacing: var(--ec-eyebrow-track);
  color: var(--ec-text-muted);
  text-transform: uppercase;
  background: var(--ec-bg);
  border-block-end: 1px solid var(--ec-border);
}

.ec-table tbody td {
  font-size: var(--ec-body-md);
  line-height: var(--ec-body-md-lh);
  border-block-end: 1px solid var(--ec-border);
}

/* The first cell of a row is its subject — a pupil, a class, a subject —
   and carries the weight that says so. */
.ec-table tbody th[scope='row'],
.ec-table tbody td:first-child {
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

.ec-table tbody tr:last-child td { border-block-end: 0; }

/* The row that sums the ones above it. A rule over it and its own weight,
   so it reads as a conclusion rather than as one more record — the two
   totals on the analytics screen were indistinguishable from the enrolment
   rows they add up. */
.ec-table tbody tr.ec-row-total td {
  background: var(--ec-surface-2);
  border-block-start: 2px solid var(--ec-border-strong);
}

.ec-table tbody tr.ec-row-total:hover td { background: var(--ec-surface-2); }

.ec-table tbody tr {
  transition: background var(--ec-transition-fast);
}

.ec-table tbody tr:hover { background: var(--ec-bg); }

/* Figures right-align so a column of them can be compared down its edge,
   and take the sheet's tabular role so the digits line up while they do. */
.ec-table__num {
  font-weight: var(--ec-weight-medium);
  letter-spacing: var(--ec-tabular-track);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'tnum', 'cv05';
  text-align: right;
}

/* Sortable header */
.ec-th-sort {
  cursor: pointer;
  user-select: none;
}

.ec-th-sort:hover { color: var(--ec-text); }

.ec-th-sort__inner {
  display: inline-flex;
  gap: var(--ec-space-1);
  align-items: center;
}

.ec-th-sort__arrow {
  width: 13px;
  height: 13px;
  opacity: 0;
  transition: opacity var(--ec-transition-fast), transform var(--ec-transition-fast);
}

.ec-th-sort:hover .ec-th-sort__arrow { opacity: 0.4; }

.ec-th-sort[aria-sort='ascending'] .ec-th-sort__arrow,
.ec-th-sort[aria-sort='descending'] .ec-th-sort__arrow {
  color: var(--ec-accent);
  opacity: 1;
}

.ec-th-sort[aria-sort='descending'] .ec-th-sort__arrow {
  transform: rotate(180deg);
}

/* ==========================================================================
   TOOLBAR (search + filters above a table)
   ========================================================================== */

.ec-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-4) var(--ec-space-5);
  border-block-end: 1px solid var(--ec-border);
}

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

.ec-search {
  position: relative;
  display: flex;
  align-items: center;
}

.ec-search svg {
  position: absolute;
  inset-inline-start: var(--ec-space-3);
  width: 16px;
  height: 16px;
  color: var(--ec-text-subtle);
  pointer-events: none;
}

/* 40px tall, 12px in, 4px corner: the sheet's base field, matching the
   button it usually stands beside. */
.ec-input,
.ec-select {
  height: 40px;
  padding-inline: var(--ec-space-3);
  font-family: inherit;
  font-size: var(--ec-body-md);
  color: var(--ec-text);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border-strong);
  border-radius: var(--ec-radius-sm);
  transition: border-color var(--ec-transition-fast), box-shadow var(--ec-transition-fast);
}

.ec-input:focus,
.ec-select:focus {
  border-color: var(--ec-accent);
  outline: none;
  box-shadow: var(--ec-ring);
}

.ec-input::placeholder { color: var(--ec-text-subtle); }

/* A file field is the one control the browser draws itself, and left alone
   it draws a grey system button in the middle of a form where every other
   control is ours — the photograph screen, the Excel import, the school
   logo. The element keeps its own height (the 40px above cuts the native
   button off in Firefox) and the button inside it is restyled to the same
   recipe as .ec-btn--outline, so the row reads as one design rather than
   two. */
.ec-input[type='file'] {
  height: auto;
  min-height: 40px;
  padding-block: 5px;
  padding-inline: 5px var(--ec-space-3);
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
  cursor: pointer;
}

.ec-input[type='file']::file-selector-button {
  height: 28px;
  padding-inline: var(--ec-space-3);
  margin-inline-end: var(--ec-space-3);
  font: inherit;
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
  cursor: pointer;
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border-strong);
  border-radius: var(--ec-radius-sm);
  transition: background var(--ec-transition-fast), border-color var(--ec-transition-fast);
}

.ec-input[type='file']::file-selector-button:hover {
  color: var(--ec-text);
  background: var(--ec-surface-hover);
  border-color: var(--ec-accent);
}

/* 240px is what it wants, not what it gets.
   The header is a flex row of eight controls inside a fixed band, and this
   was the only one of them with a hard floor — so on a 1280px laptop a long
   role title ("Super Administrator") had nowhere to take its width from and
   pushed the profile button 74px past the page, giving every screen in the
   application a horizontal scrollbar.

   It is the right control to give way: a search box narrows without losing
   its meaning, and a truncated school name does not. 7rem is the floor,
   which still shows the icon and about a word of what was typed. */
.ec-search {
  flex: 0 1 240px;
  min-width: 0;
}

.ec-search form {
  flex: 1;
  min-width: 0;
}

.ec-search .ec-input {
  width: 100%;
  min-width: 7rem;
  padding-inline-start: calc(var(--ec-space-3) + 22px);
}

.ec-select {
  padding-inline-end: var(--ec-space-8);
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--ec-space-3) center;
}

[dir='rtl'] .ec-select { background-position: left var(--ec-space-3) center; }

.ec-select--sm {
  height: 36px;
  font-size: var(--ec-body-md);
}

/* ==========================================================================
   PAGINATION
   ========================================================================== */

.ec-pagination {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-3) var(--ec-space-5);
  border-block-start: 1px solid var(--ec-border);
}

.ec-pagination__info {
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-pagination__pages {
  display: flex;
  gap: var(--ec-space-1);
  align-items: center;
}

.ec-page-btn {
  display: grid;
  place-items: center;
  min-width: 34px;
  height: 34px;
  padding-inline: var(--ec-space-2);
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text-muted);
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  transition: color var(--ec-transition-fast), background-color var(--ec-transition-fast), border-color var(--ec-transition-fast), box-shadow var(--ec-transition-fast), transform var(--ec-transition-fast);
}

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

.ec-page-btn.is-active {
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  border-color: var(--ec-accent);
}

.ec-page-btn:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

.ec-page-btn svg { width: 15px; height: 15px; }

.ec-page-ellipsis {
  padding-inline: var(--ec-space-1);
  color: var(--ec-text-subtle);
}

/* The card that is the screen's conclusion rather than one of its steps —
   the import's "you are ready, send it". An accent edge rather than an
   accent fill: it has to stand out from six identical step cards without
   becoming a second primary button. */
.ec-card--accent {
  border-color: color-mix(in srgb, var(--ec-accent) 40%, var(--ec-border));
  border-inline-start: 3px solid var(--ec-accent);
}

/* ==========================================================================
   TABS
   ========================================================================== */

/* Scrolls rather than wraps or pushes.

   A tab strip is one row by definition — a set of them on two lines stops
   reading as a switch — but inline-flex with no ceiling is a row that takes
   whatever width it wants. On a phone, WhatsApp's four tabs came to 472px
   and the strip pushed the whole document to 488px, so every screen under
   it scrolled sideways; Settings' seven came to 880px. max-width stops the
   push and overflow-x turns the surplus into a swipe, which is what a tab
   strip does on a handset everywhere else.

   overflow-y is pinned to hidden rather than left at its implied `auto`, so
   a strip that scrolls horizontally cannot also grow a vertical scrollbar
   over the active tab's shadow. */
.ec-tabs {
  display: inline-flex;
  max-width: 100%;
  gap: 2px;
  padding: 3px;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  scrollbar-width: none;
}

.ec-tabs::-webkit-scrollbar { display: none; }

.ec-tab {
  flex: 0 0 auto;
  padding: var(--ec-space-1) var(--ec-space-3);
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text-muted);
  white-space: nowrap;
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: var(--ec-radius-sm);
  transition: color var(--ec-transition-fast), background-color var(--ec-transition-fast), border-color var(--ec-transition-fast), box-shadow var(--ec-transition-fast), transform var(--ec-transition-fast);
}

.ec-tab:hover { color: var(--ec-text); }

.ec-tab.is-active {
  color: var(--ec-text);
  background: var(--ec-surface);
  box-shadow: var(--ec-shadow-sm);
}

/* ==========================================================================
   LISTS  (schedules, notices, leave requests, activity, todo)
   ========================================================================== */

.ec-list { display: flex; flex-direction: column; }

.ec-list__item {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-4) var(--ec-space-5);
  border-block-end: 1px solid var(--ec-border);
}

.ec-list__item:last-child { border-block-end: 0; }

.ec-list--hover .ec-list__item {
  transition: background var(--ec-transition-fast);
}

.ec-list--hover .ec-list__item:hover { background: var(--ec-surface-2); }

.ec-list__body { flex: 1; min-width: 0; }

.ec-list__title {
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

.ec-list__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-1) var(--ec-space-3);
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

.ec-list__meta span {
  display: inline-flex;
  gap: 4px;
  align-items: center;
}

.ec-list__meta svg { width: 13px; height: 13px; }

/* Date block used by schedules and notices */
.ec-datechip {
  display: grid;
  flex-shrink: 0;
  place-content: center;
  width: 46px;
  height: 46px;
  text-align: center;
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

.ec-datechip__day {
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-bold);
  line-height: 1;
  color: var(--ec-accent);
}

.ec-datechip__mon {
  margin-block-start: 2px;
  font-size: 10px;
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Left accent stripe, colour set inline per item */
.ec-stripe {
  border-inline-start: 3px solid var(--stripe, var(--ec-accent));
}

/* ==========================================================================
   QUICK LINKS
   ========================================================================== */

.ec-quick {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-2);
  align-items: center;
  padding: var(--ec-space-4) var(--ec-space-2);
  text-align: center;
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  transition: color var(--ec-transition-fast), background-color var(--ec-transition-fast), border-color var(--ec-transition-fast), box-shadow var(--ec-transition-fast), transform var(--ec-transition-fast);
}

.ec-quick:hover {
  background: var(--ec-surface);
  border-color: var(--tint, var(--ec-accent));
  box-shadow: var(--ec-shadow-md);
  transform: translateY(-2px);
}

.ec-quick__icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  color: var(--tint, var(--ec-accent));
  background: color-mix(in srgb, var(--tint, var(--ec-accent)) 12%, transparent);
  border-radius: var(--ec-radius-md);
}

.ec-quick__icon svg { width: 20px; height: 20px; }

.ec-quick__label {
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

/* ==========================================================================
   SHORTCUTS — the dashboard overview's quick actions
   ==========================================================================
   A row, not a tile: icon, the part of the school, the one figure that says
   whether to open it, and a chevron. Auto-filled so the grid is three across
   on a laptop, two on a tablet and one on a phone without a media query;
   min() so a 240px column can never push a 320px phone sideways. --tint is
   set inline per shortcut, the same contract as .ec-stat and .ec-quick. */

.ec-shortcuts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
  gap: var(--ec-space-3);
}

.ec-shortcut {
  display: flex;
  align-items: center;
  gap: var(--ec-space-3);
  min-width: 0;
  padding: var(--ec-space-4);
  color: var(--ec-text);
  text-decoration: none;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  transition: border-color var(--ec-transition-fast), box-shadow var(--ec-transition-lift),
              transform var(--ec-transition-lift);
}

.ec-shortcut:hover,
.ec-shortcut:focus-visible {
  color: var(--ec-text);
  border-color: color-mix(in srgb, var(--tint, var(--ec-accent)) 55%, var(--ec-border));
  box-shadow: var(--ec-shadow-md);
  transform: translateY(-2px);
}

.ec-shortcut:active { transform: translateY(0); box-shadow: none; }

.ec-shortcut__icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 40px;
  height: 40px;
  color: var(--tint, var(--ec-accent));
  background: color-mix(in srgb, var(--tint, var(--ec-accent)) 12%, transparent);
  border-radius: var(--ec-radius-md);
}

.ec-shortcut__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ec-shortcut__label {
  font-weight: var(--ec-weight-semibold);
  line-height: 1.3;
}

.ec-shortcut__value {
  font-size: var(--ec-text-sm);
  line-height: 1.35;
  color: var(--ec-text-muted);
}

/* The way in, as a small round chip rather than a bare chevron: it is the
   one part of the tile that says "this goes somewhere". */
.ec-shortcut__go {
  flex: none;
  box-sizing: content-box;
  padding: 6px;
  color: var(--ec-text-subtle);
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-full);
  transition: transform var(--ec-transition-fast), color var(--ec-transition-fast),
              background-color var(--ec-transition-fast);
}

.ec-shortcut:hover .ec-shortcut__go,
.ec-shortcut:focus-visible .ec-shortcut__go {
  color: var(--tint, var(--ec-accent));
  background: color-mix(in srgb, var(--tint, var(--ec-accent)) 12%, transparent);
  transform: translateX(2px);
}

/* The whole-page view: last, and quieter than the six parts it contains. */
.ec-shortcut--quiet {
  --tint: var(--ec-text-muted);
  background: transparent;
  border-style: dashed;
}

@media (prefers-reduced-motion: reduce) {
  .ec-shortcut,
  .ec-shortcut__go { transition: none; }
  .ec-shortcut:hover,
  .ec-shortcut:focus-visible,
  .ec-shortcut:hover .ec-shortcut__go { transform: none; }
}

/* ==========================================================================
   PROGRESS
   ========================================================================== */

/* 8px, the sheet's meter. The old 6px read as a rule rather than a bar the
   moment it carried a value under about 10%.

   Square ends rather than round. A fee meter at 79% is a measurement, and a
   rounded cap makes the last two percent ambiguous at this height — the bar
   appears to stop short of where it stops. Digisol draws its own progress and
   accent bars square for the same reason. */
.ec-progress {
  width: 100%;
  height: 8px;
  overflow: hidden;
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-sm);
}

.ec-progress__bar {
  height: 100%;
  background: var(--bar, var(--ec-accent));
  border-radius: 0;
  transition: width var(--ec-transition);
}

.ec-progress--lg { height: 10px; }

/* The label line a meter is nearly always given: what has been paid against
   what is owed, what has been marked against what is set. Declared here so a
   page stops re-inventing it as three utility classes. */
.ec-meter {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-2);
}

.ec-meter__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: baseline;
  justify-content: space-between;
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text-muted);
}

.ec-meter__row b,
.ec-meter__row strong {
  font-weight: var(--ec-weight-bold);
  color: var(--ec-text);
  font-variant-numeric: tabular-nums lining-nums;
}

/* Legend row shared by charts and attendance blocks */
.ec-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-4);
}

.ec-legend__item {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

.ec-legend__swatch {
  width: 9px;
  height: 9px;
  background: var(--swatch);
  border-radius: 3px;
}

.ec-legend__value {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
}

/* ==========================================================================
   TODO
   ========================================================================== */

.ec-todo {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-3) var(--ec-space-5);
  border-block-end: 1px solid var(--ec-border);
}

.ec-todo:last-child { border-block-end: 0; }

.ec-todo input[type='checkbox'] {
  width: 17px;
  height: 17px;
  margin-block-start: 2px;
  accent-color: var(--ec-accent);
  cursor: pointer;
}

.ec-todo__text { flex: 1; }

.ec-todo input:checked + .ec-todo__text .ec-todo__title {
  color: var(--ec-text-muted);
  text-decoration: line-through;
}

.ec-todo__title {
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
}

/* ==========================================================================
   GETTING STARTED (includes/dashboards/_setup.php)
   ========================================================================== */

.ec-setup__intro { flex: 1; min-width: 0; }

.ec-setup__bar {
  max-width: 360px;
  margin-block-start: var(--ec-space-3);
}

.ec-setup__steps {
  padding: 0;
  margin: 0;
  list-style: none;
}

.ec-setup__step {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2) var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-3) var(--ec-space-5);
  border-block-start: 1px solid var(--ec-border);
}

/* The card head already draws the line above the first step. */
.ec-setup__step:first-child { border-block-start: 0; }

.ec-setup__mark {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  align-self: flex-start;
  width: 28px;
  height: 28px;
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  border: 1px solid var(--ec-border-strong);
  border-radius: var(--ec-radius-full);
}

.ec-setup__step.is-next .ec-setup__mark {
  color: var(--ec-accent);
  border-color: var(--ec-accent);
}

.ec-setup__step.is-done .ec-setup__mark {
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  border-color: var(--ec-accent);
}

/* Takes the row; the button wraps under the text on a narrow phone. */
.ec-setup__body { flex: 1 1 220px; min-width: 0; }

.ec-setup__title {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
  margin: 0;
  font-size: var(--ec-text-md);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

.ec-setup__step.is-done .ec-setup__title { color: var(--ec-text-muted); }

.ec-setup__detail {
  margin: 2px 0 0;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-setup__step .ec-btn { flex-shrink: 0; }

@media (max-width: 575.98px) {
  .ec-setup__step .ec-btn { margin-inline-start: calc(28px + var(--ec-space-3)); }
}

/* ==========================================================================
   EMPTY / LOADING STATES
   ========================================================================== */

.ec-empty {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-12) var(--ec-space-5);
  text-align: center;
}

.ec-empty__icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  color: var(--ec-text-subtle);
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-full);
}

.ec-empty__icon svg { width: 26px; height: 26px; }

.ec-empty__title {
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-semibold);
}

.ec-empty__text {
  max-width: 380px;
  font-size: var(--ec-text-md);
  color: var(--ec-text-muted);
}

.ec-skeleton {
  background: linear-gradient(
    90deg,
    var(--ec-surface-2) 25%,
    var(--ec-surface-hover) 37%,
    var(--ec-surface-2) 63%
  );
  background-size: 400% 100%;
  border-radius: var(--ec-radius-sm);
  animation: ec-shimmer 1.4s ease infinite;
}

@keyframes ec-shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* ==========================================================================
   CHART HOST
   ApexCharts injects its own markup; this only reserves space so the layout
   does not jump between first paint and chart render.
   ========================================================================== */

.ec-chart { width: 100%; min-height: 200px; }
.ec-chart--sm  { min-height: 140px; }
.ec-chart--lg  { min-height: 300px; }
.ec-chart--donut { min-height: 230px; }

.ec-spark { height: 46px; }

/* Text inside the chart SVG is themed here rather than through chart options.
   Passing colours via updateOptions means re-theming has to touch structural
   keys, which silently collapses bar geometry; CSS re-colours for free and
   cannot break the layout. `!important` is required because ApexCharts writes
   `fill` as an inline style on these nodes. */
.apexcharts-text,
.apexcharts-xaxis-label,
.apexcharts-yaxis-label,
.apexcharts-legend-text,
.apexcharts-datalabel-label {
  fill: var(--ec-text-muted) !important;
}

.apexcharts-datalabel-value {
  fill: var(--ec-text) !important;
}

.apexcharts-legend-text { color: var(--ec-text-muted) !important; }

.apexcharts-tooltip.apexcharts-theme-light,
.apexcharts-tooltip.apexcharts-theme-dark {
  color: var(--ec-text);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  box-shadow: var(--ec-shadow-lg);
}

.apexcharts-tooltip-title {
  font-weight: var(--ec-weight-semibold);
  background: var(--ec-surface-2);
  border-block-end: 1px solid var(--ec-border);
}

/* ==========================================================================
   DROPDOWN  (rides on Bootstrap's JS, restyled to our tokens)
   ========================================================================== */

.dropdown-menu {
  --bs-dropdown-bg: var(--ec-surface);
  --bs-dropdown-border-color: var(--ec-border);
  --bs-dropdown-border-radius: var(--ec-radius-md);
  --bs-dropdown-link-color: var(--ec-text);
  --bs-dropdown-link-hover-bg: var(--ec-surface-hover);
  --bs-dropdown-link-hover-color: var(--ec-text);
  --bs-dropdown-link-active-bg: var(--ec-accent);
  --bs-dropdown-link-active-color: var(--ec-accent-on);
  --bs-dropdown-divider-bg: var(--ec-border);
  --bs-dropdown-font-size: var(--ec-text-md);
  padding: var(--ec-space-2);
  box-shadow: var(--ec-shadow-lg);
}

.dropdown-item {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  padding: var(--ec-space-2) var(--ec-space-3);
  border-radius: var(--ec-radius-sm);
}

.dropdown-item svg { width: 16px; height: 16px; opacity: 0.7; }

.dropdown-header {
  padding: var(--ec-space-2) var(--ec-space-3);
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.ec-dropdown--wide { min-width: 320px; }

/* A table row that exists but holds nothing yet — a certificate this school
   prepares that no filière currently has candidates for. Shown rather than
   dropped: a zero is a fact about the school, a missing row reads as though
   the certificate does not exist. Same meaning as .is-blank on the bulletin. */
.ec-table tbody tr.is-blank td { color: var(--ec-text-muted); }

/* ---------------------------------------------------------------------------
   SCHOOL SWITCHER
   ---------------------------------------------------------------------------
   Each entry is a <button> inside its own <form>, because switching school
   changes what every later page shows and therefore has to be a POST. Bootstrap
   already gives .dropdown-item width:100% and a transparent background, so all
   this adds is the two-line layout and a current-school marker that does not
   rely on Bootstrap's blue .active fill — which reads as "selected in a form"
   rather than as "you are here".
   --------------------------------------------------------------------------- */

.ec-switcher__menu { min-width: 300px; }

.ec-switcher__menu .dropdown-item { align-items: flex-start; }

.ec-switcher__menu .dropdown-item svg { margin-top: 2px; flex: 0 0 auto; }

.ec-switcher__menu .dropdown-item small {
  font-size: var(--ec-text-xs);
  line-height: 1.35;
}

.ec-switcher__menu .dropdown-item.active,
.ec-switcher__menu .dropdown-item.active:focus {
  color: var(--ec-text);
  background: var(--ec-surface-2);
  font-weight: var(--ec-weight-semibold);
}

/* The marker itself. A tick rather than a fill: the row is not a choice that
   has been made, it is where the reader already is. */
.ec-switcher__menu .dropdown-item.active::after {
  content: '✓';
  margin-left: auto;
  padding-left: var(--ec-space-2);
  color: var(--ec-accent);
}

/* Notification row inside a dropdown */
.ec-notif {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-3);
  border-radius: var(--ec-radius-sm);
  transition: background var(--ec-transition-fast);
}

.ec-notif:hover { background: var(--ec-surface-hover); }

.ec-notif__text {
  font-size: var(--ec-text-sm);
  line-height: var(--ec-leading-normal);
  color: var(--ec-text);
  white-space: normal;
}

.ec-notif__time {
  margin-block-start: 2px;
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

/* Profile block in the header dropdown trigger */
.ec-profile-btn {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  padding: var(--ec-space-1) var(--ec-space-2) var(--ec-space-1) var(--ec-space-1);
  cursor: pointer;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--ec-radius-sm);
  transition: background var(--ec-transition-fast);
}

.ec-profile-btn:hover { background: var(--ec-surface-hover); }

.ec-profile-btn__name {
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-semibold);
  line-height: 1.2;
  text-align: left;
}

.ec-profile-btn__role {
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
}

/* A school's short name and a role title are both arbitrary length, and the
   header is a fixed 64px band with a fixed width. Uncapped, "Super
   Administrator" on a 1280px laptop pushed the profile button 26px past the
   header's own padding and put a horizontal scrollbar on every page of the
   application.

   Truncate rather than wrap: a two-line control in a 64px band is worse than
   an ellipsis, and the full value is already in the button's aria-label. The
   min-width is not optional — a flex item will not shrink below its content
   without it, so the ellipsis would never be reached. */
.ec-profile-btn > span {
  min-width: 0;
}

.ec-profile-btn__name,
.ec-profile-btn__role {
  display: block;
  overflow: hidden;
  max-width: 10rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ==========================================================================
   PROFILE HEADER (student details)
   ========================================================================== */

.ec-profile-hero {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-5);
  align-items: center;
  padding: var(--ec-space-6);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-sm);
}

.ec-profile-hero__name {
  font-size: var(--ec-text-lg);
  letter-spacing: -0.02em;
}

.ec-profile-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2) var(--ec-space-4);
  margin-block-start: var(--ec-space-2);
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

/* Definition list used for all detail panels */
.ec-dl {
  display: grid;
  grid-template-columns: minmax(120px, max-content) 1fr;
  gap: var(--ec-space-3) var(--ec-space-4);
  font-size: var(--ec-text-md);
}

.ec-dl dt { color: var(--ec-text-muted); }

.ec-dl dd {
  margin: 0;
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text);
}

@media (max-width: 480px) {
  .ec-dl { grid-template-columns: 1fr; gap: var(--ec-space-1); }
  .ec-dl dd { margin-block-end: var(--ec-space-3); }
}

/* ==========================================================================
   DASHBOARD HEAD
   The banner every portal dashboard opens with: who is reading, which school,
   which period, and the two or three things they came here to do.

   It used to be a saturated green panel with decorative circles — the one
   deliberately loud surface in the product. The sheet does not have one. Its
   equivalent is a plain white card whose weight comes from the type: a small
   tag naming the institution, a headline, one line of period context, and the
   actions ranged right. That is what this is now.

   What that buys, beyond matching the sheet: the banner sat above four KPI
   cards and a table, and a block of saturated green above a row of quiet
   white cards trains the eye to start with the one part of the screen that
   carries no figures. Every rule below is the same class doing the same job,
   repainted — no template changed.
   ========================================================================== */

/* The page's heading, set on the canvas rather than in a card: the
   "Academix" layout of the 2026 redesign opens a dashboard with its title
   and context and lets the first row of cards be the first card. Everything
   it held stays (greeting, role, school, period, levels, actions); only the
   box around it is gone. */
.ec-hero {
  position: relative;
  padding: var(--ec-space-2) 0 var(--ec-space-2);
  color: var(--ec-text);
}

.ec-hero__row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-4) var(--ec-space-5);
  align-items: center;
}

/* The reader's initials, as a tinted square rather than a translucent one:
   there is no coloured ground to be translucent against any more. */
.ec-hero__avatar {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 56px;
  height: 56px;
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-md);
  font-weight: var(--ec-weight-bold);
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-full);
}

.ec-hero__body { flex: 1; min-width: 240px; }

.ec-hero__hi {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  font-size: var(--ec-body-sm);
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text-muted);
}

/* The institution tag: uppercase, tracked out, on the secondary tint. The
   sheet uses it to name the school and its region above the page title. */
.ec-hero__role {
  display: inline-block;
  padding: 2px 10px;
  margin-block: var(--ec-space-2);
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-bold);
  letter-spacing: var(--ec-label-sm-track);
  text-transform: uppercase;
  color: var(--ec-text-muted);
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-sm);
}

/* On a phone the initials shrink and sit at the top, so the school's name
   keeps the width; beside a 56px tile it broke over three lines. */
@media (max-width: 575.98px) {
  .ec-hero__row { align-items: flex-start; }
  .ec-hero__avatar { width: 40px; height: 40px; font-size: var(--ec-body-md); }
  .ec-hero__body { min-width: 0; flex-basis: calc(100% - 40px - var(--ec-space-5)); }
}

/* The page title, a step up from a card title now that it is the heading of
   the whole page rather than the title of its first card. */
.ec-hero__title {
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-xl);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-headline-xl-lh);
  letter-spacing: var(--ec-headline-xl-track);
  color: var(--ec-text);
}

.ec-hero__sub {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-body-sm);
  line-height: var(--ec-body-sm-lh);
  color: var(--ec-text-muted);
}

.ec-hero__sub svg { flex-shrink: 0; color: var(--ec-accent); }

.ec-hero__sub b,
.ec-hero__sub strong {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
}

/* The deadline. Set apart as a tag rather than run into the sentence,
   because it is the only part of that line that can run out. */
.ec-hero__due {
  padding: 1px 8px;
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-warning);
  background: var(--ec-warning-soft);
  border-radius: var(--ec-radius-sm);
}

.ec-hero__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  margin-block-start: var(--ec-space-3);
}

.ec-hero__chip {
  display: inline-flex;
  gap: var(--ec-space-1);
  align-items: center;
  padding: 3px 12px;
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text-muted);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-full);
}

.ec-hero__chip svg { color: var(--ec-text-subtle); }

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

/*
 * The action buttons.
 *
 * `.ec-btn--hero` existed because these sat on green and the page's ordinary
 * outline button disappeared there. They sit on white now, so the modifier
 * resolves to the sheet's secondary button — a tinted face, dark label — and
 * the FIRST of them takes the primary fill, because the sheet's action bar
 * leads with the one thing the reader most likely came to do.
 *
 * Done by position rather than by asking eight dashboards to mark one button
 * up differently: the leading action is already first in every one of them.
 */
.ec-hero .ec-btn--hero {
  color: var(--ec-text);
  background: var(--ec-bg);
  border: 1px solid transparent;
}

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

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

.ec-hero__actions > .ec-btn--hero:first-child {
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  box-shadow: var(--ec-shadow-sm);
}

.ec-hero__actions > .ec-btn--hero:first-child:hover {
  color: var(--ec-accent-on);
  background: var(--ec-accent-hover);
}

.ec-hero__actions > .ec-btn--hero:first-child svg { color: currentcolor; }

/* Drawn on the accent, so the ring goes inside it — the same flip the
   accent-filled controls make at the foot of layout.css. */
.ec-hero__actions > .ec-btn--hero:first-child:focus-visible { --ec-focus: var(--ec-accent-on); }

@media print {
  .ec-hero { box-shadow: none; }

  .ec-hero__actions { display: none; }
}

/* ==========================================================================
   UTILITIES
   ========================================================================== */

.ec-muted   { color: var(--ec-text-muted); }
.ec-subtle  { color: var(--ec-text-subtle); }
.ec-strong  { font-weight: var(--ec-weight-semibold); }
.ec-t-xs    { font-size: var(--ec-text-xs); }
.ec-t-sm    { font-size: var(--ec-text-sm); }
.ec-t-md    { font-size: var(--ec-text-md); }
.ec-t-lg    { font-size: var(--ec-text-lg); }
.ec-success { color: var(--ec-success); }
.ec-danger  { color: var(--ec-danger); }
.ec-warning { color: var(--ec-warning); }
.ec-nowrap  { white-space: nowrap; }

/* Aliases, because both spellings are already in the markup and a class that
   resolves to nothing is a silent one: .ec-t-success and .ec-t-danger are
   used on the settings and secretariat screens, .ec-t-md on the password
   screen, .ec-warning on the report-card list. */
.ec-t-success { color: var(--ec-success); }
.ec-t-danger  { color: var(--ec-danger); }

/* Ranged right, for a figure in a table cell that .ec-table__num is too much
   for — a delta, a year-on-year column, a total that is not the subject of
   the column. Thirty-five cells on the analytics screen asked for this and
   got nothing, so three columns of figures sat left-aligned against a wall
   of text. */
.ec-right { text-align: right; }

/* A value quoted verbatim: an identifier, a key, a model name. */
.ec-mono {
  font-family: ui-monospace, 'SF Mono', 'Cascadia Mono', 'Roboto Mono',
    Menlo, Consolas, monospace;
  font-size: 0.925em;
  font-variant-ligatures: none;
}
.ec-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-divider {
  height: 1px;
  margin-block: var(--ec-space-4);
  background: var(--ec-border);
}

.ec-icon-14 { width: 14px; height: 14px; }
.ec-icon-16 { width: 16px; height: 16px; }
.ec-icon-18 { width: 18px; height: 18px; }
.ec-icon-20 { width: 20px; height: 20px; }

/* The connection pill.

   Rendered empty and hidden by includes/layout.php and filled by
   assets/js/offline.js, so a page with no JavaScript shows nothing rather than
   a state it cannot keep true. It reuses .ec-badge for its shape and only adds
   the one thing a badge does not have: it is sometimes a button. */
.ec-conn[role="button"] {
  cursor: pointer;
  border: 0;
  font: inherit;
}

.ec-conn[role="button"]:hover { filter: brightness(1.06); }

.ec-conn[role="button"]:focus-visible {
  outline: 2px solid var(--ec-focus, currentColor);
  outline-offset: 2px;
}

/* The sync panel.

   Anchored bottom-right on a desktop and edge-to-edge along the bottom on a
   handset, because that is where a thumb is and because a teacher reads this
   standing up. It is deliberately not a modal: it must not stop her from
   looking at the register behind it while she decides what to do. */
.ec-sync-panel {
  position: fixed;
  inset-block-end: 1rem;
  inset-inline-end: 1rem;
  z-index: 1080;
  width: min(24rem, calc(100vw - 2rem));
  max-height: min(70vh, 34rem);
  display: flex;
  flex-direction: column;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg, 14px);
  box-shadow: var(--ec-shadow-lg, 0 12px 32px rgb(0 0 0 / 18%));
}

.ec-sync-panel__head,
.ec-sync-panel__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ec-space-3);
  padding: var(--ec-space-3) var(--ec-space-4);
}

.ec-sync-panel__head { border-block-end: 1px solid var(--ec-border); }
.ec-sync-panel__foot { border-block-start: 1px solid var(--ec-border); }

.ec-sync-panel__body {
  overflow-y: auto;
  padding: var(--ec-space-3) var(--ec-space-4);
}

.ec-sync-item {
  padding-block: var(--ec-space-3);
  border-block-end: 1px solid var(--ec-border);
}

.ec-sync-item:last-child { border-block-end: 0; }

.ec-sync-item__title {
  font-weight: var(--ec-weight-semibold);
  font-size: var(--ec-text-sm);
}

/* Refused work is marked, because it is the only kind the teacher has to do
   something about. Queued work needs no colour: waiting is the normal state. */
.ec-sync-item--conflict { border-inline-start: 3px solid var(--ec-warning); padding-inline-start: var(--ec-space-3); }
.ec-sync-item--rejected { border-inline-start: 3px solid var(--ec-danger);  padding-inline-start: var(--ec-space-3); }

@media (max-width: 575.98px) {
  .ec-sync-panel {
    inset-inline: 0;
    inset-block-end: 0;
    width: auto;
    border-radius: var(--ec-radius-lg, 14px) var(--ec-radius-lg, 14px) 0 0;
  }
}

/* ---------------------------------------------------------------------------
   Review: the stat tiles double as the state filter, so they are links.

   One row of counters that can be clicked, replacing a row of tiles above a row
   of chips that repeated the same four numbers. See includes/academic/review.php.
   --------------------------------------------------------------------------- */

.ec-stat--link { text-decoration: none; color: inherit; transition: border-color .12s, transform .12s; }
.ec-stat--link:hover { border-color: var(--tint, var(--ec-border)); transform: translateY(-1px); }
.ec-stat--link.is-active { border-color: var(--tint, var(--ec-accent)); box-shadow: inset 0 0 0 1px var(--tint, var(--ec-accent)); }

/* The filter row that replaced a whole card of chrome. */
.ec-filterbar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: 1rem; }
.ec-filterbar .ec-select { width: auto; min-width: 9rem; }

.ec-chip--sm { font-size: .78rem; padding: .18rem .6rem; }

/* A denser table for a document read on screen and then printed. It arrived
   for two class documents that belonged to basic education and went with it;
   it stays because a dense table is not a basic-education idea and the class
   is still the right one to reach for. */
.ec-table--dense th,
.ec-table--dense td { padding: var(--ec-space-2) var(--ec-space-3); }
.ec-table--dense tbody td { font-size: var(--ec-text-sm); }

.ec-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: flex-end;
}

.ec-field--actions { margin-inline-start: auto; }

/* Two card parts the class-council rule and the printed card ask for.

   `.ec-card__sub` is the sentence under a card's title — the promotion rule
   states itself there in words, because "below 40, below 50" is what a head
   teacher checks against and two numbers in a form are not. `--divided` is the
   rule between a card's sections, which is what separates stating the rule from
   applying it: two different acts that must not read as one form. */
.ec-card__sub {
  margin: 0.2rem 0 0;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
}

.ec-card__body--divided { border-block-start: 1px solid var(--ec-border); }

/* The small variant of the text input, used by the review filter row and the
   card's own cells. `.ec-input` had no size modifier at all. */
.ec-input--sm {
  padding: var(--ec-space-1) var(--ec-space-2);
  font-size: var(--ec-text-sm);
}

/* ==========================================================================
   THE TRANSACTION DIALOGUE  (assets/js/payflow.js)

   One dialogue for every place money moves: sending, approving on a handset,
   processing, and the verdict. A native <dialog>, so the focus trap, the
   Escape key and the backdrop are the browser's rather than Bootstrap's — the
   parent portal loads no Bootstrap at all.

   Restraint is the brief. One quiet spinner while something is genuinely
   happening, the three steps of a payment as a short list, and the facts that
   matter afterwards. The only motion is the spinner and a 160ms entrance, and
   prefers-reduced-motion removes both.
   ========================================================================== */

.ec-payflow {
  width: min(420px, calc(100vw - 2 * var(--ec-space-4)));
  max-height: calc(100vh - 2 * var(--ec-space-4));
  padding: var(--ec-space-6);
  overflow: auto;
  color: var(--ec-text);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-lg);
}

.ec-payflow[open] { animation: ec-payflow-in 160ms cubic-bezier(0.2, 0, 0, 1); }

.ec-payflow::backdrop { background: var(--ec-overlay); }

@keyframes ec-payflow-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

.ec-payflow__head { text-align: center; }

.ec-payflow__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  margin: 0 auto var(--ec-space-4);
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-full);
}

.ec-payflow--ok   .ec-payflow__mark { color: var(--ec-success); background: color-mix(in srgb, var(--ec-success) 14%, transparent); }
.ec-payflow--bad  .ec-payflow__mark { color: var(--ec-danger);  background: color-mix(in srgb, var(--ec-danger) 12%, transparent); }
.ec-payflow--hold .ec-payflow__mark,
.ec-payflow--ask  .ec-payflow__mark { color: var(--ec-warning); background: color-mix(in srgb, var(--ec-warning) 14%, transparent); }

/* The spinner is the ring itself; on the approve stage a handset sits inside it. */
.ec-payflow__spinner {
  position: absolute;
  inset: 0;
  border: 2.5px solid color-mix(in srgb, var(--ec-accent) 22%, transparent);
  border-top-color: var(--ec-accent);
  border-radius: 50%;
  animation: ec-payflow-spin 0.9s linear infinite;
}

.ec-payflow--busy .ec-payflow__mark { background: transparent; }

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

.ec-payflow__title {
  margin: 0;
  font-size: var(--ec-text-lg);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-leading-tight);
}

.ec-payflow__text {
  margin: var(--ec-space-2) 0 0;
  font-size: var(--ec-text-md);
  line-height: var(--ec-leading-normal);
  color: var(--ec-text-muted);
}

.ec-payflow__steps {
  display: grid;
  gap: var(--ec-space-2);
  padding: 0;
  margin: var(--ec-space-5) 0 0;
  list-style: none;
}

.ec-payflow__step {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-subtle);
}

.ec-payflow__bullet {
  flex: none;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--ec-border-strong);
  border-radius: 50%;
}

.ec-payflow__step.is-active { color: var(--ec-text); font-weight: var(--ec-weight-medium); }
.ec-payflow__step.is-active .ec-payflow__bullet { border-color: var(--ec-accent); box-shadow: inset 0 0 0 4px var(--ec-surface), inset 0 0 0 9px var(--ec-accent); }
.ec-payflow__step.is-done   { color: var(--ec-text-muted); }
.ec-payflow__step.is-done .ec-payflow__bullet {
  background: var(--ec-success) center / 12px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
  border-color: var(--ec-success);
}

.ec-payflow__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--ec-space-2) var(--ec-space-4);
  padding: var(--ec-space-4);
  margin: var(--ec-space-5) 0 0;
  font-size: var(--ec-text-sm);
  background: var(--ec-surface-2);
  border-radius: var(--ec-radius-md);
}

.ec-payflow__facts dt { color: var(--ec-text-muted); }

.ec-payflow__facts dd {
  margin: 0;
  font-weight: var(--ec-weight-medium);
  font-variant-numeric: tabular-nums;
  text-align: end;
  overflow-wrap: anywhere;
}

.ec-payflow__note,
.ec-payflow__fine {
  margin: var(--ec-space-4) 0 0;
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
  text-align: center;
}

.ec-payflow__note { font-variant-numeric: tabular-nums; }
.ec-payflow__fine { margin-top: var(--ec-space-2); }

.ec-payflow__actions {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--ec-space-2);
  margin-top: var(--ec-space-5);
}

.ec-payflow__actions .ec-btn { justify-content: center; width: 100%; }

@media (min-width: 480px) {
  .ec-payflow__actions { flex-direction: row-reverse; }
  .ec-payflow__actions .ec-btn { width: auto; flex: 1 1 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ec-payflow[open] { animation: none; }
  .ec-payflow__spinner { animation: none; border-color: var(--ec-accent); }
}

/* ==========================================================================
   THE BELL  (includes/partial-notifications.php, assets/js/notifications.js)
   ========================================================================== */

.ec-notif__menu {
  width: min(360px, calc(100vw - 2 * var(--ec-space-4)));
  padding: 0;
  overflow: hidden;
}

.ec-notif__head {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--ec-space-3) var(--ec-space-4);
  border-block-end: 1px solid var(--ec-border);
}

.ec-notif__title { font-weight: var(--ec-weight-semibold); }

.ec-notif__readall-form { margin: 0; }

.ec-notif__readall {
  padding: 0;
  font: inherit;
  font-size: var(--ec-text-xs);
  color: var(--ec-accent);
  cursor: pointer;
  background: none;
  border: 0;
}

.ec-notif__readall:hover { text-decoration: underline; }

.ec-notif__list { max-height: min(420px, 60vh); overflow-y: auto; }

.ec-notif__empty {
  padding: var(--ec-space-6) var(--ec-space-4);
  margin: 0;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
  text-align: center;
}

.ec-notif__item {
  display: flex;
  gap: var(--ec-space-3);
  padding: var(--ec-space-3) var(--ec-space-4);
  color: var(--ec-text);
  text-decoration: none;
  border-block-end: 1px solid var(--ec-border);
}

.ec-notif__item:last-child { border-block-end: 0; }
.ec-notif__item:hover { background: var(--ec-surface-hover); }
.ec-notif__item.is-unread { background: color-mix(in srgb, var(--ec-accent) 6%, var(--ec-surface)); }
.ec-notif__item.is-unread:hover { background: color-mix(in srgb, var(--ec-accent) 10%, var(--ec-surface)); }

.ec-notif__icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 32px;
  height: 32px;
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-full);
}

.ec-notif__text { display: grid; gap: 2px; min-width: 0; }

.ec-notif__line { font-size: var(--ec-text-sm); font-weight: var(--ec-weight-semibold); }
.ec-notif__item.is-unread .ec-notif__line::after {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-inline-start: var(--ec-space-2);
  vertical-align: middle;
  content: '';
  background: var(--ec-accent);
  border-radius: 50%;
}

.ec-notif__body {
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
  overflow-wrap: anywhere;
}

.ec-notif__when { font-size: var(--ec-text-2xs); color: var(--ec-text-subtle); }

.ec-notif__foot {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  justify-content: center;
  padding: var(--ec-space-3) var(--ec-space-4);
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
  text-decoration: none;
  background: var(--ec-surface-2);
  border-block-start: 1px solid var(--ec-border);
}

.ec-notif__foot:hover { color: var(--ec-text); }

/* ==========================================================================
   DASHBOARD BLOCKS
   The pieces the design sheet's dashboards are assembled from, and which
   nothing in the product had a name for: a tinted tray, a comparison panel,
   a day counter, and a row that is a count plus what to do about it.

   All four are used by includes/dashboards/_academics.php and by the family
   and teaching dashboards, so they live here rather than in any one of them.
   ========================================================================== */

/* The tray: a block set into a card, on the page colour, to say "this is a
   summary of what follows" without spending a second card on it. */
.ec-tray {
  padding: var(--ec-space-3);
  background: var(--ec-tray-bg);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

/* Comparison panels. Two or three side by side — one sub-system each, one
   term each — collapsing to a stack on a phone. minmax(0, 1fr) on purpose:
   without the 0 a long level name inside one panel widens the whole column
   and the pair stop being equal halves. */
.ec-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: var(--ec-space-3);
}

.ec-panel {
  padding: var(--ec-space-3) var(--ec-space-4);
  background: var(--ec-tray-bg);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

.ec-panel__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
  justify-content: space-between;
}

.ec-panel__name {
  display: inline-flex;
  gap: var(--ec-space-2);
  align-items: center;
  font-size: var(--ec-label-md);
  font-weight: var(--ec-weight-bold);
  letter-spacing: var(--ec-label-md-track);
  color: var(--ec-text);
}

.ec-panel__dot {
  width: 10px;
  height: 10px;
  border-radius: var(--ec-radius-full);
}

.ec-panel__meta {
  font-size: var(--ec-label-sm);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text-muted);
}

.ec-panel__figure {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: baseline;
  margin-block-start: var(--ec-space-2);
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-lg);
  font-weight: var(--ec-weight-bold);
  line-height: var(--ec-headline-lg-lh);
  letter-spacing: var(--ec-headline-lg-track);
  color: var(--ec-text);
}

.ec-panel__scale {
  font-family: var(--ec-font-sans);
  font-size: var(--ec-body-sm);
  font-weight: var(--ec-weight-normal);
  color: var(--ec-text-muted);
}

.ec-panel__rate {
  font-family: var(--ec-font-sans);
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-success);
}

/* The day counter: a number over a word, in a square, at the right of a row
   that already carries the date. It answers "how long have I got" without the
   reader doing the subtraction. */
.ec-daychip {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  min-width: 52px;
  padding: var(--ec-space-2) var(--ec-space-1);
  line-height: 1.1;
  color: var(--ec-accent);
  text-align: center;
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

.ec-daychip b {
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-md);
  font-weight: var(--ec-weight-bold);
}

.ec-daychip span {
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  text-transform: uppercase;
}

/* Inside a week. Amber rather than red: a deadline approaching is not a
   deadline missed, and spending the alarm colour on the first leaves nothing
   for the second. */
.ec-daychip--soon {
  color: var(--ec-warning);
  background: var(--ec-warning-soft);
}

/* A count, what it is, and where to go about it. The supervision card and
   the exports card are both built from it — one carries a figure in the
   leading slot, the other an icon. */
.ec-watch {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-2);
}

.ec-watch__row {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-3);
  color: inherit;
  background: var(--ec-tray-bg);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  transition: background var(--ec-transition-fast);
}

.ec-watch__row:hover {
  color: inherit;
  background: var(--ec-surface-hover);
}

.ec-watch__n {
  min-width: 36px;
  justify-content: center;
  font-size: var(--ec-label-md);
  font-weight: var(--ec-weight-bold);
}

.ec-watch__icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 34px;
  height: 34px;
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

.ec-watch__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.ec-watch__label {
  font-size: var(--ec-body-md);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-body-md-lh);
  color: var(--ec-text);
}

.ec-watch__meta {
  font-size: var(--ec-body-sm);
  line-height: var(--ec-body-sm-lh);
  color: var(--ec-text-muted);
}

.ec-watch__go {
  flex-shrink: 0;
  color: var(--ec-text-subtle);
}

/* A card foot carrying a note on one side and a link on the other, which is
   what the sheet puts under every table. */
.ec-card__foot--split {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3);
  align-items: center;
  justify-content: space-between;
}

/* ==========================================================================
   THE FAMILY DASHBOARD
   The design sheet specifies this screen twice — Android under 768px, desktop
   above it — and includes/partial-family-dashboard.php renders both out of
   one DOM. This is the half of that which is CSS.
   ========================================================================== */

/* Which width a block belongs to. Only two blocks in the product need this:
   the phone's single "next lesson" strip, and the desktop's full timetable
   card. Everything else is one element that reflows, and anything reaching
   for these a third time should be reflowing instead. */
.ec-only-phone { display: none; }

@media (max-width: 767.98px) {
  /* flex, because every phone-only block here is a row — a notice strip, a
     lesson strip. A heading is the exception and says so. */
  .ec-only-phone { display: flex; }
  h1.ec-only-phone,
  h2.ec-only-phone,
  h3.ec-only-phone { display: block; }

  .ec-only-desk  { display: none !important; }
}

/* --- The identity strip ------------------------------------------------- */

.ec-idcard {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-4);
  align-items: center;
  padding: var(--ec-space-4) var(--ec-space-5);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  box-shadow: var(--ec-shadow-sm);
}

/* The photograph, with the enrolled tick over its corner. A parent picking
   between three children reads a face faster than two initials. */
.ec-idcard__face {
  position: relative;
  flex-shrink: 0;
}

.ec-idcard__tick {
  position: absolute;
  right: 0;
  bottom: 0;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: #fff;
  background: var(--ec-success);
  border: 2px solid var(--ec-surface);
  border-radius: var(--ec-radius-full);
}

.ec-idcard__body {
  flex: 1;
  min-width: 0;
}

/* On a phone the face and the name stay side by side — the sheet's student
   card is a row, and a 200px floor on the body wrapped it into a column with
   an 88px avatar alone on the first line. The chips are the only part that
   wraps, which is what they are for. */
@media (max-width: 767.98px) {
  .ec-idcard {
    gap: var(--ec-space-3);
    align-items: flex-start;
    padding: var(--ec-space-4);
  }

  .ec-idcard__face .ec-avatar { width: 64px; height: 64px; font-size: var(--ec-headline-md); }

  .ec-idcard__chips { flex-basis: 100%; }

  .ec-idcard__switch { flex-basis: 100%; }
}

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

.ec-idcard__name {
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-lg);
  font-weight: var(--ec-weight-bold);
  line-height: var(--ec-headline-lg-lh);
  letter-spacing: var(--ec-headline-lg-track);
}

.ec-idcard__meta {
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-body-sm);
  line-height: var(--ec-body-sm-lh);
  color: var(--ec-text-muted);
}

.ec-idcard__meta b {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
  font-variant-numeric: tabular-nums lining-nums;
}

.ec-idcard__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  margin-block-start: var(--ec-space-3);
}

/* The child switcher. A tray rather than a bare select, because on the
   desktop it is the one control on this screen that changes what every figure
   below it is about. */
.ec-idcard__switch {
  display: flex;
  flex-direction: column;
  gap: var(--ec-space-2);
  padding: var(--ec-space-3);
  background: var(--ec-tray-bg);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

.ec-idcard__switch-label {
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text-muted);
  text-transform: uppercase;
}

.ec-idcard__switch-form {
  display: flex;
  gap: var(--ec-space-2);
  align-items: center;
  margin: 0;
}

/* --- The phone's next-lesson strip -------------------------------------- */

.ec-nextclass {
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-3) var(--ec-space-4);
  color: inherit;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  box-shadow: var(--ec-shadow-sm);
}

.ec-nextclass:hover { color: inherit; }

.ec-nextclass__icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 40px;
  height: 40px;
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

.ec-nextclass__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.ec-nextclass__when {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-semibold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-text-muted);
}

.ec-nextclass__what {
  font-size: var(--ec-body-lg);
  font-weight: var(--ec-weight-semibold);
  line-height: var(--ec-body-lg-lh);
  color: var(--ec-text);
}

/* --- A section heading with no card under it ----------------------------

   Carries the rule above it, the way digisolgroup.net sets every section
   heading. It is drawn as a block rather than a border so the bar is 28px
   wide and the heading is not, which is the whole point of it. */

.ec-sectionhead {
  margin-block: var(--ec-space-5) var(--ec-space-2);
  font-family: var(--ec-font-display);
  font-size: var(--ec-headline-lg);
  font-weight: var(--ec-weight-bold);
  line-height: var(--ec-headline-lg-lh);
  letter-spacing: var(--ec-headline-lg-track);
}

.ec-sectionhead::before {
  display: block;
  width: 28px;
  height: var(--ec-rule-w);
  margin-block-end: var(--ec-space-3);
  content: '';
  background: var(--ec-rule);
}

/* --- The phone's dismissable notice strip -------------------------------- */

.ec-flash {
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-3);
  margin-block-end: var(--ec-space-4);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

.ec-flash__icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 32px;
  height: 32px;
  color: var(--ec-accent-on);
  background: var(--ec-accent);
  border-radius: var(--ec-radius-full);
}

.ec-flash__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.ec-flash__label {
  font-size: var(--ec-label-sm);
  font-weight: var(--ec-weight-bold);
  letter-spacing: var(--ec-label-sm-track);
  color: var(--ec-accent);
  text-transform: uppercase;
}

.ec-flash__text {
  overflow: hidden;
  font-size: var(--ec-body-sm);
  line-height: var(--ec-body-sm-lh);
  color: var(--ec-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ec-flash__close { flex-shrink: 0; }

/* --- The payment card's operator row and buttons ------------------------ */

.ec-fees__ops {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  align-items: center;
  margin-block-end: var(--ec-space-3);
}

.ec-fees__actions {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: var(--ec-space-2);
}

@media (max-width: 575.98px) {
  .ec-fees__actions { grid-template-columns: 1fr; }
}

/* --- A row that is a document: icon, name, state ------------------------ */

.ec-docrow {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
}

.ec-docrow__icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 40px;
  height: 40px;
  color: var(--ec-accent);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

/* --- A KPI whose value is a word, not a figure -------------------------- */

/* "Up to date", "Very good". The headline-xl step is sized for two or three
   digits; a two-word value at 32px wraps the card and pushes its tray out of
   line with the three cards beside it. */
.ec-stat__value--word {
  font-size: var(--ec-headline-lg);
  line-height: var(--ec-headline-lg-lh);
  letter-spacing: var(--ec-headline-lg-track);
}

/* ==========================================================================
   TABLES THAT BECOME CARDS
   Below 768px a table with five columns is either a horizontal scroll nobody
   finds or a column of unreadable slivers. .ec-table--stack turns each row
   into a card and labels each cell from the column it came from — the label
   is read out of data-label, so the header is written once and the phone
   layout cannot fall out of step with it.

   Applied deliberately, not to every table: a register a teacher scrolls
   sideways on purpose is a different thing from a five-row summary a parent
   reads on a phone.
   ========================================================================== */

@media (max-width: 767.98px) {
  .ec-table--stack,
  .ec-table--stack tbody,
  .ec-table--stack tbody td {
    display: block;
    width: auto;
  }

  /* Two columns, so one cell can be promoted onto the subject's own line.
     Everything else spans both and reads as a labelled row underneath. */
  .ec-table--stack tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--ec-space-3);
    width: auto;
  }

  .ec-table--stack tbody td { grid-column: 1 / -1; }

  /* The header is where the labels come from, so it stays in the DOM for
     assistive technology and is taken off screen rather than hidden: a
     display:none thead removes the column names from the accessibility tree
     as well as from the page. */
  .ec-table--stack thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .ec-table--stack tr {
    padding: var(--ec-space-3);
    margin-block-end: var(--ec-space-2);
    background: var(--ec-tray-bg);
    border: 1px solid var(--ec-border);
    border-radius: var(--ec-radius-md);
  }

  .ec-table--stack tr:last-child { margin-block-end: 0; }

  .ec-table--stack tbody td {
    display: flex;
    gap: var(--ec-space-3);
    align-items: baseline;
    justify-content: space-between;
    padding: var(--ec-space-1) 0;
    text-align: right;
    white-space: normal;
    border: 0;
  }

  .ec-table--stack tbody td::before {
    content: attr(data-label);
    flex-shrink: 0;
    font-size: var(--ec-label-sm);
    font-weight: var(--ec-weight-semibold);
    letter-spacing: var(--ec-label-sm-track);
    color: var(--ec-text-muted);
    text-align: left;
    text-transform: uppercase;
  }

  /* The row's subject reads as the card's heading: flush left, no label of
     its own, and no rule underneath — the rule belongs to the whole row and
     is drawn by the first detail cell below, which spans both columns. A
     border here would stop dead under a promoted figure. */
  .ec-table--stack tbody td:first-child {
    display: block;
    grid-column: 1;
    grid-row: 1;
    padding-block-end: 0;
    font-size: var(--ec-body-lg);
    text-align: left;
  }

  .ec-table--stack tbody td:first-child::before { content: none; }

  /*
   * The figure the row is about, lifted onto the heading line.
   *
   * The sheet's phone layout reads "Mathematics … 17.0 / 20" on one line and
   * the detail underneath, not four label/value rows of equal weight. A row
   * marks its own headline figure with .ec-cell--lead; a table that marks
   * none simply stacks, which is what every other one does.
   */
  .ec-table--stack tbody .ec-cell--lead {
    grid-column: 2;
    grid-row: 1;
    display: block;
    padding: 0;
    font-size: var(--ec-headline-md);
    text-align: right;
    white-space: nowrap;
  }

  .ec-table--stack tbody .ec-cell--lead::before { content: none; }

  /* The rule under the heading line, drawn by whichever cell opens the
     detail block — the second, or the third when the second was promoted. */
  .ec-table--stack tbody td:nth-child(2):not(.ec-cell--lead),
  .ec-table--stack tbody td:nth-child(2).ec-cell--lead ~ td:nth-child(3) {
    padding-block-start: var(--ec-space-2);
    margin-block-start: var(--ec-space-2);
    border-block-start: 1px solid var(--ec-border);
  }

  .ec-table--stack tbody tr:hover { background: var(--ec-tray-bg); }

  /* The wrapper's horizontal scroll has nothing left to scroll. */
  .ec-table--stack { white-space: normal; }
}
