/* TrendCanvas — app.css
   Layout + components: nav, placard card, chips, tables, buttons, forms,
   empty-state, focus/motion rules. Tokens come from tokens.css. */

/* ---- reset ---- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.5;
}
img { max-width: 100%; display: block; }
a { color: var(--ultra); }
a:hover { color: var(--ultra-deep); }

/* ---- motion / focus (quality floor) ---- */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
:focus-visible {
  outline: 2px solid var(--ultra);
  outline-offset: 2px;
}

/* ---- type scale ---- */
h1, .page-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 28px;
  margin: 0 0 1rem;
}
h2, .section-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 20px;
  /* Heading rhythm (craft-floor): more space above a heading than below --
     2rem top separates a section from whatever precedes it, 0.75rem bottom
     keeps the heading close to its own content. Adjacent-sibling/first-child
     margin collapsing means this doesn't stack with a section wrapper's own
     margin-top (e.g. `.risers-section`), it just takes the larger value. */
  margin: 2rem 0 0.75rem;
}
.data {
  font-family: var(--font-data);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* ---- layout ---- */
.page {
  max-width: 1200px;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

/* ---- nav ---- */
.topnav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0.9rem 1.25rem;
  border-bottom: 1px solid var(--line);
  background: var(--card);
  overflow-x: auto;
  white-space: nowrap;
}
.topnav .wordmark {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 19px;
  color: var(--ink);
  text-decoration: none;
  flex-shrink: 0;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.topnav .wordmark:hover { color: var(--ink); }
.topnav-links {
  list-style: none;
  display: flex;
  gap: 1.25rem;
  margin: 0;
  padding: 0;
}
.topnav-links li { flex-shrink: 0; }
.topnav-links a {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 14px;
  padding: 0.35rem 0.1rem;
  border-bottom: 2px solid transparent;
  display: inline-block;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.topnav-links a:hover { color: var(--ink); }
.topnav-links a.active {
  color: var(--ink);
  border-bottom-color: var(--ultra);
  font-weight: 500;
}

/* ---- flash region ---- */
.flash-region:empty { display: none; }
.flash-region {
  max-width: 1200px;
  margin: 1rem auto 0;
  padding: 0 1.25rem;
}
.flash {
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--mat);
  font-size: 14px;
  margin-bottom: 0.75rem;
}
.flash.flash-error { border-color: var(--red-dot); }
.flash.flash-warn { border-color: var(--warn); }

/* ---- buttons ---- */
.btn {
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 500;
  padding: 0.5rem 0.9rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
  min-height: 44px;
}
.btn:hover { box-shadow: 0 1px 4px rgba(35, 38, 46, 0.12); }
/* In-flight / double-submit state: `hx-disabled-elt` on the import/
   regenerate forms sets the `disabled` attribute on their submit button for
   the duration of the request (preventing a second click), and htmx adds
   `.htmx-request` to the requesting form itself -- dim whichever one is
   visible so the in-flight state is never silent. No spinner, no motion. */
.btn:disabled, .btn[disabled] { opacity: .55; cursor: default; }
.htmx-request .btn[type=submit], .btn.htmx-request { opacity: .55; }
.btn-primary {
  background: var(--ultra);
  border-color: var(--ultra);
  color: #fff;
  /* Primary is the one button weight that carries real visual weight
     (it's each card/form's single main action) -- a touch more breathing
     room than the shared `.btn` box distinguishes it from secondary/quiet
     without adding a second size scale. */
  padding: 0.55rem 1.1rem;
}
.btn-primary:hover {
  /* Color-only feedback, no transition -- the only motion in the system
     stays the 120ms card hover lift (see `.card, .placard` below). */
  background: var(--ultra-deep);
  border-color: var(--ultra-deep);
}
/* Secondary in-card action: demotes what would otherwise be a full-width
   primary-blue button (e.g. "Promote to creation" repeated across a grid
   of mover cards) to quiet chrome -- boldness stays reserved for the
   placard + red dot, not a wall of blue buttons. */
.btn-secondary {
  background: transparent;
  border-color: var(--line);
  color: var(--ink);
}
.btn-secondary:hover {
  background: var(--ultra-tint);
  border-color: var(--ultra);
  color: var(--ultra);
}
/* Quiet tertiary action: no border/fill at rest, ink-soft text, underline
   on hover -- for actions that are real `<button>`/`<a class="btn">`
   elements but shouldn't compete with a card's primary action (ideas
   Dismiss/Save/Restore, creations "Copy prompt"/"Save notes"/"Link shop
   listing", the movers "Copy prompt" affordance, the ideas "In creations"
   pseudo-link). Denser padding than the shared `.btn` box since it carries
   no border/fill to visually anchor a larger hit area; min-height stays
   the shared 44px tap-target floor. */
.btn-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--ink-soft);
  padding: 0.35rem 0.6rem;
}
.btn-quiet:hover {
  color: var(--ink);
  text-decoration: underline;
  box-shadow: none;
}
/* Same ink-soft/underline treatment as `.btn-quiet`, for affordances that
   are plain inline text (not a `<button>`/`<a class="btn">`) sitting inside
   other clickable chrome -- the creations card's "Add notes"/"Edit" toggle
   text nested inside a `<summary>` (the `<summary>` itself is the 44px tap
   target; the span is a label, not a second target) and the "Link shop
   listing" submit button's `<label>` text. */
