/*
 * Dev Ieffe styling — see STYLE.md.
 *
 * The semantic layer (--bg, --text, …) maps that palette onto the app's
 * surfaces, so the components keep their class names and only the bindings
 * change.
 *
 * STYLE.md records three things its source does not have, each decided here:
 *
 *  - Hover and focus colours. The site declares none, so the -600 values are
 *    darkened from the base rather than taken from the source.
 *  - A status palette. The site has no red or green at all; the two below are
 *    chosen to sit apart from both the brown and the blue.
 *  - A dark theme. The site's four section themes are a layout device on one
 *    light page, not a light/dark pair. The dark variant follows its "Dark"
 *    section: paper text on an ink ground, with white as the accent, because
 *    the blue is invisible against it.
 *
 * Of the four section themes the app adopts the Accent one — brown headings,
 * blue filled buttons — since the Light theme's white-on-white buttons give a
 * primary action too little weight in an interface people work in.
 *
 * Two further departures, both kept from the previous stylesheet:
 *  - the sidebar collapses at 720px, not at the guide's 1280px: a chat sidebar
 *    folding away on a 1200px desktop would be a regression;
 *  - the mobile sidebar drawer keeps a shadow although the guide has no
 *    elevation, because it floats over the conversation and needs separating
 *    from it.
 */

