/* The design system, as far as it is global (Signup Garden Design System,
 * "Draft v1", July 2026). Foundations and the components that appear on more
 * than one surface live here, because base.css loads on every page; the wizard
 * and the registration record keep their own patterns in registration.css.
 *
 * Where the design project contradicts itself, the newer foundations sections
 * win over the older component specimens -- see the notes at each site. Where
 * it contradicts TECHNICAL_SPEC.md, the spec wins (CLAUDE.md): the project
 * predates the Access Fund rename and still says "scholarship" throughout.
 */

/* Fonts ------------------------------------------------------------------ *
 *
 * Four faces with strict jobs (§2). MilesToGo is the identity and appears at
 * most once per screen; Scribble is rarer still -- empty states and
 * confirmations only, one line, never a price or a time. Montserrat carries
 * the interface, Verdana every paragraph.
 *
 * MilesToGo is licensed from Tom Gordon Design LTD and is not redistributable;
 * it is served from our own origin, never hotlinked. Montserrat is SIL OFL and
 * self-hosted rather than pulled from Google's CDN -- one less third party on
 * the critical path, and the pages have to hold up on a phone at a campsite.
 */
@font-face {
    font-family: "MilesToGo";
    src: url("fonts/TG-MilesToGo-Regular.00715ae0942c.otf") format("opentype");
    font-weight: normal;
    font-display: swap;
}

@font-face {
    font-family: "MilesToGo Scribble";
    src: url("fonts/TG-MilesToGo-Scribble.2ceff7d9eefb.otf") format("opentype");
    font-weight: normal;
    font-display: swap;
}

/* Sunside is an organizer-selectable display face (§3.3): an event can pick it
   for its hero through `Event.hero_font`, and Ruta Del Jefe does. Like MilesToGo
   it is licensed, not redistributable, and served from our own origin rather
   than hotlinked; declared here so the family is available, but downloaded only
   by a page that actually chooses it -- the `.hero-font-*` rules below are the
   only thing that references it. */
@font-face {
    font-family: "Sunside";
    src: url("fonts/Sunside-Regular.9eaee5cf2ee4.otf") format("opentype");
    font-weight: normal;
    font-display: swap;
}

/* One variable file per subset covers 400-700, which is more than the three
   weights the system allows (400 / 500 / 600). latin-ext is not there for
   Spanish -- E13's catalog fits inside latin -- but for the place names §9
   asks for with their diacritics intact. */
