/* ---- figures ------------------------------------------------------------- */
/* A figure is never a bare number. Label, value, and the basis it was computed
 * on, always — "Average profit" alone is how a wrong number survives for
 * months. STORY-024. */
.fig { padding: var(--space-3) var(--space-4);
  background: var(--color-surface-raised); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); }
.fig__label { display: block; font-size: var(--text-sm); color: var(--color-text-muted); }
.fig__value { display: block; font-size: var(--text-xl); font-weight: var(--weight-semibold);
  line-height: var(--leading-tight); font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere; margin-block-start: var(--space-1); }
.fig__value--lg { font-size: var(--text-2xl); }
.fig__basis { display: block; font-size: var(--text-xs); color: var(--color-text-muted);
  margin-block-start: var(--space-1); }
.fig--lead { border-color: var(--color-accent);
  box-shadow: inset var(--border-width-accent) 0 0 var(--color-accent); }

.tiles { display: grid; gap: var(--space-3); grid-template-columns: 1fr; }
@media (min-width: 30rem) { .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 48rem) { .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* Three across at every width, for a summary strip that must not push the
 * content it summarises off the screen. The value steps down a size so the
 * longest amount still fits a third of a 390px viewport. */
.tiles--compact { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); }
.tiles--compact .fig { padding: var(--space-2) var(--space-3); }
.tiles--compact .fig__label { font-size: var(--text-xs); }
.tiles--compact .fig__value { font-size: var(--text-base); }

/* ---- currency deck --------------------------------------------------------
 * Figures in two currencies must never be read as one set, and a small muted
 * "In EUR" heading is far too weak a divider for that job — the whole risk is a
 * reader carrying the EUR total in their head three inches down the page.
 *
 * So one currency is on screen at a time, in a card that says which, loudly.
 * The usual objection to a carousel is that it hides content people never find;
 * the answer here is that each control carries its currency's **headline total**,
 * so the other currencies are never invisible — you can see that JPY exists and
 * roughly what it holds without switching to it. It never auto-advances.
 */
.deck { display: grid; gap: var(--space-3); }

.deck__bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.deck__label { font-size: var(--text-xs); color: var(--color-text-muted); flex: none; }
/* Sized to its chips, not stretched: with two currencies a growing list would
 * strand the next arrow at the far edge, half a screen from the chips it steps
 * through. It scrolls on its own once there are more chips than room. */
.deck__list { display: flex; gap: var(--space-2); overflow-x: auto;
  padding-block: var(--space-1); flex: 0 1 auto; }
.deck__chip { display: grid; gap: 0; text-align: start; flex: none; cursor: pointer;
  min-block-size: var(--target-min); padding: var(--space-2) var(--space-4);
  font: inherit; color: var(--color-text);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-md); }
.deck__chip:hover { border-color: var(--color-accent); }
.deck__chip[aria-selected="true"] { background: var(--color-accent);
  color: var(--color-text-on-accent); border-color: var(--color-accent);
  box-shadow: var(--shadow-sm); }
.deck__chip:focus-visible { outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset); }
.deck__chip-code { font-size: var(--text-sm); font-weight: var(--weight-semibold);
  line-height: var(--leading-snug); }
/* Full opacity, not a dimmed sub-label: on the selected chip this text sits on
 * the accent fill, where 4.62:1 light and 9.22:1 dark is the whole margin. */
.deck__chip-val { font-size: var(--text-xs); line-height: var(--leading-snug);
  font-variant-numeric: tabular-nums; }
.deck__nav { flex: none; border: 1px solid var(--color-border-strong); }
.deck__nav[disabled] { opacity: 0.4; cursor: not-allowed; }
.deck__count { flex: none; margin-inline-start: auto;
  font-size: var(--text-xs); color: var(--color-text-muted);
  font-variant-numeric: tabular-nums; }

/* Padding steps up with the viewport: on a phone the card sits inside the page
 * gutter and its own children have padding too, and three nested insets leave
 * the chart nothing. */
.deck__panel { border: 1px solid var(--color-border-strong); border-radius: var(--radius-lg);
  background: var(--color-surface-raised); padding: var(--space-3);
  display: grid; gap: var(--space-4); }
@media (min-width: 30rem) { .deck__panel { padding: var(--space-4); } }
@media (min-width: 48rem) { .deck__panel { padding: var(--space-5); } }
.deck__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-3);
  padding-block-end: var(--space-3); border-block-end: 1px solid var(--color-border); }
