/* ===========================================================================
   admin.css — the shell, rebuilt from the reference admin.

   PROVENANCE. Every value in the token block below was MEASURED, not chosen
   and not read out of a document. `scratchpad/palette.py` sampled the real
   capture pixels (2.73M of them across screens 025, 011 and 115) and counted
   dominant colours. The pack's own 02-original-stack.md claims this is the
   Front template by Htmlstream and lists six hex values; all six verified at
   dE = 0.0. See EVIDENCE.md block 9 §8 and PRE-DESIGN-GATE.md §4.

   The measurement also found one the pack does NOT list: #6e71ff, present at
   the same share as the #377dff it calls "primary". It is the sidebar active
   pill. Building from the document alone would have painted every active nav
   item the wrong colour — which is the whole argument for opening the files.

   WHAT IS DELIBERATELY NOT COPIED. The reference carries nine action
   treatments, six status vocabularies, five primary-button placements and a
   table that clips its own last column. Those are defects, not house style.
   One canonical form of each is used here. PRE-DESIGN-GATE.md §7 lists every
   such decision with the screen that motivated it.

   No framework, no build step, no CDN. A hostel on El Nido wifi should not
   need the internet to see who is in the building.
   ========================================================================= */

:root {
  /* --- measured, dE = 0.0 against the captures --------------------------- */
  --navy:        #132144;  /* sidebar ground, 5.888% of sampled pixels       */
  --primary:     #377dff;  /* buttons, links                                 */
  --nav-active:  #6e71ff;  /* sidebar active pill — absent from the pack     */
  --teal:        #00c9a7;  /* success                                        */
  --ink:         #1e2022;  /* body text                                      */
  --muted:       #657585;  /* secondary text; 4.40 -> 4.55 on --page         */
  --muted-2:     #627589;  /* the second grey; passes on BOTH card and page  */
  --page:        #f9fafc;  /* page ground, 19.6%                             */
  --card:        #ffffff;  /* card ground, 62.6%                             */
  --line:        #eef0f7;  /* hairline                                       */
  --hover:       #f8fafd;

  /* --- pill tints, measured from 062 / 022 / 103 ------------------------- */
  --tint-blue:   #ebf2ff;
  --tint-teal:   #e5f9f6;
  --tint-pink:   #fdedf1;
  --tint-amber:  #fefaf5;

  /* --- READABLE INKS -----------------------------------------------------
     A conflict found by measuring rather than by looking, and worth stating
     plainly: THE REFERENCE'S OWN PALETTE FAILS WCAG AA FOR NORMAL TEXT.
     Contrast-checked against the surfaces it is actually used on:

       #377dff link on white ......... 3.79:1   needs 4.5
       white on #377dff button ....... 3.79:1
       white on #6e71ff active pill .. 3.84:1
       #71869d secondary on white .... 3.75:1
       #677788 secondary on page ..... 4.40:1
       teal / pink / amber pill inks .. 3.28 - 4.08:1

     Eight of eleven pairings fail. Small bold pill text does not qualify as
     "large text" under WCAG (that is 18.66px bold and up), so the exemption
     does not apply.

     The resolution keeps the reference's look and fixes only what carries
     text: every SURFACE stays exactly as measured — the navy rail, the page
     ground, the card, all four pill tints are untouched, and those are what
     make this resemble the admin. Only the FOREGROUND inks are darkened,
     by the minimum needed to reach 4.5:1 and no further, so the hue is
     preserved. FLAWFIX §4: copy the slot, not the substance.
     ---------------------------------------------------------------------- */
  --ink-teal:    #007f69;  /* was #00997f, 3.28 -> 4.53 on --tint-teal      */
  --ink-pink:    #c52f79;  /* was #d63384, 3.98 -> 4.58 on --tint-pink      */
  --ink-amber:   #a76400;  /* was #b26a00, 4.08 -> 4.53 on --tint-amber     */
  --ink-info:    #2e69d6;  /* primary on --tint-blue, 3.37 -> 4.54          */
  --primary-ink: #3271e6;  /* links + button fill, 3.79 -> 4.56 on white    */
  --active-ink:  #6467e8;  /* active pill fill, white on it 3.84 -> 4.52    */

  /* --- geometry, measured off the captures ------------------------------- */
  --rail-w:      260px;   /* sidebar width                                   */
  --topbar-h:    61px;    /* top bar height                                  */
  --radius:      8px;
  --radius-lg:   12px;

  --shadow-card: 0 1px 2px rgba(19, 33, 68, 0.04), 0 2px 8px rgba(19, 33, 68, 0.04);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font: 14px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
}

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