@font-face {
  font-family: 'Montserrat';
  src: url('/fonts/montserrat-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Lato';
  src: url('/fonts/lato-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Lato';
  src: url('/fonts/lato-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ---- palette (STYLE.md §1) ---- */
  /*
   * Forest Green and Brown. All five are dark earthy tones — the palette has
   * no light colour at all, so the page ground and the tints below are ours.
   * That is marked where it happens.
   */
  --sage: #5f725d;           /* the lightest of the five: the sidebar ground */
  --forest: #2e4b36;         /* links and focus */
  --charcoal: #353a31;       /* the near-black; dark-mode surfaces */
  --brown: #4f473b;          /* buttons and headings */
  --ink: #312e28;            /* body text, and the dark-mode ground */

  --forest-600: #243b2a;     /* darkened for hover; the palette declares none */
  --forest-900: #1b2c1f;
  --brown-600: #3e382e;

  /*
   * Not in the palette, which stops at five dark tones: a page needs something
   * to put them on. The tints are mixed from the sage so the greys stay in the
   * same family rather than reading as a second, colder palette.
   */
  --white: #ffffff;
  --paper: #f7f6f3;          /* warm off-white: text on dark grounds */
  --gray-50: #f2f4f1;
  --gray-200: #e3e6e1;
  --gray-400: #c8cec5;
  --gray-700: #6b6459;       /* muted text: 5.85:1 on white */

  /*
   * Also ours: the palette carries no status colours. The red reads as neither
   * the brown nor the green and clears AA on white. Success takes the brand
   * green rather than inventing a second one — it is only ever a 4px border,
   * never text, so there is nothing to confuse with a link.
   */
  --red: #b3261e;
  --red-600: #8c1d17;
  /* Low credit is a warning, not yet an error, so it is not the red. 5.93:1 on
     white, and it reads as neither the brown nor the green. */
  --amber: #8a5a00;
  --green: var(--forest);

  /* ---- semantic surfaces ---- */
  --bg: var(--white);
  --bg-subtle: var(--gray-50);
  --bg-raised: var(--white);
  --border: var(--gray-200);
  --border-strong: var(--gray-400);
  --text: var(--ink);                /* 13.53:1 on the page */
  --text-muted: var(--gray-700);
  --heading: var(--brown);           /* 9.14:1 */
  /*
   * Buttons take the brown, links and focus the green, so the two roles stay
   * distinguishable. White on this brown is 9.14:1.
   */
  --accent: var(--brown);
  --accent-hover: var(--brown-600);
  --accent-text: var(--white);
  --bubble-user: var(--gray-50);
  --danger: var(--red);
  --danger-hover: var(--red-600);
  --danger-text: var(--red-600);
  --warning-text: var(--amber);
  --focus: var(--forest);
  --link: var(--forest);             /* 9.65:1 */

  /* Kept for the few rules that still name them. */
  --primary: var(--forest);
  --primary-600: var(--forest-600);
  --primary-900: var(--forest-900);
  --secondary: var(--brown);
  --secondary-600: var(--brown-600);
  --gray: var(--gray-200);
  --gray-600: var(--gray-400);

  /* ---- geometry (STYLE.md §3) ---- */
  --radius: 0;
  --radius-card: 0;
  --radius-md: 0;
  /* Only the mobile drawer uses this; the guide has no elevation. */
  --shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 64px;

  /* ---- type (STYLE.md §2) ---- */
  --font-heading: Montserrat, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font: Lato, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  --transition: 0.15s cubic-bezier(0.4, 0, 0.2, 1);
  --sidebar-width: 280px;
  /* The lightest of the five, which is what the sidebar wants: a ground of its
     own, lighter than the forest green but still dark enough to carry white.
     White on it is 5.18:1 (AA) — do not lighten it further, that figure is what
     keeps the bold white links here legible. */
  --sidebar-bg: var(--sage);
  --sidebar-line: #ffffff47;
}

/*
 * Dark mode, built from the same five. The palette is already dark, so this is
 * not an inversion: the ink becomes the ground, the warm off-white becomes the
 * text, and the two accents have to come up to meet it.
 */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: var(--ink);
    --bg-subtle: var(--charcoal);
    --bg-raised: var(--charcoal);
    --border: #45493f;
    --border-strong: #5a5f53;
    --text: var(--paper);            /* 12.52:1 on the ground */
    --text-muted: #a8a196;           /* 5.29:1 */
    --heading: var(--paper);
    /*
     * The brown cannot be the button here: #4f473b against this ground is
     * 1.48:1, which is no button at all. It lightens and takes *dark* text,
     * because white on a brown light enough to see would be about 3:1 — the
     * light-fill, dark-text pairing a dark section forces.
     *
     * Because the label is the ground colour, one number covers both jobs:
     * 4.95:1 is the label against the fill *and* the fill against the page.
     * It has to clear 4.5 — buttons here are 14px bold, which is not "large
     * text", so 3:1 would not do.
     */
    --accent: #a89b84;
    --accent-hover: #b5a892;
    --accent-text: var(--ink);       /* 4.95:1 on the accent */
    --bubble-user: #3a3f36;
    --danger-text: #f08d86;
    --warning-text: #e0a955;         /* 6.43:1 on the ground */
    /* The forest green disappears against the ink; white does not. */
    --focus: var(--paper);
    --link: var(--paper);
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  /* 17 / 19 / 20px — the source sets body copy larger than the usual 16px. */
  font-size: 1.0625rem;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

@media (min-width: 480px) {
  body {
    font-size: 1.1875rem;
  }
}

@media (min-width: 768px) {
  body {
    font-size: 1.25rem;
  }
}

body.centered {
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: var(--bg-subtle);
}

/* ---------- headings (STYLE.md §2) ---------- */

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--heading);
  margin: 0 0 1rem;
}

/*
 * The scale steps at 480 and 768, as the source does. Note how far h1 sits
 * above h2 — below h1 a heading is a label, not a smaller title.
 */
h1 {
  font-size: 2.0625rem; /* 33px */
  line-height: 1.2;
}

h2 {
  font-size: 1.4375rem; /* 23px */
  line-height: 1.3;
}

h3 {
  font-size: 1.1875rem; /* 19px */
  line-height: 1.35;
}

@media (min-width: 480px) {
  h1 {
    font-size: 2.5rem; /* 40px */
  }

  h2 {
    font-size: 1.5rem; /* 24px */
  }
}

@media (min-width: 768px) {
  h1 {
    font-size: 2.8125rem; /* 45px */
  }
}

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

.small {
  font-size: 0.875rem;
  line-height: 1.25rem;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

a {
  color: var(--link);
}

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

input[type='email'],
input[type='text'],
input[type='password'],
input[type='number'],
select,
textarea {
  width: 100%;
  font-family: var(--font);
  font-size: 0.9375rem;
  line-height: 1.375rem;
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.4375rem 0.625rem;
  transition: border-color var(--transition);
}

input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) {
  border-color: var(--accent);
}

/* §6: outline, 2px, offset 2px, primary — never removed, never unstyled. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* ---------- buttons (STYLE.md §3) ---------- */

/*
 * The .btn base, applied to every <button> directly: square-cornered,
 * uppercase, bold, always a 1px border so solid and outline share one box.
 */
button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  column-gap: 0.5rem;
  white-space: nowrap;
  font-family: var(--font);
  /* Body copy is 20px here; controls set their own size so a form does not
     inherit display-sized text. */
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 0.5rem 0.875rem;
  background: var(--accent);
  color: var(--accent-text);
  transition: all var(--transition);
}

