/* ===========================================================================
   butler.li design tokens (1.0)
   ---------------------------------------------------------------------------
   The look follows shadcn/ui (ui.shadcn.com): a neutral zinc palette, 8 px
   corners, hairline borders and soft shadows, and shadcn's own token names
   (--background, --foreground, --muted, --border, --ring, --primary ...), so
   a component described in its documentation maps onto this file one to one.
   The values are butler.li's own, from prototype V0.6 (issue #142): ink for
   the main action, the yellow only in the logo and the text selection.

   shadcn is a set of React components; butler.li is server-rendered and has
   to work without JavaScript, so only the look is taken, written here in
   plain CSS (decision 1 in #142). Every page stays a real page.

   Two places part from shadcn on purpose, both for WCAG:
     --input  the boundary of a field is 3.4:1 on white, not shadcn's faint
              #e4e4e7 (1.2:1); 1.4.11 asks 3:1 for the edge of a control.
     --ring   the focus ring is 4.8:1, not shadcn's #a1a1aa (2.6:1).

   One look for every hotel: there is no per-install colour any more
   (decision 3 in #142). The hotel's name and logo stay its own.

   Dark mode follows the system unless the page says otherwise:
   <html data-theme="light|dark"> wins over prefers-color-scheme.

   Type is Inter for everything and Bricolage Grotesque for page titles,
   both bundled in ../fonts/ (SIL Open Font License), so nothing is fetched
   from a CDN and the interface works with no route to the internet.
   =========================================================================== */

:root {
  color-scheme: light;
  --background: #ffffff;
  --foreground: #222222;
  --card: #ffffff;
  --muted: #f4f4f5;
  --muted-foreground: #5c5c66;
  --subtle: #fafafa;
  --border: #e4e4e7;
  --input: #8b8b94;
  --ring: #71717a;
  --primary: #222222;
  --primary-foreground: #ffffff;
  --secondary: #f4f4f5;
  --secondary-foreground: #222222;
  --hover: #f4f4f5;
  --brand: #e9ff00;
  --brand-ink: #222222;       /* text on the yellow, which only text selection uses */
  --link: #222222;
  /* Lucide's info, the i-icon beside a field, in muted and in ink for hover. */
  --icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235c5c66' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
  --icon-info-on: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23222222' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
  /* Lucide's chevron-down for a closed <select>, in --muted-foreground; a data URI cannot read a
     custom property, so each theme carries its own. */
  --chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235c5c66' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");

  /* Status colours: fixed, so a note or a badge means the same thing on every install. */
  --ok: #2e6b2a;
  --ok-bg: #eaf3e6;
  --info: #2b5c8a;
  --info-bg: #e8f0f8;
  --warn: #8a4b00;
  --warn-bg: #fceedc;
  --warn-line: #e7a861;       /* the orange edge of something that needs attention */
  --err: #a3231c;
  --err-bg: #fbe9e7;

  /* An open question, [in brackets], in a draft or a card. */
  --gap-bg: #ffe0c2;
  --gap-ink: #6b2a00;

  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-display: "Bricolage Grotesque", "Inter", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --radius: 8px;              /* fields, buttons, notes, popovers */
  --radius-lg: 12px;          /* cards, dialogs */
  --radius-sm: 6px;           /* badges, counts */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .05);
  --shadow: 0 1px 3px rgba(0, 0, 0, .08), 0 1px 2px -1px rgba(0, 0, 0, .06);
  --shadow-lg: 0 10px 38px -10px rgba(0, 0, 0, .25), 0 10px 20px -15px rgba(0, 0, 0, .15);
  --maxw: 1100px;

  /* Settings > Costs: one colour per thing the money went to, apart enough to tell neighbouring
     segments of a bar apart in both themes. */
  --fn-drafting: #6929c4;
  --fn-translation: #1192e8;
  --fn-knowledge: #00897b;
  --fn-indexing: #c2185b;
  --fn-other: #8b8b94;
}

/* The dark values, twice: once for a system set to dark (unless the page asked for light), once
   for a page that asked for dark. Same names, so nothing below knows which theme it is in. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --background: #161618; --foreground: #f4f4f5; --card: #1c1c1f; --muted: #27272a;
    --muted-foreground: #a1a1aa; --subtle: #1a1a1d; --border: #2e2e33; --input: #71717a;
    --ring: #a1a1aa; --primary: #f4f4f5; --primary-foreground: #161618; --secondary: #27272a;
    --secondary-foreground: #f4f4f5; --hover: #27272a; --link: #f4f4f5;
    --icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
    --icon-info-on: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f4f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
    --chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    --ok: #8fcf87; --ok-bg: #1d2b1b; --info: #8db8e3; --info-bg: #18222d; --warn: #e7a861;
    --warn-bg: #2d2214; --warn-line: #b8742c; --err: #f0948c; --err-bg: #2e1a18;
    --gap-bg: #4a2a10; --gap-ink: #ffd8b0;
    --shadow-xs: none; --shadow: none; --shadow-lg: 0 10px 38px -10px rgba(0, 0, 0, .7);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --background: #161618; --foreground: #f4f4f5; --card: #1c1c1f; --muted: #27272a;
  --muted-foreground: #a1a1aa; --subtle: #1a1a1d; --border: #2e2e33; --input: #71717a;
  --ring: #a1a1aa; --primary: #f4f4f5; --primary-foreground: #161618; --secondary: #27272a;
  --secondary-foreground: #f4f4f5; --hover: #27272a; --link: #f4f4f5;
    --icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
    --icon-info-on: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f4f4f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
    --chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --ok: #8fcf87; --ok-bg: #1d2b1b; --info: #8db8e3; --info-bg: #18222d; --warn: #e7a861;
  --warn-bg: #2d2214; --warn-line: #b8742c; --err: #f0948c; --err-bg: #2e1a18;
  --gap-bg: #4a2a10; --gap-ink: #ffd8b0;
  --shadow-xs: none; --shadow: none; --shadow-lg: 0 10px 38px -10px rgba(0, 0, 0, .7);
}

::selection { background: var(--brand); color: var(--brand-ink); }

* { box-sizing: border-box; }
/* The hidden attribute means hidden, whatever display a class gives the element. Without this,
   .actions { display: flex } showed the job's "Show the results" button on every finished import,
   where nothing was wired to it, so pressing it did nothing. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--background);
  color: var(--foreground);
  font: 14px/1.55 var(--font-sans);
  -webkit-font-smoothing: antialiased;
  /* Inter's own alternates: a single-storey a is not wanted, but the open digits and the
     straight-sided l and I read better in figures and addresses. */
  font-feature-settings: "cv11", "ss01";
}

/* Page titles in Bricolage Grotesque, the rest of the headings in Inter, sentence case. */
h1, h2, h3, h4 {
  font-family: var(--font-sans);
  font-weight: 600;
  color: var(--foreground);
  letter-spacing: -.005em;
}
h1 { font-family: var(--font-display); font-size: 28px; line-height: 1.2; margin: 0 0 6px; letter-spacing: -.01em; }
h2 { font-size: 19px; line-height: 1.3; margin: 0 0 12px; }
h3 { font-size: 16px; line-height: 1.35; margin: 0 0 6px; }

/* Links are ink with an underline, as in shadcn: the underline, not a colour, says it is a link
   (1.4.1), so it works the same in both themes. */
a { color: var(--link); text-decoration-color: var(--input); text-underline-offset: 3px; }
a:hover { text-decoration-color: currentColor; }

/* The small uppercase section labels. */
.eyebrow, .side-label {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  margin-bottom: 10px;
}
.side-label { margin-bottom: 7px; font-size: 12px; }

/* --- the frame --------------------------------------------------------------
   Phase 2 of #142 (issue #144), after prototype V0.6: a sidebar on the left with the hotel's menu,
   the search and the sections; a top bar with where you are and the help; a status line at the
   bottom. shadcn's sidebar: the subtle background, a hairline on the right, items 34 px high. */

.app { display: grid; grid-template-columns: 248px minmax(0, 1fr); min-height: 100vh;
  /* The sidebar stays as tall as the window while the page scrolls; this paints its column the
     rest of the way down, for a page printed or captured whole. */
  background: linear-gradient(to right, var(--subtle) 247px, var(--border) 247px 248px, transparent 248px); }
.sidebar {
  background: var(--subtle); border-right: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 12px; padding: 12px;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.app-main { min-width: 0; display: flex; flex-direction: column; min-height: 100vh; }

/* The hotel's menu, top left: its logo and name, opening onto the account and signing out. */
.org-menu { position: relative; }
.org-menu > summary { list-style: none; }
.org-menu > summary::-webkit-details-marker { display: none; }
.org {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 4px 6px;
  border-radius: var(--radius); color: var(--foreground); text-decoration: none; cursor: pointer;
  user-select: none;
}
.org:hover, .org-menu[open] .org { background: var(--hover); }
.org-name {
  flex: 1; min-width: 0; font-family: var(--font-display); font-weight: 700; font-size: 16px;
  line-height: 1.2; letter-spacing: -.01em; overflow-wrap: anywhere;
}
.org-chev { color: var(--muted-foreground); flex: none; }
.org-pop { left: 0; right: auto; min-width: 100%; }
.org-who { margin: 0; padding: 6px 8px; display: flex; flex-direction: column; gap: 1px; font-size: 13px; }
.org-who strong { font-size: 14px; overflow-wrap: anywhere; }

/* A circular badge: an avatar-style mark keeps its own round shape. */
.logo-disc {
  width: 32px; height: 32px; border-radius: 50%; background: var(--muted);
  display: grid; place-items: center; flex: none; overflow: hidden; padding: 3px;
}
.logo-disc img { width: 100%; height: 100%; object-fit: contain; }
/* The first two letters of the name when there is no logo, like shadcn's team switcher. */
.logo-mark {
  font-family: var(--font-sans); font-weight: 700; font-size: 11px; line-height: 1;
  text-transform: uppercase; color: var(--foreground);
}

/* The search: a field that looks like shadcn's sidebar search, the shortcut shown inside it. */
.sb-search { position: relative; margin: 0; }
.sb-search input[type=search] {
  width: 100%; height: 34px; padding: 0 52px 0 32px; font-size: 16px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
}
.sb-search-icon { position: absolute; left: 10px; top: 9px; color: var(--muted-foreground); pointer-events: none; }
.sb-kbd { position: absolute; right: 8px; top: 7px; pointer-events: none; }

.sb-nav { display: flex; flex-direction: column; gap: 1px; }
.sb-group { margin: 10px 0 2px; padding: 0 8px; font-size: 12px; font-weight: 500; color: var(--muted-foreground); }
.sb-group:first-child { margin-top: 0; }
.sb-nav a {
  display: flex; align-items: center; gap: 10px; min-height: 34px; padding: 0 8px;
  border-radius: var(--radius); color: var(--foreground); font-weight: 500; font-size: 14px;
  text-decoration: none;
}
.sb-nav a svg { color: var(--muted-foreground); flex: none; }
.sb-nav a:hover { background: var(--hover); }
/* The weight is a second, non-chromatic cue beside the background, because 1.4.1 does not allow
   colour to carry state on its own; aria-current says it to a screen reader. */
.sb-nav a.on { background: var(--muted); font-weight: 600; }
.sb-nav a.on svg { color: var(--foreground); }
/* A count, as in shadcn's sidebar: a small ink square with the number. */
.sb-nav .count {
  margin-left: auto; display: inline-grid; place-items: center; min-width: 20px; height: 20px;
  padding: 0 6px; border-radius: var(--radius-sm); background: var(--primary);
  color: var(--primary-foreground); font-size: 12px; font-weight: 600;
}

/* The open section's tabs (issue #163): shadcn's sidebar sub-menu, indented behind a thin line,
   a little smaller than the entries, with the same mark for the one you are on. */
.sb-sub {
  display: flex; flex-direction: column; gap: 1px; margin: 1px 0 4px 17px; padding-left: 10px;
  border-left: 1px solid var(--border);
}
.sb-nav .sb-sub a { min-height: 30px; font-size: 13px; font-weight: 400; color: var(--muted-foreground); }
.sb-nav .sb-sub a:hover { color: var(--foreground); }
.sb-nav .sb-sub a.on { color: var(--foreground); font-weight: 600; }

/* The top bar: the path on the left, the help on the right, sticky above the page. */
.topbar {
  position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 8px;
  min-height: 52px; padding: 0 20px 0 16px; background: var(--background);
  border-bottom: 1px solid var(--border);
}
.topbar .spacer { flex: 1; }
.crumbs { display: flex; align-items: center; gap: 6px; min-width: 0; font-weight: 500; }
.crumbs a { color: var(--foreground); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
.crumbs a:not([aria-current]) { color: var(--muted-foreground); }
.crumbs > span[aria-current] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crumb-sep { color: var(--muted-foreground); }
.sb-toggle { display: none; }
.sb-toggle > summary { list-style: none; }
.sb-toggle > summary::-webkit-details-marker { display: none; }

main { width: 100%; max-width: 1240px; padding: 24px 32px 48px; flex: 1; }
.page-head { margin-bottom: 22px; }
.page-head p { color: var(--muted-foreground); margin: 0; max-width: 75ch; }

/* The status line: who this installation is and which build runs, small, at the very bottom. */
.statusline {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; padding: 8px 20px;
  border-top: 1px solid var(--border); background: var(--subtle);
  color: var(--muted-foreground); font-size: 12px;
}
.statusline a { color: var(--muted-foreground); }
/* The butler.li logo: the B and the name in ink, as in the logo files. The drawing ends at the
   name's baseline, so it sits on the line like the text beside it. */
.statusline .foot-mark { color: var(--foreground); white-space: nowrap; }
.statusline .foot-mark .bl-logo { vertical-align: baseline; }
.statusline .build {
  margin-left: auto; padding: 1px 6px; border-radius: 4px; background: var(--muted);
  border: 1px solid var(--border); font: 11px/1.5 var(--mono); color: var(--muted-foreground);
  cursor: help;
}

/* Sign-in, the setup wizard and the two-step pages: the hotel's name above a narrow page. */
body.bare main { max-width: var(--maxw); margin: 0 auto; padding: 28px 22px 64px; }
.bare-head { max-width: var(--maxw); margin: 0 auto; padding: 18px 22px 0; }
.bare-head .logo { display: inline-flex; align-items: center; gap: 10px; color: var(--foreground); text-decoration: none; }
.bare-head .logo-disc { width: 40px; height: 40px; }
.logo-text { font-family: var(--font-display); font-weight: 700; font-size: 17px; letter-spacing: -.01em; }
body.bare .statusline { background: transparent; border-top: 0; max-width: var(--maxw); margin: 0 auto; width: 100%; padding: 0 22px 32px; }

/* A link straight to the page's content for keyboard users, visible only once focused. */
.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 100; padding: 8px 12px;
  background: var(--primary); color: var(--primary-foreground); border-radius: var(--radius);
}
.skip-link:focus { top: 8px; }

/* On a phone the sidebar leaves the page and comes back as a panel from the menu button. The
   button is a <details>, so no script is needed: while it is open (:has), the sidebar slides in
   over the page, and a tap on the button closes it again. */
@media (max-width: 900px) {
  .app { grid-template-columns: minmax(0, 1fr); background: none; }
  .sb-toggle { display: block; }
  .sidebar {
    position: fixed; top: 52px; left: 0; bottom: 0; z-index: 60; width: min(280px, 85vw);
    height: auto; transform: translateX(-105%); transition: transform .2s;
    box-shadow: var(--shadow-lg);
  }
  body:has(.sb-toggle[open]) .sidebar { transform: none; }
  .topbar { padding: 0 12px; }
  main { padding: 16px 16px 40px; }
  .statusline .sl-more { display: none; }
}
/* A browser without :has() could never open the panel, so there the sidebar stays in the page,
   above it, and the button goes. */
@supports not selector(:has(*)) {
  @media (max-width: 900px) {
    .sidebar { position: static; transform: none; width: auto; box-shadow: none; border-right: 0;
               border-bottom: 1px solid var(--border); }
    .sb-toggle { display: none; }
  }
}

/* The search page (issue #144). */
.search-page-form { display: flex; gap: 8px; margin-bottom: 18px; max-width: 40rem; }
.search-page-form input { flex: 1; min-width: 0; }
.search-group h2 { display: flex; align-items: center; gap: 8px; font-size: 16px; margin: 0 0 8px; }
.search-hits { list-style: none; margin: 0; padding: 0; }
.search-hits li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding: 10px 0;
  border-top: 1px solid var(--border);
}
.search-hits li:first-child { border-top: 0; }
.search-hits a { font-weight: 600; }
.hit-detail { flex-basis: 100%; color: var(--muted-foreground); font-size: 13px; overflow-wrap: anywhere; }
.hit-when { color: var(--muted-foreground); font-size: 12px; margin-left: auto; }