.link-affordance {
  color: var(--ink-soft);
  text-decoration: none;
}
.link-affordance:hover {
  color: var(--ink);
  text-decoration: underline;
}

/* ---- forms ---- */
label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 14px;
  color: var(--ink-soft);
}
input, select, textarea {
  font-family: var(--font-ui);
  font-size: 15px;
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  min-height: 44px;
}
input:focus, select:focus, textarea:focus { border-color: var(--ultra); }
/* Craft-floor: placeholder text must hit >= 4.5:1 contrast -- the browser
   default placeholder gray falls short of that on `--card`/`--paper`. */
::placeholder { color: var(--ink-soft); opacity: 1; }
form { display: flex; flex-direction: column; gap: 1rem; }

/* ---- placard card (gallery signature) ---- */
.card, .placard {
  background: var(--card);
  border: 1px solid var(--ink);
  border-radius: var(--radius-card);
  overflow: hidden;
}
.card:hover, .placard:hover {
  box-shadow: 0 4px 14px rgba(35, 38, 46, 0.14);
  transform: translateY(-2px);
}
/* Quality floor: the ONLY motion in the system is this 120ms card hover
   lift + shadow, and only for users who haven't asked to reduce motion —
   the transition itself lives inside the media query rather than being
   applied unconditionally and then cancelled, so nothing here can animate
   under prefers-reduced-motion: reduce. */
@media (prefers-reduced-motion: no-preference) {
  .card, .placard {
    transition: box-shadow 120ms ease, transform 120ms ease;
  }
}
.placard-mat {
  background: var(--mat);
  padding: 10px;
}
.placard-mat img {
  border-radius: 2px;
  aspect-ratio: 5 / 4;
  object-fit: cover;
  width: 100%;
}
.placard-label {
  padding: 0.65rem 0.75rem 0.85rem;
}
.placard-title {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 15px;
  margin: 0 0 0.25rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.placard-meta {
  color: var(--ink-soft);
  font-size: 13px;
  margin: 0 0 0.35rem;
}
.placard-price {
  font-family: var(--font-data);
  font-size: 13px;
}

/* ---- red dot (breakout signature) ---- */
.red-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--red-dot);
  vertical-align: middle;
}

/* ---- chips ---- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--mat);
  color: var(--ink-soft);
  font-size: 12px;
}
.chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
/* Status-tab counts (e.g. "All 6", "New 2") -- an explicit margin rather
   than relying on inter-element whitespace, since a flex/inline-flex
   layout (`.tab` is `inline-flex`) doesn't reliably preserve a bare text
   space between a text node and the next flex item, which glued the
   label and count together ("All6"). */
.chip-count {
  margin-left: 0.35rem;
  font-family: var(--font-data);
  color: var(--ink-soft);
}

