/* saaseye.css — SE-ENG-010 (Interface Design Standard) §2's tokens, plus the
   smallest set of component classes §4 needs: field, label, button (primary
   and secondary), card, hairline, sheet, disclosure.

   A DELIBERATE, RECORDED DEVIATION (runtime/briefs/bookeye-looks-like-a-
   product.md): SE-ENG-010 §8.1 says the reference solution carries this file
   and every other solution inherits it from there. The reference solution is
   mid-migration to its own repository (SE-ADR-0027) and cannot receive it
   normally right now, so BookEye gets it first. This file is not evidence
   that BookEye owns the standard — it is meant to be lifted into
   `saaseye/reference-solution` once that migration lands, unchanged in
   substance, and inherited by every solution the normal way from then on.

   Layered OVER vendored Bootstrap (SE-ADR-0024 keeps Bootstrap; this does not
   replace it). Bootstrap still supplies the grid (`container`, `row`,
   `col-*`) and is used for that, untouched. What follows overrides only the
   rules that fight SE-ENG-010 - colour, type, spacing, radius, shadow,
   control height, focus visibility - and adds the handful of classes
   Bootstrap has no equivalent for. No new dependency, no build step, no Node
   (SE-ENG-007 §3.2): this is one hand-written stylesheet, nothing more. */

/* ---------------------------------------------------------------------------
   §2 Tokens. Every colour, type size and spacing value a BookEye template
   uses has to come from one of these - the standard's own words: "a solution
   may not define a colour, a type size or a spacing value of its own."

   2026-10-23 AMENDMENT (briefs/next-version-9-the-approved-look-booker.md,
   SE-PRD-006 0.3.0 §0): the CEO rejected this file's own 2026-09-26 palette
   on 2026-10-22 ("Same as earlier feedback. IT LOOKS TERRIBLE") and approved
   the COO's Cal.com/Skedda-styled mockup on 2026-10-23 ("This look very
   nice") - `briefs/next-version-mockup-2026-10-23.md`. SE-ENG-010 §1 itself
   says what happens when the standard and the stylesheet disagree: "this
   document is wrong and gets amended, because the stylesheet is what
   ships." The values below are the mockup's own `:root` block, lifted
   verbatim into these names rather than approximated - SE-ENG-010 §2's own
   hex literals (ground #fbfbfa, ink #1c1c1a, accent #3f6b52, caution
   #a4550a, …) are now stale prose that document still needs amending to
   match; nothing in this brief's scope is the corpus itself, so that
   amendment is for whoever owns SE-ENG-010 next, not done here.

   One deliberate departure from "same values, not approximations": the
   mockup's own `--ink-3` (#9CA3AF) is 2.5:1 against white, below §7's
   4.5:1 contrast floor - a real finding, not a taste call. `--se-text-muted`
   (read everywhere `.text-muted` and `.form-text` already render, across
   every screen this file styles, not only the booker's new ones) stays
   pinned to the darker `--ink-2` tone instead, so no existing "muted"
   sentence anywhere in the product quietly drops below the floor. The
   lighter tone is kept too, as `--se-text-tertiary`, for the handful of
   mockup components that use it beside larger or bolder text already
   carrying the same fact (a price's own unit, a day tile's weekday
   abbreviation) - decorative repetition, never the only carrier of
   something a reader needs. */
:root {
  /* Colour - briefs/next-version-mockup-2026-10-23.md's own `:root` block */
  --se-ground: #f6f7f9; /* mockup --bg */
  --se-surface: #ffffff; /* mockup --surface */
  --se-ink: #111827; /* mockup --ink */
  --se-text-secondary: #4b5563; /* mockup --ink-2 */
  --se-text-muted: #4b5563; /* kept equal to --se-text-secondary, not the
                                mockup's own lighter --ink-3 - see the
                                contrast note above. */
  --se-text-tertiary: #9ca3af; /* mockup --ink-3 - decorative-only, below
                                   §7's 4.5:1 floor; never the sole carrier
                                   of required text. */
  --se-hairline: #e5e7eb; /* mockup --line */
  --se-hairline-2: #f0f1f4; /* mockup --line-2 - the fainter divider inside
                                a list row (bookings.html's own rows, the
                                "Coming up" row), distinct from the hairline
                                a card's own border draws. */
  --se-accent: #2f5d50; /* mockup --accent */
  --se-accent-hover: #3e7a69; /* mockup --accent-2 */
  --se-accent-soft: #e7f0ec; /* mockup --accent-soft - a type icon's tint,
                                 never a text colour on its own. */
  --se-caution: #b45309; /* mockup --amber */
  --se-caution-ground: #fef3c7; /* mockup --amber-soft */
  --se-blue: #1d4ed8; /* mockup --blue - a second type-icon tint, not a
                          second "meaning" colour (§7: meaning is never
                          colour alone) - the icon is always paired with
                          the type's own name. */
  --se-blue-soft: #dbeafe;
  --se-violet: #6d28d9; /* mockup --violet - a third type-icon tint */
  --se-violet-soft: #ede9fe;
  --se-rose: #be123c; /* mockup --rose - a destructive-action accent (e.g.
                          "Cancel booking" on the ticket), never a status
                          colour on its own either. */
  --se-rose-soft: #ffe4e6;

  /* Type - one family, one scale (§2), the mockup's own numbers where it
     states one. "Inter" names the family the mockup loads from Google
     Fonts - BookEye loads nothing from outside (this brief's own rule), so
     the stack falls straight through to the system sans-serif on every
     device that doesn't already have Inter installed, same as it always
     has; naming it costs nothing and matches for the device that does. */
  --se-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --se-size-micro: 12px; /* mockup .label/.chip/.day small - a step below
                             caption, new in this amendment: the uppercase
                             section tag ("Coming up", "Day", "Time") and a
                             status chip both set 12px, consistently,
                             nowhere else in the mockup's own scale. */
  --se-size-caption: 13px; /* labels and captions - unchanged; matches the
                               mockup's own .field label/.type small/.note
                               exactly. */
  --se-size-body: 15px; /* unchanged; matches the mockup's own body text
                            exactly. A handful of the mockup's own booker-
                            screen sizes (14px pills/ticket rows, 16px day
                            number) are one or two pixels off this scale's
                            nearest step - rounded onto it rather than
                            adding a third near-15px size, the same
                            discrete-steps discipline §2 already asks for. */
  --se-size-emphasis: 17px; /* unchanged; matches the mockup's own .result
                                h3 exactly. */
  --se-size-title: 22px; /* screen title - unchanged, the mockup names no
                             screen-title size of its own to compare against */
  --se-size-headline: 26px; /* the single most important fact on a screen -
                                was 30px; the mockup's own h1.big is 26px,
                                still inside §2's stated 26-34px band, so
                                this is a tightening to the approved value,
                                not a departure from the standard. */

  /* Spacing - multiples of 4 (§2). Unchanged: this brief's own scope names
     "colours, radii, shadows... type scale... components", not spacing -
     the mockup's own spacing values are close enough to these existing
     steps that every new rule below reaches for one of them rather than
     introducing a value this brief was never asked to migrate. */
  --se-space-1: 8px;
  --se-space-2: 12px;
  --se-space-3: 16px;
  --se-space-4: 20px;
  --se-space-5: 28px;
  --se-space-6: 40px;

  /* Radius, borders and shadow (§2) - the mockup's own --radius/--radius-sm,
     and its one --shadow, both lifted verbatim. Shadows stay "for overlays
     only... never on a card" everywhere this file already drew one (the
     sheet); --se-shadow-card is used nowhere but the two mockup components
     that name a shadow of their own (a type card's hover state, the when
     screen's result card) - not a blanket "cards have shadows now" change. */
  --se-radius-control: 10px; /* mockup --radius-sm, was 8px */
  --se-radius-card: 14px; /* mockup --radius, was 12px */
  --se-radius-sheet: 20px; /* unchanged - the mockup names no sheet/drawer
                               radius distinct from --radius to compare
                               against; the existing, larger value is kept */
  --se-shadow-card: 0 1px 2px rgba(17, 24, 39, 0.04), 0 4px 16px rgba(17, 24, 39, 0.06); /* mockup --shadow */
  --se-border: 1px solid var(--se-hairline);
  /* The mockup's own `.meta` block (briefs/next-version-9b-match-the-
     mockup.md item 7, `.se-meta` below) draws its two rules dashed, not
     the solid hairline every other border on this page uses - its own
     way of marking "a handful of short facts," distinct from a card edge
     or a row divider. */
  --se-border-dashed: 1px dashed var(--se-hairline);
}

/* ---------------------------------------------------------------------------
   runtime/briefs/the-palette-is-still-half-bootstrap.md: the block above
   defines the tokens and the rules below it use them, but Bootstrap draws
   most of what a template never styled itself - table borders, plain body
   text, a bare `<a>`, `<code>` - from ITS OWN custom properties, declared on
   `:root,[data-bs-theme=light]` in bootstrap.min.css and left untouched
   until now. `:root` selectors tie on specificity, so source order decides,
   and saaseye.css already loads after bootstrap.min.css (_layout.html) -
   this block wins without `!important` anywhere. Measured on se-cp-01
   (platform#94/#95) as four colours outside the §2 token set - `#dee2e6`
   table borders (392), `#212529` body text (121), `#0d6efd` link blue (10,
   and 4.35:1 against the ground - below §7's 4.5:1 floor), `#ffc107`
   warning yellow (1) - re-measured by source inspection after this change
   (no browser in this environment; see the pull request for the table and
   what a re-measure on se-cp-01 would still need to confirm).

   Two variables here are not hex at all: Bootstrap never uses
   `--bs-link-color` directly - `a{color:rgba(var(--bs-link-color-rgb),var(
   --bs-link-opacity,1))}` reads the decomposed `-rgb` triplet instead, so
   the hex form is set for anything that DOES read it (`--bs-link-color` is
   also the fallback several other variables key off) while the `-rgb`
   companion is hand-decomposed and kept in sync by hand, since CSS has no
   way to turn a hex custom property into rgba() channels without a
   preprocessor (SE-ADR-0024: no build step). Every `-rgb` value below is
   just its hex sibling's own R,G,B - not a new colour, the same token
   written twice for the two forms Bootstrap's rules actually consume. */
