: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 (blue, matches header) ── */
.mobile-nav {
    background: linear-gradient(150deg, var(--brand-1), var(--brand-2));
}

@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;
    }
    /* Same outline, inverted: the sheet behind it is the navy gradient. Full
       width and square-ish here, because every row in the sheet is. */
    .mobile-nav .nav-login {
        /* Full width because every other row in the sheet is, and this stopped
           stretching on its own when it lost `.nav-pill`. */
        width: 100%;
        justify-content: center;
        background: none;
        color: #fff;
        border-color: rgba(255, 255, 255, 0.45);
        border-radius: 0.5rem;
        padding: 0.55rem 0.9rem;
        font-size: 0.85rem;
        box-shadow: none;
    }
    .mobile-nav .nav-login:hover {
        background: rgba(255, 255, 255, 0.15);
        border-color: rgba(255, 255, 255, 0.7);
        color: #fff;
    }
    .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;
    }
}

.hero-card {
    background: linear-gradient(125deg, var(--brand-ink), var(--brand-1) 38%, var(--brand-2) 70%, var(--accent) 120%);
    box-shadow: 0 18px 38px rgba(29, 77, 127, 0.25);
}

/* 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);
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}

.method-card:hover,
.method-card:focus-within {
    transform: translateY(-3px);
    border-color: rgba(42, 95, 150, 0.35);
    box-shadow: 0 16px 32px rgba(15, 36, 72, 0.1);
}

.method-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    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. */
.method-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(140deg, rgba(42, 95, 150, 0.1), rgba(22, 136, 212, 0.16));
    color: var(--brand-1);
    transition: background 0.18s ease, color 0.18s ease;
}

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

.method-card:hover .method-card-mark,
.method-card:focus-within .method-card-mark {
    background: linear-gradient(140deg, var(--brand-ink), var(--brand-1));
    color: #fff;
}

/* 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. */
.method-card-index {
    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;
}

.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;
    margin-bottom: 0.5rem;
}

.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. */
.method-card-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,
    .method-card-mark,
    .method-card-index,
    .method-card-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 {
        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. */
.hero-banner {
    position: relative;
    isolation: isolate;
    text-align: center;
    padding: 3rem 1rem 3.25rem;
}

.hero-banner::before {
    content: "";
    position: absolute;
    z-index: -1;
    /* Bleeds past the section into the container's own gutter, so the wash
       reads as page background rather than as a block sitting on the page. */
    inset: -1rem -1rem;
    background: url("../imgs/kevinamrulloh-book-2477164.a192f3387432.jpg") center / cover no-repeat;
    opacity: 0.14;
    filter: blur(1.6px);
    /* Radii stay under 50% so the fade actually reaches transparent inside the
       box; anything wider and the photo ends on a visible rectangle edge. */
    -webkit-mask-image: radial-gradient(ellipse 48% 46% at 50% 45%, #000 10%, rgba(0, 0, 0, 0.45) 58%, transparent 100%);
    mask-image: radial-gradient(ellipse 48% 46% at 50% 45%, #000 10%, rgba(0, 0, 0, 0.45) 58%, transparent 100%);
}

@media (min-width: 768px) {
    .hero-banner {
        padding: 4.5rem 2rem 4.75rem;
    }
}

/* Sits between the promise line and the buttons, so it has to read as one beat
   in that sequence rather than as a third thing competing with the headline —
   which is why it lands well under the h1's size rather than near it. */
.hero-mark {
    display: block;
    width: clamp(96px, 16vw, 148px);
    height: auto;
    margin: 0 auto 1.5rem;
    object-fit: contain;
    filter: drop-shadow(0 3px 10px rgba(15, 36, 72, 0.14));
}

.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));
}

.testimonial-track {
    position: relative;
    overflow: hidden;
}

.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;
}

.testimonial-card {
    width: min(310px, 80vw);
    background: #ffffff;
    border: 1px solid #dbeafe;
    border-radius: 0.95rem;
    box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
    padding: 1rem;
}

.testimonial-quote {
    color: #1e293b;
    font-weight: 700;
    line-height: 1.5;
}

.testimonial-author {
    margin-top: 0.6rem;
    color: #0369a1;
    font-size: 0.9rem;
    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;
    }

    .hero-card {
        border-radius: 1.1rem;
    }

    .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; }

/* 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 */
}