/* ---- status (icon + label, never color alone) ---- */
.status { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 13px; }
.status-ok { color: var(--ok); }
.status-warn { color: var(--ochre-deep); }
.status-critical { color: var(--red-dot); }
.status-muted { color: var(--ink-soft); }

/* ---- deltas (data font, arrow ▲/▼) ---- */
.delta { font-family: var(--font-data); font-size: 13px; }
.delta.up { color: var(--ink); }
.delta.down { color: var(--ink); }
.delta.na { color: var(--ink-soft); }
.delta.flat { color: var(--ink-soft); }

/* ---- card grid ---- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
}

/* ---- tables ---- */
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 0.55rem 0.65rem; border-bottom: 1px solid var(--line); }
thead th { color: var(--ink-soft); font-weight: 500; font-size: 13px; }
tbody tr:nth-child(even) { background: var(--mat); }

/* ---- quiet links (placard titles, data tables, creation cards) ----
   Boldness lives only in the placard + red dot -- a raw blue underlined
   `<a>` inside a mover card title or a riser/store/creations table row
   competes with the artwork instead of receding into the chrome. `:not(.btn)`
   keeps this from touching real buttons (e.g. the idea card's "In creations"
   pseudo-link), and nav links are untouched since they live outside all
   three selectors. `:focus-visible` keeps the global outline regardless. */
.placard a:not(.btn),
.riser-table a:not(.btn),
table a:not(.btn),
.creation-card a:not(.btn) {
  color: var(--ink);
  text-decoration: none;
}
.placard a:not(.btn):hover,
.riser-table a:not(.btn):hover,
table a:not(.btn):hover,
.creation-card a:not(.btn):hover {
  color: var(--ultra);
  text-decoration: underline;
}

/* ---- empty state ---- */
.empty-state {
  border: 1px dashed var(--line);
  border-radius: var(--radius-card);
  padding: 2rem 1.5rem;
  text-align: center;
  color: var(--ink-soft);
}

/* ---- login page ---- */
.login {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  gap: 1rem;
  padding: 1.5rem;
}
.login h1 { font-family: var(--font-display); }
.login-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 280px;
  max-width: 100%;
}
.login .error { color: var(--red-dot); margin: 0; font-size: 14px; }

/* ---- a11y ---- */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---- tabs (category / window switches) ---- */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 1rem;
}
.tab {
  font-size: 14px;
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.tab:hover { color: var(--ink); border-color: var(--ink-soft); }
/* Selected state (active category tab, active window chip, active ideas
   status/filter chip -- the only three places `.tab` renders): ultra tint
   fill + ultra text/border, replacing the earlier gray/ink "selected"
   look so the selected state reads as the same accent family as the
   primary button, not a separate gray language. */
.tab.active {
  color: var(--ultra);
  background: var(--ultra-tint);
  border-color: var(--ultra);
  font-weight: 500;
}
.window-tabs { margin-bottom: 1.5rem; }

/* ---- weekly digest (Today, above tabs + every riser section) ----
   3px top border in --ultra, matching the /ai kind-rule pattern
   (`.ai-card-hue-*` above) -- never a left/right border, per the craft
   floor. `.section-title` normally carries a 2rem top margin (heading
   rhythm) that reads as unwanted whitespace once it's the first thing
   inside a card, so it's zeroed the same way `.section-header-row
   .section-title` already does. */
.digest-card { border-top: 3px solid var(--ultra); margin-bottom: 1.5rem; }
.digest-title { margin: 0 0 0.5rem; }
.digest-counts { margin: 0 0 0.5rem; }
.digest-learnings {
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
  font-size: 14px;
  color: var(--ink-soft);
}
.digest-learnings li { margin: 0.15rem 0; }
.digest-link { padding-left: 0; padding-right: 0; }

/* ---- mover card (gallery placard) internals ---- */
.mover-card { position: relative; }
.placard-mat { position: relative; }
.placard-mat .red-dot {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 1;
}
.mover-scoreline {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 0.4rem;
}
.score-badge {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 16px;
}
.mover-scoreline .rank { color: var(--ink-soft); }
.deltas { align-items: center; margin-bottom: 0.5rem; }
.series-chip {
  width: 8px;
  height: 8px;
  display: inline-block;
  border-radius: 1px;
}
.series-ultra { background: var(--ultra); }
.series-ochre { background: var(--ochre); }
.series-viridian { background: var(--viridian); }
.sparkline-wrap { margin: 0.4rem 0; line-height: 0; }
.collecting-chip, .dampened-caption { display: block; margin: 0.35rem 0; width: fit-content; }
.dampened-caption { color: var(--ink-soft); font-size: 12px; border: none; padding: 0; background: none; }
.mover-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.6rem;
}
.promote-form { margin: 0; }
.promote-form .btn { width: 100%; }