/* --- cards ---------------------------------------------------------------
   shadcn's Card: the card colour, a hairline border, 12 px corners and a faint shadow. A
   .card-foot is the strip at the bottom with a hint and the card's own button. */

.card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 20px 22px; margin-bottom: 16px; box-shadow: var(--shadow);
}
.card.flat { box-shadow: none; }
.card.sand { background: var(--subtle); border-color: var(--border); box-shadow: none; }
.card-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 18px -22px -20px; padding: 12px 22px; border-top: 1px solid var(--border);
  background: var(--subtle); border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.card-foot .hint { flex: 1 1 16rem; color: var(--muted-foreground); font-size: 13px; }
.card-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 14px; }
.card-head h2, .card-head h3 { margin: 0; }
.card-head .spacer { margin-left: auto; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* Two cards stacked in one grid cell, such as the reading language over the time-saved estimate. */
.card-stack { display: flex; flex-direction: column; min-width: 0; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
/* minmax(0, 1fr), not 1fr: a bare 1fr track is never narrower than its widest card's content, so
   the logo picker (a fixed 253px in Chrome) and the signature editor pushed every card on /settings
   23px into the page margin and 1px past a 320px window (1.4.10 Reflow). */
@media (max-width: 760px) { .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); } }

/* --- the dashboard (issue #145) ---------------------------------------------
   A greeting with the state beside it and the one button reception presses here; four figures,
   each saying what it is made of; then what is waiting on the left and what happened today on
   the right, one column on a phone. */
.page-head.with-actions { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 24px; }
.page-head.with-actions > div { flex: 1 1 28rem; min-width: 0; }
.page-head .badge { margin-right: 6px; vertical-align: 1px; }
.head-action button svg.lucide { margin-right: 6px; }
.card-sub { margin: 2px 0 0; font-size: 13px; color: var(--muted-foreground); }

.kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin-bottom: 16px; }
.kpi { padding: 16px 18px; margin: 0; }
a.kpi { color: inherit; text-decoration: none; }
a.kpi:hover { border-color: var(--input); }
.kpi .k {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  color: var(--muted-foreground); font-weight: 500; font-size: 13px;
}
.kpi .k svg.lucide { width: 16px; height: 16px; flex-shrink: 0; }
.kpi .v {
  margin-top: 6px; font-size: 28px; font-weight: 650; line-height: 1.15; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.kpi .v small { margin-left: 4px; font-size: 14px; font-weight: 500; letter-spacing: 0; color: var(--muted-foreground); }
.kpi .d { margin-top: 4px; font-size: 12px; color: var(--muted-foreground); }
@media (max-width: 1100px) { .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .kpis { grid-template-columns: minmax(0, 1fr); } }

.dash-cols {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 16px;
  align-items: start; margin-bottom: 16px;
}
.dash-cols > .card { margin: 0; }
@media (max-width: 1100px) { .dash-cols { grid-template-columns: minmax(0, 1fr); } }
/* The rest of the old dashboard, as many to a row as fit; a hotel with no topics yet has two
   cards, which then share the row instead of leaving a hole. */
.dash-more {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); gap: 16px;
  align-items: start; margin-bottom: 16px;
}
.dash-more > .card { margin: 0; }

.stat .num {
  font-weight: 650; font-size: 28px; line-height: 1.1; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--foreground);
}
.stat .lbl { font-size: 13px; color: var(--muted-foreground); margin-top: 4px; }

/* "To do today": one row per task, grouped under the page that settles it. Each row has an icon,
   names the kind of task in full, says what it is and what to do about it, and carries a button
   to where it is done; the card itself settles nothing. */
.todo-group { margin-top: 14px; }
.todo-group > h3 {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin: 0;
  padding-bottom: 8px; border-bottom: 1px solid var(--border);
  font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted-foreground);
}
.todo-group > h3 .count { color: var(--foreground); }
.todo-group > h3 a { margin-left: auto; font-size: 13px; letter-spacing: 0; text-transform: none; }
.todo-list { list-style: none; margin: 0; padding: 0; }
.todo-row {
  display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; gap: 6px 14px;
  align-items: center; padding: 12px 0; border-bottom: 1px solid var(--border);
}
.todo-list .todo-row:last-child, .todo-group > .todo-row:not(.lead):last-child { border-bottom: none; }
.task-ic {
  width: 32px; height: 32px; display: grid; place-items: center; border-radius: var(--radius);
  background: var(--muted); color: var(--foreground);
}
.task-ic svg.lucide { width: 16px; height: 16px; }
.task-ic.warn { background: var(--warn-bg); color: var(--warn); }
.todo-row .kind { font-weight: 500; font-size: 12px; color: var(--muted-foreground); }
.todo-row .kind.warn { color: var(--warn); }
.todo-row .what { font-weight: 600; font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; }
.todo-row .hint { margin-top: 2px; font-size: 13px; color: var(--muted-foreground); }
.todo-row .btn { white-space: nowrap; }
.todo-row.lead {
  margin-top: 8px; padding: 14px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--subtle);
}
.todo-row.lead .what { font-size: 16px; }
.todo-more { margin: 0; padding: 10px 0 2px; }
.todo-empty { margin: 0; padding: 18px 0 4px; font-size: 14px; color: var(--muted-foreground); }
.todo-empty strong { display: block; margin-bottom: 4px; font-size: 16px; color: var(--ok); }
@media (max-width: 560px) {
  .todo-row { grid-template-columns: 32px minmax(0, 1fr); }
  .todo-row .btn { grid-column: 2; justify-self: start; }
}

/* Today's activity: a thin timeline, a dot per event. The dot's colour repeats what the line says
   (sent, held), so it is never the only signal. */
.feed { list-style: none; margin: 0; padding: 0; }
.feed li { position: relative; display: flex; gap: 12px; padding: 7px 0; font-size: 14px; }
.feed li::before {
  content: ""; position: absolute; left: 5px; top: 26px; bottom: -7px; width: 1px; background: var(--border);
}
.feed li:last-child::before { display: none; }
.feed .dot {
  width: 11px; height: 11px; margin-top: 5px; flex-shrink: 0; border-radius: 50%;
  border: 2px solid var(--input); background: var(--card);
}
.feed li.ok .dot { border-color: var(--ok); }
.feed li.warn .dot { border-color: var(--warn-line); }
.feed a { color: inherit; text-decoration: none; }
.feed a:hover { text-decoration: underline; }
.feed time { display: block; font-size: 12px; font-variant-numeric: tabular-nums; }

/* --- forms --------------------------------------------------------------- */

label {
  display: block; font-weight: 500; font-size: 14px; margin-bottom: 6px; color: var(--foreground);
}
label .hint {
  display: block; font-weight: 400; font-size: 13px; color: var(--muted-foreground); margin-top: 3px;
}
/* The model picker's "this may not fit in memory" note -- same warning colour as .pill.warn,
   just as plain text rather than a pill, since it is a sentence, not a label. */
.hint.warn { color: var(--warn); }

input[type=text], input[type=password], input[type=number], input[type=email], input[type=date],
input[type=search],
input[type=color], select, textarea {
  /* 16px, not shadcn's 14px: below that iOS Safari zooms the page on focus and does not zoom
     back out. */
  width: 100%; padding: 8px 12px; font: 16px/1.4 var(--font-sans); color: var(--foreground);
  background: var(--card); border: 1px solid var(--input); border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
  /* .row/.grid-2/.grid-3 stretch .field to the tallest sibling (a two-line label, or one with a
     hint the next field over lacks); this pins the control itself to the bottom of that stretched
     box, so inputs across a row always line up regardless of what grew above them. A no-op outside
     a stretched row: with no slack to fill, auto collapses to the content's own height. */
  margin-top: auto;
}
/* The browser's own <select>, so keyboards, screen readers, phones and the no-JavaScript fallback
   all keep working, drawn as a field. Without appearance:none Safari ignored the rule above and
   painted a short, rounded box with its own double arrow (#129). The arrow is Lucide's
   chevron-down, in the muted text colour of either theme; nothing comes from a CDN.
   Only the closed field changes; the list that opens is still the system's own. */
select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  padding-right: 40px;
  background: var(--card) var(--chevron-down) no-repeat right 12px center / 16px 16px;
}
select:disabled { cursor: not-allowed; opacity: .45; }
/* A text field's own height (one line, padding, border). Browsers size a select and a date field
   by their own rules, a few pixels off either way, so a row like Status / From / To did not line
   up; pinning both to the text field's height does. The 25px of the 0.60 fields stayed behind
   in 1.0 and made every dropdown 7px taller than the text field above it. */
select, input[type=date] { height: calc(1.4em + 18px); }
/* High Contrast drops our colours, and a dark chevron could vanish on a dark system background,
   so there the browser draws its own arrow again. */
@media (forced-colors: active) {
  select { appearance: auto; -webkit-appearance: auto; padding-right: 13px; background-image: none; }
}
input[type=color] { padding: 4px 6px; height: 40px; cursor: pointer; }
input::placeholder, textarea::placeholder { color: var(--muted-foreground); opacity: .8; }
input[type=checkbox], input[type=radio] { accent-color: var(--primary); width: 16px; height: 16px; }
input[type=file] { max-width: 100%; }
/* A field the server rejected. Paired with aria-invalid so it is not colour alone (1.4.1). */
input[aria-invalid="true"], textarea[aria-invalid="true"], select[aria-invalid="true"] {
  border-color: var(--err); border-width: 2px;
}
.field-error {
  display: block; margin-top: 6px; font-weight: 400; font-size: 13px; color: var(--err);
}
input:focus, select:focus, textarea:focus {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent);
}

/* One focus indicator for the whole app: a solid outline in --ring, offset from the control
   rather than replacing its border (which vanishes under Windows High Contrast). 3px, not
   shadcn's soft 3px halo alone, because the halo is under 3:1 on white. :where() keeps
   specificity at zero so anything below can still override. */