@font-face {
    font-family: "Montserrat";
    src: url("fonts/montserrat-latin.c154477b9aff.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Montserrat";
    src: url("fonts/montserrat-latin-ext.f143fb4877cf.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

/* Tokens ----------------------------------------------------------------- */

:root {
    /* Surfaces and ink (§1). Named as the design system names them. */
    --straw: #f9f5ea;
    --straw-2: #ede8db;
    --panel: #e9e8e4;
    --card: #ecebe7;
    --cream: #f5f0e3;
    --ink: #2a2016;
    --soft: #6d6960;
    --faint: #96907f;

    /* The fill behind an input. Not in the swatch table, but used for every
       field in §5 and §6 -- a field reads as a field because it is lighter
       than the sheet it sits on, and tinting a form is called out as wrong. */
    --field-fill: #fdfaf2;

    /* Chromatics and their jobs (§1). Each has exactly one job. */
    --plum: #7d4b52;
    --plum-text: #5a3038;
    --plum-tint: #ecdcdd;
    --clay: #9c4c21;
    --clay-deep: #7a3a19;
    --clay-tint: #eed6c8;
    --sage: #5c6238;
    --sage-deep: #3f4323;
    --sage-tint: #e3e3c8;
    --teal: #2f6d69;
    --teal-deep: #215350;
    --teal-tint: #d6e5e1;
    --gold: #cd9a3c;
    --gold-text: #7a5a17;
    --gold-tint: #f2e3c4;
    --brick: #8a3f35;
    --brick-deep: #6b2f27;
    --brick-tint: #ecd8d2;

    /* The six semantic tokens (§1). Every surface below is built off these so
       that dark mode is a token swap rather than a redesign. The reserved dark
       values are recorded at the bottom of this file; they are deliberately
       not wired to prefers-color-scheme, because the design system marks them
       "unverified against AA -- audit before P4" and shipping an unaudited
       dark palette to a page read in glare is worse than shipping none. */
    --bg: var(--straw);
    --surface: var(--cream);
    --surface-2: var(--straw-2);
    --rule: var(--ink);
    --fg: var(--ink);
    --fg-2: var(--soft);

    /* Rules (§3): solid bounds, dotted separates. Four weights, no fifth. */
    --rule-divide: rgba(42, 32, 22, 0.15); /* inside a frame */
    --rule-column: rgba(42, 32, 22, 0.2); /* column divider, aside edge */
    --rule-dotted: rgba(42, 32, 22, 0.4); /* rows, sections */
    --rule-dotted-item: rgba(42, 32, 22, 0.45); /* like items in one card */

    /* Semantic jobs. The indirection is the point: a rule below says
       "the action colour", not "plum", so §1's one-job-per-colour survives
       anyone editing this file later. */
    --action: var(--plum);
    --action-deep: var(--plum-text);
    --action-tint: var(--plum-tint);
    --warning: var(--clay);
    --warning-deep: var(--clay-deep);
    --warning-tint: var(--clay-tint);
    --success: var(--sage);
    --success-deep: var(--sage-deep);
    --success-tint: var(--sage-tint);
    --attention: var(--gold);
    --attention-text: var(--gold-text);
    --attention-tint: var(--gold-tint);

    /* Categories (§1). Read-only signals, never controls. Sage / teal / brick
       per the foundations table, which assigns ride to sage and reserves clay
       for warnings. §5's specimens draw the ride dot in clay instead -- that is
       carried over from the earlier draft (Signup Garden Web Registration.dc.html,
       which used clay as its action colour throughout) and was not updated when
       §1 moved action to plum. Following it here would paint every ride session
       in the same colour as a validation error. */
    --ride: var(--sage);
    --learn: var(--teal);
    --gather: var(--brick);

    /* Type (§2) */
    --font-display: "MilesToGo", "Montserrat", sans-serif;
    --font-scribble: "MilesToGo Scribble", "MilesToGo", cursive;
    --font-ui: "Montserrat", system-ui, sans-serif;
    --font-body: Verdana, Geneva, sans-serif;

    /* Scale (§2), web column. The mobile column is applied at the bottom of
       this file -- note that ui and meta get *larger* there, not smaller. */
    --size-display: 64px;
    --size-h1: 36px;
    --size-h2: 26px;
    --size-h3: 19px;
    --size-body: 17px;
    --size-ui: 15px;
    --size-meta: 13px;
    --leading-display: 1.05;
    --leading-h1: 1.15;
    --leading-h2: 1.2;
    --leading-h3: 1.3;
    --leading-body: 1.6;
    --leading-ui: 1.4;

    /* Space (§3): 4px base. */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;
    --space-8: 64px;
    --space-9: 96px;

    /* Radii (§3). Anything that reads as a tappable object is fully round;
       a field is a container rather than an object, so it stays at 6px. */
    --radius-frame: 5px;
    --radius-field: 6px;
    --radius-pill: 999px;

    /* Page (§3) */
    --page-max: 1180px;
    --page-pad: var(--space-8);
    --measure: 68ch;

    /* Motion (§3). No parallax, no scroll reveals, no springs. */
    --motion: 140ms ease-out;

    /* Field conditions (§4): 48px, not 44 -- gloves, cold hands, one hand on
       a bike. 12px minimum between adjacent targets. */
    --target: 48px;
    --target-gap: var(--space-3);
}

/* Base ------------------------------------------------------------------- */

*,
*::before,
*::after {
    box-sizing: border-box;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--fg);
    font-family: var(--font-body);
    font-size: var(--size-body);
    line-height: var(--leading-body);
    -webkit-font-smoothing: antialiased;
}

/* Headings, buttons, nav, labels and numbers are interface, so they are
   Montserrat; running text is Verdana. Weights 400 / 500 / 600 only. */
h1,
h2,
h3,
h4 {
    font-family: var(--font-ui);
    font-weight: 600;
    color: var(--fg);
    text-wrap: balance;
}

h1 {
    font-size: var(--size-h1);
    line-height: var(--leading-h1);
    letter-spacing: -0.01em;
    margin: 0 0 var(--space-3);
}

h2 {
    font-size: var(--size-h2);
    line-height: var(--leading-h2);
    margin: var(--space-7) 0 var(--space-3);
}

h3 {
    font-size: var(--size-h3);
    line-height: var(--leading-h3);
    margin: var(--space-5) 0 var(--space-2);
}

p {
    max-width: var(--measure);
    text-wrap: pretty;
}

/* Verdana has no true italic, so emphasis leans on bold instead (§2). */
em,
i {
    font-style: normal;
    font-weight: bold;
}

a {
    color: var(--action-deep);
    text-decoration: underline;
    text-underline-offset: 2px;
    transition: color var(--motion);
}

a:hover {
    color: var(--ink);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
    outline: 2px solid var(--action-deep);
    outline-offset: 2px;
    border-radius: var(--radius-field);
}

hr {
    border: 0;
    border-top: 3px dotted var(--rule-dotted);
    margin: var(--space-7) 0;
}

code,
kbd {
    font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
    background: var(--panel);
    border: 1px solid var(--rule-column);
    border-radius: var(--radius-frame);
    padding: 1px 5px;
    font-size: 0.9em;
}

/* The identity faces. MilesToGo never goes below 32px and is never a
   paragraph; Scribble is one line, 28-44px, on an empty state or a
   confirmation, and never carries a time, a price, a name, or an error. */
.display {
    font-family: var(--font-display);
    font-size: var(--size-display);
    line-height: var(--leading-display);
    font-weight: normal;
    letter-spacing: 0.01em;
    margin: 0;
}

.scribble {
    font-family: var(--font-scribble);
    font-size: 40px;
    line-height: 1.1;
    font-weight: normal;
    /* The h1 rule above tightens tracking by -0.01em, which is a setting for
       Montserrat and not for a hand. Scribble is drawn with its own spacing
       between the letters; pulling them together is the one way to make it
       read as a font rather than as handwriting. Stated rather than left to
       inheritance, because the class is worn *by* an h1 on both confirmation
       screens. */
    letter-spacing: normal;
    color: var(--fg);
    margin: 0 0 var(--space-2);
}

/* Per-event hero face (§3.3). An event carries its choice in `Event.hero_font`,
   the landing page prints it as one of these classes on the hero's container,
   and the class repoints `--font-display` for that subtree alone -- so only the
   display face moves, body and interface text keep theirs, and every other
   event is untouched. The default class restates the Signup Garden stack, so a
   page can wear the class unconditionally and the choice is always explicit in
   the markup. Each `<value>` matches a `HeroFont` value in events/models.py. */
.hero-font-signup_garden {
    --font-display: "MilesToGo", "Montserrat", sans-serif;
}

.hero-font-ruta_del_jefe {
    --font-display: "Sunside", "MilesToGo", sans-serif;
}

.hero-font-montserrat {
    --font-display: "Montserrat", system-ui, sans-serif;
}

.hero-font-serif {
    --font-display: Georgia, "Times New Roman", serif;
}

/* Page chrome ------------------------------------------------------------ */

/* `padding-inline` rather than the `padding: 0 ...` shorthand, which also set
   the vertical padding to zero. `main` is a `.container`, so the shorthand was
   quietly overriding the `padding-top`/`padding-bottom` the `main` rule below
   sets -- a class beats an element selector whatever the order -- and every
   page's heading butted into the header while its last element butted into the
   footer. The horizontal value is unchanged. */
.container {
    max-width: var(--page-max);
    margin: 0 auto;
    padding-inline: var(--page-pad);
}


/* Sticky 64px header, text links, no hamburger above 768px (§7). Opaque, per
   §4 -- a translucent bar washes out in sun. */
.site-header {
    background: var(--panel);
    border-bottom: 1px solid var(--rule);
}

/* Scoped to the header on purpose. A bare `nav` selector also catches the
   breadcrumb rail and the wizard's own step bar -- and turning the step bar
   into a flex row collapses it to its content width, which wraps four steps
   onto two lines. */
.site-header nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-4);
    min-height: var(--space-8);
}