:root {
  /* `.form-control`, `.card`, `.list-group`, `kbd`, `.accordion` and plain
     paragraph text all set their own colour from these two rather than
     inheriting `<body>`'s - `<body>` already gets --se-ink/--se-ground
     directly below, but that alone left every one of those components at
     Bootstrap's own #212529/#fff. */
  --bs-body-color: var(--se-ink);
  --bs-body-color-rgb: 17, 24, 39; /* --se-ink, decomposed (2026-10-23: #111827) */
  --bs-body-bg: var(--se-surface);

  /* `.table` cell text and `.text-body-emphasis`/`.link-body-emphasis` key
     off `--bs-emphasis-color`, not `--bs-body-color` - it defaults to pure
     `#000`, a colour of its own that never appeared in the brief's table
     only because it isn't `#212529`; still not a token, and every one of
     the six side-by-side dashboard tables sets its cell text from it. */
  --bs-emphasis-color: var(--se-ink);
  --bs-emphasis-color-rgb: 17, 24, 39; /* --se-ink, decomposed */

  /* Reboot's `tbody,td,tfoot,th,thead,tr{border-color:inherit}` propagates
     this one variable to every table cell border, which is why 392
     occurrences of the same colour collapse to one line here. */
  --bs-border-color: var(--se-hairline);

  /* `a{color:rgba(var(--bs-link-color-rgb),...)}` - see the block comment
     above for why both forms are set. */
  --bs-link-color: var(--se-accent);
  --bs-link-color-rgb: 47, 93, 80; /* --se-accent, decomposed (2026-10-23: #2f5d50) */
  /* "A darker hover" (the brief's own words) with no darker-accent token to
     reach for: --se-ink is already darker than --se-accent and is already
     a §2 token, so the hover state reuses it rather than inventing a
     shade §2 never named. */
  --bs-link-hover-color: var(--se-ink);
  --bs-link-hover-color-rgb: 17, 24, 39; /* --se-ink, decomposed */

  /* Bootstrap's own "secondary" text tone (`.form-text`, `<caption>`,
     placeholders) - not the same variable `.text-muted` uses, which
     `.text-muted` below already pins with `!important`. This is the same
     idea applied to the components that read this variable directly. */
  --bs-secondary-color: var(--se-text-muted);

  /* `<code>` (book_confirmation.html's booking reference) defaults to
     Bootstrap's own pink, `#d63384` - a token colour with no reason to be
     anything but plain emphasis, since it is an identifier, not a link, an
     error or a caution. */
  --bs-code-color: var(--se-ink);
}

/* ---------------------------------------------------------------------------
   Base. The ground/ink/font pairing from §2, applied once at the body level
   rather than per element - Bootstrap's own body defaults (white background,
   its own font stack) are what §1's "restrained, modern, quiet" direction is
   overriding. */
body {
  background-color: var(--se-ground);
  color: var(--se-ink);
  font-family: var(--se-font);
  font-size: var(--se-size-body);
}

/* Bootstrap's `.text-muted` resolves to its own secondary-color token, not
   ours, and nothing stops it drifting away from the 4.5:1 floor §7 sets.
   Pinning it to --se-text-muted keeps every "muted" word on every screen at
   the one shade §2 already fixed for exactly this reason. */
.text-muted {
  color: var(--se-text-muted) !important;
}

/* §7: "A visible focus ring, never removed." Bootstrap already draws one;
   this makes it the standard's own accent colour instead of Bootstrap's blue,
   and guarantees it survives regardless of which control it lands on. */
:focus-visible {
  outline: 2px solid var(--se-accent);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   §3 Layout and responsiveness. One breakpoint, 768px. Below it: one column,
   16px gutters, nav at the top. At or above it: a persistent left nav and a
   content column capped at 960px unless the content is genuinely a table. */
.se-shell {
  display: flex;
  flex-direction: column; /* below 768px: nav stacked above content (§3) -
                              this is the line that was missing. Without it
                              flex defaults to row at every width, the nav's
                              `width: 100%` below claims the entire row, and
                              `.se-content` is left with whatever's left of it
                              (measured on se-cp-01 at 375px: main 32px wide). */
  min-height: 100vh;
  align-items: flex-start;
}

.se-nav {
  /* Below 768px this is a plain top bar: full width, stacked above the
     content, no side rail - "navigation at the top" (§3). */
  width: 100%;
  background-color: var(--se-surface);
  border-bottom: var(--se-border);
  padding: var(--se-space-2) var(--se-space-3);
}

.se-nav-list {
  display: flex;
  /* SE-PRD-006 0.2.0 §3.6 / briefs/next-version-6-book-in-three-taps.md
     item 6: below 768px this is a top bar with no side rail (§3 above), so
     a role's own item count - four for the operator, three for the booker -
     is never proven to fit one row at every width down to 375px. Rather than
     assert it does, the row wraps: `flex-wrap` turns what used to run an
     item off the right edge (the defect SE-PRD-006 §1 point 3 found) into a
     second line of its own items instead. `gap` already sets both axes
     (row and column) to the same token - wrapping needs no second,
     separate row-gap value to "gain" one; it was already there. Harmless
     above 768px too: the media query below switches this list to a single
     column, where there is never more than one item per row to wrap. */
  flex-wrap: wrap;
  gap: var(--se-space-3);
  list-style: none;
  margin: 0;
  padding: 0;
}

.se-nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* §4: 44px touch target, on every device */
  padding: 0 var(--se-space-1);
  color: var(--se-text-secondary);
  text-decoration: none;
  font-size: var(--se-size-body);
  font-weight: 600;
  border-bottom: 2px solid transparent;
}

.se-nav-link:hover {
  color: var(--se-ink);
}

/* The current screen's link - driven by `request.url.path`, already
   available in every template context (app.py's TemplateResponse already
   passes `request`; nothing there needed to change). Colour is never the
   only signal (§7): the underline carries the same meaning colour alone
   would otherwise have to. */
.se-nav-link.is-active {
  color: var(--se-ink);
  border-bottom-color: var(--se-accent);
}

.se-content {
  flex: 1;
  min-width: 0; /* lets a genuinely wide table scroll its own box instead of
                   forcing the page to scroll horizontally (§3: "no
                   horizontal scrolling of the page, ever") */
  padding: var(--se-space-3);
}

.se-content-narrow {
  /* "content sits in a single column of at most 960px unless the content is
     genuinely a table" - applied to the screens whose content is prose,
     forms and cards, not the dashboard's tables. */
  max-width: 960px;
}

@media (min-width: 768px) {
  .se-shell {
    flex-direction: row;
    align-items: stretch;
  }
  .se-nav {
    width: 220px;
    flex: 0 0 220px;
    min-height: 100vh;
    border-bottom: none;
    border-right: var(--se-border);
    padding: var(--se-space-4) var(--se-space-3);
  }
  .se-nav-list {
    flex-direction: column;
    gap: var(--se-space-1);
  }
  .se-nav-link {
    border-bottom: none;
    border-left: 2px solid transparent;
    padding-left: var(--se-space-2);
  }
  .se-nav-link.is-active {
    border-bottom-color: transparent;
    border-left-color: var(--se-accent);
    background-color: var(--se-ground);
  }
  .se-content {
    padding: var(--se-space-5) var(--se-space-6);
  }
}

/* A table below 768px becomes stacked rows, each carrying its own labels
   (§3's own words - "shrinking a table until it is unreadable is not a
   responsive design"). `<th>`/`<td>` gain their column header back via
   `data-label` + a generated label, and the row itself becomes a bordered
   card rather than a table row, since there is no longer a grid for it to
   line up against. Applied only where a template opts in with
   `.se-table-stack`, so ordinary tables at desktop width are unaffected. */
@media (max-width: 767.98px) {
  .se-table-stack thead {
    display: none;
  }
  .se-table-stack,
  .se-table-stack tbody,
  .se-table-stack tr,
  .se-table-stack td {
    display: block;
    width: 100%;
  }
  .se-table-stack tr {
    border: var(--se-border);
    border-radius: var(--se-radius-control);
    margin-bottom: var(--se-space-2);
    padding: var(--se-space-1) var(--se-space-2);
  }
  /* runtime/briefs/the-manage-screen-never-says-it-worked.md, item 2:
     `Interchangeable` was rendering as `Interchangea` / `ble` at 390px.
     The label (the `::before` below) and the value used to share one row,
     side by side, competing for width under `justify-content:
     space-between` - and `.card-title, .list-group-item span, .se-table-
     stack td, .se-caution { overflow-wrap: anywhere; }` further down this
     file (kept, for the reason below) is what actually broke the word:
     per the CSS flexbox spec, `overflow-wrap: anywhere` (unlike `break-
     word`) also changes a flex item's *automatic minimum size*, letting
     the label shrink below its own min-content width instead of pushing
     the value aside - so the longest labels broke wherever they had to,
     mid-syllable, to fit the space the value's flex sibling left them.
     Stacking label above value (§4's own "label above the field, always" -
     the same relationship, applied to a read-only row instead of a form
     field) removes the competition entirely: each gets the row's full
     width, so nothing on this dashboard needs 390px of horizontal room
     for a 15-character word like "Interchangeable". `overflow-wrap:
     anywhere` stays in place below - it is still what keeps a genuinely
     long, unbroken value (a 60-character resource name, §5's "Long
     content") from forcing the horizontal scroll §3 forbids; only the
     layout direction changed, not that rule. */
  .se-table-stack td {
    display: flex;
    flex-direction: column;
    gap: var(--se-space-1);
    border: none;
    padding: var(--se-space-1) 0;
    text-align: left;
  }
  .se-table-stack td[data-label]::before {
    content: attr(data-label);
    font-weight: 600;
    color: var(--se-text-secondary);
    text-align: left;
  }
}

/* ---------------------------------------------------------------------------
   §4 Controls. */

/* Label above the field, always, 13px (§4) - the field wrapper stacks
   whatever it's given so no template has to remember the order by hand. */
.se-field {
  display: flex;
  flex-direction: column;
  gap: var(--se-space-1);
  margin-bottom: var(--se-space-3);
}

.se-label {
  font-size: var(--se-size-caption);
  font-weight: 600;
  color: var(--se-text-secondary);
}

/* Bootstrap's own `.form-control`/`.form-select` are kept (they already give
   real `<input>`/`<select>` semantics per §7) - this only overrides the
   height, radius and border colour §4 and §2 set: "form controls are 44px
   high" and hairline borders rather than Bootstrap's default grey. */
.form-control,
.form-select {
  min-height: 44px;
  border-color: var(--se-hairline);
  border-radius: var(--se-radius-control);
  font-size: var(--se-size-body);
}