button:hover:not(:disabled),
button:focus:not(:disabled) {
  background: var(--accent-hover);
  color: var(--accent-text);
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/*
 * Secondary CTA. The primary now takes the brown, so this takes the blue —
 * the two accents split between the two roles instead of sharing one.
 */
button.secondary {
  background: var(--primary);
  color: var(--paper);
  border-color: var(--primary);
}

button.secondary:hover:not(:disabled),
button.secondary:focus:not(:disabled) {
  background: var(--primary-600);
  color: var(--paper);
  border-color: var(--primary-600);
}

/* btn-red, for the destructive solid buttons. */
button.danger {
  background: var(--danger);
  border-color: var(--danger);
  color: #ffffff;
}

button.danger:hover:not(:disabled),
button.danger:focus:not(:disabled) {
  background: var(--danger-hover);
  border-color: var(--danger-hover);
  color: #ffffff;
}

/*
 * Text links, whether they are <a> or a <button> inside a form.
 *
 * No underline: the weight and the colour carry the affordance instead. That
 * only works because these links stand on their own — in a row of actions, a
 * breadcrumb, a table cell — where there is no surrounding prose to blend
 * into. A link *inside* a sentence keeps the browser's underline (see the
 * bare `a` rule), because bold blue alone is not enough to pick it out of a
 * paragraph.
 */
button.link,
a.link {
  display: inline;
  background: none;
  border: none;
  padding: 0;
  color: var(--link);
  text-decoration: none;
  text-transform: none;
  font-weight: 700;
  font-size: inherit;
}

/* With no underline to drop, hover has to be the colour shift on its own. */
button.link:hover,
button.link:focus,
a.link:hover,
a.link:focus-visible {
  background: none;
  color: var(--accent-hover);
  text-decoration: none;
}

button.link.danger,
a.link.danger {
  background: none;
  border: none;
  color: var(--danger-text);
}

button.link.danger:hover,
button.link.danger:focus {
  background: none;
  color: var(--danger-text);
  text-decoration: none;
}

/* btn-tertiary / btn-gray, at btn-sm size. */
.icon-button {
  background: var(--gray);
  color: var(--primary);
  border: 1px solid var(--gray);
  padding: 0.25rem 0.5rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
}

.icon-button:hover,
.icon-button:focus {
  background: var(--gray-600);
  border-color: var(--gray-600);
  color: var(--primary);
}

.stack {
  display: grid;
  gap: var(--space-3);
}

/* A label, its control and an optional hint, stacked as one unit. */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.field > label {
  font-weight: 600;
  color: var(--heading);
}

.field .muted {
  margin: 0;
}

.row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.row--form {
  margin-bottom: var(--space-5);
}

.row--form input[type='email'] {
  flex: 1 1 220px;
  width: auto;
}

.checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text);
  font-size: 1rem;
}

.checkbox input {
  width: auto;
  accent-color: var(--accent);
}

/* ---------- cards & notices (STYLE.md §5) ---------- */

.card {
  width: 100%;
  max-width: 420px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: var(--space-6);
}

.card h1 {
  margin-bottom: var(--space-4);
}

.notice {
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
  font-size: 1rem;
  border-left: 4px solid var(--green);
  background: var(--bg-subtle);
  color: var(--text);
}

.notice--error {
  border-left-color: var(--red);
  color: var(--danger-text);
}

/*
 * A confirmation has done its job once it has been read, so it clears itself
 * after two seconds and collapses rather than leaving a gap behind. Errors are
 * deliberately left alone: those still need acting on.
 */
.notice--ok {
  overflow: hidden;
  animation: notice-dismiss 400ms ease-in 2s forwards;
}

