/* Ultimate Naturabuy — the design system, from the 2026 brandbook.
 *
 * Three colour families, because the brand has three:
 *   generic  browns  — the brand speaking as itself
 *   chasse   greens  — hunting
 *   peche    blues   — fishing
 *
 * An app picks one with THEME and everything follows. The hex values are the
 * brandbook's own; do not invent new ones — the book gives Pantone, CMYK, RGB
 * and RAL for each, and a fourth value that only exists in CSS would be the one
 * that is wrong on a printed poster.
 *
 * Public Sans is the brand typeface. It is served from static/fonts as a
 * @font-face rather than from Google's CDN: these apps sit behind an
 * authenticating ingress, and a webfont request that leaves the network leaks
 * which app an authenticated user is looking at.
 */

:root {
  /* Générique — brown. The default. */
  --generic-deep:  #281e1e;
  --generic-mid:   #3c3228;
  --generic-light: #bdbaad;

  /* Chasse — green. */
  --chasse-deep:   #002B1B;
  --chasse-mid:    #183f33;
  --chasse-light:  #a3c4ae;

  /* Pêche — blue. */
  --peche-deep:    #0D2140;
  --peche-mid:     #1f314e;
  --peche-light:   #b1cddd;

  /* Semantic colours are separate from the brand families on purpose: "this
   * failed" must not change meaning because an app switched theme. */
  --ok:    #1f7a4d;
  --warn:  #9a6b12;
  --bad:   #a03027;

  /* Chart series. A fixed order, assigned to an entity and never cycled, so a
   * filter that drops a series does not repaint the survivors.
   *
   * NOT the brand families. Those are three near-blacks and three pale tints —
   * right for chrome, indistinguishable as five adjacent series. These are
   * stepped to mid-lightness and validated rather than chosen by eye: every
   * adjacent pair clears the colour-vision-deficiency threshold (worst ΔE 9.3
   * protan) and the normal-vision floor (worst 19.0), at 3:1 against the
   * surface, in both light and dark. Changing one means re-running
   * scripts/validate_palette.js, not squinting at it.
   *
   * Distinct from --ok/--warn/--bad by construction: a status colour means a
   * state and must never also mean "the third app". */
  --series-1: #3a6fb5;   /* blue       */
  --series-2: #b8791a;   /* ochre      */
  --series-3: #0f8f74;   /* green      */
  --series-4: #8b4fb0;   /* violet     */
  --series-5: #a8543a;   /* terracotta */

  /* Spacing and radius, on a scale rather than ad hoc. */
  --s1: .25rem; --s2: .5rem; --s3: .75rem; --s4: 1rem;
  --s5: 1.5rem; --s6: 2rem; --s7: 3rem;
  --r:  12px; --r-sm: 7px; --r-pill: 999px;
  --side-w: 15.5rem;

  /* Elevation and motion. Two shadows, because there are two heights: resting
   * on the page, and floating above it (a dropdown, the drawer). One duration,
   * short enough that nothing waits for it. */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .05);
  --shadow-2: 0 10px 30px rgba(0, 0, 0, .16), 0 1px 3px rgba(0, 0, 0, .08);
  --ease: .14s ease;

  /* Tells the browser both schemes are handled, so what it draws itself — the
   * file picker's button, a select's list, the scrollbars — follows the page
   * instead of staying white on a dark one. */
  color-scheme: light dark;
}
[data-mode="light"] { color-scheme: light; }
[data-mode="dark"]  { color-scheme: dark; }

/* The theme maps a family onto roles. Every component below reads the roles,
 * never a family directly — which is what makes a theme a one-line change. */
:root, [data-theme="generic"] {
  --deep: var(--generic-deep);  --mid: var(--generic-mid);
  --tint: var(--generic-light);
  --paper: #faf9f7;  --surface: #ffffff;  --line: #e5e1da;
  --ink: var(--generic-deep);  --ink-soft: #6b6156;
  --accent: var(--generic-mid); --accent-ink: #ffffff;
}
[data-theme="chasse"] {
  --deep: var(--chasse-deep);   --mid: var(--chasse-mid);
  --tint: var(--chasse-light);
  --paper: #f7faf8;  --surface: #ffffff;  --line: #dee8e1;
  --ink: var(--chasse-deep);   --ink-soft: #4a5f55;
  --accent: var(--chasse-mid);  --accent-ink: #ffffff;
}
[data-theme="peche"] {
  --deep: var(--peche-deep);    --mid: var(--peche-mid);
  --tint: var(--peche-light);
  --paper: #f7f9fb;  --surface: #ffffff;  --line: #dde5ec;
  --ink: var(--peche-deep);    --ink-soft: #4a5768;
  --accent: var(--peche-mid);   --accent-ink: #ffffff;
}