.form-control-lg,
.form-select-lg {
  min-height: 44px; /* Bootstrap's own -lg variants overshoot §4's fixed
                        44px; capped back down rather than left to float. */
}

/* The <select> dropdown arrow is a data-URI SVG baked into bootstrap.min.css
   (`--bs-form-select-bg-img`), stroked `#343a40` directly in the SVG source
   - not a colour any custom property in the root override block above can
   reach, since it never varies, it's drawn. The only way to change it
   without editing the vendored file (SE-ADR-0024: no build step, pinned by
   digest) is to override the same property here with an otherwise-
   identical SVG. --se-ink is written as a literal hex inside it because a
   data-URI string cannot embed `var(--se-ink)` - this is not a new colour,
   it is that token spelled the one way this property can take it, and
   §8.2's raw-hex rule already excepts saaseye.css itself. */
.form-select {
  /* 2026-10-23: re-stroked with --se-ink's new hex (#111827 -> %23111827) -
     still a literal, for the same reason the original comment above gives:
     a data-URI string cannot embed var(--se-ink), and §8.2's raw-hex rule
     already excepts saaseye.css itself. */
  --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23111827' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* Bootstrap's own `:focus` state hardcodes `#86b7fe` and a matching blue
   glow directly in the rule - no custom property backs either one, so the
   root override above cannot reach it. The global `:focus-visible` rule
   below already draws a visible ring in --se-accent (§7); this only
   removes Bootstrap's own un-token colour ring so the two don't stack.
   (`.form-check-input:focus` gets the same treatment further down, next to
   the width/height rule it shares a selector prefix with - not here, so a
   plain-text scan for `.form-check-input` still lands on that rule first.) */
.form-control:focus,
.form-select:focus {
  border-color: var(--se-accent);
  box-shadow: none;
}

.form-label {
  font-size: var(--se-size-caption);
  font-weight: 600;
  color: var(--se-text-secondary);
}

/* runtime/briefs/bookeye-still-reads-like-a-debugger.md, item 4, and
   SE-ENG-010 §4/1.0.1: "a checkbox and a radio... are 15px at Bootstrap's
   default and must be enlarged" - found on the dashboard's own "Members
   interchangeable" checkbox at exactly that size (`1em` in Bootstrap's own
   `.form-check-input`, i.e. 15-16px against this stylesheet's 15px body
   text). §4's one exception (a link inline in a sentence) does not apply
   to a control, so there is no cap to preserve here the way
   `.form-control-lg` above caps *down* to 44px - a checkbox only ever needs
   to come *up* to it.

   Bootstrap positions the input with `float: left` and a *negative*
   `margin-left` on the input pulling it back over `.form-check`'s own
   `padding-left` - both sized in `em`, both tuned for the 1em box. Widening
   only the box and leaving that positioning alone pulls a 44px box only
   24px (1.5em) leftward, so a third of it would sit outside `.form-check`
   and overlap whatever precedes it - the "without breaking its alignment
   with the label beside it" half of this item, not a hypothetical: it is
   exactly what widening `width`/`height` alone did in a scratch check
   before this rule was written. Switching `.form-check` to flex instead of
   Bootstrap's float layout is what actually keeps the box and its label
   aligned at the new size; flex items ignore `float` per spec, so the
   `float: left` Bootstrap still sets on the input becomes inert once its
   parent is a flex container, but the input's own negative `margin-left`
   is a real margin regardless of `float` and still needs cancelling. */
.form-check {
  display: flex;
  align-items: center;
  gap: var(--se-space-1);
  min-height: 44px;
  padding-left: 0;
}

.form-check-input,
.form-check-input[type="radio"] {
  width: 44px;
  height: 44px;
  margin: 0;
  flex-shrink: 0; /* a long label sharing the row must wrap itself, never
                      squeeze the box below the floor */
  border-color: var(--se-hairline);
}

/* Same gap `.form-control:focus`/`.form-select:focus` close above -
   Bootstrap hardcodes `#86b7fe` and a matching glow on `:focus`, and
   `#0d6efd` flat on `:checked`, neither backed by a custom property. The
   global `:focus-visible` rule below already draws the required ring (§7)
   in --se-accent, so `:focus` only drops Bootstrap's own un-token one
   rather than recolouring it. Index.html's own "Members interchangeable"
   checkbox ships `checked` by default, so `:checked` is not a theoretical
   state - it is what that field looks like on first load. */
.form-check-input:focus {
  border-color: var(--se-accent);
  box-shadow: none;
}

.form-check-input:checked {
  background-color: var(--se-accent);
  border-color: var(--se-accent);
}

.form-check .form-check-label {
  margin-left: 0; /* Bootstrap's own spacing came from `.form-check`'s
                      `padding-left`, removed above; `gap` on the flex
                      container does that job now */
}

/* One primary action per screen: solid, ink-coloured, 52px (§4 - primary
   actions are the one control taller than the 44px floor). Everything else
   is a hairline button on the surface colour - `.se-btn-secondary` below. */
.btn,
.se-btn-primary,
.se-btn-secondary {
  min-height: 44px;
  border-radius: var(--se-radius-control);
  font-size: var(--se-size-body);
  font-weight: 600;
}

.btn-primary,
.se-btn-primary {
  min-height: 52px;
  background-color: var(--se-ink) !important;
  border-color: var(--se-ink) !important;
  color: var(--se-surface) !important;
}

.btn-primary:hover,
.se-btn-primary:hover,
.btn-primary:focus-visible,
.se-btn-primary:focus-visible {
  background-color: #000000 !important;
  border-color: #000000 !important;
  /* Bootstrap's own `.btn:focus-visible` also pulls in a per-variant
     coloured glow (`--bs-btn-focus-box-shadow`, unpinned to any token);
     the global `:focus-visible` rule below already draws the required
     ring (§7) in --se-accent, so Bootstrap's is removed rather than
     recoloured - a Tab-focused button was otherwise the one remaining
     place Bootstrap's blue survived this file. */
  box-shadow: none !important;
}

/* "Everything else is a hairline button on the surface colour" - Bootstrap's
   `btn-outline-secondary`/`btn-outline-primary` already draw a hairline
   button; this pins its colours to the tokens instead of Bootstrap's
   defaults, so a secondary action always reads as secondary, everywhere. */
.btn-outline-secondary,
.btn-outline-primary,
.se-btn-secondary {
  background-color: var(--se-surface) !important;
  border-color: var(--se-hairline) !important;
  color: var(--se-ink) !important;
}

.btn-outline-secondary:hover,
.btn-outline-primary:hover,
.se-btn-secondary:hover,
.btn-outline-secondary:focus-visible,
.btn-outline-primary:focus-visible,
.se-btn-secondary:focus-visible {
  background-color: var(--se-ground) !important;
  /* Same reason as `.btn-primary:focus-visible` above - remove Bootstrap's
     own coloured focus glow rather than recolour it; the global
     `:focus-visible` ring already satisfies §7. */
  box-shadow: none !important;
}

/* Card: 12px radius, hairline border, no shadow - "shadows are for overlays
   only... never on a card" (§2). Bootstrap's own `.card` draws a shadowless
   border already close to this; this only pins the radius and border colour
   to the tokens. */
.card {
  border-radius: var(--se-radius-card);
  border-color: var(--se-hairline);
  box-shadow: none;
}

/* Hairline: the standalone divider/border utility for anywhere a template
   needs one outside a `.card` or a form control - e.g. separating a
   disclosure's summary from its content. */
.se-hairline {
  border: none;
  border-top: var(--se-border);
}

/* Disclosure (`<details>`) - "for anything a person needs only sometimes"
   (§4): the full rejection list, not the refusal sentence itself, which is
   always needed and therefore never behind one. Bootstrap has no equivalent
   element, so this is first-party in full. */
.se-disclosure {
  border: var(--se-border);
  border-radius: var(--se-radius-control);
  padding: var(--se-space-1) var(--se-space-2);
  margin-top: var(--se-space-2);
}

.se-disclosure > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: var(--se-size-body);
  min-height: 44px; /* a summary is itself a control (it toggles) - §4's
                        floor applies to it the same as to a button */
  display: flex;
  align-items: center;
}

.se-disclosure[open] > summary {
  margin-bottom: var(--se-space-1);
  border-bottom: var(--se-border);
  padding-bottom: var(--se-space-1);
}

/* Sheet vs inline panel (§4): "a decision that costs something gets a sheet
   on a phone and an inline panel on a desktop." Below 768px `.se-sheet` is
   pinned to the bottom of the viewport, the one place shadows are allowed
   (§2: "shadows are for overlays only"); at 768px and above the same markup
   sits inline as an ordinary bordered panel - no JS, no second template,
   exactly the CSS-only split SE-PRD-003 §2's "no build step, no client-side
   state machine" already requires. */
.se-sheet {
  background-color: var(--se-surface);
}

@media (max-width: 767.98px) {
  .se-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 10;
    border-top-left-radius: var(--se-radius-sheet);
    border-top-right-radius: var(--se-radius-sheet);
    box-shadow: 0 -4px 20px rgba(28, 28, 26, 0.15);
    padding: var(--se-space-3);
    max-height: 85vh;
    overflow-y: auto;
  }
}

@media (min-width: 768px) {
  .se-sheet {
    border: var(--se-border);
    border-radius: var(--se-radius-card);
    padding: var(--se-space-4);
  }
}

/* §5 "Long content" - the name with 60 characters. A resource, class or
   bundle name is booker-supplied free text (store.py enforces no length
   cap), so any of it can be that name; this keeps a long one wrapping inside
   its own cell/card instead of forcing the row - and with it the page - to
   scroll sideways (§3: "no horizontal scrolling of the page, ever").
   `.se-caution` added by the brief's own sweep
   (runtime/briefs/the-manage-screen-is-a-database-dump.md): the steering
   step's cost messages (book_review.html) interpolate a resource name into
   a sentence inside a `.se-caution` box, so the same long-name risk applies
   there and was missing this rule.
   `break-word`, not `anywhere` (runtime/briefs/anywhere-is-the-wrong-word.md):
   neither `.card-title` nor `.se-caution` is a table cell or a flex item -
   both simply fill the block-level width their container already gives
   them - so neither box's own width is ever computed from its content's
   minimum size, and `anywhere`'s one distinguishing effect (collapsing
   that minimum, see the two rules below) was never doing anything here
   except waiting to bite the day one of them moved into a layout where it
   would. `break-word` gives the identical §5 protection - an unbroken word
   that cannot fit its line still breaks - without that latent risk. */
