/*
 * EduOnPoint: the public front pages.
 *
 * Loaded sixth, and only by the two public shells —
 * includes/layout-marketing.php and includes/layout-auth.php. Not by
 * includes/layout.php, layout-lite.php, error.php, or the owner, marketer and
 * secretariat shells.
 *
 * ---------------------------------------------------------------------------
 * Why this file exists
 * ---------------------------------------------------------------------------
 *
 * The sign-in stage used to live in modules.css, which EVERY shell loads,
 * including the signed-in application. That put the sign-in screen's CSS on
 * every dashboard, in a product whose own landing page argues about page
 * weight. It sat there for a cascade reason rather than a filing one: the
 * comment at marketing.css:55 records that .ec-auth could not override
 * marketing.css, so it was pushed earlier in the order to escape a sheet it
 * could not beat.
 *
 * A sixth slot inverts that. Anything here beats both modules.css and
 * marketing.css at equal specificity, so the stage can finally sit in a
 * marketing-adjacent sheet, and the sign-in panel and the landing page can
 * share the four things they now have in common — the pastel tiles, the pill
 * badge, the trust strip and the glass card — without a third file for the
 * shared parts.
 *
 * So: do NOT add .ec-auth to the three-selector --mk-* root at
 * marketing.css:62. That root also carries `background: var(--ec-bg)`, which
 * is the exact collision the comment there describes. Where the panel needs
 * display type, write the clamp() on the element, as .ec-auth__statement does.
 *
 * ---------------------------------------------------------------------------
 * What did NOT move
 * ---------------------------------------------------------------------------
 *
 * .ec-auth__brand and its three companions stay in modules.css, because
 * error.php renders that class and loads only four sheets — not this one.
 * Its default there is now `display: flex` and this file hides it above the
 * split breakpoint, rather than the other way round. Inverted deliberately:
 * the old default of `display: none` meant the branded error page showed no
 * wordmark at all on any screen wider than 900px, while tools/deploy_check.php
 * asserts on that class.
 *
 * ---------------------------------------------------------------------------
 * Order
 * ---------------------------------------------------------------------------
 *
 *   1  Shared pieces: the pill badge, the pastel tile, the trust strip
 *   2  Sign-in: the split stage
 *   3  Sign-in: the panel
 *   4  Sign-in: the card and its fields
 *   5  Sign-in: breakpoints
 *   6  Landing: the hero wash, the bullets, the trust trio
 *   7  Landing: the drawn dashboard
 *   8  Landing: the feature grid
 *   9  Landing: the stats slab
 *  10  Landing: breakpoints
 *  11  The four kinds of school (/schools/)
 *  12  The primary call to action, in green
 *  13  Long labels on a narrow screen
 *  14  Extra-large screens
 */

/* ==========================================================================
   1. SHARED PIECES
   ==========================================================================
   Three small objects the sign-in panel and the landing page both use. They
   live above everything else because both halves of the file consume them,
   and because a shape defined twice is a shape that drifts. */

:root {
  /* Bigger than --ec-radius-lg on purpose, and only ever on the two large
     surfaces: the sign-in card and the hero frame. A 14px corner on a 460px
     card reads as a panel; a 20px corner reads as a card. Not promoted to
     tokens.css because nothing in the application is this size. */
  --front-r-card: 20px;
  --front-r-tile: 14px;
}

/* --- The pill badge ------------------------------------------------------- */

.ec-front-badge {
  display: inline-flex;
  gap: var(--ec-space-2);
  align-items: center;
  padding: 6px 14px 6px 10px;
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent-hover);
  background: var(--ec-accent-soft);
  border: 1px solid transparent;
  border-radius: var(--ec-radius-full);
}

/* On a painted panel the soft accent fill disappears, so the dark variant is
   a wash of white rather than a wash of the accent — the same move --mk-green
   makes in marketing.css, and for the same reason: a surface that pins its own
   background owns its own ink. White on the composited fill measures 15.6:1. */
.ec-front-badge--dark {
  color: #fff;
  background: rgb(255 255 255 / 0.12);
  border-color: rgb(255 255 255 / 0.20);
}

/* --- The pastel tile ------------------------------------------------------ */
/* A coloured line icon on a tinted square. The four tints and their inks are
   --ec-tint-1..4 in tokens.css, which carries the measured ratios and the
   dark-theme lift. Anything using one has to sit on --ec-surface and not on
   --ec-surface-2: on the warm tile the tints measure 1.00-1.04:1 and are
   simply not there. */

.ec-front-tile {
  display: grid;
  flex: none;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--front-r-tile);
}

.ec-front-tile--1 { color: var(--ec-tint-1-on); background: var(--ec-tint-1); }
.ec-front-tile--2 { color: var(--ec-tint-2-on); background: var(--ec-tint-2); }
.ec-front-tile--3 { color: var(--ec-tint-3-on); background: var(--ec-tint-3); }
.ec-front-tile--4 { color: var(--ec-tint-4-on); background: var(--ec-tint-4); }

/* --- The trust strip's icon ----------------------------------------------- */
/* Outlined rather than filled: these three sit under a form or under a hero,
   where four more pastel squares would out-shout the thing they are beside. */

.ec-front-trust__ico {
  display: grid;
  flex: none;
  place-items: center;
  width: 38px;
  height: 38px;
  margin-block-end: var(--ec-space-1);
  color: var(--ec-accent);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

/* ==========================================================================
   2. SIGN IN — the stage
   ==========================================================================
   A full-bleed split: a photographic panel under a deep green wash, and the
   form beside it on the page's own surface. No site header and no footer — the
   split measures a whole viewport and anything in flow above it would make the
   document taller than the window. What the header used to provide is folded
   into the column instead: a back link to the public site, and the EN/FR
   switch, on one row above the heading.

   The panel's wash is painted from the brand ramp rather than from
   --ec-accent, so it stays the same deep green in both themes — the accent
   token deliberately lifts in dark, and a panel that lifts with it stops being
   the thing the product is. The photograph behind it is optional: with no file
   on disk the two gradients paint the same panel on their own.
   ========================================================================== */

.ec-auth {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--ec-surface);
}

/* ==========================================================================
   3. SIGN IN — the panel
   ========================================================================== */

/* Sticky, and a whole viewport tall rather than as tall as the row.
   Registration is a long form and the sign-in screen carries a demo block in
   development, so the column beside this one is routinely taller than the
   window. Left to stretch, the panel grows with it and its own content —
   pinned to the foot by space-between — falls below the fold, where the
   statement the panel exists to make is never read. Sticky keeps the
   composition in the window while the form scrolls past it. */
.ec-auth__aside {
  position: sticky;
  top: 0;
  align-self: start;
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
  color: #fff;

  /*
   * A surface that paints its own background owns its own ink. Same local
   * re-key .ec-mk-band makes in marketing.css, and it is load-bearing rather
   * than tidy: `color: #fff` above does not reach the statement, because a
   * heading is painted `color: var(--ec-text)` by components.css and that beats
   * inheritance. Without these two lines the panel's one large sentence renders
   * in the page's dark ink on a dark green photograph and is invisible.
   */
  --ec-text: #fff;
  --ec-text-muted: rgb(255 255 255 / 0.82);

  background-color: var(--ec-primary-900);
}

/* The photograph, and it is optional. Absolutely positioned so it has no say
   in the panel's height: the composition must not move when the file arrives
   or when it is taken away. */
.ec-auth__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The wash over it, and it is two gradients rather than one.
   Written with color-mix over the ramp rather than as literal rgba(), which is
   what keeps the panel on the brand green if the ramp is ever re-keyed.

   The diagonal is the composition: nearly opaque on the lockup's corner,
   thinning across the frame so the photograph is actually visible on the side
   the copy does not occupy.

   The vertical one under it is not decoration. The panel's copy is pinned to
   the foot and the photograph behind it is whatever a given school's artwork
   happens to be — assets/img/school-campus.webp is a white building in full
   sun, and on the diagonal alone the statement measured under 2:1 against it.
   A scrim that fades out by the upper third guarantees the ink without
   flattening the picture. */
