/* ComptaLincé — one stylesheet, no framework, no CDN.
   The palette is the committee's: marine for the chrome, green for money
   coming in, bordeaux for money going out. Nothing else is coloured, so a
   figure's colour always means its sign. */

:root {
  --marine: #344c77;
  --marine-dark: #293a5c;
  --green: #226f54;
  --slate: #3b6064;
  --bordeaux: #880d1e;

  --ink: #1b2430;
  --ink-soft: #5b6775;
  --line: #dfe4ea;
  --bg: #f4f6f9;
  --card: #ffffff;
  --card-alt: #fafbfd;
  --in: var(--green);
  --out: var(--bordeaux);
  /* The wash over a row htmx has just swapped in. Light enough that the ink
     and the signed amount stay readable while it fades. */
  --flash: rgba(52, 76, 119, .38);
  --radius: 10px;
  --shadow: 0 1px 2px rgba(27, 36, 48, .08), 0 2px 8px rgba(27, 36, 48, .04);
}

@media (prefers-color-scheme: dark) {
  :root {
    --marine: #5d7db5;
    --marine-dark: #44618f;
    --green: #3a9a77;
    --slate: #6aa0a5;
    --bordeaux: #c4586a;
    --ink: #e7ecf3;
    --ink-soft: #9aa7b8;
    --line: #2c3645;
    --bg: #161b22;
    --card: #1d242e;
    --card-alt: #222a35;
    --flash: rgba(93, 125, 181, .45);
  }
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  color: var(--ink);
  background: var(--bg);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a { color: var(--marine); }
a:hover { color: var(--bordeaux); }

/* ---------- Header ---------- */

.header {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0 1rem;
  min-height: 56px;
  background: var(--marine);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 10;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  color: #fff;
  text-decoration: none;
  font-size: 1.05rem;
  white-space: nowrap;
}
.brand:hover { color: #fff; }
.brand-icon { width: 24px; height: 24px; flex: none; }
.brand-text b { font-weight: 700; }

.menu {
  display: flex;
  gap: .25rem;
  margin-left: auto;
  overflow: hidden;
}
.menu a {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .55rem .7rem;
  border-radius: var(--radius);
  color: #e8eef8;
  text-decoration: none;
  white-space: nowrap;
  min-height: 44px;
}
.menu a:hover { background: rgba(255, 255, 255, .12); color: #fff; }
.menu a[aria-current="page"] { background: rgba(255, 255, 255, .2); color: #fff; font-weight: 600; }
.nav-icon { width: 18px; height: 18px; flex: none; }

.menu-admin { position: relative; }
.menu-admin summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius);
  color: #fff;
}
.menu-admin summary::-webkit-details-marker { display: none; }
.menu-admin summary:hover { background: rgba(255, 255, 255, .12); }
.icon-burger { width: 22px; height: 22px; }

.menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + .4rem);
  min-width: 15rem;
  padding: .4rem;
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
}
.menu-panel a,
.menu-panel button {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .6rem .7rem;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  min-height: 44px;
}
.menu-panel a:hover,
.menu-panel button:hover { background: var(--card-alt); color: var(--ink); }
.menu-panel form { margin: 0; }
.menu-icon { width: 18px; height: 18px; flex: none; color: var(--ink-soft); }
.menu-group {
  margin: .4rem .7rem .2rem;
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-soft);
}
.menu-group-sep { border-top: 1px solid var(--line); padding-top: .6rem; }

/* Each has to outrank the rule that lays its own container out -- `.menu a`
   and `.menu-panel a`, both one class and one type. A bare `.nav-wide` or
   `.nav-narrow` loses to them and hides nothing: the wide link was only ever
   clipped by `.menu { overflow: hidden }`, and the narrow one showed on every
   width, doubling "Rapport" in the menu. */