.deck__cur { margin: 0; font-size: var(--text-2xl); line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight); }
.deck__curname { font-size: var(--text-sm); color: var(--color-text-muted); }
.deck__note { margin: 0; font-size: var(--text-xs); color: var(--color-text-muted);
  margin-inline-start: auto; max-inline-size: none; }
/* Inside a currency card the tiles sit on the card, so they need their own edge
 * against it rather than the page. */
.deck__panel .fig, .deck__panel .panel { background: var(--color-surface); }

/* ---- bento ---------------------------------------------------------------
 * A four-column grid the tiles span unevenly, so the chart gets room and the
 * lead figures get weight. It collapses to two columns, then to one — the
 * spans collapse with it rather than forcing a horizontal scroll. */
.bento { display: grid; gap: var(--space-3); grid-template-columns: 1fr; }
.bento > * { min-inline-size: 0; }
@media (min-width: 30rem) {
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .b-2, .b-4, .b-plot { grid-column: span 2; }
}
@media (min-width: 48rem) {
  .bento { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .b-4 { grid-column: span 4; }
  .b-plot { grid-column: span 2; grid-row: span 2; }
}

.panel { padding: var(--space-4); background: var(--color-surface-raised);
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  display: flex; flex-direction: column; gap: var(--space-3); }
.panel__title { margin: 0; font-size: var(--text-sm); font-weight: var(--weight-semibold); }
.panel__sub { margin: 0; font-size: var(--text-xs); color: var(--color-text-muted);
  max-inline-size: none; }

/* ---- diverging bar: profit by event --------------------------------------
 * Profit is polarity, not identity — which side of zero a bar falls on is the
 * message, so it is a diverging form: two hues either side of a neutral
 * baseline. Blue and red rather than green and red: they read as opposite
 * without being the one pair red-green colour blindness collapses.
 *
 * Colour is never the signal here anyway. The side of the baseline carries the
 * sign, the value is written out beside every bar with its own minus, and a
 * loss is worded. In greyscale nothing is lost. */
.dbar { display: grid; gap: var(--space-1); }
.dbar__row { display: grid; grid-template-columns: minmax(0, 9rem) 1fr auto;
  gap: var(--space-3); align-items: center; inline-size: 100%;
  background: none; border: 0; font: inherit; color: inherit; text-align: start;
  padding: var(--space-2) var(--space-1); border-radius: var(--radius-sm); cursor: pointer; }
.dbar__row:hover { background: var(--color-surface-sunken); }
.dbar__row:focus-visible { outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: calc(var(--focus-ring-offset) * -1); }
/* Names wrap rather than truncate. An ellipsis on a category label is a value
 * the reader cannot recover — and two of the seed events differ only after the
 * point a 7rem column would cut them. */
.dbar__name { font-size: var(--text-sm); line-height: var(--leading-snug);
  overflow-wrap: anywhere; }
.dbar__row:hover .dbar__name { text-decoration: underline; text-underline-offset: 2px; }
.dbar__track { position: relative; block-size: var(--space-4); }
/* The baseline is a real axis, not a gridline: border-strong clears 3:1, which
 * a hairline in --color-border would not. It is extended past the bar on both
 * sides so the per-row segments meet and read as one continuous line. */
.dbar__zero { position: absolute; inset-block: calc(var(--space-3) * -1);
  inline-size: 1px; background: var(--color-border-strong); }
.dbar__fill { position: absolute; inset-block: 0; background: var(--color-accent);
  border-start-end-radius: var(--radius-sm); border-end-end-radius: var(--radius-sm); }
.dbar__fill--neg { background: var(--color-danger);
  border-radius: 0;
  border-start-start-radius: var(--radius-sm); border-end-start-radius: var(--radius-sm); }
.dbar__val { font-size: var(--text-sm); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* On a phone a 9rem name column leaves the plot about 60px, which is not a
 * chart. So the name moves above the bar and the track gets the full width —
 * the comparison the chart exists for survives, and only the layout changes. */
@media (max-width: 30rem) {
  .dbar__row { grid-template-columns: 1fr auto;
    grid-template-areas: "name name" "track value"; gap: var(--space-1) var(--space-3); }
  .dbar__name { grid-area: name; }
  .dbar__track { grid-area: track; }
  .dbar__val { grid-area: value; }
  /* The name now sits directly above the track, so the baseline must not run up
   * through it — it extends downward only. */
  .dbar__zero { inset-block: 0 calc(var(--space-2) * -1); }
}

/* ---- meter: a ratio against a limit --------------------------------------
 * A single ratio against a limit is a meter, not a chart. The track runs to
 * whichever is larger, 100% or the actual, so an overrun is visible as length
 * rather than as a bar that has simply stopped at the end. */
.meter__track { position: relative; block-size: var(--space-3);
  background: var(--color-surface-sunken); border: 1px solid var(--color-border);
  border-radius: var(--radius-full); }
.meter__fill { position: absolute; inset-block: 0; inset-inline-start: 0;
  background: var(--color-accent); border-radius: var(--radius-full); }
.meter__fill--over { background: var(--color-danger); }
.meter__mark { position: absolute; inset-block: calc(var(--space-1) * -1);
  inline-size: 1px; background: var(--color-border-strong); }
.meter__scale { display: flex; justify-content: space-between;
  font-size: var(--text-xs); color: var(--color-text-muted); }

/* Table twin. Every chart has one — a tooltip is an enhancement, never the only
 * way to reach a value. */
.twin > summary { font-size: var(--text-xs); color: var(--color-text-muted);
  cursor: pointer; min-block-size: var(--target-min);
  display: flex; align-items: center; gap: var(--space-2); }
/* `display: flex` drops the native disclosure marker, so it is replaced rather
 * than lost — a summary with no affordance reads as inert text. */
.twin > summary::-webkit-details-marker { display: none; }
.twin > summary::before { content: ""; flex: none; inline-size: 0.4em; block-size: 0.4em;
  border-inline-end: 2px solid currentcolor; border-block-end: 2px solid currentcolor;
  transform: rotate(-45deg); transition: transform var(--duration-fast) var(--ease-out); }
.twin[open] > summary::before { transform: rotate(45deg); }
.twin > summary:hover { color: var(--color-text); }
.twin > summary:focus-visible { outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset); }

#chart-tip { position: fixed; z-index: var(--z-toast); pointer-events: none;
  background: var(--color-surface-inverse); color: var(--color-text-inverse);
  font-size: var(--text-xs); line-height: var(--leading-snug);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg); max-inline-size: 16rem; }