.ec-auth__aside::after {
  position: absolute;
  inset: 0;
  content: '';
  background:
    linear-gradient(
      0deg,
      color-mix(in srgb, var(--ec-primary-900) 94%, transparent) 0%,
      color-mix(in srgb, var(--ec-primary-900) 72%, transparent) 34%,
      color-mix(in srgb, var(--ec-primary-900) 38%, transparent) 62%,
      transparent 88%),
    linear-gradient(
      120deg,
      color-mix(in srgb, var(--ec-primary-900) 90%, transparent),
      color-mix(in srgb, var(--ec-primary-800) 52%, transparent),
      color-mix(in srgb, var(--ec-primary-900) 26%, transparent));
}

.ec-auth__panel {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  padding: clamp(2rem, 3.2vw, 2.8rem) clamp(2rem, 4vw, 3.4rem);
}

/* --- The lockup ----------------------------------------------------------- */

.ec-auth__mark {
  display: inline-flex;
  gap: var(--ec-space-3);
  align-items: center;
  align-self: flex-start;
  font-size: 1.28rem;
  font-weight: var(--ec-weight-bold);
  color: #fff;
  letter-spacing: -0.02em;
}

.ec-auth__mark:hover { color: #fff; }

/* White tile, green mark. On a panel this dark the app shell's translucent
   wash disappears; the reference draws a solid chip and it is the one thing
   on the panel that has to read as an object rather than as light. */
.ec-auth__mark .ec-sidebar__logo {
  width: 39px;
  height: 39px;
  color: var(--ec-accent);
  background: #fff;
  border: 0;
  border-radius: 12px;
}

/* "On", in the light mint of the panel. --ec-primary-300 rather than the
   400: at 39px of white beside it the 400 reads as the same green as the
   background and the wordmark stops splitting. */
.ec-auth__markname > span:first-child { color: var(--ec-primary-300); }

/* --- The pitch ------------------------------------------------------------ */

.ec-auth__pitch {
  max-width: 560px;
  padding-block-end: var(--ec-space-8);
}

.ec-auth__eyebrow {
  margin: 0 0 var(--ec-space-4);
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-bold);
  color: var(--ec-primary-300);
  text-transform: uppercase;
  letter-spacing: 0.13em;
}

/* The statement. The one line on the screen that is there to be read rather
   than filled in, so it is allowed to be large: the clamp runs from a phone's
   headline to display size on a wide monitor. */
.ec-auth__statement {
  margin: 0;
  font-size: clamp(2.4rem, 4vw, 4.3rem);
  font-weight: var(--ec-weight-bold);
  line-height: 1.02;
  letter-spacing: -0.05em;
  text-wrap: balance;
}

.ec-auth__lede {
  max-width: 510px;
  margin: var(--ec-space-5) 0 0;
  font-size: var(--ec-text-base);
  line-height: var(--ec-leading-normal);
  color: rgb(255 255 255 / 0.82);
}

/* --- The points ----------------------------------------------------------- */
/* A row of pills rather than a grid of tiles: they wrap on their own at any
   panel width, and four short phrases in a line read as one claim about the
   product where four boxes read as four features. */

.ec-auth__points {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-3) var(--ec-space-2);
  padding: 0;
  margin: var(--ec-space-6) 0 0;
  list-style: none;
}

.ec-auth__points li {
  padding: 8px 11px;
  font-size: var(--ec-text-xs);
  color: rgb(255 255 255 / 0.92);
  background: rgb(255 255 255 / 0.09);
  border: 1px solid rgb(255 255 255 / 0.22);
  border-radius: var(--ec-radius-full);
}

/* ==========================================================================
   4. SIGN IN — the column, and its fields
   ========================================================================== */

.ec-auth__main {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(2rem, 3vw, 2.5rem) var(--ec-space-8);
  background: var(--ec-surface);
}

.ec-auth__col {
  width: min(440px, 100%);
}

/* No card. The form sits on the page's own surface, which is what makes the
   split read as two halves of one screen rather than as a panel with a box
   dropped on it. .ec-auth__form stays as the hook the reveal script and the
   field sizing below both select through. */
.ec-auth__form { width: 100%; }

/* --- The narrow-screen wordmark ------------------------------------------- */
/* A restatement, not a move. modules.css owns the canonical copy because
   error.php renders this class and loads that sheet rather than this one — see
   the note at the head of this file — and these screens do not load modules.css
   at all. Left to the cascade the element is a bare `display: inline` anchor,
   which drops the logo tile onto its own line above the name. */

.ec-auth__brand {
  display: flex;
  align-items: center;
  margin-block-end: var(--ec-space-8);
  font-size: var(--ec-text-lg);
  font-weight: var(--ec-weight-bold);
  color: var(--ec-text);
  letter-spacing: -0.02em;
}

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

.ec-auth__brand .ec-sidebar__logo { margin-inline-end: var(--ec-space-3); }

.ec-auth__brand > span:last-child { color: var(--ec-accent); }

/* --- The row above the heading -------------------------------------------- */
/* Back on the left, the language switch on the right. This row is what the
   suppressed site header used to carry, and it is the reason the header can
   be suppressed: somebody who reached the sign-in screen from a search result
   still has a way back to the site and a way into the other language. */

.ec-auth__toprow {
  display: flex;
  gap: var(--ec-space-4);
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--ec-space-8);
}

.ec-auth__back,
.ec-auth__lang {
  display: inline-flex;
  gap: var(--ec-space-2);
  align-items: center;
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text-muted);
  transition: color var(--ec-transition-fast);
}

.ec-auth__back:hover,
.ec-auth__lang:hover { color: var(--ec-accent); }

.ec-auth__back svg,
.ec-auth__lang svg { flex: none; }

/* --- The heading ---------------------------------------------------------- */

.ec-auth__title {
  margin: 0;
  font-size: clamp(1.75rem, 3vw, 2.35rem);
  font-weight: var(--ec-weight-bold);
  line-height: 1.08;
  color: var(--ec-text);
  letter-spacing: -0.045em;
}

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

/* --- The progress bar ----------------------------------------------------- */
/* Three inert segments above the registration form. Decorative and marked
   aria-hidden where it is rendered: it says "this is short" to somebody
   looking at it, and it has no state a screen reader could usefully announce
   because the form is a single page. */

.ec-auth__steps {
  display: flex;
  gap: var(--ec-space-2);
  margin: var(--ec-space-6) 0 var(--ec-space-6);
}

.ec-auth__steps span {
  flex: 1;
  height: 4px;
  background: var(--ec-surface-2);
  border-radius: 5px;
}

.ec-auth__steps span.is-on { background: var(--ec-accent); }

/* --- Fields --------------------------------------------------------------- */
/* Taller than the application's 38px toolbar input. This is a form somebody
   fills in on a phone with one thumb, and 49px is also what gives the reveal
   control its 44px touch target. */

.ec-auth__form { margin-block-start: var(--ec-space-6); }

.ec-auth__form .ec-input,
.ec-auth__form .ec-select {
  height: 49px;
  padding-inline: 14px;
  font-size: var(--ec-text-base);
  background: var(--ec-surface);
  border-radius: 11px;
  /* Border stays --ec-border-strong, inherited from .ec-input. The quieter
     --ec-border is a hairline between blocks; a field edge is a UI component
     boundary and WCAG 1.4.11 wants 3:1 for one. That token exists precisely
     because no tint of the page clears it — see tokens.css — and softening
     the edge here measured 1.36:1 on the dark surface. */
}

.ec-auth__form .ec-input:hover,
.ec-auth__form .ec-select:hover { border-color: var(--ec-text-subtle); }

.ec-auth__form .ec-input:focus,
.ec-auth__form .ec-select:focus {
  border-color: var(--ec-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ec-accent) 12%, transparent);
}