.card-title,
.se-caution {
  overflow-wrap: break-word;
}

/* Unlike the rule above, `.list-group-item span` genuinely needs the
   minimum-width collapse: `_rejection_badge.html`'s badge is its flex
   sibling in a `d-flex justify-content-between` row (book.html,
   book_confirmation.html), the same side-by-side, width-competing shape
   `.se-table-stack td`'s comment above describes for the label/value row -
   except this row was never restructured to stack, so the competition is
   still live. Per the CSS flexbox spec, `overflow-wrap: anywhere` (unlike
   `break-word`) also lowers a flex item's *automatic minimum size* - the
   one thing that lets a long "<resource name> - <reason>" string give way
   to the badge instead of pushing it out of the row or forcing the row,
   and the page (§3), to scroll sideways. This is the "if anywhere" case
   the brief above asked this file to name: a real flex sibling with a
   fixed-width neighbour, not a table column with room to spare. */
.list-group-item span {
  overflow-wrap: anywhere;
}

/* runtime/briefs/the-band-between-the-two-widths.md: `.se-table-stack td`
   used to sit in the rule above too, unconditionally - every cell of every
   stacked table, whatever it held. That was the wrong scope: below 768px
   nothing needed it (label and value each already get the row's full width
   - see this file's own comment on the flexed `.se-table-stack td` rule
   above). Scoped down to the columns that actually hold the free text §5
   above is about - the same "booker-supplied, no length cap" test, read
   off index.html's own `data-label` attributes rather than re-declared
   here: a type, resource or bundle name (`Name`, `Type`,
   `Resource`), a description or reason (`Description`, `Reason`), an
   attribute's own name (`Attribute`), and a bundle's member list
   (`Members`, which interpolates resource/bundle names into one cell).
   Everything left out - `Interchangeable`, `Turnaround`, `Meaningful block
   length`, `How many`, `Measured as`, `When`, `Status`, `Reference` - is
   fixed-vocabulary, store-generated or numeric, never a value long enough
   for this to be the right tool; a column of one of those that still
   feels cramped needs more width (the layout fix above), not a licence to
   break its words.

   runtime/briefs/anywhere-is-the-wrong-word.md: the scoping above was the
   right selectors and the wrong keyword. briefs/next-version-7-operator-
   in-plain-words.md item 1 later renamed the "Class" column (both
   templates that had one: index.html's Items table, report.html's two
   fit-ratio/utilization tables) to "Type" - this rule's own selector list
   and the comment above it are kept in sync with that rename below, the
   same `data-label` contract this file and those templates have always
   shared; a stale `[data-label="Class"]` here would have silently stopped
   protecting a column that still needs it. `anywhere` doesn't just permit a
   break - like `.card-title` and `.se-caution` above, it also changes the
   box's intrinsic *minimum* content width to a single character. A `<td>`
   in this stack IS sized by that minimum: these tables are
   `table-layout: auto`, so the browser hands whatever a column doesn't
   need for its minimum to the other columns. With `anywhere`, "meeting-room"
   sat in a column whose minimum was one character, and 505px of five-column
   table in a 529px container was reason enough to shrink it to the 57px
   measured on se-cp-01 at 820px and break the word mid-syllable - not
   because the value was long (it is 12 characters; §5's own case is 60),
   but because the keyword told the table it was allowed to. `break-word`
   leaves that minimum alone - a column still sizes to fit "meeting-room"
   whole - and only breaks a word once it genuinely has nowhere else to go,
   which is what still protects the §5 case this rule exists for. Same
   selectors as above; the keyword was the defect. */
.se-table-stack td[data-label="Name"],
.se-table-stack td[data-label="Description"],
.se-table-stack td[data-label="Type"],
.se-table-stack td[data-label="Resource"],
.se-table-stack td[data-label="Attribute"],
.se-table-stack td[data-label="Members"],
.se-table-stack td[data-label="Reason"] {
  overflow-wrap: break-word;
}

/* Caution - the "costly"/"refused" colour pairing from §2, kept as classes
   rather than inline styles so no template ever writes a hex of its own. */
.se-caution {
  background-color: var(--se-caution-ground);
  border: 1px solid var(--se-caution);
  color: var(--se-ink);
  border-radius: var(--se-radius-control);
  padding: var(--se-space-2);
}

/* _rejection_badge.html's four classifications, each formerly a raw
   Bootstrap colour-by-class-name combination (`bg-warning text-dark`,
   `bg-primary`, `bg-light text-dark border`, `bg-secondary`) left in place
   and never overridden - exactly what the brief's "do not leave a class in
   place and override it at a distance" rules out. `.badge` itself (size,
   padding, shape) is still Bootstrap's; only the colour is first-party now.
   Same four classes cover index.html's two section-header badges, which
   named the same two Bootstrap colours by hand for the same two meanings. */
.se-badge-caution {
  /* The same caution pairing as .se-caution above, for the one badge that
     shares its meaning - "unavailable, not a booking" (maintenance/
     calibration) is this file's caution/refused colour, not a new one. */
  background-color: var(--se-caution-ground);
  color: var(--se-caution);
}

.se-badge-booking {
  /* accent/surface: 6.11:1, clears §7's 4.5:1 floor. */
  background-color: var(--se-accent);
  color: var(--se-surface);
}

.se-badge-neutral {
  /* "not reached" - a candidate the assignment search never got to, not a
     refusal and not a booking; the hairline/surface pairing every other
     unremarkable surface in this file already uses. */
  background-color: var(--se-surface);
  border: 1px solid var(--se-hairline);
  color: var(--se-ink);
}

.se-badge-ruled-out {
  /* secondary-text/surface: 7.59:1. */
  background-color: var(--se-text-secondary);
  color: var(--se-surface);
}

/* book.html's and index.html's "Refused" alerts (§5.3) reused Bootstrap's
   `alert-danger` for the same meaning `.se-caution` already names above;
   this is that pairing in `.alert`'s own shape instead of a second class
   for one state. book_confirmation.html's "Explanation not recorded" is
   not an error or a refusal - a neutral note - and gets its own, plainer
   pairing rather than borrowing caution's for something that isn't costly
   or refused (§7: meaning is never carried by colour alone, so the two
   must not share one). */
.se-alert-caution {
  background-color: var(--se-caution-ground);
  border-color: var(--se-caution);
  color: var(--se-ink);
}

.se-alert-neutral {
  background-color: var(--se-ground);
  border-color: var(--se-hairline);
  color: var(--se-ink);
}

/* briefs/next-version-16-public-demo.md item 3: "small and calm" - muted
   caption text on the ground colour, not the caution pairing above: this
   is a fact about the deployment, never a cost or a refusal, and SE-ENG-
   010 §7's "meaning is never carried by colour alone" cuts the other way
   too - a notice with nothing wrong to report should not borrow the one
   colour this file reserves for something that is. Sits above every one
   of `_layout.html`'s three session shells, which is why it gets its own
   top-level rule rather than living inside `.se-nav`/`.se-app`/`.se-phone-
   shell`'s own padding. */
.se-demo-banner {
  margin: 0;
  background-color: var(--se-ground);
  border-bottom: var(--se-border);
  color: var(--se-text-secondary);
  font-size: var(--se-size-caption);
  text-align: center;
  padding: var(--se-space-1) var(--se-space-2);
}

/* Type utilities for the three sizes a template needs on a bare heading or
   lede paragraph rather than a form control or a label - §2's scale, and
   nothing else: "a solution may not define a colour, a type size or a
   spacing value of its own." Before these existed the same three rules
   were written inline (`style="font-size: var(--se-size-headline); ..."`)
   on every screen's `<h1>`, which is itself the thing §8.2's stack check
   fails a solution for - a `font-size` outside `saaseye.css`, even one that
   only ever names a token and never a raw value. Three classes instead of
   one continuum because §2 only names discrete sizes, not a formula. */
.se-headline {
  font-size: var(--se-size-headline);
  font-weight: 700;
}

.se-title {
  font-size: var(--se-size-title);
  font-weight: 700;
}

.se-emphasis {
  font-size: var(--se-size-emphasis);
}

/* ---------------------------------------------------------------------------
   runtime/briefs/the-manage-screen-is-a-database-dump.md, item 5: "structure
   the page... SE-ENG-010 §3 gives you a persistent left navigation; use it."
   index.html's own comment explains the choice (sections on one page, not
   separate views); this is the in-page jump list that choice needs - styled
   like `.se-nav` above (hairline, quiet, no shadow - §1) rather than
   inventing a second visual language for "a list of places to go". */
.se-jumpnav {
  border: var(--se-border);
  border-radius: var(--se-radius-control);
  background-color: var(--se-surface);
  padding: var(--se-space-1) var(--se-space-2);
  margin-bottom: var(--se-space-4);
}

.se-jumpnav-list {
  display: flex;
  flex-wrap: wrap; /* §3: "no horizontal scrolling of the page, ever" - six
                       links wrap at 390px instead of forcing a scrollbar */
  gap: var(--se-space-1) var(--se-space-3);
  list-style: none;
  margin: 0;
  padding: 0;
}

.se-jumpnav-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* §4: this is a control, not a link inline in a
                        sentence - the one case 1.0.1 exempted from the
                        floor, and this is not it */
  color: var(--se-text-secondary);
  font-weight: 600;
  text-decoration: none;
}

.se-jumpnav-link:hover {
  color: var(--se-ink);
}

/* Sweep finding, runtime/briefs/the-manage-screen-is-a-database-dump.md
   ("every screen, not just this one... fix it in this pull request if it
   is presentation"): book_review.html, book_confirmation.html and
   error.html each carry a standalone "go back" link as the sole content of
   its own paragraph - not text inline within a flowing sentence, so §4
   1.0.1's one exception (a link sized by the sentence around it) does not
   apply. Bootstrap's default renders that anchor at its text's line-height,
   well under the 44px floor. Same treatment as `.se-jumpnav-link` above. */
.se-back-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--se-text-secondary);
  font-weight: 600;
  text-decoration: none;
}

.se-back-link:hover {
  color: var(--se-ink);
}