/* The side menu's ground: the family's light tint, washed into the paper. It is
 * the one large surface that says which app this is — brown, green or blue —
 * without a slab of near-black down the screen. Until now the three light
 * tints were only ever used in dark; in light every app was the same white
 * page under a dark strip. Declared on the root, where --tint is, so one rule
 * serves the three themes. */
:root {
  --side:       color-mix(in srgb, var(--tint) 30%, var(--paper));
  --side-hover: color-mix(in srgb, var(--tint) 58%, var(--paper));
  --side-line:  color-mix(in srgb, var(--tint) 62%, var(--paper));
  --side-on:    var(--deep);
  --side-on-ink: #fff;
  --head:       color-mix(in srgb, var(--tint) 20%, var(--surface));
}

/* Dark: the deep brand colour becomes the ground and the light tint becomes the
 * ink, so the palette stays the brand's rather than becoming grey. Handles all
 * three viewer states — explicit light, explicit dark, and the default where
 * only prefers-color-scheme speaks. */
@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) {
    --paper: var(--deep);
    --surface: color-mix(in srgb, var(--deep) 88%, white);
    --line: color-mix(in srgb, var(--deep) 72%, white);
    --ink: #f4f2ee;
    --ink-soft: var(--tint);
    --accent: var(--tint);
    --accent-ink: var(--deep);
    /* Re-stepped for the dark surface, not flipped: the same hues, validated
     * again against it. */
    --series-1: #5f93d8;
    --series-2: #bd8629;
    --series-3: #1f9779;
    --series-4: #ad73d0;
    --series-5: #cc7259;
    /* The status colours too. The light ones are dark enough to read on white,
     * which makes them unreadable here: "actif" in #1f7a4d on the deep brown
     * was 2.1:1. Same hues, lifted until each clears 4.5:1 on the surface. */
    --ok: #6cc798;  --warn: #e0b04f;  --bad: #f0958b;
    --shadow-2: 0 10px 30px rgba(0, 0, 0, .5), 0 1px 3px rgba(0, 0, 0, .3);
    --side:       color-mix(in srgb, var(--deep) 82%, black);
    --side-hover: color-mix(in srgb, var(--deep) 84%, white);
    --side-line:  color-mix(in srgb, var(--deep) 80%, white);
    --side-on:    var(--tint);
    --side-on-ink: var(--deep);
    --head:       color-mix(in srgb, var(--deep) 80%, white);
  }
}
[data-mode="dark"] {
  /* Re-stepped for the dark surface, not flipped: the same hues, validated
   * again against it. */
  --series-1: #5f93d8;
  --series-2: #bd8629;
  --series-3: #1f9779;
  --series-4: #ad73d0;
  --series-5: #cc7259;
  --paper: var(--deep);
  --surface: color-mix(in srgb, var(--deep) 88%, white);
  --line: color-mix(in srgb, var(--deep) 72%, white);
  --ink: #f4f2ee;
  --ink-soft: var(--tint);
  --accent: var(--tint);
  --accent-ink: var(--deep);
  /* The status colours too. The light ones are dark enough to read on white,
   * which makes them unreadable here: "actif" in #1f7a4d on the deep brown
   * was 2.1:1. Same hues, lifted until each clears 4.5:1 on the surface. */
  --ok: #6cc798;  --warn: #e0b04f;  --bad: #f0958b;
  --shadow-2: 0 10px 30px rgba(0, 0, 0, .5), 0 1px 3px rgba(0, 0, 0, .3);
  --side:       color-mix(in srgb, var(--deep) 82%, black);
  --side-hover: color-mix(in srgb, var(--deep) 84%, white);
  --side-line:  color-mix(in srgb, var(--deep) 80%, white);
  --side-on:    var(--tint);
  --side-on-ink: var(--deep);
  --head:       color-mix(in srgb, var(--deep) 80%, white);
}

