/* Shared page CSS for every Jejak.ai page.
   Extracted from the <style> block that used to sit inline in
   jejak_ai_landing_page.html so that the landing page and the principles page
   cannot drift apart.

   This sheet MUST be linked BEFORE tailwind.css. Utilities and the custom
   classes here collide at equal specificity (.pb-0 vs .section), so the later
   sheet wins. Loading tailwind.css first silently flips that and leaves
   .section's padding-block overriding .pb-0. */

html { -webkit-font-smoothing: antialiased; }

/* Raw CSS takes a single level of quoting. The nested form
   '"Plus Jakarta Sans"' is correct ONLY inside tailwind.config.js, where
   Tailwind emits the quotes itself. In plain CSS it names a family containing
   literal quote characters, which silently falls back to system-ui. */
/* The page ground (ADR-059, 23 August 2026). A soft diagonal wash, lightest
   at the top-left corner where a left-to-right reader starts, falling through
   paper at 44 percent and settling a shade warmer at the bottom.

   44 rather than halfway is deliberate upstream: it keeps the lit corner a
   corner instead of letting it become the top half of the screen.

   `fixed` so the wash belongs to the viewport rather than to a 13,000px
   document. Attached to the document it would compress the whole gradient into
   the first screen and leave everything below it flat.

   This replaces the flat paper fill AND the paper/sand section banding. The
   alternating bands existed to separate sections on a flat ground; the ground
   is not flat any more, and two systems of separation on one page is what the
   revamp upstream was undoing. Sections are transparent now; separation comes
   from the cards that sit on them. */
