/* folio's palette, expressed as htmx-ds tokens — a theme FAMILY, "folio",
 * with a dark and a light variant, registered in web/theme.go so the design
 * system's picker and toggle know it. Loaded after the design system's
 * stylesheets; every value here binds to [data-theme="folio-dark"] or
 * [data-theme="folio-light"], the attribute layout.Base renders from the
 * theme cookie, exactly as the library's own themes do. Nothing in this file
 * styles an element directly except the handful of folio-specific rules at the
 * bottom — restyling components is what the tokens are for.
 *
 * The dark colours are folio's original Material 3 dark palette, copied
 * unchanged from the tailwind.config in web/views/layout.templ so the migration
 * could not shift a hue by accident. The light variant is the same palette's
 * light scheme. They stay hex rather than becoming oklch(): a custom property
 * carries any colour syntax, and a reviewer can diff these against the old
 * config by eye. Where the design system mixes a token (color-mix in the alert
 * and scrollbar rules) hex works exactly the same.
 *
 * Chart strokes are folio's own in every theme: the design system draws
 * series in the foreground inks unless --chart-N is set, and folio sets it
 * per scheme at the bottom of the token section — blue for the portfolio,
 * amber for the benchmark beside it. Only strokes take these; fills, ticks
 * and the tooltip stay the theme's neutrals.
 */

:root {
  /* Theme-independent, so they sit outside the .dark scope.
   *
   * radius 0.5rem reproduces folio's two radii through the derived scale:
   * --radius-lg lands on 8px (Tailwind rounded-lg, buttons and inputs) and
   * --radius-xl on 12px (rounded-xl, cards). The design system's 0.625rem
   * default would round both a step softer than the app has ever looked. */
  --radius: 0.5rem;

  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Sampled from the logo artwork, and folio's alone — hence the prefix. The
     teal is the wordmark's "folio" and is unusable as type on this background;
     it is kept because it is the brand's other half, and it does work as a
     fill. */
  --folio-brand-leaf: #a4d4a0;
  --folio-brand-deep: #184850;
}

[data-theme="folio-dark"] {
  color-scheme: dark;

  --background: #10131a; /* surface */
  --foreground: #e1e2ec; /* on-surface */

  /* Cards sit one step above the page and popovers one above cards, which is
     the same ordering the surface-container ramp already encodes. */
  --card: #191b23; /* surface-container-low */
  --card-foreground: #e1e2ec; /* on-surface */
  --popover: #1d2027; /* surface-container */
  --popover-foreground: #e1e2ec; /* on-surface */

  /* --primary is a fill with --primary-foreground written on it, so it takes
     folio's primary-container pair — the blue on the sign-in button — not the
     lighter `primary`, which the app only ever uses as accent text. */
  --primary: #4d8eff; /* primary-container */
  --primary-foreground: #00285d; /* on-primary-container */

  --secondary: #272a31; /* surface-container-high */
  --secondary-foreground: #e1e2ec; /* on-surface */
  --muted: #1d2027; /* surface-container */
  --muted-foreground: #c2c6d6; /* on-surface-variant */
  --accent: #272a31; /* surface-container-high — the hover fill */
  --accent-foreground: #e1e2ec; /* on-surface */

  /* One token covers what folio splits in two: error text and the fill behind a
     destructive button. The light tone wins it, because that is the job the
     design system gives --destructive nearly everywhere (field errors, the
     invalid input border, the alert's icon and title). The filled button is the
     single exception and is restated below. */
  --destructive: #ffb4ab; /* error */
  --destructive-foreground: #10131a; /* surface */

  /* The green every gain in the app is already written in. */
  --success: #34d399;
  --success-foreground: #0b0e15; /* surface-container-lowest */

  --border: #424754; /* outline-variant */
  --input: #424754; /* outline-variant */
  --ring: #4d8eff; /* primary-container */
}