.ec-auth__form .ec-field { margin-block-end: 17px; }

.ec-auth__form .ec-field__label {
  margin-block-end: 7px;
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-bold);
  color: var(--ec-text);
}

/* The label and a link on one baseline — "Password" and "Forgot password?".
   The link is a SIBLING of the label and never a child: nested, a click on it
   would also resolve the label's `for` and drop a caret into the field the
   visitor is navigating away from. */
.ec-field__labelrow {
  display: flex;
  gap: var(--ec-space-3);
  align-items: baseline;
  justify-content: space-between;
  margin-block-end: 7px;
}

.ec-field__labelrow .ec-field__label { margin-block-end: 0; }

.ec-auth__form .ec-btn {
  height: 50px;
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-bold);
  border-radius: 11px;
}

.ec-auth__form .ec-btn--primary {
  box-shadow: 0 10px 25px color-mix(in srgb, var(--ec-accent) 20%, transparent);
  transition: background var(--ec-transition-fast), translate var(--ec-transition-fast);
}

.ec-auth__form .ec-btn--primary:hover { translate: 0 -1px; }

@media (prefers-reduced-motion: reduce) {
  .ec-auth__form .ec-btn--primary:hover { translate: none; }
}

/* Label centred, arrow at the edge. Taking the icon out of the flex flow is
   what keeps the label optically centred in the button rather than centred
   with the arrow beside it, which reads as an off-centre label. */
.ec-btn--arrow { position: relative; }

.ec-btn--arrow > svg {
  position: absolute;
  inset-inline-end: var(--ec-space-4);
}

/* --- The two-column form grid --------------------------------------------- */
/* Registration only. Short answers pair up — a name beside a phone number —
   and anything that needs the width says so with .ec-auth__full. It collapses
   to one column at 500px, where two 200px fields stop being fillable. */

.ec-auth__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 13px;
}

.ec-auth__grid > .ec-auth__full { grid-column: 1 / -1; }

.ec-field__wrap {
  position: relative;
  display: block;
}

.ec-field__wrap .ec-input { width: 100%; }

/* z-index, and it is load-bearing on exactly one field.
   The reveal script inserts a second .ec-field__wrap between this wrapper and
   the password input, and that wrapper is itself positioned and comes later
   in the DOM — so it paints over this icon, and the input's opaque --ec-surface
   fill hides it completely. The identifier field above has no inner wrap and
   showed its icon fine, which is what made this look like a missing symbol
   rather than a stacking order. pointer-events: none is what keeps the raised
   icon from eating the click that should focus the field. */
.ec-field__icon {
  position: absolute;
  inset-inline-start: 15px;
  top: 50%;
  z-index: 2;
  width: 18px;
  height: 18px;
  color: var(--ec-text-subtle);
  pointer-events: none;
  translate: 0 -50%;
}

.ec-field__wrap--icon .ec-input { padding-inline-start: 44px; }

/* The reveal. A word rather than an eye, which is what the reference draws and
   what survives being read at 12px: "Show" states what the control does, where
   a crossed-out eye has to be decoded into either "it is hidden" or "hide it"
   and is read both ways by different people.

   44px tall, the comfortable touch target, sitting 2px inside a 49px field. */
.ec-field__reveal {
  position: absolute;
  inset-inline-end: 4px;
  top: 50%;
  display: grid;
  place-items: center;
  height: 44px;
  padding-inline: var(--ec-space-2);
  font-size: var(--ec-text-xs);
  font-weight: var(--ec-weight-bold);
  color: var(--ec-text-subtle);
  cursor: pointer;
  background: none;
  border: 0;
  border-radius: var(--ec-radius-sm);
  translate: 0 -50%;
  transition: color var(--ec-transition-fast);
}

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

.ec-field__reveal[aria-pressed='true'] { color: var(--ec-accent); }

/* Room for the control, so a long password does not run under it. */
.ec-field__wrap .ec-input[type='password'],
.ec-field__wrap .ec-input[type='text'] { padding-inline-end: 70px; }

/* A DIRECT child, not a descendant. The reveal script at
   includes/layout-auth-end.php wraps the password input in a SECOND
   .ec-field__wrap nested inside this one, so a descendant selector here also
   matches the password field the moment its type flips to `text` — and, being
   later in source order than the padding-inline-end rule above, it wins and
   the reveal control sits on top of the last characters typed. */
.ec-auth__form .ec-field__wrap--icon > .ec-input[type='text'] { padding-inline-end: 14px; }

/* --- The remember row ----------------------------------------------------- */
/* "Keep me signed in" on the left, "Forgot password?" on the right. One row,
   because they are the two things somebody does instead of typing a password
   and putting them on separate lines reads as two unrelated afterthoughts. */

.ec-auth__remember {
  display: flex;
  gap: var(--ec-space-4);
  align-items: center;
  justify-content: space-between;
  margin: 3px 0 var(--ec-space-5);
}

.ec-check {
  display: inline-flex;
  gap: 7px;
  align-items: center;
  font-size: var(--ec-text-sm);
  color: var(--ec-text-muted);
  cursor: pointer;
}

.ec-check input {
  flex: none;
  width: 16px;
  height: 16px;
  accent-color: var(--ec-accent);
}

/* The consent line on the registration form. A checkbox whose label runs to
   two lines, so the box sits at the top of it rather than centred against a
   block of text. */
.ec-check--top { align-items: flex-start; }

.ec-check--top input { margin-block-start: 2px; }

.ec-check--top a {
  font-weight: var(--ec-weight-bold);
  color: var(--ec-accent);
}

/* --- The closing matter --------------------------------------------------- */

/* A labelled rule. The empty .ec-divider elsewhere in these forms is a plain
   line and stays one; this is the variant that carries a word. */
.ec-auth__rule {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  margin-block: var(--ec-space-6);
  font-size: var(--ec-text-xs);
  color: var(--ec-text-subtle);
}

.ec-auth__rule::before,
.ec-auth__rule::after {
  flex: 1;
  height: 1px;
  content: '';
  background: var(--ec-border);
}

.ec-auth__switch {
  margin-block-start: var(--ec-space-6);
  font-size: var(--ec-text-md);
  color: var(--ec-text-muted);
  text-align: center;
}

.ec-auth__switch a {
  font-weight: var(--ec-weight-bold);
  color: var(--ec-accent);
}

.ec-auth__notice {
  padding: 12px 13px;
  margin-block-start: var(--ec-space-5);
  font-size: var(--ec-text-xs);
  line-height: 1.5;
  color: var(--ec-accent-hover);
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-md);
}

.ec-auth__terms {
  margin-block-start: var(--ec-space-5);
  font-size: var(--ec-text-2xs);
  line-height: 1.55;
  color: var(--ec-text-subtle);
  text-align: center;
}

.ec-auth__terms a {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent);
}

/* ==========================================================================
   5. BREAKPOINTS
   ==========================================================================

   The panel goes whole rather than shrinking into a stripe.

   Why it is hidden and not stacked: everything on it is either repeated on the
   public pages the back link reaches, or decorative. Stacking an eyebrow, a
   display line, a paragraph and five pills above the form puts the email field
   about six hundred pixels down, below the fold on every phone, on the screen
   a parent opens to check their child's marks.

   850px is the reference's own breakpoint and it is the right one for this
   composition: the panel carries no grid now, only a wrapping row of pills, so
   it survives a narrower column than the old two-by-two tiles did. */
@media (max-width: 849.98px) {
  .ec-auth { grid-template-columns: minmax(0, 1fr); }

  .ec-auth__aside { display: none; }

  .ec-auth__main {
    min-height: 100vh;
    min-height: 100dvh;
    padding: var(--ec-space-8) var(--ec-space-6);
  }

  .ec-auth__col { width: 100%; }
}

/* The wordmark inside the column is the narrow-screen fallback: above the
   split the panel beside it already carries the brand, and printing it twice
   is the kind of doubling that makes a page look assembled rather than
   designed.

   Stated as a hide rather than a show. modules.css declares it visible, so
   error.php — which loads four sheets and not this one — gets a wordmark at
   every width. See the note at the head of this file. */