.site-header nav ul {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    list-style: none;
    padding: 0;
    margin: var(--space-2) 0;
}

.site-header nav a,
.site-header nav input[type="submit"] {
    font-family: var(--font-ui);
    font-size: var(--size-ui);
    font-weight: 500;
    line-height: var(--leading-ui);
    color: var(--fg);
    text-decoration: none;
    padding: var(--space-2) 0;
    display: inline-block;
}

.site-header nav a:hover,
.site-header nav input[type="submit"]:hover {
    color: var(--action-deep);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* The logout control is a form because it must POST; it should not therefore
   look like a button next to a row of links. */
.site-header nav input[type="submit"] {
    background: none;
    border: 0;
    min-height: 0;
    padding: var(--space-2) 0;
    border-radius: 0;
    cursor: pointer;
}

.site-header nav form {
    display: inline;
}

/* The site-language switch (E13). Two text buttons posting to set_language;
   they sit in the header row like the links beside them, not as chrome-heavy
   buttons. The header prefix outranks `.site-header nav form` above, which
   would otherwise lay the pair out as plain inline text. */
.site-header .lang-switch form {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
}

.lang-option {
    font-family: var(--font-ui);
    font-size: var(--size-ui);
    font-weight: 500;
    line-height: var(--leading-ui);
    color: var(--fg);
    background: none;
    border: 0;
    padding: var(--space-2) 0;
    cursor: pointer;
}

.lang-option:hover {
    color: var(--action-deep);
    text-decoration: underline;
}

/* The active language is the one being read, so it is stated rather than
   offered: pressed, in ink, and not itself styled as an action. */
.lang-option.is-current {
    font-weight: 600;
    text-decoration: underline;
}

/* The wordmark slot.
 *
 * The size was a hard 32px, annotated here as "the floor MilesToGo is allowed
 * (§2)". That floor is real, but it never bound this rule: the slot sets
 * `--font-ui`, so it has always been Montserrat and never the display face.
 * Freed of a constraint it was not under, it takes a scale value -- the step
 * below the old 32px, which keeps it plainly the site's name beside 15px nav
 * links without spending a third of the 64px bar on it.
 *
 * Selector carries the header prefix so it outranks `.site-header nav a`,
 * which would otherwise put the wordmark back into the interface face -- as it
 * did for as long as the class was off the markup. */
.site-header .site-home {
    display: inline-flex;
    align-items: center;
    font-family: var(--font-ui);
    font-size: var(--size-h2);
    line-height: 1;
    color: var(--fg);
    text-decoration: none;
}

.site-header .site-home:hover {
    color: var(--action-deep);
}

/* Branding an organizer uploaded (§3.2). Both are capped rather than sized:
   the image is whatever they gave us, and the page must not shift when it is
   twice the height anyone expected.

   The markup these style arrives with the event-branding work, which is not
   merged yet -- they are here so that when it lands it lands tokenized, rather
   than bringing the hardcoded version back in behind the design system. */
.site-logo {
    display: block;
    max-height: 7rem;
    max-width: 12rem;
    width: auto;
}

.event-banner {
    display: block;
    width: 100%;
    max-height: 20rem;
    object-fit: cover;
    /* Full bleed or 1px ink on all four sides -- never a rounded card and
       never a single-side accent edge (§1). */
    border-bottom: 1px solid var(--rule);
    margin: 0 0 var(--space-6);
}

/* Ornamental borders -- the one hero exception (§1).
 *
 * A single hand-drawn repeating border, always ink, always flat. The design
 * system calls it the one place a decorative line is allowed instead of a
 * structural one: it sits where the dotted rule would be too quiet, under a
 * hero header or between two whole sections. It does **not** go inside a card,
 * next to a form, or more than once on a screen.
 *
 * Drawn as a mask rather than a background image, which is how the design
 * project draws it: the PNG supplies the shape and `background-color` supplies
 * the ink, so the motif follows the token and will follow it into dark mode
 * rather than staying a fixed-colour picture of a border.
 *
 * Heights are the design project's own, one per motif -- the art is drawn at
 * different proportions and `mask-size: auto 100%` scales to the box.
 */
.border-rule {
    background-color: var(--rule);
    -webkit-mask-repeat: repeat-x;
    mask-repeat: repeat-x;
    -webkit-mask-size: auto 100%;
    mask-size: auto 100%;
    margin: var(--space-6) 0;
}

/* Vine -- hero headers and welcome screens. */
.border-vine {
    height: 26px;
    -webkit-mask-image: url("borders/border-vine.e5b34a817bd8.png");
    mask-image: url("borders/border-vine.e5b34a817bd8.png");
}

/* Fern -- printed schedules and roster pages. */
.border-fern {
    height: 26px;
    -webkit-mask-image: url("borders/border-fern.c0b32cd5125f.png");
    mask-image: url("borders/border-fern.c0b32cd5125f.png");
}

/* Floral row -- confirmation and thank-you screens. */
.border-floral-row {
    height: 22px;
    -webkit-mask-image: url("borders/border-floral-row.01aeb0c15940.png");
    mask-image: url("borders/border-floral-row.01aeb0c15940.png");
}

/* Star wave -- evening or after-dark events. */
.border-star-wave {
    height: 20px;
    -webkit-mask-image: url("borders/border-star-wave.f83fd21c35a8.png");
    mask-image: url("borders/border-star-wave.f83fd21c35a8.png");
}

/* Spiral -- the quietest option, small dividers. */
.border-spiral {
    height: 18px;
    -webkit-mask-image: url("borders/border-spiral.ccdc67e321b1.png");
    mask-image: url("borders/border-spiral.ccdc67e321b1.png");
}

/* Roomy outside, tight inside (§3). Without the top padding a page whose first
   element is its h1 butts straight into the header's ink edge. */
main {
    padding-top: var(--space-6);
    padding-bottom: var(--space-9);
}

/* Reading and secondary text -------------------------------------------- */

.lede {
    font-size: var(--size-body);
    line-height: var(--leading-body);
    color: var(--fg-2);
    max-width: 60ch;
}

.note {
    font-family: var(--font-ui);
    font-size: var(--size-meta);
    font-weight: 500;
    line-height: var(--leading-ui);
    color: var(--fg-2);
}

/* Buttons (§5) ----------------------------------------------------------- *
 *
 * Primary, secondary, quiet, disabled -- one primary per screen. Fully round:
 * the radius is half the height, so the ends stay true semicircles at any size.
 * A disabled button states the reason in its label rather than going mute.
 */
/* An <a> and a <button> have to be indistinguishable here, because §5 draws
   these as one component and the markup underneath is whichever the action
   happens to need -- a link when it navigates, a button when it posts. Left to
   itself an <a> arrives with the underline every link gets and with its label
   sitting on the text baseline rather than centred in the pill, so both are
   settled here rather than in each caller. */
.button-primary,
.button-secondary,
button,
input[type="submit"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: 16px;
    min-height: var(--target);
    padding: 0 var(--space-5);
    border-radius: var(--radius-pill);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--motion), color var(--motion),
        border-color var(--motion);
}