/* The same Material palette in its light scheme. Surfaces step the other
   way — the page is the lightest thing and cards sit on it in white — and
   every intent colour is the scheme's own: deep enough to carry white text
   as a fill and to read as text on the page (all pairs at or above 4.5:1,
   which is the bar the library holds its own themes to). */
[data-theme="folio-light"] {
  color-scheme: light;

  --background: #f7f8fc; /* surface */
  --foreground: #191c20; /* on-surface */

  --card: #ffffff; /* surface-container-lowest */
  --card-foreground: #191c20;
  --popover: #ffffff;
  --popover-foreground: #191c20;

  --primary: #0b57d0; /* primary */
  --primary-foreground: #ffffff; /* on-primary */

  --secondary: #e8eaf2; /* surface-container-high */
  --secondary-foreground: #191c20;
  --muted: #eef0f7; /* surface-container */
  --muted-foreground: #44474f; /* on-surface-variant */
  --accent: #e2e4ec; /* surface-container-highest — the hover fill */
  --accent-foreground: #191c20;

  --destructive: #ba1a1a; /* error */
  --destructive-foreground: #ffffff; /* on-error */

  --success: #0f7a4c;
  --success-foreground: #ffffff;

  --border: #d9dbe6; /* outline-variant, lifted a step for a light page */
  --input: #c4c6d0; /* outline-variant */
  --ring: #0b57d0;

  /* The wordmark's leaf green, deepened: the logo's own tint is a fill
     colour and washes out as type on a light page. */
  --folio-brand-leaf: #3f7d3a;
}

/* Chart strokes are folio's in EVERY theme, not only its own: the portfolio
   is blue and the benchmark beside it amber whichever palette the page
   wears, because those two are what the comparison has always meant here.
   Scoped by scheme rather than by family — the dark set on any dark page,
   the light set on any light one — so they hold against every background.
   Green is deliberately absent: it means gain on every other figure on the
   page, and a benchmark line is not one. Only strokes take these; fills,
   ticks and the tooltip stay the theme's neutrals. */
:root {
  --chart-1: #0b57d0; /* the portfolio */
  --chart-2: #b45309; /* the benchmark */
  --chart-3: #6d28d9;
  --chart-4: #44474f;
  --chart-5: #0369a1;
}

.dark {
  --chart-1: #4d8eff;
  --chart-2: #f5a524;
  --chart-3: #a78bfa;
  --chart-4: #c2c6d6;
  --chart-5: #38bdf8;
}

/* Figures line up in columns across every table and card, which they only do
   with tabular numerals. Carried over from folio's app.css, where it applies to
   the whole document rather than being remembered per table. */
html,
body {
  font-feature-settings: "tnum" 1, "ss01" 1;
}

/* Two families, as before: Inter sets text (via --font-sans above) and Manrope
   sets headings. The design system has no display-font token to point at, so
   this stays a plain rule rather than an invented token. */
h1,
h2,
h3,
h4 {
  font-family: "Manrope", var(--font-sans);
}

/* The one place folio's error colours do not survive the single --destructive
   token: in the dark scheme a filled button wants the dark container with
   light text on it, which is what every destructive action in the app has
   always looked like. The light scheme's --destructive is already a fill. */
[data-theme="folio-dark"] .btn--destructive {
  background: #93000a; /* error-container */
  color: #ffdad6; /* on-error-container */
}

/* ---- Transaction kinds ------------------------------------------------- */
/* One colour per kind, and the same colour wherever the kind appears — the
   imports table, the dashboard's cash rows, the holding page's badges. The
   design system ships semantic chip variants and invites domain ones, so these
   are folio's vocabulary rather than a fork of its palette.
   Two pairs share a hue on purpose: a dividend and a deposit are both money
   arriving, a sale and a withdrawal both money leaving. */

.chip--buy {
  background: color-mix(in oklab, var(--primary), transparent 82%);
  color: color-mix(in oklab, var(--primary), var(--foreground) 32%);
}

