/*
 * Pullman Panwa Amenity Recorder - design system
 * ---------------------------------------------------------------------------
 * Hand-written, dependency-free CSS. Organised in layers so component styles
 * always beat the reset and utilities always win over components:
 *
 *   @layer reset, tokens, base, components, utilities;
 *
 * No framework (no Bootstrap, no Tailwind). Reasons, for the record:
 *  - zero runtime dependencies and no Node build step in the deploy path;
 *  - strict CSP with locally vendored assets, no CDN and no runtime <style>
 *    injection (which is what Tailwind's Play CDN would force us into);
 *  - ~5 screens with one stable design do not justify a utility-CSS toolchain.
 *
 * Phase 1 keeps this deliberately small: tokens, reset, and just enough
 * components for the login and scaffold pages. Phases 4-6 grow it.
 */

@layer reset, tokens, base, components, utilities;

/* ===========================================================================
   TOKENS
   ======================================================================== */
@layer tokens {
  :root {
    /* Brand: Pullman = deep navy; Panwa = warm coastal sand. */
    --navy-900: #0b1c2c;
    --navy-800: #12293e;
    --navy-700: #1b3955;
    --navy-600: #24506f;
    --sand-100: #faf6f0;
    --sand-200: #f1e9dc;
    --sand-300: #e2d5c1;
    --gold-500: #b4863a;
    --gold-400: #c9a04f;
    --teal-600: #12756f;
    --teal-500: #1a9188;
    --red-600: #b3261e;
    --green-600: #1f7a4d;

    /* Chart series, in draw order. Charts read these from here (reports.js) and so do the
       swatches beside the legend table, so a colour cannot mean two things on one card.
       Light-on-dark variants are remapped with the rest of the dark theme below. */
    --series-0: var(--teal-600);
    --series-1: var(--navy-600);
    --series-2: var(--gold-500);
    --series-3: var(--teal-500);
    --series-4: var(--navy-800);
    --series-5: var(--gold-400);
    --series-6: #7a5c9e;
    --series-7: #a1543e;

    /* Semantic surface tokens - remapped wholesale in the dark theme. */
    --surface: #f6f7f9;
    --surface-raised: #ffffff;
    --surface-sunken: var(--sand-100);
    --surface-brand: var(--navy-800);
    --border: #dfe3e8;
    --border-strong: #c3cad3;
    --text: #172029;
    --text-muted: #5b6675;
    --text-on-brand: #eef2f7;
    --accent: var(--teal-600);
    --accent-contrast: #ffffff;
    --focus-ring: color-mix(in srgb, var(--teal-500) 60%, transparent);

    /* Type scale - fluid, so no media queries are needed for headings. */
    --font-sans: "Inter", "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
      "Helvetica Neue", Arial, sans-serif;
    --font-mono: "JetBrains Mono", "Cascadia Mono", ui-monospace, "SFMono-Regular", Consolas,
      monospace;
    --text-xs: 0.75rem;
    --text-sm: 0.8125rem;
    --text-base: 0.9375rem;
    --text-lg: clamp(1.0625rem, 0.4vw + 0.95rem, 1.1875rem);
    --text-xl: clamp(1.25rem, 0.7vw + 1.05rem, 1.5rem);
    --text-2xl: clamp(1.5rem, 1.2vw + 1.15rem, 1.9rem);
    --leading-tight: 1.25;
    --leading-normal: 1.55;

    /* Spacing - 4px base grid. */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-8: 3rem;

    /* Shape and depth. */
    --radius-sm: 6px;
    --radius-md: 10px;
    --radius-lg: 14px;
    --radius-pill: 999px;
    --shadow-sm: 0 1px 2px rgb(15 30 45 / 8%);
    --shadow-md: 0 4px 14px rgb(15 30 45 / 10%);
    --shadow-lg: 0 12px 32px rgb(15 30 45 / 16%);

    /* Layout. */
    --page-max: 1240px;
    --control-height: 2.5rem;
    --transition: 140ms ease;

    color-scheme: light;
  }

  /* Dark theme: only the semantic tokens change. Charts read these too. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --surface: #0e1620;
      --surface-raised: #16212e;
      --surface-sunken: #111b26;
      --surface-brand: #0a1219;
      --border: #263341;
      --border-strong: #38495a;
      --text: #e8edf3;
      --text-muted: #9aa8b8;
      --text-on-brand: #e8edf3;
      --accent: var(--teal-500);
      --focus-ring: color-mix(in srgb, var(--teal-500) 70%, transparent);
      --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
      --shadow-md: 0 4px 14px rgb(0 0 0 / 45%);
      --shadow-lg: 0 12px 32px rgb(0 0 0 / 55%);

      /* The series ramp again, lifted. The light ramp's navy on a #16212e card is a shape
         nobody can see, and a chart nobody can read is not a chart. */
      --series-0: #2fa79b;
      --series-1: #6f9dc4;
      --series-2: #d8a854;
      --series-3: #46bfb2;
      --series-4: #8fb4d6;
      --series-5: #e0be76;
      --series-6: #b493d6;
      --series-7: #d98570;

      color-scheme: dark;
    }
  }
}

/* ===========================================================================
   RESET
   ======================================================================== */
@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  * {
    margin: 0;
  }

  html {
    -webkit-text-size-adjust: 100%;
  }

  body {
    min-height: 100dvh;
    -webkit-font-smoothing: antialiased;
  }

  img,
  picture,
  svg,
  canvas {
    display: block;
    max-width: 100%;
  }

  input,
  button,
  textarea,
  select {
    font: inherit;
    color: inherit;
  }

  h1,
  h2,
  h3,
  h4 {
    font-weight: 600;
    line-height: var(--leading-tight);
    text-wrap: balance;
  }

  p {
    text-wrap: pretty;
  }

  :where(a) {
    color: var(--accent);
  }

  /*
   * The `hidden` attribute must always win.
   *
   * This is a real bug fix, not tidiness: `.cell { display: block }` is an author rule,
   * and the browser's own `[hidden] { display: none }` is a user-agent rule, so the
   * class silently overrode it. Clicking a note cell therefore showed the input *and*
   * left the button visible, which is what made an inline edit look like a second box
   * stacked underneath the first.
   */
  [hidden] {
    display: none !important;
  }

  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}

