:root {
    --brand-1: #2a5f96;
    --brand-2: #56b8e9;
    --accent: #f6d957;
    --accent-soft: #f7b284;
    --brand-ink: #1d4d7f;
    --surface: rgba(255, 255, 255, 0.92);
    --surface-border: rgba(148, 163, 184, 0.25);
    /* Two font roles, overridden per language further down. A script the Latin
       faces cannot draw has to substitute both -- swap only the body face and
       every heading still falls through to whatever the OS happens to have, so
       the page renders in two unrelated typefaces. */
    --font-body: "Nunito", "Segoe UI", sans-serif;
    --font-display: "Baloo 2", "Nunito", sans-serif;
}

* { box-sizing: border-box; }

/* ── Smooth scrolling ──────────────────────────────────────────────────
   Animates every scroll the *page* makes on its own rather than jumping:
   the in-page anchors (`#about`, `#partners`, `#app`, `#reach-us`, `#offer`,
   `#features`), a "back to top", and anything that calls `scrollIntoView`.

   Wrapped in `no-preference` rather than paired with a `reduce` override,
   because that is the direction that fails safe: a visitor who has asked their
   OS for less motion never gets the rule at all, instead of getting it and
   relying on a second rule to take it back. Vestibular disorders make a
   long animated scroll genuinely unpleasant, and this is one of the few
   animations that moves the entire viewport.

   On `html` only, and `scroll-behavior` is not inherited — so the nested
   scrollers (the language panel, the program switcher, the rail strip, the
   store chips) keep their instant `auto`. That is deliberate: the rail strip
   positions itself by assigning `scrollLeft` on load, and animating that would
   be a visible slide on every portal page.

   The one viewport scroll that must *stay* instant is the one `ajax_region.js`
   does to put an admin back where they were after an in-place save; it asks
   for `behavior: 'instant'` explicitly. Anything else added later that
   restores a remembered position has to do the same. */
@media (prefers-reduced-motion: no-preference) {
    html {
        scroll-behavior: smooth;
    }
}

/* Lenis adds `.lenis` to <html> when it takes over, and from then on it is
   animating `window.scrollTo` itself on every frame. Leaving the rule above in
   force would have the browser animate each of those frames too — two
   easings on one scroll, which reads as a scroll that will not settle. So the
   CSS rule is the fallback for exactly the cases Lenis is not running: the
   portal, reduced-motion, and a browser that did not load the script.

   No `!important`: `html.lenis` already outranks the bare `html` above it on
   specificity, and wrapping that one in a media query does not change its
   specificity. Worth stating because the `!important` that used to be here
   claimed the media query was the reason it won, which would have had the next
   override reach for `!important` instead of counting selectors. */
html.lenis {
    scroll-behavior: auto;
}

body {
    font-family: var(--font-body);
    background:
        radial-gradient(circle at 0% 0%, rgba(86, 184, 233, 0.22), transparent 36%),
        radial-gradient(circle at 100% 15%, rgba(246, 217, 87, 0.22), transparent 34%),
        #f8fafc;
}

img,
video {
    max-width: 100%;
    height: auto;
}

/* The `hidden` attribute must actually hide.
   Tailwind's preflight says `[hidden]:where(:not([hidden=until-found])){display:none}`,
   which is specificity 0,1,0 — the same as `.flex`, `.grid` and every other
   display utility, and declared *earlier* in tailwind.css. So markup like
   `<span hidden class="flex …">` renders visible, and `el.hidden = true` from
   script does nothing. That is not hypothetical: the arm-then-confirm controls
   in `admin_tools/program_manage/_tab_calendar.html` are written exactly that
   way and have been showing their confirm buttons from page load.

   One rule, here, because the collision is a property of the stylesheet rather
   than of any one feature — the alternative is every component that reaches for
   the attribute rediscovering this and adding itself to a list. Safe to make
   `!important`: nothing in static/js reveals an element by setting
   `style.display` while leaving the attribute on, and `<input type="hidden">`
   is unaffected. site.css loads after tailwind.css, so this wins. */
[hidden] {
    display: none !important;
}

h1, h2, h3, .site-header a:first-child {
    font-family: var(--font-display);
}

/* ── Header ──
   Transparent while the page is at the top, so the header sits on the hero
   rather than capping it with a white bar; the solid background fades in once
   the page scrolls, where the bar is passing over arbitrary content and needs
   its own surface to stay readable. `is-scrolled` is added by the script in
   base.html, so with JavaScript blocked the header is simply transparent and
   the links are still dark-on-light everywhere the page background is light.
   `has-menu-open` covers the phone dropdown, which can be opened at scroll
   position 0 and needs the bar behind its own logo row filled in. */
.site-header {
    background: transparent;
    color: var(--brand-ink);
    border-bottom: 1px solid transparent;
    position: sticky;
    top: 0;
    z-index: 40;
    transition: background 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

.site-header.is-scrolled,
.site-header.has-menu-open {
    background: rgba(255, 255, 255, 0.92);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border-bottom-color: rgba(148, 163, 184, 0.25);
    box-shadow: 0 4px 16px rgba(15, 36, 72, 0.06);
}

@media (prefers-reduced-motion: reduce) {
    .site-header {
        transition: none;
    }
}

.site-logo-link {
    display: inline-flex;
    align-items: center;
}

.site-logo {
    width: clamp(80px, 11vw, 110px);
    height: auto;
    object-fit: contain;
    filter: drop-shadow(0 2px 6px rgba(15, 36, 72, 0.12));
}

/* ── Mobile toggle ── */
.menu-toggle {
    border: none;
    background: none;
    color: var(--brand-ink);
    padding: 0.4rem;
    display: inline-flex;
    align-items: center;
}


/* ── Desktop nav pills ──
   Set in the display face, so the three labels share a voice with the wordmark
   beside them and the headings under them rather than being the one piece of
   body type in the masthead. `--font-display` and not a font name: the five
   non-Latin scripts collapse that variable onto their own face further down, so
   Arabic, Urdu, Punjabi, Pashto and Bengali navs stay in a face that can draw
   them without this rule knowing anything about it.

   Larger and a step lighter than before -- 12.8px/700 was small enough to need
   the extra weight to hold its own, which is what made it read as UI chrome. At
   14px 600 carries it and the size does the work.

   14px rather than 16px because the row has to survive a 768px bar: the nav is
   `flex-shrink-0`, so anything that does not fit there pushes the page sideways
   instead of wrapping, and dropping to a hamburger on a tablet to buy the two
   extra pixels is the worse trade. */
.nav-pill {
    display: inline-flex;
    align-items: center;
    padding: 0.45rem 0.6rem;
    border-radius: 999px;
    color: #163b66;
    font-family: var(--font-display);
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0;
    transition: color 0.2s, background 0.2s;
    text-decoration: none;
    border: none;
    background: none;
    position: relative;
    white-space: nowrap;
}

/* One step up where the bar has the width to spend on it -- same type, more
   air around each label. */
@media (min-width: 1024px) {
    .nav-pill {
        padding: 0.5rem 0.95rem;
        font-size: 0.95rem;
    }
}

.nav-pill::after {
    content: '';
    position: absolute;
    bottom: 0.15rem;
    left: 50%;
    width: 0;
    height: 2px;
    background: rgba(22, 59, 102, 0.75);
    transition: all 0.2s;
    transform: translateX(-50%);
    border-radius: 1px;
}

.nav-pill:hover {
    color: #0f2d4d;
    background: none;
}

.nav-pill:hover::after {
    width: 60%;
}

/* The account action. A thin outline rather than the filled brand pill it was:
   the header sits above pages whose own "Request a Demo" / "Enroll" buttons are
   the loudest thing on the screen, and a solid button up here was competing
   with every one of them for the same eye. Medium weight, so it still reads as
   the heavier of the two utilities beside it — the language toggle is plain
   text, this has an edge. */
.nav-login {
    display: inline-flex;
    align-items: center;
    background: none;
    color: #163b66;
    border: 1px solid rgba(22, 59, 102, 0.3);
    font-family: var(--font-display);
    font-size: 0.825rem;
    font-weight: 600;
    padding: 0.38rem 0.9rem;
    border-radius: 999px;
    text-decoration: none;
    white-space: nowrap;
    transition: background 0.2s, border-color 0.2s, color 0.2s;
}

.nav-login:hover {
    background: rgba(22, 59, 102, 0.07);
    border-color: rgba(22, 59, 102, 0.55);
    color: #0f2d4d;
}

/* Logout, for a signed-in visitor who already has "Portal" beside it. Lighter
   than that on purpose: it is the one thing in the row nobody is looking for. */
.nav-login-btn {
    background: none;
    color: rgba(22, 59, 102, 0.6);
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 500;
    padding: 0.38rem 0.6rem;
    border-radius: 999px;
    white-space: nowrap;
    transition: background 0.2s, color 0.2s;
}

.nav-login-btn:hover {
    background: rgba(22, 59, 102, 0.07);
    color: #0f2d4d;
}

/* ── Mobile nav ──
   The same night ground the page underneath it now opens on. It was
   `--brand-1 → --brand-2`, a bright mid-blue running to cyan, which was right
   while the header was the brightest thing on the screen and wrong the moment
   the hero became a deep navy band: the sheet slid down over it and the two
   blues argued at the seam. Same stops as `.night-band`, so the sheet reads as
   the page's own surface lifted forward rather than as a different screen. */
.mobile-nav {
    background: linear-gradient(170deg, #11294b 0%, #1d4d7f 46%, #2a5f96 78%, #3b82c4 100%);
}

@media (max-width: 767px) {
    .mobile-nav .nav-pill {
        color: rgba(255,255,255,0.92);
        font-size: 1rem;
        padding: 0.65rem 0.85rem;
        border-radius: 0.5rem;
    }
    .mobile-nav .nav-pill::after {
        display: none;
    }
    .mobile-nav .nav-pill:hover {
        background: rgba(255,255,255,0.15);
        color: #fff;
    }
    /* The sheet's one primary action — Login, or Portal once you are signed in.
       Gold, like every other primary on a dark band here, where it used to be a
       translucent outline identical to the Logout button beside it: two rows of
       the same weight, one of which leaves the site. Full width because every
       other row in the sheet is, and this stopped stretching on its own when it
       lost `.nav-pill`. */
    .mobile-nav .nav-login {
        width: 100%;
        justify-content: center;
        background: linear-gradient(140deg, #f4dca9, #d5a85f);
        color: #12264a;
        border-color: transparent;
        border-radius: 0.5rem;
        padding: 0.55rem 0.9rem;
        font-size: 0.85rem;
        font-weight: 800;
        box-shadow: 0 8px 20px rgba(3, 12, 30, 0.35);
    }
    .mobile-nav .nav-login:hover {
        filter: brightness(1.06);
        color: #12264a;
    }
    .mobile-nav .nav-login-btn {
        color: rgba(255,255,255,0.7);
        border: 1px solid rgba(255,255,255,0.25);
        border-radius: 0.5rem;
    }
    .mobile-nav .nav-login-btn:hover {
        background: rgba(255,255,255,0.15);
        color: #fff;
    }
    .mobile-nav .border-t {
        border-color: rgba(255,255,255,0.15) !important;
    }
}

/* The stand-in mark for an organization that has uploaded no logo — which is
   most of them. Brand-tinted ground with the brand's own ink on it, so a cell
   without artwork still looks like something somebody designed. The letter is
   `aria-hidden` in the markup: the organization's name is always beside it, and
   a screen reader announcing "A" before "Al-Huda Academy" is noise. */
.org-monogram {
    background: linear-gradient(140deg, rgba(42, 95, 150, 0.12), rgba(22, 136, 212, 0.18));
    border: 1px solid rgba(42, 95, 150, 0.16);
    color: #1d4d7f;
}

/* ── Method cards (home "About Nibras") ──
   Cleaner than the `border-t-4` cards they replaced: the accent is a rule that
   grows at the foot rather than a coloured bar pinned to the top, and all three
   share one accent instead of running cyan / emerald / amber — three hues read
   as three unrelated things, one hue plus an index reads as a sequence.

   The movement is the point. A card that lifts, deepens its shadow and extends
   its rule under the cursor tells a reader there is substance here without a
   "read more" link the card cannot honour. `:focus-within` mirrors every hover
   state so a keyboard reaches the same affordance. */
.method-card {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 1.35rem 1.35rem 1.5rem;
    border: 1px solid var(--surface-border);
    border-radius: 1rem;
    background: var(--surface);
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);
}

/* ── The card lift ──
   One movement, two families. `.method-card` (home, navy) and `.mrn-card`
   (Mr. Nibras, teal) had the same three-property transition, the same -3px and
   the same 0 16px 32px shadow geometry written out twice, differing only in two
   colours — and a third copy of the reduced-motion block to keep in step with
   them. The geometry lives here once; each family sets its own two colours
   below, so the palettes stay independent while the motion cannot drift. */
.method-card,
.mrn-card {
    --lift-border: rgba(42, 95, 150, 0.35);
    --lift-shadow: 0 16px 32px rgba(15, 36, 72, 0.1);
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}

.method-card:hover,
.method-card:focus-within,
.mrn-card:hover,
.mrn-card:focus-within {
    transform: translateY(-3px);
    border-color: var(--lift-border);
    box-shadow: var(--lift-shadow);
}

/* Mark, heading, ordinal — one line. The heading used to sit under this row,
   which left the tile reading as a bullet for the whole card rather than as the
   heading's own mark, and put three left-aligned blocks down each card.
   `margin-inline-start: auto` on the ordinal rather than `space-between`, which
   with three children would have spread all three instead of keeping the first
   two together. */
.method-card-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.9rem;
}

/* The glyph tile. Tinted rather than solid, so three of them in a row do not
   read as three buttons. */
/* — The card mark —————————————————————————————————————
   Gold on navy, on both pages. It is the one colour pair the brand actually
   owns — the lamp is gold and it sits on a blue star, and both pieces of
   artwork on this site are painted in exactly this — and until now only Mr.
   Nibras used it, while the home page's marks were a pale blue tile with a blue
   glyph in it, which is the tile any product page has.

   Shared by `.method-card` and `.mrn-card`, the same two families that share
   the lift above. The tile is the only thing on these cards that can carry the
   lamp, so it is the thing that lights up under the cursor. */
.card-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.6rem;
    height: 2.6rem;
    flex-shrink: 0;
    border-radius: 0.8rem;
    background: linear-gradient(145deg, #1d3a63, #12264a);
    color: #e3bd76;
    box-shadow: inset 0 0 0 1px rgba(227, 189, 118, 0.3);
    transition: background 0.18s ease, color 0.18s ease;
}

.card-mark svg { width: 1.35rem; height: 1.35rem; }

/* The size for a card that centres its mark above the heading rather than
   setting it beside one. */
.card-mark--lg { width: 3.5rem; height: 3.5rem; border-radius: 1rem; }
.card-mark--lg svg { width: 1.75rem; height: 1.75rem; }

/* The tile as a disc, for the one place it is a waypoint in a diagram rather
   than a label on a block — the turn between "what goes in" and "what comes
   out" on /for-schools/. Same gradient, same gold ring, round. */
.card-mark--round { width: 2.75rem; height: 2.75rem; border-radius: 999px; }

/* `.mark-host` is the opt-in for anything that is not one of those two card
   families — /for-schools/ lights the same tile from a ruled column, a hairline
   row and a ledger cell, none of which is a card.

   One rule, so the lit state cannot drift into two versions of gold. That
   includes `.loop-disc`, the round node in the pitch page's connected loop:
   its *resting* face is its own (translucent white on the night band rather
   than a navy tile) but the gold it lights up in is this gold, and writing the
   pair out a second time down there is exactly the drift this rule exists to
   prevent. */
:is(.method-card, .mrn-card, .mark-host):is(:hover, :focus-within) .card-mark,
.loop-node:is(:hover, :focus-within) .loop-disc {
    background: linear-gradient(145deg, #f0d49a, #cfa55f);
    color: #12264a;
}

/* Large, light and set back — an ordinal, not a heading. Tabular so 01/02/03
   occupy identical width and the three cards' heads line up, and pushed to the
   far edge of the row the mark and heading share. */
.method-card-index {
    margin-inline-start: auto;
    flex-shrink: 0;
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 800;
    line-height: 1;
    color: rgba(15, 36, 72, 0.13);
    font-variant-numeric: tabular-nums;
    transition: color 0.18s ease;
}

/* Follows the mark rather than the old blue: a brand-blue ordinal beside a gold
   tile was the last thing on these cards still on the other palette. */
.method-card:hover .method-card-index,
.method-card:focus-within .method-card-index {
    color: rgba(42, 95, 150, 0.32);
}

.method-card-title {
    font-family: var(--font-display);
    font-size: 1.0625rem;
    font-weight: 800;
    line-height: 1.3;
    color: #0f2448;
    /* No bottom margin: it is a flex item in the head row now, and the row
       carries the space under it. */
    min-width: 0;
}

.method-card-body {
    font-size: 0.875rem;
    line-height: 1.65;
    color: #64748b;
    /* Pushes the rule to the foot, so all three rules sit on one line however
       long the copy runs. */
    flex: 1 1 auto;
    margin-bottom: 1.1rem;
}

/* The accent. A stub at rest, the card's width on hover. */
/* Shared with `.pillar-rule` on /for-schools/, which is the same accent under
   the same kind of block and differs only in what it grows to. */
.method-card-rule,
.pillar-rule {
    display: block;
    height: 3px;
    width: 2rem;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--brand-1), var(--brand-2));
    transition: width 0.28s ease;
}

.method-card:hover .method-card-rule,
.method-card:focus-within .method-card-rule {
    width: 100%;
}

@media (prefers-reduced-motion: reduce) {
    .method-card,
    .card-mark,
    .method-card-index,
    .method-card-rule,
    .pillar-rule {
        transition: none;
    }
    /* The lift goes too — delivered instantly it is the same motion without the
       easing. The colour and width changes are not motion and stay. */
    .method-card:hover,
    .method-card:focus-within,
    .mrn-card:hover,
    .mrn-card:focus-within {
        transform: none;
    }
}

/* ── Method journey artwork (home "About Nibras") ──
   The picture the three cards hang off. It reads left to right — tree, child
   on the lettered path, screen — and each card below points back up at its own
   beat, so the row of cards is read in the same order and for the same reason
   as the scene above it. */