@keyframes notice-dismiss {
  0% {
    opacity: 1;
    max-height: 8rem;
  }
  60% {
    opacity: 0;
    max-height: 8rem;
  }
  100% {
    opacity: 0;
    max-height: 0;
    margin-bottom: 0;
    padding-top: 0;
    padding-bottom: 0;
    border-left-width: 0;
    visibility: hidden;
  }
}

/* Still clear it, just without the fade. */
@media (prefers-reduced-motion: reduce) {
  .notice--ok {
    animation-duration: 1ms;
  }
}

/* ---------- page shell ---------- */

.page {
  max-width: 1024px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4);
}

@media (min-width: 1280px) {
  .page {
    max-width: 1280px;
    padding: var(--space-7) var(--space-6);
  }
}

.page__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--border-strong);
}

/* The trail of links beside a page heading: navigation, not body copy. */
.page__head .row {
  font-size: 0.875rem;
  line-height: 1.25rem;
}

/*
 * A table is a dense list, not body copy: it matches the links inside it
 * (.link.small) and the trail beside a page heading, so a chatbot's name is
 * the same size as the Open and Delete beside it.
 */
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
  line-height: 1.25rem;
}

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

.table th {
  color: var(--primary);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.875rem;
  line-height: 1.25rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom-color: var(--border-strong);
}

@media (prefers-color-scheme: dark) {
  .table th {
    color: var(--text);
  }
}

/* A cell may hold several actions (a link plus a delete form); keep them on one
   line instead of letting the block-level form drop below. Laid out with inline
   children rather than `display: flex`, which takes the cell out of the table
   box and leaves its bottom border drawn only under the flex area. */
.table td.actions {
  text-align: right;
  white-space: nowrap;
}

.table td.actions form {
  display: inline;
}

.table td.actions > * + * {
  margin-left: 0.75rem;
}

.table code,
code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  color: var(--primary);
  background: var(--gray);
  padding: 1px 5px;
  border-radius: var(--radius-md);
}

@media (prefers-color-scheme: dark) {
  .table code,
  code {
    color: var(--text);
    background: var(--primary);
  }
}

/* ---------- knowledge base ---------- */

.panel {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: var(--space-5) var(--space-6);
  margin-bottom: var(--space-6);
  background: var(--bg-raised);
}

/* heading5 */
.panel h2 {
  font-size: 1.125rem;
  line-height: 1.75rem;
  margin: 0 0 var(--space-2);
}

.panel .table {
  margin-top: var(--space-4);
}

.panel .table + .row--form,
.panel .table + form {
  margin-top: var(--space-5);
}

.row--between {
  justify-content: space-between;
  align-items: center;
}

.row--form input[type='text'] {
  flex: 1 1 220px;
  width: auto;
}

input[type='file'] {
  font: inherit;
  color: var(--text-muted);
  font-size: 0.875rem;
}

.editor {
  width: 100%;
  min-height: 60vh;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  line-height: 1.6;
  resize: vertical;
  tab-size: 2;
}

/* ---------- chat layout ---------- */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-width) 1fr;
  height: 100dvh;
}

.sidebar {
  display: flex;
  flex-direction: column;
  background: var(--sidebar-bg);
  color: #ffffff;
  min-height: 0;
  padding-bottom: env(safe-area-inset-bottom);
}

.sidebar a,
.sidebar .muted,
.sidebar button.link {
  color: #ffffff;
}

/*
 * Bold white, no underline — the weight is what marks a link on this ground.
 * Every interactive state stays white, :visited included: the browser would
 * otherwise recolour a visited link to something with no contrast here, and
 * the brown that used to serve as the hover colour sits at 1.44:1 against the
 * lighter blue. The muted foot text is deliberately left unbolded.
 */
.sidebar a,
.sidebar button.link {
  font-weight: 700;
  text-decoration: none;
}

.sidebar a:visited,
.sidebar a.link:hover,
.sidebar a.link:focus,
.sidebar a.link:active,
.sidebar button.link:hover,
.sidebar button.link:focus,
.sidebar button.link:active {
  color: #ffffff;
}

/* With colour ruled out, hover is the one place an underline earns its keep. */
.sidebar a.link:hover,
.sidebar button.link:hover {
  text-decoration: underline;
}

.sidebar__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-4);
  border-bottom: 1px solid var(--sidebar-line);
}