#chart-tip[hidden] { display: none !important; }
#chart-tip dl { display: grid; grid-template-columns: auto auto; gap: var(--space-1) var(--space-3);
  margin: var(--space-1) 0 0; }
#chart-tip dd { margin: 0; text-align: end; font-variant-numeric: tabular-nums; }

/* Money. Tabular figures so columns of amounts line up. A loss is never carried
 * by colour alone — the minus sign is in the formatted string and the word
 * "loss" is added beside it. design.md. */
.money { font-variant-numeric: tabular-nums; white-space: nowrap; }
.money--loss { color: var(--color-danger); }
.money--gain { color: var(--color-success); }

/* ---- reconciliation ------------------------------------------------------ */
/* The three figures and the arithmetic between them. The relationship is stated
 * in text, not implied by layout — a sighted reader sees the rule, a screen
 * reader hears it. STORY-023. */
.recon { display: grid; gap: var(--space-2); }
.recon__row { display: flex; flex-wrap: wrap; gap: var(--space-2);
  align-items: baseline; justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border); border-radius: var(--radius-md); }
.recon__row--total { border-color: var(--color-border-strong);
  border-width: 2px; font-weight: var(--weight-semibold); }
.recon__op { font-family: var(--font-mono); color: var(--color-text-muted);
  inline-size: 1.25rem; flex: none; }
.recon__name { display: flex; align-items: baseline; gap: var(--space-2);
  flex: 1 1 12rem; min-inline-size: 0; }
.recon__amount { font-size: var(--text-lg); }
.recon__note { font-size: var(--text-xs); color: var(--color-text-muted);
  margin: var(--space-1) 0 0; max-inline-size: none; }

/* ---- transaction table (desktop) ----------------------------------------- */
.t-table { inline-size: 100%; border-collapse: collapse; }
.t-table th, .t-table td { text-align: start; padding: var(--space-2) var(--space-3);
  border-block-end: 1px solid var(--color-border); vertical-align: middle; }
.t-table th { font-size: var(--text-xs); font-weight: var(--weight-medium);
  color: var(--color-text-muted); white-space: nowrap; }
.t-table td:first-child { white-space: nowrap; color: var(--color-text-muted);
  font-size: var(--text-sm); }