.chip--sell {
  background: color-mix(in oklab, var(--destructive), transparent 85%);
  color: color-mix(in oklab, var(--destructive), var(--foreground) 32%);
}

.chip--income {
  background: color-mix(in oklab, var(--success), transparent 85%);
  color: color-mix(in oklab, var(--success), var(--foreground) 32%);
}

.chip--interest {
  background: color-mix(in oklab, #38bdf8, transparent 85%);
  color: color-mix(in oklab, #38bdf8, var(--foreground) 32%);
}

.chip--fee {
  background: color-mix(in oklab, #fb923c, transparent 85%);
  color: color-mix(in oklab, #fb923c, var(--foreground) 32%);
}

.chip--tax {
  background: color-mix(in oklab, #a78bfa, transparent 85%);
  color: color-mix(in oklab, #a78bfa, var(--foreground) 32%);
}

.chip--balance {
  background: color-mix(in oklab, #fbbf24, transparent 85%);
  color: color-mix(in oklab, #fbbf24, var(--foreground) 32%);
}

/* ---- Table figures ------------------------------------------------------ */
/* Numbers line up on the right and share a width, which is what makes a column
   of them scannable. The design system's cells take a class now, so this is a
   modifier on its table rather than a folio table beside it. */

.table__head--numeric,
.table__cell--numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Stacked, a right-aligned figure sits opposite its own label rather than
   against an edge that is no longer a column. */
@media (max-width: 767px) {
  .table--stack .table__cell--numeric {
    text-align: left;
  }
}

/* ---- Signed figures ---------------------------------------------------- */
/* Stat owns what a gain and a loss look like on a KPI tile. A table full of
   signed figures needs the same two colours, and they have to be the SAME two,
   or the page tints a gain one green at the top and a different green in the
   rows underneath. So these are Stat's own expression, not a second palette:
   mixed toward the foreground by the same 32%, which is what carries the pair
   to WCAG AA at the small sizes a table uses. */

.trend--up {
  color: color-mix(in oklab, var(--success), var(--foreground) 32%);
}

.trend--down {
  color: color-mix(in oklab, var(--destructive), var(--foreground) 32%);
}

/* Flat is "measured, and it did not move" — muted rather than coloured. There
   is no .trend--none: a figure with no direction is ordinary text. */
.trend--flat {
  color: var(--muted-foreground);
}

/* ---- Dashboard --------------------------------------------------------- */

.dash__identity {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* The institution holding the account, beside the name it qualifies. Sized
   like a chip rather than a heading: it is an attribute of the portfolio, and
   reading as loudly as the name would make the header a two-title row. */
.dash__brokerage {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
  color: var(--muted-foreground);
  white-space: nowrap;
}

/* The description is a line of its own under the name and its tags, not a
   third item competing with them for the row. */
.dash__description {
  flex-basis: 100%;
  margin: 0;
}

.dash__controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.dash__summary {
  display: grid;
  gap: 1rem;
  /* Auto-fit, not a fixed three: a fixed count squeezes the total-value
     figure into a sliver at mid widths and it wraps mid-number. Cards
     reflow to two-then-one columns as space runs out instead. */
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}

/* Realized, dividends and interest, under the gain they are part of. */
.dash__income {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.5rem;
  margin: 0.5rem 0 0;
  font-size: var(--text-sm);
  color: var(--muted-foreground);
}

.dash__income > span + span::before {
  content: "·";
  margin-right: 0.5rem;
  color: var(--muted-foreground);
}

/* A section's title on the left, its controls or its count on the right. */
.dash__section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.dash__section-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.dash__count {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  white-space: nowrap;
}

/* The benchmark picker sits in a row of segmented controls, so it matches
   their height rather than a form field's. */
.select--sm {
  height: 1.9rem;
  padding-block: 0;
  font-size: var(--text-xs);
  font-weight: 600;
}

/* ---- One holding ------------------------------------------------------- */

.holding {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: 1fr;
  align-items: start;
}

/* The chart and the history are the page; the figures are what you check
   against them, so they sit beside rather than above once there is room. */
@media (min-width: 1024px) {
  .holding {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  }
}

.holding__main,
.holding__side,
.holding__stats {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  min-width: 0;
}

.holding__header {
  align-items: flex-end;
}

.holding__identity {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-width: 0;
}

.holding__names {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.holding__meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.15rem;
}

.holding__price {
  display: flex;
  align-items: flex-end;
  gap: 1rem;
}

.holding__last {
  margin: 0;
  font-family: "Manrope", var(--font-sans);
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: 1.1;
  text-align: right;
}

.holding__change {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.3rem;
  margin: 0.25rem 0 0;
  font-size: var(--text-sm);
  font-weight: 500;
}

/* The window the change is over, said once and quietly. */
.holding__window {
  margin-left: 0.4rem;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

.holding__sync {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.holding__sync-label {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  cursor: pointer;
}

.holding__sync-note {
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

.holding__section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.holding__count {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  white-space: nowrap;
}

.holding__tiles {
  display: grid;
  /* Same reflow reasoning as .dash__summary: the position figure is the
     widest thing in the column, and it must never wrap mid-number. */
  grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr));
  gap: 1rem;
}

/* A name and a number, read one at a time. */
.holding__figures {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin: 0;
}

.holding__figure {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.holding__figure dt {
  font-size: var(--text-sm);
  color: var(--muted-foreground);
}

.holding__figure-value {
  margin: 0;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* The assumption behind a derived price. */
.holding__formula {
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: color-mix(in oklab, var(--muted), transparent 55%);
}

.holding__formula-line {
  margin: 0 0 0.25rem;
}

.holding__proxy-mark {
  color: var(--muted-foreground);
}

.holding__proxy-form {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.holding__proxy-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
}

/* ---- Importing a file -------------------------------------------------- */

/* A checkbox and its words are one target — including the sentence that says
   what the box means. Aligned to the top rather than centred, because the
   explanation runs to two or three lines and the box belongs beside the title. */
.import-check {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: var(--text-sm);
  cursor: pointer;
}

.import-check__text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

/* Inside a label the help is part of the target, so it must not look like
   something else to click. */
.import-check .field__help {
  margin: 0;
  cursor: inherit;
}

/* What the file may be, and what to do when it is not one of them. */
.import-formats {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: color-mix(in oklab, var(--muted), transparent 55%);
}

.import-formats__title {
  margin: 0 0 0.4rem;
  font-size: var(--text-sm);
  font-weight: 500;
}

.import-formats__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.4rem;
}

.import-formats__other {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
}

.import-formats code {
  font-family: var(--font-mono);
  color: var(--foreground);
}

/* The anchors replace mode would destroy, listed rather than counted. */
.import-anchors {
  margin: 0.25rem 0 0;
  padding-left: 1.1rem;
  font-size: var(--text-xs);
}

.import-resume__actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

/* ---- Recording a transaction ------------------------------------------- */
/* The action tiles wear the same colours as the chips in the tables they write
   to, so picking "Fee" and later reading a fee are the same orange. The design
   system ships success and destructive; these three are folio's own kinds, and
   like the shipped pair they tint the SELECTED state only — an unselected grid
   should read as one set of options, not a colour chart. */

.choice-grid__item--interest:has(.choice-grid__input:checked) {
  border-color: #38bdf8;
  background: color-mix(in oklab, #38bdf8, transparent 90%);
}

.choice-grid__item--fee:has(.choice-grid__input:checked) {
  border-color: #fb923c;
  background: color-mix(in oklab, #fb923c, transparent 90%);
}

.choice-grid__item--tax:has(.choice-grid__input:checked) {
  border-color: #a78bfa;
  background: color-mix(in oklab, #a78bfa, transparent 90%);
}

.choice-grid__item--balance:has(.choice-grid__input:checked) {
  border-color: #fbbf24;
  background: color-mix(in oklab, #fbbf24, transparent 90%);
}

/* Shares, price and fee are one figure in three parts, so they share a row
   until there is no room for three. */
.txn-trade {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  gap: 0.75rem;
}

/* The box and its words are one target. */
.txn-drip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-sm);
  cursor: pointer;
}

/* A way out of the form rather than a field in it, so it sits in the footer's
   dead space and pushes the buttons to the far edge. */
.txn-source {
  margin-right: auto;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* ---- Table figures, dashboard ------------------------------------------ */

/* A row's identity: the mark, the symbol, and one line of context under it.
   It is the row's link as well, because a holding page is still a document
   load away and a <tr> cannot be one. */
.asset {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: inherit;
  text-decoration: none;
}

a.asset:hover .asset__symbol {
  text-decoration: underline;
}

.asset__names {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.asset__symbol {
  font-family: "Manrope", var(--font-sans);
  font-weight: 700;
}

.asset__sub {
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* A pair in one cell: what it cost, over what it is worth now. The base is
   the quiet one — it is the context for the figure under it. */
.figures {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.35;
}

.figures__base {
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* Stacked, the pair keeps its order. Laying it out along the row put the
   headline figure first and the cost after it — the reverse of every other
   place the two appear, on the one screen where there is no column header left
   to say which is which.

   The lead is the figure the reader is actually after, so it carries the size
   and the weight; the trend class beside it carries the direction. It was
   emitted with no rule behind it for the whole of this migration, which is
   exactly the kind of silence the class-definition test now breaks. */
.figures__lead {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}

/* The derived cash balance is part of the table but not a position. */
.table__row--quiet {
  background: color-mix(in oklab, var(--muted), transparent 55%);
}

/* Which import recorded a cash event, under its date. */
.cash-source {
  display: block;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* ---- Links in prose ---------------------------------------------------- */

.link {
  color: var(--foreground);
  text-decoration: none;
}

.link:hover {
  text-decoration: underline;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
}

/* ---- Ticker import status ---------------------------------------------- */
/* The block the import dialog polls. It replaces the form in place, so it
   inherits the dialog panel's own column spacing and only has to lay out its
   own line. */

.ticker-job {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.ticker-job__line {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.ticker-job__icon {
  display: inline-flex;
  font-size: 1.5rem;
}

.ticker-job__icon--ok {
  color: var(--success);
}

.ticker-job__icon--bad {
  color: var(--destructive);
}

.ticker-job__title {
  font-size: var(--text-sm);
  font-weight: 600;
}

.ticker-job__detail {
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

.ticker-job__error {
  font-size: var(--text-xs);
  color: var(--destructive);
}

/* A ticker symbol is upper-case everywhere else in the app, so the input says
   so rather than letting the value and its display disagree. */
.input--upper {
  text-transform: uppercase;
}

.input--upper::placeholder {
  text-transform: none;
}

/* ---- The brand lockup ------------------------------------------------- */
/* Two weights and two colours of one geometric sans, which is what the logo
   artwork is. Sized by modifier so the header and the sign-in page share the
   definition; the base is the header's scale. */

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  flex-shrink: 0;
  text-decoration: none;
}

.wordmark__mark {
  width: auto;
  height: 2rem;
  flex-shrink: 0;
  user-select: none;
}

.wordmark__text {
  font-family: "Poppins", "Manrope", var(--font-sans);
  font-size: var(--text-lg);
  line-height: 1;
  letter-spacing: -0.025em;
  white-space: nowrap;
}

.wordmark__name {
  font-weight: 600;
  color: var(--foreground);
}

.wordmark__suffix {
  font-weight: 500;
  /* Four fifths, so the domain reads as a tail on the name rather than as a
     second word of equal weight. */
  font-size: 0.82em;
  color: var(--folio-brand-leaf);
}

.wordmark--lg .wordmark__mark {
  height: 2.5rem;
}

.wordmark--lg .wordmark__text {
  font-size: var(--text-2xl);
}

/* ---- The chrome-less shell -------------------------------------------- */
/* One centred card on an otherwise empty page. Padding rather than a margin so
   the card still clears the edges on a phone in landscape. */

.bare-shell {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 1.5rem;
}

.login-card {
  width: 100%;
  max-width: 24rem;
  box-shadow: var(--shadow-lg);
}

.login-form {
  display: grid;
  gap: 1rem;
}

/* The submit button spans the card: it is the only action on the page, and a
   button sized to its label reads as one option among several. */
.login-form .btn {
  width: 100%;
  margin-top: 0.25rem;
}

/* ---- Page scaffolding inside the shell -------------------------------- */

/* Rhythm between blocks, and nothing else. The shell's main pane already owns
   the padding — at both breakpoints — so setting any here would add to it
   rather than replace it. */
.page {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

/* A form is a reading column, not a canvas — full-width password fields on a
   wide screen are harder to scan, not easier. */
.page--narrow > * {
  max-width: 32rem;
  width: 100%;
}

/* The same idea, one size up. The import form carries a CSV column spec that
   wraps into unreadability at the sign-in form's width. */
.page--form > * {
  max-width: 42rem;
  width: 100%;
}

/* An error page has one thing to say, so it says it in the middle rather than
   pinned to the top-left of a mostly empty pane. */
.page--centred {
  flex: 1;
  justify-content: center;
}

.page__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.page__subtitle {
  margin-top: 0.25rem;
  font-size: var(--text-sm);
  color: var(--muted-foreground);
}

.form-stack {
  display: grid;
  gap: 1rem;
}

/* A portfolio card is one target, not a card containing a link. The anchor
   covers it via a stretched pseudo-element, which keeps a single real link
   with a single accessible name — nesting the figures inside the anchor would
   make a screen reader read the whole card as the link's text. */
.portfolio-card {
  position: relative;
  transition: border-color 0.15s ease;
  /* Figures line up across a row whatever the headers above them do. Only
     some portfolios carry a description, and without this the ones that do
     push their numbers a line lower than their neighbours' — so a row of
     values reads as ragged rather than as a column to scan down. */
  display: flex;
  flex-direction: column;
}

.portfolio-card > :last-child {
  margin-top: auto;
}

.portfolio-card:hover {
  border-color: var(--primary);
}

.portfolio-card__link {
  text-decoration: none;
}

.portfolio-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* The ring belongs on the card, not on the invisible overlay, or keyboard
   focus lands on something with no visible outline. */
.portfolio-card:has(.portfolio-card__link:focus-visible) {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.portfolio-card__link:focus-visible {
  outline: none;
}

.form-stack .btn {
  justify-self: start;
  margin-top: 0.25rem;
}

/* ---- Sidebar navigation ----------------------------------------------- */
/* The lists themselves are the design system's Sidebar. What is left here is
   the one row that is not a destination — the action that opens the create
   dialog — and what a list says when it is empty. */

.nav-list {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

/* Dashed rather than solid: an action, not a destination. */
.nav-new {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.25rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  color: var(--primary);
  font-size: var(--text-sm);
  font-weight: 500;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.nav-new:hover:not(:disabled) {
  background: var(--accent);
  border-color: var(--primary);
}

.nav-new:disabled {
  opacity: 0.5;
  cursor: default;
}

.nav-empty {
  padding: 1.5rem 0.75rem;
  text-align: center;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* ---- Brokerage groups in the nav --------------------------------------- */
/* The portfolio list is grouped by the institution holding each account. The
   groups need air between them and none within: the gap is what says where one
   institution ends, so the rows under a heading can stay tight against it. */
.nav-broker + .nav-broker {
  margin-top: 0.875rem;
}

/* A heading that is also a link, to everything held there. Quieter than the
   rows it introduces — smaller, uppercase, tracked — so the eye reads the
   portfolio names first and the institution only when looking for it. */
.nav-broker__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.75rem;
  border-radius: var(--radius-md);
  color: var(--muted-foreground);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.nav-broker__head:hover,
.nav-broker__head.is-active {
  background: var(--accent);
  color: var(--foreground);
}

/* The name takes the slack so the count sits hard against the right edge. */
.nav-broker__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-broker__count {
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  opacity: 0.7;
}

/* A brand mark standing in for a nav row's icon. The avatar brings its own
   size; this only stops it being stretched by the row's flex. */
.nav-mark {
  display: inline-flex;
  flex-shrink: 0;
}

/* The Brokerages sub-list under the tags. */
.nav-subhead {
  margin: 1rem 0 0.25rem;
  padding: 0 0.75rem;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

/* ---- Brokerage groups on the index ------------------------------------- */
.brokers__group + .brokers__group {
  margin-top: 2rem;
}

/* The heading is the whole width so the rule under it reads as the group's
   lid, and the total sits at the far end where a column of figures would. */
.brokers__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
  color: inherit;
  text-decoration: none;
}

.brokers__name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display, inherit);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.brokers__total {
  flex-shrink: 0;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--muted-foreground);
}

/* The arrow only resolves on hover: the heading is a link, but a permanent
   chevron on every group would compete with the cards for attention. */
.brokers__go {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--muted-foreground);
  opacity: 0;
  transition: opacity 0.15s ease, transform 0.15s ease;
  transform: translateX(-0.25rem);
}

.brokers__head:hover .brokers__go,
.brokers__head:focus-visible .brokers__go {
  opacity: 1;
  transform: none;
}

.brokers__head:hover .brokers__name {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* ---- Top bar ----------------------------------------------------------- */

/* Where you are, when that is not already obvious from the page. Hidden on a
   phone, where the header has no room to spare. */
.context-pill {
  display: none;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--muted);
  color: var(--muted-foreground);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

@media (min-width: 768px) {
  .context-pill {
    display: inline-block;
  }

  /* A page with nothing to say leaves the pill empty rather than absent — it
     is the target the next navigation swaps. */
  .context-pill:empty {
    display: none;
  }
}

.user-menu__trigger {
  display: block;
  border-radius: 999px;
  box-shadow: 0 0 0 1px var(--border);
  transition: box-shadow 0.15s ease;
}

.user-menu__trigger:hover {
  box-shadow: 0 0 0 1px var(--primary);
}

/* ---- Reviewing an import ----------------------------------------------- */

/* The review is the densest page in folio: 1,400 rows, eight columns, and an
   edit form living inside the table. Everything here is about fitting a form
   into a grid without the grid stopping being one. */

.review__actions {
  display: flex;
  gap: 0.5rem;
}

.review__summary {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

/* The one sentence that says whether this file is ready. */
.review__headline {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
}

.review__filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

/* One wording, its count, what it should mean, and whether to remember it —
   on one line where there is room, stacked where there is not. */
.review__mapping {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}

.review__mapping:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.review__wording {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 12rem;
  flex: 1 1 12rem;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

.review__wording .field__help {
  margin: 0;
  font-family: var(--font-sans);
}

.review__add > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: 500;
}

.review__add-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.8rem;
}

.review__add-form > * {
  flex: 1 1 7rem;
  min-width: 0;
}

/* The line number is the reader's anchor back to the file itself, so it is
   monospaced and stays narrow. */
.review__line {
  width: 3rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* Editing happens inside the cells, so the fields have to be table-sized
   rather than form-sized or eight columns will not fit on a laptop. */
.input--sm {
  height: 1.9rem;
  padding-block: 0;
  font-size: var(--text-xs);
}

/* A date is ten characters wide whatever else is in the row. */
.input--date {
  min-width: 7rem;
}

/* The edit fields sit in the columns their read-only values sit in, so they
   align the way those values do — a quantity that jumps from the right edge to
   the left when you click the pencil makes the column impossible to scan while
   one row is open. */
.input--quantity,
.input--price,
.input--amount {
  text-align: right;
}

/* Tickers are stored upper-cased whatever is typed, so the field shows what
   will be saved rather than what was typed. */
.input--symbol {
  text-transform: uppercase;
}

.review__row-actions {
  display: flex;
  gap: 0.25rem;
  justify-content: flex-end;
  white-space: nowrap;
}

.review__drip {
  margin-top: 0.25rem;
  font-size: var(--text-xs);
}

/* The note row: why a row cannot be imported, under the row it is about. It
   is present even when empty, so a fixed row can swap its own note away. The
   note belongs to the row above it, so the rule between them goes. */
.review__note-row td {
  border-top: 0;
  padding-top: 0;
}

.review__note {
  padding-bottom: 0.5rem;
}

.review__note-row--empty {
  display: none;
}

.review__note-body {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

.review__note-label {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* A row and its note are one thing, so they are tinted together. */
.review__note-row--problem,
.review__row--problem {
  background: color-mix(in oklab, var(--destructive), transparent 94%);
}

.review__row--duplicate {
  background: color-mix(in oklab, var(--chart-2), transparent 96%);
}

/* An ignored row is still auditable, so it recedes rather than disappearing. */
.review__row--ignored {
  opacity: 0.55;
}

/* The original cells, one chip each, so a note wraps readably instead of
   running on in a middot-joined line. */
.review__raw {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.review__raw code {
  padding: 0.05rem 0.3rem;
  border-radius: var(--radius-sm);
  background: var(--muted);
  font-family: var(--font-mono);
}

.review__pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding-top: 0.75rem;
}

.review__pager .field__help {
  margin: 0;
}

/* What the file says, what folio will do, and every named part of the gap. */
.review__ledger {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0;
}

.review__ledger-row,
.review__ledger-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.review__ledger-row dt,
.review__ledger-total dt {
  color: var(--muted-foreground);
  font-size: var(--text-sm);
}

.review__ledger-row dd,
.review__ledger-total dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.review__ledger-total {
  padding-top: 0.35rem;
  border-top: 1px solid var(--border);
  font-weight: 600;
}

.review__ledger-total dt {
  color: var(--foreground);
}

/* A cause is subordinate to the difference it explains, and reads as one. */
.review__ledger-cause {
  padding-left: 0.9rem;
  font-size: var(--text-xs);
}

.review__ledger-cause dt,
.review__ledger-cause dd {
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* Unrecognised wordings block the whole import, so the panel that resolves
   them is marked as the thing to deal with first. */
.review__unmapped {
  border-left: 3px solid var(--destructive);
}

/* An assumption that has not been checked in months is a caution, not a
   failure — the estimate still works, it has just had longer to drift. */
.field__help--stale {
  color: color-mix(in oklab, var(--chart-2), var(--foreground) 25%);
}


/* ---- The fold's call to action ------------------------------------------ */

/* The old card ended on "Open AAPL →", and the fold keeps that: one
   full-width action naming where it goes. The ds button classes carry the
   look; this only stretches it across the fold and spaces the arrow. */
.row-open {
  width: 100%;
  justify-content: center;
  gap: 0.4rem;
}

/* ---- Appearance (account page) ----------------------------------------- */
/* The theme controls live here and nowhere else: a palette is chosen once,
   not on every screen. The picker keeps its family name at every width in a
   settings row — the design system hides it in a phone header, where the
   swatch alone has to do. */

.appearance {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

.appearance .theme-picker__label {
  display: inline;
}

.appearance__hint {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}