/* ===========================================================================
   BASE
   ======================================================================== */
@layer base {
  body {
    display: block;
    padding: var(--space-6) var(--space-4) var(--space-4);
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: var(--text);
    background-color: var(--surface);
    background-image: radial-gradient(
        1200px 400px at 15% -10%,
        color-mix(in srgb, var(--teal-500) 8%, transparent),
        transparent
      ),
      radial-gradient(900px 360px at 100% 0%, color-mix(in srgb, var(--gold-400) 10%, transparent), transparent);
  }

  .page {
    width: 100%;
    max-width: var(--page-max);
    margin: var(--space-6) auto 0;
  }

  .app-footer {
    width: 100%;
    max-width: var(--page-max);
    margin: var(--space-6) auto 0;
  }

  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
  }

  code {
    padding: 0.1em 0.35em;
    font-family: var(--font-mono);
    font-size: 0.9em;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
  }

  .skip-link {
    position: absolute;
    left: -9999px;
  }

  .skip-link:focus {
    left: var(--space-4);
    top: var(--space-4);
    z-index: 100;
    padding: var(--space-2) var(--space-3);
    background: var(--surface-raised);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
  }

  /* --- Masthead --------------------------------------------------------- */
  /*
   * Full-bleed with a contained inner row, so it reads as a slim app bar rather than
   * a centred card, and a subtle translucent surface keeps it from competing with the
   * content. sticky keeps "Sign out" and the day context reachable while scrolling.
   */
  .app-header {
    position: sticky;
    top: 0;
    z-index: 20;
    margin: calc(var(--space-6) * -1) calc(var(--space-4) * -1) var(--space-4);
    padding: var(--space-2) var(--space-4);
    background: color-mix(in srgb, var(--surface-raised) 88%, transparent);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--border);
  }

  .app-header__inner {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    align-items: center;
    justify-content: space-between;
    max-width: var(--page-max);
    margin-inline: auto;
  }

  .app-header__brand {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    color: inherit;
    text-decoration: none;
  }

  .app-header__mark {
    display: grid;
    place-items: center;
    width: 1.9rem;
    height: 1.9rem;
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--sand-100);
    background: var(--navy-800);
    border-radius: var(--radius-sm);
  }

  .app-header__name {
    display: flex;
    flex-direction: column;
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1.15;
  }

  .app-header__place {
    font-size: var(--text-xs);
    font-weight: 400;
    color: var(--text-muted);
  }

  .app-nav {
    display: flex;
    gap: var(--space-1);
    align-items: center;
  }

  .app-nav__form {
    display: inline;
  }

  .app-nav__link {
    display: inline-block;
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-sm);
    color: var(--text-muted);
    text-decoration: none;
    border-radius: var(--radius-md);
    transition: color var(--transition), background-color var(--transition);
  }

  .app-nav__link:hover {
    color: var(--text);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
  }

  .app-nav__link.is-current {
    color: var(--accent-contrast);
    background: var(--accent);
  }

  .app-nav__link--button {
    font: inherit;
    font-size: var(--text-sm);
    cursor: pointer;
    background: none;
    border: 0;
  }
}

/* ===========================================================================
   COMPONENTS
   ======================================================================== */