.t-table .num { text-align: end; }
.table-wrap { overflow-x: auto; background: var(--color-surface-raised);
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3); }

/* ---- direction control --------------------------------------------------- */
/* Real radios in a real fieldset. A Transaction is income OR expense and the
 * shape of the control says so — two amount columns are what let a spreadsheet
 * row be both. STORY-020. */
.seg { display: flex; gap: var(--space-2); border: 0; padding: 0; margin: 0; }
.seg legend { font-size: var(--text-sm); font-weight: var(--weight-medium);
  padding: 0; margin-block-end: var(--space-2); }
.seg__opt { flex: 1 1 0; }
.seg__opt input { position: absolute; inline-size: 1px; block-size: 1px;
  overflow: hidden; clip-path: inset(50%); }
.seg__opt span { display: flex; align-items: center; justify-content: center;
  gap: var(--space-2); min-block-size: var(--target-min);
  padding-inline: var(--space-3); font-size: var(--text-sm);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-md);
  background: var(--color-surface-raised); cursor: pointer; }
.seg__opt input:checked + span { border-color: var(--color-accent);
  background: var(--color-accent); color: var(--color-text-on-accent);
  font-weight: var(--weight-semibold); }
.seg__opt input:focus-visible + span {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset); }

/* ---- amount input -------------------------------------------------------- */
/* The highest-risk field in the product. The currency is a real control next to
 * the number, not decoration: the amount is meaningless without it, and the
 * number of decimals it will accept comes from it. */
.amount { display: flex; align-items: stretch; }
.amount .field__control { border-start-end-radius: 0; border-end-end-radius: 0;
  font-variant-numeric: tabular-nums; text-align: end; }
.amount__cur { flex: none; min-block-size: var(--target-min);
  padding-inline: var(--space-3); font: inherit; font-size: var(--text-sm);
  color: var(--color-text); background: var(--color-surface-sunken);
  border: 1px solid var(--color-border-strong); border-inline-start: 0;
  border-start-end-radius: var(--radius-md); border-end-end-radius: var(--radius-md);
  cursor: pointer; }
.amount__cur:focus-visible { outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset); position: relative; z-index: 1; }

.form-grid { display: grid; gap: var(--space-4); }
@media (min-width: 48rem) { .form-grid--2 { grid-template-columns: 1fr 1fr; } }

.detail-list { display: grid; gap: var(--space-3); margin: 0; }
.detail-list dt { font-size: var(--text-xs); color: var(--color-text-muted); }
.detail-list dd { margin: 0; overflow-wrap: anywhere; }

.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.cur-head { font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--color-text-muted); margin: 0; }

/* Where two currencies DO sit on one screen — the transactions strip, an event
 * with costs in two — the divider between them carries an accent edge and full
 * text weight. A muted grey line is not enough separation for two sets of
 * figures that must never be read as one. */
.cur-tag { display: flex; align-items: baseline; gap: var(--space-2);
  margin: 0 0 var(--space-2); font-size: var(--text-base);
  font-weight: var(--weight-semibold); color: var(--color-text);
  border-inline-start: var(--border-width-accent) solid var(--color-accent);
  padding-inline-start: var(--space-3); }
.cur-tag span { font-size: var(--text-xs); font-weight: var(--weight-regular);
  color: var(--color-text-muted); }

.name-link { background: none; border: 0; padding: 0; font: inherit;
  color: var(--color-accent); text-align: start; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px; overflow-wrap: anywhere; }
.name-link:focus-visible { outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset); border-radius: var(--radius-sm); }

.filters { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: end; }
.filters .field { inline-size: auto; min-inline-size: 9rem; flex: 1 1 9rem; }
.filters select.field__control { min-block-size: var(--target-min); }

.desktop-only { display: none; }
@media (min-width: 48rem) { .phone-only { display: none; } }

.sr-only { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; }

.proto { background: var(--color-surface-inverse); color: var(--color-text-inverse);
  font-size: var(--text-xs); padding: var(--space-2) var(--space-4); text-align: center;
  display: flex; gap: var(--space-2); justify-content: center; flex-wrap: wrap; align-items: center; }
.proto button { background: none; border: 1px solid currentcolor; color: inherit;
  border-radius: var(--radius-sm); font-size: var(--text-xs);
  padding: 2px var(--space-2); cursor: pointer; }
.proto button[aria-pressed="true"] { background: var(--color-text-inverse);
  color: var(--color-surface-inverse); }