:where(a, button, .btn, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--ring);
  outline-offset: 2px;
}
@media (forced-colors: active) {
  :where(a, button, .btn, summary, input, select, textarea, [tabindex]):focus-visible {
    outline: 3px solid Highlight;
  }
}
.field { margin-bottom: 15px; display: flex; flex-direction: column; }
.field[hidden] { display: none; }
.row { display: flex; gap: 12px; }
.row > * { flex: 1; }
.row .narrow { flex: 0 0 130px; }
/* 1.4.10 Reflow: .narrow is a fixed 130px that cannot shrink, so a row of fields was as wide as its
   label plus every narrow column. /knowledge's card rows came to 454px at both 320 and 390, and on
   /settings the host-and-port rows stretched each grid card 1px past the window. On a phone every
   field takes a line of its own, as it does everywhere else; a row with no .field in it, such as a
   logo beside its Remove checkbox, keeps its one line. */
@media (max-width: 620px) {
  .row:has(> .field) { flex-wrap: wrap; row-gap: 0; }
  .row > .field { flex: 1 1 100%; min-width: 0; }
}
/* 16px, not 15px: iOS Safari zooms the page on any field below that and does not zoom back out.
   This is the field the owner types in most, on the page the manual says to use on a phone.
   field-sizing lets it grow with the reply instead of scrolling; browsers without it keep the
   min-height and scroll, which is what happened before. */
textarea { line-height: 1.55; }
textarea.compose {
  min-height: 300px; font: 16px/1.6 var(--font-sans); resize: vertical;
  field-sizing: content; max-height: 70vh;
}

/* Buttons are shadcn's Button. The plain button is its "outline" variant; the variants are:
     .primary            the one action the page is for, solid ink ("default" in shadcn)
     .go                 the same look, kept as its own name for the approve buttons
     .secondary          a filled grey, for a second action beside a primary one
     .quiet / .ghost     no border until the pointer is on it
     .danger             destructive: red text, red tint on hover
     .sm                 32px instead of 36px tall, for rows and tables
     .icon               square, for a button that is only an icon (it carries an aria-label) */
button, .btn {
  font-family: var(--font-sans); font-weight: 500; font-size: 14px; letter-spacing: 0;
  text-transform: none; min-height: 36px; padding: 7px 16px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--card); color: var(--foreground);
  box-shadow: var(--shadow-xs); cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; line-height: 1.25;
  transition: background-color .12s, opacity .12s, border-color .12s;
}
button:hover, .btn:hover { background: var(--hover); color: var(--foreground); }
/* The button the owner is meant to press, often on a phone in bright sunlight: solid ink with
   the card colour for its text, which inverts by itself in the dark theme. */
button.primary, .btn.primary {
  background: var(--primary); border-color: var(--primary); color: var(--primary-foreground);
}
button.primary:hover, .btn.primary:hover {
  background: color-mix(in srgb, var(--primary) 88%, var(--background));
  border-color: transparent; color: var(--primary-foreground);
}
button.go, .btn.go { background: var(--primary); border-color: var(--primary); color: var(--primary-foreground); }
button.go:hover, .btn.go:hover {
  background: color-mix(in srgb, var(--primary) 88%, var(--background));
  border-color: transparent; color: var(--primary-foreground);
}
button.secondary, .btn.secondary {
  background: var(--secondary); border-color: transparent; color: var(--secondary-foreground);
  box-shadow: none;
}
button.secondary:hover, .btn.secondary:hover { background: var(--border); }
/* Destructive actions read as a different class of thing, not only by their word. */
button.danger, .btn.danger { color: var(--err); border-color: var(--border); background: var(--card); }
button.danger:hover, .btn.danger:hover { color: var(--err); background: var(--err-bg); }
button.sm, .btn.sm { min-height: 32px; padding: 5px 12px; font-size: 13px; }
button.icon, .btn.icon { min-width: 36px; padding: 7px; }
button.icon.sm, .btn.icon.sm { min-width: 32px; padding: 5px; }
button:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
/* A form mid-submit: the label has already changed, this stops a second press landing. */
form[aria-busy="true"] button { pointer-events: none; opacity: .55; }

.actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 18px; }
.actions .spacer { margin-left: auto; }
/* A row holding a labelled field: its buttons line up with the field, not with the label above it. */
.actions.bottom { align-items: flex-end; }
/* The button that moves on is on the right, the others just left of it, and a destructive one on
   the far left, away from it (Marcel, 2026-10-14). The picture is reordered, not the markup, where
   the row's buttons share one form: Enter in a field presses the form's first submit button. */
.actions.forward { justify-content: flex-end; }
.actions.forward > * { order: 2; }
.actions.forward > :is(.primary, .go, form:has(> :is(.primary, .go))) { order: 3; }
.actions.forward > .danger { order: 1; margin-right: auto; }
/* A button beside a field is as tall as the field, so the two share a top and a bottom edge
   instead of the button sitting on the field's baseline 4px short of its top. The field's height
   is the text field's own: one 16px line at 1.4, 8px padding and a 1px border, top and bottom. */
:is(.actions, .decide-actions, .guest-find, .two-step-form .inline-controls, .cluster-make):has(
    select, input:not([type=hidden]):not([type=checkbox]):not([type=radio]))
  > form > button,
:is(.actions, .decide-actions, .guest-find, .two-step-form .inline-controls, .cluster-make):has(
    select, input:not([type=hidden]):not([type=checkbox]):not([type=radio]))
  > :is(button, .btn) { min-height: calc(16px * 1.4 + 18px); }
/* margin-left:auto only separates within one flex line. Once the buttons wrap on a phone the
   destructive one ends up 10px under "Save for later", so the spacer becomes a real line break. */
@media (max-width: 760px) {
  .actions .spacer { flex-basis: 100%; height: 0; margin-left: 0; }
  .actions > button, .actions > .btn { width: 100%; justify-content: center; }
  /* Stacked on a phone, the button that moves on is the one at the top, under the thumb. */
  .actions.forward > * { order: 0; }
}

/* --- the i-icon and its popover -----------------------------------------
   Shown on hover for a mouse, and on focus for a keyboard or a tap, which is why the trigger is a
   real button rather than a styled span. */

.help { position: relative; display: inline-block; margin-left: 7px; vertical-align: middle; }
/* The disc still reads as 19px, but the button itself is 24x24 so it meets 2.5.8 Target Size.
   A circular icon badge, kept round like the logo disc and the step markers below.
   cursor was `help`, which tells a mouse user this is not clickable; it is a real button. */
.help-icon {
  width: 24px; height: 24px; min-height: 0; padding: 0; border: 0; border-radius: 50%;
  background: none; box-shadow: none; color: var(--muted-foreground);
  display: grid; place-items: center; cursor: pointer;
}
.help-icon:hover { background: none; }
/* Lucide's info icon, drawn from the theme's token so it follows light and dark. */
.help-icon::before {
  content: ""; width: 16px; height: 16px; background: var(--icon-info) no-repeat center / 16px 16px;
}
.help-icon:hover::before, .help:focus-within .help-icon::before { background-image: var(--icon-info-on); }
@media (forced-colors: active) {
  .help-icon::before { content: "i"; background: none; font-weight: 700; }
}
.help-text {
  position: absolute; z-index: 30; left: -6px; top: 26px;
  /* Was display:none, which drops the text out of the accessibility tree, so the
     aria-describedby on the button resolved to nothing whenever it was not focused and a screen
     reader in browse mode could never reach any of the help texts. Clipped instead: present for
     assistive technology at all times, painted only on hover or focus. */
  visibility: hidden; opacity: 0;
  width: max-content; max-width: min(340px, 74vw);
  background: var(--card); color: var(--muted-foreground);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 10px 12px; font: 400 13.5px/1.5 var(--font-sans); text-transform: none; letter-spacing: 0;
  white-space: normal;
}
/* 1.4.13 requires the popover to be hoverable. The gap between the 24px icon and the panel used to
   be dead space, so the pointer lost :hover on the way down and the panel could never be reached
   to read a long explanation. This bridges the full width of the panel. */
.help-text::after {
  content: ""; position: absolute; top: -8px; left: 0; right: 0; height: 8px;
}
.help-text::before {           /* small pointer up towards the icon */
  content: ""; position: absolute; top: -6px; left: 9px; width: 10px; height: 10px;
  background: var(--card); border-left: 1px solid var(--border);
  border-top: 1px solid var(--border); transform: rotate(45deg);
}
.help:hover .help-text, .help:focus-within .help-text, .help-text:hover {
  visibility: visible; opacity: 1;
  /* ui.js measures the open panel and sets this when it would cross the edge of the window. */
  translate: var(--help-shift, 0px) 0;
}
/* ...and the pointer moves back the other way, so it still points at the icon. */
.help-text::before { translate: calc(-1 * var(--help-shift, 0px)) 0; }
/* Esc closes it (1.4.13 dismissable); the script sets this on the wrapper. */
.help.dismissed .help-text { visibility: hidden; opacity: 0; }
/* 1.4.10 Reflow: hidden is not the same as gone. A closed panel kept its full 340px, laid out
   beside its icon, and in a right-hand column that ran past the window and /settings scrolled
   sideways between about 820 and 1000px. Closed, it now has no size at all; the text is still
   there for aria-describedby. */
.help:not(:hover):not(:focus-within) .help-text, .help.dismissed .help-text {
  width: 0; height: 0; padding: 0; border-width: 0; overflow: hidden; translate: none;
}
/* Which way it opens by default. Only a first guess: whether it fits depends on where the icon
   lands at the current width, which CSS cannot see, so ui.js nudges the open panel back inside. */
.grid-2 > *:last-child .help-text, .row > *:last-child .help-text { left: auto; right: -6px; }
.grid-2 > *:last-child .help-text::before,
.row > *:last-child .help-text::before { left: auto; right: 9px; }
@media (max-width: 620px) {
  .help-text { left: auto; right: -6px; max-width: 78vw; }
  .help-text::before { left: auto; right: 9px; }
}
/* Inside anything that scrolls or clips (a card's dialog on Wissen, a table that scrolls
   sideways), an absolute panel was cut off at that box's edge and the end of every line was lost
   (issue #161). There ui.js lifts the open panel out of the box: fixed to the window, placed under
   the icon or above it when there is no room below, and kept inside the window. The pointer
   follows the icon. */
.help.floating .help-text {
  position: fixed; left: var(--help-x, 16px); right: auto; top: var(--help-y, 16px); translate: none;
}
.help.floating .help-text::before { left: var(--help-arrow, 9px); right: auto; translate: none; }
.help.floating.above .help-text::before { top: auto; bottom: -6px; transform: rotate(225deg); }
.help.floating.above .help-text::after { top: auto; bottom: -8px; }

/* --- wizard -------------------------------------------------------------- */

/* row-gap only matters once this wraps (the step count has already outgrown one row width
   twice -- 8 steps, now 9 -- and will likely again) -- harmless whitespace above that,
   breathing room between rows if it ever does. */
.steps { display: flex; gap: 0; margin: 4px 0 26px; list-style: none; padding: 0;
  flex-wrap: wrap; row-gap: 10px; }
.steps li {
  display: flex; align-items: center; gap: 5px; padding: 8px 5px 8px 0;
  font-weight: 500; font-size: 12px; color: var(--muted-foreground);
}
/* A circular step marker. */
.steps li .dot {
  width: 20px; height: 20px; flex: none; border-radius: 99px; display: grid; place-items: center;
  border: 1px solid var(--input); background: var(--card); font-size: 11px; letter-spacing: 0;
}
.steps li.done .dot { background: var(--ok); border-color: var(--ok); color: var(--card); }
.steps li.on .dot { background: var(--primary); border-color: var(--primary); color: var(--primary-foreground); }
.steps li.on { color: var(--foreground); font-weight: 600; }
.steps li + li::before {
  content: ""; width: 8px; height: 1px; background: var(--input); margin-right: 5px;
}

/* --- progress ------------------------------------------------------------ */

.track {
  height: 8px; background: var(--muted); overflow: hidden; border-radius: 99px;
  border: 1px solid var(--input);
}
.track > i {
  display: block; height: 100%; width: 0;
  background: var(--primary);
  transition: width .4s ease;
}
/* A job that failed keeps the same warning colour used everywhere else that means "needs
   attention" (.pill.warn, .card.decide.urgent), rather than the colour that means success. */
.track.failed > i { background: var(--warn-line); }
.job-step { font-size: 14px; color: var(--muted-foreground); margin-top: 11px; }
.job-log {
  margin-top: 12px; max-height: 190px; overflow: auto; font: 13px/1.7 var(--mono);
  color: var(--muted-foreground); background: var(--muted); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 11px 13px; white-space: pre-wrap;
}

/* --- messages ------------------------------------------------------------ */

.note {
  /* shadcn's Alert: a tinted block with 12px corners; the tint and the text colour carry the
     kind, and the wording always says it too. */
  border-radius: var(--radius-lg); padding: 12px 14px; font-size: 14px; margin-bottom: 16px;
  border: 1px solid var(--border); background: var(--card);
}
.note.ok { background: var(--ok-bg); border-color: transparent; color: var(--ok); }
.note.bad { background: var(--err-bg); border-color: transparent; color: var(--err); }
.note.info { background: var(--info-bg); border-color: transparent; color: var(--info); }
/* Links inside a tinted note take its colour, so they stay readable on the tint. */
.note.ok a, .note.bad a, .note.info a, .note.warn a { color: inherit; }
/* Something to look at before sending that is not an error: the guest asked more than the
   draft could take on. Same orange as .pill.warn. */
/* What an import found: at the top of the Knowledge page after it ran, and grouped by card in
   the setup wizard. */