/* ── Type ─────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: "Public Sans";
  /* The variable TTF straight from Google Fonts' OFL release, served from here
   * rather than from their CDN. It is ~100 KB and compresses again over the
   * wire; a woff2 would be smaller, so if you have woff2_compress to hand,
   * convert it and add the url() above this one. */
  src: url("/static/fonts/PublicSans-Variable.ttf") format("truetype-variations");
  font-weight: 100 900;
  font-display: swap;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: "Public Sans", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  font-size: 16px; line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
/* Figures of one width where they are compared down a column, and only there:
 * a `.num` cell, a stat. It used to be on body, which spaced every number in
 * running text — and every date — as if it were a column of amounts: "120
 * jours" read "1 2 0 jours". `.tnum` asks for it anywhere else digits must
 * line up. */
.num, .tnum, .stat b, .code-input { font-variant-numeric: tabular-nums; }
h1, h2, h3 { margin: 0 0 var(--s2); line-height: 1.2; letter-spacing: -.015em;
             font-weight: 620; text-wrap: balance; }
h1 { font-size: clamp(1.5rem, 3.2vw, 2rem); }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }
p  { margin: 0 0 var(--s4); max-width: 68ch; }
a  { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px;
     text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
     transition: text-decoration-color var(--ease); }
a:hover { text-decoration-color: currentColor; }
code { background: color-mix(in srgb, var(--ink) 6%, transparent);
       padding: .08em .3em; border-radius: 4px; }
::selection { background: color-mix(in srgb, var(--accent) 24%, transparent); }
code, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88em; }
.muted { color: var(--ink-soft); }
.small { font-size: .85rem; }
.eyebrow { font-size: .7rem; letter-spacing: .12em; text-transform: uppercase;
           color: var(--ink-soft); font-weight: 600; }

/* ── Chrome ───────────────────────────────────────────────────────────────── */
/* For a keyboard: straight past the menu to the page. Off-screen until it has
 * focus. */
.skip { position: absolute; left: var(--s2); top: -4rem; z-index: 60;
        padding: var(--s2) var(--s3); border-radius: var(--r-sm);
        background: var(--surface); color: var(--ink); font-size: .9rem; }
.skip:focus { top: var(--s2); box-shadow: var(--shadow-2); }

/* ── The menu ───────────────────────────────────────────────────────────────
 *
 * One menu, at the side, with every page in sight. A section's sub-pages sit
 * under its name: nothing opens on hover, so nothing closes under a pointer
 * that strayed, and a touch screen and a keyboard get the same menu as a mouse.
 * Below 1100px the same element is a drawer under a slim bar: narrower than
 * that, a table has more use for the width than the menu does.
 *
 * No JavaScript. The drawer is a checkbox and its label. A menu that needs a
 * script is a menu that is missing when the script fails.
 */
.side {
  position: fixed; top: 0; bottom: 0; left: 0; z-index: 41;
  width: var(--side-w); overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column;
  padding: calc(var(--s3) + env(safe-area-inset-top, 0px)) var(--s3)
           calc(var(--s3) + env(safe-area-inset-bottom, 0px));
  background: var(--side); border-right: 1px solid var(--side-line);
}
/* The page makes room for it with padding on the body, so .shell's own
 * `margin: 0 auto` still centres the page in what is left. */
.has-side { padding-left: var(--side-w); }

/* Which app this is: the mark on the family's deep colour, the slug, and whose
 * it is. The one place the deep colour stands as a block. */
.side-brand { display: flex; align-items: center; gap: .7rem;
              padding: var(--s1) var(--s2); margin-bottom: var(--s4);
              color: var(--ink); text-decoration: none; border-radius: var(--r-sm); }
.side-brand svg { width: 2.4rem; height: 2.4rem; padding: .32rem; flex: none;
                  border-radius: 10px; background: var(--deep); color: var(--tint);
                  box-shadow: 0 0 0 1px rgba(255, 255, 255, .08); }
.side-brand b { display: block; font-size: 1.02rem; font-weight: 700;
                letter-spacing: -.01em; line-height: 1.2; }
.side-brand small { display: block; font-size: .64rem; letter-spacing: .1em;
                    text-transform: uppercase; color: var(--ink-soft);
                    white-space: nowrap; }

.side nav { display: flex; flex-direction: column; gap: 2px; }
/* Any link in the menu, not a class of them: an app that fills the nav block
 * with its own links gets the same entries without knowing their names. */