/* ===========================================================================
   Shell
   ========================================================================= */

.shell { display: flex; min-height: 100vh; }

/* The rail is sticky and 100vh tall, so on a page longer than the viewport the
   navy column simply STOPPED partway down and the page ground showed through
   beneath it. Painting the left column onto the shell itself keeps the navy
   running the full height without giving up the sticky behaviour. Found by
   screenshotting the rendered page rather than by reading the CSS. */
@media (min-width: 901px) {
  .shell {
    background: linear-gradient(
      to right,
      var(--navy) 0 var(--rail-w),
      var(--page) var(--rail-w)
    );
  }
}

/* --- sidebar ------------------------------------------------------------ */

.rail {
  width: var(--rail-w);
  flex: 0 0 var(--rail-w);
  background: var(--navy);
  color: #fff;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.rail__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  height: var(--topbar-h);
  padding: 0 20px;
  flex: 0 0 var(--topbar-h);
  font-weight: 700;
  letter-spacing: -0.01em;
  font-size: 16px;
}
.rail__mark {
  width: 26px; height: 26px; border-radius: 7px;
  background: var(--nav-active);
  display: grid; place-items: center;
  font-size: 13px; font-weight: 800;
}

.rail__group {
  padding: 18px 20px 6px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
}

.rail__nav { padding: 0 12px; }

/* ===========================================================================
   `hidden` MUST ACTUALLY HIDE.

   The `hidden` attribute is only a UA-stylesheet `display: none`, and ANY
   author rule setting `display` beats it regardless of specificity. So
   `.rail__link { display: flex }` quietly cancelled it, and every role-gated
   link in the rail — People, Mews connection, Property settings, the lot — was
   visible to the front desk. `mountRole()` renders them with `hidden` and
   unhides the permitted ones; the unhiding worked, the hiding never did.

   Nothing was exposed by it: every one of those routes checks for itself and
   answers 403, and mountRole's own comment says the rail is presentation and
   never enforcement. But its stated goal — "so that a manager is not shown a
   door that does not open" — was not being met, and the desk was being invited
   to click into six refusals.

   Found by rendering the page as the front desk and reading the sidebar. No
   test could see it: the markup was correct, the JavaScript was correct, and
   the attribute was present in the DOM exactly as intended.

   `!important` because the whole point is to beat a `display` that a component
   legitimately sets on itself. Three tooltip rules already did this locally;
   this makes it true everywhere, once.
   ========================================================================= */
[hidden] { display: none !important; }