@layer components {
  .card {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-6);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
  }

  .card--narrow {
    max-width: 26rem;
    margin: var(--space-8) auto 0;
  }

  .card__title {
    font-size: var(--text-2xl);
    letter-spacing: -0.01em;
  }

  .card__lead {
    max-width: 62ch;
    color: var(--text-muted);
  }

  .kv {
    display: grid;
    grid-template-columns: minmax(8rem, max-content) 1fr;
    gap: var(--space-2) var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border);
  }

  .kv dt {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-muted);
  }

  .kv dd {
    font-size: var(--text-sm);
  }

  .form {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  .field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
  }

  .field__label {
    font-size: var(--text-sm);
    font-weight: 600;
  }

  .input {
    height: var(--control-height);
    padding-inline: var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    transition: border-color var(--transition), box-shadow var(--transition);
  }

  .input:hover {
    border-color: color-mix(in srgb, var(--border-strong) 60%, var(--text-muted));
  }

  .input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--focus-ring);
    outline: none;
  }

  .btn {
    display: inline-flex;
    gap: var(--space-2);
    align-items: center;
    justify-content: center;
    min-height: var(--control-height);
    padding-inline: var(--space-4);
    font-size: var(--text-sm);
    font-weight: 600;
    cursor: pointer;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    transition: background-color var(--transition), border-color var(--transition),
      transform var(--transition);
  }

  .btn:active {
    transform: translateY(1px);
  }

  .btn--primary {
    color: var(--accent-contrast);
    background: var(--accent);
  }

  .btn--primary:hover {
    background: color-mix(in srgb, var(--accent) 86%, black);
  }

  .alert {
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-sm);
    border: 1px solid;
    border-radius: var(--radius-md);
  }

  .alert--error {
    color: var(--red-600);
    background: color-mix(in srgb, var(--red-600) 8%, var(--surface-raised));
    border-color: color-mix(in srgb, var(--red-600) 35%, var(--border));
  }

  .app-footer {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    align-items: baseline;
    justify-content: space-between;
    padding-top: var(--space-4);
    font-size: var(--text-xs);
    color: var(--text-muted);
    border-top: 1px solid var(--border);
  }

  /* --- Layout helpers used by the docs and token pages ---------------- */
  .page > .card + .card {
    margin-top: var(--space-5);
  }

  .section-title {
    margin-top: var(--space-5);
    font-size: var(--text-lg);
  }

  .muted {
    color: var(--text-muted);
  }

  /* --- Code and snippets ---------------------------------------------- */
  .code-block {
    padding: var(--space-4);
    margin-top: var(--space-2);
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    line-height: 1.6;
    color: var(--text);
    white-space: pre;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
  }

  .code-block--token {
    font-size: var(--text-sm);
    color: var(--navy-900);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    background: color-mix(in srgb, var(--gold-400) 22%, var(--surface-raised));
    border-color: var(--gold-500);
  }

  /* --- Tables ---------------------------------------------------------- */
  .table {
    width: 100%;
    margin-top: var(--space-3);
    font-size: var(--text-sm);
    border-collapse: collapse;
  }

  .table th,
  .table td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--border);
  }

  .table thead th {
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    border-bottom: 2px solid var(--border-strong);
  }

  .table tbody tr:hover {
    background: color-mix(in srgb, var(--accent) 4%, transparent);
  }

  /* --- Callouts and badges -------------------------------------------- */
  .callout {
    padding: var(--space-4);
    margin-block: var(--space-4);
    border: 1px solid;
    border-radius: var(--radius-md);
  }

  .callout--warning {
    background: color-mix(in srgb, var(--gold-400) 14%, var(--surface-raised));
    border-color: var(--gold-500);
  }

  .callout__title {
    font-size: var(--text-lg);
  }

  .badge {
    /* A badge is a label, not a paragraph: "catch-all" was breaking over two lines in a
       narrow column, which made the row taller than every other row. */
    display: inline-block;
    white-space: nowrap;
    padding: 0.1em 0.6em;
    font-size: var(--text-xs);
    font-weight: 600;
    border-radius: var(--radius-pill);
  }

  .badge--ok {
    color: var(--green-600);
    background: color-mix(in srgb, var(--green-600) 12%, var(--surface-raised));
  }

  .badge--muted {
    color: var(--text-muted);
    background: var(--surface-sunken);
  }

  /* --- Inline forms and small buttons --------------------------------- */
  /*
   * `display: flex` is set here rather than relied upon from `.form`: this modifier is
   * also used on forms that carry no `.form` class, and without it the modifier did
   * nothing at all - the field and its button stacked on two lines.
   */
  .form--inline {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-3);
  }

  .field--grow {
    flex: 1 1 16rem;
  }

  .field__hint {
    font-size: var(--text-xs);
    color: var(--text-muted);
  }

  .field__error {
    font-size: var(--text-xs);
    color: var(--red-600);
  }

  .inline-form {
    display: inline;
  }

  .btn--small {
    min-height: 1.9rem;
    padding-inline: var(--space-3);
    font-size: var(--text-xs);
  }

  .btn--danger {
    color: var(--red-600);
    background: transparent;
    border-color: color-mix(in srgb, var(--red-600) 40%, var(--border));
  }

  .btn--danger:hover {
    color: #fff;
    background: var(--red-600);
    border-color: var(--red-600);
  }

  /* --- Flash messages -------------------------------------------------- */
  /*
   * Django's messages are moved into the toast area by entry.js (see
   * adoptServerMessages). These rules are the no-JavaScript fallback, so they are
   * positioned out of the document flow regardless - an inline banner pushed the whole
   * page down on every save, which moved the form under the user's cursor.
   */
  .messages {
    position: fixed;
    top: var(--space-4);
    left: 50%;
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    width: min(30rem, calc(100vw - 2rem));
    padding: 0;
    margin: 0;
    list-style: none;
    transform: translateX(-50%);
  }

  .messages li {
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-sm);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-left: 4px solid var(--accent);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
  }

  .messages li.success {
    border-left-color: var(--green-600);
  }

  .messages li.error {
    border-left-color: var(--red-600);
  }

  .messages li.warning {
    border-left-color: var(--gold-500);
  }

  /* =========================================================================
     ENTRY SCREEN
     ====================================================================== */

  /*
   * Two columns: the add form on the left, the amenity log on the right.
   *
   * The log is the reason anyone opens this page, so it gets the wide column and
   * starts at the top of the page instead of being pushed below the fold by the form.
   * Below 62rem the two stack, with the log first - on a phone you want to see what has
   * been recorded without scrolling past the form to find it.
   */
  .entry-layout {
    display: grid;
    grid-template-columns: minmax(17rem, 21rem) minmax(0, 1fr);
    gap: var(--space-4);
    align-items: start;
  }

  /*
   * The whole form has to fit one viewport at 100% zoom, or the buttons fall below the
   * fold and "record" needs a scroll. That budget is tight - masthead, padding, the day
   * header, four fields, two buttons and a hint - so the panel is deliberately denser
   * than the standard card and the hint is one line instead of three.
   */
  .card--compact {
    gap: var(--space-2);
    padding: var(--space-4);
  }

  .card--compact .card__title {
    font-size: var(--text-xl);
  }

  .entry-layout__form {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
  }

  /*
   * Below 72rem the two stack, with the log second in the DOM but ordered after the form.
   *
   * This used to be 62rem, which kept the side-by-side layout on windows where the log
   * column had only ~650px to work with - too narrow for a five-column table to be
   * comfortable, and the reason a horizontal scrollbar appeared on an ordinary laptop
   * window. Stacking at 72rem gives the log the full page width (up to 1208px) as soon as
   * the two cannot both be roomy, which is a better trade than squeezing both.
   */
  @media (max-width: 72rem) {
    .entry-layout {
      grid-template-columns: minmax(0, 1fr);
    }

    /* Stacked: form first so the primary action is reachable without scrolling. */
    .entry-layout__form {
      order: 1;
    }

    .entry-layout__log {
      order: 2;
    }
  }

  .card--sticky {
    position: sticky;
    top: 4.5rem; /* clears the sticky masthead */
  }

  .entry-card__head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    align-items: flex-start;
    justify-content: space-between;
  }

  .entry-card__sub {
    margin-top: var(--space-1);
    font-size: var(--text-sm);
  }

  /* --- The entry form ---------------------------------------------------- */
  .entry-form {
    padding-top: var(--space-2);
    margin-top: 0;
    border-top: 1px solid var(--border);
  }

  /*
   * One field per row. In a narrow column a multi-column form pushed the buttons off
   * the bottom of the card, which is what made the page feel like the log had been
   * shoved out of view.
   */
  .entry-form__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2);
    align-items: end;
  }

  /* Denser controls inside the entry panel only; the rest of the app keeps its size. */
  .entry-card .field {
    gap: 0.15rem;
  }

  .entry-card .field__label {
    font-size: var(--text-xs);
  }

  .entry-card .input {
    min-height: 2.25rem;
  }

  .entry-card kbd {
    padding: 0 0.25em;
    font-size: 0.75em;
  }

  .entry-form__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    margin-top: var(--space-1);
  }

  .entry-form__actions .btn {
    min-height: 2.25rem;
    padding-inline: var(--space-3);
  }

  /*
   * One chord per line. On one line with middle dots the three read as a single sentence
   * and you have to work out where each one starts; as a list each is a rule of its own.
   * Kept tight - a low leading and no bullet indent - so three lines cost little height.
   */
  .entry-form__hint {
    display: grid;
    gap: 0.1rem;
    margin: var(--space-1) 0 0;
    padding: 0;
    font-size: var(--text-xs);
    line-height: 1.35;
    color: var(--text-muted);
    list-style: none;
  }

  .entry-form__hint li {
    display: flex;
    gap: var(--space-1);
    align-items: baseline;
  }

  /* --- The day picker in the panel header -------------------------------- */
  /*
   * Back a day, forward a day, or jump to today. The numbered day strip that used to sit
   * under this was removed: seven buttons competing with the date field above them, and
   * the field already reaches any day in the window.
   *
   * Centred, so the three sit as one control under the full-width field rather than
   * hanging off its left edge.
   */
  .daynav-bar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    align-items: center;
    justify-content: center;
    margin-top: var(--space-2);
  }

  /* An unavailable step is shown, disabled, rather than vanishing and shifting the row. */
  .btn.is-disabled {
    opacity: 0.4;
    cursor: default;
    pointer-events: none;
  }

  /*
   * Any date, not just the default range: a text field plus Go, so a correction or a late
   * entry does not depend on stepping a day at a time.
   *
   * It is NOT <input type="date">: that is a calendar button which refuses anything but
   * a complete ISO date, and the front desk types "1610". entry-date.js expands the
   * shorthand and reports what it understood in the hint below.
   */
  .date-jump {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-top: var(--space-1);
  }

  .date-jump__label {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
  }

  .date-jump__row {
    display: flex;
    gap: var(--space-1);
    align-items: center;
  }

  .date-jump__input {
    flex: 1 1 auto;
    min-width: 0;
    height: 1.9rem;
    padding-inline: var(--space-2);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
  }

  .date-jump__input:focus-visible {
    border-color: var(--accent);
    outline: none;
    box-shadow: 0 0 0 3px var(--focus-ring);
  }

  /* A date the server could not read: the field says so, and the hint says why. */
  .date-jump__input.is-invalid {
    border-color: var(--red-600);
    background: var(--surface-raised);
  }

  /*
   * The hint carries the meaning of the shorthand. Without it "1610" is a puzzle; with
   * it, the expansion is readable before Go is pressed, which is what makes a typed
   * four-digit date safe to rely on.
   */
  .date-jump__hint {
    margin: 0;
    min-height: 1rem;
    font-size: var(--text-xs);
    color: var(--text-muted);
  }

  .date-jump__hint.is-ok {
    color: var(--teal-600);
    font-weight: 600;
  }

  .date-jump__hint.is-error {
    color: var(--red-600);
    font-weight: 600;
  }

  /*
   * Far from today: recordable, but worth a second look. Amber rather than red - nothing
   * has been refused, and dressing an advisory as an error teaches people to ignore it.
   */
  .date-jump__hint.is-warn,
  .notice--warn {
    color: var(--gold-500);
    font-weight: 600;
  }

  .notice {
    margin: var(--space-1) 0 0;
    font-size: var(--text-xs);
    line-height: 1.4;
  }

  .list-card {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  .list-scroll {
    /* The whole point: the table is visible from the top of the page and scrolls in
       its own box, so a busy day does not push the form off screen. */
    max-height: calc(100vh - 16rem);
    overflow-y: auto;
    /*
     * No horizontal scrolling here, ever. This is belt-and-braces: the table's own
     * overflow was fixed at its sources (percentage column widths, and an actions cell
     * whose two buttons fit their declared width - see .table--rows), so there is
     * nothing left to scroll. It stays because a scroll container that can move
     * sideways is a hazard in a table people click into - the browser scrolls to reveal
     * a focused input, dragging every row with it.
     *
     * One honest caveat: per css-overflow-3, when the other axis is `auto` this `clip`
     * computes to `hidden`, so the box is horizontally scrollable in principle either
     * way. The guarantee that matters is that nothing overflows in the first place.
     */
    overflow-x: clip;
    overscroll-behavior: contain;
  }

  .list-scroll .table--rows thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface-raised);
    box-shadow: inset 0 -2px 0 var(--border-strong);
  }

  .list-card__hint {
    margin-top: var(--space-3);
    font-size: var(--text-xs);
  }

  .btn--ghost {
    color: var(--text);
    background: var(--surface-raised);
    border-color: var(--border-strong);
  }

  .btn--ghost:hover {
    background: color-mix(in srgb, var(--accent) 8%, var(--surface-raised));
  }

  .btn--ghost.is-active {
    color: var(--accent-contrast);
    background: var(--accent);
    border-color: var(--accent);
  }

  kbd {
    display: inline-block;
    padding: 0 0.35em;
    font-family: var(--font-mono);
    font-size: 0.85em;
    line-height: 1.5;
    color: var(--text-muted);
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-bottom-width: 2px;
    border-radius: 4px;
  }

  .kbd-hint {
    margin-left: var(--space-1);
    opacity: 0.75;
  }

  /* --- The row list ------------------------------------------------------ */
  .list-card__head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    align-items: baseline;
    justify-content: space-between;
  }

  /*
   * A fixed layout is what keeps a row still while one of its cells is edited.
   *
   * With the browser's automatic table layout, every column was sized from its content -
   * so swapping a value button for an `<input>` re-sized that column. A text input
   * carries its own intrinsic width (default `size=20`), and a date control has a
   * minimum of its own, both of them wider than the short value they replaced. The whole
   * row therefore re-laid-out the moment a cell was clicked, which slid the cell being
   * edited sideways - and that is the "row shifts left" report.
   *
   * PERCENTAGES, NOT rem. This is the part that took several attempts, so the reasoning
   * is worth recording. The columns used to carry rem widths (8.5rem, 11rem, ...), and
   * with those the table measured **822px inside a 632px container** - it overflowed and
   * the columns stayed at their declared sizes instead of scaling down. Under
   * `table-layout: fixed` the sum of the column widths plus cell padding acts as a floor
   * the table will not go below, so any generous rem width reintroduced the overflow.
   *
   * Percentages have no such floor: they are resolved against the table's own width, so
   * the table is exactly its container and the columns stay in proportion at every size.
   * Fixed layout therefore gives stability AND fit, which rem widths could not do at once.
   *
   * Measured on the live page: with `auto` the amenity column came out at 337.8px (sized
   * by its longest value), which is what made the columns move. These percentages pin it.
   */
  .table--rows {
    table-layout: fixed;
    width: 100%;
    max-width: 100%;
    /* Only a sanity limit, far below any real container, so it never forces overflow. */
    min-width: 18rem;
  }

  .table--rows td {
    padding: 0;
    vertical-align: middle;
  }

  .table--rows th {
    padding: var(--space-2) var(--space-3);
  }

  /* --- Column widths: all percentages, and they must total under 100% --- */
  /*
   * A table has no "remaining space" column the way a grid does, so the widths have to be
   * stated as shares that add up. `col-notes` is deliberately undeclared and absorbs what
   * is left. Change these and they must still total well under 100, or the browser scales
   * them and the proportions drift.
   */
  .col-select {
    width: 6%;
  }

  /*
   * 16%. The date cell holds a native date control while it is being edited, and that
   * control has a real minimum width of roughly 128px - so this is the one share with a
   * hard reason behind the number. At 16% it clears 128px from a 800px table upwards;
   * below that it compresses and the control truncates, which is the unavoidable cost of
   * proportional columns on a narrow screen.
   */
  .col-date {
    width: 16%;
  }

  /* "ALL Accor - Limitless" is the longest real status; it truncates below this. */
  .col-tier {
    width: 18%;
  }

  /* "Afternoon tea Western set" is the longest catalogue name. It truncates with an
     ellipsis and carries the full value as a title, so this is a readable share rather
     than a required one - which is why it gives up width to the date column. */
  .col-amenity {
    width: 18%;
  }

  /* Notes takes whatever is left, and is deliberately left undeclared so it absorbs the
     slack - the one column that benefits from being wide. */

  /*
   * Two 2.1rem icon buttons plus the gap between their boxes.
   *
   * This one is a rem width rather than a percentage on purpose: the buttons do not shrink,
   * so a percentage would eventually be narrower than the pair and clip the 🗑 button -
   * which is its own reported bug. `--col-actions-w` is shared with the narrow-viewport
   * override below so the two cannot disagree.
   */
  :root {
    --col-actions-w: 4.5rem;
  }

  .col-actions {
    width: var(--col-actions-w);
    text-align: right;
    white-space: nowrap;
  }

  .col-actions > * {
    display: inline-flex;
    vertical-align: middle;
  }

  .col-actions > * + * {
    margin-left: 0.25rem;
  }

  /* --- Editable cells ---------------------------------------------------- */
  .cell {
    display: block;
    width: 100%;
    /* Pinned to the same height as .cell-input below, so swapping the two never
       changes the row height - see the note on .cell-input. */
    height: 2.4rem;
    padding: var(--space-2) var(--space-3);
    font: inherit;
    font-size: var(--text-sm);
    color: var(--text);
    text-align: left;
    cursor: text;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    transition: background-color var(--transition), border-color var(--transition);
  }

  .cell:hover {
    background: color-mix(in srgb, var(--accent) 8%, transparent);
    border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
  }

  .cell.is-empty {
    font-style: italic;
  }

  /*
   * Truncate rather than push the table wider.
   *
   * `table-layout: fixed` sizes columns from these widths, but a cell whose content is
   * wider than its column still overflows visually - a long amenity name or a note would
   * spill into the next column. Clipping with an ellipsis is what keeps the table inside
   * its box, and since a click replaces the button with an input, the full value is
   * always one click away. The title carries it on hover in the meantime.
   */
  .cell--amenity,
  .cell--tier,
  .cell--notes {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .cell.is-hidden {
    /* Belt and braces alongside the [hidden] rule above: keep the button's box from
       reserving space while its input is showing. */
    display: none;
  }

  .cell-input {
    display: block;
    width: 100%;
    /* Never wider than the cell it belongs to: an input that outgrows its column would
       cover the neighbouring cell - which is how the row's checkbox became unclickable. */
    max-width: 100%;
    /*
     * Pinned to the same height as .cell, so opening an editor never moves the row.
       Measured on the live page: the value button is 38px tall while a native date
       control is 40px - without this, every date edit bounced the rows below it down
       2px and back. Text inputs already matched; the pin covers them all.
     */
    height: 2.4rem;
    padding: var(--space-2) var(--space-3);
    font: inherit;
    font-size: var(--text-sm);
    color: var(--text);
    background: var(--surface-raised);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    box-shadow: 0 0 0 3px var(--focus-ring);
  }

  .cell-input.is-editing {
    /* Matches the cell's own padding exactly, so the swap does not shift the row. */
    margin: 0;
  }

  .btn--icon {
    min-width: 2.1rem;
    min-height: 2.1rem;
    padding: 0;
    font-size: var(--text-sm);
    line-height: 1;
    background: var(--surface-sunken);
    border-color: var(--border);
  }

  .btn--icon:hover {
    background: color-mix(in srgb, var(--accent) 14%, var(--surface-raised));
    border-color: var(--accent);
  }

  /*
   * Declared *after* .btn--icon:hover on purpose. The two selectors have the same
   * specificity, so the later rule wins - and with the danger rule first, hovering a
   * delete icon repainted it in the accent tint: the button went white and the glyph
   * disappeared into it. A control that deletes stays red while it is pointed at.
   */
  .btn--icon.btn--danger:hover,
  .btn--icon.btn--danger-ghost:hover {
    color: #fff;
    background: var(--red-600);
    border-color: var(--red-600);
  }

  .btn--danger-ghost:hover {
    color: #fff;
    background: var(--red-600);
    border-color: var(--red-600);
  }

  .empty-state {
    padding: var(--space-6) var(--space-3);
    font-size: var(--text-sm);
    color: var(--text-muted);
    text-align: center;
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
  }

  /* =========================================================================
     LOG, REPORTS AND SETTINGS
     ====================================================================== */

  /* --- Filter bars (log and reports) ------------------------------------ */
  .filter-form__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
    gap: var(--space-2);
    align-items: end;
  }

  .filter-form__search {
    grid-column: span 2;
  }

  .filter-form__pickers {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    gap: var(--space-3);
    align-items: end;
    padding-top: var(--space-3);
    margin-top: var(--space-3);
    border-top: 1px solid var(--border);
  }

  .filter-form__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
  }

  .input--multi {
    height: auto;
    min-height: 5.5rem;
    padding: var(--space-1);
  }

  .input--select {
    padding-right: var(--space-2);
  }

  .switch {
    display: inline-flex;
    gap: var(--space-1);
    align-items: center;
    font-size: var(--text-xs);
    color: var(--text-muted);
    cursor: pointer;
  }

  .filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-top: var(--space-2);
  }

  .chip {
    padding: 0.1em 0.6em;
    font-size: var(--text-xs);
    color: var(--text);
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
  }

  /* --- One control for "when" -------------------------------------------
   * A segmented preset row plus its calendars, presented as a single unit so choosing
   * a preset and typing a date cannot coexist. The calendars are dimmed and disabled
   * outside Custom, so there is no hidden precedence rule to explain.
   */
  .range-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    padding-top: var(--space-3);
    margin-top: var(--space-3);
    border-top: 1px solid var(--border);
  }

  .range-row__presets {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    overflow: hidden;
  }

  .range-preset {
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
    background: var(--surface-raised);
    border: 0;
    border-right: 1px solid var(--border);
    transition: background-color var(--transition), color var(--transition);
  }

  .range-preset:last-child {
    border-right: 0;
  }

  .range-preset:hover {
    color: var(--text);
    background: color-mix(in srgb, var(--accent) 8%, var(--surface-raised));
  }

  .range-preset.is-active {
    color: var(--accent-contrast);
    background: var(--accent);
  }

  .range-row__dates {
    display: inline-flex;
    gap: var(--space-2);
    align-items: center;
  }

  .range-row__dash {
    color: var(--text-muted);
  }

  .range-row__dates .input {
    width: 10rem;
    min-height: 2.25rem;
    font-size: var(--text-xs);
  }

  /* A disabled calendar outside Custom makes the state visible, not just a tooltip. */
  .range-row__dates .input:disabled {
    opacity: 0.45;
    cursor: not-allowed;
  }

  .list-card__tools {
    display: flex;
    gap: var(--space-2);
    align-items: center;
  }

  /* --- Bulk action bar --------------------------------------------------- */
  .bulk-bar {
    position: sticky;
    top: 3.5rem; /* just under the masthead, so it follows the selection */
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-2);
    background: color-mix(in srgb, var(--gold-400) 18%, var(--surface-raised));
    border: 1px solid var(--gold-500);
    border-radius: var(--radius-md);
  }

  .bulk-bar[hidden] {
    display: none;
  }

  .bulk-bar__count {
    font-size: var(--text-sm);
  }

  .bulk-bar__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    align-items: center;
  }

  .bulk-bar .input {
    min-height: 1.9rem;
    font-size: var(--text-xs);
  }

  .input--narrow,
  .input--date {
    width: auto;
    min-width: 7.5rem;
  }

  .col-select {
    text-align: center;
  }

  .checkbox {
    width: 1rem;
    height: 1rem;
    accent-color: var(--accent);
  }

  .list-scroll--short {
    max-height: 24rem;
  }

  /* --- Pagination -------------------------------------------------------- */
  .pagination {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: center;
    justify-content: center;
    padding-top: var(--space-3);
    margin-top: var(--space-3);
    font-size: var(--text-sm);
    border-top: 1px solid var(--border);
  }

  .pagination__status {
    color: var(--text-muted);
  }

  /* --- KPI tiles --------------------------------------------------------- */
  .kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: var(--space-3);
  }

  .kpi {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    /* Roomier than a chip on purpose: the value is the point of the tile, and with the
       old padding the digits sat flush against the border and the whole row read as one
       block hanging off the filter panel above it. */
    padding: var(--space-4) var(--space-5);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
  }

  .kpi__value {
    font-size: var(--text-2xl);
    font-weight: 700;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
  }

  .kpi__value--text {
    font-size: var(--text-lg);
    line-height: 1.3;
  }

  .kpi__label {
    margin-top: var(--space-1);
    font-size: var(--text-xs);
    color: var(--text-muted);
  }

  .kpi__note {
    opacity: 0.85;
  }

  /* --- The reports page --------------------------------------------------
   * Sections on one page stack; the gap belongs to the section, not to whichever card
   * happens to follow. Without it the KPI tiles sat in the filter panel's lap: the two
   * were only 1rem apart, which read as one block. */
  .reports {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
  }

  /* --- Charts ------------------------------------------------------------ */
  .chart-wrap {
    position: relative;
    height: 320px;
    margin-top: var(--space-3);
  }

  .chart-wrap--short {
    height: 240px;
  }

  .chart-hint {
    margin-top: var(--space-2);
    font-size: var(--text-xs);
  }

  .report-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
    gap: var(--space-4);
    align-items: start;
  }

  .report-card {
    gap: var(--space-2);
    padding: var(--space-5);
  }

  .report-card__head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    align-items: baseline;
    justify-content: space-between;
  }

  .report-card__note {
    flex: 1 1 12rem;
    font-size: var(--text-xs);
    text-align: right;
  }

  /* --- Ranked list: a bar, a count and a share per amenity ----------------
   * The bar is relative to the busiest amenity (so the differences between the entries
   * below the leader stay visible); the share column states the same thing against the
   * period, which is the number a manager repeats. Both are computed on the server, from
   * the same records the pivot table uses.
   */
  .table--rank,
  .table--mix {
    margin-top: var(--space-1);
  }

  .table--rank .num,
  .table--mix .num {
    width: 4.5rem;
    font-variant-numeric: tabular-nums;
    text-align: right;
  }

  .table--rank tbody th,
  .table--mix tbody th {
    /* A row header is a label, not a table heading: keep the body weight and let the
       rule above separate the rows. */
    font-weight: 500;
    text-align: left;
    white-space: nowrap;
  }

  .rank-cell {
    /* Wide enough that the header above it does not wrap; the bar takes what is left. */
    min-width: 12rem;
    white-space: normal;
  }

  .rank-cell__label {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .rank-cell__bar {
    display: block;
    height: 0.4rem;
    margin-top: 0.2rem;
    background: linear-gradient(
      to right,
      var(--accent) 0,
      var(--accent) calc(var(--share, 0) * 1%),
      var(--surface-sunken) calc(var(--share, 0) * 1%),
      var(--surface-sunken) 100%
    );
    border-radius: var(--radius-pill);
  }

  .table--rank tfoot th,
  .table--mix tfoot th {
    text-align: left;
  }

  .rank-share {
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }

  /* --- VIP status mix ----------------------------------------------------- */
  .donut {
    /* Taller than a plain short chart: the ring is the card's centrepiece, and the hole
       in the middle carries the headline figure. */
    height: 220px;
    margin-top: var(--space-1);
  }

  /*
   * The centre of the ring. The canvas draws nothing here - it is a hole - so the total sits
   * in it as real markup, placed by CSS. It is deliberately the ONLY copy of this figure:
   * painting it into the canvas as well drew it twice, offset by a few pixels, which is what
   * "there is a duplicate number and a duplicate caption" looked like on screen.
   */
  .donut__centre {
    position: absolute;
    top: 50%;
    left: 50%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    line-height: 1.1;
    text-align: center;
    pointer-events: none;
    transform: translate(-50%, -50%);
  }

  .donut__value {
    font-size: var(--text-2xl);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
  }

  .donut__caption {
    margin-top: 0.15rem;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
  }

  .mix-swatch {
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    margin-right: var(--space-2);
    vertical-align: baseline;
    background: var(--swatch, var(--accent));
    border-radius: 3px;
  }

  @media (max-width: 46rem) {
    .report-card__note {
      text-align: left;
    }
  }

  /* --- Pivot table ------------------------------------------------------- */
  .pivot-scroll {
    margin-top: var(--space-3);
    overflow-x: auto;
  }

  .table--pivot th,
  .table--pivot td {
    padding: var(--space-2) var(--space-3);
    white-space: nowrap;
  }

  .table--pivot .num {
    font-variant-numeric: tabular-nums;
    text-align: right;
  }

  .table--pivot tbody th {
    font-weight: 500;
    text-align: left;
  }

  .table--pivot tfoot th,
  .table--pivot tfoot td {
    font-weight: 700;
    background: var(--sand-100);
    border-top: 2px solid var(--border-strong);
  }

  .table--pivot .total {
    font-weight: 600;
    background: color-mix(in srgb, var(--sand-200) 60%, transparent);
  }

  .pivot-note {
    margin-top: var(--space-3);
    font-size: var(--text-xs);
  }

  /* --- Settings: catalogue editing --------------------------------------
   * One dense row per entry, and the two catalogues side by side. The earlier layout
   * stacked two full-width five-column tables, which stretched a name field across the
   * whole viewport and pushed the second catalogue below the fold.
   */
  .settings-intro {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    align-items: center;
    justify-content: space-between;
  }

  .settings-intro__text {
    flex: 1 1 30rem;
  }

  .settings-intro__text .muted {
    margin-top: var(--space-1);
    font-size: var(--text-xs);
    line-height: 1.5;
  }

  .settings-search {
    flex: 0 1 16rem;
  }

  .settings-search .input {
    min-height: 2.25rem;
    font-size: var(--text-sm);
  }

  .catalogue-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(34rem, 1fr));
    gap: var(--space-4);
    align-items: start;
    margin-top: var(--space-4);
  }

  .catalogue-count {
    font-size: var(--text-xs);
  }

  /*
   * The list is a grid table: one shared track list for the header and every row, so the
   * Used, Status and Actions columns line up down the whole catalogue. Auto-sized tracks
   * could not do that - each row sized its own, which is why the numbers and badges
   * wandered from row to row.
   */
  .catalogue-table {
    margin-top: var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
  }

  .catalogue-head,
  .catalogue-item {
    display: grid;
    grid-template-columns: 2.6rem minmax(0, 1fr) 2.5rem 9rem 10.5rem;
    align-items: center;
    /* No gap: the cell borders are the separators, the way a table reads. */
    gap: 0;
  }

  .catalogue-head {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--border);
  }

  .catalogue-head > *,
  .catalogue-item > * {
    display: flex;
    align-items: center;
    min-width: 0;
    min-height: 2.1rem;
    padding: 0 var(--space-2);
  }

  /* Vertical rules between the cells, so each column is distinct. */
  .catalogue-head > * + *,
  .catalogue-item > * + * {
    border-left: 1px solid var(--border);
  }

  .catalogue-list {
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 0;
    margin: 0;
    list-style: none;
  }

  .catalogue-item {
    background: var(--surface-raised);
    border-bottom: 1px solid var(--border);
    transition: background-color var(--transition);
  }

  .catalogue-item:last-child {
    border-bottom: 0;
  }

  .catalogue-item:hover,
  .catalogue-item:focus-within {
    background: color-mix(in srgb, var(--accent) 6%, var(--surface-raised));
  }

  .catalogue-item[hidden] {
    display: none;
  }

  .catalogue-empty {
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
  }

  .catalogue-item.is-inactive .catalogue-item__name {
    color: var(--text-muted);
    text-decoration: line-through;
  }

  .catalogue-item.is-dragging {
    opacity: 0.45;
    background: color-mix(in srgb, var(--accent) 12%, transparent);
  }

  .catalogue-item__form {
    min-width: 0;
  }

  .catalogue-item__name {
    width: 100%;
  }

  .catalogue-item__usage {
    justify-content: flex-end;
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text);
    text-align: right;
    text-decoration: none;
  }

  .catalogue-item__usage.is-zero {
    color: var(--text-muted);
  }

  .catalogue-item__status {
    gap: 2px;
  }

  .catalogue-item__actions {
    justify-content: flex-end;
    gap: 2px;
    /* The buttons must stay inside the cell: five controls share one column. */
    flex-wrap: nowrap;
    overflow: hidden;
  }

  /* Compact buttons: five controls have to share one column width. */
  .catalogue-item__actions .btn {
    padding-inline: var(--space-2);
  }

  .catalogue-item__actions .btn--icon {
    min-width: 1.6rem;
    min-height: 1.6rem;
    font-size: var(--text-xs);
  }

  /* The row actions stay out of the way until the row is hovered or focused. */
  .catalogue-item__actions .btn {
    opacity: 0;
    transition: opacity var(--transition);
  }

  .catalogue-item:hover .catalogue-item__actions .btn,
  .catalogue-item:focus-within .catalogue-item__actions .btn {
    opacity: 1;
  }

  /*
   * Narrow windows: the five columns cannot hold their widths, so the actions drop onto
   * a second line of the same grid rather than overflowing the card.
   */
  @media (max-width: 46rem) {
    .catalogue-head {
      display: none;
    }

    .catalogue-item {
      grid-template-columns: 2.4rem minmax(0, 1fr) 2.5rem 7rem;
    }

    .catalogue-item__actions {
      grid-column: 2 / -1;
      justify-content: flex-end;
      border-left: 0;
      border-top: 1px solid var(--border);
    }

    .catalogue-item__actions .btn {
      opacity: 1;
    }
  }

  .settings-footnote {
    max-width: 60ch;
    margin-top: var(--space-4);
    font-size: var(--text-xs);
  }

  .rules {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding-left: var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-muted);
  }

  .table--catalogue td {
    vertical-align: middle;
  }

  .col-handle {
    width: 3.5rem;
    white-space: nowrap;
  }

  .drag-handle {
    color: var(--text-muted);
    cursor: grab;
    user-select: none;
  }

  .move-buttons {
    display: inline-flex;
    gap: 2px;
    margin-left: var(--space-1);
  }

  .btn--tiny {
    min-width: 1.3rem;
    min-height: 1.3rem;
    padding: 0;
    font-size: 0.6rem;
    line-height: 1;
  }

  .table--catalogue tr.is-dragging {
    opacity: 0.45;
    background: color-mix(in srgb, var(--accent) 12%, transparent);
  }

  .col-num {
    width: 5rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  .col-status {
    width: 11rem;
  }

  .input--inline {
    min-height: 2rem;
    padding-inline: var(--space-2);
    font-size: var(--text-sm);
    background: transparent;
    border-color: transparent;
  }

  .input--inline:hover {
    border-color: var(--border-strong);
  }

  .input--inline:focus {
    background: var(--surface-raised);
  }

  .catalogue-add {
    align-items: center;
    gap: var(--space-2);
    padding-top: var(--space-3);
    margin-top: var(--space-3);
    border-top: 1px solid var(--border);
  }

  /* The field takes the room; the button keeps its own, on the same line. */
  .catalogue-add .field--grow {
    flex: 1 1 auto;
    min-width: 0;
  }

  .catalogue-add .btn {
    flex: 0 0 auto;
  }

  .badge--info {
    color: var(--navy-700);
    background: color-mix(in srgb, var(--navy-600) 12%, var(--surface-raised));
  }

  .breadcrumb {
    font-size: var(--text-sm);
  }

  /* --- Native dialog ----------------------------------------------------- */
  .dialog {
    /*
     * `margin: auto` is doing real work here: the reset above sets `* { margin: 0 }`,
     * which removes the browsers' own centring rule, and a modal <dialog> with no margin
     * sits in the top-left corner. Centred, and never wider than the viewport.
     */
    margin: auto;
    max-width: min(26rem, calc(100vw - 2rem));
    padding: 0;
    color: var(--text);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
  }

  .dialog::backdrop {
    background: rgb(11 28 44 / 45%);
    backdrop-filter: blur(2px);
  }

  .dialog__form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-5);
  }

  .dialog__title {
    font-size: var(--text-lg);
  }

  .dialog__body {
    font-size: var(--text-sm);
    color: var(--text-muted);
  }

  .dialog__actions {
    display: flex;
    gap: var(--space-2);
    justify-content: flex-end;
  }
  /*
   * One fixed container for every transient message: Django's flash messages and the
   * results of inline actions both land here, so nothing ever takes part in the
   * document flow and the page cannot jump under the user's cursor.
   */
  .toasts {
    position: fixed;
    right: var(--space-4);
    bottom: var(--space-4);
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    width: min(26rem, calc(100vw - 2rem));
    pointer-events: none;
  }

  .toast {
    display: flex;
    gap: var(--space-3);
    align-items: center;
    justify-content: space-between;
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-sm);
    color: var(--text);
    pointer-events: auto;
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-left: 4px solid var(--accent);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    transition: opacity 160ms ease, transform 160ms ease;
  }

  .toast__text {
    flex: 1;
  }

  .toast--leaving {
    opacity: 0;
    transform: translateY(0.5rem);
  }

  .toast--success {
    border-left-color: var(--green-600);
  }

  .toast--warning {
    border-left-color: var(--gold-500);
  }

  .toast--error {
    border-left-color: var(--red-600);
  }

  .toast__action {
    flex: none;
    padding: 0.2rem 0.6rem;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--accent-contrast);
    cursor: pointer;
    background: var(--accent);
    border: 0;
    border-radius: var(--radius-sm);
  }

  .toast__action:hover {
    background: color-mix(in srgb, var(--accent) 85%, black);
  }

  .toast__close {
    flex: none;
    padding: 0 0.2rem;
    font-size: var(--text-lg);
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
    background: none;
    border: 0;
  }
}

/* ===========================================================================
   UTILITIES
   ======================================================================== */
@layer utilities {
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