/* Secondary is the default a bare <button> gets: ink on nothing. A button has
   to opt in to being the one plum thing on the screen. */
button,
input[type="submit"],
.button-secondary {
    background: transparent;
    color: var(--fg);
    border: 1px solid var(--ink);
}

button:hover,
input[type="submit"]:hover,
.button-secondary:hover {
    background: var(--surface-2);
}

.button-primary {
    background: var(--action);
    color: var(--cream);
    border: 1px solid var(--action);
    padding: 0 28px;
}

.button-primary:hover {
    background: var(--action-deep);
    border-color: var(--action-deep);
    color: var(--cream); 
}


.button-quiet {
    background: transparent;
    border: 0;
    color: var(--action-deep);
    text-decoration: underline;
    text-underline-offset: 3px;
    padding: 0;
}

.button-quiet:hover {
    background: transparent;
    border: 0;
    color: var(--action-deep);
    text-decoration: underline;
    text-underline-offset: 3px;
    padding: 0;
}

/* Destructive actions are clay, not plum (§1). There is no specimen for this
   in §5 -- the four shown are primary, secondary, quiet and disabled -- but §1
   gives clay the job of "destructive actions" explicitly, and giving away a
   spot should not wear the same colour as Continue. */
.button-danger {
    background: var(--warning);
    color: var(--cream);
    border: 1px solid var(--warning);
    padding: 0 28px;
}