/* ---------------------------------------------------------------------------
   Item 2: "one form per kind of thing, not one per row." Nine
   "Declare an attribute for X" forms (and the equivalent per-bundle and
   per-resource forms elsewhere on this page) become one named choice with
   one form panel that appears for whichever choice is picked.

   Each `.se-tabset-item` holds a real `<input type="radio">` (classed
   `.form-check-input` too, purely to inherit that class's existing 44px
   sizing, checked colour and focus ring above - no new rule needed for any
   of that), a `<label for>` naming the choice, and the panel it reveals, in
   that order, as three siblings under the same parent - required for the
   plain sibling-combinator rule below to reach from one to the other with
   no id-specific CSS and no JS (`static/*.js` is outside this brief's
   scope; SE-PRD-003 §2's "no client-side state machine" already argues for
   a CSS-only split here, the same reasoning book_review.html's `.se-sheet`
   above already gives). Nothing is hidden from the keyboard or a screen
   reader: every radio stays visible and in the normal tab order, exactly
   like the "Members interchangeable" checkbox already on this page. */
.se-tabset {
  border: 0; /* Bootstrap already zeroes a bare <fieldset>; stated here too
                since a fieldset's default border is otherwise the kind of
                thing this file exists to own */
  padding: 0;
  margin: 0 0 var(--se-space-2);
}

.se-tabset-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--se-space-2);
  border: var(--se-border);
  border-radius: var(--se-radius-control);
  padding: var(--se-space-2);
  margin-top: var(--se-space-2);
}

.se-tabset-label {
  font-size: var(--se-size-body);
  font-weight: 600;
  color: var(--se-ink);
  cursor: pointer;
}

.se-tabset-panel {
  display: none; /* shown only for the item whose radio is checked, below */
  flex: 1 0 100%; /* its own row, under the radio/label pair above it -
                      `flex-basis: 100%` is the usual flexbox forced-wrap,
                      so the panel never sits beside the label it belongs
                      to, only underneath it */
}

.se-tabset-radio:checked ~ .se-tabset-panel {
  display: block;
}

/* briefs/next-version-6-book-in-three-taps.md: the Book screen's own
   "Today / Tomorrow / Pick a date" and "All day / Morning / Afternoon /
   Evening / Pick a time" controls - the same radio-plus-label-plus-panel
   mechanism above, laid out in a row instead of a column, matching SE-PRD-
   006 0.2.0 §3.3's own "44px hairline pills." No new token and no new
   component (§3.1 of that document is explicit about this): `.se-tabset`
   already gives every item its own hairline box, 44px radio and reveal
   panel; this modifier only changes the one axis they stack on. */
.se-tabset-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--se-space-2);
}

.se-tabset-row .se-tabset-item {
  margin-top: 0; /* the vertical rule's own top margin would otherwise
                    misalign a pill against its row siblings */
  border-radius: 999px; /* the pill shape - "hairline pills" (§3.3), the
                            same hairline border .se-tabset-item already
                            draws, just rounded past the point a box reads
                            as a box */
}

/* Amendment, briefs/next-version-9b-match-the-mockup.md item 4: a pill that
   reveals a panel ("Pick a date," "Pick a time," "Custom") used to keep the
   plainer, squarer control radius instead of the 999px pill shape every
   other `.se-tabset-row` item gets, on the reasoning that the pill shape
   "reads wrong" once it holds more than a label. The COO's mockup
   comparison named exactly this - "'Pick a date' and 'Pick a time' are
   drawn as square boxes... they are pills, like every other choice" - so
   that reasoning loses to the mockup's own, explicit rule: there is no
   second control shape for an item that happens to hold a reveal panel,
   only `.se-tabset-row .se-tabset-item` above, same as "Today" or
   "Morning" get. */

/* ---------------------------------------------------------------------------
   briefs/next-version-9-the-approved-look-booker.md: the mockup's own pill
   fill - "selected" is a solid ink background with white text (mockup
   `.pill.on`), not just the radio dot's own `:checked` colour from §4 above
   (that rule still fires too, on whatever renders the input itself). The
   radio stays a real, focusable `<input type="radio">` in the normal tab
   order (§7: "never a div with a click handler") - only its own box is
   visually collapsed to nothing, since the mockup's pill has no visible
   circle at all, just the label. `:has()` is the same mechanism
   `.se-tabset-row .se-tabset-item:has(.se-tabset-panel > *)` above already
   relies on - no new technique, one more use of it. */
.se-tabset-row .se-tabset-radio {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
  margin: 0;
}

/* The global `:focus-visible` rule (§7, near the top of this file) would
   otherwise try to ring a box this rule just collapsed to nothing - moved
   onto the label instead, which is the only part of this control a sighted
   keyboard user can actually see, so the required ring (§7: "never
   removed") lands somewhere visible rather than nowhere. */
.se-tabset-row .se-tabset-radio:focus-visible {
  outline: none;
}

.se-tabset-row .se-tabset-radio:focus-visible ~ .se-tabset-label {
  outline: 2px solid var(--se-accent);
  outline-offset: 2px;
  border-radius: var(--se-radius-control);
}

.se-tabset-row .se-tabset-item:has(.se-tabset-radio:checked) {
  background-color: var(--se-ink);
  border-color: var(--se-ink);
}

.se-tabset-row .se-tabset-item:has(.se-tabset-radio:checked) .se-tabset-label {
  color: var(--se-surface);
}

/* The "day strip" (SE-PRD-006 0.3.0 §0's visual spec, mockup `.days`/`.day`).
   Amendment, briefs/next-version-9b-match-the-mockup.md item 3: the first
   pass drew only the three controls SE-PRD-006 0.2.0 §3.3 ratified (Today /
   Tomorrow / Pick a date) in this tile shape, on that brief's own
   "behaviour does not change" rule. The COO's mockup comparison named this
   one wrong too - "a strip of seven rounded-square day tiles... today
   first... and 'Pick a date' as an ordinary pill below the strip," not
   folded into the same seven-wide row. `app.py`'s `_day_tiles` is the seven
   tiles themselves (today plus the next six, each checked for whether the
   chosen type can be booked that day at all - see that function's own
   docstring for why UTC stands in for the booker's own local day here);
   `.se-tabset-days` now holds exactly those seven, laid out as seven equal
   grid columns - a `grid`, not the wrapping flex row `.se-tabset-row` uses
   for everything else, since seven fixed-width tiles would either overflow
   a 375px phone or wrap unevenly, neither of which is "a strip." "Pick a
   date" is a sibling pill underneath (book_when.html), in its own ordinary
   `.se-tabset-row`, not a grid column - ordinary width, ordinary radius,
   same as "Morning" or "Pick a time" below it on this same screen. */
.se-tabset-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--se-space-1);
}

.se-tabset-days .se-tabset-item {
  justify-content: center;
  padding: var(--se-space-1) 0;
  border-radius: var(--se-radius-control); /* "rounded-square" (item 3's own
                                                wording), not the 999px pill
                                                `.se-tabset-row .se-tabset-
                                                item` gives every other
                                                choice on this screen - the
                                                one shape the mockup itself
                                                draws differently between
                                                `.day` and `.pill`. */
}

/* A tile whose own radio is `disabled` (`app.py`'s `_day_tiles`: the type
   cannot be booked at all that day) reads as greyed-out, the same `0.4`
   opacity the mockup's own `.day.off` uses - and, being a real `disabled`
   input rather than a class the mockup's own JS has to remember to skip
   (`:not(.off)`), is already unreachable by click, Tab or Enter with no
   script needed to enforce that (§7's own "never a div with a click
   handler" cuts both ways: a disabled real control is also not a trap). */
.se-tabset-days .se-tabset-item:has(.se-tabset-radio:disabled) {
  opacity: 0.4;
}

.se-tabset-days .se-tabset-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}

.se-day-tile-dow {
  font-size: var(--se-size-micro);
  color: var(--se-text-tertiary); /* decorative repetition of the weekday
                                      name already spelled out in full by
                                      the screen's own heading/pill choice -
                                      never the only way to tell the tiles
                                      apart (§7: meaning never carried by
                                      colour, or by this alone). */
  text-transform: uppercase;
}

.se-tabset-days .se-tabset-item:has(.se-tabset-radio:checked) .se-day-tile-dow {
  color: var(--se-surface);
}

.se-day-tile-num {
  font-size: var(--se-size-body);
  font-weight: 700;
}

/* "Pick a date," below the strip (book_when.html) - an ordinary pill row,
   same component as "Morning"/"Pick a time" further down; this margin is
   the only thing that distinguishes it from any other `.se-tabset-row`. */
.se-tabset-days-pick {
  margin-top: var(--se-space-2);
}

/* ---------------------------------------------------------------------------
   The uppercase micro-label the mockup repeats above almost every grouped
   control - "Coming up", "Day", "Time", "People" (mockup `.label`). Not the
   same thing as `.se-label` above: that one sits directly on a form field
   (§4: "label above the field, always"); this one introduces a whole
   section of them, or a read-only list, same as a jump-nav heading would. */