.menu-panel a.nav-narrow { display: none; }
@media (max-width: 62rem) {
  .menu a.nav-wide { display: none; }
  .menu-panel a.nav-narrow { display: flex; }
  /* Hidden to the eye, kept for the screen reader: the icons are all
     aria-hidden, so display:none here would leave the links nameless. */
  .menu a span {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
@media (max-width: 34rem) {
  .brand-text { display: none; }
  .menu a { padding: .55rem .5rem; }
}

/* ---------- Page ---------- */

.page {
  flex: 1;
  width: 100%;
  max-width: 76rem;
  margin: 0 auto;
  padding: 1.25rem 1rem 3rem;
}
.page-narrow { max-width: 42rem; }

.title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.title { font-size: 1.5rem; margin: 0; }
.title-aside { font-size: .95rem; font-weight: 400; color: var(--ink-soft); margin-left: .6rem; }
/* The amount belongs in the heading of a line's own screen: it is the first
   thing read, and a card of its own cost a sixth of the window. */
.title-amount { margin-left: .6rem; font-variant-numeric: tabular-nums; }
.title-actions { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-left: auto; }
.title-actions:not(:has(*)) { display: none; }
/* For a read-only account every action of some screens is a write: the box
   would stay, empty to the eye, and still take its share of the row. */
.read-only .title-actions:not(:has(> :not([data-writes]))) { display: none; }

.back-link {
  display: inline-block;
  margin-bottom: .4rem;
  font-size: .9rem;
  color: var(--ink-soft);
}

/* ---------- Cards, flashes ---------- */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1rem 1.1rem;
  margin-bottom: 1.25rem;
}
.card h2 { font-size: 1.05rem; margin: 0 0 .75rem; }
.card h2 .count { color: var(--ink-soft); font-weight: 400; }

.flash {
  padding: .7rem .9rem;
  border-radius: var(--radius);
  border: 1px solid;
  margin: 0 0 1rem;
}
.flash-error { background: rgba(136, 13, 30, .07); border-color: var(--bordeaux); color: var(--bordeaux); }
.flash-ok { background: rgba(34, 111, 84, .08); border-color: var(--green); color: var(--green); }

.hint { color: var(--ink-soft); font-size: .9rem; }
.empty { color: var(--ink-soft); padding: 1rem 0; }

/* ---------- Figures ---------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .9rem;
  margin-bottom: 1.25rem;
}
.tile {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: .9rem 1rem;
  text-decoration: none;
  color: inherit;
  display: block;
}
.tile:hover { border-color: var(--marine); color: inherit; }
.tile-label { font-size: .85rem; color: var(--ink-soft); display: block; }
.tile-value {
  font-size: 1.5rem;
  font-weight: 600;
  display: block;
  margin-top: .2rem;
}
.tile-sub { font-size: .85rem; color: var(--ink-soft); }
.tile-alert { border-left: 4px solid var(--bordeaux); }

.amount { font-variant-numeric: tabular-nums; white-space: nowrap; }
.in { color: var(--in); }
.out { color: var(--out); }
.nil { color: var(--ink-soft); }

/* ---------- Tables ---------- */

.table-wrap { overflow-x: auto; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: .94rem;
}
th, td {
  text-align: left;
  padding: .5rem .55rem;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
th {
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--ink-soft);
  white-space: nowrap;
  background: var(--card-alt);
  /* The scrollport here is .table-wrap (overflow: auto), not the window, so an
     offset for the 56px navbar would push the header down inside the frame and
     sit on the first row for good -- it never reaches the window anyway. */
  position: sticky;
  top: 0;
}
tbody tr:hover { background: var(--card-alt); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
tfoot td { font-weight: 600; border-top: 2px solid var(--line); border-bottom: 0; }
.row-todo { background: rgba(136, 13, 30, .04); }
.col-date { white-space: nowrap; color: var(--ink-soft); }
.line-main { display: block; }
.line-sub { display: block; font-size: .85rem; color: var(--ink-soft); }

.chip {
  display: inline-block;
  padding: .08rem .45rem;
  margin: 0 .2rem .2rem 0;
  border-radius: 999px;
  background: var(--card-alt);
  border: 1px solid var(--line);
  font-size: .78rem;
  color: var(--ink-soft);
  white-space: nowrap;
}
/* A tag on a line is the way into the batch classification: one click filters
   the whole book on it. It stays a chip to the eye -- the gesture is what is
   added, not another kind of thing to read. */
a.chip { text-decoration: none; }
a.chip:hover, a.chip:focus-visible { border-color: var(--marine); color: var(--marine); }

/* One chip, two gestures: the word filters the book on it, the cross takes it
   off this line. The cross is always drawn, never revealed on hover -- half
   the receipts are classified on a phone, where there is no hover. */
.chip-tag { display: inline-flex; align-items: center; gap: .3rem; padding-right: .25rem; }
.chip-tag a { color: inherit; text-decoration: none; }
a.party { color: inherit; text-decoration: none; }
a.party:hover, a.party:focus-visible { color: var(--marine); text-decoration: underline; }
.chip-tag:hover a, .chip-tag:focus-within a { color: var(--marine); }
.chip-tag form { display: contents; }
button.chip-x {
  min-height: 0;
  padding: 0 .22rem;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--ink-soft);
  font: inherit;
  font-size: .9rem;
  line-height: 1;
  cursor: pointer;
}
button.chip-x:hover, button.chip-x:focus-visible {
  background: var(--bordeaux);
  color: #fff;
}

.chip-event { border-color: var(--marine); color: var(--marine); }
.chip-transfer { border-color: var(--slate); color: var(--slate); }
.chip-closed { border-style: dashed; }

/* A line that has just been filed says so. htmx puts htmx-settling on the row
   it swapped in, for the length of the settle phase only; the transition is
   what turns that instant into something an eye catches. Without it the whole
   feedback is a menu that quietly changed its wording. */
tr { transition: background-color 1.1s ease-out; }
/* A wash, not the full marine: the row keeps its own ink, and the amount
   keeps the green or the bordeaux that carries its sign. Filled marine made
   the label and the figure unreadable for the length of the flash. */
tr.htmx-settling { background: var(--flash); transition: none; }
@media (prefers-reduced-motion: reduce) {
  tr { transition: none; }
}

/* The likeliest events, offered as buttons in the row. A menu of thirty-seven
   costs a click to open, a scroll and a click to choose; these cost one, and
   the menu underneath still holds everything. */
.picks { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .35rem; }
.picks form { display: contents; }

button.chip {
  /* A chip, not a button: the row already carries two real buttons' worth of
     weight, and these have to read as a suggestion one may ignore. */
  min-height: 0;
  font: inherit;
  font-size: .78rem;
  font-weight: 400;
  padding: .18rem .5rem;
  background: var(--card-alt);
  /* Marine, like .chip-event: on a phone there is no hover to discover with,
     so the colour has to say "this does something" while it sits still. */
  color: var(--marine);
  border: 1px solid var(--marine);
  cursor: pointer;
}
button.chip:hover, button.chip:focus-visible {
  background: var(--marine);
  border-color: var(--marine);
  color: #fff;
}

/* A rule that gets taken back often enough to be worth re-reading. The sign
   is not colour alone: the figure is bold as well, because the column is a
   ratio and a colour-blind eye has to read it the same way. */
.rule-doubtful { color: var(--bordeaux); font-weight: 600; }

/* ---------- Forms ---------- */

form { margin: 0; }

label { display: block; margin-bottom: .75rem; font-size: .9rem; }
label > span { display: block; margin-bottom: .2rem; color: var(--ink-soft); }

input[type="text"], input[type="email"], input[type="password"], input[type="search"],
input[type="number"], input[type="file"], input[type="date"], select, textarea {
  width: 100%;
  padding: .55rem .65rem;
  min-height: 44px;
  font: inherit;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
}
textarea { min-height: 5rem; resize: vertical; }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--marine);
  outline-offset: 1px;
  border-color: var(--marine);
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0 1rem;
}

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: flex-end;
  margin-bottom: 1rem;
}
.filters label { margin: 0; min-width: 9rem; flex: 1 1 9rem; }
.filters .grow { flex: 2 1 16rem; }
.filters-actions { display: flex; gap: .5rem; }
/* A column title that sorts: the whole title is the target, and the arrow
   says which way, or that it could. */