/* ---- risers ---- */
.risers-section { margin-top: 2rem; }

/* ---- filter row (archive; dataviz "one row, above the content" rule) ---- */
.filter-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem 1rem;
  margin-bottom: 1.5rem;
}
.filter-row label { flex: 1 1 140px; min-width: 120px; }
.filter-row .btn { flex: 0 0 auto; }

/* ---- mini placard grid (archive results, similar-listings) ---- */
.mini-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.mini-placard { text-decoration: none; color: inherit; display: block; }
.archive-date { color: var(--ink-soft); margin-left: 0.5rem; }
.capped-note { color: var(--ink-soft); font-size: 13px; margin-top: 0.75rem; }

/* ---- listing detail ---- */
.listing-header {
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
  /* Vertically center the text column against the image rather than
     top-aligning it, which otherwise strands the title/meta/scores block
     at the top of a mostly-empty white area next to a taller image. */
  align-items: center;
}
.listing-info .page-title { margin-bottom: 0.35rem; }
.listing-info .page-title a { color: var(--ink); text-decoration: none; }
.listing-info .page-title a:hover { color: var(--ultra); }
.window-scores { margin-top: 0.5rem; }
.window-score-chip { gap: 0.4rem; }
.window-score-chip .rank { color: var(--ink-soft); }

.flags-row { margin-bottom: 1.5rem; }
.flag-breakout, .flag-dampened, .flag-suppressed {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.history-section, .prompts-section, .tags-section, .similar-section {
  margin-bottom: 2rem;
}
.chart-wrap { width: 100%; height: 320px; }

.prompt-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.prompt-item {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}
.prompt-text { flex: 1 1 260px; margin: 0; }

.tag-group { margin-bottom: 0.6rem; }
.tag-group-label {
  display: block;
  font-size: 12px;
  color: var(--ink-soft);
  margin-bottom: 0.3rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

@media (max-width: 390px) {
  .listing-header { grid-template-columns: 1fr; }
  .filter-row { flex-direction: column; align-items: stretch; }
  .filter-row label, .filter-row .btn { flex: 1 1 auto; }
}

/* ---- store page ---- */
.store-summary { margin-bottom: 1.25rem; }
.sparkline-cell { line-height: 0; }
.state-chip { text-transform: capitalize; }
.na { color: var(--ink-soft); }

/* ---- creations board ---- */
.board-grid {
  display: grid;
  /* 170px, not 220px: `.page` caps at 1200px width (1160px of content once
     its own padding is subtracted), and 6 columns of 220px+gaps (1400px)
     can never fit inside that regardless of viewport — it silently
     overflowed the page horizontally at every width from 701px up. 170px
     columns (6*170 + 5*16px gaps = 1100px) fit with room to spare. */
  grid-template-columns: repeat(6, minmax(170px, 1fr));
  gap: 1rem;
  align-items: start;
}
.board-column { min-width: 0; }
.board-cards { display: flex; flex-direction: column; gap: 0.85rem; }
.board-empty { color: var(--ink-soft); font-size: 13px; margin: 0; }
.creation-card { padding: 0; }
.creation-card .placard-label { display: flex; flex-direction: column; gap: 0.55rem; }
/* Learning-loop verdict chip (app.trends.learning verdicts, distinct from
   the manual winner/loser `.status` badge above it): tint background +
   icon+label, icon color set to `currentColor` so one rule per verdict
   covers both the background and the arrow glyph's color. `align-self:
   flex-start` matches every other chip sitting in a flex-column
   `.placard-label` (see `.event-category-chip`'s comment). */
.verdict-chip { align-self: flex-start; border-color: transparent; }
.verdict-chip span[aria-hidden] { color: currentColor; }
.verdict-outperforming { background: var(--viridian-tint); color: var(--viridian-deep); }
.verdict-average { background: var(--mat); color: var(--ink-soft); }
.verdict-underperforming { background: var(--ochre-tint); color: var(--ochre-deep); }
.verdict-no-baseline { background: var(--mat); color: var(--ink-soft); }
.source-chip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--ink);
  background: var(--mat);
  min-height: 44px;
  /* Overrides `.chip`'s 999px pill radius: multi-line caption text wraps
     to two lines here, and a fully-rounded container around wrapped text
     renders as a gray egg/capsule rather than a chip. */
  border-radius: var(--radius-card);
}
/* Fixed square so source-listing thumbs (creations board, similar-past
   grids) never render squished into an oval by a non-5:4 source image --
   the placard mat's `aspect-ratio: 5/4` only applies inside `.placard-mat`,
   not here. */