.se-section-label {
  display: block;
  font-size: var(--se-size-micro);
  font-weight: 600;
  color: var(--se-text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: var(--se-space-4) 0 var(--se-space-1);
}

/* ---------------------------------------------------------------------------
   The Book screen's step-1 cards (mockup `.type`): one full-width row per
   declared type, icon - name and one-line description - price, replacing
   the three-column Bootstrap `.card` grid this screen drew before this
   brief. A real `<a href>` (§7), never a `<div>` with a click handler - the
   same plain navigation book.html's own comment already describes, just
   laid out as a row instead of a grid tile. */
.se-type-card {
  display: flex;
  align-items: center;
  gap: var(--se-space-3);
  padding: var(--se-space-2);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  margin-bottom: var(--se-space-1);
  background-color: var(--se-surface);
  text-decoration: none;
  color: inherit;
}

.se-type-card:hover,
.se-type-card:focus-visible {
  border-color: var(--se-accent);
  box-shadow: var(--se-shadow-card);
}

.se-type-card-body {
  flex: 1;
  min-width: 0; /* a long declared type name wraps inside the row instead
                    of pushing the price past it (§3/§5) */
}

.se-type-card-name {
  display: block;
  font-size: var(--se-size-body);
  font-weight: 600;
  overflow-wrap: break-word; /* §5: a type name is booker-supplied free
                                 text with no length cap, same reasoning
                                 `.card-title` above already gives */
}

.se-type-card-desc {
  display: block;
  font-size: var(--se-size-caption);
  color: var(--se-text-secondary);
}

.se-type-card-price {
  flex: none;
  max-width: 40%; /* the unpriced-type fallback ("No price declared yet.")
                      is a full sentence, not a short amount - no `white-
                      space: nowrap` on this container (§3: "no horizontal
                      scrolling of the page, ever"); this caps its own
                      column instead, so that fallback wraps onto a second
                      line rather than pushing the card wider than the
                      phone it's on. The amount/unit pair below (item 1,
                      briefs/next-version-9b-match-the-mockup.md) gets its
                      own `nowrap` instead, since the mockup's own price -
                      "€25" or "from €15" - is always short enough that it
                      never needs the fallback's room to wrap. */
  text-align: right;
  font-weight: 600;
}

.se-type-card-price-amount {
  white-space: nowrap; /* "from €15" is still one line - item 1's own
                           "never wrapping," unlike the no-price fallback
                           above, which this rule does not reach */
}

.se-type-card-price small {
  display: block;
  white-space: nowrap;
  font-weight: 400;
  color: var(--se-text-tertiary); /* decorative: the unit word ("per day")
                                      beside the amount that already states
                                      the fact in full */
  font-size: var(--se-size-micro);
}

/* The icon itself (mockup `.ico`): a fixed-size tinted box around an inline
   SVG. The tint and the stroke colour are a pair, kept in step by the one
   modifier class below rather than set separately wherever an icon
   appears - `app.py`'s own `_type_icon_key` picks the modifier by the
   type's own name today; a later per-type setting (SE-PRD-006 0.3.0's own
   brief: "chosen by its name or set by the operator later") only has to
   change what picks the modifier, never these rules. */
.se-type-icon {
  flex: none;
  width: 46px;
  height: 46px;
  border-radius: var(--se-radius-control);
  display: grid;
  place-items: center;
}

.se-type-icon svg {
  width: 24px;
  height: 24px;
}

.se-type-icon-desk {
  background-color: var(--se-accent-soft);
  color: var(--se-accent);
}

.se-type-icon-room {
  background-color: var(--se-blue-soft);
  color: var(--se-blue);
}

.se-type-icon-vehicle {
  background-color: var(--se-violet-soft);
  color: var(--se-violet);
}

.se-type-icon-bike {
  background-color: var(--se-caution-ground);
  color: var(--se-caution);
}

/* A declared type this heuristic doesn't recognise, and "Anything that
   fits" (which is not a declared type at all) - the one pairing the
   mockup itself never needed, since its own four worked types all matched
   one of the four modifiers above. Neutral, not a fifth decorative colour
   §1 would otherwise rule out for its own sake. */
.se-type-icon-other,
.se-type-icon-any {
  background-color: var(--se-ground);
  color: var(--se-text-secondary);
}

/* ---------------------------------------------------------------------------
   "Coming up" (book.html) and "Your bookings" (bookings.html): one row per
   booking, a fixed-width date tile beside the item and its status (mockup
   `.bk`). */
.se-booking-row {
  display: flex;
  align-items: center;
  gap: var(--se-space-2);
  padding: var(--se-space-2) 0;
  border-bottom: 1px solid var(--se-hairline-2);
  text-decoration: none;
  color: inherit;
}

.se-booking-row:last-child {
  border-bottom: none;
}

/* briefs/next-version-13-move-on-behalf-receipt.md item 3: `.se-booking-row`
   stopped being the link itself once a Receipt link needed a second,
   sibling link on the same row (templates/bookings.html's own comment on
   why) - `display:contents` means this inner link adds no box of its own,
   so `.se-booking-row-date`/`.se-booking-row-info` still lay out as direct
   flex children of `.se-booking-row`, exactly as when the outer element
   carried the href itself. */
.se-booking-row-link {
  display: contents;
  color: inherit;
  text-decoration: none;
}

.se-booking-row-receipt {
  flex: none;
}

.se-booking-row-date {
  flex: none;
  width: 48px;
  text-align: center;
  border-radius: var(--se-radius-control);
  background-color: var(--se-ground);
  padding: var(--se-space-1) 0;
}

.se-booking-row-date small {
  display: block;
  font-size: var(--se-size-micro);
  color: var(--se-text-tertiary);
  text-transform: uppercase;
  font-weight: 600;
}

.se-booking-row-date b {
  font-size: var(--se-size-emphasis);
}

.se-booking-row-info {
  flex: 1;
  min-width: 0;
}

.se-booking-row-info strong {
  display: block;
  font-weight: 600;
  overflow-wrap: break-word; /* §5: a resource name is free text too */
}

.se-booking-row-info span {
  color: var(--se-text-secondary);
  font-size: var(--se-size-caption);
}

/* Status chip (mockup `.chip`) - SE-ENG-010 §7's "meaning never carried by
   colour alone" is kept the same way `_rejection_badge.html`'s badges
   already keep it: the word ("Booked", "Cancelled, free of charge", …)
   sits right beside the chip in every template that uses one; the chip
   itself is never the only place a status is stated. */
.se-chip {
  flex: none;
  font-size: var(--se-size-micro);
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
}

.se-chip-ok {
  background-color: var(--se-accent-soft);
  color: var(--se-accent);
}

.se-chip-warn {
  background-color: var(--se-caution-ground);
  color: var(--se-caution);
}

.se-chip-muted {
  background-color: var(--se-hairline-2);
  color: var(--se-text-tertiary);
}

/* ---------------------------------------------------------------------------
   The "when" screen's step-3 result (mockup `.result`): the uncontested
   confirm screen (book_when.html) - name and price on one line, why it was
   offered, a dashed-rule meta block, one primary action. Distinct from
   `.se-sheet`: this is never a bottom sheet (it sits inline, under the
   controls that produced it, so they stay reachable to adjust - SE-PRD-006
   0.2.0 §3.3's own "the worked 'Tomorrow, then Afternoon' example needs
   exactly that"), so it keeps the one shadow the mockup itself draws here
   rather than `.se-sheet`'s position/overlay treatment. */
.se-result-card {
  margin-top: var(--se-space-4);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  padding: var(--se-space-3);
  box-shadow: var(--se-shadow-card);
  background-color: var(--se-surface);
}

.se-result-card-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--se-space-2);
}

.se-result-card-row h2 {
  margin: 0;
  font-size: var(--se-size-emphasis);
}

.se-result-card-row b {
  flex: none;
  font-size: var(--se-size-emphasis);
}

/* Amendment, briefs/next-version-9b-match-the-mockup.md item 7: the when
   screen's result card used to stop at name/price and one combined
   interval sentence - the COO's mockup comparison named the gap: "the
   one-line reason under it, then date, time and the free-cancel line,
   then 'Book it'." `.se-result-card-why` is the mockup's own `.why` - one
   muted line, the same size/colour `.se-type-card-desc` already uses for a
   different one-line fact beside a name. `.se-meta` is the mockup's own
   `.meta`: a dashed-rule block of short facts, stacked, between the why
   line and the primary action. */
.se-result-card-why {
  font-size: var(--se-size-caption);
  color: var(--se-text-secondary);
  margin: 2px 0 var(--se-space-3);
}

.se-meta {
  display: flex;
  flex-direction: column;
  gap: var(--se-space-1);
  font-size: var(--se-size-body);
  color: var(--se-text-secondary);
  padding: var(--se-space-2) 0;
  border-top: var(--se-border-dashed);
  border-bottom: var(--se-border-dashed);
  margin-bottom: var(--se-space-3);
}

/* ---------------------------------------------------------------------------
   The confirmation screen's ticket (mockup `.ticket`) and its cancellation
   note (mockup `.note`) - book_confirmation.html. */
.se-ticket {
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  padding: var(--se-space-3);
  margin: var(--se-space-4) 0 var(--se-space-3);
}

.se-ticket-row {
  display: flex;
  justify-content: space-between;
  gap: var(--se-space-2);
  padding: var(--se-space-1) 0;
  font-size: var(--se-size-body);
}

.se-ticket-row span:first-child {
  color: var(--se-text-secondary);
}

.se-ticket-row b {
  overflow-wrap: break-word; /* §5: the resource/company name is free text */
  text-align: right;
}

/* briefs/next-version-13-move-on-behalf-receipt.md item 3 (BE-R23): "a
   clean printable page... the browser's print makes the PDF; no new
   dependency." `.se-receipt` only ever adds a little breathing room on
   screen - the "clean" half of the brief is the `@media print` block
   below, which hides every shell a signed-in session draws around it
   (the booker's phone top bar and tab bar, the operator's sidebar, the
   anonymous nav) and the on-screen-only controls this page itself carries
   (the back link, the Print button) once the page is actually printed,
   leaving nothing on the printed sheet but the receipt itself. */
.se-receipt {
  max-width: 480px;
}

.se-receipt-head {
  margin-bottom: var(--se-space-2);
}

@media print {
  .se-phone-top,
  .se-tabbar,
  .se-side,
  .se-nav,
  .se-receipt-hide-print {
    display: none !important;
  }
}

.se-note {
  display: flex;
  gap: var(--se-space-1);
  background-color: var(--se-accent-soft);
  color: var(--se-ink);
  border-radius: var(--se-radius-control);
  padding: var(--se-space-2);
  font-size: var(--se-size-caption);
}

/* The mockup's own rose-tinted ghost button for a destructive action
   (book_confirmation.html's "Cancel booking") - still `.se-btn-secondary`
   underneath (§4: never the primary action), colour is the only change. */
.se-btn-danger-text {
  color: var(--se-rose) !important;
}

/* The "booked" hero block (mockup `.done`) that holds the check mark and
   the headline below. */
.se-done {
  padding: var(--se-space-5) 0 var(--se-space-2);
}

/* The "booked" check mark (mockup `.check`) */
.se-check {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background-color: var(--se-accent-soft);
  color: var(--se-accent);
  display: grid;
  place-items: center;
  margin: var(--se-space-1) auto var(--se-space-3);
}

/* ---------------------------------------------------------------------------
   The sign-in screen (this brief's item 5): "one centred card", matching
   the booker shell's own phone width rather than the 960px prose column
   `.se-content-narrow` gives every other unauthenticated page. */
.se-auth-card {
  max-width: 375px;
  margin: var(--se-space-6) auto;
  padding: var(--se-space-4);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  background-color: var(--se-surface);
}

/* ---------------------------------------------------------------------------
   The booker's own shell (this brief's item 2): a phone-width top bar
   (brand mark, initials) and bottom tab bar (Book, Your bookings, Sign
   out), replacing `.se-nav`'s left-rail/top-bar treatment for a signed-in
   booker only - `_layout.html`'s own comment explains why operator and
   anonymous sessions keep `.se-nav` unchanged. "On wider screens the same
   content sits centred at phone width" (this brief, item 2): one max-width,
   no separate >=768px rule - unlike `.se-shell` above, there is no
   left-rail state for this shell to grow into at any width. */