.side nav a {
  display: flex; align-items: center; gap: .4rem;
  min-height: 36px; padding: .32rem .75rem; border-radius: var(--r-sm);
  font-size: .93rem; line-height: 1.3; color: var(--ink); text-decoration: none;
  transition: background var(--ease), color var(--ease);
}
.side nav a:hover { background: var(--side-hover); }
/* Where you are: the deep colour, the only filled entry in the column. */
.side nav a.on { background: var(--side-on); color: var(--side-on-ink);
                 font-weight: 600; }
.side nav a .out { opacity: .55; font-size: .85em; }
.side nav a:focus-visible { outline-offset: -2px; }
.side .heading { font-size: .68rem; letter-spacing: .12em; text-transform: uppercase;
                 color: var(--ink-soft); font-weight: 700;
                 padding: var(--s4) .75rem var(--s1); }
.side nav > .heading:first-child { padding-top: var(--s1); }
.side nav hr { border: 0; border-top: 1px solid var(--side-line);
               margin: var(--s2) .75rem; width: auto; }

/* The bottom of the column: what leaves the app, what an admin has, and who
 * is signed in. Pushed down, so the work stays at the top. */
.side .foot { margin-top: auto; padding-top: var(--s5); }
.side .who { display: flex; align-items: center; gap: .6rem;
             margin-top: var(--s3); padding: var(--s3) var(--s2) var(--s1);
             border-top: 1px solid var(--side-line); }
.side .avatar { width: 2rem; height: 2rem; flex: none; border-radius: 50%;
                display: grid; place-items: center;
                background: var(--deep); color: var(--tint);
                font-weight: 700; font-size: .85rem; text-transform: uppercase; }
.side .id { min-width: 0; flex: 1; line-height: 1.25; }
.side .id b { display: block; font-size: .85rem; font-weight: 600;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An address, so not in capitals: nobody recognises their own in them. */
.side .id small { display: block; font-size: .7rem; color: var(--ink-soft);
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The small screen's bar: the way into the drawer, and the app's name. */
.mobilebar { display: none; align-items: center; gap: var(--s2);
             min-height: 3.25rem; padding: var(--s1) var(--s3);
             padding-top: calc(var(--s1) + env(safe-area-inset-top, 0px));
             background: var(--deep); color: #fff; }
.mobilebar .brand { color: #fff; text-decoration: none; font-weight: 700;
                    letter-spacing: -.01em; }
.mobilebar a:focus-visible { outline-color: #fff; }

/* The drawer. Hidden checkbox, visible label: no script, and the panel comes
 * from the left because that is the thumb that is free while the other holds the
 * phone. */
.drawer-toggle { position: absolute; opacity: 0; pointer-events: none; }
/* The checkbox is what a keyboard reaches, and it is invisible: show its focus
 * on the label that stands for it. */
.drawer-toggle:focus-visible ~ .mobilebar .drawer-open { outline: 2px solid #fff;
                                                         outline-offset: -2px; }
.drawer-open {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin: 0 0 0 -.4rem; border-radius: var(--r-sm);
  cursor: pointer; color: #fff;
}
.drawer-open:hover { background: rgba(255, 255, 255, .12); }
.drawer-open svg { width: 20px; height: 20px; }
.drawer-scrim { display: none; margin: 0; position: fixed; inset: 0; z-index: 40;
                background: rgba(0, 0, 0, .45);
                visibility: hidden; opacity: 0;
                transition: opacity .2s ease, visibility 0s .2s; }
.drawer-close { display: none; position: absolute; margin: 0;
                top: calc(var(--s3) + env(safe-area-inset-top, 0px));
                right: var(--s2); width: 40px; height: 40px;
                place-items: center; border-radius: var(--r-sm); cursor: pointer;
                color: var(--ink-soft); font-size: 1.4rem; line-height: 1; }

/* Below this width the menu leaves the page and waits behind the bar. It is
 * laid out all the time and moved in, rather than switched from display:none,
 * so it slides; visibility takes it out of the tab order while closed. */
@media (max-width: 1099px) {
  .has-side { padding-left: 0; }
  .mobilebar { display: flex; }
  .drawer-scrim { display: block; }
  .drawer-close { display: grid; }
  .side { width: min(19rem, 86vw); box-shadow: 4px 0 24px rgba(0, 0, 0, .18);
          visibility: hidden; transform: translateX(-100%);
          transition: transform .2s ease, visibility 0s .2s; }
  /* Touched, not pointed at. */
  .side nav a { min-height: 44px; font-size: 1rem; }
  #drawer:checked ~ .drawer-scrim { visibility: visible; opacity: 1;
                                    transition-delay: 0s; }
  #drawer:checked ~ .side { visibility: visible; transform: none;
                            transition-delay: 0s; }
  /* The page behind stays where it was while the drawer is open. */
  html:has(#drawer:checked) { overflow: hidden; }
}

/* Every page's heading, from base.html. One format, so a page cannot invent its
 * own spacing above the first thing it has to say. */
.page { margin-bottom: var(--s5); padding-bottom: var(--s4);
        border-bottom: 1px solid var(--line);
        display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5);
        align-items: flex-end; justify-content: space-between; }
.page > div:first-child { min-width: 0; flex: 1 1 24rem; }
.page .actions { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; }
/* A section heading inside a page takes its distance from what is above it
 * here, once, instead of from a style attribute on each one. */
.shell > h2 { margin-top: var(--s6); margin-bottom: var(--s3); }
.shell > h2:first-child, .page + h2 { margin-top: 0; }
.page h1 { font-size: clamp(1.6rem, 3.4vw, 2.2rem); font-weight: 750;
           letter-spacing: -.03em; margin: 0 0 var(--s1); }
.page h1:last-child { margin-bottom: 0; }
.page .eyebrow { margin-bottom: .2rem; }
.page .lede { font-size: 1.02rem; color: var(--ink-soft); max-width: 62ch;
              margin: 0; }

.shell { max-width: 72rem; margin: 0 auto;
         padding: var(--s6) clamp(var(--s4), 3vw, var(--s6)) var(--s7); }
/* 72rem is a measure for reading, and it is the right default: prose past it is
 * hard to track from the end of one line to the start of the next. A wide table
 * is not read that way, it is scanned across, and inside a 72rem column it
 * scrolls sideways while the screen next to it sits empty. Such a page asks for
 * `shell wide` and gets the screen it is actually on. Reach for it only when the
 * content really is a wide table — on prose it is the wrong measure. */
.shell.wide { max-width: 100rem; }

/* ── Components ───────────────────────────────────────────────────────────── */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); padding: var(--s4);
  box-shadow: var(--shadow-1);
}
/* A card whose content goes to its edges — a table, mostly. It was
 * style="padding:0" on every one of them. */