.mini-thumb { width: 44px; height: 44px; object-fit: cover; border-radius: var(--radius); flex-shrink: 0; }
.prompt-details, .notes-details { font-size: 13px; }
/* Every <details> disclosure in the app is a tap target (prompt/notes
   toggles, the system page's error toggle) — one shared rule on the bare
   `summary` element keeps all of them at the 44px floor, including any
   future <details> a template adds, rather than an allowlist of classes
   that a new summary can slip past unstyled. */
summary {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
}
/* Disclosure caret: one shared rule for every `<summary>` in the app --
   ▸ rotating to ▾ via `[open]`, CSS-only, ink-soft. Previously only the AI
   page's "Preview bundle" toggle (`.bundle-details`, below) had this; the
   ideas "MJ prompt" and creations prompt/notes summaries read as dead text
   with no affordance at all. `::-webkit-details-marker` hides Safari's
   native triangle, which renders regardless of `summary`'s `display` value. */
summary::-webkit-details-marker { display: none; }
summary::before {
  content: "▸";
  display: inline-block;
  width: 1em;
  flex-shrink: 0;
  margin-right: 0.3rem;
  color: var(--ink-soft);
}
details[open] > summary::before { content: "▾"; }
/* "MJ prompt" toggle: a quiet ink-soft disclosure affordance, not a bold
   link -- the notes toggle is handled separately below since its summary
   also carries the notes VALUE, which must stay ink (not link-styled). */
.prompt-details summary {
  color: var(--ink-soft);
  text-decoration: none;
}
.prompt-details summary:hover {
  color: var(--ink);
  text-decoration: underline;
}
.prompt-details .prompt-text { margin: 0.5rem 0; white-space: pre-wrap; }
/* Notes value renders as plain ink text (it's content, not a link); only
   the "Add notes"/"Edit" affordance (`.link-affordance`, shared with the
   "Link shop listing" button) is the quiet ink-soft link. */
.notes-value { color: var(--ink); }
.link-form { gap: 0.5rem; }
.link-form .btn { width: 100%; }
.notes-details form { gap: 0.5rem; margin-top: 0.5rem; }
.outcome-reason { color: var(--ink-soft); font-size: 13px; margin: 0; }
.creation-actions { flex-direction: row; }
.creation-actions form { flex: 1 1 auto; margin: 0; }
.creation-actions .btn { width: 100%; }