@media (min-width: 850px) {
  .ec-auth__brand { display: none; }
}

@media (max-width: 849.98px) {
  .ec-auth__brand { margin-block-end: var(--ec-space-8); }
}

/* One column of fields. Two 190px boxes are not a form anybody fills in. */
@media (max-width: 499.98px) {
  .ec-auth__grid { grid-template-columns: minmax(0, 1fr); }

  .ec-auth__grid > .ec-auth__full { grid-column: auto; }
}

@media (max-width: 429.98px) {
  .ec-auth__main { padding: var(--ec-space-6) var(--ec-space-4); }

  .ec-auth__toprow { margin-block-end: var(--ec-space-6); }
}

/* Short windows. The panel is a fixed viewport tall and clips what does not
   fit, so on a laptop in landscape the part that carries the least goes
   first: the line of context. The statement and the pills are the panel's
   argument and they stay. */
@media (max-height: 700px) {
  .ec-auth__lede { display: none; }
}

/* ==========================================================================
   6. LANDING — hero: the wash, the bullets, the trust trio
   ==========================================================================
   The reference lays the hero on a pale mint field. Written as a mix of two
   ROLE tokens rather than a tint of a hex, which is what makes it survive the
   theme switch with no override: in light it resolves to a barely-there green
   cast over the cream page, and in dark to a green-cast near-black over the
   near-black one. A literal would have needed a [data-theme='dark'] twin and
   would have drifted from it.
   ========================================================================== */

.ec-mk-hero { background: color-mix(in srgb, var(--ec-accent) 7%, var(--ec-bg)); }

.ec-front-points {
  display: grid;
  gap: var(--ec-space-3);
  padding: 0;
  margin: var(--ec-space-6) 0 0;
  list-style: none;
}

.ec-front-points li {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  font-size: var(--mk-body);
  line-height: 1.5;
  color: var(--ec-text);
}

/* Nudged down to sit on the first line's cap height rather than its box:
   align-items alone puts a 16px tick above an 17px line. */
.ec-front-points li > svg {
  flex: none;
  margin-block-start: 4px;
  color: var(--ec-accent);
}

/* The same list, tinted for the column that describes the problem rather than
   the answer. A modifier on the one object rather than a second list class:
   the two sit side by side in a .ec-mk-split and any difference in leading or
   gap between them reads as a mistake. --ec-danger is the alert component's
   own ink, so a reader who has seen a warning on this site has seen this. */
.ec-front-points--warn li > svg { color: var(--ec-danger); }

/* The trio, laid out as rows here and as centred columns under the sign-in
   card. auto-fit at 170px rather than three fixed columns: the French labels
   run about a third longer, and three-up stops fitting before the viewport
   does. Same idiom as .ec-mk-proof. */
.ec-front-trust {
  display: grid;
  /* 150px, measured rather than picked: the hero copy column is about 550px
     at the design width, and three 170px tracks plus their gaps came to 542 —
     inside the column by eight pixels, which is not a margin, and the row
     broke to two-up on the first browser that rounded the other way. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: var(--ec-space-5) var(--ec-space-4);
  padding: 0;
  margin: clamp(2rem, 4vw, 2.75rem) 0 0;
  list-style: none;
}

.ec-front-trust li {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
}

.ec-front-trust .ec-front-trust__ico { margin-block-end: 0; }

.ec-front-trust b {
  display: block;
  font-size: var(--mk-small);
  color: var(--ec-text);
}

.ec-front-trust li > span:last-child {
  font-size: var(--mk-micro);
  line-height: 1.45;
  color: var(--ec-text-muted);
}

/* ==========================================================================
   7. LANDING — the drawn dashboard
   ==========================================================================
   Markup and CSS, not a screenshot. A screenshot is a couple of hundred
   kilobytes that goes stale the first time a heading moves, on a page whose
   argument is that a visitor's data costs them about US$1.63 a gigabyte.

   It lives inside .ec-mk-hero__art, which marketing.css already drops below
   1140px and in print, so it needs no hide rule of its own. Between there and
   its full width it sheds detail rather than shrinking it — see the
   breakpoints at the end of this file.
   ========================================================================== */

.ec-mk-frame {
  position: relative;
  width: 100%;
  max-width: 560px;
  padding: 14px;
  background: linear-gradient(150deg, var(--ec-primary-700), var(--ec-primary-900));
  border-radius: 24px;
  box-shadow: var(--mk-shadow-card);
}

.ec-mk-app {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  overflow: hidden;
  background: var(--ec-surface);
  border-radius: var(--ec-radius-lg);
}

/* --- The rail ------------------------------------------------------------- */

.ec-mk-app__rail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 10px;
  background: var(--ec-primary-900);
}

.ec-mk-app__mark {
  width: 34px;
  height: 10px;
  margin-block-end: 6px;
  background: rgb(255 255 255 / 0.9);
  border-radius: 3px;
}

.ec-mk-app__row {
  height: 7px;
  background: rgb(255 255 255 / 0.2);
  border-radius: 3px;
}

.ec-mk-app__row.is-on { background: rgb(255 255 255 / 0.72); }

/* --- The body ------------------------------------------------------------- */

.ec-mk-app__body {
  display: grid;
  gap: 10px;
  align-content: start;
  padding: 12px;
}

.ec-mk-app__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ec-mk-app__line {
  width: 42%;
  height: 8px;
  background: var(--ec-border);
  border-radius: 4px;
}

.ec-mk-app__pill {
  width: 52px;
  height: 16px;
  background: var(--ec-accent-soft);
  border-radius: var(--ec-radius-full);
}

/* --- The stat cards ------------------------------------------------------- */
/* Bars, never numerals. A drawn dashboard showing invented counts is the same
   claim as "50,000+ students managed", drawn smaller. */

.ec-mk-app__stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.ec-mk-app__stat {
  display: grid;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--ec-border);
  border-radius: 10px;
}

.ec-mk-app__bar1 {
  display: block;
  width: 62%;
  height: 8px;
  background: var(--ec-border-strong);
  border-radius: 4px;
}

.ec-mk-app__bar2 {
  display: block;
  width: 88%;
  height: 5px;
  background: var(--ec-border);
  border-radius: 3px;
}

/* The shared tile, at the size a drawing of an interface needs. */
.ec-mk-app .ec-front-tile {
  width: 26px;
  height: 26px;
  border-radius: 8px;
}

/* --- The chart and the feed ----------------------------------------------- */

.ec-mk-app__panels {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: 8px;
}

.ec-mk-app__chart {
  position: relative;
  padding: 10px;
  border: 1px solid var(--ec-border);
  border-radius: 10px;
}

.ec-mk-app__tabs {
  display: flex;
  gap: 5px;
  margin-block-end: 8px;
}

.ec-mk-app__tabs span {
  width: 26px;
  height: 8px;
  background: var(--ec-border);
  border-radius: 4px;
}

.ec-mk-app__tabs .is-on { background: var(--ec-accent); }

.ec-mk-app__spark {
  display: block;
  width: 100%;
  height: 62px;
}

/* Both paths read --ec-chart-1, which is the brand green and already lifts in
   the dark theme, so the chart re-themes with everything else. */
.ec-mk-app__stroke {
  fill: none;
  stroke: var(--ec-chart-1);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Required, not decorative: preserveAspectRatio="none" scales the two axes
     independently, and without this the stroke is visibly fatter on one. */
  vector-effect: non-scaling-stroke;
}

.ec-mk-app__area {
  stroke: none;
  fill: color-mix(in srgb, var(--ec-chart-1) 14%, transparent);
}

/* A CSS dot, not an SVG circle: inside preserveAspectRatio="none" a circle is
   drawn as an ellipse. */
.ec-mk-app__node {
  position: absolute;
  top: 30px;
  inset-inline-end: 12px;
  width: 7px;
  height: 7px;
  background: var(--ec-chart-1);
  border-radius: 50%;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ec-chart-1) 22%, transparent);
}