.card.flush { padding: 0; overflow: hidden; }
.card.flush.scroller { overflow-x: auto; }
.card > :last-child { margin-bottom: 0; }
.stack { display: flex; flex-direction: column; gap: var(--s3); }
.row   { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }
.grid  { display: grid; gap: var(--s3);
         grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 44px; padding: var(--s2) var(--s4);
  background: var(--accent); color: var(--accent-ink);
  border: 1px solid transparent; border-radius: var(--r-sm);
  font: inherit; font-weight: 600; font-size: .95rem; line-height: 1.2;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background var(--ease), border-color var(--ease),
              filter var(--ease), transform var(--ease);
}
.btn:hover { filter: brightness(1.12); }
.btn:active { transform: translateY(1px); }
.btn.ghost { background: transparent; color: var(--ink);
             border-color: color-mix(in srgb, var(--ink) 22%, var(--line)); }
/* brightness() on a transparent button brightens nothing: the ghost had no
 * hover at all. */
.btn.ghost:hover { filter: none;
                   background: color-mix(in srgb, var(--ink) 7%, transparent); }
/* The size for a row of a table, where 44px would make every row a banner. It
 * was the same three declarations in a style attribute on each of them, at two
 * different heights. */
.btn.sm { min-height: 32px; padding: 0 .7rem; font-size: .8rem; }
/* For what cannot be undone. Quiet until approached — a row of red buttons
 * down a table is an alarm that never stops — and unmistakable under the
 * pointer. */