@media (max-width: 700px) {
  .board-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- ideas page ---- */
.idea-list { display: grid; grid-template-columns: 1fr; gap: 1rem; max-width: 720px; }
@media (min-width: 900px) {
  /* Two columns above 900px. Cards are no longer forced to equal height
     (see `.idea-card` below), so rows size to each card's own content. */
  .idea-list { grid-template-columns: 1fr 1fr; gap: 20px; max-width: none; }
}
.idea-card { display: flex; flex-direction: column; }
.idea-card .placard-label { display: flex; flex-direction: column; }
/* Title connects to the gallery-placard language (display font) rather
   than the generic UI-font `.placard-title` this card also carries, since
   an idea card fronts a concept, not a listing photo. */
.idea-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 17px;
}
.idea-rationale { font-size: 14px; }
.idea-status-chip {
  border-color: transparent;
  align-self: flex-start;
  margin: 0 0 0.5rem;
}
.idea-status-chip .status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  display: inline-block;
}
.idea-status-saved { background: var(--viridian-tint); color: var(--viridian-deep); }
.idea-status-saved .status-dot { background: var(--viridian); }
.idea-status-dismissed { background: var(--mat); color: var(--ink-soft); }
.idea-status-dismissed .status-dot { background: var(--ink-soft); }
.idea-status-promoted { background: var(--ochre-tint); color: var(--ochre-deep); }
.idea-status-promoted .status-dot { background: var(--ochre); }
/* Signal chips: tint fill + a small ultra dot standing in for the icon
   half of "icon+label", data font since these name raw signal keys. */
.signal-chip {
  background: var(--ultra-tint);
  border-color: transparent;
  font-family: var(--font-data);
  font-size: 12px;
}
.signal-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ultra);
  display: inline-block;
}
/* MJ prompt: a quiet mono preview row (mat background, 1-line clamp) with
   a Copy button at the right, replacing the old bare-link-styled prompt
   text -- the `<details>` disclosure it sits inside is unchanged. */
.mono-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--mat);
  border-radius: var(--radius);
  padding: 0.5rem 0.65rem;
  margin: 0.5rem 0 0;
}
.mono-row-text {
  font-family: var(--font-data);
  font-size: 12px;
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mono-row .btn { flex-shrink: 0; }
/* Actions row: right-aligned, sitting naturally after the card's content
   with normal spacing. This used to be `margin-top: auto` on a card
   stretched to `height: 100%` (grid `align-items: stretch` matches every
   card in a row to its tallest sibling), which pinned the actions to the
   card's bottom edge -- on any card with little content (promoted/dismissed
   cards especially) that reserved a tall empty band above the row, worst on
   mobile's single-column list where the wasted space is most visible.
   `justify-content: flex-end` + content-width buttons replace the old
   full-width-button layout `.mover-actions` sets, which ideas cards no
   longer want. Promote keeps `.btn-primary`'s full styling; Dismiss/Save/
   Restore are `.btn-quiet` (see button-scale rules above). */
.idea-actions {
  flex-direction: row;
  justify-content: flex-end;
  margin-top: 1rem;
}
.idea-actions form { flex: 0 0 auto; margin: 0; }
.idea-actions .btn { width: auto; }
.status-tabs { margin-bottom: 1.5rem; }

/* ---- seasonal page ---- */
.events-section { margin-bottom: 2rem; }
.event-list { display: flex; flex-direction: column; gap: 0.85rem; max-width: 640px; }
.event-card .placard-label { display: flex; flex-direction: column; gap: 0.4rem; }
/* Category chip: a bare `.chip` sitting directly in the flex-column
   `.placard-label` above inherits the column's default `align-items:
   stretch` and stretches to the card's full width, rendering as a gray bar
   instead of a pill -- `align-self: flex-start` shrinks it back to
   fit-content, matching every other chip in the app (e.g. `.idea-status-chip`
   below, which sits in the same kind of flex-column label). */
.event-category-chip { align-self: flex-start; }

/* Observations (Phase 7 Task 5): "This week's tags" / "Last year this
   week", one subsection per active category. Tag chips reuse `.chip`/
   `.signal-chip`/`.signal-dot`/`.chip-count` exactly as-is (no new chip
   colors introduced here) -- only new rules are typography for headings
   the base type scale doesn't cover, using existing --ink/--ink-soft
   tokens. `.observations-subtitle` shares `.section-title`'s selector
   specificity (single class each); defined after it in source order so it
   wins the font-size override without needing `!important`. */
.observation-block { margin-bottom: 1.5rem; }
.observations-subtitle { font-size: 16px; margin: 1rem 0 0.5rem; }
.observation-category { margin-bottom: 0.85rem; }
.observation-category h4 {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 13px;
  color: var(--ink-soft);
  margin: 0 0 0.4rem;
}

/* ---- system page ---- */
.jobs-section, .budget-section, .llm-section { margin-bottom: 2rem; }
.jobs-section details { display: inline-block; }
.jobs-section pre {
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--font-data);
  font-size: 12px;
  margin: 0.4rem 0 0;
}
.budget-bar {
  background: var(--mat);
  border-radius: 999px;
  height: 8px;
  overflow: hidden;
  margin-top: 0.5rem;
  max-width: 320px;
}
.budget-bar-fill { height: 100%; background: var(--ultra); }
.budget-bar-fill.critical { background: var(--red-dot); }