.button-danger:hover {
    background: var(--warning-deep);
    border-color: var(--warning-deep);
}

.button-primary[disabled],
.button-secondary[disabled],
button[disabled],
input[type="submit"][disabled] {
    background: var(--surface-2);
    color: var(--faint);
    border-color: #c9b795;
    cursor: not-allowed;
}

/* Forms (§5) ------------------------------------------------------------- */

.field {
    margin: 0 0 var(--space-4);
}

.field label {
    display: block;
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: 14px;
    line-height: var(--leading-ui);
    margin-bottom: var(--space-2);
}

.field input,
.field select,
.field textarea,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="number"],
input[type="url"],
select,
textarea {
    width: 100%;
    min-height: var(--target);
    padding: 0 14px;
    background: var(--field-fill);
    color: var(--fg);
    border: 1px solid var(--ink);
    border-radius: var(--radius-field);
    font-family: var(--font-body);
    font-size: 16px;
}

textarea {
    padding: 12px 14px;
    line-height: var(--leading-body);
    min-height: calc(var(--target) * 2);
}

/* Placeholder is soft, never faint -- faint is decorative only and never
   load-bearing text (§1, §4). */
::placeholder {
    color: var(--fg-2);
    opacity: 1;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
    border-color: var(--action-deep);
}

/* A browser-default box is about 13px, which is not a thing to find in sun
   with cold hands. The 48px target is the label wrapping it (§4); this is
   about being able to see whether the box is ticked. */
.field input[type="checkbox"],
.field input[type="radio"] {
    width: 20px;
    height: 20px;
    min-height: 0;
    flex: none;
    accent-color: var(--action);
}

.field label.checkbox {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
    font-weight: 400;
    font-family: var(--font-body);
    font-size: var(--size-body);
}

.field-help {
    margin: var(--space-2) 0 0;
    font-family: var(--font-ui);
    font-size: var(--size-meta);
    line-height: var(--leading-ui);
    color: var(--fg-2);
}

/* Help text that arrives as a list -- Django writes the password rules that way
   -- rather than as one sentence. Tight against the field and in the help
   text's own size, not the body's, so a rule of thumb never outweighs the
   control it is about. */
.field-help ul {
    margin: var(--space-1) 0 0;
    padding-left: var(--space-4);
}

.field-help li {
    margin: 0 0 var(--space-1);
}

/* Errors sit inline against the control that caused them -- never a bubble,
   never a toast (§5, §7). */
.field-error {
    margin: var(--space-2) 0 0;
    font-family: var(--font-ui);
    font-size: var(--size-meta);
    font-weight: 500;
    line-height: var(--leading-ui);
    color: var(--warning-deep);
}