.rail__link {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  margin-bottom: 2px;
  border-radius: var(--radius);
  color: rgba(255, 255, 255, 0.78);
  text-decoration: none;
  font-size: 14px;
}
.rail__link:hover { background: rgba(255, 255, 255, 0.07); color: #fff; }

/* The active pill. #6e71ff, measured — see the header note. */
.rail__link[aria-current="page"] {
  background: var(--active-ink);
  color: #fff;
  font-weight: 600;
}

.rail__icon { width: 18px; text-align: center; flex: 0 0 18px; opacity: 0.9; }

/* A count beside a nav item, as the reference's sidebar carries (115). Shown
   only when there is something to count — a badge reading 0 is noise. */
.rail__badge {
  margin-left: auto;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  font-size: 11px;
  font-weight: 600;
  text-align: center;
}
.rail__badge--warn { background: var(--ink-amber); color: #fff; }

.rail__foot {
  margin-top: auto;
  padding: 16px 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Three items can sit here at once — Version, "Front desk" (D-11b) and
     "Sample data" — and three did not fit the 260px rail. Found live on
     2026-09-14 by looking at the page: "Version 0.1" wrapped onto two lines
     and the Sample data pill was cut to "SAMPLE D" at the rail edge. That is
     the reference's own clipped-column defect (REFERENCE-SPEC §D2) reproduced
     in the sidebar, by the commit that added the third item.
     Wrap instead of clipping: a badge that is half visible is worse than one
     on its own line, because the clipped half reads as a rendering fault. */
  flex-wrap: wrap;
  gap: 6px 8px;
  gap: 8px;
}

/* --- main column -------------------------------------------------------- */

.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  height: var(--topbar-h);
  flex: 0 0 var(--topbar-h);
  background: var(--card);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 24px;
  position: sticky;
  top: 0;
  z-index: 20;
}

.topbar__toggle {
  display: none;
  width: 38px; height: 38px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font-size: 16px;
  cursor: pointer;
}

.topbar__spacer { flex: 1 1 auto; }

.topbar__meta {
  display: flex; align-items: center; gap: 14px;
  font-size: 13px; color: var(--muted);
  min-width: 0;
}

.content { padding: 22px 24px 56px; max-width: 1440px; width: 100%; }

/* ===========================================================================
   Page head — breadcrumb, then H1. The reference uses "/" separators
   everywhere except one screen (026) that uses chevrons; "/" is the rule.
   ========================================================================= */

.crumb {
  display: flex; flex-wrap: wrap; gap: 6px;
  font-size: 13px; color: var(--muted);
  margin: 0 0 4px;
}
.crumb a { color: var(--muted); text-decoration: none; }
.crumb a:hover { color: var(--primary-ink); }
.crumb span[aria-current] { color: var(--ink); }

.pagehead {
  display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
  margin-bottom: 20px;
}
.pagehead h1 {
  margin: 0;
  font-size: 26px;
  line-height: 1.25;
  letter-spacing: -0.02em;
  font-weight: 700;
}
.pagehead__sub { margin: 6px 0 0; color: var(--muted); font-size: 14px; max-width: 68ch; }
.pagehead__actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

/* ===========================================================================
   Cards
   ========================================================================= */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  margin-bottom: 20px;
  overflow: hidden;
}

.card__head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
}
.card__title { margin: 0; font-size: 15px; font-weight: 700; }
.card__hint { margin: 3px 0 0; font-size: 13px; color: var(--muted); }
.card__tools { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.card__body { padding: 18px; }
.card__foot {
  padding: 12px 18px;
  border-top: 1px solid var(--line);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: 13px; color: var(--muted);
}

/* ===========================================================================
   Tables
   ========================================================================= */

/* The reference's widest tables run past the card and clip the last column —
   screen 081's ACTION header is cut to "AC", and 062 and 115 do the same.
   Scrolling the table inside its own container is the fix. */
.tablewrap { overflow-x: auto; }

table.grid { width: 100%; border-collapse: collapse; font-size: 14px; }

/* A floor, so the browser cannot solve a crowded table by crushing the first
   column. Without it the Today grid squeezed "Mateo Garcia" onto two lines and
   its sub-line onto four, making every row ~100px tall for no reason. Below
   this width .tablewrap scrolls instead — which is the reference's own table
   idiom, minus the part where it clips the last column off the card (081). */
table.grid { min-width: 900px; }

/* The identity column carries two lines of text and must not be the one that
   gives way when space is short. */
table.grid td:first-child,
table.grid th:first-child { min-width: 240px; }

/* A prose cell — a recommendation, a reason, a statement.
   Left uncapped it takes every spare pixel and pushes the last column off the
   card, which is precisely the reference's own defect (081's ACTION header cut
   to "AC"). Capping it lets the whole table fit at desktop width instead of
   relying on a scrollbar nobody notices. */
table.grid .col-note { max-width: 260px; min-width: 180px; }
table.grid .col-note .person__sub { white-space: normal; }

table.grid th {
  text-align: left;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-2);
  font-weight: 600;
  padding: 11px 14px;
  background: var(--hover);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
table.grid td {
  padding: 13px 14px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
table.grid tbody tr:last-child td { border-bottom: 0; }
table.grid tbody tr:hover { background: var(--hover); }

.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
th.num { text-align: right; }

/* A person: initial disc, name, handle beneath. The reference's canonical
   row cell (001, 064, 110, 115). The status dot it carries is dropped — it
   encoded nothing on any screen in the corpus. */
.person { display: flex; align-items: center; gap: 10px; min-width: 0; }
.person__disc {
  width: 34px; height: 34px; flex: 0 0 34px;
  border-radius: 50%;
  background: var(--tint-blue);
  color: var(--ink-info);
  display: grid; place-items: center;
  font-weight: 700; font-size: 13px;
}
.person__name { font-weight: 600; }
.person__sub { color: var(--muted); font-size: 12.5px; }
/* Keep the disc aligned to the first line of a name that wraps, rather than
   floating to the middle of a two-line cell. */
.person { align-items: flex-start; }
.person__disc { margin-top: 1px; }

/* ===========================================================================
   Pills

   The reference renders one status concept three ways — a pink filled pill,
   amber dotted text with no fill, and an amber filled pill (062 / 022 / 023).
   One form is used here: a tinted pill with a leading dot.
   ========================================================================= */

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.pill::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
}
.pill--plain::before { display: none; }

.pill--good  { background: var(--tint-teal);  color: var(--ink-teal); }
.pill--warn  { background: var(--tint-amber); color: var(--ink-amber); }
.pill--bad   { background: var(--tint-pink);  color: var(--ink-pink); }
.pill--info  { background: var(--tint-blue);  color: var(--ink-info); }
.pill--mute  { background: var(--hover);      color: var(--muted); }

/* ===========================================================================
   Buttons — one primary treatment, placed consistently
   ========================================================================= */

.btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px;
  min-height: 38px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font: inherit; font-size: 13.5px; font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}