.sidebar__brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

/*
 * The Nyenrode logo is gone; the product name stands in for it, set in the
 * heading face. Nothing here is an image, so it costs no request and needs no
 * light/dark variant.
 */
.wordmark {
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--heading);
  margin: 0 0 var(--space-4);
}

.wordmark--small {
  font-size: 1rem;
  line-height: 1.4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--space-2);
}

.page__brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.page__brand h1 {
  margin: 0;
}

/* The chatbot's name, inverted on the sidebar's ground. */
.brand__assistant {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.1875rem;
  color: #ffffff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * The one CTA in the chrome: start a new conversation. It follows STYLE.md's
 * dark section rather than the page accent — the brown and the sidebar blue
 * sit at 1.23:1, so a brown button would disappear into its own ground.
 */
.sidebar .icon-button {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--primary);
  flex: none;
}

.sidebar .icon-button:hover,
.sidebar .icon-button:focus {
  background: #e8e6e0;
  border-color: #e8e6e0;
  color: var(--primary-900);
}

.sidebar :focus-visible {
  outline-color: #ffffff;
}

.conversations {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2);
  display: grid;
  gap: 2px;
  align-content: start;
}

.conversation {
  align-items: flex-start;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  border-left: 3px solid transparent;
  padding: 0 var(--space-1) 0 var(--space-3);
}

/*
 * No hover fill on a row. The only row that should stand out is the one you
 * are in; a second highlight that follows the pointer made the list restless.
 * The row still reacts to the pointer — it reveals its delete button.
 */
.conversation[aria-current='true'] {
  background: var(--primary);
  border-left-color: #ffffff;
}

.conversation__open {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  color: #ffffff;
  text-align: left;
  text-transform: none;
  padding: var(--space-2) 0;
  font-weight: 400;
  font-size: 0.875rem;
  line-height: 1.35;
  /*
   * Two lines, then an ellipsis. Titles are already cut to 60 characters when
   * they are derived (deriveTitle in routes/chat.ts), which is about what two
   * lines hold at this width — so the clamp is a safety net, not the limit.
   */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
  white-space: normal;
}

/*
 * Prefixed with .sidebar for the specificity, not for the scope:
 * `button:hover:not(:disabled)` is (0,2,1) and would otherwise outrank a plain
 * `.conversation__open:hover` at (0,2,0) and paint the row with the brown
 * accent — a conversation title is not a button being offered, it is a place
 * you are going.
 */
.sidebar .conversation__open:hover,
.sidebar .conversation__open:focus {
  background: none;
  color: #ffffff;
}

.conversation__delete {
  background: none;
  border: none;
  color: #ffffff;
  padding: 2px 6px;
  opacity: 0;
}

.sidebar .conversation__delete:hover,
.sidebar .conversation__delete:focus {
  background: var(--forest-600);
  color: #ffffff;
}

.conversation:hover .conversation__delete,
.conversation__delete:focus-visible {
  opacity: 1;
}

.sidebar__foot {
  border-top: 1px solid var(--sidebar-line);
  padding: var(--space-4);
  display: grid;
  gap: var(--space-2);
  font-size: 0.875rem;
}

.sidebar__foot .muted {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar__foot .row {
  gap: var(--space-3);
}

.chat {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg);
}

.chat__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--bg-raised);
  box-shadow: var(--shadow);
  z-index: 1;
}

/*
 * The conversation title is already the highlighted entry in the sidebar, so
 * showing it again above the thread is duplication. It stays in the markup as
 * the page's h1 — screen readers and the tab title still want it — but is not
 * drawn.
 */
.chat__head h1 {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/*
 * With the heading hidden the bar holds only the mobile sidebar toggle, so on a
 * wide screen there is nothing left to draw.
 */
.chat__head {
  padding: 0;
  background: none;
  box-shadow: none;
}

.messages {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-6) var(--space-4);
  display: grid;
  gap: var(--space-5);
  align-content: start;
}

/* .prose: 65ch of comfortable reading width. */
/*
 * Set apart from body copy, like the controls are: STYLE.md's 20px is a
 * marketing-page size, and a long answer reads better at the size the admin
 * pages use.
 */