.btn.danger { background: transparent; color: var(--bad);
              border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
.btn.danger:hover { filter: none; background: var(--bad); color: #fff;
                    border-color: var(--bad); }
.btn.wide  { width: 100%; }
.btn[disabled] { opacity: .5; cursor: not-allowed; filter: none; transform: none; }

label { display: block; font-size: .85rem; color: var(--ink-soft);
        margin-bottom: var(--s1); font-weight: 500; }
/* Every field that takes text, named by what it is not. The list used to be
 * four types, so an <input> with no type attribute — which is a text field, and
 * is how half the forms here are written — got the browser's own 1990s box:
 * the role editor on /users, the key and value on /config, the login code.
 * :where() keeps this at the weight of a bare `input`, so a class still wins. */
input:where(:not([type=checkbox], [type=radio], [type=file], [type=range],
                 [type=color], [type=hidden], [type=submit], [type=button],
                 [type=reset], [type=image])),
select, textarea {
  width: 100%; min-height: 44px; padding: var(--s2) var(--s3);
  background: var(--surface); color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--ink) 22%, var(--line));
  border-radius: var(--r-sm);
  /* 16px so iOS does not zoom the page on focus. */
  font: inherit; font-size: 16px;
  transition: border-color var(--ease), box-shadow var(--ease);
}
textarea { line-height: 1.45; resize: vertical; }
::placeholder { color: color-mix(in srgb, var(--ink-soft) 75%, transparent); opacity: 1; }
input:where(:not([type=checkbox], [type=radio], [type=range])):hover,
select:hover, textarea:hover { border-color: color-mix(in srgb, var(--ink) 40%, var(--line)); }
input:disabled, select:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; }

/* The select's own arrow differs on every system and ignores the theme. Two
 * gradients draw one that follows the ink. */
select {
  appearance: none; -webkit-appearance: none; padding-right: 2.2rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%),
    linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
  background-position: calc(100% - 1.1rem) 50%, calc(100% - .8rem) 50%;
  background-size: .3rem .3rem; background-repeat: no-repeat;
}
select[multiple] { background-image: none; padding-right: var(--s3); }

/* Ticked in the brand's colour rather than the system's blue. */
input[type=checkbox], input[type=radio] {
  accent-color: var(--accent); width: 1.1rem; height: 1.1rem;
  margin: 0; vertical-align: -.18em; cursor: pointer;
}
/* A label that holds its own box reads as one line, in ink: it is the choice
 * itself, not the caption of a field. */
label:has(> input[type=checkbox]), label:has(> input[type=radio]) {
  display: inline-flex; align-items: center; gap: var(--s2);
  color: var(--ink); cursor: pointer; font-size: .92rem;
}
/* "activé" / "désactivé" beside a switch, following the box as it is ticked
 * rather than saying what was true when the page was sent. No script: two
 * spans, one shown. */
.when-on, input:checked ~ .when-off { display: none; }
input:checked ~ .when-on { display: inline; }

input[type=file] { font: inherit; font-size: .9rem; color: var(--ink-soft);
                   max-width: 100%; }
input[type=file]::file-selector-button {
  font: inherit; font-weight: 600; font-size: .85rem;
  min-height: 36px; padding: 0 .8rem; margin-right: var(--s3);
  background: transparent; color: var(--ink); cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--ink) 22%, var(--line));
  border-radius: var(--r-sm);
}
input[type=file]::file-selector-button:hover {
  background: color-mix(in srgb, var(--ink) 7%, transparent);
}

/* A field in a table's row is as tall as the row's button, not as a form's. */
td :where(input:not([type=checkbox], [type=radio], [type=file]), select) {
  min-height: 34px; padding: .3rem .6rem;
}
@media (hover: hover) and (pointer: fine) {
  /* Smaller only where there is a mouse: on a phone 16px is what stops the
     zoom. */
  td :where(input:not([type=checkbox], [type=radio], [type=file]), select) { font-size: .9rem; }
}

/* One focus ring for everything that takes focus. On a field the border takes
 * the colour too, so the ring reads as the field's own edge and not as a second
 * box around it. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
input:where(:not([type=checkbox], [type=radio], [type=file], [type=range])):focus-visible,
select:focus-visible, textarea:focus-visible {
  outline-offset: 0; border-color: var(--accent);
}
/* The login code: wide, spaced, and big enough to read back while typing. */
/* The one-time code. The first version was 1.6rem with .35em of tracking and a
 * fixed padding, which on a 360px screen pushed six digits past the right edge —
 * the field was unusable on exactly the device most people read the mail on.
 * Both the size and the tracking scale with the viewport now, and the padding is
 * asymmetric because tracking is applied after the last character too and would
 * otherwise sit the text off-centre. */
.code-input {
  font-family: ui-monospace, Menlo, monospace;
  font-size: clamp(1.15rem, 7vw, 1.6rem);
  letter-spacing: clamp(.12em, 3vw, .3em);
  text-align: center;
  padding: var(--s3) var(--s2) var(--s3) calc(var(--s2) + .3em);
  min-height: 56px; width: 100%; font-weight: 600;
}