.method-art {
    position: relative;
    margin: 0;
    border-radius: 1.25rem;
    overflow: hidden;
    border: 1px solid var(--surface-border);
    box-shadow: 0 18px 40px rgba(15, 36, 72, 0.1);
}

/* ── `ltr-flow`: content whose order is fixed by a picture ──
   An artwork does not mirror. A JPEG of a tree on the left is a tree on the
   left in Arabic too, so anything whose order is answering to that picture has
   to keep source order on an RTL page while its own text still runs right to
   left. Both places that need it here say so with this one class rather than
   each inventing a rule: the frame the picture is cropped in, and the row of
   cards beneath it, which would otherwise put the tradition card under the
   screen and stem it into the wrong beat.

   `> *`, not `> .method-card`: the rule is about the row's children, whatever
   they are, and naming one class means the next `<div>` added to that row runs
   the wrong way with nothing to warn anybody. */
.ltr-flow { direction: ltr; }
[dir="rtl"] .ltr-flow > * { direction: rtl; }

.method-art-scroll {
    overflow: hidden;
}

.method-art picture,
.method-art img {
    display: block;
    width: 100%;
    height: auto;
}

/* On a phone the artwork keeps its own scale and shows its middle. 1716px of
   scene squeezed into 400 is 145px tall and nobody can tell the tree from the
   screen; at its own height the centre of it — the child walking the path of
   letters — is a picture rather than a smudge.

   It scrolled sideways here for a while, which read well but cost more than it
   was worth: a horizontally scrolling strip inside a vertically scrolling page
   needs `data-lenis-prevent` to stop a swipe moving both, and Lenis applies
   that attribute to the wheel as well, so on a desktop the page stopped
   scrolling whenever the pointer crossed the picture. Centring is
   `justify-content`, and it needs no such attribute. */
@media (max-width: 767.98px) {
    .method-art-scroll {
        display: flex;
        justify-content: center;
    }

    .method-art picture { flex: none; }

    .method-art picture,
    .method-art img {
        width: auto;
        max-width: none;
        height: 210px;
    }
}

.method-beats {
    /* Named because the stems below do arithmetic with it — the row's own gap
       is half of what turns "the tree is 23% along the picture" into a position
       inside one card. Change it here and the stems follow. */
    --gap: 1rem;
    display: grid;
    gap: var(--gap);
    margin-top: 1.25rem;
}

@media (min-width: 768px) {
    .method-beats {
        grid-template-columns: repeat(3, 1fr);
        /* The gap the stems live in. */
        margin-top: 2.5rem;
    }
}

/* The hairline from a card up to the thing it is describing. Desktop only:
   stacked, each card is already directly under the whole picture and a line
   pointing at it would be pointing at nothing in particular. */
.method-stem {
    display: none;
}

@media (min-width: 768px) {
    .method-stem {
        display: block;
        position: absolute;
        bottom: 100%;
        /* Where the stem meets the picture. The markup gives `--beat-x`, the
           fraction of the *artwork* the beat sits at — 0.23 for the tree, which
           is a number somebody can check by looking at the picture. This turns
           it into a position inside one card, which is not: percentages on
           `left` resolve against the card, so `300% + 2 * var(--gap)` is the
           row's full width, and subtracting `--beat` whole columns walks back to
           this card's own left edge.

           The three numbers used to be written out already converted (70%, 47%,
           36%) and nobody could get from those to the picture without redoing
           the algebra — nor would they have known to redo it after changing the
           gap or the column count, both of which are in this sum. */
        left: calc(
            var(--beat-x) * (300% + 2 * var(--gap))
            - var(--beat) * (100% + var(--gap))
        );
        width: 2px;
        height: 2.5rem;
        transform: translateX(-50%);
        background: linear-gradient(180deg, rgba(42, 95, 150, 0) 0%, rgba(42, 95, 150, 0.4) 70%);
    }

    /* The head of the stem, resting on the artwork's lower edge. */
    .method-stem::before {
        content: '';
        position: absolute;
        top: -4px;
        left: 50%;
        width: 9px;
        height: 9px;
        margin-left: -4.5px;
        border-radius: 999px;
        background: var(--brand-2);
        box-shadow: 0 0 0 3px rgba(86, 184, 233, 0.22);
        transition: background 0.18s ease, box-shadow 0.18s ease;
    }

    /* Picking up a card lights the point in the picture it is talking about. */
    .method-card:hover .method-stem::before,
    .method-card:focus-within .method-stem::before {
        background: var(--brand-1);
        box-shadow: 0 0 0 5px rgba(86, 184, 233, 0.3);
    }
}

/* ── Reveal on scroll ──
   Generic, driven by `reveal.js`. The hidden state hangs off `js-reveal` on
   the root element, which that script sets only once it has decided it is
   actually going to observe something — so a browser without
   IntersectionObserver, or a visitor who has asked their OS for less motion,
   is never left looking at content that was hidden and never un-hidden. */
.js-reveal [data-reveal] {
    opacity: 0;
    transform: translateY(18px);
}

.js-reveal [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1);
    /* `--beat` is the card's index, so the row lands left to right — the
       direction the picture above it is read in. Both numbers came down (0.55s
       and 110ms): three cards at the old pair took 0.77s to finish arriving,
       and a reader who has scrolled to a row and is waiting for the third card
       is reading a slow page, whatever its frame rate. */
    transition-delay: calc(var(--beat, 0) * 70ms);
}

/* ── Mr. Nibras landing page ──────────────────────────────────────────
   The marketplace keeps its teal, which is its identity across the directory,
   the profiles and the application form. What it borrows from the home page is
   the *structure*: artwork carrying the first screen, cards that lift under the
   cursor, and a staggered arrival on scroll (`[data-reveal]`, above).

   The hero is one band with the copy on one side and the character on the
   other. Stacked below `lg`, artwork first: on a phone the picture is what says
   what this place is, and it says it in less vertical space than the paragraph
   does. */
.mrn-hero {
    position: relative;
    overflow: hidden;
    display: grid;
    gap: 1.75rem;
    align-items: center;
    padding: 2.5rem 1.5rem;
    border-radius: 1.5rem;
    color: #fff;
    /* #0b2143 is sampled from the artwork's paper sky, and re-sampled when the
       artwork was replaced with a darker render. The teal the marketplace is
       identified by is still here, but only in the far corner: the band the
       picture actually touches is the colour the picture is painted on, which is
       what stops it reading as a rectangle stuck onto a different page. */
    background: linear-gradient(170deg, #0b2143 0%, #163654 45%, #10535c 78%, #0f766e 100%);
    /* The teal the lit door's ink and eyebrow take; see `.door`. */
    --door-accent: #0f766e;
}

/* A slow light behind the artwork, roughly where the lamp sits. The band was a
   flat gradient with a rectangle on it; this gives the picture something to
   stand in rather than a colour to sit against, and it is the only thing on the
   page that moves on its own. Cheap: one composited element, no layout. */
.mrn-hero::before {
    content: '';
    position: absolute;
    top: -30%;
    inset-inline-end: -10%;
    width: 60%;
    aspect-ratio: 1;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(240, 212, 154, 0.2), rgba(240, 212, 154, 0) 70%);
    pointer-events: none;
    animation: hero-glow 14s ease-in-out infinite;
}

@keyframes hero-glow {
    0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: 0.85; }
    50%      { transform: translate3d(-3%, 4%, 0) scale(1.08); opacity: 1; }
}

.mrn-hero > *:not(.sky) { position: relative; }

@media (min-width: 1024px) {
    .mrn-hero {
        /* The copy leads: it holds both doors, and the artwork is the argument
           for walking through one rather than the thing being offered. */
        grid-template-columns: 1fr 1.15fr;
        gap: 2.5rem;
        padding: 3rem;
    }
}

/* Artwork first on a stacked layout, second beside the copy. `order` rather
   than moving it in the markup, because the copy is what a screen reader and a
   search engine should meet first at every width. */
.mrn-hero-art {
    order: -1;
    margin: 0;
    /* Barely a drift — six pixels over nine seconds. Enough that the character
       reads as alive rather than pasted on, and far too slow to pull a reader's
       eye off the two buttons beside it. */
    animation: hero-float 9s ease-in-out infinite;
}

@keyframes hero-float {
    0%, 100% { transform: translate3d(0, 0, 0); }
    50%      { transform: translate3d(0, -6px, 0); }
}

@media (prefers-reduced-motion: reduce) {
    .mrn-hero::before,
    .mrn-hero-art { animation: none; }
}

.mrn-hero-art picture,
.mrn-hero-art img {
    display: block;
    width: 100%;
    height: auto;
}

.mrn-hero-art picture {
    border-radius: 1.125rem;
    overflow: hidden;
    /* A shadow and nothing else. The 1px white ring that used to be here was
       holding a navy picture off a teal band; with the band navy where the
       picture meets it, an edge is the one thing that would give away that this
       is a rectangle. */
    box-shadow: 0 26px 60px rgba(3, 12, 30, 0.45);
}

@media (min-width: 1024px) {
    .mrn-hero-art { order: 0; }
}

.mrn-hero-copy { min-width: 0; }

/* The "by Kunh" badge above both heroes' headlines. One class, because it is
   one partial now: the two pages carried the same eleven declarations under two
   names. A link, so it answers a cursor. */
.brand-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 1.25rem;
    padding: 0.25rem 0.75rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.15);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.025em;
    transition: background 0.15s ease;
}

.brand-badge:hover { background: rgba(255, 255, 255, 0.26); }

/* ── The sky ──────────────────────────────────────────────
   Paper stars behind the hero and behind the teacher's door at the foot, drawn
   by `_sky.html`. Four-point rather than the artwork's five, because at 8–15px
   a five-point star turns into a blob and a four-point one still reads as a
   star.

   Position, size, delay and tilt arrive as custom properties on each star, the
   way `--beat` arrives on the cards: the numbers belong to the thing being
   placed, and the alternative is ten `nth-child` rules that say nothing. */
.sky {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    border-radius: inherit;
    pointer-events: none;
}