.message {
  max-width: 65ch;
  width: 100%;
  margin: 0 auto;
  display: grid;
  gap: var(--space-2);
  font-size: 0.9375rem;
  line-height: 1.75;
}

/* heading6 */
.message__role {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.875rem;
  line-height: 1.25rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--primary);
}

@media (prefers-color-scheme: dark) {
  .message__role {
    color: var(--secondary);
  }
}

.message__body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/*
 * A rendered answer: real blocks, so the newlines between the tags must not be
 * shown. Only the assistant's own messages get this — what a user typed is
 * still shown exactly as typed, which is what pre-wrap above is for.
 */
.message__body--rich {
  white-space: normal;
}

/* Tight, because these are the paragraphs of one answer, not of a page. */
.message__body--rich > :first-child { margin-top: 0; }
.message__body--rich > :last-child { margin-bottom: 0; }

.message__body--rich p {
  margin: 0 0 var(--space-3);
}

.message__body--rich h3,
.message__body--rich h4,
.message__body--rich h5,
.message__body--rich h6 {
  font-family: var(--font-heading);
  color: var(--heading);
  line-height: 1.3;
  margin: var(--space-5) 0 var(--space-2);
}

/*
 * The guide's jump from h1 to h2 is enormous, which is right for a page and
 * wrong inside an answer: these are labels within a few paragraphs of text, so
 * they step gently and lean on weight instead.
 */
.message__body--rich h3 { font-size: 1.125rem; }
.message__body--rich h4 { font-size: 1.0625rem; }
.message__body--rich h5,
.message__body--rich h6 { font-size: 1rem; }

.message__body--rich ul,
.message__body--rich ol {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
}

.message__body--rich li {
  margin-bottom: var(--space-1);
}

/* A nested list belongs to its parent item, not to the space after it. */
.message__body--rich li > ul,
.message__body--rich li > ol {
  margin: var(--space-1) 0 0;
}

.message__body--rich code {
  background: var(--bg-subtle);
  padding: 0.1em 0.3em;
  border: 1px solid var(--border);
}

.message__body--rich pre {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  overflow-x: auto;
}

/* Inside a block the padding and border would be drawn twice. */
.message__body--rich pre code {
  background: none;
  border: none;
  padding: 0;
  font-size: 0.875rem;
  white-space: pre;
}

.message__body--rich blockquote {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-3);
  border-left: 3px solid var(--border-strong);
  color: var(--text-muted);
}

.message__body--rich hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: var(--space-4) 0;
}

.message__body--rich .md-table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 var(--space-3);
  font-size: 0.875rem;
  /* A wide table scrolls rather than stretching the 65ch column. */
  display: block;
  overflow-x: auto;
}

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

.message__body--rich .md-table th {
  background: var(--bg-subtle);
  font-family: var(--font-heading);
  font-weight: 700;
}

.message__body--rich a {
  text-decoration: underline;
}

.message--user .message__body {
  background: var(--bubble-user);
  border-radius: var(--radius-card);
  padding: var(--space-4);
}

.message--error .message__body {
  color: var(--danger-text);
  border-left: 4px solid var(--red);
  padding-left: var(--space-3);
}

.message code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.empty-state {
  white-space: pre-line;
  max-width: 65ch;
  margin: auto;
  text-align: center;
  font-size: 0.9375rem;
  color: var(--text-muted);
}

.typing::after {
  content: '▍';
  animation: blink 1s steps(2, start) infinite;
}

@keyframes blink {
  to {
    visibility: hidden;
  }
}

@media (prefers-reduced-motion: reduce) {
  .typing::after {
    animation: none;
  }
}

.composer {
  display: flex;
  gap: var(--space-3);
  align-items: flex-end;
  width: 100%;
  border-top: 1px solid var(--border-strong);
  padding-top: var(--space-4);
  /* The setup strip sits below and carries the bottom inset instead. */
  padding-bottom: var(--space-4);
  /*
   * Centres the field on the message column while the rule still spans the
   * whole pane — a rule that stopped at the form's width read as a stray line.
   *
   * `ch` is relative to the font size of the element it is written on, so this
   * only lines up with `.message`'s own 65ch while the two agree on that size.
   * They did not for a while: the messages were set to 0.9375rem and this was
   * still measuring against the 20px body copy, which made the column ~490px
   * and the field ~780px. Change one of these two sizes and change the other.
   */
  font-size: 0.9375rem;
  padding-inline: max(var(--space-4), (100% - (65ch + 8rem)) / 2);
}

