/* job-scraper — "Quiet Modern / Quiet Night"
   Pretendard sans, JetBrains Mono numerals, warm gold accent.
   Light + dark via CSS color-scheme + light-dark(). Override via
   html[data-theme="light|dark"] from the theme toggle. */

/* Vendored fonts — shipped inside the binary so the app renders correctly
   offline. Paths are relative to this file's URL (/static/styles.css). */
@font-face {
  font-family: "Pretendard Variable";
  font-weight: 45 920;
  font-style: normal;
  font-display: swap;
  src: url("vendor/fonts/PretendardVariable.woff2") format("woff2-variations");
}
@font-face {
  font-family: "JetBrains Mono";
  font-weight: 300;
  font-style: normal;
  font-display: swap;
  src: url("vendor/fonts/JetBrainsMono-Light.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url("vendor/fonts/JetBrainsMono-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  src: url("vendor/fonts/JetBrainsMono-Medium.woff2") format("woff2");
}

:root { color-scheme: light dark; }
html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"]  { color-scheme: dark; }

:root {
  --bg:          light-dark(#fbf7ef, #15120d);
  --ink:         light-dark(#14120c, #f0e8d6);
  --ink-soft:    light-dark(#7d7466, #8c8068);
  --accent:      light-dark(#b08838, #d4ad5a);   /* warm gold */
  --accent-soft: light-dark(#f1e6cc, #2e2616);
  --highlight:   light-dark(#c0552e, #d97a48);   /* deadline */
  --rule:        light-dark(#e3d8c1, #2b2618);

  --sans: "Pretendard Variable", Pretendard, -apple-system,
          BlinkMacSystemFont, "Apple SD Gothic Neo",
          "Malgun Gothic", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo,
          Consolas, monospace;
}

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 19px;
  line-height: 1.6;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
}

/* Desktop bump — mobile reads fine at 19px; on a desktop viewport the eye
   sits farther from the screen, so step everything up. Every other size
   in this stylesheet is in rem and inherits this scaling. */
@media (min-width: 640px) {
  body { font-size: 22px; }
}
.wrap { max-width: 900px; margin: 0 auto; padding: 3.5rem 1.5rem 4.5rem; }
a { color: inherit; }

/* Header */
header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  padding-bottom: 1.75rem;
  border-bottom: 1px solid var(--ink);
}
.eyebrow {
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  color: var(--ink-soft);
  text-transform: uppercase;
  font-weight: 500;
  margin: 0 0 0.45rem;
}
header h1 {
  font-weight: 700;
  font-size: 2.5rem;
  letter-spacing: -0.03em;
  margin: 0;
  line-height: 1;
}
.head-right {
  text-align: right;
  font-family: var(--mono);
  color: var(--ink-soft);
  font-size: 0.82rem;
  line-height: 1.7;
}
.head-right a {
  display: inline-block;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1.5px solid var(--accent);
  padding-bottom: 1px;
  font-family: var(--sans);
  font-weight: 500;
}
.theme-toggle {
  background: none;
  border: 0;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 0;
  margin-left: 0.7rem;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s ease;
}
.theme-toggle:hover { color: var(--accent); }
.theme-toggle svg { display: block; }

/* Primary nav — same four destinations on every page, so the user never
   has to learn a different layout when they switch screens. The active
   page gets a quiet gold underline; everything else stays muted. */
nav.primary {
  display: flex;
  gap: 1.6rem;
  padding: 1.1rem 0 0;
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 500;
  flex-wrap: wrap;
}
nav.primary a {
  color: var(--ink-soft);
  text-decoration: none;
  padding-bottom: 3px;
  border-bottom: 2px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
}
nav.primary a:hover { color: var(--ink); }
nav.primary a.active {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* Scrape row */
.scrape-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.3rem 0 1.5rem;
  border-bottom: 1px solid var(--rule);
}
.count {
  font-family: var(--mono);
  color: var(--ink-soft);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
}
.count strong { color: var(--ink); font-weight: 500; }
/* 전체 공고 sort toggle (날짜순 / 점수순) — a small segmented control on the right
   of the count row. The active state is server-rendered via a ?sort= query-param
   link (no JS), so it survives reload and is shareable. Calm gold for the active
   segment, matching the source pills. */
.archive-sort { display: inline-flex; gap: 0.25rem; flex: 0 0 auto; }
.sort-opt {
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.25rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  white-space: nowrap;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.sort-opt:hover { color: var(--ink); border-color: var(--ink-soft); }
.sort-opt.active { color: var(--ink); border-color: var(--accent); background: var(--accent-soft); }
.btn-primary {
  background: var(--ink);
  color: var(--bg);
  border: 0;
  padding: 0.65rem 1.5rem;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
  letter-spacing: 0.02em;
  transition: opacity 0.15s ease;
}
.btn-primary:hover { opacity: 0.9; }
.btn-primary:disabled { opacity: 0.4; cursor: default; }
.btn-primary .arrow { color: var(--accent); margin-left: 0.45rem; }

/* 재평가 — a secondary/ghost button (gold outline + text), never a second
   primary; the black 스크랩 시작 stays the single primary on /. has-stale lifts
   it to a gold attention treatment (filled, bolder) when visible rows were
   computed against a prior profile. Never terracotta (reserved for deadlines). */
.rerate-row {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  flex-wrap: wrap;
  padding: 0.9rem 0 0;
}
.btn-ghost {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 0.4rem 1rem;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 0.85rem;
  cursor: pointer;
  letter-spacing: 0.02em;
  transition: background 0.15s ease, opacity 0.15s ease;
}
.btn-ghost:hover { background: var(--accent-soft); }
.btn-ghost:disabled { opacity: 0.4; cursor: default; }
.btn-ghost:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-ghost.has-stale { background: var(--accent-soft); font-weight: 600; }
.rerate-hint { color: var(--ink-soft); font-size: 0.78rem; }
/* Persistent "N/M 분석됨" progress — counts the cache, not the chips, so it is
   honest about silent (no-signal) rows. Muted until the visible list is fully
   analyzed, then it settles to the accent color (calm "done", never a badge). */
.rerate-count {
  color: var(--ink-soft);
  font-size: 0.78rem;
  font-family: var(--mono);
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.rerate-count.complete { color: var(--accent); }

/* Streaming scrape log */
.scrape-log { margin: 0.6rem 0 0; }
.scrape-log:empty { margin: 0; }  /* keep symmetric spacing when no log yet */
.scrape-log p {
  margin: 0.2rem 0;
  color: var(--ink-soft);
  font-size: 0.82rem;
  font-family: var(--mono);
  letter-spacing: 0.01em;
}

/* Source-filter pills — appear above the posting list when the page has
   postings from 2+ sources. Filled in by web/source-filter.js. The bar
   stays empty (zero height) when JS chooses not to render pills. */
.source-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  padding: 0.9rem 0 0.2rem;
}
.source-filter:empty { display: none; }
.source-pill {
  background: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.25rem 0.8rem;
  font-family: var(--sans);
  font-size: 0.82rem;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
  flex: 0 0 auto;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.source-pill:hover { color: var(--ink); border-color: var(--ink-soft); }
.source-pill.on {
  color: var(--ink);
  border-color: var(--accent);
  background: var(--accent-soft);
}
/* Empty pill: the source exists but has nothing visible on this page.
   Still clickable — the user might want to confirm. Muted so the eye
   skips past it to the populated pills. */
.source-pill.pill-empty { opacity: 0.45; }
.source-pill.pill-empty.on {
  opacity: 1; /* once selected, no point dimming it */
  border-color: var(--ink-soft);
  background: transparent;
  color: var(--ink-soft);
}
.filter-hidden { display: none !important; }
.posting[hidden] { display: none !important; }

/* Visual seam between the aggregator pills (left) and the single-
   company-portal pills (right). 1px solid line, height matches the
   pill row, with margin so it reads as breathing space rather than
   a hard divider. Hidden via the inline flex layout when there are
   no companies to render. */
.source-pill-divider {
  flex: 0 0 auto;
  width: 1px;
  height: 1.1rem;
  background: var(--rule);
  margin: 0 0.45rem;
  align-self: center;
}

/* Company-portal pills carry a slightly lighter border-weight to
   reinforce the group separation visually. Keeps the calm aesthetic
   — no fill, no badge, just a quieter stroke. */
.source-pill--company {
  border-style: dashed;
}
.source-pill--company.on {
  border-style: solid;
}

/* Quiet message shown when the active filter selection produces zero
   visible postings — distinct from the page's own empty state, which
   fires when there are no postings at all. */
.source-filter-empty {
  margin: 1.8rem 0 0;
  padding: 1.4rem 0;
  text-align: center;
  color: var(--ink-soft);
  font-size: 0.95rem;
  letter-spacing: -0.005em;
}

/* Text search box — sits above the source pills on /, /archive, /bookmarks.
   A quiet rounded input; web/source-filter.js ANDs the query with the active
   source pill, so the two filters compose. */
.posting-search {
  display: block;
  width: 100%;
  max-width: 22rem;
  margin: 0.9rem 0 0;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  transition: border-color 0.15s ease;
}
.posting-search:focus { outline: none; border-color: var(--accent); }
.posting-search::placeholder { color: var(--ink-soft); }

/* Postings */
.postings { list-style: none; padding: 0; margin: 0; }
.posting {
  display: grid;
  grid-template-columns: 100px 1fr auto;
  gap: 1.5rem;
  /* Padding-top is intentionally smaller than padding-bottom: the score's
     font-metric ascent space (3.1rem / line-height: 1, ~8px of empty room
     above the digit glyphs) gets added to the top visual gap, while the
     chip at the bottom has no such whitespace. Trimming padding-top by
     ~8px makes the visual gaps match. */
  padding: 1rem 0 1.5rem;
  border-bottom: 1px solid var(--rule);
  align-items: start;
}
.score {
  font-family: var(--mono);
  font-weight: 300;
  font-size: 3.1rem;
  line-height: 1;
  color: var(--accent);
  letter-spacing: -0.05em;
}
.posting-body { min-width: 0; }
.posting-title {
  font-weight: 600;
  font-size: 1.08rem;
  margin: 0 0 0.3rem;
  letter-spacing: -0.015em;
  line-height: 1.4;
  color: var(--ink);
  text-decoration: none;
  display: inline-block;
  transition: color 0.15s ease;
}
.posting-title:hover { color: var(--accent); }
.posting-meta {
  color: var(--ink-soft);
  font-size: 0.85rem;
  margin: 0 0 0.55rem;
  display: flex;
  gap: 0.55rem;
  flex-wrap: wrap;
  align-items: center;
}
.posting-meta .sep { color: var(--rule); }

/* Score cell — stacks the score number with a small source-stamp under
   it. The stamp tells the reader where the posting came from (점핏 /
   랠릿 / 네이버 / 잡알리오); putting it under the score makes it read
   as a row-level "source", not as a property of the company. */
.score-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
}
.source-stamp {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}
/* Deadline badge — tiered by urgency so a far-off or rolling deadline doesn't
   shout in the same alarm color as one closing today (P6 calm thesis). The
   base holds the layout; the kind classes set the color + left accent bar. The
   2px transparent base border keeps every tier the same width, so the badge
   never causes a layout jump (e.g. "마감 D-2" vs "마감 정보 없음"). */
.deadline {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  border-left: 2px solid transparent;
  padding-left: 0.55rem;
  text-transform: uppercase;
}
.deadline-urgent { color: var(--highlight); border-left-color: var(--highlight); } /* 오늘 마감 · 마감 D-1~3 · 마감 */
.deadline-calm   { color: var(--accent);    border-left-color: var(--accent); }    /* 마감 D-4+ */
.deadline-open   { color: var(--ink-soft);  border-left-color: var(--ink-soft); }  /* 상시채용 */
.deadline-none   { color: var(--ink-soft); }                                       /* 마감 정보 없음 — faintest, no bar */
.chips { display: flex; gap: 0.35rem; flex-wrap: wrap; align-items: center; }
.chip {
  font-size: 0.73rem;
  background: var(--accent-soft);
  color: var(--ink);
  padding: 0.12rem 0.55rem;
  border-radius: 3px;
  letter-spacing: 0.01em;
}
.chip .v {
  color: var(--accent);
  font-family: var(--mono);
  font-weight: 500;
  margin-left: 0.2rem;
}
/* A negative AI delta reads muted — never terracotta (reserved for deadlines),
   so a demerit never shouts louder than a closing date. The minus sign is the
   real U+2212, supplied by the template. */
.chip .v.neg { color: var(--ink-soft); }

/* AI 분석 chip — the only clickable chip. Styled as a .chip but a real <button>
   so keyboard activation (Enter/Space) is free; self-identifies by its caret and
   focus ring. The cited quote in the popover is the signal, not chip chrome. */
.chip-ai-wrap { display: contents; }
.chip-ai {
  font: inherit;
  font-size: 0.73rem;
  letter-spacing: 0.01em;
  background: var(--accent-soft);
  color: var(--ink);
  border: none;
  border-radius: 3px;
  padding: 0.12rem 0.55rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}
.chip-ai .chip-caret {
  color: var(--ink-soft);
  font-size: 0.72em;
  margin-left: 0.3rem;
  transition: transform 0.15s ease;
}
.chip-ai[aria-expanded="true"] .chip-caret { transform: rotate(90deg); }
.chip-ai:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Stale: a delta computed against a prior profile. Faded, value still shown
   (it's in the Total), with a quiet "(이전 프로필 기준)" note. */
.chip-ai.is-stale { opacity: 0.6; }
.chip-stale-note { color: var(--ink-soft); margin-left: 0.3rem; font-size: 0.9em; }

/* Evidence popover — an inline disclosure, never a floating tooltip (a tooltip
   can't clip inside a 375px card). flex-basis:100% drops it onto its own full-
   width line below the chip row. */
.ai-evidence {
  flex-basis: 100%;
  width: 100%;
  margin-top: 0.4rem;
  padding: 0.5rem 0.7rem;
  background: var(--accent-soft);
  border-radius: 4px;
}
.ai-evidence-item {
  margin: 0.2rem 0;
  font-size: 0.75rem;
  display: flex;
  gap: 0.4rem;
  align-items: baseline;
  flex-wrap: wrap;
}
.ai-evidence-text { color: var(--ink); }
.ai-evidence-goal { color: var(--ink-soft); font-size: 0.92em; }
/* ≥44px tap target on mobile (the chip is the only touch control in the row). */
@media (max-width: 639px) {
  .chip-ai { min-height: 44px; padding-left: 0.7rem; padding-right: 0.7rem; }
}

/* Mobile tightening — at narrow widths the desktop's 100px score column
   plus a 1.5rem gap puts uncomfortable whitespace between the number and
   the posting title. Tighten both so the row reads as one unit. Score
   also shrinks since the column is narrower. */
@media (max-width: 639.98px) {
  .posting {
    grid-template-columns: 56px 1fr auto;
    gap: 0.55rem;
  }
  .score { font-size: 2.4rem; }
}

/* Archive page — day-grouped sections. The day header sits above each
   group's <ol> as a quiet temporal anchor, mirroring the eyebrow style. */
.archive-day { margin-top: 2.5rem; }
.archive-day:first-of-type { margin-top: 0.5rem; }
.day-header {
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.18em;
  color: var(--ink-soft);
  text-transform: uppercase;
  margin: 0;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--rule);
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
}
.day-today {
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  color: var(--accent);
  text-transform: none;
}

/* Dim styling for dealbreaker-excluded rows shown inline (bookmarks /
   archive pages). The dashboard's own excluded list lives inside
   .excluded-box and uses its own rules. */
.posting-dim { opacity: 0.5; }
.posting-dim .score-dim {
  color: var(--ink-soft);
  font-size: 2rem;
  padding-top: 0.5rem;
}

/* Bookmark icon — outline by default, fills with the warm gold accent
   when the posting is saved. Sits in the third grid column of .posting. */
.bookmark {
  background: none;
  border: 0;
  padding: 0.3rem;
  margin-top: 0.2rem;
  cursor: pointer;
  color: var(--ink-soft);
  line-height: 0;
  border-radius: 3px;
  transition: color 0.15s ease, background 0.15s ease;
}
/* fill/stroke live on the <svg>, not the path: the bookmark icon is pulled
   from the inline <symbol> sprite via <use>, and only inherited properties
   reach the cloned symbol. Off state is an outline (fill:none); .on fills gold. */
.bookmark svg {
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linejoin: round;
  transition: fill 0.15s ease;
}
.bookmark:hover { color: var(--accent); background: var(--accent-soft); }
.bookmark.on   { color: var(--accent); }
.bookmark.on svg { fill: var(--accent); }
.bookmark:disabled { cursor: default; opacity: 0.6; }

/* Action cluster — the mute ("관심 없음") and bookmark buttons share the
   third grid column of .posting, sitting close together at the row's edge. */
.posting-actions {
  display: flex;
  align-items: flex-start;
  gap: 0.1rem;
}

/* "관심 없음" mute icon — Lucide eye-off, stroke-based (the bookmark fills;
   this one stays an outline). Hover stays neutral rather than gold: saving a
   posting is encouraged, hiding one is merely available — the calm thesis
   says don't nudge people to dismiss. Once muted, the gold "on" tint matches
   every other active toggle in the app. */
.not-interested {
  background: none;
  border: 0;
  padding: 0.3rem;
  margin-top: 0.2rem;
  cursor: pointer;
  color: var(--ink-soft);
  line-height: 0;
  border-radius: 3px;
  transition: color 0.15s ease, background 0.15s ease;
}
.not-interested svg { display: block; }
.not-interested:hover { color: var(--ink); background: var(--accent-soft); }
.not-interested.on { color: var(--accent); }
.not-interested:disabled { cursor: default; opacity: 0.6; }

/* Gentle exit for a card the user just muted or un-hid: fade before the JS
   removes the node from the DOM. */
.posting.removing {
  opacity: 0;
  transition: opacity 0.22s ease;
}

/* Empty state */
.empty { text-align: center; padding: 4rem 1rem; }
.empty-title { font-weight: 600; font-size: 1.05rem; margin: 0 0 0.5rem; }
.empty-sub { color: var(--ink-soft); margin: 0; font-size: 0.92rem; line-height: 1.65; }

/* Excluded disclosure */
.excluded-box { margin-top: 2.5rem; }
.excluded-box summary {
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--ink-soft);
  letter-spacing: 0.02em;
  padding: 0.5rem 0;
  list-style: none;
}
.excluded-box summary::before { content: "▾ "; color: var(--accent); }
.excluded-box[open] summary::before { content: "▴ "; }
.excluded-box .posting { opacity: 0.5; }
.excluded-box .score {
  color: var(--ink-soft);
  font-size: 2rem;
  padding-top: 0.5rem;
}

/* Footer */
footer {
  margin-top: 4rem;
  font-family: var(--mono);
  font-size: 0.92rem;
  color: var(--ink-soft);
  letter-spacing: 0.18em;
  text-align: center;
  text-transform: uppercase;
}

/* Profile form */
.form-intro {
  color: var(--ink-soft);
  margin: 1.25rem 0 0;
  font-size: 0.95rem;
}
.demo-banner {
  color: var(--ink);
  margin: 1.25rem 0 0;
  padding: 0.8rem 1rem;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--accent-soft);
  font-size: 0.95rem;
}
.profile-fields {
  border: 0;
  margin: 0;
  padding: 0;
}
.profile-fields:disabled {
  opacity: 0.72;
}
form label {
  display: block;
  margin: 1.5rem 0 0;
  font-weight: 600;
  font-size: 0.92rem;
}
form label.check {
  font-weight: 400;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.field-hint {
  display: block;
  font-weight: 400;
  color: var(--ink-soft);
  font-size: 0.82rem;
  margin: 0.15rem 0 0;
}
/* Derived-value hint shown under a weight input — the live-updated number
   gets the gold mono treatment so it reads as a computed value. */
.field-hint.derived { margin-top: 0.4rem; }
.field-hint.derived strong {
  color: var(--accent);
  font-family: var(--mono);
  font-weight: 500;
}
input[type="text"], input[type="number"], select, textarea {
  width: 100%;
  margin-top: 0.45rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: 0.95rem;
  transition: border-color 0.15s ease;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
}
form label.check input { width: auto; margin: 0; }
textarea { resize: vertical; min-height: 4rem; }
.hint {
  color: var(--ink-soft);
  font-size: 0.83rem;
  background: var(--accent-soft);
  padding: 0.7rem 0.9rem;
  border-radius: 4px;
  margin-top: 1.5rem;
  line-height: 1.6;
}
.form-actions { margin-top: 2rem; }

.source-toggles,
.ai-goals {
  margin: 1.5rem 0 0;
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
}
.source-toggles legend,
.ai-goals legend {
  font-weight: 600;
  font-size: 0.92rem;
  padding: 0 0.4rem;
}
.source-toggles .field-hint,
.ai-goals .field-hint { margin-bottom: 0.7rem; }
.source-toggles .check { margin-top: 0.4rem; }