.sky-star {
    position: absolute;
    left: var(--x);
    top: var(--y);
    width: var(--s);
    height: var(--s);
    background: var(--c, #e8ddc9);
    clip-path: polygon(50% 0%, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0% 50%, 39% 39%);
    opacity: 0.45;
    animation: sky-twinkle 7s ease-in-out infinite;
    animation-delay: var(--d, 0s);
}

@keyframes sky-twinkle {
    0%, 100% { opacity: 0.2;  transform: rotate(var(--r, 0deg)) scale(0.8); }
    50%      { opacity: 0.55; transform: rotate(var(--r, 0deg)) scale(1.12); }
}

/* One shooting star, crossing about four times a minute. It is off screen for
   six sevenths of that: a streak that came past often enough to notice would be
   competing with the two buttons under it. The artwork has two of these drawn
   into it, which is where the idea comes from. */
.sky-comet {
    position: absolute;
    top: 2%;
    left: -14%;
    width: 88px;
    height: 2px;
    border-radius: 999px;
    background: linear-gradient(90deg, rgba(232, 221, 201, 0), rgba(232, 221, 201, 0.95));
    opacity: 0;
    animation: sky-comet 15s linear infinite;
}

/* 1250 across and 266 down is tan(12deg) — it travels along the line it is
   drawn on, rather than sliding sideways while pointing somewhere else. Shallow
   on purpose: at 22 degrees it came down through the headline, and a pale streak
   crossing the type reads as a scratch on the screen rather than as a sky. */
@keyframes sky-comet {
    0%   { opacity: 0;   transform: translate3d(0, 0, 0) rotate(12deg); }
    3%   { opacity: 0.9; }
    16%  { opacity: 0;   transform: translate3d(1250px, 266px, 0) rotate(12deg); }
    100% { opacity: 0;   transform: translate3d(1250px, 266px, 0) rotate(12deg); }
}

/* ── The hero's copy ────────────────────────────────────── */
.mrn-hero-title {
    margin-bottom: 1rem;
    font-size: clamp(1.875rem, 1.1rem + 3.4vw, 3rem);
    font-weight: 800;
    line-height: 1.12;
    text-wrap: balance;
}

.mrn-hero-title-hi {
    display: block;
    /* The lamp's own cream, not the teal that used to be here: the highlighted
       half is the promise, and it is lit by the thing in the picture. */
    color: #f0d49a;
}

.mrn-hero-lede {
    max-width: 34rem;
    margin-bottom: 1.75rem;
    font-size: 1rem;
    line-height: 1.65;
    color: rgba(255, 255, 255, 0.85);
}

@media (min-width: 768px) {
    .mrn-hero-lede { font-size: 1.0625rem; }
}

/* ── The two doors ───────────────────────────────────────────────
   One lit and one cut into the sky, and that is the whole difference between
   them — same size, same shape, same weight. Both heroes on this site put two
   audiences side by side and neither of them has a bigger one, so a solid
   button beside an outline one would be saying something neither page means.

   Shared by the home hero and the Mr. Nibras hero, which differ in exactly one
   colour: the ink on the lit door. That arrives as `--door-accent` from the
   band, the way `--lift-border` arrives from the card families above. The gold
   is not parameterised because it does not vary — Nibras's lamp and Mr. Nibras
   are the same lamp, in the same gold, on both. */
.door {
    display: block;
    padding: 0.9rem 1.15rem;
    border-radius: 0.85rem;
    border: 1px solid rgba(240, 212, 154, 0.4);
    color: #fff;
    transition: background 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
}

.door:hover { background: rgba(255, 255, 255, 0.1); transform: translateY(-1px); }

.door--lit {
    border-color: transparent;
    background: #fff;
    color: var(--door-accent, #0f4f4a);
}

.door--lit:hover { background: #f6fafc; }

.door-eyebrow {
    display: block;
    margin-bottom: 0.15rem;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #f0d49a;
}

.door--lit .door-eyebrow { color: var(--door-accent, #0f4f4a); }

.door-label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 700;
}

/* The accent as ink on a neutral pill, rather than as a tint of itself: a
   background alpha cannot be derived from a custom property without
   `color-mix`, and the count reads at least as well this way. */
.door-count {
    padding: 0.05rem 0.5rem;
    border-radius: 999px;
    background: rgba(15, 23, 42, 0.07);
    font-size: 0.75rem;
    font-weight: 600;
}

/* `surface-card` still draws these — `.mrn-card` only says "answers a cursor
   the way the home page's cards do", and the movement itself is the shared
   rule up beside `.method-card`. All this contributes is the teal. It does not
   touch `display`: the teacher's-door card is a `.mrn-card` too and lays itself
   out as a grid, and a `flex-direction` here used to sit on it doing nothing,
   overridden only because `.mrn-teach` happened to come later in the file. */
.mrn-card {
    /* Gold, since the mark inside the card turns gold under the same cursor. It
       was teal, which is still the page's interactive colour — the links, the
       buttons, the directory band — but nothing about these cards is a control,
       and a teal rim around a gold mark was the last thing on the page still
       arguing with the picture. Only this page uses `.mrn-card`. */
    --lift-border: rgba(207, 165, 95, 0.5);
    --lift-shadow: 0 16px 32px rgba(18, 38, 74, 0.14);
}

/* The mark and the heading on one line, on the three claim cards. They were
   stacked, which gave each card three left-aligned blocks down its length and
   made the tile read as a bullet for the whole card rather than as the
   heading's own mark. Side by side, the head is one object and the paragraph
   below it is the body.

   (`.mrn-claim` itself has carried no rule since `.mrn-card-mark` was folded
   into the shared `.card-mark` and took the neighbouring block with it. It did
   not need one: the cards stack on ordinary block flow.) */
.mrn-claim-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.mrn-claim-title {
    font-weight: 800;
    line-height: 1.3;
    color: #0f172a;
}

/* The commitment, drawn as the banner in the artwork: cream paper, notched at
   both ends. It was a tinted box with a teal bar down one side, which is the
   shape this site uses for an aside — and this is the page's one promise.

   `clip-path` rather than pseudo-element triangles, so the notch survives the
   band being any width. The horizontal padding has to clear it, which is why
   there is a wider one above 768px, where the notch grows with the band. */
.mrn-pledge {
    max-width: 46rem;
    margin-inline: auto;
    padding: 1.5rem 2.25rem;
    text-align: center;
    background: linear-gradient(180deg, #f8efdd 0%, #efe1c6 100%);
    clip-path: polygon(0 0, 100% 0, calc(100% - 1.4rem) 50%, 100% 100%, 0 100%, 1.4rem 50%);
}

@media (min-width: 768px) {
    .mrn-pledge {
        padding: 2rem 3.5rem;
        clip-path: polygon(0 0, 100% 0, calc(100% - 1.75rem) 50%, 100% 100%, 0 100%, 1.75rem 50%);
    }
}

.mrn-pledge-eyebrow {
    margin-bottom: 0.5rem;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #9a7020;
}

.mrn-pledge-title {
    margin-bottom: 0.5rem;
    font-size: 1.25rem;
    font-weight: 800;
    color: #12264a;
}

@media (min-width: 768px) {
    .mrn-pledge-title { font-size: 1.5rem; }
}

.mrn-pledge-body {
    max-width: 38rem;
    margin-inline: auto;
    font-size: 0.9375rem;
    line-height: 1.65;
    color: #3d4a63;
}

/* ── The teacher's door ────────────────────────────────────
   The same sky as the hero, so the page closes where it opened — and because
   with nobody listed yet this is the card the page most needs somebody to
   reach, and it was a white panel at the foot of three white panels. It drops
   `surface-card`, so the radius and the ground are its own. */
.mrn-teach {
    position: relative;
    overflow: hidden;
    display: grid;
    gap: 1.25rem;
    align-items: center;
    border-radius: 1rem;
    /* Transparent so there is an edge for the lift to colour: `surface-card`
       used to supply one, and without it the shared rule's `border-color` had
       nothing to paint. */
    border: 1px solid transparent;
    color: rgba(255, 255, 255, 0.85);
    background: linear-gradient(160deg, #12264a 0%, #173a5c 55%, #0f5f5d 100%);
    /* Read by the shared lift beside `.method-card`. */
    --lift-border: rgba(240, 212, 154, 0.45);
    --lift-shadow: 0 18px 40px rgba(3, 12, 30, 0.4);
}

.mrn-teach > *:not(.sky) { position: relative; }

@media (min-width: 768px) {
    .mrn-teach { grid-template-columns: 1fr auto; }
}

.band-eyebrow {
    margin-bottom: 0.65rem;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #f0d49a;
}

/* Sized here rather than by a utility on the tag: site.css loads after
   tailwind.css, so a `text-2xl` beside this class was silently doing nothing. */
.band-title {
    margin-bottom: 0.65rem;
    font-size: 1.375rem;
    font-weight: 800;
    color: #fff;
}

@media (min-width: 768px) {
    .band-title { font-size: 1.75rem; }
}

/* Its own colour: it used to inherit the 85% white `.mrn-teach` set on itself,
   and `.night-band` sets a flat white. */
.band-body {
    margin-bottom: 0.75rem;
    font-size: 0.9375rem;
    line-height: 1.65;
    color: rgba(255, 255, 255, 0.85);
}

.band-note {
    font-size: 0.8125rem;
    color: rgba(255, 255, 255, 0.6);
}

/* A tinted line inside a dark band: Mr. Nibras's "we are just opening", and
   for-schools' "free plan available". One piece of news, in the band's own gold
   rather than as another paragraph or another green pill. */
.band-flag {
    display: inline-block;
    margin-bottom: 0.75rem;
    padding: 0.35rem 0.7rem;
    border-radius: 0.5rem;
    background: rgba(240, 212, 154, 0.16);
    font-size: 0.8125rem;
    font-weight: 600;
    color: #f0d49a;
}

/* The lamp button: gold, for the primary action on a dark band. `btn-solid` is
   the site's blue gradient, which on a navy ground is a dark button on a dark
   one. Both pages' closing cards use it. */
.btn-lamp {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.7rem 1.25rem;
    border-radius: 0.75rem;
    background: linear-gradient(140deg, #f4dca9, #d5a85f);
    color: #12264a;
    font-weight: 800;
    box-shadow: 0 8px 20px rgba(3, 12, 30, 0.35);
    transition: box-shadow 0.15s, transform 0.15s;
}

.btn-lamp:hover {
    transform: translateY(-1px);
    box-shadow: 0 14px 28px rgba(3, 12, 30, 0.45);
}

.btn-lamp:active { transform: none; }

.btn-lamp:focus-visible {
    outline: 2px solid #f0d49a;
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    /* The card's own lift and the mark's tint are dropped by the shared block
       beside `.method-card`. These are the ones that block does not reach. */
    .door,
    .btn-lamp { transition: none; }

    .sky-star { animation: none; }

    /* Stopped rather than paused: a halted comet is a bright streak parked in
       the middle of the band. */
    .sky-comet { display: none; }
}

/* ── Find your teacher (the Mr. Nibras directory) ─────────────────────
   The search band. It was a page title and a grey row of form controls; on the
   page where somebody chooses who teaches their child the Quran, the choosing
   should look like the subject rather than like a filter on a table. */
.mrn-find {
    padding: 1.75rem 1.5rem;
    margin-bottom: 1.5rem;
    border-radius: 1.5rem;
    color: #fff;
    background: linear-gradient(135deg, #0f766e 0%, #115e59 55%, #164e63 100%);
}

@media (min-width: 768px) {
    .mrn-find { padding: 2.25rem 2.5rem; }
}

.mrn-find-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 1.25rem;
}

.mrn-find-title {
    font-family: var(--font-display);
    font-size: 1.75rem;
    font-weight: 800;
    line-height: 1.15;
}

@media (min-width: 768px) {
    .mrn-find-title { font-size: 2.25rem; }
}

.mrn-find-sub {
    margin-top: 0.25rem;
    font-size: 0.9375rem;
    color: rgba(255, 255, 255, 0.75);
}

.mrn-find-back {
    font-size: 0.875rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.8);
    white-space: nowrap;
}

.mrn-find-back:hover { color: #fff; }

/* One field, its icon and its button as a single object, so the search reads as
   one control rather than three things that happen to be adjacent. */
.mrn-search {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.35rem 0.35rem 0.35rem 0.9rem;
    border-radius: 999px;
    background: #fff;
    box-shadow: 0 10px 28px rgba(3, 25, 30, 0.18);
}

.mrn-search svg {
    width: 1.15rem;
    height: 1.15rem;
    flex-shrink: 0;
    color: #94a3b8;
}

/* `!important` is not needed, but the reset is: the site's base input styling
   gives every box a border and a white ground, which inside this pill draws a
   second box around the text. */
.mrn-search input[type="search"] {
    flex: 1 1 auto;
    min-width: 0;
    border: 0;
    outline: 0;
    padding: 0.55rem 0;
    background: transparent;
    font-size: 0.9375rem;
    color: #0f172a;
    box-shadow: none;
}

.mrn-search input[type="search"]:focus { box-shadow: none; }

.mrn-search-go {
    flex-shrink: 0;
    padding: 0.55rem 1.25rem;
    border-radius: 999px;
    background: linear-gradient(135deg, #0f766e, #115e59);
    color: #fff;
    font-size: 0.875rem;
    font-weight: 700;
    transition: filter 0.18s ease;
}

.mrn-search-go:hover { filter: brightness(1.12); }

.mrn-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1rem;
}

/* Links, not buttons — see `_toggled` in the view. Pressed state is a real
   colour inversion rather than a tint, because at three or four chips a tint is
   not enough to tell on from off at a glance. */
.mrn-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.35);
    font-size: 0.8125rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.9);
    transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}

.mrn-chip:hover { background: rgba(255, 255, 255, 0.14); }

.mrn-chip.is-on {
    background: #fff;
    border-color: #fff;
    color: #115e59;
}

.mrn-chip-clear {
    font-size: 0.8125rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.7);
    text-decoration: underline;
}

.mrn-chip-clear:hover { color: #fff; }

.mrn-sort { margin-inline-start: auto; }

.mrn-sort select {
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.35);
    background: rgba(255, 255, 255, 0.1);
    color: #fff;
    font-size: 0.8125rem;
    font-weight: 600;
}

/* The options themselves are drawn by the OS, which does not inherit the band's
   white-on-teal — without this they are white on white in several browsers. */
.mrn-sort select option { color: #0f172a; }

/* ── The teacher card ──
   Was a `surface-card` with utility classes. Its own component now because it
   grew a schedule, and because three pages include it — a card that drifts
   between the featured row and the directory is three cards. */
.teacher-card {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 1.25rem;
    border: 1px solid var(--surface-border);
    border-radius: 1rem;
    background: var(--surface);
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}

.teacher-card:hover,
.teacher-card:focus-visible {
    transform: translateY(-3px);
    border-color: rgba(13, 148, 136, 0.35);
    box-shadow: 0 16px 32px rgba(8, 51, 68, 0.12);
}

/* In flow, not pinned to the corner. Absolute, it landed on top of the name of
   any teacher whose name was longer than the space left beside it — and
   reserving that space on every card to protect the one card that uses it would
   truncate two names to save one badge. */
.teacher-card-flag {
    align-self: flex-start;
    margin-bottom: 0.6rem;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    border: 1px solid #fde68a;
    background: #fef3c7;
    color: #b45309;
    font-size: 0.625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.teacher-card-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.teacher-card-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    flex-shrink: 0;
    border-radius: 50%;
    background: linear-gradient(140deg, #14b8a6, #0891b2);
    color: #fff;
    font-family: var(--font-display);
    font-size: 1.125rem;
    font-weight: 800;
}

.teacher-card-name {
    font-size: 1rem;
    font-weight: 700;
    color: #1e293b;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color 0.18s ease;
}

.teacher-card:hover .teacher-card-name { color: #0f766e; }

.teacher-card-meta {
    font-size: 0.75rem;
    color: #94a3b8;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.teacher-card-credential {
    margin-bottom: 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: #0f766e;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.teacher-card-headline {
    margin-bottom: 0.75rem;
    font-size: 0.875rem;
    color: #475569;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.teacher-card-rating {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin-bottom: 0.75rem;
}

/* The times. The whole reason this card was rebuilt: a family picks on "can my
   child be there", and that used to be a page deeper. */
.teacher-card-slots {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin-bottom: 0.9rem;
    padding: 0.6rem 0.7rem;
    border-radius: 0.65rem;
    background: rgba(13, 148, 136, 0.06);
    font-size: 0.75rem;
    color: #334155;
}

.teacher-card-slots li {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
}

.teacher-card-slots svg {
    width: 0.875rem;
    height: 0.875rem;
    flex-shrink: 0;
    color: #0f766e;
}

.teacher-card-slot-time {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Only drawn when the number is small — "12 seats left" is not scarcity, it is
   noise, and the template decides that rather than this rule. */
.teacher-card-seats {
    flex-shrink: 0;
    margin-inline-start: auto;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: #fef3c7;
    color: #b45309;
    font-size: 0.6875rem;
    font-weight: 700;
}

.teacher-card-slots-more {
    color: #0f766e;
    font-weight: 600;
}

.teacher-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-top: auto;
    padding-top: 0.6rem;
    border-top: 1px solid rgba(148, 163, 184, 0.2);
}

.teacher-card-tags {
    min-width: 0;
    font-size: 0.75rem;
    color: #94a3b8;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.teacher-card-open,
.teacher-card-full {
    flex-shrink: 0;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}

.teacher-card-open { color: #059669; }
.teacher-card-full { color: #94a3b8; }

/* The empty directory. For now this is what every visitor sees, so it is a
   room with two doors rather than a grey box saying "no results" on a page
   nobody searched. */
.mrn-empty {
    padding: 2.5rem 1.5rem;
    border-radius: 1.25rem;
    border: 1px dashed rgba(13, 148, 136, 0.35);
    background: rgba(240, 253, 250, 0.6);
    text-align: center;
}

.mrn-empty-title {
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 800;
    color: #0f2448;
    margin-bottom: 0.5rem;
}

.mrn-empty-body {
    max-width: 34rem;
    margin: 0 auto 1.25rem;
    font-size: 0.9375rem;
    line-height: 1.65;
    color: #64748b;
}

.mrn-empty-doors {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    justify-content: center;
}

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

/* ── Parent-brand link row ──
   The one action in the "Technology Behind Nibras" band, and a link that leaves
   the site. It was styled as an inline text link in cyan, which is the weight
   of a link inside a paragraph rather than of the band's only button. */
.brand-link-row {
    display: inline-flex;
    align-items: center;
    gap: 0.9rem;
    margin-top: 1rem;
    padding: 0.6rem 0.9rem;
    border: 1px solid var(--surface-border);
    border-radius: 0.75rem;
    background: #fff;
    text-decoration: none;
    transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}

.brand-link-row:hover {
    border-color: rgba(42, 95, 150, 0.45);
    box-shadow: 0 6px 16px rgba(15, 36, 72, 0.08);
    transform: translateY(-1px);
}

.brand-link-row:active { transform: translateY(0); }

.brand-link-row:focus-visible {
    outline: 2px solid var(--brand-1);
    outline-offset: 2px;
}

.brand-link-text {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
    min-width: 0;
}

.brand-link-label {
    font-family: var(--font-display);
    font-size: 0.875rem;
    font-weight: 700;
    color: var(--brand-ink);
}

/* The bare host. Quiet, and always LTR — a domain is not a translated string
   and must not be reordered inside an Arabic page. */
.brand-link-host {
    font-size: 0.72rem;
    color: #94a3b8;
}

.brand-link-icon {
    width: 0.95rem;
    height: 0.95rem;
    flex-shrink: 0;
    color: #94a3b8;
    transition: color 0.15s;
}

.brand-link-row:hover .brand-link-icon { color: var(--brand-1); }

@media (prefers-reduced-motion: reduce) {
    .brand-link-row { transition: none; }
    .brand-link-row:hover { transform: none; }
}

/* ── Home editorial block ──
   The lead article, the rows under it, and the updates feed beside them. All
   three replaced `surface-card` grids; what they have in common is that the
   whole item is one link, so each is either an <a> or carries the
   `::after` overlay `.program-card` established. */

.editorial-lead {
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* Only the lead has a picture, and it is optional. A fixed height with
   `object-cover` so a portrait upload and a wide one crop to the same band
   instead of changing the card's height. */
.editorial-lead-img {
    width: 100%;
    height: 11rem;
    object-fit: cover;
}

.editorial-lead-body { padding: 1.1rem 1.25rem 1.25rem; }

.editorial-lead-title {
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 800;
    line-height: 1.25;
    color: #0f2448;
    margin-bottom: 0.5rem;
}

/* One target for the whole card: the overlay covers it, so there is a single
   focusable element with a single accessible name. */
.editorial-lead-link { color: inherit; text-decoration: none; }
.editorial-lead-link::after { content: ""; position: absolute; inset: 0; }
.editorial-lead:hover .editorial-lead-title { color: var(--brand-1); }

.editorial-lead-summary {
    font-size: 0.9rem;
    color: #64748b;
    line-height: 1.6;
    margin-bottom: 0.6rem;
}

.editorial-meta {
    font-size: 0.75rem;
    color: #94a3b8;
}

/* The second and third articles. A rule between them and nothing else — no
   box, because a box would make them peers of the lead. */
.editorial-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.8rem 0.25rem;
    border-bottom: 1px solid var(--surface-border);
    text-decoration: none;
}

.editorial-row:first-of-type { margin-top: 0.35rem; }
.editorial-row:last-child { border-bottom: none; }

.editorial-row-title {
    font-size: 0.9rem;
    font-weight: 700;
    color: #334155;
    line-height: 1.4;
}

.editorial-row:hover .editorial-row-title { color: var(--brand-1); }

.editorial-row-date,
.news-item-date {
    flex-shrink: 0;
    font-size: 0.7rem;
    font-weight: 700;
    color: #94a3b8;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* ── Updates feed ──
   Date above the title rather than beside it, so a long headline is not fighting the
   date for the same narrow column. Hairlines between, none at the ends. */
.news-feed {
    display: flex;
    flex-direction: column;
}

.news-item {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.7rem 0.25rem;
    border-bottom: 1px solid var(--surface-border);
    text-decoration: none;
}

.news-item:last-child { border-bottom: none; }

.news-item-title {
    font-size: 0.875rem;
    font-weight: 700;
    color: #334155;
    line-height: 1.4;
}

.news-item:hover .news-item-title { color: var(--brand-1); }

.editorial-more {
    flex-shrink: 0;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--brand-1);
    text-decoration: none;
    white-space: nowrap;
}

.editorial-more:hover { color: var(--brand-ink); text-decoration: underline; }

/* ── Logo wall (home "Trusted by") ──
   Marks on the page, not in cards. Generous even gaps, because the spacing is
   what makes a row of logos read as one wall; `row-gap` smaller than
   `column-gap` so a wrapped second row sits close to the first instead of
   looking like a separate list. */
.logo-wall {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    column-gap: 2.25rem;
    row-gap: 1.1rem;
}

/* Greyscale and dimmed at rest, true colour on hover — the convention every
   "trusted by" row uses. It is also what makes marks of unequal quality sit
   together: one partner has uploaded a real logo and six have monograms, and at
   rest all seven are the same weight of grey, so the row looks composed rather
   than half-finished. */
.logo-wall-item {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    text-decoration: none;
    filter: grayscale(1);
    opacity: 0.62;
    transition: opacity 0.18s, filter 0.18s;
}

.logo-wall-item:hover,
.logo-wall-item:focus-visible {
    filter: none;
    opacity: 1;
}

.logo-wall-name {
    font-family: var(--font-display);
    font-size: 0.875rem;
    font-weight: 700;
    color: #334155;
    white-space: nowrap;
}

/* The last item in the row rather than a button beneath it: "and there is more
   this way" only reads that way while it sits on the same line as the marks. */
.logo-wall-more {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--brand-1);
    text-decoration: none;
    white-space: nowrap;
}

.logo-wall-more:hover {
    color: var(--brand-ink);
    text-decoration: underline;
}

@media (max-width: 640px) {
    /* Tighter on a phone, where 2.25rem between items leaves two per row and a
       lot of empty gutter. */
    .logo-wall { column-gap: 1.4rem; row-gap: 0.9rem; }
    .logo-wall-name { font-size: 0.8125rem; }
}

/* ── Homepage hero ──
   No card: the copy sits on the page itself, centred, over a photograph of a
   handwritten mushaf. The photo is on a ::before rather than on the section so
   its opacity drops without taking the text down with it, and a radial mask
   dissolves its rectangle into the page instead of ending on a hard edge.

   This photograph is dense Arabic script edge to edge — high-frequency dark
   strokes at roughly the size of the headline sitting on top of them — so it
   needs more restraint than a softer image would:

   `opacity: 0.14` composites the darkest strokes to about #d9dbdc over the
   page ground. The headline (#0f172a) still clears 12.9:1 against that and the
   line under it 7.5:1, so this is not a contrast compromise — the risk with
   this image was busyness, not legibility.

   `blur(1.6px)` is the part that does the work: blurred below the size of a
   letterform, script stops being something the eye tries to read and becomes
   texture. The mask fades every edge to transparent, so the blur has no
   rectangle to leave a soft seam on.

   The padding below is load-bearing, not taste. The mask is an ellipse sized
   as a *percentage of this box*, so growing the padding grows the box, and the
   photo's visible core stretches and slides relative to the copy sitting on
   it. Inflating these values once — 3rem to 6rem — was enough to visibly
   change the overlay without touching a single line of the overlay's own
   rules. Change them and check the hero, or move the mask's percentages to
   match. */
/* ── Home hero ───────────────────────────────────────────────────────
   Nibras's blue where Mr. Nibras has its navy-to-teal, and the same shape: a
   band with a ground of its own, copy on one side and the lamp on the other,
   two labelled doors, a slow light and a sky. The mark is the same lamp on both
   pages — one drawn flat here and folded out of paper there — so a visitor who
   arrives at either recognises the other.

   The book photograph is a background layer rather than the `::before` wash it
   used to be. It was 14% opacity behind centred type on white; here the
   gradient above it is nearly opaque and the photo is grain under the blue,
   which is what it was doing all along. One declaration, so there is no
   pseudo-element to keep out of the sky's stacking order. */
/* The ground every dark band on the public site stands on: the two the home page
   opens and closes with, the interior pages' `.page-hero`, and the closing cards
   on /for-schools/, /programs/ and /app/. Its own class because those are six
   bands and a four-stop gradient copied six times is six copies to keep in step.

   It replaced `.hero-card`, whose gradient ran through the brand's gold at 120%
   and landed near lime in the corner. That class has no users left and is gone
   from this file. */
.night-band {
    position: relative;
    overflow: hidden;
    color: #fff;
    background: linear-gradient(170deg, #11294b 0%, #1d4d7f 46%, #2a5f96 78%, #3b82c4 100%);
    /* The ink the lit door takes; see `.door`. */
    --door-accent: #1d4d7f;
}

/* Everything except the sky is lifted above it. */
.night-band > *:not(.sky) { position: relative; }

/* — The interior pages' band —————————————————————————————
   /programs/, /for-schools/ and /contact/ each opened on a cyan pill and a
   headline floating on the page's own wash. Nothing was wrong with any of them
   on its own; what was wrong is that the two pages a visitor is most likely to
   land on open on a band, and these three opened on nothing, so the site changed
   identity depending which link you followed.

   Centred rather than two-column, because none of them has artwork to sit
   beside — the lamp is on the home page and the paper-craft scene is on Mr.
   Nibras, and a third band borrowing one of them would be the same picture
   twice. */
.page-hero {
    padding: 2.5rem 1.5rem;
    border-radius: 1.5rem;
    text-align: center;
}

@media (min-width: 768px) {
    .page-hero { padding: 3.25rem 2.5rem; }
}

.page-hero-eyebrow {
    margin-bottom: 0.6rem;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: #f0d49a;
}

.page-hero-title {
    margin-inline: auto;
    max-width: 22ch;
    font-size: clamp(1.75rem, 1.1rem + 2.6vw, 2.75rem);
    font-weight: 800;
    line-height: 1.15;
    text-wrap: balance;
}

.page-hero-lede {
    max-width: 40rem;
    margin: 0.9rem auto 0;
    font-size: 1rem;
    line-height: 1.65;
    color: rgba(255, 255, 255, 0.85);
}

.home-hero {
    display: grid;
    gap: 1.75rem;
    align-items: center;
    padding: 2.5rem 1.5rem;
    border-radius: 1.5rem;
}

@media (min-width: 1024px) {
    .home-hero {
        /* The copy leads here, where on Mr. Nibras the artwork does: that page's
           picture is a scene with an argument in it, and this one is a mark. */
        grid-template-columns: 1.1fr 0.9fr;
        gap: 2.5rem;
        padding: 3.5rem 3rem;
    }
}

.home-hero-copy { min-width: 0; }

.home-hero-title {
    margin-bottom: 1rem;
    font-size: clamp(2rem, 1.2rem + 3.6vw, 3.25rem);
    font-weight: 800;
    line-height: 1.1;
    text-wrap: balance;
}

.home-hero-lede {
    max-width: 32rem;
    margin-bottom: 1.75rem;
    font-size: 1.0625rem;
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.85);
}

@media (min-width: 768px) {
    .home-hero-lede { font-size: 1.1875rem; }
}

/* Artwork first when the band stacks, beside the copy when it does not.
   `order` rather than moving it in the markup, so a screen reader and a search
   engine meet the headline first at every width. */
.home-hero-art {
    position: relative;
    order: -1;
    margin: 0;
    text-align: center;
}

@media (min-width: 1024px) {
    .home-hero-art { order: 0; }
}

/* The lamp lights the band it is in. */
.home-hero-art::before {
    content: '';
    position: absolute;
    inset: -20% -14%;
    border-radius: 50%;
    background: radial-gradient(ellipse at center, rgba(250, 215, 130, 0.38), rgba(250, 215, 130, 0) 68%);
    pointer-events: none;
    animation: hero-glow 14s ease-in-out infinite;
}

/* The full lockup rather than the mark alone. The header carries the lockup too,
   two rows up — but it carries it at 3rem on a white bar, and this is the
   brand at the size a brand introduces itself. `_light` because the wordmark is
   drawn in blue and slate for a pale ground: on this band the Arabic scored
   1.04:1, which is not low contrast but no contrast. */
.home-mark {
    position: relative;
    display: block;
    width: clamp(220px, 46vw, 440px);
    height: auto;
    margin: 0 auto;
    filter: drop-shadow(0 18px 34px rgba(3, 12, 30, 0.4));
    animation: hero-float 9s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .home-hero-art::before,
    .home-mark { animation: none; }
}

.surface-card {
    background: var(--surface);
    border: 1px solid var(--surface-border);
    border-radius: 1rem;
    box-shadow: 0 8px 22px rgba(15, 23, 42, 0.06);
}

/* ── Program card ──
   The whole card opens the program, but the title is the only link: its ::after
   covers the card, so there is one focusable element and one accessible name
   rather than a div with an onclick nobody can reach from a keyboard. The enroll
   button lifts above that overlay instead of nesting inside it. */
.program-card {
    position: relative;
}

.program-card-link {
    color: inherit;
    text-decoration: none;
}

.program-card-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}

.program-card:hover {
    transform: translateY(-3px);
}

.program-card:focus-within {
    outline: 2px solid var(--brand-1);
    outline-offset: 3px;
}

.program-card-link:focus-visible {
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .program-card:hover {
        transform: none;
    }
}

.btn-solid {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.75rem;
    padding: 0.7rem 1rem;
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1), var(--brand-2));
    color: #fff;
    font-weight: 800;
    box-shadow: 0 4px 12px rgba(29, 77, 127, 0.18);
    transition: box-shadow 0.15s, transform 0.15s;
}

.btn-solid:hover {
    box-shadow: 0 10px 24px rgba(29, 77, 127, 0.32);
    transform: translateY(-1px);
}

/* Pressed, and it has to be a separate state: with only a hover the button
   gives no feedback on the press itself, which on touch — where there is no
   hover at all — meant tapping it did nothing visible until the next page
   painted. */
.btn-solid:active {
    box-shadow: 0 2px 8px rgba(29, 77, 127, 0.24);
    transform: translateY(0);
}

.btn-solid:focus-visible,
.btn-outline:focus-visible {
    outline: 2px solid var(--brand-1);
    outline-offset: 2px;
}

/* The solid button on the dark gradient card. Its own gradient is the same
   navy as the card behind it, so at rest it read as a slightly different patch
   of the same colour; on white it fills with the brand and keeps the weight of
   the primary action. */
.btn-on-dark {
    background: #fff;
    color: var(--brand-ink);
    box-shadow: 0 4px 14px rgba(8, 24, 48, 0.28);
}

.btn-on-dark:hover {
    background: #fff;
    color: var(--brand-ink);
    box-shadow: 0 10px 26px rgba(8, 24, 48, 0.36);
}

.btn-on-dark:focus-visible {
    outline-color: #fff;
}

/* The secondary action beside .btn-solid, on a light surface.
   Sized to match it exactly: the 2px border is subtracted from the padding, so
   an outline button and a solid one sitting side by side are the same height
   rather than differing by the border. */
.btn-outline {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.75rem;
    padding: calc(0.7rem - 2px) calc(1rem - 2px);
    border: 2px solid var(--brand-1);
    background: #fff;
    color: var(--brand-ink);
    font-weight: 700;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.btn-outline:hover {
    background: var(--brand-ink);
    border-color: var(--brand-ink);
    color: #fff;
}

/* Modifiers on the button family above, rather than a private class per feature.
   `.btn-solid` hard-codes its padding, so call sites had already started patching
   the size by hand (`class="btn-solid text-sm py-2"`) and the store's grid — two
   cards across a phone, where the full padding wraps a label onto three lines —
   would have been the first to fork the whole thing. A fork is the expensive
   kind of duplication here: a rebrand or a focus-visible change made to
   `.btn-solid` silently misses anything that only *looks* like it. */
.btn-compact {
    gap: 0.35rem;
    padding: 0.45rem 0.7rem;
    font-size: 0.78rem;
    line-height: 1.15;
    text-align: center;
}

/* The same button with the emphasis taken out, for a link that still works but
   should not be promising much — an out-of-stock item's shop page, say. Reads as
   secondary without becoming a different control. */
.btn-muted {
    background: #fff;
    border: 1px solid #e2e8f0;
    color: #64748b;
    box-shadow: none;
}

.btn-muted:hover {
    background: #f8fafc;
    border-color: #cbd5e1;
    color: #0f172a;
    box-shadow: none;
}

/* The same button on a dark background — the hero cards, where the surface is a
   brand gradient and a brand-coloured border would disappear into it. This was
   what `.btn-outline` used to be for every caller; it is its own class now
   because the great majority sit on white cards, where white-on-white made them
   invisible. */
.btn-outline-on-dark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.75rem;
    padding: calc(0.7rem - 2px) calc(1rem - 2px);
    border: 2px solid rgba(255, 255, 255, 0.55);
    background: transparent;
    color: #fff;
    font-weight: 700;
    transition: background 0.15s, border-color 0.15s;
}

.btn-outline-on-dark:hover {
    background: rgba(255, 255, 255, 0.16);
    border-color: #fff;
    transform: translateY(-1px);
}

.btn-outline-on-dark:active {
    transform: translateY(0);
}

.btn-outline-on-dark:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .btn-solid,
    .btn-outline,
    .btn-outline-on-dark {
        transition: none;
    }

    /* The lift goes too, not just its transition — an instant 1px jump on
       hover is the same motion delivered without the easing. */
    .btn-solid:hover,
    .btn-outline-on-dark:hover {
        transform: none;
    }
}

.btn-mini,
.btn-mini-secondary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.6rem;
    padding: 0.45rem 0.75rem;
    font-size: 0.85rem;
    font-weight: 700;
}

.btn-mini {
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
    color: #fff;
}

.btn-mini-secondary {
    background: #fff2b3;
    color: var(--brand-ink);
    border: 1px solid #f7da6f;
}

.admin-pill {
    display: inline-flex;
    align-items: center;
    padding: 0.4rem 0.7rem;
    border-radius: 999px;
    background: #e9f6ff;
    color: var(--brand-ink);
    border: 1px solid #9bd1f1;
}

.admin-pill:hover {
    background: #d5efff;
}

.pill-item {
    display: inline-flex;
    width: 100%;
    border: 1px solid #e2e8f0;
    background: #f8fafc;
    padding: 0.5rem 0.7rem;
    border-radius: 0.7rem;
}

.schedule-item {
    border: 1px solid #e2e8f0;
    background: #f8fafc;
    border-radius: 0.75rem;
    padding: 0.6rem 0.8rem;
}

.schedule-item summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    font-weight: 700;
}

.schedule-item summary::-webkit-details-marker {
    display: none;
}

.note-item {
    border-left: 4px solid var(--brand-2);
    background: #f8fafc;
    border-radius: 0.65rem;
    padding: 0.7rem 0.8rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
input[type="tel"],
input[type="url"],
select,
textarea {
    width: 100%;
    border-radius: 0.7rem;
    border: 1px solid #cbd5e1;
    padding: 0.55rem 0.7rem;
    background: #fff;
    transition: border-color 0.15s, box-shadow 0.15s;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
input[type="datetime-local"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
select:focus,
textarea:focus {
    outline: none;
    border-color: var(--brand-1);
    box-shadow: 0 0 0 3px rgba(42, 95, 150, 0.15);
}

/* Error-state form fields */
.field-error input,
.field-error select,
.field-error textarea {
    border-color: #e11d48;
    background: #fff1f2;
    box-shadow: 0 0 0 2px rgba(225, 29, 72, 0.12);
}

.field-error input:focus,
.field-error select:focus,
.field-error textarea:focus {
    border-color: #e11d48;
    box-shadow: 0 0 0 3px rgba(225, 29, 72, 0.18);
}

.form-error-summary {
    border: 1px solid #fecdd3;
    background: #fff1f2;
    border-radius: 0.75rem;
    padding: 0.9rem 1rem;
    margin-bottom: 1rem;
}

.form-error-summary p {
    margin: 0 0 0.35rem;
    font-weight: 700;
    color: #9f1239;
}

.form-error-summary ul {
    margin: 0;
    padding-left: 1.25rem;
    color: #be123c;
    font-size: 0.875rem;
    line-height: 1.5;
}

label {
    display: inline-block;
    font-weight: 700;
    margin-bottom: 0.25rem;
}

.consent-line {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-bottom: 0.35rem;
    font-weight: 700;
}

.consent-line input[type="checkbox"] {
    width: 1.1rem;
    height: 1.1rem;
    margin-top: 0.1rem;
}

.consent-line span {
    font-weight: 700;
}

/* ═══════════════════════════════════════════════════════════════════
   Auth Pages – Login / Signup / Password-Reset / Closed pages
   ═══════════════════════════════════════════════════════════════════ */

.auth-page-wrapper {
    display: flex;
    justify-content: center;
    padding: 1rem 0 3rem;
}

.auth-card {
    width: 100%;
    max-width: 28rem;           /* 448px – comfortable for single-column forms */
    background: var(--surface);
    border: 1px solid var(--surface-border);
    border-radius: 1.25rem;
    box-shadow:
        0 4px 6px rgba(15, 23, 42, 0.04),
        0 12px 28px rgba(15, 23, 42, 0.08);
    padding: 2rem 1.75rem;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}

.auth-card-wide {
    max-width: 38rem;           /* 608px – for the signup form with sections */
}

/* ── Card Header ── */
.auth-card-header {
    text-align: center;
    margin-bottom: 0.25rem;
}

.auth-icon-ring {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 50%;
    background: linear-gradient(135deg, rgba(42, 95, 150, 0.12), rgba(86, 184, 233, 0.18));
    color: var(--brand-1);
    margin-bottom: 0.75rem;
}

.auth-icon-ring svg {
    width: 1.25rem;
    height: 1.25rem;
}

.auth-icon-ring-lg {
    width: 3.25rem;
    height: 3.25rem;
}

.auth-icon-ring-lg svg {
    width: 1.5rem;
    height: 1.5rem;
}

.auth-icon-ring-success {
    background: linear-gradient(135deg, rgba(16, 185, 129, 0.14), rgba(52, 211, 153, 0.22));
    color: #059669;
}

.auth-icon-ring-warning {
    background: linear-gradient(135deg, rgba(245, 158, 11, 0.14), rgba(251, 191, 36, 0.22));
    color: #d97706;
}

.auth-icon-ring-error {
    background: linear-gradient(135deg, rgba(225, 29, 72, 0.12), rgba(251, 113, 133, 0.18));
    color: #e11d48;
}

.auth-icon-pulse {
    animation: auth-pulse 2.4s ease-in-out infinite;
}

@keyframes auth-pulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.06); }
}

.auth-title {
    font-family: var(--font-display);
    font-size: 1.65rem;
    font-weight: 800;
    color: #0f172a;
    line-height: 1.2;
    margin-bottom: 0.35rem;
}

.auth-subtitle {
    color: #64748b;
    font-size: 0.92rem;
    line-height: 1.5;
}

/* ── Form ── */
.auth-form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.auth-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.auth-label {
    font-size: 0.82rem;
    font-weight: 700;
    color: #334155;
    letter-spacing: 0.01em;
}

/* A quiet line under a field — "Forgot your password?" beneath the password
   box. Trailing edge (logical, so it follows the reading direction), muted
   until hovered. Not the brand blue at rest: a coloured link here pulls the eye
   on every visit, and the overwhelming majority of visits are somebody who
   knows their password and is about to press Sign In. */
.auth-field-aside {
    margin-top: 0.15rem;
    text-align: end;
    font-size: 0.78rem;
    line-height: 1.3;
}

.auth-field-aside a {
    color: #64748b;
    font-weight: 600;
    text-decoration: none;
}

.auth-field-aside a:hover {
    color: var(--brand-1);
    text-decoration: underline;
}

.auth-input-wrap {
    position: relative;
}

.auth-input-icon {
    position: absolute;
    left: 0.7rem;
    top: 50%;
    transform: translateY(-50%);
    color: #94a3b8;
    pointer-events: none;
    display: flex;
    align-items: center;
}

.auth-input-icon svg {
    width: 1rem;
    height: 1rem;
}

.auth-input-wrap input,
.auth-input-wrap select,
.auth-input-wrap textarea {
    padding-left: 2.3rem;
}

/* When there is no icon inside the wrap, reset padding */
.auth-input-wrap:not(:has(.auth-input-icon)) input,
.auth-input-wrap:not(:has(.auth-input-icon)) select,
.auth-input-wrap:not(:has(.auth-input-icon)) textarea {
    padding-left: 0.7rem;
}

/* ── Password show/hide ────────────────────────────────────────────────
   Mirror of `.auth-input-icon` on the other edge, but a real button: that one
   is decorative and `pointer-events: none`, this one is pressed.

   `display: none` until `password_reveal.js` sets `hidden = false`, so a
   browser with the script blocked never shows a control that could not work.
   Logical properties (`inset-inline-end`, `padding-inline-end`) so it lands on
   the trailing edge in Arabic, Urdu, Punjabi and Pashto without a second
   `[dir="rtl"]` rule — which is what the icon on the other side still needs. */
.auth-reveal {
    display: none;
    position: absolute;
    inset-inline-end: 0.35rem;
    top: 50%;
    transform: translateY(-50%);
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: none;
    border-radius: 0.5rem;
    background: none;
    color: #94a3b8;
    cursor: pointer;
    transition: color 0.15s, background 0.15s;
}

.auth-reveal[hidden] { display: none; }
.auth-reveal:not([hidden]) { display: flex; }

.auth-reveal:hover { color: #475569; background: rgba(15, 23, 42, 0.06); }
.auth-reveal:focus-visible {
    color: #475569;
    outline: 2px solid var(--brand-1);
    outline-offset: 1px;
}
.auth-reveal svg { width: 1.05rem; height: 1.05rem; }

/* Only on a box that actually grew a button — the class is added by the script,
   so a no-JS page keeps the padding it had. Enough room for the 2rem button
   plus its offset, so a long password never runs underneath it. */
.auth-input-wrap.has-reveal input {
    padding-inline-end: 2.6rem;
}

.auth-field-error input,
.auth-field-error select,
.auth-field-error textarea {
    border-color: #fb7185;
    background: #fff1f2;
    box-shadow: 0 0 0 2px rgba(225, 29, 72, 0.1);
}

.auth-field-msg {
    font-size: 0.8rem;
    color: #e11d48;
    font-weight: 600;
    margin-top: 0.15rem;
}

/* ── Error Banner ── */
.auth-error-banner {
    border: 1px solid #fecdd3;
    background: linear-gradient(135deg, #fff1f2, #ffe4e6);
    border-radius: 0.85rem;
    padding: 0.85rem 1rem;
    color: #9f1239;
}

.auth-error-banner ul {
    list-style: disc;
    padding-left: 1.1rem;
    color: #be123c;
}

/* ── Info Box ── */
.auth-info-box {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    border: 1px solid #bae6fd;
    background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
    border-radius: 0.85rem;
    padding: 0.85rem 1rem;
    color: #0c4a6e;
}

/* ── Buttons ── */
.auth-btn-primary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    border-radius: 0.85rem;
    padding: 0.75rem 1.25rem;
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1), var(--brand-2));
    color: #fff;
    font-weight: 800;
    font-size: 0.95rem;
    border: none;
    cursor: pointer;
    transition: transform 0.15s, box-shadow 0.15s, opacity 0.15s;
    box-shadow: 0 4px 14px rgba(29, 77, 127, 0.25);
}

.auth-btn-primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 6px 20px rgba(29, 77, 127, 0.35);
}