body {
    background-color: #fbf9f4; /* groundMid, and the fallback if the gradient fails */
    background-image: linear-gradient(163deg, #e7f1e3 0%, #fbf9f4 44%, #f7f3e9 100%);
    background-attachment: fixed;
    color: #191c16;            /* ink */
    font-family: "Plus Jakarta Sans", system-ui, sans-serif;
}

/* Layout primitives */
.container-page { max-width: 1536px; margin-inline: auto; padding-inline: 1.5rem; }
@media (min-width: 1024px) { .container-page { padding-inline: 2.5rem; } }

/* At 390px the hero chart runs straight through the body copy, because
   the content column fills most of the canvas. A paper scrim veils the
   lower half so the text stays legible while the chart keeps its full
   strength in the space above. Not needed once the column narrows. */
.hero-scrim {
    height: 70%;
    background: linear-gradient(to top, #fbf9f4 62%, rgba(251, 249, 244, 0) 100%);
}

/* Citation markers render as narrow as 7px, which is fiddly to tap. A
   pseudo-element hit area was tried first and proved unreliable, since
   it is clipped by the cards that carry overflow-hidden. This grows the
   anchor's own box instead, which nothing clips. Inline links inside a
   sentence are exempt from WCAG 2.2 target size, so this is a usability
   improvement rather than a conformance fix. */
.cite {
    display: inline-block;
    min-width: 24px;
    padding-inline: 0.35rem;
    text-align: center;
}

/* Mobile first. 8rem top and bottom on a 390px screen spends a third of
   a viewport on padding alone, and the page is already long. */
/* 12rem above 1024 meant 192px top AND bottom, so 384px of nothing between
   every pair of sections. Measured 19 August 2026: gaps larger than 150px
   accounted for about 5,400px of a 13,472px page, and seven of them were
   400-503px, more than half a screen each. Every one of those seven was this
   rule. Dropping the large screens to the same 8rem removes roughly 1,000px
   without touching a word, which is why there is no longer a 1024 breakpoint
   here: it would set the value it already has. */
.section { padding-block: 4.5rem; }
@media (min-width: 640px) { .section { padding-block: 8rem; } }

/* The <picture> wrappers exist only to offer a WebP source. Without
   display:contents the wrapper, not the <img>, becomes the flex/grid
   item and the sizing classes on the image stop applying. */
picture { display: contents; }

.measure { max-width: 62ch; }

/* The one opaque field every section after the hero sits on. See the comment
   above `<div class="page-body">` in the markup for why the opacity lives here
   rather than on each section, and why `fixed` is not optional. */
.page-body {
    position: relative;
    background-color: #fbf9f4;
    background-image: linear-gradient(163deg, #e7f1e3 0%, #fbf9f4 44%, #f7f3e9 100%);
    background-attachment: fixed;
}

/* A CARD floats on the ground. Two shadow layers, and no hairline: the tight
   layer draws the edge, the wide one gives the card somewhere to sit, and one
   layer cannot do both. Wide enough to lift is too diffuse to have an edge;
   tight enough to have an edge does not lift.

   Matches `MindaElevation.card` after ADR-059, when the card lost its border on
   light and the shadow became the only thing separating it from the ground.

   Hierarchy still comes from tint, weight and spacing. No transform on hover,
   ever, and since 24 August no card wears a coloured line on any edge either:
   not a left rail, not a top rule. What is left for colour-coding a card is
   tint, weight, spacing and a glow, and a glow is weight. */
.card-surface {
    box-shadow: 0 1px 2px rgba(20, 23, 15, 0.04), 0 10px 28px rgba(20, 23, 15, 0.055);
}

/* A TILE is already inside something, so it is separated by a tinted fill and
   a hairline and gets NO shadow. The asymmetry is the design: a shadow on a
   tile reads as a second card, and two levels of floating in one column is the
   everything-is-a-card look this refit is undoing. */
.tile-surface {
    background: #f6f1e7;
    border: 1px solid #e8e2d4;
    border-radius: 14px;
}

/* Display type scale. Weight 800 with heavy negative tracking is the
   brand-compliant equivalent of the reference's low-weight serif.

   THE MAXIMA CAME DOWN ABOUT 20% ON 30 AUGUST 2026, and the vw coefficients
   with them. Product owner, looking at the download close on a wide screen:
   the headings were "disproportionately too big compared to its context
   typography".

   Measured before changing anything, against the 17px body, at 1440px and
   wider where every clamp is pinned to its cap:

       display-close  120px  7.06x body        now  96px  5.65x
       display-1       88px  5.18x             now  72px  4.24x
       display-2       64px  3.76x             now  52px  3.06x
       display-row     32px  1.88x             now  30px  1.76x

   THE MINIMA ARE DELIBERATELY UNTOUCHED. The same measurement at 390px put
   the largest step at 3.29x body, so the phone was already proportionate and
   the cap was the whole defect. Shrinking the minima would have fixed a
   viewport that was not broken.

   Each vw coefficient is scaled by the same 0.8 as its cap (10 to 8, 8 to
   6.5, 5 to 4, 3 to 2.8) so each size still reaches its cap at roughly the
   viewport it did before, rather than flattening out several hundred pixels
   early and leaving the mid-range sizes unchanged.

   404.html carries its own copy of .display-2 and .eyebrow because it must
   render when nothing else on the site is reachable. It is updated in the
   same change; the comment in that file asks for exactly that. */
.display-1 {
    font-size: clamp(3rem, 6.5vw, 4.5rem);
    font-weight: 800;
    letter-spacing: -0.04em;
    line-height: 1.02;
}

.display-2 {
    font-size: clamp(2.25rem, 4vw, 3.25rem);
    font-weight: 800;
    letter-spacing: -0.035em;
    line-height: 1.08;
}

.display-row {
    font-size: clamp(1.75rem, 2.8vw, 1.875rem);
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.15;
}

.display-close {
    font-size: clamp(3.5rem, 8vw, 6rem);
    font-weight: 800;
    letter-spacing: -0.045em;
    line-height: 1;
}

.card-title {
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.2;
}

/* The label role, and it is NOT mono any more (24 August 2026).

   Fira Code is for things a person transcribes or compares character by
   character: an MRN, a claim code, a route, a build string. A mono keeps 0
   apart from O and 1 apart from l, which matters when somebody is reading a
   code off a printed letter and typing it into a field. That is the whole job,
   and a section eyebrow is not it. Setting every eyebrow on the site in a
   terminal face made a claim about the product that the product does not make.

   These are the exact numbers of `MindaType.label`: 11.5px, weight 800,
   +1.2px tracking. The console sets this over every card and every metric, so
   a label here and a label there are now the same thing rather than two things
   that happen to be small and uppercase. */
.eyebrow {
    font-family: "Plus Jakarta Sans", system-ui, sans-serif;
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: 1.2px;
    line-height: 1.2;
    text-transform: uppercase;
}

/* Mono is kept for what mono is for, and it already has the classes it needs:
   `.shot-route` for a route, `.record-chip` and `.ui-chip` for an MRN, and
   Tailwind's `font-mono` for a quantity shown AS DATA (the download size line,
   the cited NHMS figures). A fourth general-purpose mono class was written
   here on 24 August and deleted the same hour: nothing needed it, and an
   unused class in a hand-written sheet is a class the next person applies to
   something that is not a code. */

.body-lg { font-size: 1.25rem; line-height: 1.6; }
.body    { font-size: 1.0625rem; line-height: 1.65; }

/* THE NAV RAIL. One capsule, one treatment, defined here so the four
   hand-written pages cannot drift apart. Rebuilt 30 August 2026; product
   owner: it "looks boring, just standard text becoming text buttons".

   TWO THINGS WERE WRONG AND ONLY ONE OF THEM WAS THE OBVIOUS ONE.

   The links carried no shape at all: six words floating in a row, separated
   from the wordmark and the buttons by nothing but space. The capsule gives
   them one edge to belong to.

   The second is that an ANCHOR and a PAGE looked identical. "Perjalanan" and
   "Produk" scroll the page you are already on; the other four load a new
   document. A hairline now sets the two groups apart, so the nav says which
   kind of thing you are about to do before you do it.

   THE ACTIVE STATE IS KEYED OFF aria-current, NOT A CLASS, so the thing a
   screen reader announces and the thing an eye sees cannot disagree. Both
   values are styled: "page" for the document you are on, which the sub-pages
   already set and the home page cannot, and "location" for the section you
   have scrolled to, which the observer in the page sets and clears.

   The sub-pages DID mark the current page before this, with text-ink against
   text-muted. That is #191c16 against #6b6e64 at 15px and it read as nothing,
   which is why the nav looked stateless when it was not quite.

   NO WEIGHT CHANGE ON THE ACTIVE LINK, and it was in the sketch this was
   built from. Bold is wider than regular, so the scroll-spy would have
   re-flowed the whole rail every time it crossed a section boundary, shoving
   the links sideways while somebody reads them. Reserving the bold width
   needs the label duplicated in an attribute, which the language toggle does
   not update, so it would break on the first switch to English. The white
   fill carries the state on its own and nothing moves. */
.nav-link {
    border-radius: 9999px;
    /* Tight below 1280 and roomier above it, because the ENGLISH labels are
       the binding case and Malay hides it. At 1024, the width the capsule
       first appears at, the six English labels ("JEJI and friends", "User
       guide", "Principles") ran the capsule flush into the language toggle
       with exactly 0px between them. In Malay at the same width there was
       103px, so this was invisible in the language the page ships in. */
    padding: 0.375rem 0.625rem;
    font-size: 14px;
    line-height: 1.4;
    color: #6b6e64;
    white-space: nowrap;
    transition: background-color 160ms ease, color 160ms ease;
}

@media (min-width: 1280px) {
    .nav-link {
        padding: 0.375rem 0.875rem;
        font-size: 15px;
    }
}

.nav-link:hover {
    background-color: rgba(25, 28, 22, 0.045);
    color: #191c16;
}

/* "page" is the document you are on, "location" the section you have reached.
   One look for both: they answer the same question. */
.nav-link[aria-current] {
    background-color: #ffffff;
    color: #191c16;
}

/* Buttons. Full-radius pills.
 
   THIS SAID "no shadow, no lift" UNTIL 30 AUGUST 2026. Product owner, on the
   download pill: make it "cooler than just sad looking green button", with
   icons, and floating. That reverses the rule deliberately, and only for the
   PRIMARY tier: an outline button is still flat, because a secondary control
   that floats is competing with the one action on the page.

   The transition list gains box-shadow and transform. It carried three
   colour properties, so a lift added without this would have snapped rather
   than moved. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border-radius: 9999px;
    padding: 1rem 1.5rem;
    font-weight: 600;
    font-size: 1rem;
    transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease,
                box-shadow 200ms ease, transform 200ms ease;
}

/* The primary tier is the ACCENT, not ink. It was ink until 24 August 2026,
   which made the one committing action on the page the only control that did
   not look like the product: every primary button in the console is an accent
   fill with white on it. Hover goes to `accentStrong`, the same direction the
   old ink button already went. */
.btn-primary {
    background-color: #2e7d32;
    color: #ffffff;
    /* TWO shadows doing two jobs. The tight neutral one seats the pill on the
       ground so it has a contact edge. The wide one carries a little of the
       button's OWN green, so it reads as light passing through a green object
       rather than a grey smudge parked under it.

       The green stays under 0.6 alpha on a -10px spread. Past that it stops
       being a shadow and becomes a halo, which is the coloured-line problem
       this site avoids, wearing a different hat. */
    box-shadow: 0 1px 2px rgba(25, 28, 22, 0.16),
                0 10px 24px -10px rgba(46, 125, 50, 0.55);
}

.btn-primary:hover {
    background-color: #1b5e20;
    box-shadow: 0 2px 4px rgba(25, 28, 22, 0.18),
                0 18px 34px -12px rgba(46, 125, 50, 0.62);
}

/* The lift is MOTION, so it sits behind the same preference the reveal and
   the SVG line-draw already respect. Without this a reduced-motion visitor
   gets the one animation on the page nobody asked for. The shadow still
   deepens on hover for them: that is a state change, not movement.

   :active presses it back down. A button that lifts and never returns feels
   like it did not take the click. */
@media (prefers-reduced-motion: no-preference) {
    .btn-primary:hover { transform: translateY(-2px); }
    .btn-primary:active {
        transform: translateY(0);
        box-shadow: 0 1px 2px rgba(25, 28, 22, 0.2);
        transition-duration: 80ms;
    }
}

/* THE NAV'S DOWNLOAD PILL MATCHES THE LANGUAGE CHIP. Measured on the live
   site: the chip was 44px and the pill 55px, sitting side by side in the same
   rail, which is what the product owner spotted. min-height rather than
   recomputed padding, so the two cannot drift apart again when the type
   inside either one changes.

   AN EXPLICIT CLASS, not `#site-nav .btn`, which is what this was first and
   was wrong: the mobile panel lives inside #site-nav too, so the rule reached
   the full-width download button in the dropdown and cut it from 55px to 44px.
   That button is a primary action in a list, not a chip in a rail, and it has
   no language pill to line up with. Found by measuring the panel at 390px.

   The hero button (56px) and the two platform buttons (64px) are correctly
   larger and must not follow this down either. */
.nav-cta {
    min-height: 44px;
    padding-top: 0;
    padding-bottom: 0;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
}

/* The ghost tier: a real alternative to the primary and a peer of it. An
   outline and equal height, which is what makes it a peer rather than a way
   out of the block. */
.btn-outline { border: 1px solid #d3cbb8; color: #191c16; }
.btn-outline:hover { border-color: #2e7d32; color: #2e7d32; }

.btn-lg { padding: 1.25rem 4rem; }

/* Focus. Never removed. */
a:focus-visible, button:focus-visible {
    outline: 2px solid #2e7d32;
    outline-offset: 2px;
    border-radius: 4px;
}

/* Scroll reveal. Final state is the default so that a JS failure or
   reduced-motion preference leaves the page fully readable. */
.reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: no-preference) {
    .js-motion .reveal {
        opacity: 0;
        transform: translateY(12px);
        transition: opacity 700ms cubic-bezier(0.5, 0, 0, 1),
                    transform 700ms cubic-bezier(0.5, 0, 0, 1);
    }
    .js-motion .reveal.is-visible { opacity: 1; transform: none; }
}

/* Feature row SVG line-draw. Final state is drawn, so a JS failure or
   reduced motion leaves every diagram fully visible. */
.svg-draw { stroke-dasharray: none; stroke-dashoffset: 0; }

@media (prefers-reduced-motion: no-preference) {
    .js-motion .svg-draw {
        stroke-dasharray: var(--len, 400);
        stroke-dashoffset: var(--len, 400);
        transition: stroke-dashoffset 1400ms cubic-bezier(0.5, 0, 0, 1);
    }
    .js-motion .svg-draw.is-drawn { stroke-dashoffset: 0; }

    .js-motion .svg-pop { opacity: 0; transition: opacity 500ms ease; }
    .js-motion .svg-pop.is-drawn { opacity: 1; }
}

/* Hero record card. The hero used to be a headline over an empty grid, and
   the product only appeared 3000px down the page. This puts one real record
   beside the headline: a code-based id, a falling score, and the note that
   the cloud never learns the child's name. The claim is the product's
   headline claim, so showing it is worth more than saying it.

   Everything in the card is synthetic and says so, per the air-gap rule. */
.hero-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 2.5rem;
    width: 100%;
    align-items: center;
}
@media (min-width: 1024px) {
    .hero-grid { grid-template-columns: 1.05fr 0.95fr; gap: 4rem; }
}

/* Card level: radius 20, white, no hairline, the two-layer resting shadow. */
.record-card {
    background: #ffffff;
    border-radius: 20px;
    padding: 1.5rem;
    box-shadow: 0 1px 2px rgba(20, 23, 15, 0.04), 0 10px 28px rgba(20, 23, 15, 0.055);
}

/* The app's PseudonymChip, reproduced: lock glyph plus a mono id on an
   accent-soft ground. It is a design signature and the product's thesis. */
/* `PseudonymChip`, reproduced exactly. It is a 9px chip in the app, not a
   pill: a pill is the button shape, and the chip is a chip. Mono is correct
   here and is the case the face exists for. */
.record-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    border-radius: 9px;
    padding: 0.35rem 0.75rem;
    background: #e9f3e9;
    color: #1b5e20;
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 0.75rem;
}

.record-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding-block: 0.7rem;
    border-top: 1px solid #f0ebdf;
}
.record-row > :first-child { font-size: 0.9375rem; color: #4d5147; }
.record-row > :last-child {
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 0.9375rem;
    color: #191c16;
    white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   "Di dalam aplikasi": the screenshot walk.

   A visitor cannot get into this app, and should not be able to: it holds
   child mental-health records. So the only way they learn what it is, is to be
   shown. This section is that showing.

   The shape is a sticky stage beside scrolling captions. One frame stays put
   while the reader moves through four steps, and the frame swaps under them.
   That is worth the complexity here for a reason that does not apply anywhere
   else on the page: the four screens are the SAME product seen four ways, and
   a stacked gallery makes them read as four unrelated pictures.

   Motion follows the page's existing contract exactly (see .reveal above):
   the final state is the DEFAULT, `.js-motion` opts into the animated start,
   and an IntersectionObserver flips the class. So with JavaScript off, with
   reduced motion, or if the observer never fires, every frame and every
   caption is present and readable, just not animated.
   --------------------------------------------------------------------------- */

.shots { display: grid; gap: 3rem; }

/* The rail: the brand's trajectory line, stood on its end. Four nodes, and the
   line between them fills as the reader descends. */
.shots-rail { display: none; }
.shots-rail-line { position: absolute; left: 7px; top: 8px; bottom: 8px; width: 2px;
                   background: rgba(25, 28, 22, 0.10); border-radius: 2px; }
.shots-rail-fill { position: absolute; left: 0; top: 0; width: 100%; height: 0;
                   background: #2e7d32; border-radius: 2px; }
.shots-node { position: relative; width: 16px; height: 16px; border-radius: 50%;
              background: #fbf9f4; border: 2px solid rgba(25, 28, 22, 0.15); }
.shots-node.is-active { background: #2e7d32; border-color: #2e7d32; }

/* The frame. Deliberately NOT a mac window with three dots: that says
   "screenshot of some app". A hairline frame with the route in Fira Code says
   which screen of THIS product, using the same mono-for-identifiers rule the
   console itself follows. */
.shot-frame { position: relative; border: 1px solid rgba(25, 28, 22, 0.12);
              border-radius: 20px; background: #fbf9f4; overflow: hidden; }
.shot-chrome { display: flex; align-items: center; gap: 0.5rem;
               padding: 0.6rem 0.9rem; border-bottom: 1px solid rgba(25, 28, 22, 0.08);
               background: #f6f1e7; }
.shot-route { font-family: "Fira Code", ui-monospace, monospace; font-size: 0.75rem;
              color: #6b6e64; letter-spacing: 0.02em; white-space: nowrap; }
.shot-dot { width: 7px; height: 7px; border-radius: 50%; background: #2e7d32; flex: none; }

/* The plate holds REBUILT INTERFACE, not a screenshot (24 August 2026).

   The three captures it used to hold were taken on 20 August against 0.1.16.
   Four days later they showed a product that did not exist: the retired agent
   masthead, the interior grid, a "Summary before assessment" box that had been
   removed, the old double top bar, and v0.1.14 in the footer. A screenshot
   goes stale on its own and bypasses every check on this site, because it is
   an image.

   What replaced them is not a replica of a whole screen, which would go stale
   the same way and more quietly. It is the ELEMENTS: a waiting-list row, a
   trajectory, a generated card with its evidence. Those are token-governed,
   they are what a visitor actually wants to look at, and they are a
   demonstration rather than a claim about what a screen looks like today.

   The interface below is in English because the console is in English: the
   clinicians using it read English clinical notes. The prose around it stays
   BM-first. */
.shot-plate { position: relative; display: block; padding: 1.25rem;
              background: linear-gradient(163deg, #e7f1e3 0%, #fbf9f4 44%, #f7f3e9 100%); }
@media (min-width: 640px) { .shot-plate { padding: 1.75rem; } }

/* --- the console's own surfaces, at landing-page scale ----------------- */

/* A card floats: radius 20, white, no hairline, two shadow layers. */
.ui-card { background: #ffffff; border-radius: 20px; padding: 1rem 1.125rem;
           box-shadow: 0 1px 2px rgba(20, 23, 15, 0.04), 0 10px 28px rgba(20, 23, 15, 0.055); }
/* Any card following any card. This was `.ui-card + .ui-card` and so covered
   only one of the three pairs that occur: a plain card followed by an AGENT
   card or by an ENGINE card got no gap at all and the two surfaces touched.
   Both of those pairs were already on the page before the photographs mock
   made it obvious, so this fixes the existing mocks as well as the new one. */
.ui-card + .ui-card,
.ui-card + .ui-card-agent,
.ui-card + .ui-card-engine,
.ui-card-agent + .ui-card,
.ui-card-agent + .ui-card-agent,
.ui-card-agent + .ui-card-engine,
.ui-card-engine + .ui-card,
.ui-card-engine + .ui-card-agent,
.ui-card-engine + .ui-card-engine { margin-top: 0.875rem; }

/* An ENGINE card: deterministic code counted, derived or ranked, with no model
   call anywhere in it. A faint accent glow and a hairline. It differs from the
   agent surface in KIND, not in volume: an engine counted, a model wrote, and
   the two must not read as more and less of one thing. */
.ui-card-engine { background: #ffffff; border: 1px solid #e8e2d4; border-radius: 20px;
                  padding: 1rem 1.125rem; box-shadow: 0 5px 20px rgba(46, 125, 50, 0.09); }

/* An AGENT card: a model wrote the words inside it.

   The aurora. Three blurred blooms over a diagonal wash, veiled in the middle
   so the text sits on an even ground. It replaced a green masthead band and an
   accent grid painted across the card interior, and the grid is the reason:
   a hard one-pixel line under a paragraph is a step change in the ground
   beneath the letters, and no cell size removes that. A gradient varies too,
   but it varies SMOOTHLY, and a reader separates letters from a smoothly
   changing ground without effort.

   Two numbers govern it upstream and are not taste. No colour in it falls
   below 0.69 relative luminance on light, which puts worst-case contrast
   against ink at 12.1:1. And the luminance range under a text block stays
   within 0.06, because the first version passed the contrast rule at 10.1:1
   and still drowned the text: a contrast ratio describes one pixel against
   another and says nothing about a ground that keeps changing under a line.

   These hexes are local constants upstream too, deliberately, so they are
   quoted here rather than tokenised. */
.ui-card-agent {
    position: relative; border: 1px solid rgba(46, 125, 50, 0.5); border-radius: 20px;
    padding: 1rem 1.125rem; overflow: hidden;
    box-shadow: 0 4px 18px rgba(46, 125, 50, 0.10);
    background:
        radial-gradient(120% 90% at 50% 64%, rgba(255,255,255,0.72) 0%, rgba(255,255,255,0.32) 46%, rgba(255,255,255,0) 80%),
        radial-gradient(58% 74% at 2% 2%,    #b6e8a8 0%, rgba(182,232,168,0) 68%),
        radial-gradient(56% 70% at 99% 10%,  #ace7dd 0%, rgba(172,231,221,0) 68%),
        radial-gradient(62% 66% at 62% 112%, #e4f2b4 0%, rgba(228,242,180,0) 68%),
        linear-gradient(155deg, #edf8e9 0%, #f7fbf4 44%, #fdfbf6 78%, #fbf9f4 100%);
}

/* A tile sits INSIDE a card: tinted fill, a hairline, radius 14, no shadow. */
.ui-tile { background: #f6f1e7; border: 1px solid #e8e2d4; border-radius: 14px;
           padding: 0.75rem 0.875rem; }

.ui-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
.ui-title { font-size: 15px; font-weight: 800; letter-spacing: -0.2px; color: #191c16; }
.ui-sub { font-family: "Fira Code", ui-monospace, monospace; font-size: 11px;
          font-weight: 500; color: #6b6e64; }
.ui-label { font-size: 10.5px; font-weight: 800; letter-spacing: 1.2px;
            text-transform: uppercase; color: #1b5e20; }
.ui-body { font-size: 13.5px; line-height: 1.5; color: #4d5147; margin: 0; }

/* The pseudonym chip. The air-gap made visible in the chrome: the cloud never
   learns the child's name, so the identifier a clinician reads IS the record.
   Mono, because a person transcribes it. */
.ui-chip { display: inline-flex; align-items: center; gap: 0.35rem; border-radius: 9px;
           padding: 0.2rem 0.45rem; background: #e9f3e9; color: #1b5e20;
           font-family: "Fira Code", ui-monospace, monospace; font-size: 11px;
           font-weight: 500; white-space: nowrap; }

/* Status, encoded in form as well as in colour, so it reads at a glance.
   Coral appears on exactly one of these and only because it means something:
   it is reserved for safety and critical severity and never becomes a
   decorative colour. */
/* The one control a rebuilt screen shows. The consent step's action is a
   button in the console, and rendering it as a provenance line read as a
   caption rather than as something a clinician presses, which is the whole
   point of that screen. Not the site's own button styles: this sits inside a
   mock at mock scale, so it borrows the accent and nothing else. */
.ui-action { display: inline-block; border-radius: 10px; padding: 0.4rem 0.8rem;
             background: #2e7d32; color: #ffffff; font-size: 12px; font-weight: 700;
             letter-spacing: -0.1px; }

.ui-pill { display: inline-block; border-radius: 9px; padding: 0.15rem 0.45rem;
           font-size: 9.5px; font-weight: 800; letter-spacing: 1.1px;
           text-transform: uppercase; white-space: nowrap; }
.ui-pill-routine { background: #f6f1e7; color: #4d5147; border: 1px solid #e8e2d4; }
.ui-pill-elevated { background: #f7efd9; color: #96600b; border: 1px solid rgba(150, 96, 11, 0.25); }
.ui-pill-critical { background: #fbf1ef; color: #b0301f; border: 1px solid rgba(176, 48, 31, 0.22); }

/* An icon chip: radius 12, a `surface2` fill, no ring. The character avatar
   shares this material rather than being the only differently-filled round
   thing on the card. */
.ui-icon { width: 24px; height: 24px; border-radius: 12px; background: #f6f1e7;
           display: inline-flex; align-items: center; justify-content: center;
           flex: none; color: #4d5147; font-size: 12px; }
.ui-avatar { width: 24px; height: 24px; border-radius: 50%; background: #f6f1e7; flex: none; }

/* The character name plate. Near-black, and it is only ever the small plate.
   Blackening the whole band was shipped once, for one release, from a misread
   of that instruction, and corrected the same day. */
.ui-plate { background: #14170f; color: #f7f5ef; border-radius: 9px;
            padding: 0.1rem 0.35rem; font-size: 9px; font-weight: 800;
            letter-spacing: 1.1px; flex: none; }

/* A waiting-list row. */
.ui-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
          padding-block: 0.5rem; }
.ui-row + .ui-row { border-top: 1px solid #f0ebdf; }
.ui-rank { font-family: "Fira Code", ui-monospace, monospace; font-size: 11px;
           color: #7f8377; flex: none; }
.ui-meta { font-size: 12px; color: #4d5147; }
.ui-wait { font-size: 11.5px; color: #6b6e64; margin-left: auto; white-space: nowrap; }

/* A metric. Tabular figures, because a row of numbers that does not line up
   reads as sloppy at exactly the moment it is being counted on. */
.ui-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.625rem; }
.ui-metric .n { font-size: 26px; font-weight: 800; letter-spacing: -0.03em;
                line-height: 1; font-variant-numeric: tabular-nums; color: #191c16; }
.ui-metric .k { display: block; font-size: 9.5px; font-weight: 800; letter-spacing: 1.1px;
                text-transform: uppercase; color: #1b5e20; margin-bottom: 0.4rem; }
.ui-metric.is-warn .n { color: #96600b; }
.ui-metric.is-warn .k { color: #96600b; }

/* An evidence marker. Every generated statement carries one, and it walks back
   to the form or note it came from. A statement with nothing behind it renders
   as a gap, never as a fact. */
.ui-ev { display: inline-block; border-radius: 4px; padding: 0 0.25rem; margin-left: 0.15rem;
         background: rgba(46, 125, 50, 0.10); color: #1b5e20;
         font-family: "Fira Code", ui-monospace, monospace; font-size: 10px;
         font-weight: 500; vertical-align: 1px; }

/* The layer, said in WORDS. This is the load-bearing provenance signal and the
   only one a screen reader can hear, so it is the one that never changes. */
.ui-prov { display: flex; align-items: center; gap: 0.35rem; margin-top: 0.75rem;
           font-size: 10.5px; font-weight: 800; letter-spacing: 1.1px;
           text-transform: uppercase; color: #6b6e64; }
.ui-prov::before { content: ""; width: 5px; height: 5px; border-radius: 50%;
                   background: #2e7d32; flex: none; }

.ui-ev.is-bare { margin-left: 0; }
.ui-gap { margin-top: 0.75rem; }
.ui-gap-body { margin-top: 0.35rem; }

.ui-legend { display: flex; gap: 1rem; margin-top: 0.5rem; font-size: 11.5px; color: #4d5147; }
.ui-legend span { display: inline-flex; align-items: center; gap: 0.4rem; }
.ui-legend i { width: 12px; height: 3px; border-radius: 2px; display: inline-block; }
.ui-legend i.is-teacher { background: #0277bd; }
.ui-legend i.is-parent  { background: #ea7317; }

/* The three panels are stacked in one box on the sticky stage, and the FIRST
   one sets its height (the others are absolutely positioned inside it). They
   are within about 40px of each other, which is close enough to look
   deliberate and not close enough to stop the shortest clipping the tallest,
   so the plate carries a floor. Below lg there is no stage and each panel
   flows on its own, so the floor is scoped to where the stacking happens. */
@media (min-width: 1024px) {
    .shots-stage .shot-plate { min-height: 27rem; }
}

.shots-step { padding-block: 1.25rem; }
.shots-step h3 { margin-bottom: 0.5rem; }

@media (min-width: 1024px) {
    .shots { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 4.5rem;
             align-items: start; }
    .shots-stage { position: sticky; top: 12vh; }
    .shots-frames { position: relative; }
    /* Every frame is stacked in the same box, so the stage never changes
       height as it swaps and the page cannot jitter under the reader.

       Hiding the inactive ones is NOT part of the animation and must not be
       gated on the motion preference: four frames in one box all at opacity 1
       is four frames printed on top of each other. Only the TRANSITION below
       is motion. With JavaScript off, the first frame ships carrying
       `is-active` in the markup, so one readable frame renders rather than
       none. */
    .shots-frames > .shot { position: absolute; inset: 0; opacity: 0; }
    .shots-frames > .shot.is-active { opacity: 1; }
    .shots-frames > .shot:first-child { position: relative; }
    .shots-steps { display: flex; flex-direction: column; }
    .shots-step { min-height: 62vh; display: flex; flex-direction: column;
                  justify-content: center; }
    .shots-rail { display: flex; flex-direction: column; justify-content: space-between;
                  position: relative; }
}

/* Below lg there is no room for a stage beside anything, so the steps carry
   their own frames and the sticky column is hidden outright. */
@media (max-width: 1023px) {
    .shots-stage { display: none; }
    .shot-inline { margin-top: 1.25rem; }
}
@media (min-width: 1024px) {
    .shot-inline { display: none; }
}

/* Final state is the default: all four frames visible and unfaded. */
.shot { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
    /* A FADE, and only a fade. This carried a 10px rise and a 0.985 scale
       until 24 August 2026, which was the last slide left on the site: every
       transition in the product has been a pure crossfade since 17 August,
       registered for all six platforms in the app. A scroll REVEAL is a
       different thing and keeps its small rise, because it is an entrance
       rather than a transition between two states of one surface. */
    .js-motion .shots-frames > .shot {
        transition: opacity 500ms cubic-bezier(0.5, 0, 0, 1); }
    .js-motion .shots-rail-fill { transition: height 450ms cubic-bezier(0.5, 0, 0, 1); }
    .js-motion .shots-node { transition: background-color 300ms ease, border-color 300ms ease; }
    .js-motion .shots-step { opacity: 0.38; transition: opacity 400ms ease; }
    .js-motion .shots-step.is-active { opacity: 1; }
    /* The route types itself out when a frame becomes active. Mono text, one
       character at a time: the same gesture the console uses to say "this is
       an identifier", borrowed as motion rather than invented as decoration. */
    .js-motion .shot-caret::after { content: "\00a0"; border-right: 1.5px solid #2e7d32;
        margin-left: 1px; animation: shotCaret 1s steps(1) infinite; }
    @keyframes shotCaret { 50% { border-color: transparent; } }
}

/* ---------------------------------------------------------------------------
   The consequence chain.

   These four lines were four bare list items in a two-column grid, while their
   direct counterpart four hundred pixels below (the solution list) had
   numbered cells, dividers and ink type. One was built and one was not, which
   is why the block read as filler.

   They are also not four separate complaints. They are a chain: gathering
   scattered information takes time, which becomes admin, which takes the time
   that would have gone on thinking, and the end of it is that nobody can tell
   whether the child is getting better. A two-column grid destroys that, because
   it reads across rather than down.

   So: one column, connected. The first three markers are `warn`, the page's
   established colour for the problem side. The LAST is `accentStrong` and its
   text is ink, because that consequence is the one the product answers, and
   the section immediately below it is "Penyelesaiannya". The colour is the
   hand-off.
   --------------------------------------------------------------------------- */

.chain { margin-top: 0.5rem; }
.chain-item { position: relative; padding-left: 2rem; padding-bottom: 1.75rem; }
.chain-item:last-child { padding-bottom: 0; }

.chain-dot { position: absolute; left: 0; top: 0.62rem; width: 7px; height: 7px;
             border-radius: 50%; background: #96600b; }
.chain-item:last-child .chain-dot { left: -1px; top: 0.56rem; width: 9px; height: 9px;
                                    background: #1b5e20; }

/* Sits between this marker and the next one. Absent on the last item, so the
   chain ends on the dot rather than trailing into nothing. */
.chain-link { position: absolute; left: 3px; top: 1.25rem; bottom: -0.4rem; width: 1px;
              background: rgba(25, 28, 22, 0.14); transform-origin: top; }
.chain-item:last-child .chain-link { display: none; }

/* Final state is drawn, so a JS failure or reduced motion leaves the chain
   whole rather than leaving four disconnected dots. */
@media (prefers-reduced-motion: no-preference) {
    .js-motion .chain-link { transform: scaleY(0);
        transition: transform 600ms cubic-bezier(0.5, 0, 0, 1); }
    .js-motion .reveal.is-visible .chain-link { transform: scaleY(1); }
    .js-motion .chain-item:nth-child(2) .chain-link { transition-delay: 120ms; }
    .js-motion .chain-item:nth-child(3) .chain-link { transition-delay: 240ms; }
}

/* ==========================================================================
   The user guide.

   A STANDALONE page that happens to live in this directory. Product owner,
   28 August 2026: "when we open docs, i expect to see a different page (dont
   even show the top header anymore) ... it feels weird if the top nav rail is
   in malay and the doc is in english."

   So this page carries no site nav, no language toggle and no dictionary. It
   is English throughout, with one way back to the product page. That removes
   the mismatch rather than papering over it, and it removes a whole class of
   stale-dictionary bug with it.

   It is still unmistakably the same product: the same wash ground, the same
   hairlines, the same forest green, the same corner ramp. A docs page with its
   own look would be the drift the one-look rule exists to stop.
   ========================================================================== */

.docs-page {
    min-height: 100vh;
    background:
        radial-gradient(1200px 620px at 8% -6%, #e7f1e3 0%, rgba(231, 241, 227, 0) 62%),
        linear-gradient(180deg, #fbf9f4 0%, #f7f3e9 100%);
}

/* ---- the slim top bar ---------------------------------------------------- */

.docs-top {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 0.85rem 1.5rem;
    background: rgba(251, 249, 244, 0.82);
    backdrop-filter: saturate(140%) blur(10px);
    border-bottom: 1px solid #e8e2d4;
}

.docs-top-mark { display: flex; align-items: center; gap: 0.7rem; min-width: 0; }

.docs-top-name {
    font-family: "Plus Jakarta Sans", system-ui, sans-serif;
    font-size: 1.05rem;
    font-weight: 800;
    letter-spacing: -0.035em;
    color: #191c16;
}

.docs-top-name span { color: #2e7d32; }

/* "User guide" sits after a hairline, so the page says what it is without a
   second heading competing with the h1 below. */
.docs-top-kind {
    padding-left: 0.85rem;
    margin-left: 0.15rem;
    border-left: 1px solid #d3cbb8;
    font-family: "Plus Jakarta Sans", system-ui, sans-serif;
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: #6b6e64;
    white-space: nowrap;
}

@media (max-width: 640px) { .docs-top-kind { display: none; } }

.docs-back {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 1.05rem;
    border-radius: 999px;
    border: 1px solid #d3cbb8;
    background: #ffffff;
    color: #191c16;
    font-size: 0.9rem;
    font-weight: 600;
    white-space: nowrap;
    transition: border-color 200ms ease, color 200ms ease, box-shadow 200ms ease;
}

.docs-back:hover {
    border-color: #2e7d32;
    color: #1b5e20;
    box-shadow: 0 1px 2px rgba(20, 23, 15, 0.04), 0 8px 20px rgba(20, 23, 15, 0.06);
}

.docs-back svg { transition: transform 220ms cubic-bezier(0.5, 0, 0, 1); }
.docs-back:hover svg { transform: translateX(-3px); }

/* ---- the two panels ------------------------------------------------------ */

.docs-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    max-width: 1500px;
    margin-inline: auto;
    padding-inline: 1.5rem;
}

@media (min-width: 1024px) {
    .docs-layout { grid-template-columns: 19rem minmax(0, 1fr); gap: 4rem; }
}

.docs-rail { padding-block: 2.5rem 1rem; }

/* THE GUIDE'S SEARCH FIELD. It filters the contents rail, so it lives at the
   top of the rail rather than over the prose: the control and the thing it
   changes are the same column. */
.docs-search-wrap { margin-bottom: 1.5rem; }

/* The field stays put while the contents scroll under it, which is the
   whole point of a filter: you retype and re-read without hunting for
   the control. The rail scrolls INTERNALLY, so this sticks to the
   rail's own scrollport rather than to the page.

   It borrows the header's treatment rather than inventing one. The
   page ground is a gradient, so a flat fill behind a sticky element
   would show a seam the moment it moved; the translucent fill and blur
   are exactly what .docs-top already uses two inches above it. */
@media (min-width: 1024px) {
    /* The rail's own top padding moves ONTO the sticky element. A sticky
       offset is measured from the scrollport's PADDING box, so top: 0 with
       padding-top on the container pins the field 2.5rem down and leaves a
       band above it where the contents scroll past in the open. Carrying the
       padding here pins it flush and lets its background cover the whole
       band. */
    .docs-rail { padding-top: 0; }
    .docs-search-wrap {
        position: sticky;
        top: 0;
        z-index: 5;
        margin-bottom: 1.25rem;
        padding-block: 2.5rem 0.75rem;
        /* Nearly opaque where the field sits, dissolving at the foot. There
           is no flat colour to match: the page ground is a gradient plus a
           radial tint, so a solid fill would print a rectangle over it. A
           hard translucent edge let the contents ghost through, which reads
           as a rendering fault rather than as depth. The rail already fades
           at its own foot; this is the same move at the head. */
        background: linear-gradient(180deg,
            rgba(251, 249, 244, 0.985) 0%,
            rgba(251, 249, 244, 0.985) 74%,
            rgba(251, 249, 244, 0) 100%);
        backdrop-filter: saturate(140%) blur(14px);
    }
}
.docs-search {
    width: 100%; border: 1px solid #e8e2d4; border-radius: 12px;
    background: #fdfcf9; padding: 0.5rem 0.75rem;
    font-family: inherit; font-size: 14px; color: #191c16;
}
.docs-search::placeholder { color: #7f8377; }
/* The focus ring is INSET. The rail is its own scroll container
   (overflow-y: auto at lg), and an outline with an offset is drawn
   outside the element, so the container clipped it: the ring arrived
   with pieces of its edges missing and read as a rendering fault. An
   inset shadow is inside the box and follows the same radius, so
   there is nothing for the scrollport to cut. */
.docs-search:focus-visible {
    outline: none;
    border-color: #2e7d32;
    box-shadow: inset 0 0 0 2px #2e7d32;
}
.docs-search-note { margin-top: 0.5rem; font-size: 12px; color: #6b6e64; min-height: 1em; }

/* While filtering, every surviving subnav is open. The rail otherwise opens
   only the part being read, which would hide the matches the search just
   found. */
.docs-rail.is-searching .docs-subnav { grid-template-rows: 1fr; opacity: 1; }
.docs-rail [hidden] { display: none !important; }

/* A label that only a screen reader needs. The placeholder says the same
   thing to everyone else, and a placeholder is not a label. */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

@media (min-width: 1024px) {
    .docs-rail {
        position: sticky;
        top: 3.75rem;
        align-self: start;
        height: calc(100vh - 3.75rem);
        overflow-y: auto;
        overscroll-behavior: contain;
        padding-right: 1rem;
        scrollbar-width: thin;
        scrollbar-color: #d3cbb8 transparent;
        /* The rail fades at its foot rather than ending on a hard cut, so a
           list that continues below looks like it continues. */
        mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - 2.5rem), transparent 100%);
    }
}

.docs-rail-title {
    font-family: "Plus Jakarta Sans", system-ui, sans-serif;
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: #6b6e64;
    margin-bottom: 1rem;
}

/* One measure for the whole column, so the chapter plate and the prose share
   an edge. They did not until 28 August 2026: the plate ran the full column
   while the text stopped at its own max-width, and the mismatch read as a
   layout mistake. */
.docs-main { padding-block: 3rem 6rem; min-width: 0; max-width: 52rem; }

/* ---- rail rows ----------------------------------------------------------- */

.docs-nav-link {
    position: relative;
    display: grid;
    grid-template-columns: 1.7rem minmax(0, 1fr);
    align-items: baseline;
    gap: 0.25rem;
    padding: 0.55rem 0.75rem 0.55rem 0.55rem;
    border-radius: 12px;
    color: #4d5147;
    font-size: 0.95rem;
    line-height: 1.35;
    transition: background-color 200ms ease, color 200ms ease, transform 200ms cubic-bezier(0.5, 0, 0, 1);
}

.docs-nav-num {
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 0.76rem;
    color: #9aa093;
    transition: color 200ms ease;
}

.docs-nav-link:hover { background: rgba(46, 125, 50, 0.055); color: #191c16; transform: translateX(2px); }
.docs-nav-link:hover .docs-nav-num { color: #2e7d32; }

/* The reading position. A filled pill rather than a bar beside the row: a
   vertical strip next to content is barred across this product. */
.docs-nav-link[aria-current="true"] {
    background: #e9f3e9;
    color: #1b5e20;
    font-weight: 700;
}

.docs-nav-link[aria-current="true"] .docs-nav-num { color: #2e7d32; }

/* ---- the subsection list, and its two depths ----------------------------- */

/* Animated open and close on a grid row rather than display:none, so the list
   grows instead of appearing. `grid-template-rows: 0fr -> 1fr` animates to a
   height nobody has to measure. */
.docs-subnav {
    display: grid;
    grid-template-rows: 0fr;
    margin-left: 1.55rem;
    padding-left: 0.9rem;
    border-left: 1px solid #e8e2d4;
    opacity: 0;
    transition: grid-template-rows 320ms cubic-bezier(0.5, 0, 0, 1), opacity 220ms ease;
}

.docs-subnav > div { overflow: hidden; }

.docs-subnav[data-open="true"] { grid-template-rows: 1fr; opacity: 1; margin-block: 0.2rem 0.7rem; }

/* THE NUMBER IS THE BULLET. Product owner, 28 August 2026, on the first
   version, where "1.1c" simply began the sentence behind a dash: "i dont like
   how it just being lined up with the text like that. Thats lazy. The
   numbering should be a distinctive bullet form."

   So the number sits in its own column and the title in another. Every title
   starts at the same x, a wrapped title wraps under itself rather than under
   its number, and the dash connector is gone: the number is the mark now, and
   two marks would be one too many. */
.docs-sublink {
    display: grid;
    grid-template-columns: 2.6rem minmax(0, 1fr);
    gap: 0.15rem;
    align-items: baseline;
    padding: 0.34rem 0.5rem;
    border-radius: 9px;
    font-size: 0.875rem;
    line-height: 1.4;
    color: #6b6e64;
    transition: color 180ms ease, background-color 180ms ease;
}

.docs-sublink-num {
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 0.72rem;
    letter-spacing: -0.02em;
    color: #9aa093;
    transition: color 180ms ease;
}

.docs-sublink:hover { color: #1b5e20; background: rgba(46, 125, 50, 0.05); }
.docs-sublink:hover .docs-sublink-num { color: #2e7d32; }
.docs-sublink[aria-current="true"] { color: #1b5e20; font-weight: 600; }
.docs-sublink[aria-current="true"] .docs-sublink-num { color: #2e7d32; }

/* A lettered section (1.1b, 7.3b) belongs to the one above it, so it is
   indented one step. Its number column narrows to keep the two levels' titles
   from drifting far apart. */
.docs-sublink[data-sub="true"] {
    margin-left: 1.1rem;
    grid-template-columns: 2.4rem minmax(0, 1fr);
    font-size: 0.845rem;
    color: #82877a;
}

.docs-sublink[data-sub="true"]:hover,
.docs-sublink[data-sub="true"][aria-current="true"] { color: #1b5e20; }

/* ---- a chapter ----------------------------------------------------------- */

.docs-part-head {
    position: relative;
    overflow: hidden;
    border-radius: 22px;
    border: 1px solid #e8e2d4;
    background: linear-gradient(135deg, #e7f1e3 0%, #fbf9f4 58%, #f7f3e9 100%);
    padding: 1.75rem 1.75rem 1.6rem;
    margin-bottom: 2.5rem;
}

.docs-part-head::after {
    content: "";
    position: absolute;
    right: -3.5rem;
    top: -3.5rem;
    width: 9rem;
    height: 9rem;
    border-radius: 50%;
    background: rgba(46, 125, 50, 0.07);
}

.docs-part-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.25rem;
    height: 2.25rem;
    padding-inline: 0.5rem;
    border-radius: 999px;
    background: #2e7d32;
    color: #fbf9f4;
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 0.95rem;
    font-weight: 600;
}

/* ---- prose --------------------------------------------------------------- */

.docs-prose { max-width: 46rem; }

/* A handle or a code must not break across lines. `MO-SUP1` wrapping to "MO-"
   and "SUP1" reads as two things, and the hyphen it breaks on is part of the
   value rather than punctuation. */
.docs-prose code { white-space: nowrap; }
.docs-prose strong { font-weight: 700; color: #191c16; }
.docs-prose p { margin-block: 0.95rem; }
.docs-prose ul, .docs-prose ol { margin-block: 1rem; padding-left: 1.35rem; }
.docs-prose li { margin-block: 0.4rem; }
.docs-prose ul > li::marker { color: #2e7d32; }
.docs-prose ol > li::marker { color: #6b6e64; font-family: "Fira Code", ui-monospace, monospace; font-size: 0.85em; }

/* A numbered heading in the body. The number is lifted out of the sentence
   into its own mark and hangs in the left margin, so every title in the
   chapter starts at the same x and the number reads as the section's label
   rather than as its first word.

   Hanging rather than in-flow: an in-flow number column would push every
   heading right and leave the prose under it starting somewhere else. */
.docs-h {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    position: relative;
}

.docs-h-num {
    position: absolute;
    right: calc(100% + 0.85rem);
    top: 0.18em;
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    color: #2e7d32;
    background: #e9f3e9;
    border-radius: 999px;
    padding: 0.22rem 0.5rem;
    white-space: nowrap;
}

.docs-h-text { display: block; }

/* Under about 1180px the hanging number would sit off the left edge of the
   column, so it comes back in flow above the title. */
@media (max-width: 1180px) {
    .docs-h-num { position: static; display: inline-block; margin-bottom: 0.45rem; }
}

/* A lettered heading is indented one step, the same as its rail row. The
   number is the only other signal it needs, so there is no connector line
   here: two marks for one relationship is one too many. */
.docs-sub-heading {
    margin-left: 1.6rem;
    font-size: 1.24rem !important;
    color: #2f3329;
}

.docs-sub-heading .docs-h-num {
    color: #6b6e64;
    background: #f0ece1;
}

@media (max-width: 640px) { .docs-sub-heading { margin-left: 0; } }

.docs-prose blockquote {
    margin: 1.5rem 0;
    padding: 1.1rem 1.35rem;
    border-radius: 16px;
    background: #f6f1e7;
    border: 1px solid #e8e2d4;
    color: #4d5147;
}

.docs-prose blockquote p:first-child { margin-top: 0; }
.docs-prose blockquote p:last-child { margin-bottom: 0; }

.docs-prose code {
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 0.86em;
    background: #f6f1e7;
    border: 1px solid #e8e2d4;
    border-radius: 6px;
    padding: 0.1em 0.36em;
}

.docs-prose pre {
    margin: 1.5rem 0;
    padding: 1.1rem 1.25rem;
    border-radius: 16px;
    background: #191c16;
    color: #f6f1e7;
    overflow-x: auto;
}

.docs-prose pre code { background: none; border: 0; padding: 0; color: inherit; font-size: 0.85rem; line-height: 1.6; }
.docs-prose hr { margin: 2.25rem 0; border: 0; border-top: 1px solid #e8e2d4; }
.docs-prose a { color: #1b5e20; text-decoration: underline; text-underline-offset: 3px; }
.docs-prose a:hover { color: #2e7d32; }

.docs-foot {
    border-top: 1px solid #e8e2d4;
    padding: 2rem 1.5rem 3rem;
    color: #6b6e64;
    font-size: 0.9rem;
}

/* ---------------------------------------------------------------- The gate */

/* The access-key dialog is a CARD, so it takes the card treatment: radius 20,
   white, no hairline. Its resting shadow is deeper than a card on the page
   because it floats over the whole page rather than on the ground, and the
   backdrop behind it is doing half that work. */
.gate-dialog {
    width: min(30rem, calc(100vw - 2rem));
    border-radius: 20px;
    background: #ffffff;
    color: #191c16;
    box-shadow: 0 1px 2px rgba(20, 23, 15, 0.06), 0 24px 60px rgba(20, 23, 15, 0.16);
}

.gate-dialog::backdrop { background: rgba(20, 23, 15, 0.42); }

/* Mono, for the same reason the pseudonym chip is mono: a person transcribes
   this from a message we sent them, so the characters have to be tellable
   apart. Letter-spacing for the same reason. */
.gate-input {
    width: 100%;
    padding: 0.875rem 1rem;
    border: 1px solid #d3cbb8;
    border-radius: 12px;
    background: #fbf9f4;
    color: #191c16;
    font-family: "Fira Code", ui-monospace, monospace;
    font-size: 1rem;
    letter-spacing: 0.04em;
    /* A key is case-insensitive, and the script upper-cases before it checks.
       This is so the field AGREES with that rather than showing a person
       something different from what it is about to test. */
    text-transform: uppercase;
}

.gate-input::placeholder { color: #6b6e64; opacity: 1; }

.gate-input:focus-visible {
    border-color: #2e7d32;
    outline: 2px solid #2e7d32;
    outline-offset: 2px;
}

/* Colour and nothing else. No panel, no rail, no border on one edge: the same
   rule the cards follow, wearing a smaller hat. */
.gate-message { font-size: 0.9375rem; line-height: 1.5; color: #b0301f; }