.ec-mk-app__feed {
  display: grid;
  gap: 8px;
  align-content: start;
  padding: 10px;
  border: 1px solid var(--ec-border);
  border-radius: 10px;
}

.ec-mk-app__item {
  display: flex;
  gap: 7px;
  align-items: center;
}

.ec-mk-app__av {
  flex: none;
  width: 16px;
  height: 16px;
  background: var(--ec-surface-2);
  border-radius: 50%;
}

.ec-mk-app__lines {
  display: grid;
  flex: 1;
  gap: 4px;
}

.ec-mk-app__lines i {
  display: block;
  height: 5px;
  background: var(--ec-border);
  border-radius: 3px;
}

.ec-mk-app__lines i:first-child { width: 78%; }
.ec-mk-app__lines i:last-child  { width: 46%; }

.ec-mk-app__quick {
  display: flex;
  gap: 8px;
}

/* --- The optional cut-out ------------------------------------------------- */
/* Bleeds outside the frame's bottom-right corner. Safe only because
   .ec-mk-hero carries overflow: hidden — removing that line ships a page that
   scrolls sideways on every desktop. The frame is never sized by this image,
   so the composition does not move when it arrives. */

.ec-mk-frame__photo {
  position: absolute;
  inset-block-end: -7%;
  inset-inline-end: -11%;
  width: min(42%, 220px);
  height: auto;
  pointer-events: none;
  object-fit: contain;
}

/* ==========================================================================
   8. LANDING — the feature grid
   ==========================================================================
   .ec-mk-card paints itself --ec-surface-2, the warm tile. The pastel tints
   measure about 1.02:1 against that and are simply not there, so a card
   carrying one moves to --ec-surface and takes a hairline instead. Six cards
   and a seventh that is a link — the reference puts a customer testimonial in
   that slot, and there is no customer quote in this codebase to put in it.
   ========================================================================== */

.ec-mk-card.ec-front-card {
  display: block;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
}

.ec-mk-card.ec-front-card:hover,
[data-theme='dark'] .ec-mk-card.ec-front-card:hover {
  background: var(--ec-surface);
  box-shadow: var(--mk-shadow-card);
}

.ec-front-card .ec-front-tile { margin-block-end: var(--ec-space-5); }

.ec-front-card--link,
.ec-front-card--link:hover { color: inherit; }

.ec-front-card__more {
  display: inline-flex;
  gap: var(--ec-space-2);
  align-items: center;
  margin-block-start: var(--ec-space-4);
  font-size: var(--mk-small);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent);
}

/* ==========================================================================
   9. LANDING — the stats slab
   ==========================================================================
   The same four figures the proof strip always carried, in the reference's
   one dark band instead of four alternating tiles.

   It keeps .ec-mk-proof as well: the print block and the entrance-reveal
   observer in layout-marketing-end.php both select through that name, and the
   auto-fit grid underneath is still exactly what is wanted. What changes is
   the paint — so the two rules below have to out-specify
   `.ec-mk-proof > div:nth-child(even)`, which they do by matching its
   specificity and arriving in a later sheet.

   Pinned green in both themes, and therefore re-keying its own ink AND its
   own focus colour: a ring drawn in --ec-accent on an --ec-accent field is
   the green-on-green case layout.css documents at length.
   ========================================================================== */

.ec-front-stats__eyebrow { margin-block-end: var(--ec-space-4); }

.ec-mk-proof.ec-front-stats {
  --ec-text: #ffffff;
  --ec-text-muted: rgb(255 255 255 / 0.82);
  --mk-focus: #ffffff;

  gap: 0;
  padding: clamp(1.25rem, 2.5vw, 2rem) clamp(0.5rem, 1.5vw, 1.25rem);
  margin-block-start: 0;
  color: #fff;
  background: linear-gradient(150deg, var(--ec-primary-800), var(--ec-primary-900));
  border-radius: 24px;
}

.ec-front-stats > div,
.ec-front-stats > div:nth-child(even) {
  display: grid;
  gap: 2px;
  justify-items: center;
  padding: var(--ec-space-4) var(--ec-space-3);
  color: inherit;
  text-align: center;
  background: none;
}

.ec-front-stats__ico {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-block-end: var(--ec-space-2);
  color: #fff;
  border: 1px solid rgb(255 255 255 / 0.22);
  border-radius: var(--ec-radius-md);
}

/* ==========================================================================
   10. LANDING — breakpoints
   ==========================================================================
   The frame sheds detail rather than shrinking it, because a mini sidebar, a
   chart and a feed at two thirds scale are four illegible smudges. Below
   1140px marketing.css removes the whole thing; these two steps are what
   happens on the way there.
   ========================================================================== */

@media (max-width: 1319.98px) {
  .ec-mk-app { grid-template-columns: minmax(0, 1fr); }

  .ec-mk-app__rail,
  .ec-mk-app__quick { display: none; }
}

/* The last step before marketing.css removes the frame at 1139.98px, where
   the header also folds into the burger. Keep this above that number or it
   is a rule that can never match. */
@media (max-width: 1219.98px) {
  .ec-mk-app__panels { grid-template-columns: minmax(0, 1fr); }

  .ec-mk-app__feed { display: none; }
}

/* ==========================================================================
   11. THE FOUR KINDS OF SCHOOL (/schools/)
   ==========================================================================
   Four cards, each one a registry entry rendered. The four-row spec list is
   the part that carries the argument — it is where a head teacher sees their
   own marks scale and their own examination named — so it is set as a
   definition list with a hairline per row rather than as prose.
   ========================================================================== */

/* The names on the gate. Set apart from the label above it and the body
   below, because it is neither: it is the words a visitor is looking for. */