.import-results { margin-bottom: 30px; }
.import-summary a { margin-left: 4px; }
.job-log-more { margin-top: 8px; font-size: 14px; color: var(--muted-foreground); }
.job-log-more summary { cursor: pointer; }
.decide-group .group-count { margin-left: 8px; vertical-align: middle; }
.decide-group .decide-item + .decide-item { border-top: 1px solid var(--border); margin-top: 18px; padding-top: 16px; }
/* The earlier messages of a conversation on a draft's page, folded by default. */
.thread summary { cursor: pointer; }
.thread summary h2 { display: inline; }
.thread-message { margin-top: 14px; }
.thread-who { font-size: 13.5px; color: var(--muted-foreground); margin-bottom: 4px; }
.thread-message.us .quoted { border-left: 3px solid var(--primary); }
.note.warn { background: var(--warn-bg); border-color: transparent; color: var(--warn); }
.note ul.extra-questions { margin: 8px 0 0; padding-left: 20px; }
.note ul.extra-questions li { margin: 2px 0; overflow-wrap: anywhere; }
.note code { background: color-mix(in srgb, currentColor 10%, transparent); padding: 1px 5px; border-radius: 4px; }

.pill {
  /* shadcn's Badge, "outline" variant; .good, .warn and .rec are the tinted ones. */
  font-family: var(--font-sans); font-weight: 500; font-size: 12px; line-height: 20px; letter-spacing: 0;
  padding: 0 8px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  color: var(--foreground); background: var(--card); white-space: nowrap;
  /* Pills hold text we do not control: a page title off the owner's website, a guest's name. One
     long enough to exceed the card used to run off the edge of it. Truncate instead, with the full
     text on the element's title attribute. */
  display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  vertical-align: middle;
}

/* A row of pills. Needed because a Jinja loop emits </span><span> with no whitespace between them,
   and inline elements only wrap at whitespace, so without this they never wrap at all. */
.pill-row {
  display: flex; flex-wrap: wrap; gap: 7px; align-items: center; margin: 0;
  min-width: 0;
}
.pill-row .pill { max-width: min(100%, 34ch); }

/* "and 19 more", which opens the rest as a plain list rather than 19 more pills. */
.pill-more { position: relative; }
.pill-more > summary { cursor: pointer; list-style: none; }
.pill-more > summary::-webkit-details-marker { display: none; }
.pill-more[open] > summary { border-color: var(--input); }
.pill-more > ul {
  margin: 8px 0 0; padding: 10px 14px 10px 28px; background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius); font-size: 14px;
  max-height: 240px; overflow: auto;
}
.pill-more > ul li { margin-bottom: 3px; }
.pill.good { color: var(--ok); border-color: transparent; background: var(--ok-bg); }
.pill.warn { color: var(--warn); border-color: transparent; background: var(--warn-bg); }
.pill.lang { text-transform: uppercase; background: var(--secondary); border-color: transparent; }
.pill.rec { color: var(--info); border-color: transparent; background: var(--info-bg); }

/* One row per candidate model in the setup wizard's model picker: the whole row is the label, so
   clicking anywhere (not just the radio dot) selects it. Three columns -- the radio, what the
   model is, its status -- each centred on the row, so the radio, the text and the status line up
   whatever the description's length. On a phone the status moves under the text rather than
   being squeezed into "do…". */
.model-option {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 6px 14px;
  align-items: center; padding: 14px 12px; margin: 0 -12px;
  border-bottom: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; font: 400 15px/1.45 var(--font-sans); letter-spacing: normal;
}
.model-option:last-child { border-bottom: none; }
.model-option:has(input:checked) { background: var(--muted); }
.model-option input[type=radio] { margin: 0; }
.model-option-title { font-family: var(--font-sans); font-weight: 600; }
.model-option-name { font: 13px/1.4 var(--mono); color: var(--muted-foreground); margin-left: 6px; }
.model-option-desc { display: block; font-size: 14px; color: var(--muted-foreground); margin-top: 3px; }
.model-option-meta { display: block; font-size: 13px; color: var(--muted-foreground); margin-top: 3px; }
.model-option .pill-row { justify-content: flex-end; }
.model-memory { margin: 0 0 12px; color: var(--muted-foreground); font-size: 14.5px; }
@media (max-width: 620px) {
  .model-option { grid-template-columns: auto minmax(0, 1fr); }
  .model-option .pill-row { grid-column: 2; justify-content: flex-start; }
}

/* Models on this machine: name, size, then what it is used for or a way to delete it, in three
   columns centred on each row, so a status pill and a Delete button sit on the same line as the
   name rather than one of them lower. */
.disk-card { margin-top: 32px; }

/* Settings > Advanced, Cloud drafting: one fieldset per provider, only the chosen one shown. */
.cloud-provider { border: 0; border-top: 1px solid var(--border); margin: 6px 0 0; padding: 12px 0 0;
  min-width: 0; }
.cloud-provider legend { font-family: var(--font-sans); font-weight: 600; font-size: 14.5px; padding: 0 8px 0 0; }
.cloud-option { margin-top: 18px; }
.ik-models-row { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; margin-top: 2px; }
.ik-models-row .field-hint { margin-top: 0; }
.ik-models-row button { padding: 4px 8px; font-size: 13.5px; }

/* Signing in, and the accounts an admin manages (butler_li/accounts.py). */
.login-wrap { display: flex; justify-content: center; padding-top: 6vh; }
.login-card { width: 100%; max-width: 26rem; padding: 26px 28px; }
.login-card h1 { margin: 0 0 6px; font-size: 26px; }
.login-card .actions { margin-top: 6px; }
.login-card .actions button { width: 100%; }
.login-help { margin: 14px 0 0; font-size: 14px; }
.accounts-card { margin-top: 32px; }
.accounts-intro { margin: -4px 0 12px; font-size: 14px; }
/* The rows share their columns (subgrid), each as wide as its widest entry in any row, so roles
   and dates line up down the list and a long pill such as "Erste Anmeldung ausstehend" stays on
   one line rather than wrapping under the role in a fixed column. */
.account-list {
  display: grid; column-gap: 16px; border-bottom: 1px solid var(--border);
  grid-template-columns: minmax(8rem, 1fr) minmax(0, max-content) minmax(0, max-content) minmax(7.5rem, max-content);
}
.account-row {
  grid-column: 1 / -1; display: grid; grid-template-columns: subgrid;
  /* At least as tall as the Manage button, so the row does not shift when it opens (below). */
  grid-template-rows: minmax(35px, auto);
  row-gap: 10px; align-items: center; padding: 11px 0; border-top: 1px solid var(--border);
  position: relative;
}
.account-row.off .account-who strong { color: var(--muted-foreground); }
.account-who { display: flex; flex-wrap: wrap; gap: 2px 8px; align-items: baseline; min-width: 0; }
.account-who strong { font-family: var(--font-sans); font-weight: 600; font-size: 16px; overflow-wrap: anywhere; }
.account-who .muted { font-size: 14.5px; }
.account-state { display: flex; gap: 6px; flex-wrap: wrap; min-width: 0; }
.account-last { font-size: 14px; }
.account-actions { justify-self: end; }
.account-manage > summary { list-style: none; cursor: pointer; padding: 6px 14px; font-size: 14px;
  line-height: 20px; display: block; width: fit-content; margin-left: auto; white-space: nowrap; }
.account-manage > summary::-webkit-details-marker { display: none; }
.account-manage[open] > summary { background: var(--hover); }
/* Opened, the forms take the whole width under the row, and the button stays where it was rather
   than dropping onto a line of its own above them, which left a gap and made the forms look as if
   they belonged to the next account. */
.account-row:has(.account-manage[open]) .account-actions { grid-column: 1 / -1; justify-self: stretch; }
.account-row:has(.account-manage[open]) .account-manage > summary {
  position: absolute; top: 11px; right: 0;
}
.account-row:has(.account-manage[open]) { padding-bottom: 16px; }
.account-forms {
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 12px 20px;
  align-items: center; padding: 16px 18px; border-radius: var(--radius);
  background: var(--subtle); border: 1px solid var(--border);
}
.account-forms .inline-form {
  grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: center; margin: 0;
}
/* A form with no label of its own (deactivate, reactivate, delete) starts at the left edge. */
.account-forms .inline-controls:only-child { grid-column: 1 / -1; }
.account-forms .account-danger { padding-top: 12px; border-top: 1px solid var(--border); }
.account-forms .inline-form > label, .account-forms .inline-legend {
  margin: 0; font-family: var(--font-sans); font-weight: 600; font-size: 14.5px;
}
.inline-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; min-width: 0; }
/* One height for every control in the panel: the field, its button and the radio row. */
.account-forms button { padding: 0 18px; height: 38px; font-size: 14.5px; }
.account-forms input[type=password] {
  width: 15rem; max-width: 100%; height: 38px; padding: 0 12px; margin: 0;
}
.account-forms input[type=radio] { width: auto; margin: 0; }
.account-forms .muted { font-size: 14px; }
.accounts-subhead { margin: 26px 0 4px; font-size: 16px; }
/* The sign-in history under the accounts (issue #191): one line per attempt, which wraps at phone
   width (time and outcome, then who, then where from). Failed ones carry the warning pill. */
.signins-card { margin-top: 24px; }
/* The switch for the mail about a sign-in from a new browser (issue #192): the choice and its
   button on one line where there is room, with the warning under it while it is off. */
.browser-mail-card { margin-top: 24px; }
.browser-mail-intro { margin: -4px 0 12px; font-size: 14px; }
.browser-mail-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; }
.browser-mail-form .field { margin: 0; min-width: min(100%, 16rem); }
.browser-mail-card .note { margin-top: 12px; }
.signins-intro { margin: -4px 0 8px; font-size: 14px; }
.signins-day { margin: 0 0 12px; font-size: 14px; }
.signin-list { list-style: none; margin: 0; padding: 0; border-bottom: 1px solid var(--border); }
.signin-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; padding: 9px 0;
  border-top: 1px solid var(--border); font-size: 14px; min-width: 0;
}
.signin-when { font-variant-numeric: tabular-nums; white-space: nowrap; }
.signin-who { font-weight: 600; overflow-wrap: anywhere; }
.signin-where { overflow-wrap: anywhere; min-width: 0; }
.signins-more { margin: 12px 0 0; font-size: 14px; }
/* Two-step sign-in (account_two_step.html): the numbered setup, the QR code on white whatever the
   theme, since a phone camera needs the contrast, and the recovery codes in two columns to copy. */
.two-step-steps { margin: 0; padding-left: 1.4em; display: grid; gap: 18px; }
.two-step-steps > li { padding-left: 4px; }
.two-step-qr { margin: 12px 0 8px; width: fit-content; max-width: 100%; background: #fff;
  border: 1px solid var(--border); line-height: 0; }