/* An error on a checkbox or a radio group is the only sign anything is wrong --
   there is no box to turn red -- and at the 13px a text field's error gets it
   reads as a footnote. Body size, a rule down its side, so it is read as the
   reason the form did not go through. */
fieldset > .field-error,
.field:has(label.checkbox) > .field-error {
    margin: var(--space-3) 0;
    padding-left: var(--space-3);
    border-left: 3px solid var(--warning-deep);
    font-size: var(--size-body);
    font-weight: 600;
}

.has-error input,
.has-error textarea,
.has-error select {
    border-color: var(--warning-deep);
    background: var(--warning-tint);
}

.required-mark {
    color: var(--action-deep);
}

/* A fieldset is a group of fields, so it is drawn the way §3 draws a group:
 * no box of its own -- the browser's default is a 2px groove, which is not a
 * rule this system has -- and a single 1px ink-at-15% line beneath it, which
 * is exactly what §3 calls "the line under a fieldset".
 *
 * Without these the Address group on the rider-details step rendered in the
 * browser's default chrome: a groove border, a legend in the body face, and
 * 0.75em of padding nothing else on the page uses.
 *
 * A fieldset that sits *inside* a framed card is a different case and keeps
 * its own rules -- see `.slot` and `.choice-group` in registration.css, where
 * the card's frame is already the boundary and a second line under the legend
 * would draw the same edge twice.
 */
fieldset {
    border: 0;
    margin: 0 0 var(--space-5);
    padding: 0 0 var(--space-4);
    border-bottom: 1px solid var(--rule-divide);
}

legend {
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: var(--size-h3);
    line-height: var(--leading-h3);
    padding: 0;
    margin-bottom: var(--space-3);
}

.field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 0 var(--space-5);
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    align-items: center;
    margin: var(--space-6) 0;
}

.actions .note {
    margin: 0;
    max-width: 34ch;
}

/* Panels (§3) ------------------------------------------------------------ *
 *
 * 1px solid ink bounds a thing; 5px on the outside corners of anything with a
 * rule drawn around it. Inside the frame, divisions stay square.
 */
.panel {
    border: 1px solid var(--rule);
    border-radius: var(--radius-frame);
    background: var(--surface);
    padding: var(--space-4);
    margin: 0 0 var(--space-4);
}

.panel h2 {
    font-size: var(--size-ui);
    margin: 0 0 var(--space-3);
}

/* §3's two exceptions. A dotted rule normally goes *between* things of the
   same kind and never around anything -- except that a 2px dotted, or a 2px
   double, ink border may bound a whole card, panel or button on its own,
   "as long as nothing inside it is further divided". So these two carry no
   internal rules, by construction. */
.panel-dotted {
    border: 2px dotted var(--rule);
}

.panel-double {
    border: 2px double var(--rule);
}

/* A row of panels sharing their edges (§3).
 *
 * The gap *is* the rule: 1px of ink shows through between cells, so the group
 * reads as one framed thing subdivided rather than as separate boxes with a
 * space between them. Structure from ink, never from shadow -- and the
 * divisions inside stay square while the outside corners round.
 */
.panel-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 1px;
    background: var(--rule);
    border: 1px solid var(--rule);
    border-radius: var(--radius-frame);
    overflow: hidden;
    margin: 0 0 var(--space-4);
}

.panel-grid > * {
    background: var(--surface);
    padding: var(--space-4);
    margin: 0;
}

/* A cell is a panel that draws no border of its own -- the 1px gap draws it --
   so it needs `.panel h2`'s sizing without `.panel`'s frame. Without this the
   cells fall back to the global h2 and a row of panels disagrees with a lone
   one standing next to it. */
.panel-grid > * > h2 {
    font-size: var(--size-ui);
    margin: 0 0 var(--space-3);
}

/* Cards (§5) -------------------------------------------------------------- *
 *
 * The route card generalised: a framed thing with an optional picture band
 * across the top, then a body. Same bounding rule as a panel -- 1px ink, 5px
 * on the outside corners only -- so `overflow: hidden` is what keeps the media
 * band's corners inside the frame instead of poking through it.
 *
 * No shadow, ever: §3 is explicit that depth is surface tone plus a rule.
 */
.card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--rule);
    border-radius: var(--radius-frame);
    background: var(--surface);
    overflow: hidden;
}

/* The picture, or the slot where one will go. Its lower edge is a full ink
   rule -- the band is a division inside the frame, so that edge stays square
   and full strength rather than the 15% tint used for list rows. */
.card-media {
    display: grid;
    place-items: center;
    height: 130px;
    border-bottom: 1px solid var(--rule);
    background: var(--surface-2);
}

.card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.card-media-label {
    font-family: var(--font-ui);
    font-size: 13px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--fg-2);
}

.card-body {
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    flex: 1;
}

.card-title {
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: 20px;
    line-height: var(--leading-ui);
    margin: 0;
}