.ec-front-inst {
  margin-block: calc(var(--ec-space-1) * -1) var(--ec-space-3);
  font-size: var(--mk-micro);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.ec-front-spec {
  margin: var(--ec-space-5) 0 0;
  font-size: var(--mk-micro);
}

.ec-front-spec > div {
  display: grid;
  grid-template-columns: minmax(0, 10rem) minmax(0, 1fr);
  gap: var(--ec-space-3);
  padding-block: var(--ec-space-3);
  border-block-start: 1px solid var(--ec-border);
}

.ec-front-spec > div:last-child { border-block-end: 1px solid var(--ec-border); }

.ec-front-spec dt {
  font-weight: var(--ec-weight-medium);
  color: var(--ec-text-muted);
}

.ec-front-spec dd {
  margin: 0;
  line-height: 1.45;
  color: var(--ec-text);
}

/* Two columns need about 260px each before the label and the value stop
   fitting on one line together; below that the row stacks. The examination
   list for a technical school is nine names long and is what runs out first. */
@media (max-width: 560px) {
  .ec-front-spec > div { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}

/* ==========================================================================
   12. THE PRIMARY CALL TO ACTION, IN GREEN
   ==========================================================================
   marketing.css paints .ec-btn--primary amber under .ec-mk — the site's rule
   was "amber points, green states". The reference the front pages now follow
   puts its primary control in the brand green, and so does the sign-in card,
   which has no .ec-mk on its body and therefore always got the green one. Two
   different primary buttons on two halves of the same visit is the kind of
   seam a visitor reads as two different products, so the marketing pages come
   over to green.

   With one exception, and it is the reason the original rule existed: inside
   .ec-mk-band the field is already the brand green, and a green button on it
   is a button nobody can find. The reference draws exactly that and it is the
   weakest element in it. Amber stays there.

   Focus needs nothing: .ec-mk draws its ring in --mk-focus, which is the text
   colour outside the band and white inside it — never the accent — so the
   green-on-green case layout.css documents does not arise on these pages.
   ========================================================================== */

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

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

.ec-mk-band .ec-btn--primary {
  color: var(--mk-amber-ink);
  background: var(--mk-amber);
}

.ec-mk-band .ec-btn--primary:hover {
  color: var(--mk-amber-ink);
  background: var(--mk-amber-hover);
}

/* Two by two, not three and a stranded fourth. .ec-mk-grid--2 auto-fits at
   330px and gives three columns at the design width, which leaves the
   technical school alone on a second row — and these four are a set, so the
   shape should say four.

   Stated as an explicit two above 900px rather than as an auto-fit at 420px.
   Auto-fit is a track count derived from the viewport, so it went back to
   three the moment the wrap grew for large screens, and put the orphan back.
   A fixed pair cannot drift: four items, two rows, at every width where two
   columns fit at all. */
/* Both selectors, not just the modifier. This element is also .ec-mk-grid,
   and the extra-large block at the end of this file re-declares
   grid-template-columns on that class — at equal specificity and later in the
   file, so a bare .ec-front-typegrid lost, and the four cards went back to
   three-and-an-orphan on exactly the screens this pin exists for. */
.ec-mk-grid.ec-front-typegrid {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 900px) {
  .ec-mk-grid.ec-front-typegrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   13. LONG LABELS ON A NARROW SCREEN
   ==========================================================================
   .ec-btn is white-space: nowrap, which is right for a toolbar and wrong for
   a pill carrying a whole sentence. "L'inscription au GCE, etape par etape"
   measures 346px against a 320px column at 360px wide, and the landing page
   scrolled six pixels sideways in French — on the one page where a visitor's
   first impression is the whole point.

   Only below 480px, and only under .ec-mk. Above that the labels fit, and a
   button that can wrap is a button whose height moves with its translation.
   ========================================================================== */

@media (max-width: 480px) {
  .ec-mk .ec-btn {
    height: auto;
    min-height: 48px;
    padding-block: var(--ec-space-3);
    line-height: 1.35;
    text-align: center;
    white-space: normal;
  }
}

/* ==========================================================================
   14. EXTRA-LARGE SCREENS
   ==========================================================================
   Everything on these pages stops growing at about 1470px. .ec-mk-wrap is
   capped at 1140px, and every --mk-* type token is a clamp() whose ceiling is
   reached before that — so a 2560px window renders the same 1140px column as
   a 1500px one, with 710px of empty margin down each side, a 68px header
   floating over it, and a drawn dashboard 477px wide on a screen five times
   that. Nothing is broken; it is just a laptop design shown very large.

   Two steps rather than one continuous scale, and they are deliberately
   conservative:

   - The wrap grows, but not to the viewport. Measure is what caps it: the
     lead is 60ch, the hero sub 52ch, and a card body wants to stay near 45.
     Past about 1440px of content the grid stops being a layout and becomes a
     row of far-apart objects.
   - The type grows one notch per step, not proportionally. Display type at
     2560px does not want to be twice what it is at 1280px; it wants to be
     about a fifth more, because the reader is not twice as far away.
   - The section rhythm grows with the type, or the page reads as squashed:
     vertical space is what makes a wide page feel composed rather than
     stretched.

   1600 and 1920 are the two real population clusters above 1440 — 1600x900
   and 1920x1080 desktops, and the 2560 and 3440 monitors that both rules also
   catch. There is no third step: past 1920 the design holds and the margins
   simply grow, which at that width is correct rather than lazy.
   ========================================================================== */

@media (min-width: 1600px) {
  /* On :root, not on .ec-mk, because the auth stage reads it too — its sticky
     panel is sized calc(100dvh - var(--mk-head-h)) and the anchor offset is
     calc(var(--mk-head-h) + …). One number, and everything follows it. */
  :root { --mk-head-h: 76px; }

  .ec-mk,
  .ec-mk-head,
  .ec-mk-foot {
    --mk-display: 4.75rem;
    --mk-h2: 2.75rem;
    --mk-h3: 1.5rem;
    --mk-lead: 1.3125rem;
    --mk-body: 1.125rem;
    --mk-small: 1rem;
    --mk-micro: 0.875rem;
    --mk-section-y: 7.5rem;
    --mk-gutter: 2.5rem;
  }

  .ec-mk-wrap { max-width: 1320px; }
  .ec-mk-wrap--narrow { max-width: 900px; }

  /* .ec-mk-h1 has no measure of its own outside the hero, where 15ch already
     caps it. At 76px on a 1240px column a page title runs to a single
     forty-character line; text-wrap: balance then has nothing to balance. */
  .ec-mk-h1 { max-width: 22ch; }

  .ec-front-tile { width: 48px; height: 48px; }

  /* --- The hero ----------------------------------------------------------- */

  .ec-mk-frame { max-width: 640px; padding: 16px; }

  .ec-mk-app__spark { height: 76px; }

  .ec-mk-app__body { gap: 12px; padding: 14px; }

  .ec-mk-app__stat { padding: 10px; }

  /* --- The card grids ----------------------------------------------------- */
  /* Hold the three-up.

     .ec-mk-grid auto-fits at 290px, and a track count derived from the
     viewport is a layout that changes when the wrap does: widening the wrap
     turned every three-up grid on the site into a four-up one, quietly, on
     pages nobody looked at. Raising the floor to 380px keeps the count where
     it was designed and spends the extra width on the cards instead — 440px
     each rather than 325.

     The seventh card, the link to /schools/, then takes the whole last row
     rather than sitting alone in the first third of it. A banner is a shape;
     an orphan is an accident. */

  .ec-mk-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr)); }

  /* Full width means it stops being a card shape, so it is laid out as one:
     the tile on the left, the title and sentence in the middle, and the
     control on the right where a banner puts it. Placed explicitly rather
     than wrapped in a new div, because the same four elements have to keep
     stacking as an ordinary card at every width below this one. */
  .ec-mk-grid > .ec-front-card--link {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    column-gap: var(--ec-space-5);
    align-items: center;
  }

  .ec-front-card--link > .ec-front-tile {
    grid-row: 1 / 3;
    grid-column: 1;
    margin-block-end: 0;
  }

  .ec-front-card--link > .ec-mk-card__title {
    grid-row: 1;
    grid-column: 2;
  }

  .ec-front-card--link > .ec-mk-card__body {
    grid-row: 2;
    grid-column: 2;
    max-width: 74ch;
  }

  .ec-front-card--link > .ec-front-card__more {
    grid-row: 1 / 3;
    grid-column: 3;
    margin-block-start: 0;
    white-space: nowrap;
  }

  /* --- The sign-in stage -------------------------------------------------- */

  /* The panel's content stops at a measure and the padding takes the rest.
     Left-aligned rather than centred: it is an editorial column, and the
     lockup at the top and the pitch at the foot share one left edge. */
  .ec-auth__panel { padding: clamp(3rem, 4vw, 4.5rem); }

  .ec-auth__pitch { max-width: 46rem; }

  .ec-auth__lede { max-width: 44ch; }

  .ec-auth__col { width: min(480px, 100%); }
}

@media (min-width: 1920px) {
  :root { --mk-head-h: 82px; }

  .ec-mk,
  .ec-mk-head,
  .ec-mk-foot {
    --mk-display: 5.25rem;
    --mk-h2: 3rem;
    --mk-h3: 1.625rem;
    --mk-lead: 1.375rem;
    --mk-body: 1.1875rem;
    --mk-section-y: 8.5rem;
  }

  .ec-mk-wrap { max-width: 1440px; }
  .ec-mk-wrap--narrow { max-width: 960px; }

  .ec-mk-frame { max-width: 720px; }

  .ec-mk-app__spark { height: 88px; }

  /* The panel's measure grows with the panel. At 2560 the aside is about
     1170px wide and 46rem of content left four hundred pixels of empty green
     down its right-hand side. */
  .ec-auth__pitch { max-width: 52rem; }

  .ec-auth__lede { max-width: 52ch; }

  .ec-auth__col { width: min(520px, 100%); }
}