.two-step-qr svg { display: block; max-width: 100%; height: auto; }
.two-step-key { letter-spacing: .04em; overflow-wrap: anywhere; }
.two-step-form { display: grid; gap: 6px; margin: 12px 0 0; }
.two-step-form > label { font-family: var(--font-sans); font-weight: 600; font-size: 14.5px; }
.two-step-form input { width: 15rem; max-width: 100%; margin: 0; }
.two-step-form.account-danger { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.recovery-codes { margin: 4px 0 18px; padding-left: 2em; columns: 2 12rem; column-gap: 32px; }
.recovery-codes li { margin-bottom: 6px; break-inside: avoid; }
.recovery-codes code { font-size: 16px; letter-spacing: .04em; }
/* Adding an account: the three fields keep their own heights, so labels stay level at the top. */
.account-add .grid-2, .account-add .grid-3 { align-items: start; }
/* A hint under its field rather than inside the label: see .account-add and _infomaniak_model.html. */
.field-hint { display: block; margin-top: 6px; font-size: 13.5px; color: var(--muted-foreground); }
.role-choice { border: 0; margin: 2px 0 14px; padding: 0; min-width: 0; }
.role-choice legend { font-family: var(--font-sans); font-weight: 600; font-size: 15px;
  padding: 0; margin-bottom: 8px; }
.role-options { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.role-option {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; align-items: start;
  padding: 13px 14px; border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; font: 400 15px/1.45 var(--font-sans); letter-spacing: normal;
}
.role-option:hover { border-color: var(--input); }
.role-option:has(input:checked) { border-color: var(--primary); background: var(--muted); }
.role-option input[type=radio] { margin: 3px 0 0; }
.role-option-title { display: block; font-family: var(--font-sans); font-weight: 600; }
.role-option-desc { display: block; font-size: 14px; color: var(--muted-foreground); margin-top: 2px; }
.role-inline { display: inline-flex; gap: 14px; align-items: center; }
.role-inline label { display: inline-flex; gap: 6px; align-items: center; margin: 0; font-size: 15px;
  font-family: var(--font-sans); font-weight: 400; cursor: pointer; }
.inline-legend { font-family: var(--font-sans); font-weight: 600; font-size: 14px; }
@media (max-width: 620px) { .role-options { grid-template-columns: 1fr; } }
@media (max-width: 760px) {
  /* On a phone each row stacks: the name with Manage beside it, then role and state, then the
     date; opened, the forms come after all three. */
  .account-list { display: block; }
  .account-row { grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; row-gap: 6px; }
  .account-who { grid-column: 1; grid-row: 1; }
  .account-actions { grid-column: 2; grid-row: 1; }
  .account-state, .account-last { grid-column: 1 / -1; }
  .account-row:has(.account-manage[open]) .account-actions { grid-row: auto; margin-top: 6px; }
}
@media (max-width: 620px) {
  /* Labels over their controls rather than beside them. */
  .account-forms { grid-template-columns: minmax(0, 1fr); row-gap: 8px; padding: 14px; }
  .account-forms .inline-form { row-gap: 6px; }
  .account-forms .inline-form + .inline-form { margin-top: 6px; }
  /* min-width: a field's automatic minimum comes from its size attribute, about 14rem, which kept
     Set off the field's line even where both fit. */
  .account-forms input[type=password] { flex: 1 1 10rem; width: auto; min-width: 0; }
}

/* Settings > Costs: what the models were asked to do, in tokens and time. */
.usage-card { margin-top: 32px; }
.usage-head { flex-wrap: wrap; row-gap: 10px; }
.usage-periods { display: flex; flex-wrap: wrap; gap: 6px; }
.usage-periods .btn { padding: 6px 12px; font-size: 14px; }
.usage-intro { margin: -4px 0 16px; font-size: 14px; }
.usage-stats { display: flex; flex-wrap: wrap; gap: 14px 34px; margin: 0 0 14px; }
.usage-stats .num { font-size: 26px; font-variant-numeric: tabular-nums; }
.usage-reply { margin: 0 0 6px; }
.usage-subhead { margin: 22px 0 6px; font-size: 16px; }
/* Seven columns do not fit a phone; the table scrolls inside its card, never the page. */
.usage-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* Fixed widths, so the task table and the job table under it line up column for column. */
.usage-table { width: 100%; min-width: 620px; border-collapse: collapse; font-size: 14.5px;
  table-layout: fixed; }
.usage-table .c-name { width: 31%; }
.usage-table .c-calls { width: 9%; }
.usage-table .c-read, .usage-table .c-written { width: 12%; }
.usage-table .c-time { width: 13%; }
.usage-table .c-notes { width: 23%; }
.usage-table th, .usage-table td {
  padding: 7px 10px 7px 0; border-bottom: 1px solid var(--border); text-align: left;
  vertical-align: top; white-space: nowrap;
}
.usage-table thead th { font-family: var(--font-sans); font-weight: 600; color: var(--muted-foreground); }
.usage-table tbody th { font-weight: 400; white-space: normal; }
.usage-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.usage-table tr:last-child td, .usage-table tr:last-child th { border-bottom: none; }
.usage-table .num + td, .usage-table thead th:last-child { padding-left: 14px; }
.usage-notes { color: var(--muted-foreground); white-space: normal; }
.usage-downloads { margin: 18px 0 4px; display: flex; flex-wrap: wrap; gap: 6px 10px; }
.usage-kept { margin: 0; font-size: 13.5px; }

/* Settings > Costs. The period buttons sit above the cards; the cards stack. */
.cost-periods { margin: 0 0 18px; }
.cost-stack { display: grid; gap: 18px; }
.cost-stack > .card { margin: 0; min-width: 0; }
.cost-figures { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 28px; }
@media (max-width: 900px) { .cost-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 460px) { .cost-figures { grid-template-columns: minmax(0, 1fr); } }
.cost-figures .num { font-size: 28px; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.cost-basis { font-size: 13.5px; color: var(--muted-foreground); margin-top: 4px; }
.cost-note { margin: 14px 0 0; font-size: 14px; color: var(--muted-foreground); }
.cost-summary .note { margin: 16px 0 0; }

/* Settings > Data & security: a short two-column table of what is kept, then one block per
   outside service, its pill beside the heading and wrapping under it on a phone. */
.data-table { border-collapse: collapse; font-size: 14.5px; margin: 4px 0 0; }
.data-table th, .data-table td { padding: 6px 0; border-bottom: 1px solid var(--border);
  text-align: left; font-weight: 400; }
.data-table td.num { padding-left: 28px; text-align: right; font-variant-numeric: tabular-nums; }
.data-table tr:last-child th, .data-table tr:last-child td { border-bottom: none; }
.data-services { list-style: none; margin: 0; padding: 0; }
.data-services li { padding: 12px 0; border-top: 1px solid var(--border); }
.data-service-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 12px; }
.data-service-head h3 { margin: 0; font-size: 16px; }
.data-services p { margin: 6px 0 0; overflow-wrap: anywhere; }
.data-points { margin: 0; padding-left: 20px; }
.data-points li { margin: 0 0 8px; }
/* One guest's data: the address field and its button on one line where there is room. */
.guest-find { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0 12px; }
.guest-find .field { flex: 1 1 260px; margin-bottom: 0; }
.guest-list { margin: 0; padding-left: 20px; }
.guest-list li { margin: 0 0 4px; overflow-wrap: anywhere; }
/* The box that confirms a deletion sits at the start of its sentence, which wraps beside it. */
.guest-confirm { display: flex; align-items: flex-start; gap: 8px; font: inherit; overflow-wrap: anywhere; }
.guest-confirm input { flex: none; margin-top: 4px; }

/* Bars of one height scale, each split by what its money went to. A day with nothing is an
   empty slot, so the days stay evenly spaced. */
.cost-chart { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: 160px auto;
  column-gap: 10px; }
.cost-chart-scale { display: flex; flex-direction: column; justify-content: space-between;
  font-size: 12.5px; color: var(--muted-foreground); text-align: right; font-variant-numeric: tabular-nums; }
.cost-chart-bars { display: flex; align-items: flex-end; gap: 2px; height: 160px;
  border-bottom: 1px solid var(--input); }
.cost-chart-bars.many { gap: 1px; }   /* three months of days still fit a phone */
.cost-bar { flex: 1 1 0; min-width: 0; height: 100%; display: flex; align-items: flex-end; }
.cost-bar-fill { width: 100%; display: flex; flex-direction: column-reverse; min-height: 1px; }
.cost-bar-fill > span { display: block; width: 100%; }
.cost-chart-axis { grid-column: 2; display: flex; justify-content: space-between; margin-top: 4px;
  font-size: 12.5px; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.cost-legend { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap;
  gap: 6px 18px; font-size: 14px; }
.cost-legend li { display: flex; align-items: center; gap: 6px; }
.cost-swatch { display: inline-block; width: 10px; height: 10px; margin-right: 6px; flex: none; }
.cost-legend .cost-swatch { margin-right: 0; }
/* The chart island (CostChart.tsx) replaces the CSS bars once it has mounted; until then, or
   without its script, the bars above stand. Recharts writes its colours as var() tokens, so the
   dark theme reaches it without a line of script. */
.cost-chart-live:empty, .cost-chart-enhanced .cost-chart { display: none; }
.cost-chart-live { margin-top: 4px; }
.cost-chart-live .recharts-surface { overflow: visible; }
.chart-tooltip { background: var(--popover, var(--card)); color: var(--popover-foreground, var(--foreground));
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; font-size: 13px;
  box-shadow: 0 4px 12px rgb(0 0 0 / .12); min-width: 180px; }
.chart-tooltip-title { font-weight: 600; margin-bottom: 6px; }
.chart-tooltip-row { display: grid; grid-template-columns: 10px 1fr auto; align-items: center; gap: 8px; line-height: 1.6; }
.chart-tooltip-value { font-variant-numeric: tabular-nums; text-align: right; }
.chart-tooltip .cost-swatch { margin: 0; border-radius: 2px; }
.chart-tooltip-total { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 4px; font-weight: 600; }
.fn-drafting { background: var(--fn-drafting); }
.fn-translation { background: var(--fn-translation); }
.fn-knowledge { background: var(--fn-knowledge); }
.fn-indexing { background: var(--fn-indexing); }
.fn-other { background: var(--fn-other); }
@media (forced-colors: active) {
  /* The colours are the information here: keep them, and an outline for the empty slots. */
  .cost-bar-fill > span, .cost-swatch { forced-color-adjust: none; }
}

/* The cost tables share the usage table's look, with their own columns. */
.cost-table .c-name { width: 34%; }
.cost-table .c-cost { width: 11%; }
.cost-table .c-share { width: 22%; }
.cost-table .c-kind { width: 15%; }
.cost-table .c-price { width: 16%; }
.cost-table .c-use { width: 34%; }
.cost-hint { display: block; font-size: 13px; color: var(--muted-foreground); }
.cost-model { font-family: var(--mono); font-size: 13.5px; overflow-wrap: anywhere; }
.cost-share { display: flex; align-items: center; gap: 8px; font-variant-numeric: tabular-nums; }
.cost-share-bar { flex: 1 1 auto; max-width: 110px; height: 8px; border-radius: 99px; overflow: hidden; background: var(--muted); }
.cost-share-bar > span { display: block; height: 100%; background: var(--muted-foreground); }
.cost-prices .pill { margin-right: 4px; }
.cost-prices td, .cost-prices tbody th { vertical-align: middle; }
.cost-prices .price-input { width: 6.5em; max-width: 100%; text-align: right;
  font-variant-numeric: tabular-nums; padding: 5px 8px; }
.cost-own { color: var(--muted-foreground); font-style: italic; }
.cost-compare .c-name { width: 46%; }
.cost-compare .c-cost { width: 18%; }
.cost-compare .c-diff { width: 36%; }
.disk-summary { margin: -4px 0 6px; color: var(--muted-foreground); font-size: 14px; }
.disk-model {
  /* A fixed last column, so the sizes stay in one column when a row turns into its question. */
  display: grid; grid-template-columns: minmax(0, 1fr) 5.5em 17rem; gap: 8px 16px;
  align-items: center; min-height: 54px; padding: 8px 0; border-top: 1px solid var(--border);
}
.disk-model:first-child { border-top: none; }
.disk-model code { font-size: 14px; }
.disk-model-size { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
.disk-model-action { justify-self: end; }
.disk-model-action form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  justify-content: flex-end; margin: 0; }
.disk-model-action .disk-question { font-size: 14px; color: var(--muted-foreground); }
@media (max-width: 620px) {
  .disk-model { grid-template-columns: minmax(0, 1fr) auto; }
  .disk-model-action { grid-column: 1 / -1; justify-self: start; }
}

/* --- queue --------------------------------------------------------------- */

a.qrow { display: block; text-decoration: none; color: inherit; }
a.qrow .card { margin-bottom: 10px; padding: 16px 19px; transition: border-color .15s; }
a.qrow:hover .card { border-color: var(--input); background: var(--subtle); }
.qmeta {
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  font-size: 14px; color: var(--muted-foreground); margin-bottom: 6px;
}
/* An <h2> so the queue has a real heading outline and can be navigated by heading, styled back
   down to the size it always looked. */
.qsubj {
  font-family: var(--font-sans); font-weight: 600; font-size: 16px; line-height: 1.35;
  margin: 0 0 4px; color: var(--foreground);
}
.qsnip {
  color: var(--muted-foreground); font-size: 14px; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}

pre.quoted {
  white-space: pre-wrap; word-break: break-word; margin: 0; padding: 14px 16px;
  background: var(--subtle); border: 1px solid var(--border); border-radius: var(--radius);
  font: 15px/1.6 var(--font-sans); max-height: 340px; overflow: auto;
}
/* Our own reply: a stronger edge on the left, so it is not told apart by colour alone. */
pre.quoted.mine { border-left: 3px solid var(--primary); background: var(--card); }
/* Spam's Set aside list: enough to recognise a newsletter, the rest scrolls in its box. */
pre.quoted.spam-preview { max-height: 220px; }

/* A proposed answer read off the website. Deliberately not styled like an accepted fact: it stays
   a suggestion until the owner presses the button. */
.answer-suggest {
  margin-top: 10px; padding: 12px 15px; border-radius: var(--radius);
  background: var(--info-bg); border: 1px dashed var(--info); color: var(--info);
  font: 15px/1.55 var(--font-sans);
}
.answer-suggest a { margin-left: 8px; }

/* A conversation in the reading language, with the switch to the languages it was written in
   (issue #161), and the other version of the reply folded away underneath. */
.view-switch {
  display: flex; align-items: center; justify-content: space-between; gap: 8px 12px; flex-wrap: wrap;
  margin-bottom: 16px;
}
.translated-from { margin: 0 0 6px; font-size: 13px; }
.reply-fold { margin-top: 12px; }
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.pair pre.quoted { max-height: 520px; }
@media (max-width: 800px) { .pair { grid-template-columns: 1fr; } }

.empty { text-align: center; padding: 48px 20px; color: var(--muted-foreground); }
.empty svg { margin-bottom: 14px; color: var(--muted-foreground); }
.empty h2 { font-size: 17px; margin: 0 0 6px; }
.empty p { margin: 0 auto 6px; max-width: 46ch; }
.empty .actions { justify-content: center; margin-top: 20px; }

/* --- the decisions inbox -------------------------------------------------
   One list, one row per decision, one primary action per row. The visual job is to make the row you
   should act on first obvious without reading all of them, and to make the list look finishable. */

.muted { color: var(--muted-foreground); }
/* Replaces the style="color:var(--foreground)" repeated on four templates. */
.ink { color: var(--foreground); }
/* Replaces style="display:inline" on nine forms. */
form.inline { display: inline; }
/* The folder list on the connection step. Two fixed columns of server folder names were
   unreadable at 360px; this drops to one column when there is no room for two. */
.folder-list {
  columns: 2 16em; margin: 0; padding-left: 20px; font-size: 14px;
}

.inbox-head { display: flex; align-items: baseline; gap: 12px; margin: 26px 0 12px; flex-wrap: wrap; }
.inbox-head h2 { margin: 0; }

.card.decide { border-left: 4px solid var(--border); }
/* A gap on a card that is switched on is putting brackets into live replies, so it earns the one
   loud colour in the palette. Nothing else in the list does. */
.card.decide.urgent { border-left-color: var(--warn-line); }

.decide-title {
  margin: 8px 0 2px; font-size: 16px; line-height: 1.35;
  overflow-wrap: anywhere;           /* a placeholder can be one long unbroken string */
}
.decide-context { margin: 0 0 12px; color: var(--muted-foreground); font-size: 14.5px; }
/* The sentence the gap stands in reads as the question itself, so it is in the text colour,
   with a quiet label and the link to the draft beside it. */
.decide-asked { margin: 6px 0 6px; font-size: 14.5px; overflow-wrap: anywhere; max-width: 75ch; }
.decide-asked-label { color: var(--muted-foreground); }
.decide-asked a { white-space: nowrap; }
.decide-asked-label ~ a { margin-left: 4px; }
.decide-form { margin-top: 12px; }
/* Only the answer field: a :last-of-type that reached into .decide-actions also matched "Remember
   it on", whose 12px then lifted the dropdown above the buttons beside it. */
.decide-form > .field:last-of-type { margin-bottom: 12px; }
.decide-actions { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
/* The button that moves on ("Answer and add to drafts", "Remember it") is always on the far
   right, and "No thanks" or "Open the card" just left of it; the field keeps the left edge. Only
   the picture is reordered: the main button stays first in the form, because Enter in the answer
   field presses a form's first submit button, and that must never be "No thanks". */
.decide-actions:has(> .go) { justify-content: flex-end; }
.decide-actions > .field { margin-right: auto; }
.decide-actions > :is(button, .btn):not(.go) { order: 2; }
.decide-actions > .go { order: 3; }
.decide-actions .field { margin-bottom: 0; }
.decide-actions .field.narrow { flex: 0 1 230px; }
/* "Remember it on" when it also offers a card's gaps (issue #108): an option names the gap, so it
   takes the room the row has rather than cutting that to a few words, and min-width lets the
   longest option shrink with the page instead of pushing it sideways on a phone. */
.decide-actions .field.wide { flex: 1 1 260px; min-width: 0; }
.decide-note { margin: 12px 0 0; color: var(--muted-foreground); font-size: 13.5px; max-width: 68ch; }
button.quiet, button.ghost, .btn.ghost {
  border-color: transparent; background: none; box-shadow: none; color: var(--muted-foreground);
}
button.quiet:hover, button.ghost:hover, .btn.ghost:hover { background: var(--hover); color: var(--foreground); }

.decide-more { margin-top: 10px; font-size: 14px; }
/* padding takes the disclosure control from ~22px to 30px tall, clearing the 24px in 2.5.8. */
.decide-more > summary { cursor: pointer; color: var(--muted-foreground); padding: 4px 0; }
.decide-more ul { margin: 6px 0 0; color: var(--muted-foreground); }
.decide-more p { margin: 6px 0 4px; overflow-wrap: anywhere; }
.decide-more blockquote {
  margin: 0; padding: 8px 12px; background: var(--card); border-radius: 0 var(--radius) var(--radius) 0;
  border-left: 3px solid var(--input); color: var(--muted-foreground); font-size: 14px;
}

.empty-inbox { display: flex; gap: 16px; align-items: flex-start; }
.empty-inbox svg { color: var(--ok); flex: 0 0 auto; margin-top: 2px; }

/* --- collapsible blocks -------------------------------------------------
   The store of cards used to be one page you scrolled for ten screens. Each card is now a row that
   opens, so the list is readable at a glance and the editor is one click away. */

.block, .card.fact-card { margin-bottom: 12px; }
.block {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.block > summary, .card.fact-card > summary {
  cursor: pointer; padding: 16px 20px; display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; list-style: none; font-family: var(--font-sans); font-weight: 600;
  border-radius: var(--radius-lg);
}
.card.fact-card { padding: 0; }
.card.fact-card > summary { padding: 15px 20px; }
.block > summary::-webkit-details-marker,
.card.fact-card > summary::-webkit-details-marker { display: none; }
.block > summary::before, .card.fact-card > summary::before {
  content: ""; flex: 0 0 auto; width: 8px; height: 8px; margin-right: 2px;
  border-right: 2px solid var(--muted-foreground); border-bottom: 2px solid var(--muted-foreground);
  transform: rotate(-45deg); transition: transform .15s ease;
}
.block[open] > summary::before, .card.fact-card[open] > summary::before { transform: rotate(45deg); }
.block > summary:focus-visible, .card.fact-card > summary:focus-visible {
  outline: 3px solid var(--ring); outline-offset: 2px;
}
.block-body { padding: 4px 20px 20px; }
.block-body > p:first-child { margin-top: 0; color: var(--muted-foreground); }

/* An <h3> inside the <summary> so the card list appears in the heading outline. Heading content
   inside summary is valid and is the documented pattern. */
.fact-name { font-size: 15px; margin: 0; font-family: var(--font-sans); font-weight: 600; }

/* The fact-card editors. Monospace is the point of them, 15px was not: below 16px iOS Safari
   zooms the page on focus and does not zoom back out. Replaces an inline style on two textareas. */
textarea.mono-edit {
  min-height: 130px; font: 16px/1.6 var(--mono); field-sizing: content; max-height: 60vh;
}
.fact-preview {
  color: var(--muted-foreground); font-size: 13.5px; font-family: var(--font-sans); font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 120px; min-width: 0;
}
@media (max-width: 700px) { .fact-preview { display: none; } }

table.kv { width: 100%; border-collapse: collapse; font-size: 14px; }
table.kv td, table.kv th {
  padding: 8px 0; border-bottom: 1px solid var(--border); vertical-align: top;
  overflow-wrap: anywhere; text-align: left;
}
table.kv th { font-family: var(--font-sans); font-weight: 600; }
table.kv td:first-child, table.kv th:first-child { color: var(--muted-foreground); width: 34%; }
table.kv th:first-child { color: var(--foreground); }
table.kv tr:last-child td { border-bottom: none; }
/* A cell cannot shrink below its longest unbreakable token, and these hold things like
   http://192.168.1.20:11434 and INBOX.Sent Messages. At 320px the fixed 34% column forced the
   whole document to scroll sideways, which is a 1.4.10 Reflow failure on the proposal page the
   manual tells the owner to use on her phone. Stack the pairs instead. */
@media (max-width: 620px) {
  table.kv, table.kv tbody, table.kv tr, table.kv td, table.kv th {
    display: block; width: auto;
  }
  table.kv td, table.kv th { border-bottom: none; padding: 1px 0; }
  table.kv td:first-child, table.kv th:first-child { padding-top: 10px; font-size: 14px; }
  table.kv tr { border-bottom: 1px solid var(--border); padding-bottom: 9px; margin-bottom: 3px; }
  table.kv tr:last-child { border-bottom: none; }
  /* Stacked pairs label themselves, so a two-cell header row is just noise repeated once. */
  table.kv thead { display: none; }
}
code { font: 13px/1.4 var(--mono); overflow-wrap: anywhere; }

/* One row per availability period. Was a two-column .kv table, wrong tool here: the first
   column held a date range rather than a label, and the second crammed a pill, a note and a
   Remove button together with no breathing room. */
.period-list { display: flex; flex-direction: column; }
.period-row {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 12px 0; border-bottom: 1px solid var(--border);
}
.period-list .period-row:last-child { border-bottom: none; padding-bottom: 6px; }
.period-main { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.period-dates { font-family: var(--font-sans); font-weight: 600; font-size: 14px; }
.period-note { flex: 1 1 200px; min-width: 0; font-size: 14.5px; }
.period-remove { margin-left: auto; }
.period-remove button { padding: 4px 12px; font-size: 12.5px; }
@media (max-width: 560px) {
  .period-remove { margin-left: 0; flex-basis: 100%; }
}

/* --- visually hidden ------------------------------------------------------
   Text for assistive technology only. Used for "opens in a new tab" and for the headings that
   give the queue and the card list a real outline without changing how they look. */
.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* --- motion ---------------------------------------------------------------
   Nothing here is parallax and nothing flashes, so this is a courtesy rather than a conformance
   fix, but it costs two lines. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* --- rich-text editors -------------------------------------------------
   The Jodit editors: the signature in settings.html (frontend/src/components/RichTextEditor.tsx),
   and the reply and the fact cards (frontend/src/components/fields.ts). Jodit brings
   its own toolbar and dialogs (jodit's css is served as signature-editor.css); what is set here is
   only what makes it look like the rest of the app, and how the saved signature is previewed. */
.card.span-all, .grid-2 > .span-all { grid-column: 1 / -1; }

.signature-editor-box .jodit-container:not(.jodit_inline) {
  border: 1px solid var(--input); border-radius: var(--radius); background: var(--card); overflow: hidden;
}
.signature-editor-box .jodit-toolbar__box:not(:empty) {
  background: var(--card); border-bottom: 1px solid var(--border);
}
/* Jodit draws with its own colour variables; pointed at the app's tokens, its toolbar, popups and
   dialogs take the shadcn look and follow the dark theme with the rest of the page (issue #147).
   Set on Jodit's own elements, so it does not matter that its stylesheet loads after this one. */
.jodit, .jodit-container, .jodit-popup, .jodit-dialog, .jodit-toolbar__box {
  --jd-font-default: var(--font-sans);
  --jd-border-radius-default: var(--radius-sm);
  --jd-color-background-default: var(--card);
  --jd-color-panel: var(--card);
  --jd-color-white: var(--card);
  --jd-color-border: var(--input);
  --jd-color-separator: var(--border);
  --jd-color-default: var(--foreground);
  --jd-color-text: var(--foreground);
  --jd-color-dark: var(--foreground);
  --jd-color-icon: var(--foreground);
  --jd-color-text-icons: var(--muted-foreground);
  --jd-color-label: var(--muted-foreground);
  --jd-color-placeholder: var(--muted-foreground);
  --jd-color-background-button-hover: var(--hover);
  --jd-color-button-background-hover: var(--hover);
  --jd-color-background-gray-hover: var(--hover);
  --jd-color-background-active: var(--muted);
  --jd-color-background-gray: var(--muted);
  --jd-color-background-light-gray: var(--subtle);
  --jd-color-border-active: var(--ring);
  --jd-color-border-selected: var(--ring);
}
.jodit-toolbar-button__button { border-radius: var(--radius-sm); }
.jodit-popup, .jodit-dialog__panel { border: 1px solid var(--border); box-shadow: var(--shadow-lg); border-radius: var(--radius); }
.jodit-wysiwyg { color: var(--foreground); }
.signature-editor-box .jodit-workplace { font: 16px/1.5 var(--font-sans); }
.signature-editor-box .jodit-wysiwyg { padding: 14px; font: 16px/1.5 var(--font-sans); }
.signature-editor-box .jodit-wysiwyg p { margin: 0 0 8px; }
.signature-editor-box .jodit-container.jodit-container_focused,
.signature-editor-box .jodit-container:focus-within {
  outline: 2px solid var(--ring); outline-offset: -2px;
}
.signature-editor-box .jodit-placeholder { padding: 14px; }
/* Open questions in a draft or a fact card: the [bracketed text], coloured over the text by the
   CSS Custom Highlight API (frontend/src/components/gapHighlight.ts), so nothing is added to the reply
   itself. The brackets stay, so the colour is never the only way to tell. */
::highlight(mm-gap) { background-color: var(--gap-bg); color: var(--gap-ink); }

/* The link dialog offers a CSS class name. The sanitizer strips class attributes from a
   signature anyway, so the field would only promise something that does not happen. */
.jodit-ui-block:has(> .jodit-ui-block__className) { display: none; }
/* Jodit's dialogs and popups are appended to <body>, above the app's own layers. */
.jodit-popup, .jodit-dialog { z-index: 2000; }

/* The translations sit side by side now that the card is the full width; the one being edited
   takes a whole row. */
.signature-translations { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.signature-translations .signature-translation { margin-top: 0; }
.signature-translations .signature-translation.editing { grid-column: 1 / -1; }
@media (max-width: 760px) { .signature-translations { grid-template-columns: 1fr; } }

.signature-preview {
  border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; min-height: 120px;
  background: var(--card); font: 15px/1.5 var(--font-sans);
}
/* The signature and butler.li's note are shown as the guest's mail program shows them, which is
   on white whatever theme the interface is in, so a dark signature never looks right here and
   wrong in the guest's inbox. */
.signature-preview, .footer-preview {
  color-scheme: light; background: #ffffff; color: #222222;
}
.signature-preview th { color: #222222; background: #f4f4f5; }
.signature-preview p { margin: 0 0 8px; }
.signature-preview img { max-width: 100%; height: auto; }
.signature-preview table {
  border-collapse: collapse; margin: 8px 0;
}
.signature-preview td, .signature-preview th {
  border: 1px solid var(--input); padding: 6px 10px;
}
.signature-translation { margin-top: 12px; }
/* The files on a reply (issue #221): one line each, a long name wraps instead of pushing the
   remove button off a phone's screen. */
.file-list { list-style: none; margin: 6px 0 10px; padding: 0; }
.file-list li {
  display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border);
}
.file-list li:last-child { border-bottom: 0; }
.file-list a, .file-list span:not(.muted) { overflow-wrap: anywhere; min-width: 0; }
.file-list .muted { white-space: nowrap; font-size: 13px; }
.file-list .muted.wrap { white-space: normal; }
.file-list button { margin-left: auto; }
.file-list li.card-offer { display: block; padding: 4px 0 10px 24px; }
.file-list li.card-offer .field { margin: 0; max-width: 360px; }
.file-list li.card-offer .actions { margin-top: 6px; }
.file-list li.card-offer select { width: 100%; }
.file-list li.card-offer button { margin-left: 0; }
.doc-offer, .doc-others.fold { margin: 4px 0 14px; }
.doc-count { display: inline-flex; align-items: center; gap: 2px; margin-left: 6px; font-size: 13px; }
.attach-pick input[type=file] { max-width: 100%; min-width: 0; font: inherit; font-size: 16px; }
.attach-pick input[type=file]::file-selector-button {
  font: inherit; margin-right: 10px; padding: 6px 12px; border: 1px solid var(--input);
  border-radius: var(--radius); background: var(--background); color: var(--foreground); cursor: pointer;
}
/* butler.li's note is shown as it will look, but greyed: it is not part of what can be edited. */
.footer-preview { min-height: 0; opacity: .75; }
.footer-preview div { margin: 0 !important; }

/* --- components for the 1.0 pages -------------------------------------------
   shadcn's components that the pages of #142 are rebuilt from, phase by phase. Each works without
   JavaScript: tabs are links, a dialog is a <dialog> (or its own page), a dropdown is a
   <details>. /styleguide shows every one of them in both themes. */

svg.lucide { flex: none; vertical-align: -3px; }
.empty svg.lucide { display: block; margin: 0 auto 12px; color: var(--muted-foreground); }

/* Badge: .pill is the same thing under its older name. */
.badge {
  display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px;
  border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--card);
  color: var(--foreground); font-size: 12px; font-weight: 500; white-space: nowrap;
}
.badge.secondary { background: var(--secondary); border-color: transparent; }
.badge.solid { background: var(--primary); border-color: transparent; color: var(--primary-foreground); }
.badge.ok { background: var(--ok-bg); border-color: transparent; color: var(--ok); }
.badge.warn { background: var(--warn-bg); border-color: transparent; color: var(--warn); }
.badge.bad { background: var(--err-bg); border-color: transparent; color: var(--err); }
.badge.info { background: var(--info-bg); border-color: transparent; color: var(--info); }
.badge svg.lucide { width: 14px; height: 14px; vertical-align: 0; }

/* Tabs: a row of links under the page title; the current one is underlined in ink and carries
   aria-current, so it is not told by colour alone. On a phone the row scrolls inside itself. */
.tabs {
  display: flex; gap: 4px; margin: 0 0 20px; border-bottom: 1px solid var(--border);
  overflow-x: auto; scrollbar-width: none;
}
/* A tab's own explanation, below the tabs rather than under the title, so the tabs stay where they
   are whichever tab is open (Marcel, 2026-10-13). */
.tab-intro { margin: -4px 0 20px; max-width: 75ch; color: var(--muted-foreground); }
.tabs a {
  display: inline-flex; align-items: center; gap: 6px; height: 40px; padding: 0 10px;
  margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--muted-foreground);
  font-weight: 500; text-decoration: none; white-space: nowrap;
}
.tabs a:hover { color: var(--foreground); }
.tabs a[aria-current="page"] { color: var(--foreground); border-bottom-color: var(--foreground); }
.tabs .n {
  min-width: 20px; padding: 0 6px; border-radius: var(--radius-sm); background: var(--muted);
  color: var(--muted-foreground); font-size: 12px; text-align: center;
}
.tabs a[aria-current="page"] .n { background: var(--primary); color: var(--primary-foreground); }

/* Table: in a bordered box that scrolls sideways inside itself on a phone, never the page. */
.table-wrap {
  border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--card);
  box-shadow: var(--shadow); overflow-x: auto; margin-bottom: 16px;
}
.table-tools {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 12px;
  border-bottom: 1px solid var(--border);
}
table.table { width: 100%; border-collapse: collapse; font-size: 14px; }
table.table th {
  height: 40px; padding: 0 12px; text-align: left; font-size: 13px; font-weight: 500;
  color: var(--muted-foreground); background: var(--subtle); border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
table.table td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
table.table tr:last-child td { border-bottom: none; }
table.table tbody tr:hover { background: var(--hover); }
table.table .sub { display: block; font-size: 13px; color: var(--muted-foreground); }
table.table .end { text-align: right; white-space: nowrap; width: 1%; }

/* --- tables of rows to open: Entwürfe (issue #146), Wissen's cards (issue #147) --------------
   The row's title is its link, stretched over the whole row, so a click anywhere opens it and
   there is still exactly one link per row for a screen reader. */
.tabs .n.warn, .sb-nav .count.warn { background: var(--warn-bg); color: var(--warn); }
.tabs a[aria-current="page"] .n.warn { background: var(--warn); color: var(--background); }
.sb-nav .count + .count { margin-left: 4px; }
.chips-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin: 0 0 18px; }
.chips-row .spacer, .table-tools .spacer { margin-left: auto; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chips a {
  display: inline-flex; align-items: center; height: 28px; padding: 0 10px; border: 1px solid var(--border);
  border-radius: 999px; color: var(--muted-foreground); font-size: 13px; text-decoration: none;
}
.chips a:hover { color: var(--foreground); background: var(--hover); }
.chips a[aria-current="page"] { color: var(--primary-foreground); background: var(--primary); border-color: var(--primary); }
.table-search { position: relative; flex: 1 1 16rem; max-width: 22rem; }
.table-search .table-search-icon {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--muted-foreground);
  pointer-events: none;
}
.table-search input[type=search] { width: 100%; padding-left: 34px; margin: 0; }
.table-count { font-size: 13px; }
table.rows tbody tr { position: relative; }
/* Fixed column widths, so a long subject is cut short instead of squeezing the columns beside it. */
table.drafts { table-layout: fixed; }
table.drafts th:nth-child(1) { width: 24%; }
table.drafts th:nth-child(3) { width: 12rem; }
table.drafts th:nth-child(4) { width: 9.5rem; }
table.drafts th:nth-child(5) { width: 44px; }
table.rows td { vertical-align: top; }
table.rows td.end { vertical-align: middle; }
table.rows .trunc { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
table.rows a.row-link {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--foreground); font-weight: 500; text-decoration: none;
}
table.rows a.row-link::after { content: ""; position: absolute; inset: 0; }
table.rows .snip {
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
  margin-top: 2px;
}
table.rows .pill-row { display: flex; flex-wrap: wrap; gap: 4px 6px; margin-top: 6px; }
table.rows .pill-row:empty { display: none; }
table.rows .when { white-space: nowrap; color: var(--muted-foreground); font-size: 13px; }
table.rows .end svg { color: var(--muted-foreground); }
table.rows tr.is-open { background: var(--muted); }
table.rows td .badge { white-space: nowrap; }
/* On a phone a row is a small card: the sender, the subject and the preview stacked, status and
   time folded into the badges below (issue #55). */
@media (max-width: 760px) {
  table.rows thead, table.rows .hide-m { display: none; }
  table.rows, table.rows tbody, table.rows tr { display: block; }
  table.rows tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; padding: 12px; border-bottom: 1px solid var(--border); }
  table.rows tr:last-child { border-bottom: 0; }
  table.rows td { display: block; padding: 0; border: 0; max-width: none; width: auto; }
  table.rows td.who { grid-column: 1; }
  table.rows td.what { grid-column: 1; margin-top: 4px; }
  table.rows td.end { grid-column: 2; grid-row: 1 / span 2; align-self: center; padding-left: 8px; }
  table.rows .snip { -webkit-line-clamp: 2; }
  .table-search { max-width: none; }
}

/* Wissen's cards: the row's own ⋯ menu sits above the stretched link. */
.table-wrap.menus { overflow: visible; }
.table-wrap.menus table.table th:first-child { border-top-left-radius: var(--radius-lg); }
.table-wrap.menus table.table th:last-child { border-top-right-radius: var(--radius-lg); }
table.cards-table { table-layout: fixed; }
table.cards-table th:nth-child(2) { width: 15rem; }
table.cards-table th:nth-child(3) { width: 7rem; }
table.cards-table th:nth-child(4) { width: 8.5rem; }
table.cards-table th:nth-child(5) { width: 64px; }
table.rows .row-menu { position: relative; z-index: 2; }
table.rows .row-menu .dropdown-menu { min-width: 11rem; }
table.rows .row-menu .dropdown-menu form { margin: 0; }
table.rows .show-m { display: none; }
@media (max-width: 760px) {
  table.rows .show-m { display: flex; }
  table.rows td.end { grid-row: 1 / span 2; }
}
:target > td { background: var(--muted); }

/* A large dialog: the editor on the left, its settings on the right (Wissen's cards). Opened by
   ui.js as a modal; with no script it is shown in place, open, as part of the page. */
dialog.dialog.wide { width: min(1040px, calc(100% - 32px)); max-height: calc(100vh - 32px); overflow: auto; }
/* Shown in place, it is part of the page, so it needs no scrolling of its own; without one, a help
   panel in it is not cut off at its edge either (issue #161). */
dialog.dialog:not(:modal)[open] {
  position: relative; margin: 0 0 24px; width: 100%; z-index: 1; max-height: none; overflow: visible;
}
.dialog-head.with-close { display: flex; align-items: flex-start; gap: 12px; padding-bottom: 4px; }
.dialog-head.with-close > div { flex: 1 1 auto; min-width: 0; }
.dialog-cols { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 24px; }
.dialog-side { border-left: 1px solid var(--border); padding-left: 24px; }
.dialog-foot .spacer { margin-left: auto; }
@media (max-width: 760px) {
  .dialog-cols { grid-template-columns: minmax(0, 1fr); }
  .dialog-side { border-left: 0; padding-left: 0; }
}

/* Wissen's sources: one card each, with its icon beside the heading. */
.card.source { margin-bottom: 16px; }
.source-head { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 16px; }
.source-head h2 { font-size: 17px; margin: 4px 0 4px; }
.source-head p { margin: 0; color: var(--muted-foreground); max-width: 75ch; }
.source-head .task-ic { flex: 0 0 auto; }

/* The draft panel: a sheet from the right over the list, holding the draft's own page. */
dialog.panel {
  position: fixed; inset: 0 0 0 auto; width: min(880px, 100%); max-width: 100%; height: 100%;
  max-height: 100%; margin: 0; padding: 0; border: 0; border-left: 1px solid var(--border);
  background: var(--background); color: var(--foreground); box-shadow: var(--shadow-lg);
}
dialog.panel[open] { display: flex; flex-direction: column; }
dialog.panel::backdrop { background: rgba(0, 0, 0, .3); }
.panel-head {
  display: flex; align-items: center; gap: 8px; min-height: 52px; padding: 0 12px 0 20px;
  border-bottom: 1px solid var(--border); font-size: 13px;
}
.panel-head .spacer { margin-left: auto; }
dialog.panel iframe { flex: 1 1 auto; width: 100%; border: 0; background: var(--background); }
body.in-panel main { padding: 20px 24px 40px; max-width: none; }

/* Dialog: the browser's own <dialog>, so Esc, focus and the backdrop come for free. Without
   JavaScript a dialog's content is its own page. */
dialog.dialog {
  width: min(520px, calc(100% - 32px)); padding: 0; border: 1px solid var(--border);
  border-radius: var(--radius-lg); background: var(--background); color: var(--foreground);
  box-shadow: var(--shadow-lg);
}
dialog.dialog::backdrop { background: rgba(0, 0, 0, .4); }
.dialog-head { padding: 20px 20px 0; }
.dialog-head h2 { font-size: 17px; margin: 0; }
.dialog-head p { margin: 4px 0 0; color: var(--muted-foreground); }
.dialog-body { padding: 16px 20px; }
.dialog-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 0 20px 20px; flex-wrap: wrap; }
/* The same box drawn in place, for the style guide and for a page that is a dialog without
   JavaScript. */
.dialog-static {
  max-width: 520px; border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--background); box-shadow: var(--shadow-lg);
}

/* Sheet: a panel from the right edge (phase 4 opens a draft in one). Drawn in place here; the
   script that slides it in comes with the page that uses it. */
.sheet {
  display: flex; flex-direction: column; max-width: 720px; border: 1px solid var(--border);
  border-radius: var(--radius-lg); background: var(--background); box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.sheet-head { display: flex; gap: 12px; align-items: flex-start; padding: 16px 20px; border-bottom: 1px solid var(--border); }
.sheet-head h2 { font-size: 17px; margin: 0; }
.sheet-head .spacer { margin-left: auto; }
.sheet-body { padding: 20px; }
.sheet-foot {
  display: flex; gap: 8px; align-items: center; padding: 12px 20px; border-top: 1px solid var(--border);
  background: var(--subtle);
}
.sheet-foot .spacer { margin-left: auto; }

/* Dropdown menu: a <details>, so it opens with a click, Enter or Space and needs no script. */
details.dropdown { position: relative; display: inline-block; }
details.dropdown > summary { list-style: none; }
details.dropdown > summary::-webkit-details-marker { display: none; }
.dropdown-menu {
  position: absolute; z-index: 40; top: calc(100% + 6px); right: 0; min-width: 13rem; padding: 4px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.dropdown-menu .label { padding: 6px 8px; font-size: 13px; font-weight: 600; }
.dropdown-menu a, .dropdown-menu button {
  display: flex; width: 100%; align-items: center; gap: 8px; min-height: 32px; padding: 6px 8px;
  border: 0; border-radius: var(--radius-sm); background: none; box-shadow: none;
  color: var(--foreground); font-size: 14px; font-weight: 400; text-decoration: none;
  justify-content: flex-start; text-align: left;
}
.dropdown-menu a:hover, .dropdown-menu button:hover { background: var(--hover); }
.dropdown-menu .danger { color: var(--err); }
.dropdown-menu hr { border: 0; border-top: 1px solid var(--border); margin: 4px -4px; }

/* Keyboard keys and a plain separator. */
kbd {
  font: 500 11px var(--font-sans); padding: 1px 5px; border: 1px solid var(--border);
  border-radius: 4px; background: var(--muted); color: var(--muted-foreground);
}
hr.sep { border: 0; border-top: 1px solid var(--border); margin: 20px 0; }

/* The style guide's own layout: a heading per component and a row of examples. */
.sg-section { margin-bottom: 28px; }
.sg-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.sg-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: 10px; }
.sg-swatch { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; font-size: 12px; }
.sg-swatch > span { display: block; height: 44px; border-bottom: 1px solid var(--border); }
.sg-swatch > code { display: block; padding: 6px 8px; font-size: 12px; }

/* --- print ----------------------------------------------------------------
   pre.quoted caps at 340px and scrolls, so printing a held message to forward to the police, or a
   past exchange for the records, silently cut the body off at the fold and printed nothing after
   it. That is content loss, not a styling preference. */
@media print {
  body { background: #fff; color: #000; font-size: 11pt; }
  .sidebar, .topbar, .skip-link, .actions, .decide-actions, .decide-form, .statusline .build, .help { display: none; }
  .app { display: block; background: none; }
  main { max-width: none; padding: 0; }
  .card, .block, .card.fact-card {
    box-shadow: none; border: 1px solid #999; break-inside: avoid;
  }
  pre.quoted, .job-log, .pill-more > ul, .block-body {
    max-height: none !important; overflow: visible !important;
  }
  /* Print what is inside a collapsed disclosure rather than only its summary. */
  details > *:not(summary) { display: block !important; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; word-break: break-all; }
  h1, h2, h3 { break-after: avoid; }
}

/* The Google choice in the mailbox step (issue #94): three numbered steps for the app password,
   and the server fields folded away because nobody needs to change them. */
.google-steps { margin: 0 0 12px; padding-left: 22px; }
.google-steps li { margin: 6px 0; }
.server-settings { margin: 0 0 18px; }
.server-settings > summary { cursor: pointer; color: var(--muted-foreground); padding: 4px 0 12px; }

/* --- settings, your account, setup and sign-in (issue #148) ---------------
   Settings is one tab per subject, and each tab a column of cards, each a form with its own Save
   in the card's foot, so nothing on another card is saved with it by surprise. */
.settings-stack { max-width: 880px; }
.settings-stack > .muted { margin: 4px 0 0; font-size: 14px; }
.settings-tabs { margin-bottom: 20px; }
.card-desc { color: var(--muted-foreground); font-size: 14px; margin: -8px 0 16px; max-width: 75ch; }
.card > .card-foot:last-child { margin-top: 20px; }
.card .grid-2, .card .grid-3 { gap: 0 16px; }
.season-card { margin-top: 28px; }
.settings-stack .season-card { margin-top: 0; }

/* What is rarely changed, folded away inside its card: the mail servers and ports. */
details.fold { border: 1px solid var(--border); border-radius: var(--radius); margin: 6px 0 0; }
details.fold > summary {
  cursor: pointer; padding: 10px 14px; font-weight: 500; font-size: 14px; list-style: none;
  display: flex; align-items: center; gap: 8px;
}
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary::before {
  content: ""; width: 7px; height: 7px; border: solid var(--muted-foreground); border-width: 0 2px 2px 0;
  transform: rotate(-45deg); transition: transform .15s; margin-right: 2px;
}
details.fold[open] > summary::before { transform: rotate(45deg); }
details.fold[open] > summary { border-bottom: 1px solid var(--border); }
details.fold > :not(summary) { margin-left: 14px; margin-right: 14px; }
details.fold > :not(summary):first-of-type { margin-top: 14px; }
details.fold > :last-child { margin-bottom: 14px; }

/* Light, dark or the system's: three small pictures of the page, the radio button kept so a
   keyboard and a screen reader use it as one. */
.theme-choice { border: 0; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.theme-option {
  display: grid; gap: 8px; padding: 10px; border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; position: relative;
}
.theme-option input { position: absolute; top: 12px; right: 12px; margin: 0; }
.theme-option:has(input:checked) { border-color: var(--foreground); box-shadow: 0 0 0 1px var(--foreground); }
.theme-option:has(input:focus-visible) { outline: 2px solid var(--ring); outline-offset: 2px; }
.theme-name { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 500; }
.theme-swatch { display: block; height: 64px; border-radius: 6px; border: 1px solid var(--border); }
.theme-swatch.light { background: linear-gradient(#fff 0 0) 10px 10px / 46% 8px no-repeat,
  linear-gradient(#e4e4e7 0 0) 10px 26px / 70% 6px no-repeat, linear-gradient(#e4e4e7 0 0) 10px 38px / 56% 6px no-repeat, #f4f4f5; }
.theme-swatch.dark { background: linear-gradient(#3f3f46 0 0) 10px 10px / 46% 8px no-repeat,
  linear-gradient(#27272a 0 0) 10px 26px / 70% 6px no-repeat, linear-gradient(#27272a 0 0) 10px 38px / 56% 6px no-repeat, #09090b; }
.theme-swatch.system { background:
  linear-gradient(90deg, #f4f4f5 0 50%, #09090b 50% 100%); }
@media (max-width: 520px) { .theme-choice { grid-template-columns: minmax(0, 1fr); } .theme-swatch { height: 40px; } }

/* Your account: the initials in a disc beside the name. */
.account-head { display: flex; align-items: center; gap: 16px; }
.avatar {
  display: inline-grid; place-items: center; flex: none; width: 32px; height: 32px; border-radius: 999px;
  background: var(--muted); color: var(--foreground); font-weight: 600; font-size: 13px;
}
.avatar.lg { width: 56px; height: 56px; font-size: 19px; }

/* The setup wizard: the steps down the left of the page, the step itself beside them. */
main.with-steps { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 0 40px; align-items: start; }
.setup-steps { position: sticky; top: 24px; }
.setup-steps .steps { flex-direction: column; flex-wrap: nowrap; margin: 6px 0 0; row-gap: 0; position: relative; }
.setup-steps .steps li { padding: 7px 0; font-size: 14px; gap: 10px; position: relative; }
.setup-steps .steps li + li::before {
  content: ""; position: absolute; left: 11px; top: -9px; width: 1px; height: 18px; margin: 0; background: var(--border);
}
.setup-steps .steps li .dot { width: 23px; height: 23px; font-size: 12px; }
.setup-steps .steps li.done { color: var(--foreground); }
.steps li .dot svg { width: 12px; height: 12px; }
@media (max-width: 900px) {
  main.with-steps { display: block; }
  .setup-steps { position: static; }
  .setup-steps .steps { flex-direction: row; flex-wrap: wrap; gap: 6px; margin: 0 0 18px; }
  .setup-steps .steps li .dot { width: 20px; height: 20px; font-size: 11px; }
  .setup-steps .steps li { padding: 0; }
  .setup-steps .steps li + li::before { display: none; }
  .setup-steps .steps li:not(.on) .step-name { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

/* Sign-in, the link to choose a password and the second step: the name above one narrow card in
   the middle of a quiet page. */
body.bare:has(.login-wrap) { background: var(--subtle); }
body.bare:has(.login-wrap) .bare-head { display: flex; justify-content: center; padding-top: 9vh; }
body.bare:has(.login-wrap) .bare-head .logo { flex-direction: column; gap: 10px; }
body.bare:has(.login-wrap) .bare-head .logo-disc { width: 48px; height: 48px; }
body.bare:has(.login-wrap) .bare-head .logo-text { font-size: 20px; }
body.bare:has(.login-wrap) .login-wrap { padding-top: 22px; }
body.bare:has(.login-wrap) .statusline { justify-content: center; }
body.bare:has(.login-wrap) .statusline .build { margin-left: 0; }
.login-card { max-width: 25rem; padding: 28px; }
.login-card h1 { font-size: 22px; text-align: center; }
.login-card > p.muted:first-of-type { text-align: center; margin-bottom: 18px; }
.login-help { text-align: center; }

/* Wissen → Quellen → "Herausfinden, wonach wirklich gefragt wird": each group of enquiries.
   Its name leads; the counts and words sit quietly under it; each example is cut to two whole
   lines; the card's name and its button share one line (Marcel, 2026-10-13). */
.discover-run { margin: 0 0 16px; }
.cluster { margin-bottom: 12px; }
.cluster h3 { margin: 0 0 6px; }
.cluster-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 0 0 12px; font-size: 13px; }
.cluster-examples { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 8px; }
.cluster-examples li {
  /* No padding above or below: the cut is at the padding's edge, so padding there would
     show a sliver of a third line. */
  margin: 2px 0; padding: 0 12px; border-left: 2px solid var(--border); color: var(--muted-foreground);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}
.cluster-make { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px 12px; }
.cluster-make .field { flex: 0 1 280px; margin: 0; min-width: 0; }
.cluster-make button[type=submit] { margin-top: 0; }

/* Saison und Verfügbarkeit: the periods as a table like the cards, the "+" beside the heading
   (Marcel, 2026-10-13). */
.card-head.with-action { align-items: center; }
.card-head.with-action h2 { flex: 1; margin: 0; }
table.season-table td.period-dates { font-weight: 600; white-space: nowrap; }
table.season-table td.end { width: 56px; }
table.season-table td.end form { margin: 0; }

/* usage report: start -- How the interface was used (_usage_report.html, issue #202). Shared with
   the operator's page per hotel, which butler_li/fleet.py writes with these rules copied in from
   between the two markers, so keep to the tokens both pages define. */
.pu-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 16px; }
.pu-range { margin: 0; color: var(--muted-foreground); }
.pu-periods { display: inline-flex; gap: 2px; padding: 3px; border: 1px solid var(--border); border-radius: 8px; background: var(--muted); }
.pu-periods a { padding: 4px 12px; border-radius: 6px; color: var(--muted-foreground); text-decoration: none; font-size: 13px; font-weight: 500; }
.pu-periods a:hover { color: var(--foreground); }
.pu-periods a[aria-current] { background: var(--card); color: var(--foreground); box-shadow: var(--shadow); }
.pu-totals { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); }
.pu-totals li { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; box-shadow: var(--shadow); min-width: 0; }
.pu-totals .n { display: block; font-family: var(--font-display); font-size: 28px; font-weight: 600; line-height: 1.15; font-variant-numeric: tabular-nums; }
.pu-totals .n small { font-family: var(--font-sans); font-size: 14px; font-weight: 400; color: var(--muted-foreground); }
.pu-totals .l { color: var(--muted-foreground); font-size: 13px; }
.pu-panel { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 18px 20px 20px; box-shadow: var(--shadow); min-width: 0; margin: 0 0 16px; }
.pu-panel h2 { margin: 0 0 4px; font-size: 17px; }
.pu-hint { margin: 0 0 14px; color: var(--muted-foreground); font-size: 13px; }
.pu-empty { margin: 0 0 16px; color: var(--muted-foreground); }
.pu-grid { display: grid; gap: 0 16px; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); }
.pu-chart, .pu-axis { display: grid; grid-template-columns: repeat(var(--pu-days, 30), minmax(0, 1fr)); gap: 3px; }
.pu-chart { align-items: end; height: 150px; border-bottom: 1px solid var(--border); }
.pu-col { display: flex; flex-direction: column-reverse; height: 100%; }
.pu-col.we { background: var(--subtle); }
.pu-col span { display: block; width: 100%; }
.pu-col .u { background: var(--primary); border-radius: 0 0 2px 2px; }
.pu-col .a { background: var(--ring); border-radius: 3px 3px 0 0; }
.pu-axis { margin-top: 4px; font-size: 11px; color: var(--muted-foreground); }
.pu-axis span { white-space: nowrap; overflow: visible; }
.pu-legend { margin: 10px 0 0; font-size: 12px; color: var(--muted-foreground); display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.pu-legend .sw { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.pu-legend .sw.u, .pu-split .u { background: var(--primary); }
.pu-legend .sw.a, .pu-split .a { background: var(--ring); }
.pu-legend .sw.a { margin-left: 10px; }
.pu-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.pu-table th { text-align: left; font-weight: 500; color: var(--muted-foreground); font-size: 12px; padding: 0 8px 6px 0; border-bottom: 1px solid var(--border); }
.pu-table td { padding: 7px 8px 7px 0; border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums; }
.pu-table tr:last-child td { border-bottom: 0; }
.pu-table .num { text-align: right; white-space: nowrap; }
.pu-table .share { width: 30%; }
.pu-track { display: block; height: 6px; background: var(--muted); border-radius: 999px; overflow: hidden; }
.pu-track span { display: block; height: 100%; background: var(--primary); border-radius: 999px; }
.pu-split { display: flex; height: 12px; border-radius: 999px; overflow: hidden; background: var(--muted); }
.pu-privacy { margin: 4px 0 0; color: var(--muted-foreground); font-size: 13px; display: flex; gap: 8px; align-items: flex-start; }
.pu-privacy svg { flex: none; margin-top: 2px; }
.pu-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* usage report: end */