th a.sort { color: inherit; text-decoration: none; display: inline-flex; gap: .3rem; align-items: center; }
th a.sort:hover { color: var(--ink); text-decoration: underline; }
th .sort-arrow { opacity: .45; }
th[aria-sort] .sort-arrow { opacity: 1; }

/* The list's filter bar: one row always shown, the rest folded under
   « Plus de filtres », close enough to read as one form. */
.filter-bar { margin-bottom: 1rem; }
.filter-bar .filters { margin-bottom: .5rem; }
.filter-bar .filters-actions .btn, .filter-bar .filters-actions button { white-space: nowrap; }
.more-filters > summary { cursor: pointer; font-size: .9rem; color: var(--ink-soft); width: max-content; }
.more-filters > summary:hover { color: var(--ink); }
.more-filters[open] > summary { margin-bottom: .4rem; }
/* Two bounds under one label: a period, an amount. An amount is shorter
   than a date, so its pair takes less room. */
.filters .range { min-width: 16rem; flex: 2 1 16rem; font-size: .9rem; }
.filters .range-amount { min-width: 12rem; flex: 1 1 12rem; }
.filters .range > span { display: block; margin-bottom: .2rem; color: var(--ink-soft); }
.filters .range > div { display: flex; align-items: center; gap: .4rem; }
.filters .range > div > input { flex: 1 1 0; min-width: 0; }
.filters .range > div > span { color: var(--ink-soft); }
.filters .range .amount { text-align: right; font-variant-numeric: tabular-nums; }
/* A class per row, every row on the same columns, the button as tall as the
   fields. Without the year there is no budget, and one column fewer. */
.class-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 8.5rem;
  gap: .5rem;
  align-items: end;
  border-top: 1px solid var(--line);
  padding-top: .6rem;
  margin-top: .6rem;
}
.class-row.class-row-nobudget { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) 8.5rem; }
.class-row label { margin: 0; min-width: 0; }
@media (max-width: 34rem) {
  .class-row, .class-row.class-row-nobudget { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.class-spent { grid-column: 1 / -1; margin: 0; font-variant-numeric: tabular-nums; }

button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  min-height: 44px;
  padding: .55rem 1rem;
  font: inherit;
  font-weight: 500;
  border-radius: 8px;
  border: 1px solid var(--marine);
  background: var(--marine);
  color: #fff;
  text-decoration: none;
  cursor: pointer;
}
button:hover, .btn:hover { background: var(--marine-dark); border-color: var(--marine-dark); color: #fff; }
.btn-secondary {
  background: var(--card);
  color: var(--marine);
}
.btn-secondary:hover { background: var(--card-alt); color: var(--marine); }
.btn-danger { background: var(--bordeaux); border-color: var(--bordeaux); }
.btn-danger:hover { background: #6d0a18; border-color: #6d0a18; }
.btn-small { min-height: 36px; padding: .3rem .6rem; font-size: .88rem; }
.btn-icon { width: 18px; height: 18px; }

/* The shoot button is for a phone in the hand at the shop counter. A desktop
   ignores `capture`, where it would only be a second file picker. */
/* A receipt is read, not filed: show it rather than name it. Square and
   cropped so a column of them lines up whatever shape the photos are. */
.att { display: flex; align-items: center; gap: .6rem; }
.thumb {
  width: 90px;
  height: 90px;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: 6px;
  display: block;
  background: var(--card-alt);
}

.shoot { display: none; }
@media (hover: none) and (pointer: coarse) {
  .shoot { display: block; margin-top: .75rem; }
  .shoot .btn { display: inline-flex; }
}

/* A card that folds. What it holds is worth keeping — the bank's own
   sentence, the justificatifs of a line that has none — but not a screenful
   of scrolling on the way to the form. */
.fold > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: .5rem;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary h2 { margin: 0; }
.fold > summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--ink-soft);
  font-size: .9rem;
  transform: rotate(-90deg);
}
.fold[open] > summary::after { transform: none; }
.fold[open] > summary { margin-bottom: .75rem; }

/* The line's footer: what happened to it last, and the one destructive
   action, which is not worth a card of its own. */
.txn-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.txn-meta { margin-top: -.5rem; }

.inline-form { display: flex; gap: .3rem; align-items: center; }
.inline-form select { min-height: 36px; padding: .2rem .4rem; font-size: .88rem; min-width: 11rem; }

.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1rem;
  color: var(--ink-soft);
  font-size: .9rem;
}