.se-phone-shell {
  max-width: 430px;
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--se-surface);
}

.se-phone-top {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--se-space-2);
  padding: var(--se-space-2) var(--se-space-3);
  background-color: var(--se-surface);
  border-bottom: var(--se-border);
}

.se-brand {
  display: flex;
  align-items: center;
  gap: var(--se-space-1);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.se-brand-mark {
  width: 26px;
  height: 26px;
  border-radius: var(--se-radius-control);
  background-color: var(--se-accent);
  color: var(--se-surface);
  display: grid;
  place-items: center;
  font-size: var(--se-size-caption);
  font-style: normal;
}

.se-avatar {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background-color: var(--se-accent-soft);
  color: var(--se-accent);
  display: grid;
  place-items: center;
  font-weight: 600;
  font-size: var(--se-size-caption);
}

.se-phone-shell .se-content {
  flex: 1;
  padding: var(--se-space-2) var(--se-space-3) var(--se-space-5);
}

/* Bottom tab bar (mockup `.p-tabs`). Each tab is a plain `<a href>` whose
   only visible content is its own text ("Book", "Your bookings",
   "Account") - the
   icon beside it is drawn by CSS alone (`::before`, a data-URI SVG, the
   same "literal hex inside a data-URI" exception §8.2 already grants
   saaseye.css for the <select> arrow above), never a DOM element inside
   the link. That keeps each tab exactly the plain-navigation anchor it
   always was (nothing here changes what `href="/book"` points at or reads
   back), while still drawing the icon the mockup gives it. */
.se-tabbar {
  display: flex;
  border-top: var(--se-border);
  background-color: var(--se-surface);
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.se-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-height: 44px; /* §4's own floor, still a real control even though its
                        own background never changes */
  padding: var(--se-space-1) 0 var(--se-space-2);
  font-size: var(--se-size-micro);
  font-weight: 500;
  color: var(--se-text-tertiary);
  text-decoration: none;
  background: none;
  border: none;
}

.se-tab::before {
  content: "";
  width: 22px;
  height: 22px;
  background-color: currentColor;
  -webkit-mask-image: var(--se-tab-icon);
  mask-image: var(--se-tab-icon);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

.se-tab.is-active {
  color: var(--se-accent);
}

.se-tab-book {
  --se-tab-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22' fill='none' stroke='%23000' stroke-width='1.8'%3e%3cpath d='M4 11l8-6 8 6v8H4z'/%3e%3c/svg%3e");
}

.se-tab-bookings {
  --se-tab-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22' fill='none' stroke='%23000' stroke-width='1.8'%3e%3crect x='4' y='5' width='16' height='15' rx='2'/%3e%3cpath d='M4 10h16M9 3v4M15 3v4'/%3e%3c/svg%3e");
}

/* briefs/next-version-9b-match-the-mockup.md item 2: the third tab is now
   Account, not Sign out (that control moved to the new Account screen
   itself) - this rule used to be named `.se-tab-signout` and draw that
   tab's exit-door icon; renamed to match `_layout.html`'s own
   `se-tab-account` class, and the icon swapped for the mockup's own
   person glyph (`briefs/next-version-mockup-2026-10-23.md`'s `.p-tabs`
   third `<svg>`: a head circle over a shoulders arc) so the tab still
   draws *something* rather than silently losing its icon to a mask-image
   that no rule any longer sets. */
.se-tab-account {
  --se-tab-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 22' fill='none' stroke='%23000' stroke-width='1.8'%3e%3ccircle cx='11' cy='8' r='4'/%3e%3cpath d='M3 19c1.5-4 14.5-4 16 0'/%3e%3c/svg%3e");
}

/* ---------------------------------------------------------------------------
   briefs/next-version-10-the-operators-today.md: the operator's own shell
   (mockup `.app`/`.side`), replacing `.se-nav`'s left-rail/top-bar treatment
   for a signed-in operator only - `_layout.html`'s own comment explains why
   a booker and an anonymous visitor are unaffected. Fixed two-column grid,
   same as the mockup: this screen is checked at 1280px and 1440px (this
   brief's own "How it is checked"), the same desktop-only scope the
   mockup itself draws (its own stylesheet collapses `.board-wrap`/`.cols`
   and `.stats` below 1100px - mirrored below - but never the `.app`/`.side`
   split itself). A narrower, phone-first treatment of this screen is not
   this brief's scope - unlike the booker's own surface (briefs/next-
   version-4-phone-first-interface.md), nothing here claims the operator's
   own screens are ever opened at phone width. */
.se-app {
  display: grid;
  grid-template-columns: 232px 1fr;
  min-height: 100vh;
  align-items: start;
}

.se-side {
  background-color: var(--se-surface);
  border-right: var(--se-border);
  padding: var(--se-space-4) var(--se-space-2);
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 100vh;
}

.se-side .se-brand {
  padding: 0 var(--se-space-1) var(--se-space-3);
}

.se-side-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.se-side-nav-link {
  display: flex;
  align-items: center;
  gap: var(--se-space-1);
  min-height: 44px; /* §4's own floor, same as every other nav link */
  padding: 0 var(--se-space-2);
  border-radius: var(--se-radius-control);
  text-decoration: none;
  color: var(--se-text-secondary);
  font-weight: 500;
  font-size: var(--se-size-body);
}

.se-side-nav-link:hover {
  background-color: var(--se-ground);
}

.se-side-nav-link.is-active {
  background-color: var(--se-accent-soft);
  color: var(--se-accent);
}

.se-side-foot {
  margin-top: auto;
  padding: var(--se-space-2) var(--se-space-2) 0;
  border-top: var(--se-border);
  display: flex;
  align-items: center;
  gap: var(--se-space-1);
  font-size: var(--se-size-caption);
}

.se-side-foot small {
  display: block;
  color: var(--se-text-tertiary);
}

.se-main {
  min-width: 0;
}

.se-main .se-content {
  padding: var(--se-space-4) var(--se-space-5);
}

/* ---------------------------------------------------------------------------
   The Today board itself (mockup `.head`/`.datepick`/`.stats`/`.board-wrap`),
   templates/today.html. */
.se-board-head {
  display: flex;
  align-items: center;
  gap: var(--se-space-3);
  margin-bottom: var(--se-space-4);
}

.se-datepick {
  display: flex;
  align-items: center;
  border: var(--se-border);
  border-radius: var(--se-radius-control);
  background-color: var(--se-surface);
}

.se-datepick-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 36px;
  color: var(--se-text-secondary);
  text-decoration: none;
}

.se-datepick-label {
  padding: 0 var(--se-space-1);
  font-weight: 600;
  font-size: var(--se-size-caption);
}

.se-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--se-space-3);
  margin-bottom: var(--se-space-4);
}

.se-stat {
  background-color: var(--se-surface);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  padding: var(--se-space-2) var(--se-space-3);
}

.se-stat small {
  color: var(--se-text-tertiary);
  font-weight: 500;
  font-size: var(--se-size-micro);
}

.se-stat b {
  display: block;
  font-size: var(--se-size-headline);
  letter-spacing: -0.02em;
  margin-top: 2px;
}

.se-stat span {
  font-size: var(--se-size-micro);
  color: var(--se-accent);
  font-weight: 600;
}

.se-board-wrap {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: var(--se-space-3);
  align-items: start;
}

.se-board {
  background-color: var(--se-surface);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  overflow: hidden;
}

/* 170px item column + 13 one-hour cells (07:00-20:00 - `app.py`'s own
   `_BOARD_HOUR_START`/`_BOARD_HOUR_END`, traced against the mockup's own
   worked percentages in that function's comment). */
.se-board-grid {
  display: grid;
  grid-template-columns: 170px repeat(13, 1fr);
  font-size: var(--se-size-micro);
}

.se-board-col-head {
  padding: var(--se-space-2) 0 var(--se-space-1);
  color: var(--se-text-tertiary);
  border-bottom: var(--se-border);
  font-weight: 500;
}

.se-board-col-head-first {
  padding-left: var(--se-space-3);
}

.se-board-group {
  grid-column: 1 / -1;
  padding: var(--se-space-2) var(--se-space-3) var(--se-space-1);
  font-size: var(--se-size-micro);
  font-weight: 700;
  color: var(--se-text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  background-color: var(--se-ground);
  border-bottom: 1px solid var(--se-hairline-2);
}

.se-board-row-label {
  padding: 0 var(--se-space-3);
  min-height: 56px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-bottom: 1px solid var(--se-hairline-2);
}

.se-board-row-label b {
  font-weight: 600;
  font-size: var(--se-size-caption);
  overflow-wrap: break-word; /* §5: an item's own name is free text */
}

.se-board-row-label small {
  color: var(--se-text-tertiary);
  font-size: var(--se-size-micro);
}

.se-board-lane {
  grid-column: 2 / -1;
  position: relative;
  min-height: 56px;
  border-bottom: 1px solid var(--se-hairline-2);
  background-image: repeating-linear-gradient(
    90deg,
    transparent 0,
    transparent calc(100% / 13 - 1px),
    var(--se-hairline-2) calc(100% / 13 - 1px),
    var(--se-hairline-2) calc(100% / 13)
  );
}

.se-board-block {
  position: absolute;
  top: 7px;
  bottom: 7px;
  border-radius: var(--se-radius-control);
  padding: 5px 8px;
  line-height: 1.3;
  font-size: var(--se-size-micro);
  font-weight: 600;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-decoration: none;
  border-left: 3px solid;
  cursor: pointer;
}

/* A plain, non-interactive block (the multi-unit "N of Q booked" summary,
   today.html's own comment on why it is a `<div>`, not an `<a>`) still
   reads as the same kind of thing visually - only `cursor` differs. */
.se-board-block-fill {
  cursor: default;
}

.se-board-block small {
  display: block;
  font-weight: 400;
  opacity: 0.8;
  font-size: var(--se-size-micro);
}

/* Reuses the exact tint pairing `.se-type-icon-*` already gives each type
   on the booker's own step-1 cards (`app.py`'s `_type_icon_key` - one
   colour per type, item 2) - never a second palette invented for the same
   four names. */
.se-board-block-desk {
  background-color: var(--se-accent-soft);
  color: #24493f;
  border-color: var(--se-accent);
}

.se-board-block-room {
  background-color: var(--se-blue-soft);
  color: #1e3a8a;
  border-color: var(--se-blue);
}

.se-board-block-vehicle {
  background-color: var(--se-violet-soft);
  color: #4c1d95;
  border-color: var(--se-violet);
}

.se-board-block-bike {
  background-color: var(--se-caution-ground);
  color: #78350f;
  border-color: var(--se-caution);
}

.se-board-block-other,
.se-board-block-any {
  background-color: var(--se-ground);
  color: var(--se-ink);
  border-color: var(--se-text-tertiary);
}

.se-board-now {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background-color: var(--se-rose);
  z-index: 2;
}

.se-board-now::before {
  content: "";
  position: absolute;
  top: -4px;
  left: -4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--se-rose);
}