/* ==========================================================================
   15. BORROWED FROM modules.css
   ==========================================================================
   Copies, not moves. modules.css keeps every one of these because the
   signed-in application still needs them; this sheet carries a second copy so
   the two PUBLIC shells can stop loading a 14 KB stylesheet for the four
   things they actually use.

   modules.css is the application's sheet — the timetable grid, the month
   calendar, messaging, the settings screen, the mark sheet, the import
   wizard. None of it renders on a marketing page or a sign-in screen, and yet
   both shells linked it, because a handful of small form and alert rules
   happened to live in it.

   The first attempt at this move listed four blocks. It was wrong, and it
   would have shipped a visibly broken lead form: `.ec-field`, its label and
   the `.ec-alert` base are ALSO modules.css-only, and the contact form uses
   all three. The list below was derived by extracting every class token from
   every public and auth template and matching it against all six sheets, so
   it is the whole set rather than the memorable part of it.

   What is deliberately NOT here: `.ec-auth__brand`. error.php renders that
   class and loads tokens/layout/components/modules and not this file, so it
   has to stay where it is. Copying it here as well would be harmless; moving
   it would break a page nothing tests.

   Source lines in modules.css at the time of copying:
     .ec-field family              646-681
     .ec-alert family + is-invalid 852-901
     .ec-pick / .ec-picks          314-367
     .ec-portal-hint / .ec-strength 903-945

   If one of them changes there, change it here. They are small, stable and
   have not been touched in months, which is what makes a copy cheaper than
   the alternative of splitting modules.css into two files that every shell
   then has to reason about.
   ========================================================================== */

.ec-field { margin-block-end: var(--ec-space-4); }

.ec-field__label {
  display: block;
  margin-block-end: var(--ec-space-2);
  font-size: var(--ec-text-sm);
  font-weight: var(--ec-weight-medium);
}

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

.ec-field .ec-input,
.ec-field .ec-select,
.ec-field textarea { width: 100%; }

.ec-field textarea {
  min-height: 96px;
  padding: var(--ec-space-3);
  font-family: inherit;
  font-size: var(--ec-text-md);
  color: var(--ec-text);
  resize: vertical;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border-strong);
  border-radius: var(--ec-radius-md);
}

.ec-field textarea:focus {
  border-color: var(--ec-accent);
  outline: none;
  box-shadow: var(--ec-ring);
}

/* ==========================================================================
   ALERTS: server-rendered flash messages
   ========================================================================== */

.ec-alert {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-3) var(--ec-space-4);
  margin-block-end: var(--ec-space-4);
  font-size: var(--ec-text-md);
  border: 1px solid transparent;
  border-radius: var(--ec-radius-md);
}

.ec-alert svg { flex-shrink: 0; margin-block-start: 1px; }

.ec-alert--success {
  color: var(--ec-success);
  background: var(--ec-success-soft);
  border-color: color-mix(in srgb, var(--ec-success) 30%, transparent);
}

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

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

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

/* Field-level validation */
.ec-input.is-invalid,
.ec-select.is-invalid { border-color: var(--ec-danger); }

.ec-field__error {
  margin-block-start: var(--ec-space-1);
  font-size: var(--ec-text-xs);
  color: var(--ec-danger);
}

/* A checkbox that reads as a thing you are choosing rather than a setting you
   are toggling. The module picker on the owner console's subscription form is
   twenty-eight of these in a grid, and a naked <input> beside a label made a
   purchase look like a preferences page.

   :has() paints the chosen ones so the answer is legible without reading every
   box; a browser without it simply shows the checkbox, which is what was there
   before. */
.ec-pick {
  display: flex;
  gap: var(--ec-space-3);
  align-items: flex-start;
  padding: var(--ec-space-3);
  cursor: pointer;
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
  transition: background var(--ec-transition-fast), border-color var(--ec-transition-fast);
}

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

.ec-pick:has(input:checked) {
  background: var(--ec-accent-soft);
  border-color: var(--ec-accent);
}

.ec-pick input { margin-block-start: 3px; }

/* The grid the `picks` field type lays its tiles out on. Auto-fit rather than a
   column count, so eighteen subjects and three sections both fill the width
   they are given without either being re-counted by hand. */
.ec-picks {
  display: grid;
  gap: var(--ec-space-2);
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

/* Section letters, room codes, a filière abbreviation: short enough that a
   200px track is mostly empty space. */
.ec-picks--tight {
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
}

/* A tile for something that is already there. Ticked, unclickable, and visibly
   not a choice — a disabled checkbox alone reads as "not allowed" rather than
   "already done". */
.ec-pick.is-fixed {
  cursor: default;
  opacity: 0.62;
  background: var(--ec-surface-2);
  border-style: dashed;
}


/* Demo-account shortcuts on the login screen */
.ec-portal-hint {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ec-space-2);
  margin-block-start: var(--ec-space-3);
}

.ec-portal-hint button {
  padding: var(--ec-space-1) var(--ec-space-3);
  font-size: var(--ec-text-xs);
  color: var(--ec-text-muted);
  cursor: pointer;
  background: var(--ec-surface-2);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-full);
  transition: all var(--ec-transition-fast);
}

.ec-portal-hint button:hover {
  color: var(--ec-accent);
  border-color: var(--ec-accent);
}

/* Password strength meter */
.ec-strength {
  display: flex;
  gap: 4px;
  margin-block-start: var(--ec-space-2);
}

.ec-strength span {
  flex: 1;
  height: 4px;
  background: var(--ec-border);
  border-radius: var(--ec-radius-full);
  transition: background var(--ec-transition-fast);
}

.ec-strength[data-score='1'] span:nth-child(-n+1) { background: var(--ec-danger); }
.ec-strength[data-score='2'] span:nth-child(-n+2) { background: var(--ec-warning); }
.ec-strength[data-score='3'] span:nth-child(-n+3) { background: var(--ec-info); }
.ec-strength[data-score='4'] span                 { background: var(--ec-success); }

/* ==========================================================================
   16. SCHOOL LEVELS (/levels/)
   ==========================================================================
   The stage axis: nursery, primary, secondary, and the high-school cycle
   inside the last of those.
   ========================================================================== */

/* The line-up photograph, at its own aspect ratio.
   .ec-mk-shot--band is 16/7, which is right for a band restating the copy
   beside it and wrong for this one: the eleven figures occupy 6% to 99% of
   the frame height, so a letterbox cuts the heads off the tall pupils and the
   feet off the small ones. Measured, not guessed — see tools/make_images.php. */
.ec-mk-shot--full {
  aspect-ratio: 3 / 2;
  border-radius: 20px;
  overflow: hidden;
}

.ec-mk-shot--full img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 40%;
}

/* Three stages, and three is the whole set — auto-fit would collapse to two
   at some widths and leave secondary alone on a row. */