.composer textarea {
  resize: none;
  max-height: 200px;
}

.only-mobile {
  display: none;
}

@media (max-width: 720px) {
  .app {
    grid-template-columns: 1fr;
  }

  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(80vw, var(--sidebar-width));
    z-index: 10;
    box-shadow: var(--shadow);
    transform: translateX(-100%);
    transition: transform var(--transition);
  }

  .sidebar.is-open {
    transform: translateX(0);
  }

  .only-mobile {
    display: inline-flex;
  }

  .chat__head {
    padding: var(--space-4);
    background: var(--bg-raised);
    box-shadow: var(--shadow);
  }
}

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

/* ---------- admin settings form ---------- */

.settings {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 46rem;
}

.settings hr {
  border: 0;
  border-top: 1px solid var(--border-strong);
  margin: var(--space-2) 0;
  width: 100%;
}

.settings textarea {
  resize: vertical;
  font-family: var(--font-mono);
  font-size: 0.875rem;
}

.settings .muted {
  margin: 0;
}

.settings > button {
  align-self: flex-start;
  margin-top: var(--space-2);
}

/* ---------- thinking and sources ---------- */

.thinking {
  margin-bottom: var(--space-3);
  border-left: 4px solid var(--gray-600);
  background: var(--bg-subtle);
  border-radius: var(--radius-md);
  font-size: 0.875rem;
}

.thinking summary {
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  color: var(--text-muted);
  font-weight: 600;
  user-select: none;
}

.thinking__body {
  padding: 0 var(--space-3) var(--space-3);
  color: var(--text-muted);
  white-space: pre-wrap;
}

.sources {
  margin: var(--space-4) 0 0;
  padding-left: 1.1rem;
  font-size: 0.875rem;
  color: var(--text-muted);
}

.sources li {
  margin-top: 0.2rem;
}

.sources a {
  color: inherit;
}

/* ---------- OpenRouter balance ---------- */

.balance {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  flex-wrap: wrap;
  padding: var(--space-4);
  border-left: 4px solid var(--gray-600);
  border-radius: var(--radius-md);
  background: var(--bg-subtle);
}

.balance--ok { border-left-color: var(--green); }
.balance--low { border-left-color: var(--secondary); }
.balance--empty { border-left-color: var(--red); }

.balance__figure {
  display: flex;
  flex-direction: column;
}

/* heading2 */
.balance__amount {
  font-family: var(--font-heading);
  font-size: 1.875rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--heading);
}

.balance--empty .balance__amount { color: var(--danger-text); }
.balance--low .balance__amount { color: var(--warning-text); }

.balance__detail {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0.15rem 0.75rem;
  margin: 0;
  font-size: 0.875rem;
  color: var(--text-muted);
}

.balance__detail dt { grid-column: 1; }
.balance__detail dd { grid-column: 2; margin: 0; }

.balance__warning {
  margin: var(--space-3) 0 0;
  font-size: 1rem;
  font-weight: 700;
  color: var(--danger-text);
}

.balance--low .balance__warning { color: var(--warning-text); }

/* ---------- assistant picker ---------- */

/* heading2 promoted to heading1 from xl up, per §2 — not an ad-hoc size. */
.page--picker h1 {
  font-size: 1.875rem;
  line-height: 1.25;
}


.picker {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: var(--space-5);
}

/*
 * The card pattern from §5: white surface, rounded-lg, shadow-md, and the
 * whole card is one target — hovering it fills the CTA, as `group-hover` does
 * on the site.
 */
.picker__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  height: 100%;
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-top: 4px solid var(--secondary);
  border-radius: var(--radius-card);
  background: var(--bg-raised);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  transition: all var(--transition);
}

.picker__card:hover,
.picker__card:focus-visible {
  background: var(--primary-600);
  border-top-color: var(--secondary-600);
}

.picker__card:hover .picker__name,
.picker__card:focus-visible .picker__name,
.picker__card:hover .picker__description,
.picker__card:focus-visible .picker__description {
  color: #ffffff;
}

/* heading4 */
.picker__name {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--heading);
  transition: color var(--transition);
}