/* ---- responsive ---- */
@media (max-width: 390px) {
  .page { padding: 1rem 0.85rem 3rem; }
  .card-grid { grid-template-columns: 1fr; }
  table, thead, tbody, th, td, tr { display: block; }
  thead { display: none; }
  tbody tr {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin-bottom: 0.75rem;
    padding: 0.25rem 0;
  }
  tbody td {
    border-bottom: none;
    display: flex;
    justify-content: space-between;
    gap: 1rem;
  }
  tbody td::before {
    content: attr(data-label);
    color: var(--ink-soft);
    font-size: 12px;
  }
}

/* ---- ai page ---- */
/* Explainer line under an h1 -- ink-soft body text, not a placard-meta
   (which is scoped to card metadata), so a page-level one-liner has its
   own name. */
.page-lede { color: var(--ink-soft); font-size: 15px; margin: -0.5rem 0 1.5rem; }
.section-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  /* The row carries the shared heading rhythm's 2rem top margin itself,
     since the `.section-title` inside it is zeroed out below (a margin on
     the h2 would throw off the row's flex `align-items: center` against the
     "Regenerate bundles" button). */
  margin: 2rem 0 1rem;
}
.section-header-row .section-title { margin: 0; }
.section-header-row form { flex: 0 0 auto; }
.ai-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 1rem;
}
/* Kind title: display font per the design system ("kind title, display
   font, small") -- distinct from `.placard-title` (UI font, used for
   listing titles elsewhere) since this heads a bundle card, not a
   listing. */
.ai-card-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 17px;
  margin: 0;
}
/* The three cards are a sequence, not three unrelated panels: a step
   marker + per-kind hue (tagging = viridian, MJ prompts = ultra,
   brainstorm = ochre, matching `KINDS` order in app/web/routes/ai.py)
   ties title, marker, and the card's top border into one quiet identity
   per kind -- structural accent use of the palette's secondary pigments,
   not a color spread. */
.ai-card-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.5rem;
}
.ai-step-marker {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 14px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ai-step-viridian { background: var(--viridian-tint); color: var(--viridian-deep); }
.ai-step-ultra { background: var(--ultra-tint); color: var(--ultra); }
.ai-step-ochre { background: var(--ochre-tint); color: var(--ochre-deep); }
.ai-card-hue-viridian { border-top: 3px solid var(--viridian); }
.ai-card-hue-ultra { border-top: 3px solid var(--ultra); }
.ai-card-hue-ochre { border-top: 3px solid var(--ochre); }
.ai-card-actions { margin: 0.5rem 0; }
.bundle-preview {
  font-family: var(--font-data);
  font-size: 12px;
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--mat);
  border-radius: var(--radius);
  padding: 0.75rem;
  margin: 0.5rem 0 0;
  max-height: 260px;
  overflow-y: auto;
}
/* "Preview bundle" disclosure text color: a quiet ink-soft label, not a
   bold link. The caret itself is the shared `summary::before` rule above --
   this only sets the text color/hover for this specific summary. */
.bundle-details > summary { color: var(--ink-soft); }
.bundle-details > summary:hover { color: var(--ink); text-decoration: underline; }
.ai-import-form { margin-top: 0.75rem; gap: 0.5rem; }
.ai-import-result { margin-top: 0.5rem; }
.ai-skipped-list {
  list-style: none;
  margin: 0.35rem 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.ai-skipped-list li { font-size: 13px; }