.ec-mk-grid.ec-front-stagegrid {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 900px) {
  .ec-mk-grid.ec-front-stagegrid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* --- The ladder ----------------------------------------------------------- */
/* Six columns of real class names. They are deliberately not a table: the
   columns have different lengths — anglophone nursery is two levels and
   francophone maternelle is three — and a table would draw empty cells to
   make them agree, which is the one thing the section is trying to show is
   not true. */

.ec-front-ladder {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: var(--ec-space-5) var(--ec-space-4);
  margin-block-start: var(--ec-space-6);
}

.ec-front-ladder__col {
  padding: var(--ec-space-4);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
}

.ec-front-ladder__head {
  margin: 0 0 var(--ec-space-3);
  font-size: var(--mk-micro);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.ec-front-ladder__list {
  padding: 0;
  margin: 0;
  list-style: none;
  counter-reset: ec-rung;
}

/* Numbered, because the order is the point — this is a ladder a pupil climbs,
   not a set of options. */
.ec-front-ladder__list li {
  display: flex;
  gap: var(--ec-space-3);
  align-items: baseline;
  padding-block: var(--ec-space-2);
  font-size: var(--mk-small);
  color: var(--ec-text);
  border-block-start: 1px solid var(--ec-border);
  counter-increment: ec-rung;
}

.ec-front-ladder__list li:first-child { border-block-start: 0; }

.ec-front-ladder__list li::before {
  content: counter(ec-rung);
  flex: none;
  min-width: 1.25em;
  font-size: var(--ec-text-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--ec-text-subtle);
}

/* --- The three mastery codes ---------------------------------------------- */

.ec-front-codes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
  gap: var(--ec-space-3);
  padding: 0;
  margin: var(--ec-space-6) 0 0;
  list-style: none;
}

.ec-front-codes li {
  display: flex;
  gap: var(--ec-space-3);
  align-items: baseline;
  padding: var(--ec-space-3) var(--ec-space-4);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

.ec-front-codes b {
  flex: none;
  min-width: 2.5em;
  font-size: var(--ec-text-base);
  font-weight: var(--ec-weight-bold);
  color: var(--ec-accent);
  letter-spacing: -0.02em;
}

.ec-front-codes span {
  font-size: var(--mk-micro);
  line-height: 1.4;
  color: var(--ec-text-muted);
}

/* ==========================================================================
   17. THE GROUPED HEADER
   ==========================================================================
   Two disclosure groups and two plain links, because eleven public pages will
   not fit across a header as eleven links. The measurement is in the comment
   above $ecMkNav in includes/partial-marketing-head.php.

   The no-JavaScript state is the important one. Nothing here hides a panel:
   .ec-mk-menu is `position: static` and visible until the script promotes it,
   so a visitor without JavaScript reads the header as four headings with
   their links underneath — which is exactly what the mobile drawer looks like
   anyway. The script adds [hidden] and the absolute positioning together, and
   only above the breakpoint where a dropdown is what the header is.
   ========================================================================== */

.ec-mk-navgroup { position: relative; }

.ec-mk-nav__trigger {
  display: inline-flex;
  gap: var(--ec-space-1);
  align-items: center;
  font: inherit;
  cursor: pointer;
  background: none;
  border: 0;
}

/* The chevron leans over when the panel is open, so the control says which
   way it is pointing rather than only what it is called. */
.ec-mk-nav__trigger > svg { transition: rotate var(--ec-transition-fast); }
.ec-mk-nav__trigger[aria-expanded='true'] > svg { rotate: 180deg; }

/* Enhanced only. Until the script runs, this selector matches nothing and the
   panel is a plain block in the flow. */
.ec-mk-nav__trigger[aria-expanded] + .ec-mk-menu {
  position: absolute;
  inset-inline-start: 0;
  top: calc(100% + var(--ec-space-3));
  z-index: var(--ec-z-dropdown);
  display: grid;
  min-width: 15rem;
  padding: var(--ec-space-2);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  box-shadow: var(--ec-shadow-lg);
}

.ec-mk-menu__link {
  padding: var(--ec-space-2) var(--ec-space-3);
  font-size: var(--mk-small);
  color: var(--ec-text);
  border-radius: var(--ec-radius-sm);
}

.ec-mk-menu__link:hover {
  color: var(--ec-accent);
  background: var(--ec-surface-hover);
}

.ec-mk-menu__link.is-active { color: var(--ec-accent); font-weight: var(--ec-weight-semibold); }

/* Below the fold breakpoint the whole header is a drawer, so the groups stop
   being menus and become plain labelled sections of it. The trigger is not a
   control there — the script removes its handlers — so it reads as a heading. */
@media (max-width: 1139.98px) {
  .ec-mk-navgroup { display: grid; }

  .ec-mk-nav__trigger {
    justify-content: space-between;
    width: 100%;
    font-size: var(--ec-text-2xs);
    font-weight: var(--ec-weight-semibold);
    color: var(--ec-text-subtle);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    cursor: default;
  }

  .ec-mk-nav__trigger > svg { display: none; }

  .ec-mk-menu { display: grid; gap: var(--ec-space-1); }

  .ec-mk-menu__link {
    padding: var(--ec-space-3);
    font-size: var(--mk-body);
    /* 44px, the comfortable touch target. The drawer's links were 40. */
    min-height: 44px;
    display: flex;
    align-items: center;
  }
}

/* ==========================================================================
   18. THE MODULE CATALOGUE (/modules/)
   ==========================================================================
   A reference page: dense, scannable, no photography. Thirty-three rows in
   eight groups, and the job of the design is to let somebody find one.
   ========================================================================== */

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

.ec-front-cathead {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  margin: 0;
}

/* The tile is decorative furniture at this size, so it shrinks rather than
   holding a 48px box beside a heading. */
.ec-front-cathead .ec-front-tile {
  width: 34px;
  height: 34px;
  border-radius: var(--ec-radius-md);
  margin-block-end: 0;
}

.ec-front-catcount {
  margin: 0;
  font-size: var(--ec-text-lg);
  font-weight: var(--mk-weight-black);
  color: var(--ec-text-subtle);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
}

.ec-front-mods {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: var(--ec-space-3);
  padding: 0;
  margin: var(--ec-space-5) 0 0;
  list-style: none;
}

.ec-front-mods li {
  display: grid;
  gap: 2px;
  align-content: start;
  padding: var(--ec-space-3) var(--ec-space-4);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-md);
}

.ec-front-mods h3 {
  margin: 0;
  font-size: var(--mk-small);
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-text);
}

/* Core and prerequisite are the two facts worth carrying, and they are
   mutually exclusive: a core module has no prerequisite. */
.ec-front-mods__core,
.ec-front-mods__needs {
  font-size: var(--ec-text-2xs);
  line-height: 1.4;
}

.ec-front-mods__core {
  font-weight: var(--ec-weight-semibold);
  color: var(--ec-accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.ec-front-mods__needs { color: var(--ec-text-subtle); }

/* ==========================================================================
   19. THE FOUR RUNGS
   ==========================================================================
   Nursery, primary, secondary, high school — on the landing page, as a
   pointer at /levels/ rather than a substitute for it. An ordered list
   because the order is the whole content: this is a ladder a pupil climbs.
   ========================================================================== */

.ec-front-rungs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr));
  gap: var(--ec-space-4);
  padding: 0;
  margin: var(--ec-space-6) 0 0;
  list-style: none;
  counter-reset: ec-rung;
}

.ec-front-rungs li {
  display: flex;
  gap: var(--ec-space-3);
  align-items: center;
  padding: var(--ec-space-4);
  background: var(--ec-surface);
  border: 1px solid var(--ec-border);
  border-radius: var(--ec-radius-lg);
  counter-increment: ec-rung;
}

.ec-front-rungs b {
  font-size: var(--mk-small);
  font-weight: var(--ec-weight-semibold);
  line-height: 1.3;
  color: var(--ec-text);
}

/* The arrow between rungs, on one line only. It is a ladder, and the shape
   should say so — but a chevron pointing right at the end of a wrapped row
   points at a margin, so it is drawn only while the row is unbroken. */
@media (min-width: 900px) {
  .ec-front-rungs li { position: relative; }

  .ec-front-rungs li:not(:last-child)::after {
    position: absolute;
    inset-inline-end: calc(var(--ec-space-4) * -1);
    top: 50%;
    width: var(--ec-space-4);
    height: 1px;
    content: '';
    background: var(--ec-border-strong);
  }
}

/* ==========================================================================
   20. HEADINGS THAT WERE PARAGRAPHS
   ==========================================================================
   /faq/'s seven published limits and /gce/'s five flow steps were <p> styled
   to look like headings. They are headings — they title a block and a reader
   navigates by them — so they became h3.

   A browser's default h3 is bold and 1.17em with its own margins, which would
   have made a semantic fix a visual regression. These two rules pin the
   element back to what the class already drew, so the change is inert on
   screen and only the outline moves. */

h3.ec-mk-qa__q,
h3.ec-mk-step__title {
  margin: 0 0 var(--ec-space-2);
  font-size: var(--mk-h3);
  font-weight: var(--ec-weight-semibold);
  line-height: 1.35;
  letter-spacing: -0.01em;
}