.picker__description {
  font-size: 1rem;
  color: var(--text-muted);
  line-height: 1.5rem;
  /* line-clamp-3, as the site's cards do. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: color var(--transition);
}

@media (prefers-reduced-motion: reduce) {
  .picker__card,
  .picker__name,
  .picker__description {
    transition: none;
  }
}

/* ---------- the setup strip under the composer ---------- */

/*
 * What the chatbot is set to, in a closed <details>. Native, so it needs no
 * JavaScript and keeps its keyboard behaviour for free. It is deliberately the
 * quietest thing on the screen: it answers a question people ask once.
 */
.setup {
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
  padding-inline: max(var(--space-4), (100% - (65ch + 8rem)) / 2);
  font-size: 0.8125rem;
  line-height: 1.25rem;
  color: var(--text-muted);
}

.setup__summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  cursor: pointer;
  /* The marker is drawn by ::before below, so the row can be a flex container. */
  list-style: none;
}

.setup__summary::-webkit-details-marker {
  display: none;
}

.setup__summary::before {
  content: '▸';
  margin-right: var(--space-2);
  transition: transform var(--transition);
}

.setup[open] .setup__summary::before {
  transform: rotate(90deg);
}

.setup__summary:hover {
  color: var(--text);
}

.setup__summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/*
 * The running total for this conversation. It sits between the label and the
 * model and takes the spare room, so both of those keep their place as the
 * number grows.
 */
.setup__totals {
  flex: 1;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The model is the one thing worth reading while the strip is shut. */
.setup__model {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Per-answer tokens and cost, under the answer it belongs to. */
.usage {
  margin: var(--space-1) 0 0;
  font-size: 0.75rem;
  line-height: 1rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.setup__list {
  margin: 0;
  padding: 0 0 var(--space-4);
  display: grid;
  gap: var(--space-1);
}

.setup__row {
  display: grid;
  grid-template-columns: 14rem 1fr;
  gap: var(--space-3);
  align-items: baseline;
}

.setup__row dt {
  color: var(--text);
}

.setup__row dd {
  margin: 0;
}

.setup__note::before {
  content: '— ';
}

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

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

/* Two columns do not fit a phone; the value goes under its label. */
@media (max-width: 560px) {
  .setup__row {
    grid-template-columns: 1fr;
    gap: 0;
    margin-bottom: var(--space-2);
  }
}

/* ---------- the document editor ---------- */

/*
 * A user's own documents. The editor is deliberately plain: a textarea and a
 * live preview, with no toolbar — people paste into this far more often than
 * they compose in it, and a toolbar that formats a selection is a promise the
 * markdown behind it has to keep.
 */
.page--editor {
  max-width: 72rem;
}

.editor {
  display: grid;
  gap: var(--space-5);
}

.editor__meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

/* Side by side while there is room; stacked when there is not. */
.editor__panes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
  align-items: start;
}

.editor__write textarea {
  min-height: 28rem;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  line-height: 1.5;
  resize: vertical;
}

.editor__preview {
  display: grid;
  gap: var(--space-2);
}

/*
 * The preview matches the field beside it rather than the page: it has to look
 * like a pane of the editor, and it holds the same text as the textarea.
 */
.editor__preview .message__body--rich {
  min-height: 28rem;
  max-height: 40rem;
  overflow-y: auto;
  padding: var(--space-3);
  border: 1px solid var(--border);
  background: var(--bg-subtle);
  font-size: 0.9375rem;
}

/* The same weight and size as a <label>, without claiming to label a field. */
.field__label {
  font-family: var(--font-heading);
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.editor__actions {
  align-items: center;
  gap: var(--space-4);
}

@media (max-width: 860px) {
  .editor__meta,
  .editor__panes {
    grid-template-columns: 1fr;
  }

  /* Stacked, the preview is below the fold anyway; give it less room. */
  .editor__preview .message__body--rich {
    min-height: 12rem;
  }
}

/* A tag on the document list. */
.tag {
  display: inline-block;
  padding: 0 0.375rem;
  border: 1px solid var(--border-strong);
  font-size: 0.75rem;
  line-height: 1.25rem;
  color: var(--text-muted);
  white-space: nowrap;
}