/* The figures line -- distance, elevation, dates. Ink rather than the clay the
   design project's route-card specimen uses: §1 gives clay to validation
   errors and destructive actions, and a route's mileage is neither. The
   specimens predate §1's colour pass and still carry the older palette; where
   the two disagree the written rule wins. Weight and face do the emphasis. */
.card-stat {
    font-family: var(--font-ui);
    font-weight: 500;
    font-size: 15px;
    color: var(--fg);
    margin: var(--space-1) 0 0;
}

.card-text {
    font-size: 15px;
    line-height: 1.5;
    color: var(--fg-2);
    margin: var(--space-2) 0 0;
}

/* Pushed to the bottom so buttons line up across a row of cards whose bodies
   are different lengths. */
.card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: auto;
    padding-top: var(--space-4);
}

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
    gap: var(--space-4);
    margin: 0 0 var(--space-6);
}

/* Status pills (§5) ------------------------------------------------------ *
 *
 * One per registration, at the top of the record. Held spots -- awaiting
 * payment, and an Access Fund application under review -- read as active,
 * because they are.
 */
.status-pill {
    display: inline-block;
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: var(--size-meta);
    line-height: var(--leading-ui);
    padding: 6px 12px;
    border: 1px solid var(--faint);
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--fg-2);
    margin: 0 0 var(--space-2);
}

.status-draft {
    border-color: var(--faint);
    color: var(--fg-2);
}

.status-awaiting_payment {
    border-color: var(--warning);
    color: var(--warning-deep);
    background: var(--warning-tint);
}

.status-waitlisted {
    border-color: var(--soft);
    color: #4a3f31;
    background: var(--surface-2);
}

.status-registered {
    border-color: var(--success);
    color: var(--cream);
    background: var(--success);
}

.status-cancelled {
    border-color: var(--faint);
    color: var(--faint);
    background: transparent;
    text-decoration: line-through;
}

/* Notices (§4) ----------------------------------------------------------- *
 *
 * A condition the system knows, not something anyone said -- so a bar, never a
 * talk bubble (§5's "not this"). Opaque, bounded on all four sides.
 */
.notice {
    border: 1px solid var(--rule);
    border-radius: var(--radius-frame);
    background: var(--surface);
    padding: var(--space-3) var(--space-4);
    margin: var(--space-4) 0;
}

/* Clay: something needs a fix before the rider can move on -- a step left
   unfinished, a chosen session that filled, a destructive action about to be
   taken (§1).

   `.notice-attention` is the name this rule shipped under and is kept as a
   second selector so the surfaces still wearing it keep their colour. The
   name was a trap: `--attention` is *gold*, and a class called
   `.notice-attention` built out of `--warning` put the two roles one typo
   apart. New markup says `.notice-warning`. */
.notice-warning,
.notice-attention {
    border-color: var(--warning);
    background: var(--warning-tint);
}

/* Gold: worth flagging, with nothing to fix before moving on (§1) -- a spot
   that is held, a commitment still outstanding, an event that is full and
   has a waitlist behind it. Built from the attention role the way
   `.notice-good` below is built from the success role. */
.notice-caution {
    border-color: var(--attention);
    background: var(--attention-tint);
}

/* The other half of the pair, for a condition that is in the rider's favour --
   their window being open. No specimen in the design project, which draws only
   the attention variant; built from §1's success role the way .notice-attention
   is built from its warning role. */
.notice-good {
    border-color: var(--success);
    background: var(--success-tint);
}

.notice p:first-child {
    margin-top: 0;
}

.notice p:last-child {
    margin-bottom: 0;
}

/* Django's messages framework lands here. Same reasoning as .notice: these are
   conditions, so they stay bars. */
.message-box {
    border: 1px solid var(--rule);
    border-radius: var(--radius-frame);
    background: var(--surface);
    padding: var(--space-3) var(--space-4);
    margin: var(--space-4) 0;
}

.message-box-summary {
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: var(--size-ui);
    line-height: var(--leading-ui);
    display: block;
}

.message-box-content p {
    margin: var(--space-1) 0 0;
}

.message-box-success {
    border-color: var(--success);
    background: var(--success-tint);
}

.message-box-info {
    border-color: var(--teal);
    background: var(--teal-tint);
}

.message-box-warning {
    border-color: var(--attention);
    background: var(--attention-tint);
}

.message-box-error,
.message-box-debug {
    border-color: var(--warning);
    background: var(--warning-tint);
}

/* Category (§5) ---------------------------------------------------------- *
 *
 * A pill where a row has spare width, a dot on phone rows and dense lists.
 * Both read-only: a category is never a control, so neither takes a hover or
 * a press state.
 */
.dot {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex: none;
}