/* The detail card column (mockup `.panel`) - `.se-board-panel` reuses
   `.se-ticket`/`.se-chip` for its own rows and status chip rather than a
   second component for the same two kinds of fact. Exactly one panel (or
   the empty state) is ever visible: `:target` is the one CSS selector that
   matches by element id directly, so this needs no per-booking rule the
   way a fixed set of modifier classes would - the same real-navigation,
   no-JS idiom `se-jumpnav`'s own anchors already use, just reading the
   fragment back instead of only scrolling to it. */
.se-board-panel-col {
  background-color: var(--se-surface);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  padding: var(--se-space-3);
}

.se-board-panel {
  display: none;
}

.se-board-panel:target {
  display: block;
}

.se-board-panel h3 {
  margin: var(--se-space-1) 0 2px;
}

.se-board-panel-actions {
  display: flex;
  gap: var(--se-space-1);
  margin-top: var(--se-space-3);
}

.se-board-panel-actions .btn {
  flex: 1;
}

.se-board-panel-empty {
  display: block;
}

/* `:has()` - already relied on elsewhere in this file
   (`.se-tabset-row .se-tabset-item:has(...)`) - is what lets this read "is
   any panel currently the target" without a rule per booking id. */
.se-board-panel-col:has(.se-board-panel:target) .se-board-panel-empty {
  display: none;
}

@media (max-width: 1100px) {
  .se-board-wrap {
    grid-template-columns: 1fr;
  }

  .se-stats {
    grid-template-columns: repeat(2, 1fr);
  }

  /* briefs/next-version-11-items-companies-billing.md: `.se-cols` (below)
     is the same "a table and a side column" shape `.se-board-wrap` already
     collapses at this width - kept in the same query rather than a second
     one, so the two can never drift to different breakpoints by accident. */
  .se-cols {
    grid-template-columns: 1fr;
  }
}

/* ---------------------------------------------------------------------------
   briefs/next-version-11-items-companies-billing.md: the operator's Items &
   prices, Companies, Bookings and Billing screens (mockup `.cols`/`.tbl`/
   `.drawer`) - a data table beside a side panel, the same two-column shape
   `.se-board-wrap` already gives Today, just a narrower side column (340px,
   the mockup's own `.cols` value, against `.se-board-wrap`'s 320px) since a
   form panel needs a little more room than a read-only detail card. */
.se-cols {
  display: grid;
  grid-template-columns: 1fr 340px;
  gap: var(--se-space-3);
  align-items: start;
}

/* The page-header spacer (mockup `.head .grow`) - pushes a header's own
   trailing buttons ("+ New type", "+ New item") to the right of its title,
   the same job `.se-board-head`'s two children already do without one
   today.html's own header has only a title and a date switcher, so it
   never needed a third, flexible spacer between them. */
.se-head-grow {
  flex: 1;
}

/* An amount-and-unit pair (mockup `.two`: a price field's text input beside
   its own unit picker) - two columns, the second sized to its own content
   rather than splitting the row evenly, so a short unit word never claims
   half the field's width from the amount beside it. */
.se-field-pair {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--se-space-2);
}

/* The operator's own data table (mockup `.tbl`) - desktop-only, like the
   Today board (that screen's own comment explains why: this brief's own
   "How it is checked" names 1280px, never a phone width, for every screen
   it touches). Plain `<table>`, not `.se-table-stack` - there is no narrow
   layout for this table to collapse into. */
.se-tbl {
  width: 100%;
  border-collapse: collapse;
  background-color: var(--se-surface);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  overflow: hidden;
}

.se-tbl th {
  text-align: left;
  font-size: var(--se-size-micro);
  color: var(--se-text-tertiary);
  font-weight: 600;
  padding: var(--se-space-2) var(--se-space-3);
  border-bottom: var(--se-border);
  background-color: var(--se-ground);
}

.se-tbl td {
  padding: var(--se-space-3);
  border-bottom: 1px solid var(--se-hairline-2);
  font-size: var(--se-size-body);
  vertical-align: middle;
}

.se-tbl tr:last-child td {
  border-bottom: none;
}

/* The name cell (mockup `.tbl .nm`): icon, name, an optional capacity chip -
   a real `<a href>` to the matching drawer panel below (§7: never a `<div>`
   with a click handler), the same plain-navigation idiom the Today board's
   own blocks and `se-jumpnav`'s anchors already use. */
.se-tbl-name {
  display: flex;
  align-items: center;
  gap: var(--se-space-2);
  font-weight: 600;
  text-decoration: none;
  color: inherit;
}

.se-tbl-name:hover {
  color: var(--se-accent);
}

/* A smaller `.se-type-icon` for a table row (mockup `.tbl .nm .ico`: 36px,
   against the Book screen's own 46px type card icon) - the modifier only
   changes size; the tint/stroke pairing still comes from whichever
   `.se-type-icon-*` class names the type, so a row's icon and its type's
   own card/board colour never drift apart. */
.se-type-icon-sm {
  width: 36px;
  height: 36px;
  border-radius: var(--se-radius-control);
}

.se-type-icon-sm svg {
  width: 20px;
  height: 20px;
}

/* The side panel (mockup `.drawer`) - an ordinary bordered surface, never a
   bottom sheet: this screen is checked at 1280px only (same reasoning as
   `.se-app`/`.se-side` above), so there is no phone-width state for it to
   collapse into the way `.se-sheet` does for the booker. */
.se-drawer {
  background-color: var(--se-surface);
  border: var(--se-border);
  border-radius: var(--se-radius-card);
  padding: var(--se-space-4);
}

/* Exactly one panel (or the default "New item"/"New type" one) is ever
   shown - the same `:target` idiom `.se-board-panel` already uses, for the
   same reason: a real `<a href="#...">` picks which one, no JS, no second
   template per entity. `.se-drawer-panel-default` is the one panel shown
   when nothing is targeted yet (a fresh "Items & prices" visit), hidden the
   moment any other panel becomes the target - the same `:has()` trick
   `.se-board-panel-empty` already relies on. */
.se-drawer-panel {
  display: none;
}

.se-drawer-panel:target {
  display: block;
}

.se-drawer-panel-default {
  display: block;
}

.se-drawer:has(.se-drawer-panel:target) .se-drawer-panel-default {
  display: none;
}

/* briefs/next-version-16b-laptop-layout.md - the CEO, 2026-11-12, on the
   public demo opened on a laptop: "It shows as if on a phone while on the
   laptop" and "The window is higher than the viewpoint".

   1. Every page fits the screen, demo notice included. `body` becomes one
      column - the notice (when there is one), then the session shell - and
      the shell takes whatever height is left instead of a full 100vh of
      its own below the notice. The shells' own 100vh floors step aside. */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}
body > .se-phone-shell,
body > .se-app,
body > .se-shell {
  flex: 1 0 auto;
  min-height: 0;
  width: 100%;
}
.se-app {
  align-items: stretch;
}
.se-side {
  min-height: 0;
}
@media (min-width: 768px) {
  .se-nav {
    min-height: 0;
  }
}

/* 2. On a phone the booker's tab bar stays on screen while the page
      scrolls, as a phone app's own does. */
.se-tabbar {
  position: sticky;
  bottom: 0;
  z-index: 5;
}

/* 3. From tablet width up the booker gets a web layout, not a phone in the
      middle of the screen: one full-width top bar - brand left, the same
      three links in the middle, initials right - and the content in a
      centred column. Pure CSS over the one shell `_layout.html` already
      draws: the tab bar's `<nav>` is placed into the top bar's grid row,
      so there is still exactly one set of links in the page. */
@media (min-width: 768px) {
  .se-phone-shell {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr;
  }
  .se-phone-top {
    grid-row: 1;
    grid-column: 1;
    padding: var(--se-space-2) var(--se-space-5);
  }
  .se-tabbar {
    grid-row: 1;
    grid-column: 1;
    position: sticky;
    top: var(--se-space-1);
    bottom: auto;
    z-index: 6;
    align-self: center;
    justify-self: center;
    gap: var(--se-space-1);
    padding: 0;
    border-top: none;
    background: none;
  }
  .se-tab {
    flex: none;
    flex-direction: row;
    gap: var(--se-space-1);
    min-height: 40px;
    padding: 0 var(--se-space-3);
    border-radius: var(--se-radius-control);
    font-size: var(--se-size-body);
  }
  .se-tab::before {
    width: 18px;
    height: 18px;
  }
  .se-tab:hover {
    color: var(--se-accent);
  }
  .se-tab.is-active {
    background-color: var(--se-accent-soft);
  }
  .se-phone-shell .se-content {
    grid-row: 2;
    grid-column: 1;
    width: 100%;
    max-width: 880px;
    margin: 0 auto;
    padding: var(--se-space-6) var(--se-space-5);
  }
  /* The Book screen's type cards, two to a row; an odd last card ("Anything
     that fits") takes the whole row. */
  .se-type-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--se-space-2);
  }
  .se-type-grid > .se-type-card {
    margin-bottom: 0;
  }
  .se-type-grid > .se-type-card:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
  /* A name or password field the full column wide reads as a banner,
     not a field. */
  .se-phone-shell .se-content .form-control,
  .se-phone-shell .se-content .form-select {
    max-width: 480px;
  }
}

/* 4. Bootstrap's `.form-check .form-check-input` (two classes) outranks the
      `.form-check-input` rule above, so its `margin-left: -1.5em` survived
      and pulled every checkbox and radio half outside its column. Same
      reset, at Bootstrap's own weight. */
.form-check .form-check-input {
  float: none;
  margin: 0;
}

/* 5. A signed-out visitor's rail holds one link, "Sign in" - on a page
      whose whole content already is the sign-in card (or its forgot/reset
      siblings) it is an empty column beside the card. Hidden there only. */
.se-shell:has(.se-auth-card) > .se-nav {
  display: none;
}
.se-shell:has(.se-auth-card) > .se-content {
  flex: 1;
  max-width: none;
}