.dialog {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  padding: 1.25rem;
  max-width: 28rem;
}
.dialog::backdrop { background: rgba(0, 0, 0, .4); }
.dialog-actions { display: flex; gap: .5rem; justify-content: flex-end; margin-top: 1rem; }

/* ---------- Sign-in ---------- */

.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: var(--marine);
}
.login-card {
  width: 100%;
  max-width: 24rem;
  background: var(--card);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .2);
  padding: 1.75rem;
}
.login-brand {
  display: flex;
  align-items: center;
  gap: .6rem;
  font-size: 1.3rem;
  margin-bottom: .25rem;
  color: var(--marine);
}
.login-brand svg { width: 28px; height: 28px; }
.login-sub { color: var(--ink-soft); font-size: .9rem; margin: 0 0 1.25rem; }
.login-card button { width: 100%; }

/* ---------- Footer ---------- */

.footer {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1rem;
  border-top: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: .85rem;
}

/* ---------- Report ---------- */

.months { font-size: .9rem; }
.bar {
  display: block;
  height: 6px;
  border-radius: 3px;
  background: var(--line);
  overflow: hidden;
}
.bar > span { display: block; height: 100%; background: var(--marine); }

@media print {
  .header, .footer, .filters, .title-actions, .pager, form { display: none !important; }
  body { background: #fff; }
  .card { box-shadow: none; border: 0; padding: 0; }
  th { position: static; }
  a { color: inherit; text-decoration: none; }
}

/* ---------- Charts ----------
   Inline SVG, no library. The colours are a diverging pair, not the green and
   red of the tables: under deuteranopia this application's green and bordeaux
   separate by a ΔE of 2.4 on the dark surface — indistinguishable — and on a
   chart the sign is the whole content. This pair was validated for both
   surfaces (lightness band, chroma floor, CVD separation, contrast).
   Light: ΔE 20.0 (protan) · Dark: ΔE 24.2 (protan). */

:root {
  --chart-in: #0369a1;   /* un excédent */
  --chart-out: #b45309;  /* un déficit */
  --chart-axis: #c3cad4;
  /* The recessive line on the treasury chart. Not --ink-soft, which it used
     to borrow: against the surplus blue that grey separates by a ΔE of 9.3
     for a normal-vision reader, under the floor of 15 at which two marks
     stop being told apart. This one measures 15.2, and the stroke width and
     the name at the line's end carry the rest. */
  --chart-line: #334155;
}

@media (prefers-color-scheme: dark) {
  :root {
    --chart-in: #2b90d9;
    --chart-out: #c87a26;
    --chart-axis: #3a4555;
    /* On the dark surface the old grey was worse still, ΔE 14.6; going
       lighter rather than darker takes it to 24.5 and reads better besides. */
    --chart-line: #c3cdd9;
  }
}

/* The palette a chart wears once it leaves the application. A chart is copied
   into a slide or a report, and a slide has no dark mode: whatever the screen
   is showing, the exported image is the light pair on white paper. These are
   deliberately NOT redefined under prefers-color-scheme, which is the whole
   point — app.js reads them to repaint the copy. */
:root {
  --export-paper: #ffffff;
  --export-ink: #10151c;
  --export-soft: #5b6676;
  --export-axis: #c3cad4;
  --export-in: #0369a1;
  --export-out: #b45309;
  --export-line: #334155;
}

.chart { margin: 0 0 .5rem; }

.chart-caption {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  font-size: .9rem;
  color: var(--ink-soft);
  margin-bottom: .5rem;
}
.chart-scale { font-variant-numeric: tabular-nums; white-space: nowrap; }

.chart-svg { display: block; width: 100%; height: auto; overflow: visible; }

/* Recessive: the bars are the content, the baseline is furniture. */
.chart-axis { stroke: var(--chart-axis); stroke-width: 1; }

.chart-bar-in { fill: var(--chart-in); }
.chart-bar-out { fill: var(--chart-out); }

/* The row the reader came from: a ring in the surface colour, not a fourth hue. */
.chart-row-here .chart-bar {
  stroke: var(--card);
  stroke-width: 2;
  paint-order: stroke;
}
.chart-row-here .chart-label { font-weight: 700; fill: var(--ink); }

/* Text wears ink, never the series colour. */
.chart-label, .chart-value, .chart-sublabel {
  font-family: inherit;
  fill: var(--ink);
}
.chart-label { font-size: 12px; }
.chart-sublabel { font-size: 10px; fill: var(--ink-soft); }
.chart-value { font-size: 11.5px; font-variant-numeric: tabular-nums; }

/* The hover layer an SVG chart owes the reader: the <title> gives the native
   tooltip, this gives the feedback that the row is live. */
.chart-row a { cursor: pointer; }
.chart-row:hover .chart-bar { filter: brightness(1.12); }
.chart-row:hover .chart-label { text-decoration: underline; }
.chart-svg a { text-decoration: none; }

@media print {
  /* Ink is expensive and the committee prints this: outline the bars. */
  .chart-bar-in, .chart-bar-out { fill: #fff; stroke: #333; stroke-width: 1; }
  .chart-bar-out { stroke-dasharray: 3 2; }
}

/* The legend: two meanings live in the fill, so identity is never colour
   alone — the words sit next to the swatches. */
.chart-legend {
  display: flex;
  align-items: center;
  gap: .9rem;
  flex-wrap: wrap;
}
.chart-key { display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap; }
.chart-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}
/* The swatches are <span>s, so they need background rather than fill. */
span.chart-bar-in { background: var(--chart-in); }
span.chart-bar-out { background: var(--chart-out); }

/* A value that will not fit beside its bar sits on it. White clears 4.5:1 on
   both light fills (5.9 and 5.0); on the lighter dark-mode fills it only
   reaches 3.4, so there the ink is the dark one (4.5 and 4.7). */
.chart-value-inside { fill: #ffffff; font-weight: 600; }

@media (prefers-color-scheme: dark) {
  .chart-value-inside { fill: #10151c; }
}

/* A grouped chart names each block once, above its three bars. */
.chart-head { font-size: 12px; font-weight: 700; fill: var(--ink); }

/* ---------- The line form ----------
   Colour carries no identity: every line is named at its right end. The one
   colour is the surplus blue, on the single line that matters — what the
   association holds all told. The accounts behind it are drawn in soft ink,
   which keeps them legible without competing for the eye. */
.chart-grid { stroke: var(--chart-axis); stroke-width: 1; }
.chart-line { fill: none; stroke: var(--chart-line); stroke-width: 1.2; }
.chart-line-total { stroke: var(--chart-in); stroke-width: 2.5; }
.chart-tick {
  font-family: inherit;
  font-size: 10.5px;
  fill: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.chart-end {
  font-family: inherit;
  font-size: 10.5px;
  fill: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.chart-end-total { fill: var(--ink); font-weight: 700; }
span.chart-swatch-total { background: var(--chart-in); height: 3px; border-radius: 1px; }
span.chart-swatch-account { background: var(--chart-line); height: 2px; border-radius: 1px; }

@media print {
  .chart-line { stroke: #666; }
  .chart-line-total { stroke: #111; }
}

/* The door out: the picture to a slide, the figures to a spreadsheet. */
.chart-actions {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  margin-top: .4rem;
}
.chart-said {
  font-size: .85rem;
  color: var(--ink-soft);
}
.chart-data { display: none; }

@media print {
  .chart-actions { display: none; }
}

/* A rule is a block of two rows on a wide screen: the words it looks for span
   both, then where and which way, then what it posts. Every rule uses the
   same columns, so the list reads down as well as across. */
.rule {
  display: grid;
  grid-template-columns: minmax(0, 2fr) repeat(3, minmax(0, 1fr)) minmax(0, 1.3fr);
  gap: .5rem;
  align-items: end;
  padding: .75rem 0;
  border-top: 1px solid var(--line);
}
.rule label { margin: 0; min-width: 0; }
.rule .rule-words { grid-row: span 2; align-self: stretch; display: flex; flex-direction: column; }
.rule .rule-words textarea { flex: 1; min-height: 5rem; resize: vertical; }
.rule .rule-words .line-sub { margin-top: .3rem; }
.rule-actions { display: flex; gap: .5rem; }
/* The last column holds both buttons: a little wider, and the two of them a
   little more compact than a lone button. */
.rule-actions button { flex: 1; padding-inline: .6rem; font-size: .9rem; white-space: nowrap; }
@media (max-width: 70rem) {
  .rule { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .rule .rule-words { grid-column: 1 / -1; grid-row: auto; }
}
@media (max-width: 34rem) {
  .rule { grid-template-columns: minmax(0, 1fr); }
}

/* An activity listed under the pot it was run for. Indented by a drawn glyph
   rather than by padding, so the figures stay in their column and the table
   still copies into a spreadsheet with its rows aligned. */
.row-child > td:first-child { padding-left: 1.2rem; }
.row-tree { color: var(--muted); margin-right: .15rem; }

/* What an amount box says about itself while it is being typed: the figure as
   the rest of the pages print it, or why it will not be accepted. */
.field-note { display: block; margin-top: .3rem; font-size: .85rem; color: var(--ink-soft); }
.field-note:empty { display: none; }
.field-note.is-bad { color: var(--out); }
input[aria-invalid="true"] { border-color: var(--out); }

/* ---------- La fiche d'une opération ---------- */
/* Classer et justifier sont le même geste, donc les deux sont à l'écran en
   même temps: le classement à gauche, où l'on tape, les pièces à droite, où
   l'on dépose. Sous 64rem il n'y a plus deux colonnes à faire, et l'ordre
   retombe sur celui de la lecture. */
.txn-layout {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: 1rem;
  align-items: start;
}
@media (max-width: 64rem) { .txn-layout { grid-template-columns: 1fr; } }

/* La colonne de droite empile ce qu'on regarde: les pièces, puis la phrase
   de la banque. */
.txn-side { display: grid; gap: 1rem; align-content: start; }
.txn-side h2 { margin-top: 0; }
.txn-files h2 { margin-top: 0; }
.txn-files .files { list-style: none; margin: 0 0 1rem; padding: 0; }
.txn-files .files li {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--line);
}
.txn-files .files li:last-child { border-bottom: 0; }
.file-open { display: flex; align-items: center; gap: .75rem; flex: 1; min-width: 0; text-decoration: none; }
.file-name { display: flex; flex-direction: column; min-width: 0; }
.file-name .line-sub { color: var(--ink-soft); font-size: .85rem; }
.thumb-doc {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius);
  background: var(--card-alt);
  border: 1px solid var(--line);
  color: var(--ink-soft);
}

/* Le dépôt: une cible franche, qui réagit pendant qu'on survole avec un
   fichier. Le pointillé dit « lâchez ici » sans un mot de plus. */
.dropzone { margin: 0; }
.dropzone-target {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .25rem;
  margin: 0;
  padding: 1.5rem 1rem;
  text-align: center;
  cursor: pointer;
  border: 2px dashed var(--line);
  border-radius: var(--radius);
  background: var(--card-alt);
  transition: border-color .12s, background .12s;
}
.dropzone-target:hover { border-color: var(--marine); }
.dropzone-target .btn-icon { width: 1.5rem; height: 1.5rem; color: var(--ink-soft); }
.dropzone-target .hint { margin: 0; }
.dropzone.is-over .dropzone-target { border-color: var(--marine); background: var(--flash); }
.dropzone-title { margin-top: .75rem; }

/* Ce que la banque a écrit: lu, jamais tapé, donc pas une carte. Un filet à
   gauche et un fond retiré, juste au-dessus du champ qu'on recopie depuis
   lui — rapprocher la source de l'endroit où elle sert suffit à équilibrer
   les deux colonnes, sans l'avoir cherché. */
.source {
  margin: 0 0 1rem;
  padding: .7rem .9rem;
  border-left: 3px solid var(--marine);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--card-alt);
}
.source-label {
  display: block;
  margin-bottom: .2rem;
  font-size: .78rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.source .statement + .statement { margin-top: .35rem; }

.statement { white-space: pre-wrap; color: var(--ink-soft); margin: 0; }

/* ---------- Announced expenses ---------- */

/* The badge in the header: how many tickets are waiting for a line. It is
   never drawn at zero — a counter showing nothing is a standing reproach. */
.nav-count {
  background: rgba(255, 255, 255, .28);
  border-radius: 999px;
  padding: 0 .4rem;
  font-size: .78rem;
  font-weight: 700;
  margin-left: .1rem;
}

/* One row per ticket: the thumbnail, what is known about it, what to do
   with it. The answer to "has its line turned up?" sits under the label, in
   a rule whose colour already carries the yes or the no. */
.waiting { list-style: none; margin: 0; padding: 0; }
.waiting > li {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) auto;
  gap: .9rem;
  padding: .9rem 0;
  border-bottom: 1px solid var(--line);
  align-items: start;
}
.waiting > li:last-child { border-bottom: 0; }
.waiting .shot {
  width: 64px;
  height: 64px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  object-fit: cover;
  display: block;
  background: var(--card-alt);
}
.waiting .shot.thumb-doc { width: 64px; height: 64px; }
.said-box { min-width: 0; }
.said { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; }
.said .amount { font-weight: 600; font-size: 1.05rem; }
.waiting .acts { display: flex; flex-direction: column; gap: .35rem; align-items: stretch; }

/* The verdict. Green: a line matches. Grey: none does, either because it
   has not had time or because it never will. The rule carries the colour,
   the icon carries the sign and the sentence says why — the same thing said
   three times, as on the charts. */
.found, .none {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  margin-top: .45rem;
  padding: .45rem .6rem;
  background: var(--card-alt);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .9rem;
}
.found { border-left: 3px solid var(--green); }
.none { border-left: 3px solid var(--ink-soft); color: var(--ink-soft); }
.found > svg, .none > svg { width: 16px; height: 16px; flex: none; }
.found > svg { color: var(--green); }
.found .hint, .none .hint { margin: 0; }

/* One ticket's screen is a phone screen first: the photograph large,
   because it is what says which expense this is. */
.phone-shot {
  width: 100%;
  max-width: 24rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  display: block;
}

/* The pairings at the end of an import: the announcement on the left, the
   line the bank has just sent on the right, the arrow between them. */
.pairs td { vertical-align: middle; }
.pair-arrow { color: var(--ink-soft); text-align: center; width: 2rem; }
.cell-shot { display: flex; align-items: center; gap: .6rem; }
.shot-sm {
  width: 40px;
  height: 40px;
  border-radius: 6px;
  border: 1px solid var(--line);
  object-fit: cover;
  display: block;
  background: var(--card-alt);
}

/* Where the classification of a line nobody classified by hand came from.
   The same shape as "Ce que la banque a écrit": read, never typed. */
.from-said {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  margin: 0 0 .9rem;
  padding: .5rem .7rem;
  border-left: 3px solid var(--marine);
  background: var(--card-alt);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .9rem;
  color: var(--ink-soft);
}
.from-said svg { width: 16px; height: 16px; flex: none; color: var(--marine); margin-top: .15rem; }

/* A chip that was chosen stays visibly chosen: without this, clicking one
   leaves no mark at the place that was clicked. */
.chip-pick[aria-pressed="true"] {
  background: var(--marine);
  border-color: var(--marine);
  color: #fff;
}

@media (max-width: 34rem) {
  .waiting > li { grid-template-columns: 48px minmax(0, 1fr); }
  .waiting .shot, .waiting .shot.thumb-doc { width: 48px; height: 48px; }
  .waiting .acts { grid-column: 1 / -1; flex-direction: row; flex-wrap: wrap; }
}

/* ---------- Classes ---------- */

/* A line's classes as boxes to tick: several at once, and each one a tap
   on a phone. */
.class-picks { border: 0; padding: 0; margin: 0 0 1rem; min-width: 0; }
.class-picks legend { padding: 0; margin-bottom: .35rem; font-size: .9rem; color: var(--ink-soft); }
.chip-check {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .25rem .6rem;
  font-size: .85rem;
  cursor: pointer;
}
.chip-check input { margin: 0; width: auto; min-height: 0; }
.chip-check:has(input:checked) { border-color: var(--marine); color: var(--marine); background: var(--card); }
/* Proposed, not chosen: dashed until somebody ticks it. */
.chip-check.is-suggested:not(:has(input:checked)) { border-style: dashed; border-color: var(--marine); }
.chip-class { border-color: var(--slate); color: var(--slate); }

/* The Classement column: event and class, each a short line of one-click
   suggestions, the full menu folded behind "autre…". */
.cls-cell { display: flex; flex-direction: column; gap: .3rem; align-items: flex-start; }
.cls-row { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center; }
.cls-row form { display: contents; }
.cls-label {
  flex: none;
  width: 5.6rem;
  font-size: .7rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-soft);
}
.chip-done { background: rgba(52, 76, 119, .08); border-color: transparent; color: var(--marine); }
.chip-donec { background: rgba(59, 96, 100, .10); border-color: transparent; color: var(--slate); }
.chip-pickc {
  background: var(--card);
  border: 1px dashed var(--slate);
  color: var(--slate);
  cursor: pointer;
}
.other, .other > summary { font-size: .82rem; color: var(--ink-soft); text-decoration: underline; cursor: pointer; }
details.other > summary { list-style: none; }
details.other > summary::-webkit-details-marker { display: none; }
details.other[open] { flex-basis: 100%; text-decoration: none; }
details.other[open] > summary { margin-bottom: .3rem; }
/* A line still to classify says so at its edge, in the colour of a deficit. */
.row-todo > td:first-child { box-shadow: inset 4px 0 0 rgba(136, 13, 30, .5); }

/* ---------- Classer une à une ---------- */

.triage-count { margin: -.5rem 0 .3rem; }
.progress { height: 6px; background: var(--line); border-radius: 999px; overflow: hidden; margin: 0 0 1rem; }
.progress > span { display: block; height: 100%; background: var(--marine); }
.triage { display: grid; grid-template-columns: minmax(0, 1fr) 20rem; gap: 1rem; align-items: start; }
.triage-side { display: grid; gap: 1rem; }
.triage-side h2 { margin-top: 0; }
@media (max-width: 52rem) { .triage { grid-template-columns: 1fr; } }
.the-line { display: grid; gap: .35rem; }
.the-line .amount { font-size: 2rem; font-weight: 700; }
.the-line .said { font-size: 1.35rem; font-weight: 600; color: var(--ink); text-decoration: none; }
.bank { color: var(--ink-soft); font-size: .9rem; border-left: 3px solid var(--line); padding-left: .6rem; }
.step { margin-top: 1.1rem; }
.step h3 { font-size: .95rem; margin: 0 0 .5rem; }
.step h3 small { font-weight: 400; color: var(--ink-soft); }
.big-picks { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.big-picks select { max-width: 16rem; }
.big-pick {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-height: 44px;
  padding: .45rem .9rem;
  border: 1.5px solid var(--marine);
  border-radius: 12px;
  color: var(--marine);
  background: var(--card);
  font-weight: 600;
  cursor: pointer;
}
.big-pick input { position: absolute; opacity: 0; pointer-events: none; }
.big-pick kbd { font: 600 .72rem ui-monospace, monospace; border: 1px solid currentColor; border-radius: 4px; padding: 0 .3rem; opacity: .7; }
.big-pick:has(input:checked) { background: var(--marine); color: #fff; }
.big-pick:has(input:focus-visible), .cls-btn:has(input:focus-visible) { outline: 2px solid var(--marine); outline-offset: 2px; }
.cls-grid { display: flex; flex-wrap: wrap; gap: .45rem; }
.cls-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  min-width: 3.2rem;
  padding: .4rem .8rem;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  cursor: pointer;
}
.cls-btn input { position: absolute; opacity: 0; pointer-events: none; }
.cls-btn.sug { border-style: dashed; border-color: var(--slate); color: var(--slate); }
.cls-btn:has(input:checked) { background: var(--slate); border: 1.5px solid var(--slate); color: #fff; }
.actions { display: flex; gap: .6rem; margin-top: 1.4rem; align-items: center; flex-wrap: wrap; }
.actions .hint { margin: 0; }
.side-thumb { width: 100%; border-radius: var(--radius); border: 1px solid var(--line); display: block; }
.mini { list-style: none; padding: 0; margin: 0; font-size: .88rem; }
.mini li { display: flex; justify-content: space-between; gap: .5rem; padding: .4rem 0; border-bottom: 1px solid var(--line); }
.mini li:last-child { border: 0; }

/* The way back from a one-click classification, on the row it just changed. */
.undo { display: contents; }
.undo-btn {
  min-height: 0;
  padding: .1rem .5rem;
  border: 0;
  background: none;
  color: var(--marine);
  font-size: .82rem;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}

/* ---- Read-only account -----------------------------------------------------
   The server refuses every write of a read-only account (readOnlyMayDo); this
   only spares it the buttons. app.js disables the controls of every POST form,
   which keeps their values on screen; what only exists to write is marked
   data-writes and goes. */
.read-only [data-writes],
.read-only button[data-locked] { display: none !important; }
/* A field that cannot be changed must not look like one that can: no arrow
   on a menu, a dashed edge, the value in full ink (Safari greys it). Boxes
   and radios are left alone: appearance would take the tick off a box, and
   the triage screen hides its radios behind the labels on purpose. */
.read-only :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea):disabled {
  appearance: none;
  background: var(--card-alt);
  border-style: dashed;
  color: var(--ink);
  -webkit-text-fill-color: var(--ink);
  opacity: 1;
  cursor: default;
}

/* A fieldset that only groups fields to disable them together (the
   classification of an announced expense, for a read-only account). */
.plain-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
/* The read-only account's upload: a plain form, the description required. */
.ro-upload { margin-top: 1rem; }

/* ---- Access accounts (/users) ---------------------------------------------
   One account per block. Its two forms share a three-column grid, so the
   fields line up and both buttons sit in the last column, as tall as the
   fields beside them. */
.account { border-top: 1px solid var(--line); padding-top: .75rem; margin-top: .75rem; }
.account-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.account-head p { margin: 0; }
.account-row {
  display: grid;
  grid-template-columns: 1fr 1fr 8.5rem;
  gap: .5rem;
  align-items: end;
  margin-bottom: .6rem;
}
.account-row label { margin: 0; }
.account-row .account-wide { grid-column: span 2; }
.account-row button { width: 100%; }
@media (max-width: 560px) {
  .account-row { grid-template-columns: 1fr; }
  .account-row .account-wide { grid-column: auto; }
}

/* ---- The transactions table on a phone ------------------------------------
   Four columns do not fit 390px: the amount was cut at the edge and the event
   had to be scrolled to. Each line becomes a small card instead — the date
   and the amount on top, the libellé, then the event. The column titles stay
   as a row of sort links. */
@media (max-width: 34rem) {
  .txn-table, .txn-table tbody, .txn-table tfoot { display: block; }
  .txn-table thead tr { display: flex; gap: .25rem; }
  .txn-table thead th { position: static; flex: 1; border: 0; }
  .txn-table thead th:last-child { display: none; }
  .txn-table tbody tr, .txn-table tfoot tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: .75rem;
    row-gap: .3rem;
    padding: .6rem .55rem;
    border-bottom: 1px solid var(--line);
  }
  .txn-table td { display: block; border: 0; padding: 0; }
  .txn-table tbody td:nth-child(3) { grid-column: 2; grid-row: 1; }
  .txn-table tbody td:nth-child(2), .txn-table tbody td:nth-child(4) { grid-column: 1 / -1; }
  .txn-table tfoot td:empty { display: none; }
  .txn-table .row-todo { box-shadow: inset 4px 0 0 rgba(136, 13, 30, .5); }
  .txn-table .row-todo > td:first-child { box-shadow: none; }
}