.pill { display: inline-flex; align-items: center; gap: .35rem;
        font-size: .74rem; font-weight: 550; line-height: 1.5;
        padding: .08rem .55rem; white-space: nowrap;
        border-radius: var(--r-pill); border: 1px solid var(--line);
        background: color-mix(in srgb, var(--ink) 4%, transparent);
        color: var(--ink-soft); }
/* A state is a tint and a dot as well as a colour of text, so a column of them
 * is read at a glance, and by somebody who does not tell red from green. The
 * text is the status colour pulled a fifth of the way to the ink: on its own
 * tint the pure colour fell just under 4.5:1; this clears it in every theme,
 * light and dark (worst 4.7). */
.pill.ok, .pill.warn, .pill.bad { --c: var(--ink-soft);
  color: color-mix(in srgb, var(--c) 78%, var(--ink));
  background: color-mix(in srgb, var(--c) 12%, transparent);
  border-color: color-mix(in srgb, var(--c) 35%, transparent); }
.pill.ok   { --c: var(--ok); }
.pill.warn { --c: var(--warn); }
.pill.bad  { --c: var(--bad); }
.pill.ok::before, .pill.warn::before, .pill.bad::before {
  content: ""; width: .42rem; height: .42rem; border-radius: 50%;
  background: var(--c); flex: none; }

/* What just happened. A white box with a 3px edge was the same box three
 * times, told apart by that edge alone; the ground says it now. */
.flash { --c: var(--ink-soft);
         padding: var(--s3) var(--s4); border-radius: var(--r-sm);
         border: 1px solid color-mix(in srgb, var(--c) 30%, transparent);
         border-left: 3px solid var(--c);
         background: color-mix(in srgb, var(--c) 9%, var(--surface));
         margin-bottom: var(--s3); font-size: .92rem; }
.flash.error { --c: var(--bad); }
.flash.ok    { --c: var(--ok); }
.flash > :last-child { margin-bottom: 0; }

/* A stat tile: one number, its label, and nothing competing with it. Used where
 * a chart would be a worse answer than a figure. */
.stat { display: flex; flex-direction: column; gap: .15rem; }
.stat b { font-size: clamp(1.5rem, 4vw, 2rem); font-weight: 650; line-height: 1;
          letter-spacing: -.02em; }
.stat span { font-size: .78rem; color: var(--ink-soft); }
.stat.bad b  { color: var(--bad); }
.stat.warn b { color: var(--warn); }
.stat.ok b   { color: var(--ok); }
/* A row of them. auto-fit, not .grid's auto-fill at 17rem: four figures in a
 * 72rem page came out as three and an orphan underneath. */
.stats { display: grid; gap: var(--s4) var(--s5);
         grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); }

/* A meter. Thin, with the fill anchored to the start and rounded only at the
 * data end, so the bar's length is the only thing carrying the value. */
.bar { height: 6px; border-radius: var(--r-pill); background: var(--line);
       overflow: hidden; min-width: 3rem; }
.bar > i { display: block; height: 100%; background: var(--accent);
           border-radius: 0 var(--r-pill) var(--r-pill) 0; }
.bar > i.hot  { background: var(--warn); }
.bar > i.crit { background: var(--bad); }

/* A standing banner, not a flash: a flash says what just happened and goes away
 * on the next request; a banner says something true about the whole page. */
.banner { padding: var(--s2) var(--s4); display: flex; flex-wrap: wrap;
          gap: var(--s2); align-items: baseline; font-size: .88rem;
          border-radius: var(--r-sm); margin-bottom: var(--s3);
          background: color-mix(in srgb, var(--ink-soft) 10%, var(--surface));
          border: 1px solid var(--line); }