.btn:hover { background: var(--hover); }
.btn--primary { background: var(--primary-ink); border-color: var(--primary-ink); color: #fff; }
.btn--primary:hover { background: #2a63cf; }
.btn[disabled] { opacity: 0.55; cursor: not-allowed; }

/* ===========================================================================
   Form controls
   ========================================================================= */

.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field > label { font-size: 12px; color: var(--muted); font-weight: 600; }

.input, select.input {
  font: inherit; font-size: 14px;
  padding: 8px 11px;
  min-height: 38px;
  min-width: 0;              /* a flex child will not shrink below intrinsic
                                width without this; a date input's is wide */
  border: 1px solid #dfe3ec;
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
}
.input:focus-visible, select.input:focus-visible, .btn:focus-visible, a:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.filters {
  display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap;
}

/* ===========================================================================
   Metric tiles
   ========================================================================= */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}

.tile {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 16px 18px;
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
}
.tile__label { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.tile__value {
  font-size: 28px; font-weight: 700; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.tile__value--none { color: var(--muted-2); font-size: 20px; font-weight: 600; }
.tile__q { font-size: 12.5px; color: var(--muted-2); }

/* The "why" disclosure. Every metric carries its own derivation; this is the
   control that opens it. */
.tile details { margin-top: 4px; }
.tile summary {
  cursor: pointer; font-size: 12.5px; color: var(--primary-ink); font-weight: 600;
  list-style: none;
}
.tile summary::-webkit-details-marker { display: none; }
.tile details[open] summary { margin-bottom: 6px; }
.derivation {
  font-size: 12.5px; color: var(--muted);
  border-left: 2px solid var(--line);
  padding-left: 10px;
}
.derivation code { background: var(--hover); padding: 1px 5px; border-radius: 4px; }
.derivation ul { margin: 6px 0 0; padding-left: 16px; }

/* ===========================================================================
   Notices
   ========================================================================= */

/* BLOCK, NOT FLEX — and this was a real defect, visible the first time a notice
   carried mixed markup.

   `display: flex` makes EVERY child a flex item, including bare text nodes and
   inline <code>. A notice reading "CREDENTIAL_KEY is not set, so a token saved
   here would ..." rendered as a narrow column of squeezed words beside two wide
   <code> blocks — unreadable. It had never shown up because every notice until
   now held a single text node, which is exactly the shape that hides it.

   Normal flow is what prose wants: inline children sit in the sentence, block
   children stack. Nothing in the codebase relied on the flex row. */
.notice {
  display: block;
  padding: 12px 14px;
  border-radius: var(--radius);
  font-size: 13.5px;
  line-height: 1.5;
  margin-bottom: 16px;
  border: 1px solid transparent;
  /* A pasted command or a token tail must not push the card sideways. */
  overflow-wrap: anywhere;
}
.notice code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  background: rgba(0, 0, 0, .06);
  border-radius: 4px;
  padding: 1px 5px;
  /* Long one-liners wrap instead of running off the right-hand edge. */
  overflow-wrap: anywhere;
}
.notice--warn { background: var(--tint-amber); border-color: #f3e2c7; color: #7a4b00; }
.notice--bad  { background: var(--tint-pink);  border-color: #f6d6e2; color: #99205c; }
.notice--info { background: var(--tint-blue);  border-color: #d8e5ff; color: #1c4fb8; }
.notice strong { font-weight: 700; }
.notice a { color: inherit; }

/* ===========================================================================
   Marking what happened when a suggestion was offered (feature 68).

   These are NEW classes, added here rather than invented in the page — which
   is the mistake this file's next note records. Four buttons in a table cell
   that is read one-handed at a counter while a guest waits: they have to be
   hittable without being large enough to push the row taller than the text
   beside them.
   ========================================================================= */

.btn--tiny {
  padding: 3px 8px;
  font-size: 11.5px;
  line-height: 1.4;
  border-radius: 5px;
}

.marked {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  align-items: center;
  margin-top: 6px;
}
/* Once something is recorded the row stops offering buttons, so the spacing
   changes from a control strip to a statement. */
.marked .pill { margin: 0; }
.marked .person__sub { margin: 0; }

@media (max-width: 760px) {
  /* On a phone the cell is already stacked; the buttons get a full row of
     their own rather than wrapping raggedly beside the suggestion text. */
  .marked { width: 100%; margin-top: 8px; }
  .btn--tiny { padding: 6px 10px; font-size: 12.5px; }
}

/* THE TABLE COMPONENT IS `table.grid`, NOT `.table`.
   Written down because it has now been got wrong twice in one session: three
   tables on Who buys and two on People were built with `class="table"`, which
   matches nothing in this stylesheet, so they rendered as bare unstyled HTML —
   no width, no borders, columns bunched into the left third of the card. It
   type-checks, it passes every test, and it is only visible by looking.
   The same went for an invented `.statrow`/`.stat`; the tile component is
   `.tiles` + `.tile`. If a class is not in this file, it does nothing. */

/* ===========================================================================
   Empty state

   The reference pairs an illustration with "No data to show" (017, 079, 114).
   The words here say WHICH query came back empty and what to do, because
   "No data to show" leaves the reader with no move.
   ========================================================================= */

.empty {
  padding: 40px 18px;
  text-align: center;
  color: var(--muted);
}
.empty__mark {
  width: 46px; height: 46px; margin: 0 auto 12px;
  border-radius: 50%;
  background: var(--hover);
  display: grid; place-items: center;
  color: var(--muted-2); font-size: 20px;
}
.empty strong { display: block; color: var(--ink); margin-bottom: 4px; font-size: 15px; }

/* ===========================================================================
   Footer meta — the freshness line

   A nightly job's characteristic failure is that it stops running while every
   screen keeps showing yesterday's figures. So freshness is on every screen,
   not on a status page nobody opens.
   ========================================================================= */

.freshness { display: inline-flex; align-items: center; gap: 7px; }

/* ===========================================================================
   Responsive
   ========================================================================= */

@media (max-width: 900px) {
  .rail {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 40;
    transform: translateX(-100%);
    transition: transform 0.18s ease;
  }
  .rail[data-open="true"] { transform: none; box-shadow: 0 0 40px rgba(0, 0, 0, 0.3); }
  .topbar__toggle { display: grid; place-items: center; }
  .content { padding: 18px 16px 48px; }
  .topbar { padding: 0 16px; }
  .scrim {
    position: fixed; inset: 0; z-index: 30;
    background: rgba(19, 33, 68, 0.45);
  }
  .scrim[hidden] { display: none; }
}

@media (max-width: 520px) {
  .pagehead h1 { font-size: 22px; }
  .tile__value { font-size: 24px; }
  .filters > * { flex: 1 1 140px; }

  /* `margin-left: auto` pushes the page actions right, which at this width
     stranded the date field and its Go button on separate half-width lines.
     On a phone they go full width under the heading instead. */
  .pagehead__actions { margin-left: 0; width: 100%; }
  .pagehead__actions .filters { width: 100%; }
  .pagehead__actions .field { flex: 1 1 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .rail { transition: none; }
}

/* ------------------------------------------------------- attribution trend */
/* A stat tile with a sparkline (D-08), not a chart card.
   The data's job is "one current number, plus is it drifting" — which
   `dataviz/choosing-a-form` puts squarely in the stat-tile row, not the
   chart row. A one-series line in a full chart frame would be a bigger
   container saying less. */

.trend { display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap; }

.trend__now { min-width: 150px; }

.trend__value {
  /* Proportional figures, not tabular: equal-width digits make a large
     standalone number look loose. tabular-nums is for the table below. */
  font-size: 34px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.trend__caption { margin-top: 4px; font-size: 13px; color: var(--muted); }

.trend__plot { flex: 1 1 380px; min-width: 280px; }

/* The SVG scales to its HEIGHT, not its width — a 560x150 viewBox in a box
   150px tall can only scale 1:1, and the default xMidYMid then CENTRES it,
   stranding the plot in the right half of the card behind ~160px of dead
   space at each side. Found by looking at the rendered page; the markup,
   the console and the scroll check were all clean.
   Cap the box at the viewBox width and anchor it left (xMinYMid on the
   element) so it sits directly beside the number it belongs to. */
.trend__plot svg { display: block; max-width: 560px; }

/* Hairline, solid, one shade off the surface. Never dashed: a dashed grid
   reads as "threshold" or "projection" when it is just a grid. */
.trend__grid { stroke: var(--line); stroke-width: 1; }
.trend__axis { fill: var(--muted-2); font-size: 10px; }

/* 2px line, thin mark. Single series, so it carries no identity job and needs
   no legend — the card title names it. */
.trend__line { fill: none; stroke: var(--primary-ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }

/* >=8px markers, with a 2px surface ring so overlapping points stay separable.
   pointer-events: none is load-bearing, not tidiness. The dot is painted AFTER
   its own r=12 hit circle, so it sat on top and swallowed the pointer at the
   exact centre — Playwright: "<circle class=trend__dot> intercepts pointer
   events". The tooltip therefore failed precisely where a reader aims, and
   worked only on the ring around it. A screenshot cannot show this; it took
   driving the hover to find. */
.trend__dot { fill: var(--primary-ink); stroke: var(--card); stroke-width: 2; pointer-events: none; }

/* A month with too few charges to mean anything is drawn hollow rather than
   hidden. Hiding it would silently change the shape of the line; this says
   "this point exists and is not worth reading". */
.trend__dot--thin { fill: var(--card); stroke: var(--muted-2); }

.trend__hit { fill: transparent; cursor: pointer; }
.trend__hit:hover + .trend__dot,
.trend__hit:focus-visible + .trend__dot { r: 6; }

.trend__tip {
  position: absolute; z-index: 5; pointer-events: none;
  background: var(--ink); color: #fff;
  font-size: 12px; line-height: 1.45;
  padding: 6px 9px; border-radius: 6px;
  white-space: nowrap;
  transform: translate(-50%, -125%);
  box-shadow: 0 4px 14px rgba(19, 33, 68, 0.22);
}
.trend__tip[hidden] { display: none !important; }

.trend__wrap { position: relative; }

/* The table view is not a fallback, it is the accessible peer of the plot.
   Collapsed by default so it does not double the card's height. */
.trend__table { margin-top: 14px; font-size: 13px; }
.trend__table summary { cursor: pointer; color: var(--primary-ink); }
.trend__table table { margin-top: 10px; width: 100%; border-collapse: collapse; }
.trend__table th, .trend__table td {
  text-align: right; padding: 4px 8px; border-bottom: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.trend__table th:first-child, .trend__table td:first-child { text-align: left; }
.trend__table th { color: var(--muted-2); font-weight: 600; }

/* ============================================================================
   The Today grid on a phone.

   The front desk opens this on a handset, and at 400px a horizontally
   scrolling table shows GUEST and STATUS — while the two columns that make the
   screen worth opening, "what to offer" and "what they already bought", sit
   off-screen in columns 4 and 5. Nothing was clipped (every grid is a
   scrollable .tablewrap, measured), but "reachable by scrolling sideways" is
   not the same as usable one-handed at a counter.

   So below 620px each row becomes a stacked card and the fields are REORDERED
   to the order the job needs — the suggestion moves directly under the guest,
   and nights-left and spend drop to the bottom. A table cannot be reordered in
   CSS while it is a table, which is why the row becomes a grid first.

   Desktop is untouched: above 620px this is the reference's table, unchanged.
   ========================================================================== */
@media (max-width: 620px) {
  /* `table.grid` is (0,1,1); a bare `.grid--stack` is (0,1,0) and LOSES to it.
     The table therefore kept its 900px min-width inside a 400px screen, the
     rows became 896px grids, and `space-between` threw every value out to
     x=800+ — off-screen. The result read as "the values are missing": labels
     visible, nothing beside them, no console error, no page-level horizontal
     scroll (the .tablewrap absorbed it). Only "Worth mentioning" survived,
     because .col-note caps its width at 260px.
     Every rule below is therefore prefixed with `table` to outrank the base.
     Found by cropping the 400px capture to full size and then reading the
     computed geometry — the thumbnail looked plausible. */
  table.grid--stack { min-width: 0; width: 100%; }
  table.grid--stack thead { display: none; }     /* headers become per-cell labels */
  table.grid--stack, table.grid--stack tbody { display: block; }

  /* Nothing left to scroll sideways, so the wrapper must not imply otherwise. */
  .tablewrap:has(> table.grid--stack) { overflow-x: visible; }

  table.grid--stack tbody tr {
    display: grid;
    gap: 6px;
    padding: 14px 2px;
    border-bottom: 1px solid var(--line);
  }
  table.grid--stack tbody tr:hover { background: transparent; }

  table.grid--stack tbody td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    border: 0;
    padding: 0;
    text-align: left;
  }

  /* The header text, reattached to each value. Without it a stacked row is a
     column of unlabelled facts. */
  table.grid--stack tbody td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted-2);
  }

  /* The guest cell carries its own name and sub-line, so it needs no label and
     spans the full width. */
  table.grid--stack tbody td[data-label="Guest"] { display: block; }
  table.grid--stack tbody td[data-label="Guest"]::before { content: none; }

  table.grid--stack tbody td.num { justify-content: space-between; }
  /* The suggestion cell holds a product name AND a sentence explaining it, so
     sharing one flex row with its label squeezed the name into ~66px and wrapped
     "Private Boat Charter (full day)" over four lines. It is the most important
     field on this screen for the desk; it gets the full width. */
  table.grid--stack .col-note {
    max-width: none;
    min-width: 0;
    display: block;
  }
  table.grid--stack .col-note::before { display: block; margin-bottom: 4px; }
  table.grid--stack .col-note .person__name { margin-bottom: 2px; }

  /* The order the desk actually needs. */
  table.grid--stack td[data-label="Guest"]            { order: 1; }
  table.grid--stack td[data-label="Status"]           { order: 2; }
  table.grid--stack td[data-label="Worth mentioning"] { order: 3; }
  table.grid--stack td[data-label="Already bought"]   { order: 4; }
  table.grid--stack td[data-label="Nights left"]      { order: 5; }
  table.grid--stack td[data-label="Typical spend"]    { order: 6; }
}

/* The download affordance in a card head (feature 7.6). Quiet: it is a tool,
   not a call to action, and it sits beside the title it exports. */
.csvlink {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--primary-ink);
  text-decoration: none;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  white-space: nowrap;
}
.csvlink:hover { background: var(--tint-blue); border-color: var(--tint-blue); }
.csvlink:focus-visible { outline: 2px solid var(--primary-ink); outline-offset: 2px; }

/* ============================================================================
   The overview screen: KPI tiles, charts, ranked bars.
   Marks are thin, grids are solid hairlines one shade off the surface, and no
   value wears a series colour — text stays in ink, the mark beside it carries
   identity.
   ========================================================================== */

.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

.kpi {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  box-shadow: var(--shadow-card);
}
.kpi__label { font-size: 13px; color: var(--muted); }
.kpi__value {
  /* Proportional figures: tabular-nums makes a large standalone number look
     loose. tabular-nums is for columns, not headlines. */
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.15;
  margin-top: 2px;
  color: var(--ink);
}
.kpi__none { font-size: 19px; font-weight: 600; color: var(--muted-2); }
.kpi__q { margin-top: 8px; font-size: 12px; color: var(--muted); }

.kpi__move { margin-top: 6px; font-size: 13px; font-weight: 600; }
.kpi__move--good { color: var(--ink-teal); }
.kpi__move--bad  { color: var(--ink-pink); }
.kpi__move--flat { color: var(--muted); }
.kpi__vs { font-weight: 400; color: var(--muted); }
.kpi__flat { margin-top: 6px; font-size: 12px; color: var(--muted-2); }

/* --- charts -------------------------------------------------------------- */

.ch { margin-top: 4px; }
.ch__wrap { position: relative; }
.ch__grid { stroke: var(--line); stroke-width: 1; }
.ch__axis { fill: var(--muted-2); font-size: 10px; }
.ch__line { fill: none; stroke: var(--primary-ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ch__dot  { fill: var(--primary-ink); stroke: var(--card); stroke-width: 2; pointer-events: none; }
.ch__hit  { fill: transparent; cursor: pointer; }
.ch__seg  { cursor: pointer; }
.ch__seg:hover { opacity: 0.85; }
.ch__empty { color: var(--muted); font-size: 13px; padding: 10px 0; }

.ch__legend { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 10px; font-size: 12px; color: var(--muted); }
.ch__key { display: inline-flex; align-items: center; gap: 6px; }
.ch__key i { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }

.ch__tip {
  position: absolute; z-index: 5; pointer-events: none;
  background: var(--ink); color: #fff;
  font-size: 12px; line-height: 1.45;
  padding: 6px 9px; border-radius: 6px;
  white-space: nowrap;
  transform: translate(-50%, -125%);
  box-shadow: 0 4px 14px rgba(10, 10, 10, 0.22);
}
.ch__tip[hidden] { display: none !important; }

/* --- ranked bars --------------------------------------------------------- */

.rank { display: flex; flex-direction: column; gap: 9px; }
.rank__row {
  display: grid;
  grid-template-columns: minmax(90px, 200px) 1fr auto;
  align-items: center;
  gap: 12px;
  font-size: 13px;
}
.rank__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.rank__track { background: var(--hover); border-radius: 999px; height: 12px; overflow: hidden; }
.rank__bar { height: 100%; background: var(--primary-ink); border-radius: 999px; }
.rank__val { font-variant-numeric: tabular-nums; color: var(--ink); white-space: nowrap; }
.rank__note { grid-column: 1 / -1; font-size: 12px; color: var(--muted); margin-top: -4px; }

@media (max-width: 620px) {
  .rank__row { grid-template-columns: 1fr auto; }
  .rank__track { grid-column: 1 / -1; }
}

/* Two cards side by side on a wide screen, stacked below. Used by the overview
   for the pair of ranked lists that answer the same question from two angles. */
.grid2 { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 1100px) { .grid2 { grid-template-columns: 1fr 1fr; } }

/* The "incomplete month" key. A 45-degree hatch, matching the mask in the SVG. */
.ch__hatchkey {
  width: 11px; height: 11px; border-radius: 3px; display: inline-block;
  background: repeating-linear-gradient(45deg, var(--muted-2) 0 2px, transparent 2px 4px);
  border: 1px solid var(--line);
}
.ch__key--note { color: var(--muted-2); }

/* ---------------------------------------------------------------------------
   The trend chart's new surface. `currentColor` on the gradient stops means the
   fill follows whatever colour the line is set to, so re-theming the series
   never leaves an area in the old hue underneath it.
   --------------------------------------------------------------------------- */
.trend__plot svg { color: var(--ink-amber); }
.trend__area { stroke: none; }
.trend__halo { fill: var(--ink-amber); opacity: 0.15; }
.trend__dot--now { stroke: var(--card); stroke-width: 2; }