.auth-btn-primary:active {
    transform: translateY(0);
}

.auth-btn-primary:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none;
}

.auth-btn-outline {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border-radius: 0.85rem;
    padding: 0.7rem 1.25rem;
    border: 2px solid var(--brand-1);
    color: var(--brand-ink);
    font-weight: 700;
    font-size: 0.92rem;
    background: transparent;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.auth-btn-outline:hover {
    background: rgba(42, 95, 150, 0.06);
}

/* ── "Continue with Google" ──
   Google's own button spec, not the site's: white ground, #747775 hairline,
   #1f1f1f label, and the four-colour mark at its published proportions. A
   sign-in button that has been restyled to match a site is the one thing a
   visitor cannot recognise at a glance, and it is what a brand review rejects.
   It sits under a divider because it is an alternative to the form above it,
   not the next step in it. */
.auth-btn-google {
    width: 100%;
    gap: 0.65rem;
    background: #fff;
    border: 1px solid #747775;
    color: #1f1f1f;
    font-weight: 600;
}

.auth-btn-google:hover {
    background: #f7f8f8;
    box-shadow: 0 1px 3px rgba(60, 64, 67, 0.2);
}

.auth-google-mark {
    width: 1.15rem;
    height: 1.15rem;
    flex-shrink: 0;
}

.auth-google-form {
    margin: 0;
}

/* A rule with the word "or" sitting in it. Two absolutely-positioned lines
   would need to know the page background; a flex row with a border on each
   side does not, and it flips for RTL on its own. */
.auth-divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 1rem 0;
    color: #94a3b8;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    border-top: 1px solid #e2e8f0;
}

