/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Theming (2026-10-02): every colour below reads an optional custom
   property whose fallback is the browser's own system colour, so with no
   theme the form keeps its plain light/dark "form" look — the default the
   organizer chose. An event re-skins it with a :root { … } block setting any
   of: --bg, --fg, --muted (secondary text: notes, counters, the language
   buttons), --field-bg, --line (box and field borders), --accent (heading,
   links, submit, outlined buttons), --accent-ink, --focus, and --radius
   (corner rounding, default 6px). build-form's theme file is that block,
   checked by nix/lib/apply-theme.py, whose list must match this one. The
   form stays visibly a form, not one of the event's own pages. */
:root { color-scheme: light dark; }
/* More contrast asked for: secondary text at full strength. html:root
   outranks a theme's :root, which is appended after this file. */
@media (prefers-contrast: more) { html:root { --muted: var(--fg, CanvasText); } }
h1 { color: var(--accent, inherit); }
a { color: var(--accent, LinkText); }
body { background: var(--bg, Canvas); color: var(--fg, CanvasText); }
input:not([type="checkbox"]):not([type="radio"]), textarea, select {
  background: var(--field-bg, Field); color: var(--fg, FieldText);
  border: 1px solid var(--line, ButtonBorder);
}
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent, auto); }
body { font-family: system-ui, sans-serif; line-height: 1.5; margin: 0; }
main { max-width: 52rem; margin: 2rem auto; padding: 0 1rem; }
h1 { margin-bottom: 0.25rem; }
/* One phone view for a guest (2026-10-02): the whole form, submit included,
   without scrolling; the role box and the optional sections may extend it. */
@media (max-width: 40rem) {
  main { margin: 1rem auto; }
  h1 { font-size: 1.6rem; margin-top: 0; }
}
.folds { display: flex; flex-wrap: wrap; column-gap: 1.25rem; }
.folds details[open] { flex-basis: 100%; }
fieldset[data-compartment="main"] { border: 0; padding: 0; margin: 0.75rem 0; }
/* The contact section's heading is read, not shown (2026-10-02): the fields
   speak for themselves, and the line it took kept the submit button below a
   phone's fold. Its counter cannot near the limit with these fields. */
fieldset[data-compartment="main"] > legend {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; padding: 0;
}
.counter[hidden] { display: none; }
/* Required marks (2026-10-02), drawn from the `required` attribute — the
   mark follows exactly what the browser enforces, conditional fields
   included. Each language half carries it, so whichever is shown has one. */
.req-note { font-size: 0.85rem; margin: 0 0 0 auto; align-self: center;
            color: var(--muted, color-mix(in srgb, currentColor 75%, transparent)); }
