@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("fonts/newsreader-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("fonts/newsreader-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("fonts/newsreader-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/*
 * Pepper design system — GENERATED by design-system/build.py. Do not edit.
 * Token values come from tokens/pepper.tokens.json; component rules from
 * css/components.css. Edit those and rebuild.
 *
 * Language: warm paper, serif identity, a single orange brand gradient,
 * minimal chat (no bubbles), one whole-brief card, a draggable
 * suggestion drawer, and a gradient send affordance. Light and dark are
 * both first-class and both warm.
 */

:root {
  color-scheme: light dark;

  --bg: #fbfaf5;
  --card: #fffefb;
  --card-hover: #f4efe3;
  --chip: #f4eddd;
  --field: #ffffff;
  --border: #e9e2d1;
  --border-strong: #d9cfba;
  --field-border: #e6dfcd;
  --scrim: rgba(42, 33, 18, 0.32);
  --scrim-heavy: rgba(30, 24, 14, 0.88);
  --fg: #24211a;
  --fg-strong: #171510;
  --fg-soft: #3c382f;
  --muted: #7c725b;
  --faint: #a99f88;
  --user: #5b5340;
  --chip-ink: #6f685b;
  --accent: #24211a;
  --primary-accent: #cf4a1e;
  --ok: oklch(0.55 0.13 155);
  --warn: #b5872f;
  --danger: #b0402f;
  --pep-grad-text: linear-gradient(96deg, #96600a 0%, #b93c0b 48%, #992810 100%);
  --elev-menu: 0 6px 24px rgba(55, 43, 24, 0.10);
  --elev-pop: 0 12px 32px rgba(55, 43, 24, 0.13), 0 1px 3px rgba(55, 43, 24, 0.08);
  --elev-modal: 0 24px 64px rgba(55, 43, 24, 0.20);

  --pep-grad: linear-gradient(96deg, #ffc257 0%, #ff6a3d 48%, #e2391c 100%);
  --on-scrim: rgba(255, 255, 255, 0.13);
  --on-scrim-hover: rgba(255, 255, 255, 0.24);
  --on-scrim-ink: rgba(255, 255, 255, 0.95);
  --on-scrim-muted: rgba(255, 255, 255, 0.62);
  --serif: "Newsreader", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-xl: 16px;
  --r-pill: 999px;
  --s-1: 2px;
  --s-2: 4px;
  --s-3: 6px;
  --s-4: 8px;
  --s-5: 10px;
  --s-6: 12px;
  --s-7: 14px;
  --s-8: 16px;
  --s-9: 18px;
  --s-10: 24px;
  --s-11: 32px;
  --s-12: 56px;
  --motion-fast: 160ms;
  --motion-base: 220ms;
  --motion-slow: 550ms;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14120d;
    --card: #211d15;
    --card-hover: #2a2419;
    --chip: #241f16;
    --field: #211d15;
    --border: #2a251b;
    --border-strong: #463c29;
    --field-border: #38311f;
    --scrim: rgba(0, 0, 0, 0.55);
    --scrim-heavy: rgba(9, 7, 4, 0.93);
    --fg: #f2ecdf;
    --fg-strong: #fbf6ea;
    --fg-soft: #d6cebc;
    --muted: #9a9078;
    --faint: #6f6754;
    --user: #cabfa5;
    --chip-ink: #b7ad95;
    --accent: #f2ecdf;
    --primary-accent: #ff8a5c;
    --ok: oklch(0.72 0.08 155);
    --warn: #d6a55d;
    --danger: #e07a6b;
    --pep-grad-text: linear-gradient(96deg, #ffc257 0%, #ff6a3d 48%, #e2391c 100%);
    --elev-menu: 0 6px 24px rgba(0, 0, 0, 0.34);
    --elev-pop: 0 14px 36px rgba(0, 0, 0, 0.46);
    --elev-modal: 0 24px 64px rgba(0, 0, 0, 0.55);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #14120d;
  --card: #211d15;
  --card-hover: #2a2419;
  --chip: #241f16;
  --field: #211d15;
  --border: #2a251b;
  --border-strong: #463c29;
  --field-border: #38311f;
  --scrim: rgba(0, 0, 0, 0.55);
  --scrim-heavy: rgba(9, 7, 4, 0.93);
  --fg: #f2ecdf;
  --fg-strong: #fbf6ea;
  --fg-soft: #d6cebc;
  --muted: #9a9078;
  --faint: #6f6754;
  --user: #cabfa5;
  --chip-ink: #b7ad95;
  --accent: #f2ecdf;
  --primary-accent: #ff8a5c;
  --ok: oklch(0.72 0.08 155);
  --warn: #d6a55d;
  --danger: #e07a6b;
  --pep-grad-text: linear-gradient(96deg, #ffc257 0%, #ff6a3d 48%, #e2391c 100%);
  --elev-menu: 0 6px 24px rgba(0, 0, 0, 0.34);
  --elev-pop: 0 14px 36px rgba(0, 0, 0, 0.46);
  --elev-modal: 0 24px 64px rgba(0, 0, 0, 0.55);
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg: #fbfaf5;
  --card: #fffefb;
  --card-hover: #f4efe3;
  --chip: #f4eddd;
  --field: #ffffff;
  --border: #e9e2d1;
  --border-strong: #d9cfba;
  --field-border: #e6dfcd;
  --scrim: rgba(42, 33, 18, 0.32);
  --scrim-heavy: rgba(30, 24, 14, 0.88);
  --fg: #24211a;
  --fg-strong: #171510;
  --fg-soft: #3c382f;
  --muted: #7c725b;
  --faint: #a99f88;
  --user: #5b5340;
  --chip-ink: #6f685b;
  --accent: #24211a;
  --primary-accent: #cf4a1e;
  --ok: oklch(0.55 0.13 155);
  --warn: #b5872f;
  --danger: #b0402f;
  --pep-grad-text: linear-gradient(96deg, #96600a 0%, #b93c0b 48%, #992810 100%);
  --elev-menu: 0 6px 24px rgba(55, 43, 24, 0.10);
  --elev-pop: 0 12px 32px rgba(55, 43, 24, 0.13), 0 1px 3px rgba(55, 43, 24, 0.08);
  --elev-modal: 0 24px 64px rgba(55, 43, 24, 0.20);
}

/*
 * Pepper component primitives — the hand-authored half of the design system.
 * design-system/build.py prepends the token variables generated from
 * tokens/pepper.tokens.json and writes the combined file to
 * generated/pepper.css. Edit this file (never generated/pepper.css), then run:
 *
 *   uv run python design-system/build.py
 */

*, *::before, *::after {
  box-sizing: border-box;
}

/* How much room the composer needs beneath a surface's last line.
 *
 * The composer floats over the foot of every surface it is on rather than
 * reserving a band, so the *scrolling* box has to end above it or the last
 * thing on the page is unreachable underneath the card. Every page needs the
 * same amount, so it is one number here rather than a guess per page — five
 * applet pages had picked 140px, 200px and `calc(var(--s-12) * 3)`
 * independently and the other eight had picked nothing at all, which is
 * exactly the bug: the last row of Todos, Feeds, Help and Images sat under
 * the dock.
 *
 * Sized from the dock's *resting* height (its chip line plus the field, plus
 * the composer's own padding and a gap), not its open one — an open launcher
 * is a temporary overlay, not a floor. The two surfaces that read it are
 * `.shell-scroll` in the shell and `body` on an applet page; both treat it as
 * a floor a measurement may raise, never as the final word.
 */
:root {
  --composer-floor: calc(var(--s-12) * 3);
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
}

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

input,
select,
textarea {
  color: inherit;
}

::selection {
  background: color-mix(in srgb, var(--primary-accent) 28%, transparent);
}

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

/* ---------------------------------------------------------------- Type */

.pepper-h1,
.pepper-h2,
.pepper-h3 {
  color: var(--fg-strong);
  font-family: var(--serif);
  font-weight: 500;
}

.pepper-h1 {
  font-size: 28px;
  letter-spacing: -0.015em;
  line-height: 1.2;
}

.pepper-h2 {
  font-size: 22px;
  letter-spacing: -0.01em;
  line-height: 1.25;
}

.pepper-h3 {
  font-size: 17px;
  letter-spacing: -0.01em;
}

.pepper-kicker {
  color: var(--muted);
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.pepper-lede {
  color: var(--muted);
  font-size: 16px;
  line-height: 1.55;
  max-width: 56ch;
}

/* --------------------------------------------------------------- Prose */

/* Rendered markdown: help topics, memory summaries, both dossier halves,
   artifact and skill bodies. Markdown reaches the page as a closed set of
   structural tags, so the primitive styles those tags directly instead of
   every surface restating the same twenty rules under its own class name.
   Prose owns type only — the caller owns the container, and per patterns/
   prose.md that container is the page or a card, never a box nested inside
   one. Add .is-tight where prose sits in a dense row or a side-by-side
   panel rather than a full reading column. */
.pepper-prose {
  color: var(--fg);
  font-size: 14.5px;
  line-height: 1.65;
}

.pepper-prose > :first-child {
  margin-top: 0;
}

.pepper-prose > :last-child {
  margin-bottom: 0;
}

.pepper-prose h1,
.pepper-prose h2,
.pepper-prose h3,
.pepper-prose h4,
.pepper-prose h5,
.pepper-prose h6 {
  color: var(--fg-strong);
  font-family: var(--serif);
  font-weight: 500;
  letter-spacing: -0.015em;
  margin: 28px 0 10px;
}

.pepper-prose h1 {
  font-size: 24px;
}

.pepper-prose h2 {
  font-size: 22px;
}

.pepper-prose h3 {
  font-size: 17px;
}

.pepper-prose h4,
.pepper-prose h5,
.pepper-prose h6 {
  font-size: 15px;
}

.pepper-prose p {
  margin: 0 0 14px;
}

.pepper-prose ul,
.pepper-prose ol {
  margin: 0 0 14px;
  padding-left: 22px;
}

.pepper-prose li {
  margin: 4px 0;
}

.pepper-prose li p {
  margin: 0 0 6px;
}

.pepper-prose strong {
  color: var(--fg-strong);
  font-weight: 600;
}

.pepper-prose code {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  border-radius: var(--r-sm);
  font-family: var(--mono);
  font-size: 0.92em;
  padding: 1px 5px;
}

/* A code block is one scroll container: a long line scrolls the block, never
   the page. The inner <code> drops the inline chip styling. */
.pepper-prose pre {
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  margin: 0 0 14px;
  overflow-x: auto;
  padding: 10px 12px;
}

.pepper-prose pre code {
  background: none;
  font-size: 12.5px;
  padding: 0;
}

/* Links stay readable rather than loud: full-strength ink with a hairline
   underline that firms up on hover. Prose carries model- and user-authored
   links, so the surface must not depend on color alone to mark them. */
.pepper-prose a {
  color: var(--fg-strong);
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  text-underline-offset: 2px;
}

.pepper-prose a:hover {
  text-decoration-color: var(--fg);
}

.pepper-prose blockquote {
  border-left: 2px solid var(--border-strong);
  color: var(--muted);
  font-style: italic;
  margin: 0 0 14px;
  padding: 6px 14px;
}

.pepper-prose hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 24px 0;
}

.pepper-prose img {
  border-radius: var(--r-md);
  max-width: 100%;
}

/* Markdown tables borrow the data-table look, but stay a native table.
   `display: block` would buy horizontal scrolling the way .pepper-table-wrap
   does, at the cost of stripping table semantics out of the accessibility tree
   — Safari/VoiceOver stops treating it as a table at all, so cell/header
   association and table navigation go with it. Markdown emits a bare <table>
   with nowhere to hang a wrapper, so cells wrap instead: `anywhere` lets a long
   URL break, which also lets the table's min-content width shrink rather than
   pushing the page sideways. A genuinely wide markdown table would need the
   renderer to emit the wrapper — see patterns/prose.md. */
.pepper-prose table {
  border-collapse: collapse;
  margin: 0 0 14px;
  width: 100%;
}

.pepper-prose th,
.pepper-prose td {
  border-bottom: 1px solid var(--border);
  overflow-wrap: anywhere;
  padding: var(--s-3) var(--s-6);
  text-align: left;
}

.pepper-prose th {
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Dense variant: list rows, side-by-side panels, anywhere prose is a detail
   inside a larger surface rather than the surface itself. */
.pepper-prose.is-tight {
  font-size: 13.5px;
  line-height: 1.6;
}

.pepper-prose.is-tight h1,
.pepper-prose.is-tight h2,
.pepper-prose.is-tight h3,
.pepper-prose.is-tight h4,
.pepper-prose.is-tight h5,
.pepper-prose.is-tight h6 {
  margin: 16px 0 6px;
}

.pepper-prose.is-tight h1 {
  font-size: 19px;
}

.pepper-prose.is-tight h2 {
  font-size: 17px;
}

.pepper-prose.is-tight h3,
.pepper-prose.is-tight h4,
.pepper-prose.is-tight h5,
.pepper-prose.is-tight h6 {
  font-size: 14.5px;
}

.pepper-prose.is-tight p,
.pepper-prose.is-tight ul,
.pepper-prose.is-tight ol,
.pepper-prose.is-tight blockquote,
.pepper-prose.is-tight pre {
  margin-bottom: 10px;
}

.pepper-prose.is-tight li {
  margin: 2px 0;
}

/* -------------------------------------------------------------- Brand */

/* The primary Pepper mark: the wordmark, in the display serif, filled with
   the brand gradient. Prefer this over any icon or glyph box. */
.pepper-wordmark {
  background: var(--pep-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  display: inline-block;
  font-family: var(--serif);
  font-size: 25px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Optional avatar / favicon glyph. The wordmark is the primary mark; use this
   only where a compact square avatar is genuinely needed. */
.pepper-glyph {
  align-items: center;
  background: var(--pep-grad);
  border-radius: var(--r-md);
  color: #fff;
  display: inline-flex;
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 500;
  height: 28px;
  justify-content: center;
  letter-spacing: -0.02em;
  width: 28px;
}

.pepper-avatar {
  align-items: center;
  background: linear-gradient(140deg, #ffb24a, #d8391f);
  border-radius: var(--r-pill);
  color: #fff;
  display: inline-flex;
  font-family: var(--serif);
  font-weight: 500;
  height: 32px;
  justify-content: center;
  width: 32px;
}

/* ------------------------------------------------------------ Surfaces */

.pepper-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-8) var(--s-9);
}

/* ------------------------------------------------------------- Buttons */

.pepper-button {
  align-items: center;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  cursor: pointer;
  display: inline-flex;
  font-size: 13.5px;
  font-weight: 500;
  gap: var(--s-3);
  justify-content: center;
  padding: 7px 14px;
  text-decoration: none;
  transition: background var(--motion-fast) ease, border-color var(--motion-fast) ease, color var(--motion-fast) ease;
  user-select: none;
  white-space: nowrap;
}

.pepper-button-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}

.pepper-button-secondary {
  background: var(--card);
  border-color: var(--border-strong);
  color: var(--fg);
}

.pepper-button-ghost {
  background: transparent;
  color: var(--fg);
}

.pepper-button-secondary:hover,
.pepper-button-ghost:hover {
  background: var(--card-hover);
}

/* A button that cannot be pressed has to look like one. Until this existed a
   disabled `.pepper-button` rendered at full strength — same fill, same
   contrast, same pointer — so the only way to discover it was to press it and
   watch nothing happen. That is exactly how the booking create button read
   when a title had not been typed yet: live, and inert.

   `:disabled` and `[aria-disabled]` together, because the two mean the same
   thing to a reader and only the first is a real `<button>` attribute — a
   link or a div standing in for a button can only say the second. The pointer
   change is the other half: a cursor that stops being a pointer is the
   earliest signal available, and it arrives on hover rather than on click. */
.pepper-button:disabled,
.pepper-button[aria-disabled="true"] {
  cursor: default;
  opacity: 0.45;
}

/* Hover is a promise of what a press would do, so a dead button withdraws it. */
.pepper-button:disabled:hover,
.pepper-button[aria-disabled="true"]:hover {
  background: inherit;
}

.pepper-button-secondary:disabled:hover,
.pepper-button-ghost:disabled:hover {
  background: transparent;
}

/* Destructive modifier — layers on any button variant. Reserved for actions
   that remove or revoke something; the copy beside it must say what is lost. */
.pepper-button.is-danger {
  border-color: color-mix(in srgb, var(--danger) 35%, var(--border));
  color: var(--danger);
}

.pepper-button.is-danger:hover {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
}

.pepper-icon-button {
  align-items: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  height: 32px;
  justify-content: center;
  padding: 0;
  transition: background var(--motion-fast) ease, color var(--motion-fast) ease;
  width: 32px;
}

.pepper-icon-button:hover {
  color: var(--fg);
}

/* The send affordance carries the brand gradient — the one recurring mark in
   chat. Round, gradient-filled, white glyph. */
.pepper-send {
  align-items: center;
  background: var(--pep-grad);
  border: 0;
  border-radius: var(--r-pill);
  color: #fff;
  cursor: pointer;
  display: inline-flex;
  flex: 0 0 auto;
  font-size: 16px;
  height: 36px;
  justify-content: center;
  width: 36px;
}

/* --------------------------------------------------------------- Chips */

.pepper-chip {
  align-items: center;
  background: var(--chip);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  color: var(--chip-ink);
  display: inline-flex;
  font-size: 11px;
  font-weight: 500;
  gap: 5px;
  letter-spacing: 0.02em;
  padding: 2px 8px;
  text-transform: uppercase;
}

.pepper-chip-ok {
  background: color-mix(in srgb, var(--ok) 15%, transparent);
  border-color: color-mix(in srgb, var(--ok) 35%, transparent);
  color: var(--ok);
}

.pepper-chip-warn {
  background: color-mix(in srgb, var(--warn) 15%, transparent);
  border-color: color-mix(in srgb, var(--warn) 35%, transparent);
  color: var(--warn);
}

/* --------------------------------------------------------------- Field */

.pepper-field {
  background: var(--field);
  border: 1px solid var(--field-border);
  border-radius: var(--r-md);
  color: var(--fg);
  font-size: 13.5px;
  outline: none;
  padding: 8px 12px;
  transition: border-color var(--motion-fast) ease;
  width: 100%;
}

.pepper-field::placeholder {
  color: var(--muted);
}

.pepper-field:focus {
  border-color: var(--primary-accent);
}

/* ---------------------------------------------------------------- Chat */

/* Chat is the primary surface. No bubbles: Pepper speaks as plain leading
   prose, the user answers in the trailing serif voice. A centered reading
   column keeps lines comfortable. */
/* Turns are separated by air — not by rules, and not by containers. The gap
   is wide enough that a multi-paragraph reply reads as one block instead of
   running into the message below it, which is the job the old `.pepper-obj`
   card was doing on whichever turns a formatting coin-flip happened to card.
   A hairline between turns was drawn and rejected: it re-introduces chrome
   into a surface whose whole posture is that alignment and voice carry the
   structure.

   `--turn-gap` is the gap under its own name, because the rows that hang off
   a message (the meta row, the reasoning disclosure, attachments) cancel most
   of it with a negative margin to group with the message above rather than
   the one below. Those rows are shared with the applet pane's transcript,
   which sets a smaller gap of its own — so the cancellation has to be
   expressed against whatever gap the container actually has. A hard-coded
   negative margin is right for exactly one container and silently overlaps in
   the other. */
.pepper-chat {
  --turn-gap: var(--s-10);

  display: flex;
  flex-direction: column;
  gap: var(--turn-gap);
  margin: 0 auto;
  max-width: 640px;
  width: 100%;
}

.pepper-msg {
  font-size: 15px;
  line-height: 1.62;
  max-width: 100%;
}

.pepper-msg-pepper {
  align-self: flex-start;
  color: var(--fg);
}

/* Pepper's prose is measured in `ch`, never as a percentage of the column.
   A percentage tracks the viewport and says nothing about how many characters
   land on a line: the previous `max-width: 88%` of a 640px column was ~80
   characters at 14.5px, well past the readable band, and that — not the
   absence of a box — is what made long answers hard to read.

   The cap sits on the message's *blocks* rather than on the message box, so
   a table or a code block can exceed it (a 64ch table is a worse table) while
   the prose around it stays measured. The stream then has one left edge and
   exactly two right edges, instead of a different width per message. */
.pepper-msg-pepper > :is(p, ul, ol, blockquote, h1, h2, h3, h4, h5, h6) {
  max-width: 64ch;
}

/* The block sits trailing — that, plus the serif voice, is what carries
   attribution. The text inside sets left: right-aligned prose gives every line
   a different starting edge, and past a line or two the eye loses where each
   one begins (#1051). A short message is unaffected either way, since the box
   shrinks to hug its text. The narrower measure keeps a long message from
   running the full width of the column. */
.pepper-msg-user {
  align-self: flex-end;
  color: var(--user);
  font-family: var(--serif);
  font-size: 16px;
  max-width: min(100%, 44ch);
  text-align: left;
}

/* Header: one header for every app surface — the wordmark floating over the
   top of the surface with no bar and no border, so content scrolls *under* it,
   softly veiled (tinted + blurred) for legibility rather than cut off by an
   opaque bar. The shell's hamburger floats over the leading corner and a
   page may float one affordance over the trailing corner. The empty area
   passes pointer events through to the content beneath; only the wordmark
   stays interactive. Needs a positioned ancestor (the app shell frame), and
   the scroll area below needs top clearance for it. */
.pepper-app-header {
  align-items: center;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  background: color-mix(in srgb, var(--bg) 18%, transparent);
  display: flex;
  justify-content: center;
  left: 0;
  min-height: 46px;
  padding: var(--s-3) var(--s-8);
  pointer-events: none;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 20;
}

.pepper-app-header .pepper-wordmark {
  pointer-events: auto;
  text-decoration: none;
}

/* --------------------------------------------------------------- Brief */

/* The daily brief is the one card in the chat stream, and it renders whole:
   the picture of the day, then a sheet climbing over the bottom of it
   carrying the dateline, the day-shaping sentence, the agenda rail, the
   lead-in strip and the feed stories. This mirrors the Brief applet's own
   page (`web/apps/defaults/brief.html`) — deliberately, so that opening the
   app from the card lands somewhere that looks like where you came from.

   It was previously specified as a collapsed teaser with a fading body under
   a "More" affordance. That is gone, and the subtraction is the design: in
   chat the brief *is* the message, so hiding it behind a tap put a shutter
   over the one thing the reader opened the conversation to read.

   The card is not interactive beyond opening the app. The page's story links
   and todo checkboxes are absent rather than disabled — see
   `templates/applets/_brief_card.html`, which never receives a URL or an
   action to render. That is what lets `applet.js` treat the whole background
   as one gesture: with no nested control, there is nothing for a click to
   mean instead. */
.pepper-brief {
  background: var(--card);
  border-radius: var(--r-xl);
  box-shadow: var(--elev-pop);
  cursor: pointer;
  overflow: hidden;
  /* The containing block for the date tag, which is placed against the card
     rather than the sheet so it can sit over the picture. */
  position: relative;
}

/* The image is capped by aspect ratio, cropping rather than squashing, and
   the sheet then climbs over its bottom edge — so body text never sits on
   artwork and only the date tag does. Same two moves as the applet page, at
   the narrower proportions a card in a conversation column can afford. */
.pepper-brief-hero {
  aspect-ratio: 16 / 9;
  display: block;
  object-fit: cover;
  width: 100%;
}

@media (min-width: 640px) {
  .pepper-brief-hero {
    aspect-ratio: 21 / 9;
  }
}

/* Carries its own scrim: it sits on a generated image whose palette is
   different every morning, so it cannot rely on the artwork behind it. */
.pepper-brief-tag {
  align-items: center;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  background: rgba(20, 16, 10, 0.5);
  border-radius: var(--r-pill);
  color: #fff;
  display: inline-flex;
  font-size: 11.5px;
  gap: var(--s-3);
  left: var(--s-5);
  padding: var(--s-2) var(--s-4);
  position: absolute;
  top: var(--s-4);
}

.pepper-brief-tag .sep {
  opacity: 0.45;
}

.pepper-brief-sheet {
  background: var(--card);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  margin-top: -7%;
  padding: var(--s-6) var(--s-6) var(--s-5);
  position: relative;
}

/* With no image there is no overlap to make — the brief starts at the top. */
.pepper-brief.is-imageless .pepper-brief-sheet {
  border-radius: 0;
  margin-top: 0;
}

.pepper-brief-dateline {
  color: var(--muted);
  font-size: 12px;
  margin: 0 0 var(--s-4);
}

/* The brief's opening line, set as the card's headline. The skill writes it to
   a budget (`skills/morning-brief/SKILL.md`, "The opening line") so it lands in
   one or two lines here; `balance` evens those two rather than leaving a
   one-word orphan under a full one, which a display serif at this size makes
   conspicuous. Deliberately no clamp — a brief that ran long is a brief that
   broke its budget, and hiding the words would hide that too. */
.pepper-brief-lede {
  color: var(--fg-strong);
  font-family: var(--serif);
  font-size: 21px;
  letter-spacing: -0.015em;
  line-height: 1.24;
  margin: 0 0 var(--s-6);
  text-wrap: balance;
}

/* today's commitments — a time rail, not a table */
.pepper-brief-agenda {
  display: flex;
  flex-direction: column;
}

.pepper-brief-ag-row {
  border-top: 1px solid var(--border);
  display: flex;
  gap: var(--s-5);
  padding: var(--s-4) 0;
}

.pepper-brief-agenda .pepper-brief-ag-row:first-child {
  border-top: 0;
}

.pepper-brief-ag-time {
  color: var(--muted);
  flex: 0 0 66px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: -0.02em;
  padding-top: 2px;
}

.pepper-brief-ag-title {
  font-size: 14px;
  line-height: 1.35;
}

/* tomorrow / weather / todos — inline lead-ins, as the brief writes them */
.pepper-brief-strip {
  border-top: 1px solid var(--border-strong);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-6);
  padding-top: var(--s-5);
}

.pepper-brief-strip-row {
  display: flex;
  font-size: 13.5px;
  gap: var(--s-5);
  line-height: 1.5;
}

.pepper-brief-lab {
  color: var(--faint);
  flex: 0 0 66px;
  font-family: var(--mono);
  font-size: 10.5px;
  padding-top: 3px;
}

.pepper-brief-grp {
  align-items: baseline;
  display: flex;
  gap: var(--s-4);
  margin: var(--s-8) 0 var(--s-4);
}

.pepper-brief-grp span {
  color: var(--muted);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

/* Plain text, not a link: the applet's page makes a story row an anchor to
   the publisher, and in chat the card has exactly one destination. */
.pepper-brief-story {
  border-top: 1px solid var(--border);
  padding: var(--s-5) 0;
}

.pepper-brief-stories .pepper-brief-story:first-child {
  border-top: 0;
}

.pepper-brief-st-head {
  font-family: var(--serif);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.3;
  margin: 0 0 var(--s-2);
}

.pepper-brief-st-body {
  color: var(--fg-soft);
  font-size: 13.5px;
  line-height: 1.52;
  margin: 0;
}

.pepper-brief-st-src {
  color: var(--muted);
  font-size: 11px;
  margin-top: var(--s-2);
}

/* Anything the parser did not recognise, shown as the prose it was. */
.pepper-brief-rest p {
  color: var(--fg-soft);
  font-size: 13.5px;
  line-height: 1.6;
  margin: 0 0 var(--s-4);
}

/* The whole brief when there was no structure to lay out. `pre-wrap` keeps
   the line breaks the model wrote, which are the only shape it has left. */
.pepper-brief-plain {
  font-size: 14.5px;
  line-height: 1.65;
  margin: 0;
  white-space: pre-wrap;
}

.pepper-brief-foot {
  border-top: 1px solid var(--border);
  margin-top: var(--s-7);
  padding-top: var(--s-4);
}

/* The card's only control, and the keyboard and screen-reader route to the
   gesture the whole background already carries. */
.pepper-brief-open {
  background: none;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  padding: var(--s-2) 0;
}

.pepper-brief-open:hover {
  color: var(--accent);
}

/* --------------------------------------------------- Suggestion drawer */

/* A compact, draggable drawer of follow-ups. One row at rest with the handle
   overlapping the suggestions to save height; drag the handle up to expose
   more. Suggestion text carries the readable text gradient (--pep-grad-text),
   which darkens in light theme to hold contrast. */
.pepper-suggest-drawer {
  height: 50px;
  max-height: 168px;
  min-height: 40px;
  overflow: hidden;
  position: relative;
  transition: height var(--motion-base) var(--ease);
}

.pepper-suggest-handle {
  cursor: grab;
  display: grid;
  height: 15px;
  left: 0;
  place-items: center;
  position: absolute;
  right: 0;
  top: 0;
  touch-action: none;
  z-index: 2;
}

.pepper-suggest-handle:active {
  cursor: grabbing;
}

.pepper-suggest-grip {
  background: var(--field-border);
  border-radius: var(--r-pill);
  height: 4px;
  width: 40px;
}

.pepper-suggest-list {
  align-content: flex-start;
  display: flex;
  flex-wrap: wrap;
  gap: 2px 18px;
  height: 100%;
  overflow-y: auto;
  padding: 13px 4px 6px;
  scrollbar-width: none;
}

.pepper-suggest-list::-webkit-scrollbar {
  display: none;
}

.pepper-suggest {
  background-image: var(--pep-grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  border: 0;
  color: transparent;
  cursor: pointer;
  font-size: 13.5px;
  padding: 3px 0;
  white-space: nowrap;
}

.pepper-suggest:hover {
  opacity: 0.78;
}

/* ------------------------------------------------------- In-app asks */

/* An app page handing a sentence to the composer. Every mini app has these —
   "swap this meal", "follow my local teams", "teach Pepper a new skill" — and
   before this primitive each app invented its own: a dashed block of quoted
   phrases, a bordered route card, a dotted call-to-action, or just a plain
   button indistinguishable from the one beside it that changes the data.

   The text gradient is shared with `.pepper-suggest` on purpose. It is the
   product's one mark for "this string is something Pepper will be asked", and
   an app page is exactly where that needs to be legible without being taught:
   next to it sits a `.pepper-button`, which acts on the app's own data and
   never leaves the page. Same mark, so the gesture is learned once.

   What differs is the surface. `.pepper-suggest` is single-line by contract —
   the field must not move, so a dock suggestion may never wrap, and it scrolls
   sideways in a fixed row. These live in content, stack, and wrap, because an
   in-app ask can be a whole sentence built from what is on screen and has a
   column to use rather than a strip.

   Contained, unlike the dock's. A suggestion sitting in a page's own content
   has nothing holding it — no frosted strip, no field beneath it — and reads
   as a stray line of coloured text rather than something to press. The dock
   needs no box because the dock *is* the box.

   The vivid brand gradient is the chip's edge, and the label is the readable
   variant. That split is forced by contrast, not taste: on light paper the
   vivid stops measure 1.53:1, 2.72:1 and 4.16:1 against the background, so
   they cannot carry text at AA and `--pep-grad-text` darkens them until they
   can. A hairline is not text, so the true brand gradient survives where it is
   actually legible — and ends up more present than it ever was as type. In
   dark theme the two tokens are identical and this simply agrees with itself.

   The border is drawn as two background layers rather than a `border-image`,
   which cannot follow a border-radius. The first layer is the fill clipped to
   the padding box, the second the gradient clipped to the border box, with a
   transparent border letting the second show through. That is also why the
   label needs its own element: `background-clip` is one property for every
   layer on an element, so a box that clips to its padding box cannot also clip
   a gradient to its text. */
/* The container, and it owns the spacing. Sized for a *contained* chip: at the
   4px this started as, two bordered pills stack into one block, and a chip
   landing under a row of filter chips — the meals cuisine grids — reads as part
   of that row rather than as the thing you say instead of tapping them. No app
   may override one side of the margin; that is how the plan chip ended up flush
   against the button beneath it. */
.pepper-asks {
  align-items: flex-start;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  margin: var(--s-8) 0;
}

/* A row rather than a stack, for two or three short labels that read as a set
   ("How did Arsenal look?" / "What did I miss?"). Anything long enough to wrap
   belongs in the default column, where a wrapped label cannot be mistaken for
   two. */
.pepper-asks-row {
  align-items: center;
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--s-4);
}

.pepper-ask {
  background-clip: padding-box, border-box;
  background-image: linear-gradient(var(--card), var(--card)), var(--pep-grad);
  background-origin: border-box;
  border: 1px solid transparent;
  border-radius: var(--r-lg);
  cursor: pointer;
  font: inherit;
  font-size: 13.5px;
  padding: var(--s-4) var(--s-7);
  text-align: left;
  white-space: normal;
}

/* The label. Always present inside a `.pepper-ask` — the chip has no text of
   its own, for the `background-clip` reason above. */
.pepper-ask-tx {
  background-image: var(--pep-grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.pepper-ask:hover {
  background-image: linear-gradient(var(--card-hover), var(--card-hover)), var(--pep-grad);
}

/* ------------------------------------------------------------ Composer */

.pepper-composer {
  align-items: center;
  display: flex;
  gap: var(--s-5);
}

/* Rounded rectangle, not a pill: the composer field grows with the message,
   and a pill's radius grows with it — at three or four lines the curve eats
   into the first and last lines of text (#1050). A fixed radius keeps every
   line clear of the corners at any height. The send affordance stays round. */
.pepper-composer .pepper-field {
  border-radius: var(--r-lg);
  flex: 1;
  min-width: 0;
  padding: 11px 16px;
}

/* -------------------------------------------------------- Applet shell */

.pepper-applet {
  margin: 0 auto;
  max-width: 680px;
  padding: var(--s-10) var(--s-8) var(--s-12);
  width: 100%;
}

/* ----------------------------------------------------------- Checklist */

/* Actionable collections: todos, groceries, packing, setup steps. Rows are
   hairline-separated; add .is-done to a row to mute the completed item. */
.pepper-checklist {
  display: flex;
  flex-direction: column;
}

.pepper-check-row {
  align-items: flex-start;
  border-top: 1px solid var(--border);
  display: flex;
  gap: var(--s-6);
  padding: var(--s-6) var(--s-2);
}

.pepper-check-row:first-child {
  border-top: 0;
}

.pepper-check {
  appearance: none;
  background: var(--field);
  border: 1.5px solid var(--border-strong);
  border-radius: 6px;
  cursor: pointer;
  display: grid;
  flex: 0 0 auto;
  height: 18px;
  margin: 2px 0 0;
  place-items: center;
  width: 18px;
}

.pepper-check:checked {
  background: var(--accent);
  border-color: var(--accent);
}

.pepper-check:checked::after {
  border-bottom: 2px solid var(--bg);
  border-left: 2px solid var(--bg);
  content: "";
  height: 5px;
  transform: rotate(-45deg) translate(0, -1px);
  width: 9px;
}

.pepper-check-title {
  font-size: 14px;
}

.pepper-check-meta {
  color: var(--muted);
  font-size: 12px;
  margin-top: 1px;
}

.pepper-check-row.is-done .pepper-check-title {
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-color: var(--faint);
}

/* ---------------------------------------------------------- Data table */

/* Compact comparison of items with shared attributes. Wrap in
   .pepper-table-wrap so wide tables scroll instead of breaking the page. */
.pepper-table-wrap {
  overflow-x: auto;
}

.pepper-table {
  border-collapse: collapse;
  font-size: 13.5px;
  width: 100%;
}

.pepper-table th {
  border-bottom: 1px solid var(--border-strong);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: var(--s-4) var(--s-6);
  text-align: left;
  text-transform: uppercase;
  white-space: nowrap;
}

.pepper-table td {
  border-bottom: 1px solid var(--border);
  padding: var(--s-5) var(--s-6);
}

.pepper-table tbody tr:hover {
  background: var(--card-hover);
}

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

/* ---------------------------------------------------------------- Form */

/* Structured input: a label (short noun), the control, and a hint that
   explains the concrete effect. */
.pepper-form {
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
}

.pepper-form-row {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.pepper-label {
  color: var(--fg-strong);
  font-size: 13px;
  font-weight: 500;
}

.pepper-hint {
  color: var(--muted);
  font-size: 12px;
}

.pepper-form-grid {
  display: grid;
  gap: var(--s-8);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 560px) {
  .pepper-form-grid {
    grid-template-columns: 1fr;
  }
}

/* Segmented control — pick one of a short, closed list.

   A rounded rectangle, deliberately not a pill, and this is the rule rather
   than a preference: **a control that contains other controls is a box.** A
   pill's radius is half its height, so the curve cuts into the corners of
   whatever sits inside it — the first and last segments lose their ends, a
   two-line label collides with the arc, and the fix people reach for is
   padding, which makes the oval longer and the problem worse. `.pepper-chip`
   and `.pepper-button` are pills because each is one indivisible thing with
   nothing inside it to clip.

   The `--r-md` inner radius against `--r-lg` outer is the standard nested
   pair: concentric, so the gap between the selected segment and the track
   reads as even all the way round.

   Wraps when it has to. A segmented control that overflows its column is the
   other half of the same failure, and `flex-wrap` is cheaper than a
   horizontal scroller nobody discovers. Past four or five options this is
   the wrong control — use a select. */
.pepper-segment {
  align-self: flex-start;
  background: var(--chip);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  max-width: 100%;
  padding: 3px;
}

.pepper-segment button {
  background: none;
  border: 0;
  border-radius: var(--r-md);
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  padding: 6px 12px;
  transition: background var(--motion-fast) ease, color var(--motion-fast) ease;
  white-space: nowrap;
}

.pepper-segment button:hover {
  color: var(--fg);
}

/* `aria-pressed` is the selection, so the paint keys off it rather than a
   class — a segment that looks chosen and doesn't announce it is the bug
   this avoids by construction. */
.pepper-segment button[aria-pressed="true"] {
  background: var(--card);
  color: var(--fg);
  font-weight: 500;
}

/* ---------------------------------------------------------- Metric row */

/* Two to four key numbers or short statuses. Semantic color signals state
   via .is-ok / .is-warn / .is-danger on the metric; never decorates. */
.pepper-metrics {
  display: grid;
  gap: var(--s-6);
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
}

.pepper-metric {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-6) var(--s-8);
}

.pepper-metric-value {
  color: var(--fg-strong);
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 500;
  line-height: 1.2;
}

.pepper-metric-label {
  color: var(--muted);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  margin-top: 2px;
  text-transform: uppercase;
}

.pepper-metric.is-ok .pepper-metric-value {
  color: var(--ok);
}

.pepper-metric.is-warn .pepper-metric-value {
  color: var(--warn);
}

.pepper-metric.is-danger .pepper-metric-value {
  color: var(--danger);
}

@media (max-width: 560px) {
  .pepper-metrics {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ------------------------------------------------------------ Timeline */

/* Ordered events: calendars, agent actions, activity history. Group with a
   .pepper-tl-day kicker when the sequence spans more than one day. Render
   times in the user's Pepper timezone. */
.pepper-timeline {
  display: flex;
  flex-direction: column;
}

.pepper-tl-day {
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  margin: var(--s-8) 0 var(--s-4);
  text-transform: uppercase;
}

.pepper-tl-item {
  border-top: 1px solid var(--border);
  display: flex;
  gap: var(--s-6);
  padding: var(--s-5) var(--s-2);
}

.pepper-tl-time {
  color: var(--muted);
  flex: 0 0 64px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  padding-top: 1px;
}

.pepper-tl-title {
  font-size: 14px;
}

.pepper-tl-meta {
  color: var(--muted);
  font-size: 12px;
  margin-top: 1px;
}

/* ------------------------------------------------------------ Settings */

/* Operational shell: ONE centered column of stacked sections. There is no
   second persistent sidebar — spacing, section titles, and orientation text
   carry the structure instead (see patterns/settings.md). The column takes
   its width from the surrounding content frame (the app's shell layout
   modes), so this primitive sets no max-width of its own. */
.pepper-settings {
  display: flex;
  flex-direction: column;
  gap: var(--s-11);
  min-width: 0;
}

/* One control group: a .pepper-section-title, a one-sentence
   .pepper-section-sub, then its controls. Carries an id so a save redirect or
   deep link can land on it; scroll-margin keeps that landing off the top edge
   of the scroll container. */
.pepper-settings-section {
  min-width: 0;
  scroll-margin-top: var(--s-10);
}

/* A hairline between sections keeps a long scroll legible without boxing
   each group in a card. Keyed on :first-of-type (sections are <section>
   elements) rather than an adjacent-sibling rule, so a non-section child in
   the stack — a page's modal markup, say — can't swallow a divider. */
.pepper-settings-section:not(:first-of-type) {
  border-top: 1px solid var(--border);
  padding-top: var(--s-11);
}

/* Content navigation for a long control surface: one wrapping row of anchors
   into the sections below, sitting at the top of the page. Deliberately plain
   — no counts, no badges, no active state, no sticky rail — so it reads as a
   table of contents and never as a second product navigation. */
.pepper-section-links {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-7);
}

.pepper-section-links a {
  border-bottom: 1px solid transparent;
  color: var(--muted);
  font-size: 12.5px;
  text-decoration: none;
}

/* Keyboard focus gets the same cue as hover, on top of the global
   :focus-visible outline — traversing the section index shouldn't feel weaker
   than pointing at it. */
.pepper-section-links a:hover,
.pepper-section-links a:focus-visible {
  border-bottom-color: var(--border-strong);
  color: var(--fg);
}

.pepper-section-title {
  color: var(--fg-strong);
  font-family: var(--serif);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0 0 var(--s-2);
}

.pepper-section-sub {
  color: var(--muted);
  font-size: 13px;
  margin: 0 0 var(--s-8);
  max-width: 68ch;
}

/* ------------------------------------------------------- Floating layers */

.pepper-modal-scrim {
  align-items: center;
  backdrop-filter: blur(6px);
  background: color-mix(in srgb, #000 60%, transparent);
  display: flex;
  inset: 0;
  justify-content: center;
  padding: var(--s-10);
  position: fixed;
  z-index: 80;
}

.pepper-modal {
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  box-shadow: var(--elev-modal);
  max-width: 520px;
  overflow: hidden;
  width: 100%;
}

.pepper-menu {
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: var(--elev-pop);
  min-width: 200px;
  padding: var(--s-2);
}

/* Left navigation overlay opened by the menu icon: a scrim plus a slide-in
   panel. Add .is-open to the container to reveal it. */
.pepper-nav-scrim {
  background: var(--scrim);
  inset: 0;
  opacity: 0;
  pointer-events: none;
  position: absolute;
  transition: opacity var(--motion-base) ease;
  z-index: 40;
}

.pepper-nav-panel {
  background: var(--card);
  border-right: 1px solid var(--border);
  bottom: 0;
  box-shadow: var(--elev-pop);
  display: flex;
  flex-direction: column;
  gap: 3px;
  left: 0;
  max-width: 288px;
  padding: 20px 14px;
  position: absolute;
  top: 0;
  transform: translateX(-102%);
  transition: transform var(--motion-base) var(--ease);
  width: 76%;
  z-index: 41;
}

.is-open > .pepper-nav-scrim {
  opacity: 1;
  pointer-events: auto;
}

.is-open > .pepper-nav-panel {
  transform: none;
}

.pepper-nav-item {
  border-radius: var(--r-lg);
  color: var(--fg-soft);
  cursor: pointer;
  display: block;
  font-size: 14.5px;
  padding: 11px 12px;
  text-decoration: none;
}

.pepper-nav-item:hover,
.pepper-nav-item.is-active {
  background: var(--card-hover);
}

.pepper-nav-item.is-active {
  color: var(--fg-strong);
  font-weight: 600;
}

.pepper-toast {
  align-items: center;
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: var(--elev-pop);
  color: var(--fg);
  display: inline-flex;
  font-size: 13.5px;
  gap: var(--s-4);
  padding: 10px 16px;
}

/* -------------------------------------------------------------- Lightbox */

/* Any picture in Pepper, at the size the viewport allows.
 *
 * It is not a modal. A modal holds a task, so it has a title, a body and a row
 * of buttons; this holds an *object*, so it has none of them — the picture is
 * the content and everything else is an interruption of it. That is the whole
 * design: a wash, the picture, and the smallest chrome that still answers
 * "what is this and where do I go from here".
 *
 * The wash is warm (`--scrim-heavy`), never neutral black — Pepper is warm
 * paper in both themes and a #000 wash reads as some other product's viewer
 * dropped on top. It is also blurred, which is what keeps it from reading as a
 * black rectangle: the surface behind stays legible as a place you are still
 * in. And it is heavier than `--scrim`, because the navigation overlay dims a
 * surface you are still reading while this one replaces it.
 *
 * Chrome is ink, not paper: translucent `--on-scrim` fills rather than
 * `--card`, so nothing here competes with the picture for the eye. The one
 * concession is that every piece of *text* carries its own `--on-scrim` pill.
 * Bare white text is unreadable over a photograph taken at night, and a
 * filename over a night photograph is precisely the case this has to survive.
 *
 * Stacking: z-index 90 puts it above the applet stage (30), the navigation
 * overlay (40/41) and the modal scrim (80). A picture can be opened from
 * inside any of those, and there is nothing a lightbox can be opened *from*.
 */

.pepper-lightbox {
  backdrop-filter: blur(10px) saturate(0.8);
  background: var(--scrim-heavy);
  display: grid;
  /* Stage takes what is left; the caption row is only as tall as it needs. */
  grid-template-rows: 1fr auto;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  position: fixed;
  transition: opacity var(--motion-base) var(--ease);
  z-index: 90;
}

.pepper-lightbox.is-open {
  opacity: 1;
  pointer-events: auto;
}

/* `min-height: 0` is load-bearing: without it a tall picture grows this grid
   row past the viewport and pushes the caption off the bottom of the screen. */
.pepper-lightbox-stage {
  display: grid;
  min-height: 0;
  min-width: 0;
  padding: 52px var(--s-8) var(--s-4);
  place-items: center;
  position: relative;
}

.pepper-lightbox-img {
  border-radius: var(--r-sm);
  /* One hairline, not a frame — without it a picture with a white sky bleeds
     into the wash and loses its edge. */
  box-shadow: 0 0 0 1px var(--on-scrim);
  cursor: zoom-in;
  display: block;
  max-height: 100%;
  max-width: 100%;
  object-fit: contain;
  transition: opacity var(--motion-fast) linear;
}

/* Two zoom states, not continuous zoom. Fit is the default because that is
   what "see it bigger" means; 1:1 is the second press, for the receipt and
   the screenshot where the detail *is* the point and fitting destroys it. */
.pepper-lightbox.is-zoomed .pepper-lightbox-stage {
  overflow: auto;
  place-items: start center;
}

.pepper-lightbox.is-zoomed .pepper-lightbox-img {
  cursor: zoom-out;
  max-height: none;
  max-width: none;
}

/* Loading and failure hide the picture rather than replacing it, so the layer
   never flashes empty in between — and a failed src never paints the
   browser's broken-image glyph on top of our own explanation. */
.pepper-lightbox.is-loading .pepper-lightbox-img,
.pepper-lightbox.is-failed .pepper-lightbox-img {
  opacity: 0;
}

.pepper-lightbox-status {
  color: var(--on-scrim-muted);
  display: grid;
  font-size: 13.5px;
  inset: 0;
  padding: 0 var(--s-11);
  place-items: center;
  pointer-events: none;
  position: absolute;
  text-align: center;
}

.pepper-lightbox-spinner {
  animation: pepper-lightbox-spin 700ms linear infinite;
  border: 2px solid var(--on-scrim);
  border-radius: 50%;
  border-top-color: var(--on-scrim-muted);
  height: 26px;
  width: 26px;
}

@keyframes pepper-lightbox-spin {
  to { transform: rotate(360deg); }
}

.pepper-lightbox-close,
.pepper-lightbox-nav {
  align-items: center;
  backdrop-filter: blur(6px);
  background: var(--on-scrim);
  border: 0;
  border-radius: 50%;
  color: var(--on-scrim-ink);
  cursor: pointer;
  display: flex;
  justify-content: center;
  position: absolute;
  transition: background var(--motion-fast) ease;
  z-index: 2;
}

.pepper-lightbox-close:hover,
.pepper-lightbox-nav:hover {
  background: var(--on-scrim-hover);
}

.pepper-lightbox-close {
  height: 40px;
  right: var(--s-4);
  top: var(--s-4);
  width: 40px;
}

.pepper-lightbox-close svg {
  height: 18px;
  width: 18px;
}

.pepper-lightbox-nav {
  height: 44px;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
}

.pepper-lightbox-nav svg {
  height: 20px;
  width: 20px;
}

.pepper-lightbox-nav.prev { left: var(--s-5); }
.pepper-lightbox-nav.next { right: var(--s-5); }

.pepper-lightbox-nav[disabled] {
  cursor: default;
  opacity: 0.25;
}

/* A group of one has nowhere to go, and a coarse pointer swipes instead. */
.pepper-lightbox.is-single .pepper-lightbox-nav { display: none; }

@media (pointer: coarse) {
  .pepper-lightbox-nav { display: none; }
}

/* Position in the set, parked out of the picture's way. Its own pill for the
   same reason the caption has one. */
.pepper-lightbox-count {
  backdrop-filter: blur(6px);
  background: var(--on-scrim);
  border-radius: var(--r-pill);
  color: var(--on-scrim-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  left: var(--s-5);
  padding: 5px 10px;
  position: absolute;
  top: 15px;
  white-space: nowrap;
  z-index: 2;
}

.pepper-lightbox.is-single .pepper-lightbox-count { display: none; }

/* The caption row: what this picture is, and the one place it leads. */
.pepper-lightbox-rail {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: center;
  padding: var(--s-5) var(--s-8) var(--s-9);
}

.pepper-lightbox-caption,
.pepper-lightbox-act {
  backdrop-filter: blur(6px);
  background: var(--on-scrim);
  border-radius: var(--r-pill);
  padding: 5px 12px;
}

.pepper-lightbox-caption {
  color: var(--on-scrim-ink);
  font-size: 13.5px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pepper-lightbox-caption:empty { display: none; }

.pepper-lightbox-act {
  border: 0;
  color: var(--on-scrim-ink);
  cursor: pointer;
  font-family: inherit;
  font-size: 12.5px;
  text-decoration: none;
  white-space: nowrap;
}

.pepper-lightbox-act:hover { background: var(--on-scrim-hover); }

/* While a picture is open the surface behind must not scroll under it. The
   shell's scroller is `.shell-scroll`, not the document, so locking the root
   alone would do nothing on the surface where this is used most. */
.pepper-lightbox-lock { overflow: hidden; }
.pepper-lightbox-lock .shell-scroll { overflow: hidden; }

/* An image that opens says so before it is pressed. Images that opted in are
   known from markup; the ones lightbox.js matched by size are not knowable to
   a stylesheet, so the script tags them on hover. */
[data-lightbox],
.pepper-lightbox-hot { cursor: zoom-in; }

[data-lightbox-off] { cursor: auto; }

@media (prefers-reduced-motion: reduce) {
  .pepper-lightbox,
  .pepper-lightbox-img { transition-duration: 1ms; }
  .pepper-lightbox-spinner { animation-duration: 2400ms; }
}

/* ------------------------------------------------- Applet card/expanded */

/* An applet is an object with two presentations, not a page: a card that
   sits in a stream or an index, and an expanded takeover that covers the
   surface. The card is host-drawn from a typed summary; the expanded state
   loads the applet's own page in a frame. Tapping the card expands it; the
   "✕" collapses it back.

   Stacking: the takeover deliberately covers the app header and the menu
   trigger (z-index 30), leaving only the "✕" — but it stays *below* the
   navigation overlay (scrim 40 / panel 41), which remains the escape hatch,
   and below the composer, which must stay usable in every state
   (patterns/chat.md). */

/* Soft-shadowed fill, no border — the same recipe as `.pepper-brief`, because
   the two are the same kind of thing: an object in the stream that opens. A
   bordered variant would read as a third kind, and there are only two.

   This shadow used to be shared with `.pepper-obj`, a card drawn around any
   Pepper message whose markdown looked structured. That is gone: a container
   in the stream now means an object you can open, and prose is never one, so
   this recipe is again the mark of something tappable rather than a weight
   applied to whichever replies happened to contain a list.

   The card's background is a click target for opening it (the host binds it),
   but the real control lives in the foot — a row may carry its own checkbox,
   and interactive elements cannot nest. */
.pepper-applet-card {
  background: var(--card);
  border-radius: 16px;
  box-shadow: var(--elev-pop);
  cursor: pointer;
  overflow: hidden;
}

/* Kept deliberately quiet: no rules between the head, body and foot. Spacing
   separates them, and the only weight on the card belongs to the controls a
   row actually carries. */
.pepper-teaser-head {
  align-items: center;
  display: flex;
  gap: var(--s-4);
  padding: 13px 16px 0;
}

/* The object's own picture, at the head of its card. Only the daily brief
   has one today; the card contract takes a same-origin path and the host
   builds the element, so this is styling, never a hole for applet markup. */
.pepper-teaser-image {
  aspect-ratio: 21 / 9;
  border-radius: var(--r-md) var(--r-md) 0 0;
  display: block;
  object-fit: cover;
  width: 100%;
}

/* The one sentence an applet would say if it could say only one. Display
   type, because for a brief the headline is the content — the rows beneath
   are supporting detail, which is the opposite of how a list card reads. */
.pepper-teaser-lead {
  color: var(--fg);
  font-family: var(--serif);
  font-size: 1.0625rem;
  line-height: 1.35;
  /* The card has no padding of its own — head, body and foot each carry
     their own 16px — so this has to match its siblings or it sits flush
     against the edge. */
  margin: 0;
  padding: var(--s-3) 16px 0;
}

.pepper-teaser-head .pepper-teaser-title {
  color: var(--fg-strong);
  font-family: var(--serif);
  font-size: 14px;
  font-weight: 600;
}

.pepper-teaser-head .pepper-chip {
  margin-left: auto;
}

.pepper-teaser-body {
  padding: var(--s-4) 16px var(--s-2);
}

.pepper-teaser-row {
  align-items: center;
  color: var(--fg-soft);
  cursor: default;
  display: flex;
  font-size: 13px;
  gap: var(--s-4);
  padding: var(--s-3) 0;
}

/* A row's own control, e.g. ticking a to-do off without opening the applet.
   Slightly smaller than the checklist's, since a card row is denser. */
.pepper-teaser-row .pepper-check {
  cursor: pointer;
  height: 16px;
  margin: 0;
  width: 16px;
}

.pepper-teaser-row.is-done .pepper-teaser-row-text {
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-color: var(--faint);
}

/* With two lines of text, a centred `meta` floats against the middle of the
   block; it belongs beside the name it qualifies. Scoped by `:has()` so
   every single-line row keeps centring. */
.pepper-teaser-row:has(.pepper-teaser-row-sub) {
  align-items: flex-start;
}

/* Holds the text and its optional second line. A row with no subtext is one
   line, exactly as before — the wrapper only gives the two-line case somewhere
   to stack. */
.pepper-teaser-row .pepper-teaser-row-main {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.pepper-teaser-row .pepper-teaser-row-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* An excerpt under the text, for a row whose name doesn't say what it is.
   Wraps to two lines and then clips: a card is a glance, so the cost of a
   long one is the whole card, not just this row. */
.pepper-teaser-row .pepper-teaser-row-sub {
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  color: var(--muted);
  display: -webkit-box;
  font-size: 11.5px;
  line-height: 1.4;
  overflow: hidden;
}

.pepper-teaser-row .pepper-teaser-row-meta {
  color: var(--muted);
  flex: none;
  font-size: 11.5px;
}

.pepper-teaser-foot {
  color: var(--muted);
  font-size: 12px;
  padding: 0 16px 13px;
}

/* The card's one real "open" control — a button, so it is focusable and
   announced, in the link accent so it reads as something you can act on
   rather than as more footer text. */
.pepper-teaser-open {
  background: none;
  border: 0;
  color: var(--primary-accent);
  cursor: pointer;
  font: inherit;
  padding: 0;
  text-align: left;
}

.pepper-teaser-open:hover {
  text-decoration: underline;
}

/* A card whose applet has not been read yet (`applets/_card_pending.html`).
   The applet index paints these, then swaps in real cards when the launcher
   payload lands, so what matters here is that the box is the size it will
   keep — a grid that reflows under the pointer is worse than one that waits. */
.pepper-applet-card.is-pending {
  /* Slightly receded, so a loaded card beside a loading one reads as the
     finished thing rather than the two competing for attention. */
  opacity: 0.75;
}

.pepper-teaser-pending {
  color: var(--faint);
}

.pepper-skeleton-line {
  background: linear-gradient(
    90deg,
    var(--chip) 0%,
    color-mix(in srgb, var(--chip) 45%, transparent) 50%,
    var(--chip) 100%
  );
  background-size: 200% 100%;
  border-radius: 4px;
  display: block;
  height: 9px;
  width: 100%;
  animation: pepper-skeleton 1.1s ease-in-out infinite;
}

.pepper-skeleton-line.is-short {
  width: 62%;
}

@keyframes pepper-skeleton {
  from {
    background-position: 200% 0;
  }

  to {
    background-position: -200% 0;
  }
}

/* The shimmer is decoration on a box that already holds its place without it. */
@media (prefers-reduced-motion: reduce) {
  .pepper-skeleton-line {
    animation: none;
  }
}

/* The expanded applet fills the surface it opened in — no inset, no scrim, no
   floating edges. An applet is somewhere you *are* inside Pepper, not a layer
   over somewhere else, and a bounded panel reads as a popup no matter how
   well it is styled. Cohesion comes from sharing the app's background, type
   and chrome rather than from a frame drawn around it.

   `bottom` is set by the host at runtime: the composer keeps its place at the
   foot of the surface and the applet occupies everything above it. An applet
   that covered the composer would be a page (patterns/chat.md). */
.pepper-applet-stage {
  background: var(--bg);
  inset: 0;
  overflow: hidden;
  position: absolute;
  transform-origin: top left;
  z-index: 32;
}

.pepper-applet-frame {
  border: 0;
  display: block;
  height: 100%;
  width: 100%;
}

.pepper-applet-close {
  align-items: center;
  background: var(--chip);
  border: 1px solid var(--border);
  border-radius: 50%;
  box-shadow: var(--elev-menu);
  color: var(--fg);
  cursor: pointer;
  display: flex;
  height: 34px;
  justify-content: center;
  position: absolute;
  right: 13px;
  top: 13px;
  width: 34px;
  z-index: 33;
}

.pepper-applet-close:hover {
  background: var(--card-hover);
}

/* A picture open inside the framed applet covers the stage, and the lightbox's
   own close lands in exactly this spot — one document lower, so this one wins
   the click and collapses the applet instead. Standing down for as long as the
   picture is up leaves one "✕" there, and it closes the picture. The applet
   host sets this from the frame's own message; see `static/applet.js`. */
.pepper-applet-stage.has-lightbox .pepper-applet-close {
  display: none;
}

/* Expanding animates the card's own box growing into the stage (and back on
   collapse), so the surface reads as the card opening rather than a new page
   arriving. The host drives it from the card's measured geometry; there is no
   keyframe here because the start position is only known at runtime. */

/* --------------------------------------------- Applet page navigation */

/* An applet is a place inside Pepper, and every other place has the navigation
   overlay one press away. An applet page had nothing: standalone it carried
   only whatever back link it happened to author, and framed it had none at all
   — the stage covers the shell's own trigger. So every applet page now carries
   its own, injected at serve time by `pepper.web.apps.router` so a
   model-generated page gets it as surely as a bundled one.

   The wrapper is the positioned frame the scrim and panel resolve against.
   Both are `position: absolute`, and on a page with no shell around them the
   containing block would be the document — an overlay as tall as the content
   that scrolls away from the button that opened it. Fixed, so it is the
   viewport in every case.

   `pointer-events` is off on the frame and back on for the two things that are
   really there, because a transparent box over the whole page would otherwise
   swallow every click meant for the applet. `clip` keeps the closed panel's
   off-screen transform from widening the page. */
.pepper-appnav {
  bottom: 0;
  left: 0;
  overflow: clip;
  pointer-events: none;
  position: fixed;
  right: 0;
  top: 0;
  z-index: 60;
}

.pepper-appnav > .pepper-appnav-btn,
.pepper-appnav > .pepper-nav-panel {
  pointer-events: auto;
}

/* The trigger is `.pepper-applet-close` mirrored to the leading corner: the
   same floating chip, so a framed applet's two affordances read as one set,
   and the same leading-hamburger / trailing-affordance arrangement
   `.pepper-app-header` gives every other surface. */
.pepper-appnav-btn {
  align-items: center;
  background: var(--chip);
  border: 1px solid var(--border);
  border-radius: 50%;
  box-shadow: var(--elev-menu);
  color: var(--fg);
  cursor: pointer;
  display: flex;
  height: 34px;
  justify-content: center;
  left: 13px;
  position: absolute;
  top: 13px;
  width: 34px;
}

.pepper-appnav-btn:hover {
  background: var(--card-hover);
}

/* Clearance for the floating trigger, on `body` rather than on
   `.pepper-applet`: a generated page is under no obligation to use the
   primitive, and `body` is the one box every page has. Scoped to pages that
   actually carry the nav so anything served without it is untouched, and
   `:has()` outranks a page's own `body { padding: 0 }` on specificity.

   A descendant, deliberately, not `> `. The nav is appended before `</body>`
   without touching the stack of elements the parser has open, so a generated
   page that left a `<div>` unclosed nests it under that div instead — and the
   direct-child form would then drop the clearance in exactly the case this
   whole feature exists for. The overlay itself is `position: fixed` and does
   not care how deep it sits.

   The bottom is the same clearance for the host's composer, and it is here
   for all of the reasons above plus one more: an applet page framed by chat
   is *not* always squeezed above the dock. The host raises the stage's floor
   only while the composer spans the foot of the surface (`spansTheFoot` in
   static/applet.js); parked as a pane on desktop it does not, and the card
   then floats over the frame's last rows. So the page keeps its own floor
   rather than trusting whatever is beneath it, which is also what makes a
   page opened standalone, in the iOS webview, or from an SMS link behave
   the same everywhere.

   Unconditional, not gated on being framed. The cost when there is no
   composer is a screen's worth of empty scroll past the end of a long page —
   invisible on a short one, since padding under content that already fits
   scrolls nothing. The cost of getting it wrong the other way is a control
   the user cannot click, which is how "I'm done for now" at the end of
   onboarding became unreachable. A rule that holds without JavaScript also
   holds for a *generated* page, which is the kind we cannot go and fix. */
body:has(.pepper-appnav) {
  padding-bottom: var(--composer-floor);
  padding-top: 48px;
}

/* The overlay's scroll lock. `hidden`, and deliberately not the `clip` the
   shell uses: on an applet page the body *is* the scroller, and a clipped box
   is not a scroll container at all — its offset resets to 0, so the page jumps
   to the top behind the panel and back on close. `hidden` keeps the box
   scrollable in principle, so the position survives. */
body.pepper-nav-locked:has(.pepper-appnav) {
  overflow: hidden;
}

/* ---------------------------------------------------- Applet run states */

/* Every applet hits the same three non-content states, so they are shared
   rather than re-invented per applet: nothing to show, still loading, and
   something went wrong. They are deliberately quiet — an applet that can't
   load is an operational fact, not an occasion for illustration. */

.pepper-applet-empty {
  color: var(--muted);
  font-size: 13.5px;
  padding: var(--s-11) var(--s-6);
  text-align: center;
}

.pepper-applet-error {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 32%, transparent);
  border-radius: var(--r-md);
  color: var(--danger);
  font-size: 13px;
  padding: 10px 14px;
}

/* Skeleton rows stand in for content while the applet's first fetch is in
   flight, so the surface doesn't jump when data lands. */
.pepper-applet-loading {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}

/* `hidden` has to keep working. This is the only run state that sets
   `display`, and a class selector outranks the UA stylesheet's
   `[hidden] { display: none }` — so without this, the obvious way to
   dismiss the skeleton (`el.hidden = true`) silently does nothing and the
   applet pulses forever over its own loaded content. */
.pepper-applet-loading[hidden] {
  display: none;
}

.pepper-applet-loading::before,
.pepper-applet-loading::after {
  animation: pepper-applet-pulse 1.4s ease-in-out infinite;
  background: var(--card-hover);
  border-radius: var(--r-md);
  content: "";
  height: 38px;
}

.pepper-applet-loading::after {
  animation-delay: 0.18s;
  opacity: 0.6;
}

@keyframes pepper-applet-pulse {
  50% {
    opacity: 0.45;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* The brief card is no longer among these: it has no collapsed state left
     to animate between, so there is no transition to switch off. */
  .pepper-suggest-drawer,
  .pepper-nav-scrim,
  .pepper-nav-panel {
    transition: none;
  }

  /* The expand/collapse animation is skipped in the host script rather than
     here — it is driven from measured geometry, so there is no declaration to
     switch off. */
  .pepper-applet-loading::before,
  .pepper-applet-loading::after {
    animation: none;
  }
}

/* ---------------------------------------------------------------- hidden */

/* `hidden` wins. The one `!important` in this stylesheet, and the reason is
   that it is repairing a rule that should never have been losing.
   `[hidden] { display: none }` comes from the UA stylesheet, so *any* class
   selector setting `display` outranks it — which is most of the primitives
   above, and most of what a page authors for itself. The result is that
   `el.hidden = true`, the obvious way to dismiss something, silently does
   nothing: no console message, no failing selector, just an element that
   stays on screen.

   Every surface that has met this has arrived at exactly this rule. The
   applet loading skeleton pulsed forever over its own loaded content; three
   applets shipped with a live instance (an empty pill, an empty filter row,
   an "+ Add" offered while disconnected, both steps of an unfollow control at
   once); and all seven bundled applet pages ended up carrying a page-local
   copy of the line below. It belongs here once instead.

   Scoped deliberately wide — no `.pepper-` prefix. An applet page is a
   standalone document that loads this stylesheet and then authors its own
   classes, so a guard covering only these primitives would leave every page
   still writing the same rule for itself. The cost is that "hidden but shown
   by a more specific rule" stops being expressible; that is not a thing worth
   expressing, and `hidden` meaning hidden is worth more. */
[hidden] {
  display: none !important;
}