/* ── Card Footer ──
   The other door: registering. Roomier than it was, because it holds a button
   now rather than two lines of small print. */
.auth-card-footer {
    text-align: center;
    padding-top: 1.1rem;
    margin-top: 0.25rem;
    border-top: 1px solid #e2e8f0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

/* The one line of copy above the button. Quiet: the button is the thing being
   read, this only says who it is for. */
.auth-footer-lead {
    font-size: 0.85rem;
    color: #64748b;
}

/* Full width, so it matches the form's own button and the Google one and the
   card reads as three stacked actions rather than two and a link. */
.auth-btn-block {
    width: 100%;
    text-decoration: none;
}

.auth-card-footer p {
    font-size: 0.85rem;
    color: #64748b;
}

.auth-card-footer a {
    color: var(--brand-1);
    font-weight: 700;
}

.auth-card-footer a:hover {
    text-decoration: underline;
}

/* ── Signup Sections ── */
.auth-section {
    border: 1px solid #e2e8f0;
    border-radius: 0.95rem;
    overflow: hidden;
    background: #fff;
}

.auth-section-header {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.65rem 1rem;
    background: linear-gradient(135deg, #f8fafc, #f1f5f9);
    border-bottom: 1px solid #e2e8f0;
}

.auth-step-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
    color: #fff;
    font-size: 0.75rem;
    font-weight: 800;
    flex-shrink: 0;
}

.auth-section-title {
    font-size: 0.92rem;
    font-weight: 800;
    color: #1e293b;
}

.auth-section-body {
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

/* ── Consent Rows ── */
.auth-consent-row {
    padding: 0.65rem 0.8rem;
    border: 1px solid #e2e8f0;
    border-radius: 0.75rem;
    background: #f8fafc;
    transition: border-color 0.15s;
}

.auth-consent-row:hover {
    border-color: #cbd5e1;
}

.auth-consent-row.auth-field-error {
    border-color: #fb7185;
    background: #fff1f2;
}

.auth-consent-row input[type="checkbox"] {
    width: 1.15rem;
    height: 1.15rem;
    accent-color: var(--brand-1);
    margin-top: 0.15rem;
    flex-shrink: 0;
}

/* ── Responsive ── */
@media (max-width: 640px) {
    .auth-card {
        padding: 1.5rem 1.15rem;
        border-radius: 1rem;
    }

    .auth-title {
        font-size: 1.35rem;
    }

    .auth-section-body {
        padding: 0.75rem;
    }
}

.site-footer {
    background: linear-gradient(180deg, rgba(233, 246, 255, 0.4), rgba(255, 255, 255, 0.82));
}

/* The quotes used to glide past inside a bordered card, which is a moving thing
   in a frame. The frame is gone and the strip runs the full width of the
   viewport instead; what replaces the border is a fade at each end, so the row
   reads as carrying on past the screen rather than as starting and stopping
   just off it.

   The fade is two overlays rather than a `mask-image`, and the difference is
   not cosmetic. The row inside this element is a promoted layer whose transform
   changes on every frame, forever; a mask on its ancestor makes the compositor
   draw that layer into an offscreen surface the width of the viewport and
   multiply it by the mask, sixty times a second, instead of blitting it at an
   offset. Two gradients painted once into this element's own layer buy the same
   picture for nothing. They match the page ground (`body`, above) — the corner
   washes there are transparent well before this far down the page. */
.testimonial-track {
    position: relative;
    overflow: hidden;
}

.testimonial-track::before,
.testimonial-track::after {
    content: '';
    position: absolute;
    z-index: 1;
    top: 0;
    bottom: 0;
    width: 5%;
    pointer-events: none;
}

.testimonial-track::before {
    left: 0;
    background: linear-gradient(90deg, #f8fafc, rgba(248, 250, 252, 0));
}

.testimonial-track::after {
    right: 0;
    background: linear-gradient(270deg, #f8fafc, rgba(248, 250, 252, 0));
}

.testimonial-track-row {
    display: flex;
    gap: 1rem;
    width: max-content;
    animation: testimonials-slide 38s linear infinite;
    will-change: transform;
}

.testimonial-track:hover .testimonial-track-row {
    animation-play-state: paused;
}

/* A horizontal slide that never ends is the strongest reduced-motion trigger on
   the page, and it was the one animation here with no guard — it predates the
   setting being honoured elsewhere in this file. Stopped rather than slowed:
   the row is `width: max-content` and starts at `translateX(0)`, so at rest it
   shows the first quotes and the rest are simply off the end, which is what the
   overflow is for. */
@media (prefers-reduced-motion: reduce) {
    .testimonial-track-row {
        animation: none;
        will-change: auto;
    }
}

/* A quote, not a card. These were six white panels with a border and a shadow
   sliding past inside a seventh — and on a page whose every other section has
   given up its box, a row of boxes is the one thing left arguing with it. A
   rule at the start of each and nothing else, which is how the two outcome
   flags and the two wide points above are drawn. */
.testimonial-card {
    width: min(310px, 80vw);
    padding-block: 0.35rem;
    padding-inline-start: 1.1rem;
    border-inline-start: 2px solid rgba(42, 95, 150, 0.22);
}

.testimonial-quote {
    color: #334155;
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1.6;
}

.testimonial-author {
    margin-top: 0.6rem;
    color: #0f766e;
    font-size: 0.8125rem;
    font-weight: 700;
}

@keyframes testimonials-slide {
    from {
        transform: translateX(0);
    }
    to {
        transform: translateX(-50%);
    }
}

.prose {
    color: #1f2937;
    line-height: 1.75;
}

.prose > *:first-child {
    margin-top: 0;
}

.prose > *:last-child {
    margin-bottom: 0;
}

.prose h1,
.prose h2,
.prose h3,
.prose h4 {
    color: #0f172a;
    font-family: var(--font-display);
    font-weight: 800;
    line-height: 1.25;
    margin-top: 1.6rem;
    margin-bottom: 0.7rem;
}

.prose h1 {
    font-size: 2rem;
}

.prose h2 {
    font-size: 1.6rem;
}

.prose h3 {
    font-size: 1.3rem;
}

/* Fourth level exists for the sub-headings inside the privacy policy's Google
   section. Only slightly above body text: the step from h3 has to read as a
   step without turning a subsection into a section. */
.prose h4 {
    font-size: 1.08rem;
}

.prose p,
.prose ul,
.prose ol,
.prose blockquote,
.prose pre,
.prose table {
    margin-bottom: 1rem;
}

.prose ul {
    list-style: disc;
    padding-left: 1.5rem;
}

.prose ol {
    list-style: decimal;
    padding-left: 1.5rem;
}

.prose li + li {
    margin-top: 0.35rem;
}

.prose a {
    color: #0f766e;
    text-decoration: underline;
}

.prose code {
    background: #e2e8f0;
    border-radius: 0.25rem;
    padding: 0.1rem 0.35rem;
}

.prose pre {
    background: #0f172a;
    color: #e2e8f0;
    border-radius: 0.75rem;
    padding: 0.9rem 1rem;
    overflow-x: auto;
}

.prose pre code {
    background: transparent;
    padding: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   Portal Pages – Dashboard / Teacher / Parent / Student
   ═══════════════════════════════════════════════════════════════════ */

/* ── Portal Header ── */
.portal-header {
    margin-bottom: 1.75rem;
}

.portal-header h1 {
    font-family: var(--font-display);
    font-size: clamp(1.6rem, 4vw, 2.25rem);
    font-weight: 800;
    color: #0f172a;
    line-height: 1.15;
}

.portal-header-sub {
    color: #64748b;
    font-size: 0.95rem;
    margin-top: 0.25rem;
}


/* ── Stat Cards ── */
.portal-stats {
    display: grid;
    gap: 0.75rem;
    margin-bottom: 1.75rem;
}

.portal-stat {
    background: var(--surface);
    border: 1px solid var(--surface-border);
    border-radius: 1rem;
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.05);
    padding: 1rem 1.15rem;
    display: flex;
    align-items: center;
    gap: 0.85rem;
    transition: transform 0.15s, box-shadow 0.15s;
}

.portal-stat:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 22px rgba(15, 23, 42, 0.09);
}

.portal-stat-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 0.75rem;
    flex-shrink: 0;
}

.portal-stat-icon svg {
    width: 1.35rem;
    height: 1.35rem;
}

.portal-stat-icon-blue {
    background: linear-gradient(135deg, #dbeafe, #bfdbfe);
    color: #2563eb;
}

.portal-stat-icon-teal {
    background: linear-gradient(135deg, #ccfbf1, #99f6e4);
    color: #0d9488;
}

.portal-stat-icon-amber {
    background: linear-gradient(135deg, #fef3c7, #fde68a);
    color: #d97706;
}

.portal-stat-icon-purple {
    background: linear-gradient(135deg, #ede9fe, #ddd6fe);
    color: #7c3aed;
}

.portal-stat-icon-rose {
    background: linear-gradient(135deg, #ffe4e6, #fecdd3);
    color: #e11d48;
}

.portal-stat-icon-green {
    background: linear-gradient(135deg, #d1fae5, #a7f3d0);
    color: #059669;
}

.portal-stat-body {
    flex: 1;
    min-width: 0;
}

.portal-stat-label {
    font-size: 0.75rem;
    font-weight: 700;
    color: #64748b;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.portal-stat-value {
    font-family: var(--font-display);
    font-size: 1.65rem;
    font-weight: 800;
    color: #0f172a;
    line-height: 1.15;
}

/* ── Portal Sections ── */
.portal-section {
    background: var(--surface);
    border: 1px solid var(--surface-border);
    border-radius: 1rem;
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.05);
    margin-bottom: 1.5rem;
    overflow: hidden;
}

.portal-section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid #f1f5f9;
}

.portal-section-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-display);
    font-size: 1.1rem;
    font-weight: 800;
    color: #0f172a;
}

.portal-section-title svg {
    width: 1.2rem;
    height: 1.2rem;
    color: var(--brand-1);
    flex-shrink: 0;
}

.portal-section-body {
    padding: 1rem 1.25rem;
}

/* ── Portal Tables ── */
.portal-table {
    width: 100%;
    text-align: left;
    font-size: 0.875rem;
    border-collapse: separate;
    border-spacing: 0;
}

.portal-table thead th {
    padding: 0.65rem 0.85rem;
    font-size: 0.75rem;
    font-weight: 700;
    color: #64748b;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    background: #f8fafc;
    border-bottom: 2px solid #e2e8f0;
}

.portal-table thead th:first-child {
    border-radius: 0.5rem 0 0 0;
}

.portal-table thead th:last-child {
    border-radius: 0 0.5rem 0 0;
}

.portal-table tbody td {
    padding: 0.65rem 0.85rem;
    border-bottom: 1px solid #f1f5f9;
    color: #334155;
}

.portal-table tbody tr:last-child td {
    border-bottom: none;
}

.portal-table tbody tr:hover td {
    background: rgba(42, 95, 150, 0.04);
}

.portal-table tbody tr:nth-child(even) td {
    background: rgba(248, 250, 252, 0.6);
}

.portal-table tbody tr:nth-child(even):hover td {
    background: rgba(42, 95, 150, 0.04);
}

/* ── Filter Bar ── */
.portal-filter-bar {
    background: linear-gradient(135deg, #f8fafc, #f1f5f9);
    border: 1px solid #e2e8f0;
    border-radius: 0.85rem;
    padding: 1rem 1.15rem;
    margin-top: 1rem;
    display: grid;
    gap: 0.75rem;
    align-items: end;
}

.portal-filter-bar label {
    font-size: 0.82rem;
    font-weight: 700;
    color: #475569;
}

.portal-filter-bar select {
    font-size: 0.875rem;
}

.portal-filter-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

/* ── Timeline Improvements ── */
.portal-timeline {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.portal-day {
    border: 1px solid #e2e8f0;
    border-radius: 0.85rem;
    background: #fff;
    overflow: hidden;
    transition: border-color 0.15s;
}

.portal-day:hover {
    border-color: #cbd5e1;
}

.portal-day[open] {
    border-color: #93c5fd;
    box-shadow: 0 2px 8px rgba(42, 95, 150, 0.08);
}

.portal-day summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    font-weight: 700;
    color: #1e293b;
    user-select: none;
    transition: background 0.15s;
}

.portal-day summary:hover {
    background: #f8fafc;
}

.portal-day summary::-webkit-details-marker {
    display: none;
}

.portal-day-date {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.portal-day-date svg {
    width: 1rem;
    height: 1rem;
    color: var(--brand-1);
}

.portal-day-counts {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
}

.portal-day-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.72rem;
    font-weight: 700;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
}

.portal-day-pill-hw {
    background: #dbeafe;
    color: #1e40af;
}

.portal-day-pill-pn {
    background: #d1fae5;
    color: #065f46;
}

.portal-day-pill-gr {
    background: #fef3c7;
    color: #92400e;
}

.portal-day-content {
    padding: 0.75rem 1rem 1rem;
    border-top: 1px solid #f1f5f9;
}

.portal-day-group {
    margin-bottom: 0.75rem;
}

.portal-day-group:last-child {
    margin-bottom: 0;
}

.portal-day-group-title {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.82rem;
    font-weight: 800;
    color: #475569;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.35rem;
}

.portal-day-group-title svg {
    width: 0.9rem;
    height: 0.9rem;
}

.portal-day-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.portal-day-list li {
    padding: 0.4rem 0.6rem;
    margin-bottom: 0.25rem;
    border-radius: 0.5rem;
    font-size: 0.875rem;
    color: #334155;
    border-left: 3px solid transparent;
}

.portal-day-list li:hover {
    background: #f8fafc;
}

.portal-day-list-hw li { border-left-color: #3b82f6; }
.portal-day-list-pn li { border-left-color: #10b981; }
.portal-day-list-gr li { border-left-color: #f59e0b; }

/* ── Halaqa Cards (Teacher Dashboard) ── */
.portal-halaqa-card {
    border: 1px solid #e2e8f0;
    border-radius: 0.95rem;
    overflow: hidden;
    background: #fff;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.portal-halaqa-card:hover {
    border-color: #93c5fd;
    box-shadow: 0 4px 16px rgba(42, 95, 150, 0.08);
}

.portal-halaqa-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.85rem 1rem;
    background: linear-gradient(135deg, #f8fafc, #f1f5f9);
    border-bottom: 1px solid #e2e8f0;
}

.portal-halaqa-head-info h3 {
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 800;
    color: #0f172a;
}

.portal-halaqa-head-info p {
    font-size: 0.82rem;
    color: #64748b;
}

.portal-halaqa-schedule {
    font-size: 0.82rem;
    color: #64748b;
    padding: 0.55rem 1rem;
    border-bottom: 1px solid #f1f5f9;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.portal-halaqa-schedule svg {
    width: 0.9rem;
    height: 0.9rem;
    color: var(--brand-2);
    flex-shrink: 0;
}

.portal-halaqa-body {
    padding: 0;
}

/* ── Log Cards ── */
.portal-log {
    border-left: 3px solid var(--brand-2);
    background: #f8fafc;
    border-radius: 0 0.65rem 0.65rem 0;
    padding: 0.75rem 0.9rem;
    transition: background 0.15s;
}

.portal-log:hover {
    background: #f1f5f9;
}

.portal-log-kind {
    display: inline-flex;
    align-items: center;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.portal-log-kind-homework {
    background: #dbeafe;
    color: #1e40af;
}

.portal-log-kind-progress {
    background: #d1fae5;
    color: #065f46;
}

.portal-log-kind-assessment {
    background: #fef3c7;
    color: #92400e;
}

.portal-log-kind-general {
    background: #ede9fe;
    color: #5b21b6;
}

.portal-log-meta {
    font-size: 0.78rem;
    color: #94a3b8;
    margin-top: 0.15rem;
}

.portal-log-text {
    font-size: 0.875rem;
    color: #334155;
    margin-top: 0.3rem;
}

/* ── Badges ── */
.portal-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
}

.portal-badge-green {
    background: #d1fae5;
    color: #065f46;
}

.portal-badge-slate {
    background: #f1f5f9;
    color: #475569;
}

.portal-badge-amber {
    background: #fef3c7;
    color: #92400e;
}

.portal-badge-blue {
    background: #dbeafe;
    color: #1e40af;
}

.portal-badge-rose {
    background: #ffe4e6;
    color: #be123c;
}

/* ── Empty States ── */
.portal-empty {
    text-align: center;
    padding: 2rem 1rem;
    color: #94a3b8;
}

.portal-empty svg {
    width: 2.5rem;
    height: 2.5rem;
    margin: 0 auto 0.5rem;
    opacity: 0.5;
}

.portal-empty p {
    font-size: 0.92rem;
    font-weight: 600;
}

/* ── Portal Forms ── */
.portal-form-card {
    background: var(--surface);
    border: 1px solid var(--surface-border);
    border-radius: 1.15rem;
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.06);
    padding: 1.75rem 1.5rem;
    max-width: 36rem;
}

.portal-form-card h1 {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: 800;
    color: #0f172a;
    margin-bottom: 0.25rem;
}

.portal-form-card .portal-form-sub {
    color: #64748b;
    font-size: 0.88rem;
    margin-bottom: 1.25rem;
}

.portal-form-field {
    margin-bottom: 1rem;
}

.portal-form-field label {
    font-size: 0.82rem;
    font-weight: 700;
    color: #334155;
    margin-bottom: 0.3rem;
}

/* ── Toggle switch (iOS-style) for single boolean checkboxes ── */
.toggle-switch {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    cursor: pointer;
    user-select: none;
}

.toggle-switch input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    position: relative;
    width: 40px;
    height: 22px;
    background: #cbd5e1;
    border-radius: 999px;
    border: none;
    cursor: pointer;
    transition: background 0.2s;
    flex-shrink: 0;
    margin: 0;
}

.toggle-switch input[type="checkbox"]::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    background: #fff;
    border-radius: 50%;
    transition: transform 0.2s;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.toggle-switch input[type="checkbox"]:checked {
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
}

.toggle-switch input[type="checkbox"]:checked::before {
    transform: translateX(18px);
}

.toggle-switch input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--brand-1);
    outline-offset: 2px;
}

/* ── Styled checkbox group for multi-select (program_days, enabled_subjects) ── */
.pill-checkbox-group ul,
.pill-checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.pill-checkbox-group li {
    margin: 0;
}

.pill-checkbox-group label {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.45rem 0.85rem;
    border-radius: 0.6rem;
    border: 1.5px solid #e2e8f0;
    background: #fff;
    color: #475569;
    font-size: 0.82rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
    user-select: none;
    margin: 0;
}

.pill-checkbox-group label:hover {
    border-color: #94a3b8;
    background: #f8fafc;
}

.pill-checkbox-group input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    width: 16px;
    height: 16px;
    border-radius: 4px;
    border: 1.5px solid #cbd5e1;
    background: #fff;
    cursor: pointer;
    position: relative;
    margin: 0;
    flex-shrink: 0;
    transition: all 0.15s;
}

.pill-checkbox-group input[type="checkbox"]:checked {
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
    border-color: var(--brand-1);
}

.pill-checkbox-group input[type="checkbox"]:checked::after {
    content: "";
    position: absolute;
    top: 1px;
    left: 4px;
    width: 4px;
    height: 8px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.pill-checkbox-group label:has(input:checked) {
    border-color: var(--brand-1);
    background: rgba(42, 95, 150, 0.05);
    color: var(--brand-ink);
}

.portal-form-footer {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding-top: 0.5rem;
}

/* ── Messages Section ── */
.portal-messages-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.portal-msg-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.6rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    background: #f1f5f9;
    color: #475569;
    border: 1px solid #e2e8f0;
}

.portal-msg-pill-count {
    background: var(--brand-1);
    color: #fff;
    padding: 0.05rem 0.35rem;
    border-radius: 999px;
    font-size: 0.68rem;
    min-width: 1.2rem;
    text-align: center;
}

/* ── Roster Items ── */
.portal-roster-item {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.6rem 0.85rem;
    border: 1px solid #e2e8f0;
    border-radius: 0.7rem;
    background: #fff;
    transition: border-color 0.15s, background 0.15s;
    text-decoration: none;
    color: #1e293b;
    font-weight: 600;
}

.portal-roster-item:hover {
    border-color: #93c5fd;
    background: #f0f9ff;
}

.portal-roster-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
    color: #fff;
    font-size: 0.82rem;
    font-weight: 800;
    flex-shrink: 0;
}

/* ── Activity Day Headers ── */
.portal-activity-day {
    margin-bottom: 1rem;
}

.portal-activity-day:last-child {
    margin-bottom: 0;
}

.portal-activity-day-head {
    font-size: 0.78rem;
    font-weight: 800;
    color: #64748b;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-bottom: 0.4rem;
    margin-bottom: 0.5rem;
    border-bottom: 1px solid #f1f5f9;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.portal-activity-day-head svg {
    width: 0.9rem;
    height: 0.9rem;
}

/* ── Grade Band Colors ── */
.portal-grade-a { color: #059669; font-weight: 700; }
.portal-grade-b { color: #2563eb; font-weight: 700; }
.portal-grade-c { color: #d97706; font-weight: 700; }
.portal-grade-d { color: #ea580c; font-weight: 700; }
.portal-grade-f { color: #dc2626; font-weight: 700; }

/* ── Responsive Portal ── */
@media (max-width: 640px) {
    .portal-stat {
        padding: 0.75rem 0.85rem;
    }

    .portal-stat-icon {
        width: 2.25rem;
        height: 2.25rem;
    }

    .portal-stat-icon svg {
        width: 1.1rem;
        height: 1.1rem;
    }

    .portal-stat-value {
        font-size: 1.35rem;
    }

    .portal-section-body {
        padding: 0.75rem 0.85rem;
    }

    .portal-section-head {
        padding: 0.75rem 0.85rem;
    }

    .portal-form-card {
        padding: 1.25rem 1rem;
    }

    .portal-day summary {
        padding: 0.6rem 0.75rem;
    }

    .portal-day-content {
        padding: 0.6rem 0.75rem 0.75rem;
    }
}

@media (max-width: 1024px) {
    .site-menu {
        font-size: 0.95rem;
    }

    .surface-card {
        border-radius: 0.9rem;
    }
}

@media (max-width: 640px) {
    .site-menu .nav-pill {
        padding: 0.6rem 0.75rem;
        width: 100%;
        border-radius: 0.45rem;
        font-size: 0.9rem;
    }

    .site-logo {
        width: 100px;
    }

    .site-logo {
        width: 140px;
    }

    h1 {
        font-size: 1.7rem;
        line-height: 1.2;
    }

    h2 {
        font-size: 1.35rem;
        line-height: 1.25;
    }

    /* The outline variants are listed too: they are the same size and sit beside
       a solid button, so leaving them out gave a CTA row two different heights
       on a phone as well as one target under the comfortable minimum. */
    .btn-solid,
    .btn-outline,
    .btn-outline-on-dark,
    .btn-mini,
    .btn-mini-secondary {
        min-height: 42px;
    }

    table {
        display: block;
        width: 100%;
        overflow-x: auto;
        white-space: nowrap;
    }

    .site-footer > div {
        flex-direction: column;
        align-items: flex-start;
    }

    .testimonial-card {
        width: min(280px, 86vw);
    }
}

/* ──────────────────────────────────────────────────────────────────────
   Internationalization (i18n)
   ────────────────────────────────────────────────────────────────────── */

/* ── Language switcher ─────────────────────────────────────────────────
   A globe + the current ISO code, opening a panel that lists every language
   by its own name. It replaced a <select>, which stopped working at this
   size: a native dropdown renders its options in the OS font at the OS size,
   gives no room for a second line, and on Android and iOS turns into a
   full-screen wheel that shows about four entries at a time. */
.lang-switcher {
    position: relative;
    display: inline-flex;
    align-items: center;
    margin: 0;
}

/* Closed state: a text pill, deliberately not a button.

   It is the weakest thing in the header — lighter than the nav links it sits
   beside and lighter again than the outlined account action — because changing
   language is something a visitor does once, on their first visit, and never
   looks for again. So no border and no fill at rest: the globe and the code are
   the whole control, and only a hover tint says it is clickable. It had a
   1px border and a grey fill, which at this size read as a form field somebody
   had forgotten to finish styling.

   Both bars this sits in are light -- the site header and the portal bar -- so
   it borrows the nav pills' ink at a lower opacity; `.mobile-nav` inverts for
   its own navy sheet. */
.lang-trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.36rem 0.55rem;
    border-radius: 999px;
    background: none;
    border: none;
    color: rgba(22, 59, 102, 0.8);
    font-family: inherit;
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    cursor: pointer;
    transition: background 0.2s, color 0.2s;
}
.lang-trigger:hover,
.lang-trigger:focus-visible {
    background: rgba(22, 59, 102, 0.08);
    color: #163b66;
}
/* Kept at 16px even though the label beside it is smaller: a globe outline
   drawn any smaller than this stops reading as a globe. */
.lang-globe { width: 1rem; height: 1rem; flex-shrink: 0; }
.lang-trigger-code { font-weight: 600; }
.lang-caret {
    width: 0.65rem;
    height: 0.65rem;
    flex-shrink: 0;
    opacity: 0.6;
    transition: transform 0.2s;
}
.lang-switcher.is-open .lang-caret { transform: rotate(180deg); }
/* Open state. Collapsed from the first paint on any page that can reopen it —
   `.js` is set by an inline script in <head>, before the body is parsed, so the
   panel is never briefly visible on navigation. Without JavaScript the class
   never lands, the list is simply always there, and its rows still submit. */
.js .lang-panel { display: none; }
.js .lang-switcher.is-open .lang-panel { display: block; }

.lang-panel {
    position: absolute;
    top: calc(100% + 0.5rem);
    inset-inline-end: 0;
    z-index: 60;
    min-width: 15rem;
    max-height: min(70vh, 26rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0.4rem;
    margin: 0;
    background: #fff;
    border: 1px solid var(--surface-border);
    border-radius: 0.9rem;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
    text-align: start;
}
.lang-panel-title {
    margin: 0.25rem 0.5rem 0.4rem;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #64748b;
}
.lang-list { display: flex; flex-direction: column; gap: 0.1rem; }

.lang-option {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.45rem 0.5rem;
    border: none;
    border-radius: 0.6rem;
    background: transparent;
    color: #1e293b;
    font-family: inherit;
    text-align: start;
    cursor: pointer;
    transition: background 0.15s;
}
.lang-option:hover,
.lang-option:focus-visible { background: rgba(42, 95, 150, 0.09); }
.lang-option.is-current { background: rgba(42, 95, 150, 0.13); }

/* Fixed-width so the names line up into a column no matter how wide the code
   renders, and always LTR because "UR" is not an Urdu word. */
.lang-option-code {
    flex: 0 0 1.9rem;
    font-size: 0.65rem;
    font-weight: 800;
    letter-spacing: 0.05em;
    color: #64748b;
    text-align: center;
    padding: 0.1rem 0;
    border-radius: 0.3rem;
    background: rgba(100, 116, 139, 0.12);
}
.lang-option.is-current .lang-option-code {
    color: #fff;
    background: var(--brand-1);
}
.lang-option-names {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}
.lang-option-autonym {
    font-size: 0.92rem;
    font-weight: 600;
}
/* The English name is the fallback for a reader who cannot yet read the script
   they are switching to -- a parent setting a phone up for someone else. */
.lang-option-english {
    font-size: 0.7rem;
    color: #64748b;
}
.lang-option-check {
    width: 0.9rem;
    height: 0.9rem;
    flex-shrink: 0;
    color: var(--brand-1);
}

/* Nastaliq and Bengali sit on much taller line boxes than Latin does; without
   this the descenders of the autonym clip against the row below. */
.lang-option-names:lang(ur),
.lang-option-names:lang(pa),
.lang-option-names:lang(bn) { line-height: 1.9; }

/* In the mobile menu (white background, full width) the switcher stops being a
   popover: there is no room to float a panel over a sheet that is already the
   width of the screen, so it expands in place. */
.mobile-nav .lang-switcher { display: block; }
.mobile-nav .lang-trigger {
    width: 100%;
    justify-content: center;
    background: none;
    border: none;
    color: rgba(255, 255, 255, 0.8);
    font-size: 0.85rem;
    padding: 0.55rem 0.9rem;
    border-radius: 0.5rem;
}
.mobile-nav .lang-trigger:hover,
.mobile-nav .lang-trigger:focus-visible {
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
}
.mobile-nav .lang-panel {
    position: static;
    min-width: 0;
    width: 100%;
    margin-top: 0.4rem;
    max-height: 15rem;
    box-shadow: none;
    background: rgba(248, 250, 252, 0.9);
}

/* ── Script + RTL foundation ───────────────────────────────────────────
   Four of the offered languages are right-to-left (ar, ur, pa, ps) and a
   fifth (bn) needs a non-Latin face while staying LTR.
   Layout flips automatically for elements using logical Tailwind utilities
   (ms-/me-/ps-/pe-/start-/end-). The rules below cover the few cases that
   rely on physical direction or fixed transforms. As more templates migrate
   to logical utilities (Layer 3), this block should stay small. */
/* Per-script faces. Keyed on `lang`, not on `dir`, because direction and
   script are separate questions: Arabic, Urdu and Pashto are all RTL and each
   wants a different face, while Bengali is LTR and still needs one. The
   matching webfont is loaded in base.html from config/languages.py, so a
   visitor downloads only their own. */
/* One face serves both roles: a script the Latin pair cannot draw has no
   separate display cut to fall back to. Scoped to <html> so the switcher rows,
   which carry their own lang= to shape each autonym, are not caught by it. */
html[lang="ar"], html[lang="ur"], html[lang="pa"], html[lang="ps"], html[lang="bn"] {
    --font-display: var(--font-body);
}
html[lang="ar"] {
    --font-body: "Cairo", "Nunito", system-ui, sans-serif;
}
/* Urdu and Shahmukhi Punjabi are both written in Nastaliq. It is a steeply
   cascading style: the glyphs of a word descend as they go, so a line needs
   roughly twice the leading Latin does or the tails collide with the line
   under them. */
html[lang="ur"],
html[lang="pa"] {
    --font-body: "Noto Nastaliq Urdu", "Cairo", system-ui, sans-serif;
    line-height: 2;
}
html[lang="ps"] {
    --font-body: "Noto Naskh Arabic", "Cairo", system-ui, sans-serif;
}
html[lang="bn"] {
    --font-body: "Noto Sans Bengali", "Nunito", system-ui, sans-serif;
    line-height: 1.7;
}

/* ── RTL overrides for hand-written components ─────────────────────────
   Tailwind utilities in templates flip automatically (migrated to logical
   ms-/me-/ps-/pe-/text-start/text-end/start-/end- classes). These rules
   cover the custom CSS that still uses physical left/right. */

/* Text alignment */
[dir="rtl"] .portal-table { text-align: right; }

/* Content whose order is fixed by a picture keeps source order on an RTL page.
   The idiom is `.ltr-flow`, defined beside the component that needs it rather
   than here: it is a layout intent, not a physical-property patch, which is all
   this section is for. */

/* Auth input icon sits on the start (right) side; flip the icon padding */
[dir="rtl"] .auth-input-icon { left: auto; right: 0.7rem; }
[dir="rtl"] .auth-input-wrap input,
[dir="rtl"] .auth-input-wrap select,
[dir="rtl"] .auth-input-wrap textarea { padding-left: 0.7rem; padding-right: 2.3rem; }
[dir="rtl"] .auth-input-wrap:not(:has(.auth-input-icon)) input,
[dir="rtl"] .auth-input-wrap:not(:has(.auth-input-icon)) select,
[dir="rtl"] .auth-input-wrap:not(:has(.auth-input-icon)) textarea { padding-right: 0.7rem; }

/* List indentation flips to the start (right) side */
[dir="rtl"] .form-error-summary ul { padding-left: 0; padding-right: 1.25rem; }
[dir="rtl"] .auth-error-banner ul { padding-left: 0; padding-right: 1.1rem; }
[dir="rtl"] .prose ul,
[dir="rtl"] .prose ol { padding-left: 0; padding-right: 1.5rem; }

/* Accent border stripes move to the start (right) edge */
[dir="rtl"] .note-item { border-left: none; border-right: 4px solid var(--brand-2); }
[dir="rtl"] .portal-log { border-left: none; border-right: 3px solid var(--brand-2); }
[dir="rtl"] .portal-day-list li { border-left: none; border-right: 3px solid transparent; }
[dir="rtl"] .portal-day-list-hw li { border-right-color: #3b82f6; }
[dir="rtl"] .portal-day-list-pn li { border-right-color: #10b981; }
[dir="rtl"] .portal-day-list-gr li { border-right-color: #f59e0b; }

/* Toggle switch knob slides from the right in RTL */
[dir="rtl"] .toggle-switch input[type="checkbox"]::before { left: auto; right: 2px; }
[dir="rtl"] .toggle-switch input[type="checkbox"]:checked::before { transform: translateX(-18px); }

/* ── Square icon action button ───────────────────────────────────────
   The row-level verbs (view, edit, delete, add) on the Organizations page.
   In CSS rather than repeated utility strings because an organization row and
   a program row sit one above the other: written inline, the two drifted into
   words on one and icons on the other, which read as two different kinds of
   thing when they are the same four actions at two levels. */
.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    width: 2rem;
    height: 2rem;
    flex-shrink: 0;
    border-radius: 0.5rem;
    border: 1px solid #e2e8f0;
    background: #fff;
    color: #64748b;
    text-decoration: none;
    cursor: pointer;
    transition: background-color .15s, color .15s, border-color .15s;
}
.icon-btn:hover { background: #f8fafc; border-color: #cbd5e1; color: #1e293b; }
.icon-btn:focus-visible { outline: 2px solid #93c5fd; outline-offset: 1px; }
.icon-btn svg { width: 1rem; height: 1rem; }
/* Carries a label beside the icon; the label hides on narrow screens. */
.icon-btn-labelled { width: auto; padding: 0 0.7rem; font-size: 0.75rem; font-weight: 600; }
.icon-btn-primary {
    border-color: transparent;
    color: #fff;
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
}
/* The gradient is repeated here, and that repetition is the fix: `.icon-btn:hover`
   above sets `background: #f8fafc`, and this rule used to override only `color`
   and `border-color`. Same specificity, declared later — so hovering a primary
   button replaced its gradient with near-white and left white text on white.
   A variant that overrides a hover state has to restate every property that
   state changes. */
.icon-btn-primary:hover {
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
    color: #fff;
    filter: brightness(1.12);
    border-color: transparent;
}
.icon-btn-danger { color: #f43f5e; }
.icon-btn-danger:hover { background: #fff1f2; border-color: #fecdd3; color: #e11d48; }
@media (max-width: 639px) {
    .icon-btn-labelled .icon-btn-label { display: none; }
    .icon-btn-labelled { width: 2rem; padding: 0; }
}


/* ── Phone fields ─────────────────────────────────────────────────────
   A country picker and a number box on one row (``config.form_fields``).
   The picker takes only what its longest label needs and the number box takes
   the rest; below 26rem they stack, because a country name plus a phone number
   side by side on a narrow phone leaves neither readable. Logical properties
   throughout, so the row reverses itself in Arabic. */
.phone-input {
    display: flex;
    gap: 0.5rem;
    align-items: stretch;
}

.phone-input .phone-country-select {
    /* Sized for the closed label — a flag and a dial code ("+376" is the longest)
       — rather than for the country name, which `static/js/phone_field.js` puts
       back only while the list is open. A native select is otherwise as wide as
       its widest option, which here is a country name nobody needs to read once
       they have chosen it. Fixed rather than shrinkable so the code never
       ellipsises. */
    flex: 0 0 8rem;
    min-width: 0;
    text-overflow: ellipsis;
}

.phone-input .phone-number-input {
    flex: 1 1 auto;
    min-width: 0;
}

@media (max-width: 26rem) {
    .phone-input {
        flex-direction: column;
    }

    .phone-input .phone-country-select {
        flex: 0 0 auto;
    }
}


/* ── The store ────────────────────────────────────────────────────────────
   In CSS rather than utility strings on the templates because the same pieces
   are read by two files — the grid card and the quick-view panel both draw a
   photograph on the same light ground, and the chips need a `.is-active` class
   `static/js/store.js` can toggle. Logical properties throughout, so the whole
   page reverses in Arabic without a second rule. */

/* The filter bar. On a phone the chips are how a visitor changes shelf after
   scrolling, and having to scroll back to the top to reach them is what makes
   browsing a catalogue tiring — so it follows the page down, parked below the
   sticky site header.

   `--site-header-height` is published by base.html, which owns the header and is
   the only thing that can measure it: the logo is `clamp()`-sized, so the number
   changes with the viewport and again when the webfont lands, and a CSS guess is
   wrong at some breakpoint — being wrong by 20px leaves the bar half-swallowed.
   `is-sticky` is added by `static/js/store.js`, so with the script blocked the
   bar is an ordinary one at the top of the page, which is a fine thing to be. */
.store-filters.is-sticky {
    position: sticky;
    top: var(--site-header-height, 0px);
    z-index: 20;
}

.store-filters {
    background: rgba(248, 250, 252, 0.94);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid var(--surface-border);
    border-radius: 1rem;
    padding: 0.7rem 0.8rem;
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
}

/* The chip row bleeds past the bar's padding on a narrow screen so it scrolls
   under the thumb edge-to-edge instead of being boxed in with a stub of a
   scrollbar. `scrollbar-width: none` because the row is obviously scrollable
   from the chips clipped at its end. */
.store-chips {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.6rem;
    /* Bleeds back through the bar's own padding, so the row runs to the edge of
       the card and the last chip is clipped rather than sitting in a gutter —
       which is what tells a thumb there is more to scroll. */
    margin-inline: -0.8rem;
    padding-inline: 0.8rem;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 0.15rem;
}

.store-chips::-webkit-scrollbar { display: none; }

.store-chip {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    padding: 0.42rem 0.85rem;
    border-radius: 999px;
    border: 1px solid #e2e8f0;
    background: #fff;
    color: #475569;
    font-size: 0.8rem;
    font-weight: 700;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: background-color .15s, color .15s, border-color .15s;
}

.store-chip:hover { border-color: #cbd5e1; color: #0f172a; }
.store-chip:focus-visible { outline: 2px solid #93c5fd; outline-offset: 2px; }

.store-chip.is-active {
    background: linear-gradient(135deg, var(--brand-ink), var(--brand-1));
    border-color: transparent;
    color: #fff;
}

/* ── Item card ── */
.store-card {
    transition: box-shadow .2s, transform .2s;
}

.store-card:hover {
    box-shadow: 0 14px 30px rgba(15, 23, 42, 0.1);
    transform: translateY(-2px);
}

/* A fixed 4:3 window on a near-white ground, with the photo `contain`ed inside
   it (see the card template). Every card is then the same height whatever the
   supplier's photograph happens to be, which is what stops the grid from
   ragging — and a book cover keeps its title instead of being cropped to fill. */
.store-card-media {
    position: relative;
    aspect-ratio: 4 / 3;
    background: #f8fafc;
    border-bottom: 1px solid #f1f5f9;
    overflow: hidden;
}

/* ── Availability badge ──
   Rendered only when there is news: "Available" is the ordinary case, and
   badging it would put a label on every card in the grid, which is the same as
   putting one on none of them. On the photograph rather than in the text block,
   so it never competes with the title for the two lines it has. */
.store-badge {
    position: absolute;
    bottom: 0.5rem;
    inset-inline-start: 0.5rem;
    display: inline-flex;
    align-items: center;
    border-radius: 999px;
    padding: 0.15rem 0.55rem;
    font-size: 0.625rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border: 1px solid transparent;
}

/* The same badge inside the quick-view panel, where it sits in the text flow
   under the title instead of over a photograph. */
.store-badge--inline {
    position: static;
    align-self: flex-start;
}

.store-badge--out_of_stock {
    background: #f1f5f9;
    border-color: #e2e8f0;
    color: #64748b;
}

.store-badge--coming_soon {
    background: #eff6ff;
    border-color: #bfdbfe;
    color: #1d4ed8;
}

/* A round translucent icon button floated over a photograph — the ⓘ in a card's
   corner ("there is more to read here") and the × on the quick-view panel. One
   class with a size modifier, because they are the same control twice.

   The ⓘ sits in the corner rather than beside the outbound button because two
   cards fit across a phone, and an icon on that row wrapped the "Get it on…"
   label onto three lines. */
.store-float-btn {
    position: absolute;
    top: 0.5rem;
    inset-inline-end: 0.5rem;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.85rem;
    height: 1.85rem;
    border: 1px solid rgba(148, 163, 184, 0.3);
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.88);
    color: #475569;
    cursor: pointer;
    transition: background-color .15s, color .15s;
}

.store-float-btn:hover { background: #fff; color: #0f172a; }

.store-float-btn--lg {
    top: 0.6rem;
    inset-inline-end: 0.6rem;
    width: 2.1rem;
    height: 2.1rem;
    border-color: transparent;
    background: rgba(255, 255, 255, 0.9);
}

/* ── Quick view ──
   A sheet from the bottom on a phone and a centred panel on a desktop: the same
   markup, because on a small screen a dialog anchored to the thumb is reachable
   and one centred in the viewport is not. */
.store-modal {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}

.store-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.55);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
}

.store-modal-panel {
    position: relative;
    width: 100%;
    max-width: 30rem;
    max-height: 92vh;
    overflow-y: auto;
    background: #fff;
    border-radius: 1.25rem 1.25rem 0 0;
    box-shadow: 0 -10px 40px rgba(15, 23, 42, 0.25);
    animation: store-sheet-in .22s ease-out;
}

.store-modal-media {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 4 / 3;
    background: #f8fafc;
    border-bottom: 1px solid #f1f5f9;
}

.store-modal-media img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    padding: 1.25rem;
}

.store-modal-body { padding: 1.1rem 1.25rem 1.5rem; }

@keyframes store-sheet-in {
    from { transform: translateY(14px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

@media (min-width: 640px) {
    .store-modal { align-items: center; }

    .store-modal-panel {
        border-radius: 1.25rem;
        max-height: 88vh;
        box-shadow: 0 24px 60px rgba(15, 23, 42, 0.3);
    }
}

@media (prefers-reduced-motion: reduce) {
    .store-card,
    .store-chip { transition: none; }

    .store-card:hover { transform: none; }
    .store-modal-panel { animation: none; }
}

/* ── Bulk-action buttons ──────────────────────────────────────────────
   The peer actions in a "Bulk Actions" card: send the welcome mails, send the
   reminder, send the progress summary, export the CSV. One style for all of
   them, because they are peers — they were blue, amber, purple and green, four
   accent colours for four things of equal weight, which left the card with no
   colour available to mean anything. It now has one: red, on the single action
   that cannot be undone. */
.bulk-action-btn {
    font-size: 0.75rem;
    font-weight: 500;
    padding: 0.375rem 0.75rem;
    border-radius: 0.5rem;
    border: 1px solid rgb(203 213 225);      /* slate-300 */
    background: #fff;
    color: rgb(51 65 85);                    /* slate-700 */
    transition: background-color 0.15s, border-color 0.15s;
}

.bulk-action-btn:hover {
    background: rgb(248 250 252);            /* slate-50 */
    border-color: rgb(148 163 184);          /* slate-400 */
}

/* ──────────────────────────────────────────────────────────────────────
   /for-schools/ — the pitch page
   ──────────────────────────────────────────────────────────────────────

   This is the page a school is read on, and everything below the hero used to
   be one shape: a centred eyebrow, a centred heading, and a grid of equal
   rounded rectangles with an icon tile, a bold line and a grey paragraph in
   each. Twenty-five of them. The only thing that changed section to section was
   the column count — three, four, four, six, two, two — which reads as noise
   rather than as variety, and a reader scrolling it has no way to tell where
   they are because every screen looks like the last one.

   What follows gives the page a score instead. Six silhouettes, each used once:

     the ruled column   three pillars, hairline-separated, no card at all
     the sticky split   a heading that holds still while its list scrolls past
     the lit loop       four discs on a connector, inside the dark band
     the ledger         six modules as one hairline-divided slab on a wide band
     the flow           what goes in → what comes out, as bars rather than boxes
     the rail           the app, as a line of buttons rather than two cards

   Plus the two grounds the page already owns — white and the night band — and
   one new one, the tinted band the ledger sits on, which is also the only
   thing on the page that breaks the reading column. So the page now goes
   white → dark → tinted → white → dark, and a reader always knows where
   they are.

   Motion is `[data-reveal]` (see "Reveal on scroll") with `--beat` on every
   row, so each group arrives in reading order rather than all at once, and one
   scroll-linked layer at the bottom of this file for the browsers that have it.

   Nothing here invents a string. Every heading, label and sentence on the page
   is the msgid it already was, because fourteen catalogues are translated
   against them and a reshuffle of the shapes should cost none of that. */

/* ── Section heads ──
   The three lines every marketing section writes out: a cyan eyebrow, a display
   heading, a grey lede. Named because this page sets them start-aligned in some
   sections and centred in others — "centred" should be the modifier, not the
   default spelled out in Tailwind eleven times.

   Applied on /for-schools/ only so far. The same three Tailwind strings are
   still written out inline on the home page, /programs/ and the two article
   templates; sweeping those is a follow-up, not something that was considered
   and rejected. */
.section-eyebrow {
    margin-bottom: 0.5rem;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.2em;
    color: #0e7490;
}

.section-title {
    font-family: var(--font-display);
    font-size: clamp(1.5rem, 1.15rem + 1.35vw, 2rem);
    font-weight: 800;
    line-height: 1.2;
    color: #0f172a;
    text-wrap: balance;
}

.section-lede {
    margin-top: 0.65rem;
    max-width: 38rem;
    font-size: 1rem;
    line-height: 1.65;
    color: #64748b;
}

.section-head--center {
    text-align: center;
}

.section-head--center .section-lede {
    margin-inline: auto;
}

/* ── The two atoms every silhouette here is built from ──
   A block's heading and its supporting line. Six sections draw different
   containers around them — a ruled column, a hairline row, a ledger cell, a
   flag, a wide point — but the display ink and the body grey are the same in
   all of them, and written per section they were this page's one heading colour
   stated four times and its one body colour six. Each section now sets only
   what it actually varies: a size, a line-height, a margin.

   The three bodies that share a size are grouped too, because they are the same
   block of copy in three different containers. The other two are genuinely set
   smaller — a one-line caption under a row, and a one-line gloss in a ledger
   cell — so they keep their own. */
.pillar-title,
.rail-title,
.ledger-title,
.point-title {
    font-family: var(--font-display);
    font-weight: 800;
    color: #0f2448;
}

.pillar-body,
.rail-note,
.ledger-body,
.point-body,
.flow-note,
.flag-note {
    color: #64748b;
}

.pillar-body,
.point-body,
.flow-note {
    font-size: 0.9375rem;
    line-height: 1.7;
}

/* ── The wide band ──
   The one element allowed out of the reading column, so the page has a moment
   that is not 72rem wide.

   No `100vw`: on a desktop with classic scrollbars `100vw` is wider than the
   page by the scrollbar and the whole site gains a horizontal scroll, which is
   a poor trade for a background. Instead it cancels `<main>`'s own `px-4` — an
   exact number — which makes it edge-to-edge on a phone, and once the viewport
   is wide enough to have real margin outside the column it takes 4rem of that
   on each side. In between it is simply flush with the text, which is the one
   width where a band breaking out would have nowhere to break out to. */
.wide-band {
    margin-inline: -1rem;
}

/* The one breakpoint that is derived rather than chosen: 72rem of column plus
   enough margin either side for the band to have somewhere to go. Every rule
   that cares about the band being detached from the gutters lives in this one
   block, so the number is stated once. */
@media (min-width: 88rem) {
    .wide-band {
        margin-inline: -4rem;
    }

    /* Off the gutters, so the tinted slab under the ledger becomes an object
       with edges rather than a full-width stripe. */
    .ledger-band {
        border-inline: 1px solid rgba(148, 163, 184, 0.22);
        border-radius: 2rem;
    }
}

/* Puts the content back on the reading column inside a band that is not on it. */
.band-inner {
    max-width: 72rem;
    margin-inline: auto;
    padding-inline: 1rem;
}

/* ── The three pillars (platform, training, curriculum) ──
   The page's opening argument, and the three cards it used to be carried a
   blue, an emerald and an amber bar across their tops. Three accent hues for
   three peers is the pattern the home page's method cards already retired: it
   reads as three unrelated products rather than as one offer in three parts.

   No card at all now — a hairline between columns, the gold mark the rest of
   the site uses, and the ordinal set large and pale behind the text so the
   three are visibly one, two, three of a thing rather than three of anything. */
.pillars {
    display: grid;
    gap: 2rem;
}

.pillar {
    position: relative;
    display: flex;
    flex-direction: column;
}

/* The hairline goes on "every pillar but the first" in both directions, rather
   than on all three and then off the first: one idiom, and the desktop block
   below already had to say `+` anyway. */
.pillar + .pillar {
    padding-top: 1.75rem;
    border-top: 1px solid var(--surface-border);
}

@media (min-width: 768px) {
    .pillars {
        grid-template-columns: repeat(3, 1fr);
        gap: 0;
    }

    .pillar:not(:last-child) {
        padding-inline-end: 2.25rem;
    }

    .pillar + .pillar {
        padding-top: 0;
        border-top: 0;
        border-inline-start: 1px solid var(--surface-border);
        padding-inline-start: 2.25rem;
    }
}

/* An ordinal, not a heading: `aria-hidden` in the markup and set behind the
   copy rather than in it. */
.pillar-index {
    position: absolute;
    top: -0.5rem;
    inset-inline-end: 0;
    font-family: var(--font-display);
    font-size: 3.5rem;
    font-weight: 800;
    line-height: 1;
    color: rgba(15, 36, 72, 0.07);
    font-variant-numeric: tabular-nums;
    pointer-events: none;
    transition: color 0.2s ease;
}

.pillar:hover .pillar-index {
    color: rgba(42, 95, 150, 0.16);
}

.pillar-title {
    margin: 1rem 0 0.5rem;
    font-size: 1.1875rem;
    line-height: 1.25;
}

/* Grows, so the three rules underneath sit on one line however long each
   column's copy runs — the same trick `.method-card-body` uses, and for the
   same reason: three feet at three heights is what makes a row of columns look
   like three separate things. */
.pillar-body {
    flex: 1 1 auto;
}

/* The pillar's own rule, under the copy: the one piece of card chrome kept,
   because without it three columns of text have no foot. The stub itself is
   declared beside `.method-card-rule`, which is the same accent under the same
   kind of block; a pillar is wider than a card, so it grows to a length rather
   than to its full width. */
.pillar-rule {
    margin-top: 1.1rem;
}

.pillar:hover .pillar-rule {
    width: min(100%, 7rem);
}

/* ── The sticky split ──
   A heading column that holds still while its list scrolls past it. Used once,
   on "who uses Nibras" — four one-line audiences that were getting a full
   centred section header and a four-across grid of cards for eight words each.

   The stickiness is the point: it is the only place on the page where something
   stays put while you scroll, so it reads as a change of gear rather than as
   another section. Below the breakpoint it is a heading above a list, and
   `position: static` is restored by not applying the rule at all. */
.split {
    display: grid;
    gap: 1.5rem;
}

@media (min-width: 900px) {
    .split {
        grid-template-columns: 19rem 1fr;
        gap: 3.5rem;
        align-items: start;
    }

    .split-aside {
        position: sticky;
        /* Clears the sticky header, whose measured height base.html publishes.
           The fallback matches the `scroll-mt-24` every anchor target on this
           page carries, so the two ways of clearing the same header agree on
           how tall it is for the one frame before the observer reports. */
        top: calc(var(--site-header-height, 6rem) + 2rem);
    }
}

/* ── The hairline row ──
   A list that is a list: rules between items, nothing around them. The nudge on
   hover is the whole affordance — these rows go nowhere, so they get movement
   instead of a border that would promise a link. */
.rail {
    list-style: none;
    margin: 0;
    padding: 0;
}

.rail-row {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.15rem 0;
    padding-inline-start: 0.6rem;
    border-top: 1px solid var(--surface-border);
}

.rail-row:last-child {
    border-bottom: 1px solid var(--surface-border);
}

/* The nudge moves the row's contents, not the row's padding. Animating
   `padding-inline-start` re-laid-out the row twelve times per hover, and
   because it narrows the content box it could push `.rail-note` onto another
   line — which changes the row's height, shifts every row under it, and makes
   the sticky heading beside them recompute. A transform on the children is
   composited, and the content box never changes width, so neither can happen.
   The rule stays on the row's border box either way, so the drawing is the
   same. */
.rail-row > * {
    transform: translateX(-0.6rem);
    transition: transform 0.2s ease;
}

[dir="rtl"] .rail-row > * {
    transform: translateX(0.6rem);
}

.rail-row:hover > * {
    transform: none;
}

.rail-title {
    font-size: 1rem;
}

.rail-note {
    margin-top: 0.15rem;
    font-size: 0.85rem;
    line-height: 1.55;
}

/* ── The lit loop ──
   "Parent, student, teacher, administration — one loop, no gaps" was the
   section's closing line, illustrated by four translucent boxes in a row with a
   decorative hairline floating behind them. The claim is a loop; this draws it.

   Four discs joined by connectors that live in the gaps rather than a single
   rail running behind the discs — a 2px line crossing a circle is the detail
   that makes a diagram look unfinished, and a line stopped at each disc is the
   same drawing without it. On a phone the same connectors run vertically, which
   turns the row into a timeline rather than squeezing four columns into two. */
.loop-band {
    border-radius: 1.5rem;
    padding: 2.25rem 1.5rem;
    text-align: center;
}

@media (min-width: 768px) {
    .loop-band {
        padding: 3rem 2.5rem 2.75rem;
    }
}

.loop {
    --loop-gap: 1.5rem;
    display: grid;
    gap: var(--loop-gap);
    margin-top: 2.25rem;
}

/* Start-aligned only while the nodes are stacked; in a row they inherit the
   band's own centring, so the desktop side says nothing about alignment. */
@media (max-width: 767.98px) {
    .loop { text-align: start; }
}

@media (min-width: 768px) {
    .loop {
        --loop-gap: 1rem;
        grid-template-columns: repeat(4, 1fr);
        margin-top: 2.75rem;
    }
}

.loop-node {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 1rem;
}

@media (min-width: 768px) {
    .loop-node {
        flex-direction: column;
        align-items: center;
        gap: 0.85rem;
    }
}

/* The connector, in the gap between this node and the next.

   Each orientation states itself rather than cancelling the other: the vertical
   form is scoped to the phone breakpoint, so the desktop rule sets the five
   things it needs instead of setting nine and unsetting four. `767.98px` is the
   same stop `.method-art-scroll` uses, and is `768px - 0.02` so the two never
   both apply. */
.loop-node::after {
    content: '';
    position: absolute;
}

@media (max-width: 767.98px) {
    .loop-node::after {
        inset-inline-start: 1.75rem;   /* the disc's centre */
        margin-inline-start: -1px;
        top: 3.9rem;
        bottom: calc(0.4rem - var(--loop-gap));
        width: 2px;
        background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.4));
    }
}

@media (min-width: 768px) {
    .loop-node::after {
        top: 1.7rem;
        height: 2px;
        /* Percentages resolve against the node, so `100%` is one column: the
           line starts 2rem past this disc's centre and runs a column plus the
           gap, less the 2rem clearance at each end — landing 2rem short of the
           next disc whatever the column turns out to be. */
        inset-inline-start: calc(50% + 2rem);
        width: calc(100% + var(--loop-gap) - 4rem);
        background: linear-gradient(90deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.08));
    }
}

.loop-node:last-child::after {
    display: none;
}

.loop-disc {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.28);
    background: linear-gradient(145deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.07));
    transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.loop-disc svg {
    width: 1.6rem;
    height: 1.6rem;
}

/* The gold itself is on the shared lit-mark rule beside `.card-mark`; only the
   lift is local to the loop. */
.loop-node:hover .loop-disc {
    transform: translateY(-2px);
}

.loop-role {
    font-size: 0.8125rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.loop-note {
    margin-top: 0.3rem;
    font-size: 0.8125rem;
    line-height: 1.5;
    opacity: 0.85;
}

.loop-claim {
    margin-top: 2rem;
    font-size: 0.9375rem;
}

@media (min-width: 768px) {
    .loop-claim {
        font-size: 1rem;
    }
}

/* The page's one highlighter stroke, on the sentence the whole band is for.
   Gold rather than the 25%-amber wash it was: over this blue that read as grey,
   which is a highlight that highlights nothing. */
.loop-claim strong {
    border-radius: 0.3rem;
    padding: 0.2rem 0.5rem;
    background: rgba(240, 212, 154, 0.2);
    box-shadow: inset 0 0 0 1px rgba(240, 212, 154, 0.35);
    color: #f5dfb0;
    font-weight: 800;
}

/* ── The ledger ──
   Six modules that were six cards. One slab now, divided by hairlines: the
   cells are the page's only white against the tinted band under them, so the
   grid reads as a single object — which is what "run your whole program from
   one place" is claiming, and six separate boxes were quietly arguing against.

   The hairlines are the grid's own `gap` showing the container's background
   through, so there is one rule between any two cells rather than two borders
   meeting. */
.ledger-band {
    padding-block: 2.5rem 2.25rem;
    background:
        radial-gradient(120% 100% at 50% 0%, rgba(86, 184, 233, 0.16), transparent 62%),
        linear-gradient(180deg, rgba(246, 217, 87, 0.09), rgba(255, 255, 255, 0));
    border-block: 1px solid rgba(148, 163, 184, 0.22);
}

.ledger {
    display: grid;
    gap: 1px;
    background: var(--surface-border);
    border: 1px solid var(--surface-border);
    border-radius: 1.25rem;
    overflow: hidden;
    box-shadow: 0 14px 34px rgba(15, 36, 72, 0.08);
}

@media (min-width: 640px) {
    .ledger {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1024px) {
    .ledger {
        grid-template-columns: repeat(3, 1fr);
    }
}

.ledger-cell {
    display: flex;
    gap: 0.9rem;
    padding: 1.3rem 1.2rem;
    background: #fff;
    transition: background 0.18s ease;
}

.ledger-cell:hover {
    background: #f7fbff;
}

.ledger-title {
    font-size: 1rem;
    margin-bottom: 0.2rem;
}

.ledger-body {
    font-size: 0.875rem;
    line-height: 1.6;
}

/* What the four pills under the modules became: a dotted line of facts. Four
   bordered capsules were four more rectangles under a grid that had just
   stopped being rectangles. */
.facts {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem 1.75rem;
    margin-top: 1.5rem;
    font-size: 0.8125rem;
    font-weight: 700;
    color: #475569;
}

.facts span + span {
    position: relative;
}

.facts span + span::before {
    content: '';
    position: absolute;
    inset-inline-start: -0.95rem;
    top: 0.6em;
    width: 4px;
    height: 4px;
    border-radius: 999px;
    background: #94a3b8;
}

/* ── The flow (what the data gives back) ──
   In on one side, out on the other, and a mark between them pointing the way:
   the section is about a transformation and was drawn as a card with two more
   cards inside it.

   The two outcomes keep their emerald and rose — that pair is carrying meaning,
   not decoration — but as a bar at the edge rather than a tinted box, which is
   the same signal without another rectangle. */
.flow {
    display: grid;
    gap: 1.5rem;
    align-items: center;
}

@media (min-width: 900px) {
    .flow {
        grid-template-columns: 1fr auto 1fr;
        gap: 2.5rem;
    }
}

.flow-label {
    margin-bottom: 0.75rem;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: #94a3b8;
}

.flow-note {
    margin-top: 1rem;
}

.flow-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.flow-chip {
    border-radius: 999px;
    padding: 0.4rem 0.85rem;
    background: rgba(42, 95, 150, 0.07);
    font-size: 0.8125rem;
    font-weight: 700;
    color: #334155;
}

/* The turn. Down the page on a phone, across it on a desktop — and back the
   other way on an RTL desktop, where "across" is the other direction.

   The disc itself is `.card-mark .card-mark--round`; all that is left here is
   where it sits and which way its arrow points. */
.flow-seam {
    justify-self: center;
}

.flow-seam svg {
    width: 1.25rem;
    height: 1.25rem;
    transform: rotate(90deg);
}

@media (min-width: 900px) {
    .flow-seam svg {
        transform: none;
    }

    [dir="rtl"] .flow-seam svg {
        transform: rotate(180deg);
    }
}

.flag {
    border-inline-start: 3px solid;
    padding-inline-start: 1rem;
}

.flag + .flag {
    margin-top: 1.5rem;
}

.flag-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9375rem;
    font-weight: 800;
}

.flag-title svg {
    width: 1.15rem;
    height: 1.15rem;
    flex: none;
}

.flag-note {
    margin-top: 0.3rem;
    font-size: 0.8125rem;
    line-height: 1.55;
}

.flag--good {
    border-color: #10b981;
}

.flag--good .flag-title {
    color: #047857;
}

.flag--risk {
    border-color: #f43f5e;
}

.flag--risk .flag-title {
    color: #be123c;
}

/* ── The wide point ──
   Two longer claims that closed the insights section as two more cards. A rule
   over the top and nothing else: at this length the copy is the object. */
.points {
    display: grid;
    gap: 1.75rem;
    margin-top: 2.75rem;
}

@media (min-width: 768px) {
    .points {
        grid-template-columns: repeat(2, 1fr);
        gap: 2.5rem;
    }
}

.point {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding-top: 1.25rem;
    border-top: 2px solid rgba(42, 95, 150, 0.18);
}

.point-title {
    font-size: 1.0625rem;
    margin-bottom: 0.35rem;
}

/* ── The app rail ──
   The two store links were two cards in a centred two-column grid under a
   centred three-line header — a whole screen for two buttons. Heading on one
   side, buttons on the other, one row. */
.app-rail {
    display: grid;
    gap: 1.5rem;
    align-items: center;
}

@media (min-width: 900px) {
    .app-rail {
        grid-template-columns: 1fr auto;
        gap: 3rem;
    }
}

.app-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

.app-link {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.7rem 1.15rem;
    border-radius: 0.9rem;
    background: linear-gradient(145deg, #1d3a63, #12264a);
    color: #fff;
    text-decoration: none;
    transition: transform 0.18s ease, box-shadow 0.18s ease;
}

.app-link:hover {
    transform: translateY(-2px);
    box-shadow: 0 14px 28px rgba(15, 36, 72, 0.25);
}

.app-link:focus-visible {
    outline: 2px solid var(--brand-2);
    outline-offset: 3px;
}

.app-link svg {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    color: #e3bd76;
}

.app-link-sub {
    display: block;
    font-size: 0.625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    opacity: 0.7;
}

.app-link-name {
    display: block;
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 800;
    line-height: 1.2;
}

/* ── Platform numbers ──
   One shape with a modifier rather than two. `_platform_stats.html` is shared
   by the home page and /for-schools/, and the only difference between them is
   whether the three figures are inside a card: home keeps the card it has
   always had, and by the time this band appears on the pitch page every other
   section has stopped being a rounded rectangle, so one left over would look
   like something nobody got round to.

   The bare form is therefore the base and `--framed` adds the chrome back,
   because the chrome is the exception. */
/* ── Platform numbers ──
   One shape, parameterised. `_platform_stats.html` is shared by the home page
   and /for-schools/, and the only difference between them is the chrome and the
   size of the figures: home sets the three numbers inside the site's card,
   among other panels, and by the time the band appears on the pitch page every
   other section has stopped being a rounded rectangle, so one left over would
   look like something nobody got round to.

   Written as custom properties rather than as a modifier that undoes the base,
   which is this file's own idiom (`--lift-border` / `--lift-shadow`, re-set by
   each card family). A `--bare` that had to reset the typeface and swap a fixed
   size back for a `clamp()` would be saying the two forms do not share that
   typography — and then neither of them should be the other's exception.

   The card is `.surface-card` in the markup, not copied here. It is the site's
   one card treatment and it takes a smaller radius below 1024px; a second copy
   would quietly stop following it. */
.stats {
    --stat-face: var(--font-body);
    --stat-size: 1.875rem;
    --stat-size-md: 2.25rem;
    --stat-pad: 1rem;
    --stat-rule: #f1f5f9;          /* what `divide-slate-100` drew */
    --stat-ink: #1e293b;
}

.stats--bare {
    --stat-face: var(--font-display);
    --stat-size: clamp(2.25rem, 1.4rem + 3vw, 3.25rem);
    --stat-size-md: var(--stat-size);
    --stat-pad: 1.4rem;
    --stat-rule: var(--surface-border);
    --stat-ink: #0f2448;
}

.stats-eyebrow {
    margin-bottom: 0.25rem;
    text-align: center;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.2em;
    color: #0f766e;
}

.stats-title {
    margin-bottom: 1.5rem;
    text-align: center;
    font-size: 1.25rem;
    font-weight: 700;
    color: #1e293b;
}

@media (min-width: 768px) {
    .stats-title { font-size: 1.5rem; }
}

.stats-row {
    display: grid;
}

@media (min-width: 640px) {
    .stats-row {
        grid-template-columns: repeat(3, 1fr);
    }
}

.stat {
    text-align: center;
    padding: var(--stat-pad) 1rem;
}

.stat + .stat {
    border-top: 1px solid var(--stat-rule);
}

@media (min-width: 640px) {
    .stat + .stat {
        border-top: 0;
        border-inline-start: 1px solid var(--stat-rule);
    }
}

.stat-figure {
    font-family: var(--stat-face);
    font-size: var(--stat-size);
    font-weight: 800;
    line-height: 1;
    color: var(--stat-ink);
}

@media (min-width: 768px) {
    .stat-figure { font-size: var(--stat-size-md); }
}

.stat-plus { color: #0d9488; }

.stat-label {
    margin-top: 0.25rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: #334155;
}

.stat-note {
    font-size: 0.75rem;
    color: #64748b;
}

/* ── The closing band ──
   The same night ground the page opens on, which is the point — a reader who
   has scrolled the whole thing arrives back where they started — but this one
   is the ask, so it gets the lamp's glow behind it and more room.

   The glow is `::before`, not `::after`, and the difference is the whole
   mechanism. `.night-band > *:not(.sky)` gives every real child `position:
   relative`, so the copy is painted in the positioned pass; a pseudo-element is
   not matched by `*`, but it is still absolutely positioned, so it lands in
   that same pass and is ordered against the copy by tree order alone. As
   `::after` it came last and washed gold over the headline. As `::before` it
   comes first and is what it is supposed to be: a light behind them. */
.cta-band {
    border-radius: 1.5rem;
    padding: 2.75rem 1.5rem;
    text-align: center;
}

@media (min-width: 768px) {
    .cta-band {
        padding: 4rem 3rem;
    }
}

.cta-band::before {
    content: '';
    position: absolute;
    inset-inline: 20%;
    top: -35%;
    height: 90%;
    border-radius: 50%;
    background: radial-gradient(ellipse at center, rgba(250, 215, 130, 0.28), rgba(250, 215, 130, 0) 68%);
    pointer-events: none;
}

/* ── Reduced motion ──
   Every hover on this page is a colour change plus a small movement. The colour
   stays; the movement and the easing go. */
@media (prefers-reduced-motion: reduce) {
    .pillar-index,
    .rail-row > *,
    .loop-disc,
    .ledger-cell,
    .app-link {
        transition: none;
    }

    /* `.rail-row > *` is offset at rest and slides back on hover, so the motion
       goes by settling it where the hover would have put it. The `:is()` is
       there to outrank `[dir="rtl"] .rail-row > *`, which sets the mirrored
       offset and would otherwise win this on specificity. */
    :is(.rail-row, [dir="rtl"] .rail-row) > *,
    .loop-node:hover .loop-disc,
    .app-link:hover {
        transform: none;
    }
}

/* ── Scroll-linked layer ──
   Two things that are driven by the scroll itself rather than by a one-shot
   observer: the pillars' ordinals drift as their column comes up, and the
   loop's connectors draw themselves between the discs.

   Behind `@supports`, and behind `no-preference`, so this is strictly an
   addition for the browsers that have view timelines. Everywhere else the
   ordinals sit still and the connectors are simply already drawn — which is
   what they look like at the end of the animation anyway, so nothing is missing
   and nothing has to be un-hidden. Lenis moves the real scroll position rather
   than transforming a wrapper, so these track it frame for frame. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .pillar-index {
            animation: pillar-drift linear both;
            animation-timeline: view();
            animation-range: entry 10% cover 45%;
        }

        .loop-node::after {
            animation: loop-draw linear both;
            animation-timeline: view();
            animation-range: entry 25% cover 40%;
            transform-origin: top center;
        }

        @media (min-width: 768px) {
            .loop-node::after {
                transform-origin: left center;
            }

            [dir="rtl"] .loop-node::after {
                transform-origin: right center;
            }
        }
    }
}

@keyframes pillar-drift {
    from { transform: translateY(0.9rem); opacity: 0; }
    to   { transform: translateY(0); opacity: 1; }
}

/* Uniform `scale`, so one set of keyframes draws the connector whichever way it
   runs — the transform-origin above is what decides which end it grows from.
   It was a `scaleY` pair with a `scaleX` pair redefining the same name inside a
   media query, which works (the last matching `@keyframes` wins) and is a trap:
   nothing at the first definition says it is conditional. The cross-axis
   shrinks too, but the cross-axis is 2px. */
@keyframes loop-draw {
    from { transform: scale(0); }
    to   { transform: scale(1); }
}