.banner.warn { background: color-mix(in srgb, var(--warn) 14%, var(--surface));
               border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.banner.bad  { background: color-mix(in srgb, var(--bad) 13%, var(--surface));
               border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
.banner.ok   { background: color-mix(in srgb, var(--ok) 12%, var(--surface));
               border-color: color-mix(in srgb, var(--ok) 40%, transparent); }

.scroller { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* A table wider than its card is cut off at the edge with nothing to say there
 * is more. A shadow at whichever side still has content says it: the two
 * shadows stay put, and two covers of the surface's own colour scroll with the
 * content and hide each one once its end is reached. Only on a card, whose
 * colour the covers can be sure of. */
.card.scroller {
  background-image:
    linear-gradient(to right, var(--surface) 40%, transparent),
    linear-gradient(to left,  var(--surface) 40%, transparent),
    linear-gradient(to right, rgba(0, 0, 0, .13), transparent),
    linear-gradient(to left,  rgba(0, 0, 0, .13), transparent);
  background-position: left, right, left, right;
  background-size: 2.5rem 100%, 2.5rem 100%, .9rem 100%, .9rem 100%;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
}
table { border-collapse: collapse; width: 100%; font-size: .9rem; }
th, td { text-align: left; padding: .6rem var(--s3);
         border-bottom: 1px solid var(--line); vertical-align: middle; }
th { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
     color: var(--ink-soft); font-weight: 600; white-space: nowrap; }
/* The head of a table on the family's tint: the row that is not data, told
 * apart by its ground rather than by one more rule. */
thead th { background: var(--head); padding-top: .7rem; padding-bottom: .7rem;
           border-bottom-color: color-mix(in srgb, var(--ink) 14%, var(--line)); }
td.num, th.num { text-align: right; }
/* The row under the pointer, so the eye keeps its line across a wide table. */
tbody tr { transition: background var(--ease); }
tbody tr:hover { background: color-mix(in srgb, var(--ink) 3.5%, transparent); }
/* In a card the last row's rule doubled the card's own edge. */
.card > table > tbody > tr:last-child > *,
.card > table > tr:last-child > * { border-bottom: 0; }

/* Nothing here yet. Said in a box of its own shape — dashed, centred — rather
 * than in a card, which looks like content that failed to load. */
.empty { padding: var(--s6) var(--s4); text-align: center;
         border: 1px dashed color-mix(in srgb, var(--ink) 22%, var(--line));
         border-radius: var(--r); color: var(--ink-soft); font-size: .92rem; }
.empty > :last-child { margin-bottom: 0; }
.empty p { margin-left: auto; margin-right: auto; }

/* An error page: the code, what it means, and one way out. */
.oops { max-width: 34rem; margin: var(--s7) auto; text-align: center; }
.oops .code { font-size: clamp(3.5rem, 14vw, 6rem); font-weight: 750;
              letter-spacing: -.04em; line-height: 1;
              color: color-mix(in srgb, var(--ink) 22%, transparent); }
.oops h1 { margin-top: var(--s3); }
.oops p { margin-left: auto; margin-right: auto; color: var(--ink-soft); }

/* The sign-in page, in two halves: the brand's ground — its deep colour under
 * the brandbook's contour lines — and the form. The half with the lines is the
 * page's own ::before, so login.html and verify.html say nothing about it.
 * min-height, not height: a keyboard opening on a phone shrinks the viewport and
 * a fixed height would scroll the card under it. */
.auth-wrap { min-height: 100svh; display: grid;
             grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
.auth-wrap::before { content: "";
                     background: var(--deep) url("/static/contours.svg") center / cover no-repeat; }
.auth-wrap > .flash { grid-column: 2; margin: var(--s4) var(--s5) 0; align-self: start; }
.auth-card { grid-column: 2; align-self: center; justify-self: center;
             width: 100%; max-width: 27rem; padding: var(--s6) var(--s5); }
.auth-mark { display: block; width: 3rem; height: 3rem;
             margin-bottom: var(--s5); border-radius: 12px;
             background: var(--deep); color: var(--tint); padding: .4rem;
             /* In dark the page is the deep colour too: a hairline keeps the
                tile a tile. */
             box-shadow: 0 0 0 1px rgba(255, 255, 255, .1); }
.auth-card h1 { font-size: clamp(1.7rem, 4vw, 2.2rem); font-weight: 750;
                letter-spacing: -.03em; margin-bottom: var(--s3); }
.auth-card .card { margin-top: var(--s4); padding: var(--s5);
                   box-shadow: var(--shadow-2); }
/* On a phone the ground is a band above the form, not half of a screen that
 * has no half to give. */
@media (max-width: 859px) {
  .auth-wrap { grid-template-columns: minmax(0, 1fr); grid-template-rows: 6.5rem auto 1fr; }
  .auth-wrap > .flash, .auth-card { grid-column: 1; }
  .auth-card { align-self: start; padding: var(--s5) var(--s4); }
  .auth-mark { margin-top: calc(-1 * var(--s5) - 1.5rem); margin-bottom: var(--s4);
               box-shadow: 0 0 0 4px var(--paper); }
  .auth-card .card { padding: var(--s4); }
}

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