label:has(> :is(input:not([type="radio"]), select, textarea):required) > span[lang]::after,
.role-choice:has(input:required) > span[id$="-legend"] > span[lang]::after,
label.consent:not(:has(#commitment-box)):has(> input:required) > .consent-text > span[lang]::after,
/* The commitment's mark ends its sentence, which holds both links (2026-10-03). */
label.consent:has(> #commitment-box:required) .commit-sentence::after { content: "\a0*"; /* non-breaking: the mark never wraps alone */ }
.title-row { display: flex; flex-wrap: wrap-reverse; align-items: center;
             justify-content: space-between; column-gap: 0.75rem; }
.title-row h1 { margin: 0.25rem 0; }
.intro { opacity: 0.85; }
fieldset { border: 1px solid var(--line, currentColor); border-radius: var(--radius, 6px); margin: 1rem 0; padding: 0.75rem 1rem; }
legend { font-weight: 600; }
/* Reflow at 320px with text at 200% (WCAG 1.4.10; 2026-10-02 adversarial
   review, F6): a fieldset defaults to min-width: min-content and would not
   shrink below its widest content, and a long word or link could not break —
   the open crew box scrolled sideways to 431px. */
fieldset { min-width: 0; }
body { overflow-wrap: anywhere; }
label { display: block; margin: 0.35rem 0; }
/* A hanging indent: a wrapped choice ("Oulusta, muuten (bussilla,
   pyörällä…)") lines its second line up with its text, not under the radio
   (360px phone, 2026-10-02). */
.role-choice label { margin: 0.3rem 0; padding-left: 1.6em; text-indent: -1.6em; }
/* `:not([type="checkbox"])` is load-bearing (2026-08-27). A bare
   `input { width: 100% }` stretches the consent checkbox to the full column;
   the control itself is then drawn centred inside that width, so it appears
   on its own line above its label, detached from the sentence it gates. Found
   on a phone — the browser tier ticks it with page.check(), which is actionable
   whatever the box's width, so no headless check could have seen it. */
input:not([type="checkbox"]):not([type="radio"]), textarea, select {
  width: 100%; box-sizing: border-box; padding: 0.4rem; font: inherit;
  margin-top: 0.15rem;
}
/* 44px: a thumb target, and no taller (2026-10-02). Selects too — the day
   choices were the browser's ~20px default, under the 24px floor. */
input:not([type="checkbox"]):not([type="radio"]), select { height: 2.75rem; }
/* inline-block so the badge breaks BEFORE itself rather than through itself
   — on a phone the legend wrapped mid-counter, leaving the unit stranded on
   its own line — yet can still wrap inside when it is wider than a whole
   line (200% text at 320px; nowrap scrolled the page sideways, F6). */
.counter { font-weight: 400; font-size: 0.8rem;
           color: var(--muted, color-mix(in srgb, currentColor 70%, transparent));
           display: inline-block; max-width: 100%; }
.counter.over { color: #c00; opacity: 1; }
button { font: inherit; padding: 0.5rem 1rem; border-radius: var(--radius, 6px); cursor: pointer; }
/* Every button but submit is outlined (2026-10-02): the browser's grey
   default read as a different form, and Delete — destructive — must never
   look like the filled primary action. */
#copy-btn, #download-btn, #again-btn, #delete-btn {
  background: transparent; color: var(--fg, ButtonText);
  border: 1px solid var(--accent, ButtonBorder);
}
button:disabled { opacity: 0.5; cursor: not-allowed; }
#submit-btn { background: var(--accent, ButtonFace); color: var(--accent-ink, ButtonText);
              border: 1px solid var(--accent-ink, ButtonBorder); font-weight: 600; }
.error { color: #c00; min-height: 1.25rem; }
/* The "did not load" notice is in the HTML so a browser that cannot parse
   app.js still gets one — but on a slow link it painted before app.js
   arrived and read "did not load" over a form that was loading (2026-10-08).
   Held 4 s by a zero-length delayed animation: no script, and a browser too
   old for keyframes shows it at once, which is the one case that wants it. */
#old-browser { visibility: hidden; animation: late-notice 0s 4s forwards; }
@keyframes late-notice { to { visibility: visible; } }
.warning { border-left: 4px solid #c60; padding-left: 0.75rem; }
.token-row { display: flex; gap: 0.5rem; align-items: center; margin: 0.5rem 0; }
#token { font-size: 1.1rem; padding: 0.4rem 0.6rem; background: rgba(128,128,128,0.15); border-radius: 4px; user-select: all; }
pre { background: rgba(128,128,128,0.12); padding: 0.75rem; border-radius: 6px; overflow-x: auto; }
hr { margin: 2rem 0; opacity: 0.3; }

/* Art. 9 consent gates. `optional` and `gate-note` are the Art. 7(4) half —
   a registration without these sections is complete, and it has to LOOK it.
   `placeholder` marks deployment facts (controller, purpose, retention) that
   the build must fill: they are loud on purpose, so an unfilled one is seen
   before an attendee reads it rather than after. */
.optional { font-weight: 400; font-size: 0.8rem; text-transform: uppercase;
            letter-spacing: 0.04em; margin-left: 0.4rem;
            color: var(--muted, color-mix(in srgb, currentColor 75%, transparent));
            display: inline-block; max-width: 100%; }
/* A field's hint, under its label (the crew box's "who invited you", 2026-10-02). */
.hint { display: block; font-size: 0.85rem;
        color: var(--muted, color-mix(in srgb, currentColor 75%, transparent)); }
.gate-note { font-size: 0.9rem; margin: 0.25rem 0 0.6rem;
             color: var(--muted, color-mix(in srgb, currentColor 85%, transparent)); }
/* The checkbox sits beside the sentence and the sentence wraps as one block
   beside it, rather than flowing under the box. `align-items: flex-start`
   keeps it on the first line; the min sizes are the thumb target — the
   browser's default 13px box is well under the 24px WCAG 2.2 (2.5.8) floor,
   and this is a control an attendee taps on a phone. */
.consent { display: flex; align-items: flex-start; gap: 0.5rem;
           margin: 0 0 0.8rem; font-size: 0.9rem; }
.consent input[type="checkbox"] {
  flex: 0 0 auto; width: 1.5rem; height: 1.5rem; margin: 0.1rem 0 0;
}
/* The sentence is ONE flex item on purpose: without this wrapper every span
   inside it — each [PLACEHOLDER], the English rendering — would become a flex
   item of its own and the sentence would lay out as columns. `min-width: 0`
   lets it wrap instead of forcing the row wider than the fieldset. */
.consent-text { min-width: 0; }
/* Shown and hidden by app.js through the `hidden` attribute, not by `:has()`:
   a browser without it would never open the box for crew. Sets no `display`,
   so `hidden` wins. */
.role-box { border: 1px solid currentColor; border-radius: var(--radius, 6px);
            padding: 0.75rem 1rem; margin: 0.5rem 0 0; }
.placeholder { background: #fe6; color: #000; padding: 0 0.25rem;
               font-family: monospace; font-weight: 700; }
/* The gated fields fold away until their box is ticked (app.js sets
   `hidden`); `label { display: block }` would otherwise override it. */
fieldset[data-requires-consent] label[hidden], label.optional-toggle[hidden],
.role-box label[hidden], .role-box .hint[hidden] { display: none; }
/* Laid out like a consent row, one checkbox size for the page (2026-10-02):
   the toggle's box was the browser's small default beside the 24px ticks. */
label.optional-toggle { display: flex; align-items: flex-start; gap: 0.5rem; }
/* Its intro line sits close above it, read as one invitation (2026-10-03). */
.optional-intro { margin: 1rem 0 0.4rem; }
/* A line break in the value is kept (2026-10-04, the RSVP pilot's shape):
   the escaped text carries a newline, rendered as one; nothing else. */
.optional-intro > p { white-space: pre-line; }
/* The crew box's "Arrival" subheading (2026-10-04): a quiet divider. */
.box-subhead { font-size: 1rem; margin: 1rem 0 0.25rem; }
label.optional-toggle input[type="checkbox"] {
  flex: 0 0 auto; width: 1.5rem; height: 1.5rem; margin: 0.1rem 0 0;
}
fieldset[data-requires-consent] input:disabled,
fieldset[data-requires-consent] textarea:disabled { background: rgba(128,128,128,0.15); }
:focus-visible { outline: 2px solid var(--focus, currentColor); outline-offset: 2px; }
/* A text field draws its own border, so a ring 2px outside it read as a
   second, offset border on a phone (2026-10-02): the field's border takes
   the focus colour and the ring sits flush against it — one thick edge. */
input:not([type="checkbox"]):not([type="radio"]):focus-visible, textarea:focus-visible {
  outline-offset: 0; border-color: var(--focus, currentColor);
}

/* Bilingual page (2026-08-27; layout 2026-10-02): Finnish is operative,
   English accompanies it, both always shown, never hidden behind a toggle.
   Short strings read as one line, "Nimi / Name", at equal weight — dimming a
   single word cost readability and saved nothing. Prose stands as two whole
   parallel blocks (.bi): side by side on a wide screen, Finnish then English
   under a left rule on a phone; never alternating sentence by sentence.
   `lang="en"` is what makes assistive technology switch voice (WCAG 3.1.2). */
.bi { display: grid; gap: 0.6rem; }
.bi > [lang="en"] { padding-left: 0.9rem;
                    border-left: 1px solid var(--line, rgba(128, 128, 128, 0.45)); }
.bi > * { margin: 0; min-width: 0; }
@media (min-width: 46rem) {
  .bi { grid-template-columns: 1fr 1fr; gap: 0 1.75rem; }
  .jump { display: none; }   /* the English column is already in view */
}
.jump { margin: 0 0 0.25rem; font-size: 0.9rem; }

/* Language choice (trial, 2026-10-02). With a language chosen, the other one
   is hidden and every parallel block collapses to one column, the consent
   sentences and the commitment tick included (trial, not decided). The
   chosen button is filled, not only recoloured. */
/* Small and centred (2026-10-02). A language code, not a flag (2026-10-04:
   a flag stands for a country): a visual tag, aria-hidden, so the accessible
   names stay "Suomeksi" / "In English". The label's width keeps the tap
   target above WCAG 2.5.8's 24×24 px. */
nav.langs { display: flex; gap: 0.5rem; justify-content: center; margin: 0 0 0.75rem; }
nav.langs .lang-code { font-weight: 700; }
/* On the title row: small, right-aligned, still 24px tall (WCAG 2.5.8). */
.title-row nav.langs { margin: 0 0 0 auto; gap: 0.3rem; }
.title-row nav.langs button { font-size: 0.7rem; letter-spacing: 0.06em; padding: 0.15rem 0.45rem; }
nav.langs button { font-size: 0.8rem; letter-spacing: 0.12em; padding: 0.3rem 0.7rem;
                   background: none; color: var(--muted, inherit);
                   border: 1px solid var(--line, currentColor);
                   border-radius: var(--radius, 4px); min-height: 24px; }
nav.langs[hidden] { display: none; }
nav.langs button[aria-pressed="true"] {
  color: var(--fg, CanvasText); font-weight: 600; border-color: var(--accent, CanvasText);
  background: color-mix(in srgb, var(--accent, CanvasText) 42%, var(--bg, Canvas));
}
html[data-show="fi"] [lang="en"]:not(button),
html[data-show="en"] [lang="fi"]:not(button),
html[data-show] .sep, html[data-show] .jump { display: none; }
html[data-show] .bi { grid-template-columns: 1fr; }
html[data-show] .bi > [lang] { padding-left: 0; border-left: 0; }

/* Read by screen readers, not shown: says a link opens a new tab (WCAG
   G201), beside the visible ↗. */
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden;
      clip-path: inset(50%); white-space: nowrap; }
.lang-note { font-size: 0.9rem; border-left: 4px solid currentColor;
             padding-left: 0.75rem; opacity: 0.9; }
.subtitle { margin-top: 0; opacity: 0.85; }

/* Printing the receipt must print the TOKEN — never the Art. 9 echo. */
@media print {
  .intro, #privacy-notice, #reg-form, #delete-section, hr,
  #submitted, #again-btn, #copy-btn, #download-btn,
  #receipt h3 { display: none !important; }
}

/* The guest's phone view again holds the submit (2026-10-04, the organizer:
   tighter layout, legal texts unchanged). Last in the file so it wins over
   the rules above; measured at 360×640, see the commit. */
@media (max-width: 40rem) {
  body { line-height: 1.4; }
  main { margin: 0.25rem auto; }
  .title-row h1 { margin: 0; }
  label { margin: 0.2rem 0; }
  fieldset[data-compartment="main"] { margin: 0.5rem 0; }
  .optional-intro { margin: 0.5rem 0 0.2rem; font-size: 0.9rem; }
  label.optional-toggle { font-size: 0.9rem; }
  .consent { margin-bottom: 0.5rem; }
}

/* The event's theme (build-form's theme file), checked by apply-theme.py. */
:root {
  color-scheme: dark;
  --bg: #000;
  --fg: #cfe8c8;
  --muted: #829f7b;
  --field-bg: #000;
  --line: #829f7b;
  --accent: #3dff2f;
  --accent-ink: #000;
  --focus: #3dff2f;
  --radius: 0;
}