.category-pill {
    display: inline-block;
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: var(--size-meta);
    line-height: var(--leading-ui);
    padding: 6px 12px;
    border-radius: var(--radius-pill);
    color: var(--cream);
}

.dot-ride,
.category-pill.category-ride {
    background: var(--ride);
}

.dot-learn,
.category-pill.category-learn {
    background: var(--learn);
}

.dot-gather,
.category-pill.category-gather {
    background: var(--gather);
}

/* Empty states (§5) ------------------------------------------------------ */

.empty-state {
    border: 1px solid var(--rule);
    border-radius: var(--radius-frame);
    background: var(--surface);
    padding: 28px var(--space-5);
}

/* Whichever level the surrounding outline calls for -- an empty state inside a
   section sits a level down from one that is the page's own subject. */
.empty-state :is(h2, h3) {
    font-size: 17px;
    margin: 0 0 var(--space-2);
}

/* The sentence under the heading, at the size §5 draws it: a step down from
   body, in soft. Left to inherit it came out at 17px ink, which made the
   explanation compete with the heading it explains. The component carries
   this rather than each caller -- `.window-preview` on the home page was the
   only caller when the empty state landed and is no longer. */
.empty-state p {
    font-size: 15px;
    line-height: 1.55;
    color: var(--fg-2);
    margin: 0 0 var(--space-4);
}

.empty-state p:last-child {
    margin-bottom: 0;
}

/* Records (§3, §5) -------------------------------------------------------- *
 *
 * A list of label-and-value pairs -- what is on file about a rider, and what
 * they are booked on. Rows carry the ruled-paper line, 1px dotted ink at 40%,
 * because they are like items bounded by the frame around them rather than by
 * a rule of their own.
 *
 * Label above value rather than beside it: these values are names, addresses
 * and free text of no predictable length, and a two-column row breaks the
 * moment one wraps. `.cost` in registration.css is the two-column sibling,
 * where the value is always a short amount.
 *
 * Lived in registration.css until the profile page grew a record of its own;
 * this file is where a component that appears on more than one surface goes.
 */
.record {
    margin: 0;
}

.record > div {
    display: flex;
    flex-direction: column;
    padding: 10px 0;
    border-bottom: 1px dotted var(--rule-dotted);
}

.record dt {
    color: var(--fg);
    font-family: var(--font-ui);
    font-weight: 600;
    font-size: 14px;
    margin-bottom: var(--space-1);
}

.record dd {
    margin: 0;
    text-align: left;
    font-family: var(--font-body);
    font-weight: 400;
}

/* §3's rule about rules: they go *between* things. On the last row the dotted
   line would sit a few pixels above the frame's own edge and read as a mistake. */
.record > div:last-child {
    border-bottom: 0;
    padding-bottom: 0;
}

/* An unanswered field is still a row -- the gap is the information (§5). Faint
   is the decorative grey and is not allowed to carry meaning, so this is soft. */
.record .record-empty {
    color: var(--fg-2);
    font-style: italic;
}

.footer {
    width: 100%;
    padding: 2em;
}

/* Mobile ----------------------------------------------------------------- *
 *
 * Body does not shrink below 17px, and ui and meta get *larger* than the web
 * column -- the phone is read in sunlight, at arm's length, by someone
 * standing over a bike (§2).
 */
@media (max-width: 1023px) {
    :root {
        --page-pad: var(--space-6);
    }
}

@media (max-width: 767px) {
    :root {
        --page-pad: 20px;
        --size-display: 40px;
        --size-h1: 28px;
        --size-h2: 22px;
        --size-h3: 17px;
        --size-ui: 16px;
        --size-meta: 14px;
        --leading-display: 1.1;
        --leading-h1: 1.2;
        --leading-h2: 1.25;
        --leading-body: 1.55;
    }

    .scribble {
        font-size: 32px;
    }

    /* Primary actions get the full row width (§4). */
    .actions .button-primary {
        width: 100%;
    }
}

@media (prefers-reduced-motion: reduce) {
    * {
        transition: none !important;
        animation: none !important;
    }
}

/* Dark mode -- the path, not the spec (§1) ------------------------------- *
 *
 * Not shipping in v1, and deliberately not wired to prefers-color-scheme: the
 * design system marks these values "unverified against AA -- audit before P4",
 * and a palette that has not been checked in glare is worse than none on a
 * page read outdoors. When the audit lands, this becomes a block that
 * redefines the six tokens and the four chromatics that lift on ink -- no rule
 * above it needs to change.
 *
 *   --bg: #1c1610;  --surface: #2a2016;  --surface-2: #372c1f;
 *   --rule: #4a3d2c;  --fg: #f5f0e3;  --fg-2: #96907f;
 *   --clay: #c96a38;  --sage: #8d9459;  --teal: #4f9c96;  --gold: #e0b45f;
 */
