/* Four cascade layers, lowest first (C23). A rule in a later layer wins
   over one in an earlier layer whatever either selector's weight, so a
   general rule can no longer beat the specific rule written for the case
   in front of it - the trap that cost the search's clear button in C7 and
   three rules in C8. Specificity and order still decide inside a layer.

   - base: tokens, the elements, the form grammar and the general classes
     (.list, .aside, .error, the disabled field) - wherever in this file
     they stand. Three sit further down, each in a base block of its own.
   - components: cards, tiles and folds, the parts every screen is built of.
   - screens: one screen's own rules, and a state that has to beat a
     component's resting look (a pressed toggle).
   - overlays: what opens over a page and must not take a row's size from
     the row that opened it - the confirmation's two answers.

   The weights quoted in older comments below, «(0,2,1) against ...», are
   the competition inside one layer, or date from before the layers. A new
   rule goes into the layer of what it is about; it does not need more
   weight than a general rule any more. */
@layer base, components, screens, overlays;

@layer base {
:root {
  --bg: #faf6f0;
  --surface: #fffdfa;
  --border: #e6dccd;
  --text: #2f2a24;
  --muted: #7a7065;
  --terracotta: #c65d3b;
  /* The same hue for running text: #c65d3b measures 3.88:1 on the light
     background, under the 4.5:1 a body-size line needs; this shade clears
     it on both bg and surface. Fills, borders and hover flashes keep
     --terracotta - white on a darker fill would drift the other way. */
  --terracotta-ink: #b5502f;
  /* The red that says «this removes something». It stood as a literal in
     seven rules and in no variable, which is also why it never got the dark
     treatment --terracotta-ink got: measured on #1b1815 it was 2.94:1, under
     even the 3:1 a plain UI element needs, and 2.66:1 on a card. The value
     below is the light one, unchanged; the dark block lightens it. */
  --danger: #b03a2e;
  /* The two grounds a message box stands on (C15 section 1): a form's
     refusal on a red tint, a notice on a gold one. Measured with the WCAG
     formula: --text on them 11.02:1 and 11.53:1, --danger (the refusal's
     icon) 4.67:1 on its tint; against --surface 1.27 and 1.21, against
     --bg 1.20 and 1.15, so the box shows on a card and on the page. The
     gold icon measures 1.98:1 on its tint - it is ornament, the sentence
     beside it carries the notice, as the gold rule did before. */
  --danger-tint: #f7ddd8;
  --notice-tint: #f5e6c8;
  /* Lucide 1.47.0 «circle-alert» and «info» (ISC, see static/icons/
     symbols.svg), as masks: the stroke only has to be opaque, the colour
     comes from the box's own rule colour. */
  --icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cline x1='12' x2='12' y1='8' y2='12'/%3E%3Cline x1='12' x2='12.01' y1='16' y2='16'/%3E%3C/svg%3E");
  --icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
  --olive: #5b7a4a;
  --gold: #d99a2b;
  --radius: 10px;
  /* A select's arrow (see select below), drawn in --muted. A data URI cannot
     read a variable, so the dark block repeats it in the dark shade. */
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237a7065' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  /* What the browser draws itself - a radio, a tick, a date picker's panel,
     a select's arrow, a scrollbar - follows this and nothing in the palette
     above. Without it those controls stayed light on the dark theme, and an
     unchecked radio came out a bright white disc beside a checked one that
     was merely terracotta: the wrong one of the two read as chosen. */
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1b1815;
    --surface: #262119;
    --border: #3a332a;
    --text: #f0e9df;
    --muted: #a89c8d;
    /* Ink and olive brighten the way --muted does: dark ground needs the
       lighter shade for the same 4.5:1 that light ground needed the darker
       one. */
    --terracotta-ink: #d97d5c;
    /* 5.41:1 on this background and 4.90:1 on a card, where the light red
       measured 2.94 and 2.66. The red sibling of --terracotta-ink, lightened
       by the same amount and for the same reason. */
    --danger: #d9705f;
    /* --text on them 11.93:1 and 11.24:1, --danger 4.41:1 on its tint
       (an icon, 3:1 is its bar), gold 5.55:1; against --surface 1.11 and
       1.18, against --bg 1.23 and 1.31. */
    --danger-tint: #3b2421;
    --notice-tint: #362d1e;
    --olive: #7fa06a;
    --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a89c8d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    /* --terracotta stays as it is on purpose: everything it colours here is
       a fill, a border or a hover flash, where 3:1 non-text contrast holds.
       Anything that is text wears the ink - that is the token's charter at
       the top of this file, and it is what this block already lightens. */
  }
}

* { box-sizing: border-box; }

/* The browser's own rule for `hidden` is `display: none` at the weight of a
   single element selector, so any rule here that names a display - a flex
   banner, a grid row - silently outranks it and shows what the server or a
   script had just hidden. The tip draft's banner appeared empty on every
   entry screen that way. `hidden` is used across this app as the plain
   answer to «not now» (the rate line, both mode panels, the offline notes),
   so it is settled once, here, rather than guarded rule by rule. */
[hidden] { display: none !important; }

/* A link is marked by its colour alone. Hovering moves the colour to the
   body text's, which is unmistakable in either theme and never less legible
   than the resting state.

   The resting colour has to be said out loud: without it every link inside
   a page fell back to the browser's own blue, the one colour that appears
   nowhere else in this app, while the two rules below quietly implied a
   palette that was never applied. The topbar sets its own and still does. */
a { text-decoration: none; color: var(--terracotta-ink); }
a:hover { color: var(--text); }

/* A page name is a label, not a headline: the browser's 2em default made a
   headline of it, twice the size a label needs. */
h1 { font-size: 1.5rem; }

/* A symbol from static/icons/symbols.svg, drawn at the size of the type it
   stands in - so a symbol in a smaller row is smaller with it, and none of
   them carries a pixel size of its own. The baseline nudge is what puts it
   level with the letters beside it rather than sitting on the line. */
.icon { width: 1em; height: 1em; vertical-align: -0.125em; stroke: currentColor; fill: none; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  padding-bottom: env(safe-area-inset-bottom);
}

/* The insets go on the padding, never on the body: the bar is meant to run
   edge to edge, and a body with side padding would leave a strip of page
   background beside it. In landscape the cutout sits at one side, which is
   where viewport-fit=cover would otherwise put the first word of the nav. */
.topbar {
  display: flex;
  /* Centred, not on a baseline: the trip control is a flex box whose first
     child is a symbol, and a symbol has no baseline of its own. The engine
     synthesises one from its lower edge, which stood the trip and its name
     two pixels above every word beside them. */
  align-items: center;
  gap: 1.5rem;
  padding: 0.75rem 1rem;
  padding-top: calc(0.75rem + env(safe-area-inset-top));
  padding-left: calc(1rem + env(safe-area-inset-left));
  padding-right: calc(1rem + env(safe-area-inset-right));
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
}

.topbar .brand { font-weight: 700; color: var(--terracotta); }

/* The bar of a signed-in admin with no trip to name yet (base.html). Its
   entries are words with no symbol, so they are deliberately not a <nav> -
   the rule further down fixes every .topbar nav to the foot of a phone as a
   row of symbols with a caption under each, which is not what these are. */
.topbar-admin > a:not(.brand) { color: var(--muted); }
.topbar-admin > a.active { color: var(--text); font-weight: 600; }
.topbar-admin > form { margin-left: auto; }

/* --- The four places one walks between ---------------------------------
   One list in the document, in two positions: inside the bar where the row
   is wide enough for its words, and fixed to the foot of the screen on a
   phone, where the top of a 6-inch screen is out of a thumb's reach and iOS
   puts its own tabs. The switch is the media query at the end of this
   block. */
/* min-width: 0 and a basis of auto, or the row cannot give anything back:
   with flex-basis 0 a flex item's shrink factor is scaled by that basis and
   comes out at nothing, so a bar one word too wide pushed the gear off the
   screen instead of shortening the word. */
.topbar nav { display: flex; flex: 1 1 auto; min-width: 0; gap: 1rem; }
/* position: relative for the tap target below. flex: none so a row that is
   one word too wide never squeezes a symbol - the word gives way instead. */
.topbar nav a { color: var(--muted); white-space: nowrap; position: relative; flex: none; }
/* Where you are, said twice over: colour and weight. Colour alone would be
   the same signal a:hover already uses, so a finger resting on a link would
   read as two places at once. */
.topbar nav a.active { color: var(--text); font-weight: 600; }
/* The entry one stands on is the only one that may give width back: it
   shortens to an ellipsis rather than pushing the gear off the edge, which
   is the failure this bar was rebuilt for. */
.topbar nav a.active { flex: 0 1 auto; min-width: 0; overflow: hidden;
                       text-overflow: ellipsis; }
/* The word an entry carries, and the space between it and its symbol. The
   margin sits on the word rather than on the symbol, so a layout that drops
   the word takes the gap with it. */
.topbar nav a > span { margin-left: 0.35rem; }
/* The whole row is words and symbols at this size, so the target a finger
   gets is the word; the overlay is what the symbols alone would lack. */
.topbar nav a::after { content: ""; position: absolute; inset: -0.7rem -0.4rem; }

/* --- The trip, and the settings ----------------------------------------
   Both stay in the bar at every width: neither is a place one walks to, and
   the trip is the one thing here that no symbol can say.

   The trip is also the only thing on the row that may shrink, because it is
   the only one whose width nobody controls; the name is cut off, never a
   symbol. Two auto margins in one flex row would share the free space
   between them and put the trip in the middle, so the gear gives its own up
   wherever the trip stands before it. */
.topbar .settings-link { margin-left: auto; }
.topbar .trip-here { margin-left: auto; position: relative;
                     flex: 0 1 auto; min-width: 0; }
.topbar .trip-here ~ .settings-link { margin-left: 0; }
/* The gear never says its name, at any width and on its own page too: a
   symbol standing directly after four words reads as a fifth of them. The
   word stays in the document for the screen reader; aria-label is what names
   the link once display:none takes it out of that tree. */
.topbar .settings-link { color: var(--muted); position: relative; flex: none; }
.topbar .settings-link.active { color: var(--text); }
.topbar .settings-link > span { display: none; }
.topbar .settings-link::after { content: ""; position: absolute; inset: -0.7rem -0.4rem; }

.topbar .trip-switch > summary,
.topbar .trip-name {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--muted);
  white-space: nowrap;
}
.topbar .trip-switch > summary { cursor: pointer; }
/* display:flex takes the disclosure triangle off everywhere but WebKit, which
   needs to be told. */
.topbar .trip-switch > summary::-webkit-details-marker { display: none; }
.topbar .trip-switch > summary:hover { color: var(--text); }
/* One chevron in the set, turned to point the way the fold moves. */
.topbar .trip-switch-more { width: 0.75em; height: 0.75em; flex: none; }
.topbar .trip-switch[open] > summary .trip-switch-more { transform: rotate(90deg); }
.topbar .trip-here .icon { flex: none; }
/* A name of any length is what this bar was carrying on the far left before.
   Here it is cut off instead - at 9rem where the row is wide enough for it,
   and at whatever the row can spare where it is not. The open list names
   every trip in full. */
.topbar .trip-switch > summary > span,
.topbar .trip-name > span { max-width: 9rem; min-width: 0; overflow: hidden;
                            text-overflow: ellipsis; }

/* The panel hangs off the bar rather than pushing it apart, aligned to its
   own right edge so a long name grows leftwards into the page instead of off
   the screen. A border, not a shadow: nothing else in this app casts one. */
.topbar .trip-switch > ul {
  position: absolute;
  top: 100%;
  right: 0;
  margin: 0.5rem 0 0;
  padding: 0.3rem 0;
  min-width: 11rem;
  max-width: 16rem;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  z-index: 11;
}
.topbar .trip-switch > ul a { display: block; padding: 0.5rem 0.9rem; color: var(--text); }
.topbar .trip-switch > ul a:hover { color: var(--terracotta); }
.topbar .trip-switch > ul a.active { font-weight: 600; }
/* The way to a trip that is not in the list yet. Separated by a rule, because
   it is not one of the trips above it. */
.topbar .trip-switch-manage { border-top: 1px solid var(--border); margin-top: 0.3rem;
                              padding-top: 0.3rem; }
.topbar .trip-switch-manage a { color: var(--muted); }

/* --- The same four places, at the foot of the screen -------------------
   Four German words plus the trip name measured 715 px across, so below
   46rem the row cannot be a row. It becomes what iOS puts at the bottom of
   every app of this shape: a symbol over its word, one cell per place, the
   whole cell a target. The bar above keeps the app, the trip and the gear -
   three things one reaches for rarely and on purpose.

   position: fixed rather than a second list in the document: one nav, one
   accessibility tree, one place where an entry is added. A sticky ancestor
   is no containing block for it, so it is the viewport this is fixed to. */
@media (max-width: 46rem) {
  .topbar { gap: 1rem; }
  .topbar nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    gap: 0;
    padding: 0.35rem 0 calc(0.35rem + env(safe-area-inset-bottom));
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
    background: var(--surface);
    border-top: 1px solid var(--border);
  }
  .topbar nav a,
  .topbar nav a.active {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.1rem;
    padding: 0.2rem 0.2rem 0.1rem;
    font-size: 0.7rem;
    line-height: 1.3;
  }
  /* The symbol is drawn at the size of the type it stands in everywhere else
     in this app; here the type is a caption under it and the symbol is the
     part one aims at, so it is given its own size. */
  .topbar nav a .icon { width: 1.4rem; height: 1.4rem; }
  .topbar nav a > span { margin-left: 0; max-width: 100%; overflow: hidden;
                         text-overflow: ellipsis; }
  /* The cell is the target now, and it is larger than any overlay was. */
  .topbar nav a::after { content: none; }
  /* Room for the name, since the row above it holds three things instead of
     seven. */
  .topbar .trip-switch > summary > span,
  .topbar .trip-name > span { max-width: 12rem; }
}

/* One column for every page, centred under a bar that runs edge to edge.

   Sixty rem suited a list of entry cards and nothing that pairs a word with
   a value: at 1100 px «Andreas» stood a hand's width away from the box
   holding his factor. So every form and paired list carried a forty-rem cap
   of its own, and stood at the left edge of the column it was capped
   inside. The entry card ran on past the mask it held, and tip, balance and
   settings sat left of the screen's middle. A column that is itself the
   reading width puts every tab between the same two edges, and switching
   tabs moves nothing sideways.

   Forty-six rem less the padding leaves forty-four for the content, which
   still holds an item line's label, count and sum on one row. A phone is
   narrower than that, so the phone layout is untouched by it. */
main {
  max-width: 46rem;
  margin: 0 auto;
  padding: 1rem;
  padding-left: calc(1rem + env(safe-area-inset-left));
  padding-right: calc(1rem + env(safe-area-inset-right));
}

/* Where the four places are fixed to the foot of the screen, the page has to
   end above them rather than behind them - and the inset below belongs to
   that bar now rather than to the body. After the rule above rather than up
   with the rest of the bar, because `padding` there is a shorthand and would
   reset this whatever the media query said. */
@media (max-width: 46rem) {
  body { padding-bottom: 0; }
  main { padding-bottom: calc(4rem + env(safe-area-inset-bottom)); }
}

/* One step up. Above the heading and at the same spot on every page that has
   one, rather than beside a title whose length differs per page. Muted,
   because it is the way out and not the subject. */
.back-link { display: inline-flex; align-items: center; gap: 0.4rem;
             margin-bottom: 0.7rem; color: var(--muted); }

/* font-family and an explicit size rather than `font: inherit`: a field
   standing inside a label now inherits that label's smaller, muted type,
   and `font: inherit` would hand it to the field as well - a caption-sized
   box to type an amount into. The size is stated here so the label may be
   small without taking the field down with it.

   Ticks and radios are left out: the padding and the border below are what
   a text field needs, and on a control the browser draws itself they only
   push it off its own baseline. */
input:not([type="checkbox"], [type="radio"]), button, textarea, select {
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.4;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}

/* Safari on the Mac draws a select at its own height, about half a text
   field's, and ignores the padding above, so «Kategorie» stood lower than
   «Titel» beside it. Without the browser's look the select takes the box
   every other field has, and the arrow that went with that look comes back
   as a background image. */
select {
  appearance: none;
  padding-right: 2.2rem;
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right 0.7rem center;
  background-size: 1rem;
}

/* The date field came out four pixels taller than the rest of its row, so
   «Datum» stood above «Titel» beside it. WebKit pads the value inside the
   field, and each of its day, month and year again, by a pixel above and
   below. Only that vertical padding goes; the digits keep their spacing.

   C29 section 4: `<input type="time">` renders its own hour and minute
   subfields the very same way, padded the very same two pixels, which is
   what stood «Zeit» two pixels above «Datum» beside it - hour-field and
   minute-field are what a 24-hour locale (this app's own two) actually
   renders; ampm-field is never reached here, but costs nothing to reset
   alongside them for whichever locale would render one. */
input::-webkit-datetime-edit,
input::-webkit-datetime-edit-day-field,
input::-webkit-datetime-edit-month-field,
input::-webkit-datetime-edit-year-field,
input::-webkit-datetime-edit-hour-field,
input::-webkit-datetime-edit-minute-field,
input::-webkit-datetime-edit-ampm-field {
  padding-block: 0;
}

input[type="checkbox"], input[type="radio"] {
  accent-color: var(--terracotta);
  /* Comfortably tappable without becoming a box of its own: the phone is
     the primary device and a 13 px tick beside a 24 px line is a miss. */
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
}

/* The one action a screen is for. Everything beside it is .secondary, and
   anything that removes something is .destructive - three weights, so that
   «Speichern» and «Entfernen» stop looking like the same offer. Before
   this every button in the app wore the terracotta fill, which is the same
   as none of them wearing it. */
button {
  background: var(--terracotta);
  color: #fff;
  border-color: transparent;
  cursor: pointer;
}

.secondary {
  background: none;
  color: var(--terracotta-ink);
  border-color: var(--border);
}

/* Outlined rather than filled red: a wall of red buttons down a list of
   participants is louder than the act deserves, and the colour alone
   already says which one it is. Chuchi settled the same question the same
   way for «Rezept löschen». */
.destructive {
  background: none;
  color: var(--danger);
  border-color: var(--border);
}

.destructive:hover { border-color: var(--danger); }

/* On a phone the load-bearing action is the one worth a whole row: a primary
   submit - no class of its own, unlike .secondary and .destructive - runs
   the full width and stands taller, so a thumb finds it without aiming.
   [type="submit"] rather than a bare `button`: every toggle and utility
   control in this app (.tip-round, .chip, the ×s) is type="button" and never
   carried this weight to begin with. .item-remove is the one × that is not:
   _confirm.html's without-JavaScript branch renders the same trigger as a
   submit, so on the repayments (balance.html calls the macro with
   trigger_class='item-remove') a scripting-off phone put the × under this
   rule. The width was harmless there - that form shrinks to its content
   inside a flex row, so 100% of it is the × itself - but padding-block was
   not, and the cross came back half again as tall as the one everybody else
   sees. .lang-switch is the one submit pair that is not a single action - a
   language switch, not a save - and keeps the small size it already had,
   right below. .confirm-yes is the affirmative of a confirmation that books
   rather than destroys («Bezahlt», C12): it keeps the primary look and
   shares a row with «Nein» inside the dialog, where a full-width, taller
   button would be one of two on that row and look nothing like its
   neighbour. */
@media (max-width: 46rem) {
  button[type="submit"]:not(.secondary, .destructive, .item-remove, .confirm-yes) {
    width: 100%;
    padding-block: 1rem;
  }

  .lang-switch button { width: auto; }
}

/* :user-invalid rather than :invalid - the latter paints every empty
   required field red the moment the page loads, which reads as an accusation
   before anything was even typed. This one waits for a submit attempt or a
   visit to the field. aria-invalid is validate.js's own mark on the field
   it refused (C15 section 1): only the border's colour changes, never its
   width, so the field keeps its size. (0,1,1) each, against the base field
   rule input:not([type="checkbox"], [type="radio"]) at (0,1,1) further up -
   these stand later and win. */
input:user-invalid, textarea:user-invalid,
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--danger);
}

/* A message about a form or a page (C15 section 1, Andreas, 21.09.2026): a
   box with its own ground, a rule on the left and an icon, so it no longer
   reads like the terracotta every link and secondary button wears. Every
   .error that is not a .field-error, and every .notice - the icon is a
   ::before, so a message scan.js, share.js or draft.js unhides or writes
   gets it too. Margin 0: where it stands, its call site's own spacing
   decides the gap around it. (0,2,0) and (0,1,0): .aside, the muted
   colour some of these share an element with, stands later at (0,1,0) and
   loses to the (0,2,0) either way; .notice never shares an element with
   it. */
.error:not(.field-error), .notice {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0;
  padding: 0.55rem 0.7rem;
  border-left: 3px solid var(--danger);
  border-radius: var(--radius);
  background: var(--danger-tint);
  color: var(--text);
}

/* The gold sibling. (0,1,0), the same as .notice's own place in the rule
   above; this one stands later and wins the two properties it names. */
.notice { border-left-color: var(--gold); background: var(--notice-tint); }

.error:not(.field-error)::before, .notice::before {
  content: "";
  flex: 0 0 1.1rem;
  height: 1.1rem;
  /* Centred on the box's first line at any size (C25 section 1): a fixed
     0.1rem put the icon's middle 1.6 px over the middle of a 16 px line,
     and 1lh is the line's own height, 21.6 px on the scan status's 0.9rem. */
  /* Fallback for an engine without `lh` (Safari before 16.4), which drops
     the rule below rather than the whole declaration and would otherwise
     leave the icon at 0 (C25 final review). */
  margin-top: 0.1rem;
  margin-top: calc((1lh - 1.1rem) / 2);
  background: var(--danger);
  -webkit-mask: var(--icon-alert) center / contain no-repeat;
  mask: var(--icon-alert) center / contain no-repeat;
}

/* (0,1,1) against the shared rule's .notice::before at (0,1,1); later wins. */
.notice::before {
  background: var(--gold);
  -webkit-mask-image: var(--icon-info);
  mask-image: var(--icon-info);
}

/* The message about one field (validate.js, and the tip screen's inline
   lines): no box, no icon - a small red line directly under the field,
   which carries the rest by its border (above). */
.field-error { margin: 0.2rem 0 0; font-size: 0.85rem; color: var(--danger); }

/* A vertical run of items with air between them - the trip chooser, a
   settings page's rows. min-width: 0 keeps a flex item from growing past its
   container to fit a word it cannot break. */
/* list-style and the padding go with it: these are <ul>s used as layout, not
   as prose, and the browser's 40 px indent pushed every settings row, every
   participant and every item line in from the card's own edge for nothing.
   min-width: 0 keeps a flex item from growing past its container to fit a
   word it cannot break. */
.list { display: flex; flex-direction: column; gap: 1rem; min-width: 0;
        list-style: none; margin: 0; padding: 0; }

/* A list read as prose rather than as rows: the walkthrough's «Was nicht
   mitkommt», three prohibitions with nothing to line up at the right edge.
   `.list` above is the layout kind and takes its markers off, so a run of
   sentences under it stood there as three unmarked paragraphs (Andreas,
   23.09.2026). No flex here - a flex item is blockified, and Safari draws
   no marker beside one. */
.bullets { list-style: disc; margin: 0; padding-left: 1.25rem; }
.bullets > li + li { margin-top: 0.4rem; }

/* Supplementary text set apart from the line it stands in - a parenthetical
   in an entry's description, a hint beside a name. */
.aside { color: var(--muted); }

/* Alpine sets x-cloak before it starts; without this the two-step archive
   confirmation on the trips and people pages would be visible for one frame
   on every load. */
[x-cloak] { display: none !important; }

/* The line above every managed list - trips, people - where a new one is
   entered. */
.manage-new { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }

/* A row of a managed list: a name (or its own edit form) plus the controls
   that act on that one row, kept apart from the neighbours by .list's gap
   rather than by a border. */
.list li { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
           justify-content: space-between; }

/* Told nothing was archived yet, in the same muted voice as .aside. */
.empty { color: var(--muted); }
}

/* Screens, in the middle of base: a pressed toggle and the tip's suggested
   chip have to beat .chip, .text-button and .item-remove further down,
   which they did by weight before the layers. */
@layer screens {
/* The tip screen's own chip matching the country's suggested percentage -
   "der Landeswert hervorgehoben" (task 21). Never set for round_up or
   included conventions: highlighting a chip there would be a percentage
   claim nobody made (see tips.py's own module docstring). A border rather
   than a fill, because it is a suggestion and not a state: nothing has
   been chosen yet when it appears.

   The pressed rounding buttons wore this same look until every button on
   the screen stopped being a terracotta fill. Now that they rest outlined,
   the fill is free to mean what it means everywhere else - this is on,
   that is off - and this border goes back to meaning only «we suggest
   this». Two signals for two different things.

   A solid terracotta border now, not a gold outline ring (task 9, after
   use) - the same weight .tile[open] marks the open tile with, below.
   .chip.tip-recommended, not .tip-recommended alone: .chip's own border
   sits later in this file at equal specificity, which would otherwise win
   and erase this one. */
.chip.tip-recommended { border-color: var(--terracotta); }

button[aria-pressed="true"] {
  background: var(--terracotta);
  border-color: transparent;
  color: #fff;
}
}

@layer base {
/* --- Form grammar ------------------------------------------------------- */

/* A label that wraps its own control - the shape every form in this app
   uses - sets the word above the field, in the smaller, muted type of a
   caption. Left to the browser they stand side by side, so a row of three
   fields wraps into a ladder in which no word is beside what it names; the
   trip form on the settings page had lost its words that way altogether.
   Selected by what the label contains rather than by a class on each one,
   because the templates already say what a label is by what is inside it. */
label:has(> input:not([type="checkbox"], [type="radio"])),
label:has(> select),
label:has(> textarea) {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* A tick and its word are one line, and the word is the size of the text
   around it - these labels name a choice, not a field. */
label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  cursor: pointer;
}

/* The browser's own frame around a fieldset is the only border in the app
   that nothing else matches, and it nested: the entry form drew a box in a
   box in a box, each one indenting what it held. A group is told apart by
   the space around it and by the small heading over it - the way Chuchi's
   settings sections are - not by a rectangle. */
fieldset { border: none; margin: 0; padding: 0; min-width: 0; }

legend {
  padding: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}

/* And the one between a group's small heading and what it names, the same
   under every heading of the entry form. It was nothing under the split's
   old heading and a row's own padding under the participants' and the
   payers', until 15.09.2026. A margin, not padding: WebKit lays a legend's
   padding into the fieldset's border, where it moves nothing below it
   (measured 15.09.2026). */
.entry-form legend { margin-bottom: 0.4rem; }

/* A caption over something that is not a field: «Offene Zahlungen» over
   the balance's switch, «Angemeldeter Wert (CHF)» over a declaration's
   goods (C15 sections 4 and 7). The size and colour a label's own word
   has (label:has(> input) above), as a paragraph of its own. */
.caption { margin: 0; font-size: 0.85rem; color: var(--muted); }

/* Fields standing side by side in a row that wraps. Aligned at the bottom,
   not the middle: every label is a word over a box, and centring them puts
   the boxes at different heights the moment one word wraps. */
.manage-new { align-items: end; }
.manage-new > label { flex: 1 1 8rem; }

/* validate.js inserts the refusal message as .manage-new's own direct
   child, right after the label it belongs to (settings.html's trip form,
   trips.html's create form, people.html's create/password forms,
   balance.html's payment form, _entry_form.html's entry-basics/entry-money
   rows). Without this it competes with the base .manage-new rule above
   (flex-wrap: wrap, no basis set for anything but a label), which leaves it
   at the flex default of content-sized and free to wedge in beside a field
   instead of under it. Same pattern as .budget-bar's own `flex: 1 1 100%`
   further down: it says nothing where .field-error is not a flex child at
   all, which is every other form in this app. */
.manage-new > .field-error { flex: 1 1 100%; }

/* Every field inside one of these labels, and every field a flex row sizes:
   a flex item's min-width is `auto`, which floors it at its own intrinsic
   width - for an <input> that is the browser's 20-character default, around
   222 px. Without this, flex-basis is a suggestion the layout ignores, and
   a 5rem factor box came out three times that and pushed its own name onto
   the line above. */
.manage-new > label > input, .manage-new > label > select,
.manage-new > label > textarea { min-width: 0; }
}

@layer components {
/* --- Cards -------------------------------------------------------------- */

/* The surface a group of rows sits on. One border, one radius, one
   background - said once here rather than per screen. */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* --- Entries ------------------------------------------------------------ */

/* Each record is a tile, so a list reads as a stack of receipts rather
   than as one paragraph of runs-on text. The generic .list row layout is
   turned off here: these rows hold a single block that has to span the
   full width, not two things pushed to opposite ends. */
.tile-list { gap: 0.6rem; }
.tile-list > li { display: block; }

/* A column of tiles that is not an entries list - the balance, one
   declaration, the settings: the same gap between two tiles as the list
   keeps, and the tile that adds one no further from the first record under
   it than on the entries tab. */
.tile-stack { display: flex; flex-direction: column; gap: 0.6rem; }
.tile-stack > .tile-new { margin-bottom: 0.6rem; }

/* What a tile holds when it is not an entry's form: the same space under
   its last line as the form keeps (.tile > form:last-child).
   .entry-items-bulk-body shares this: the bulk fold's own body, styled
   through its own classes rather than .tile-body itself (C28 section 6.2,
   fix round 1). */
.tile-body, .entry-items-bulk-body { padding-bottom: 0.9rem; }

/* A first child's own top margin (e.g. .customs-close) collapses through
   this padding box and doubles the gap under the tile's head. */
.tile-body > :first-child { margin-top: 0; }

/* A form-level box flush against whatever stands after it (C15 section 3):
   Task 2 put margin: 0 on every box (the shared .error:not(.field-error),
   .notice rule above) and Task 5 dropped what used to fill the gap, so a
   box that is a .tile-body's first child left nothing between itself and
   the next control. Restored as the stack's own 0.6rem, on the bottom
   margin only: the top stays 0 (.tile-body > :first-child above still
   applies, a different property), and a following element's own larger
   top margin still collapses through unshrunk - .balance-rate-error above
   is one of these boxes too, and its own 0.4rem top margin, and the 1rem
   the caption after it collapses against, are both untouched by a bottom
   margin. (0,3,0), against the shared rule's own (0,2,0) - wins on
   specificity alone, whichever stands first. */
.tile-body > .error:not(.field-error), .tile-body > .notice { margin-bottom: 0.6rem; }

/* What stands in an entry row until static/js/lazy.js has fetched its form
   (_lazy.html, C22 section 1). Its line starts where the form's first field
   will once it is there: the open summary's own margin is the whole gap, so
   the paragraph's browser margin goes. (0,2,0) - no rule here sets a margin
   for .empty, and .error:not(.field-error) above already sets 0 at the same
   weight, earlier. Under the line the space a tile keeps under its content
   (.tile-body, .tile > form:last-child).

   :last-child on the second rule (0,2,1, final review, 23.09.2026): a
   customs card's own placeholder (C22 section 3, _customs_goods.html) is
   never the tile's last child - its «Angemeldeter Wert» caption always
   follows it, with a margin-top that already supplies the gap the loaded
   form gets from the very same caption rule below (`.tile > .lazy +
   .customs-goods-caption`). Without the restriction this rule's own
   0.9rem stacked on top of that margin too, and the placeholder sat 0.9rem
   lower than the form that replaces it. The entries row is unaffected: its
   own placeholder has nothing after it, so it is still the last child. */
.lazy > .lazy-wait, .lazy > .lazy-failed { margin: 0; }
.tile > .lazy:last-child { padding-bottom: 0.9rem; }

/* The same box where it stands directly above a tile instead of inside one
   (_entry_list.html, _customs_goods.html: a saved-entry notice above that
   entry's own card, outside the <details> so data-fold-on-save folding the
   card shut never hides it). :not(.tile-stack) keeps this off a box that is
   itself a direct child of a stack - there the flex gap already spaces it
   from the tile after it, and a margin here would double that gap on top
   of it. (0,4,0); .tile itself carries no margin rule here to outrank. */
:not(.tile-stack) > .error:not(.field-error) + .tile { margin-top: 0.6rem; }

/* .entry-items-bulk shares the frame: the bulk fold looks like a folded
   tile through its own classes (C28 section 6.2, fix round 1) rather than
   carrying `.tile` itself - `.tile[open]`'s terracotta border below is
   deliberately not shared, so the fold keeps this plain border while open,
   the way `.tile-section[open]` does for a card that structures a page. */
.tile, .entry-items-bulk {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0 0.8rem;
}

/* The open one is marked, because on a long list the form inside it starts
   far enough down that its own heading is off the screen. */
.tile[open] { border-color: var(--terracotta); }

/* A tile that structures the page rather than competing for attention inside
   a stack of many - the balance page's «Salden» and «Ausgleich», always
   open the way a section heading is. The terracotta above answers "which of
   these is open"; this kind of tile never asks that question, so the border
   stands plain instead. */
.tile-section[open] { border-color: var(--border); }

/* A tile that leads to a page of its own rather than opening (C3 spec
   2.3): the same head on the same grid, the chevron pointing the way it
   goes and never turning, and the text in the body's colour rather than a
   link's - the whole tile is the link. */
.tile-link {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline;
  gap: 0.15rem 0.7rem;
  padding-block: 0.7rem;
  color: var(--text);
}
/* The border stays plain under the mouse (Andreas, 23.09.2026): terracotta
   is the border that says «this tile is open» (.tile[open]), and on a Mac
   every tile that only leads elsewhere used to borrow it on hover. */
.tile-link:hover { color: var(--text); }
.tile-link > .tile-fold {
  grid-column: 3;
  grid-row: 1;
  align-self: center;
  width: 1.3rem;
  height: 1.3rem;
}

/* A head that does not open (C18 section 4.3): a non-admin's view of the
   participants and groups. The summary's own grid, without the chevron's
   column, the pointer or a link tile's hover. padding-block (0,1,0)
   stands after .tile's padding (0,1,0) and wins on order. */
.tile-fixed {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.15rem 0.7rem;
  padding-block: 0.7rem;
}

/* A person's, a group's or a household member's own form inside its tile
   (C3 spec 5): one field under the other, the two short ones side by side
   in a .manage-new row. */
.tile-form { display: flex; flex-direction: column; gap: 0.8rem; }
.tile-form > .aside { margin: -0.5rem 0 0; font-size: 0.85rem; }

/* .manage-new's own margin-bottom (below) is for a managed list's row;
   inside a tile it only doubles the space the tile already keeps under
   its last line. */
.tile-form > .manage-new, .tile-body > .manage-new:last-child { margin-bottom: 0; }

/* Save and the way to take the record off the list, on one line, the way an
   entry's save and delete stand (.entry-actions). Save sits outside its form
   and reaches it through form="…": the second button is a form of its own,
   with its own question. */
.tile-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.8rem;
  margin-top: 1.2rem;
}
/* The padding only (C18 section 4.4): at 0.9rem «Entfernen» read smaller
   than «Speichern» beside it (Andreas, 22.09.2026). */
.tile-actions .destructive, .tile-actions .secondary { padding: 0.45rem 0.7rem; }

/* On a phone this row wants a two-to-one shape: Speichern with the larger
   share, the way off the list beside it, and the whole width used. It did
   not get there the way .entry-actions gets its own shape, because the two
   rows are built differently - the entry form's delete is a submit of that
   same form (formaction), a direct child of the flex row, while everywhere
   else the delete is _confirm.html's macro and brings a <form> of its own.
   That form is a block, and the full-width rule above put Speichern on a
   row by itself, so the two stood one under the other.

   So the submit gives up its own width here and takes a flex share instead -
   the taller padding stays, that part was the point - and the macro's form
   becomes the flex item beside it, two shares against one. The form carries
   display: flex of its own so that the button inside it fills it; without
   that the form would stretch and the button sit small in its corner.

   The question itself no longer grows into this row - it opens in a
   <dialog> over the page instead (_confirm.html) - so align-items: stretch
   is the only thing still needed here: it keeps the shorter destructive
   button at the same height as the taller submit beside it. */
@media (max-width: 46rem) {
  .tile-actions { align-items: stretch; }
  .tile-actions > button[type="submit"] { flex: 2 1 0; width: auto; min-width: 0; }
  .tile-actions > form { display: flex; flex: 1 1 0; min-width: 0; }
  .tile-actions > form > button { flex: 1 1 auto; min-width: 0; }

  /* The one action pair in this app that is neither full width nor a
     two-to-one row: .secondary and .destructive are excluded from the
     button[type="submit"]:not(...) rule above (app.css:422), and
     .tile-actions .destructive, .tile-actions .secondary (app.css:677) only
     sets padding, never width, so it never gave this pair a shape to begin
     with. Both halves of a question deserve a thumb.

     A <dialog> is width: fit-content by UA default, and the question text
     itself ("Wirklich löschen?") is shorter than the two buttons need, so
     the dialog was shrinking to the question rather than growing to its own
     max-width. width: 100% (resolved against the viewport, same as a
     top-layer element's containing block) makes it actually use the room
     max-width already allows, instead of hugging its shortest line. */
  dialog.confirm-dialog { width: 100%; }
  .confirm-dialog-actions { align-items: stretch; }
  .confirm-dialog-actions > button { flex: 1 1 0; min-width: 0; }
}

/* The question, over the page instead of inside the row that asked it. A
   <dialog> is display:none until showModal(), which is also what keeps it
   out of the way on a device with no JavaScript at all. */
dialog.confirm-dialog {
  max-width: min(22rem, calc(100vw - 2rem));
  padding: 1.2rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}
dialog.confirm-dialog::backdrop { background: rgb(0 0 0 / 0.4); }
.confirm-dialog > p { margin: 0 0 1rem; }
.confirm-dialog-actions { display: flex; justify-content: center; gap: 0.6rem; }
}

/* Overlays: the row a confirmation opens from styles its own buttons
   (.balance-payments button, .balance-transfer-tools button), and those
   reach into the dialog, which is the row's child in the DOM. */
@layer overlays {
/* Both answers, one size in every dialog (C14 section 4). A dialog is a DOM
   child of the row that asks (_confirm.html), so that row's own button rules
   reach into it: `.tile-actions .destructive, .tile-actions .secondary`
   (0,2,0) shrank the answers on the participant, group and person tiles,
   and `.balance-transfer-tools button` and `.balance-payments button`
   (0,1,1) shrank «Bezahlt» and the repayment × further; `.customs-good >
   form button` (0,1,2) would do the same to a dialog that ever opened from
   a good. This selector is (0,2,2), above all four. Size only: the colours
   stay with .destructive, .secondary and .confirm-yes. */
dialog.confirm-dialog .confirm-dialog-actions > button {
  padding: 0.6rem 0.7rem;
  font-size: 1rem;
  line-height: 1.4;
}

/* On a phone as tall as «Speichern» (`button[type="submit"]` at
   padding-block 1rem, in the phone block above), which Andreas chose on
   21.09.2026. A media block of its own after the rule above, not inside the
   phone block further up: the selector is the same, so only standing later
   wins. Padding only, never width - .confirm-yes stays out of the
   full-width submit rule and keeps its half of the row. */
@media (max-width: 46rem) {
  dialog.confirm-dialog .confirm-dialog-actions > button { padding-block: 1rem; }
}
}

/* Components again, after overlays: the budget field's own width has to
   beat .manage-new > label's general flex up in base, which it did by
   weight before the layers. */
@layer components {
.manage-new > label:has(> .field-budget) { flex: 0 1 8rem; }

/* The budget's locked currency box (C18 section 4.5): three letters wide,
   in the figure's colour rather than the muted grey of an unavailable
   choice - the tip screen's .tip-currency:disabled treatment, as a rule of
   its own. (0,3,1) against input:disabled's (0,1,1); -webkit-text-fill-
   color and opacity for iOS Safari, which greys a disabled field's text
   through those two. */
.manage-new > label:has(> .budget-currency) { flex: 0 0 4.5rem; }
.manage-new input.budget-currency:disabled {
  color: var(--text);
  -webkit-text-fill-color: var(--text);
  opacity: 1;
}

/* A tile that does not fold - the tip calculator (C3 spec 2.4): the card's
   surface and border with the tile's side padding. The first row of the
   calculator brings its own space above it. */
.tile-static { padding: 0 0.8rem 1rem; }

.tile > summary, .entry-items-bulk > summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline;
  gap: 0.15rem 0.7rem;
  padding: 0.7rem 0;
  cursor: pointer;
  list-style: none;
}

/* The triangle the browser draws sits outside the padding and pushes the
   whole row across; this replaces it with a chevron in a column of its
   own, so every summary starts at the same left edge. */
.tile > summary::-webkit-details-marker, .entry-items-bulk > summary::-webkit-details-marker { display: none; }
.tile > summary::marker, .entry-items-bulk > summary::marker { content: ""; }

/* The mark on every fold of a card - the entry row itself and each section
   inside it, entry, customs, split, payment, distribution, history alike -
   is the sprite's chevron, turned, rather than a corner drawn out of two
   borders. The glyph sits in the middle of its own box, so a turn pivots it
   where it stands; the drawn corner sat in one corner of its box instead,
   and half a turn threw the tip from the bottom edge to the top, which
   reads as the arrow jumping rather than turning. .entry-items-bulk-fold is
   the bulk fold's own chevron, sharing this look through its own class
   rather than through .tile-fold (C28 section 6.2, fix round 1). */
.tile-fold, .entry-items-bulk-fold { flex: none; color: var(--muted); transition: transform 0.15s; }

/* Up while the entry is shut, down while it is open, through the horizontal
   on its way. */
.tile > summary > .tile-fold, .entry-items-bulk > summary > .entry-items-bulk-fold {
  grid-column: 3;
  grid-row: 1;
  align-self: center;
  width: 1.3rem;
  height: 1.3rem;
  transform: rotate(-90deg);
}
.tile[open] > summary > .tile-fold, .entry-items-bulk[open] > summary > .entry-items-bulk-fold {
  transform: rotate(90deg);
}

.tile-title, .entry-items-bulk-title { grid-column: 1; grid-row: 1; font-weight: 600; }

/* Right-aligned and on figures of one width, so a column of amounts can be
   read down rather than word by word. */
.tile-amount {
  grid-column: 2;
  grid-row: 1;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Everything about the entry that is not its name or its amount, on one
   quiet line under them: the day and the category - or the state for a
   deleted entry, or the rate on the customs tab. .entry-items-bulk-state is
   the bulk fold's own quiet line, sharing this look through its own class
   rather than through .tile-meta (C28 section 6.2, fix round 1). */
.tile-meta, .entry-items-bulk-state {
  grid-column: 1 / -1;
  grid-row: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.4rem;
  font-size: 0.85rem;
  color: var(--muted);
}

/* The parts of the quiet line told apart by a middle dot: «16.09.2026 ·
   Essen». Punctuation rather than a word, so it comes from here and not
   from the catalogue, and stands the same in every card. Colour set outright
   rather than inherited: a ::before is a child of the span it decorates, and
   on the customs tab that span can be .customs-entry-drifted, which carries
   its own terracotta - without this the dot before "Eintrag geändert" would
   wear the warning colour the drift itself is marked with. */
.tile-meta > span + span::before, .entry-items-bulk-state > span + span::before {
  content: "·";
  margin-right: 0.4rem;
  color: var(--muted);
}

/* What a card still lacks, asked where the value would stand: «Kategorie?»,
   «Zoll?», «Positionen?» (Andreas, 16.10.2026). Terracotta like the drift
   on the customs tab - something to see to, not a refusal, which would be
   --danger. The dot before it keeps --muted through the rule above. */
.tile-meta > .tile-missing { color: var(--terracotta-ink); font-weight: 600; }

/* The line under an open card's title is the summary's own, so it stands
   inside the card's padding - set in like a section's, not drawn from edge
   to edge. */
.tile[open] > summary { border-bottom: 1px solid var(--border); margin-bottom: 0.9rem; }

.tile > form:last-child { padding-bottom: 0.9rem; }

/* The row that adds one stands apart from the receipts below it: it is the
   only thing on this screen that is an action rather than a record. */
.tile-new > summary { font-weight: 600; color: var(--terracotta-ink); }
.tile-new { margin-bottom: 1.2rem; }
}

@layer screens {
/* The line over the entries list that counts what still lacks something
   (_entry_list.html, Andreas, 16.10.2026), in the colour of the marks it
   counts. */
.entry-incomplete { margin: 0 0 0.75rem; }
.entry-incomplete-link { color: var(--terracotta-ink); font-weight: 600; }

/* The search over the entries list (entries.html): a field the width of the
   column, between the new entry and the cards it filters. Relative so the
   clear cross can sit inside the field, at its right edge, rather than
   beside it - which is why this box holds the field and the cross and
   nothing else. «Kein Eintrag gefunden» used to stand in here too, and on
   the only screen where both show at once - a filled field with no hits -
   it made the box two elements tall and left the cross centred on the pair
   rather than on the field. */
.entry-search { position: relative; flex: 1; min-width: 0; }
/* A day's heading over its cards (C29 section 3): the weekday and date on
   the left, what the day cost on the right, in the muted caption voice so
   the cards stay what the eye reads. */
.entry-day + .entry-day { margin-top: 1.2rem; }
.entry-day-head {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0 0 0.5rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
}
/* Room for the clear cross, so typed text stops before it (C24 section 8). */
.entry-search-input { width: 100%; padding-right: 2rem; }
/* WebKit's own cancel button, unstyled and impossible to match - switched
   off so it never stands next to ours. */
.entry-search-input::-webkit-search-cancel-button { -webkit-appearance: none; }
.entry-search-clear {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
}
/* The cross keeps the field's own inset: on .item-remove's 0.5rem it stood
   nine pixels from the right edge while the text started twelve from the
   left, and read as stuck to the border (Andreas, 18.09.2026). Padding
   rather than `right`, so the button itself still reaches the edge and the
   tap target does not shrink; the selector is compounded because
   .item-remove's padding shorthand stands later in this sheet and would
   win at equal weight. */
.entry-search > .entry-search-clear { padding-right: 0.7rem; }
/* A sibling of the field now, so it keeps the same distance to the list
   below that the field itself would have had. The count of what a filter
   leaves standing (C31 section 5.1) keeps the same. */
.entry-search-empty, .entry-search-count { margin: 0 0 1rem; }

/* The search and the «Filter» panel as one card (Andreas, 06.10.2026), at
   the distance from the list the field alone used to keep: the field and
   the button that opens the panel on one line, the panel under both. Open,
   the card wears the terracotta border of an open tile, which answers the
   same question; the state is the button's aria-expanded, which search.js
   sets. */
.entry-filter { margin-bottom: 1rem; padding: 0.6rem; }
.entry-filter:has(.entry-filter-toggle[aria-expanded="true"]) { border-color: var(--terracotta); }
.entry-filter-bar { display: flex; gap: 0.5rem; }

/* Outlined in the secondary voice, like the tip screen's locate button, and
   as tall as the field beside it: the bar stretches both. Open, it takes a
   wash of terracotta, mixed rather than a token of its own so the dark
   theme's terracotta and surface carry over. */
.entry-filter-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-width: 2.75rem;
  background: none;
  color: var(--terracotta-ink);
  border-color: var(--border);
}
.entry-filter-toggle > .icon { width: 1.3rem; height: 1.3rem; }
.entry-filter-toggle[aria-expanded="true"] {
  background: color-mix(in srgb, var(--terracotta) 12%, var(--surface));
  border-color: var(--terracotta);
}

/* The word only where there is room: on an upright phone the symbol stands
   alone, named by the button's aria-label (C18 section 1.3, the locate
   button's rule). */
@media (max-width: 30rem) {
  .entry-filter-toggle > .entry-filter-label { display: none; }
}

/* How many of the panel's groups are set, on the button's corner, where
   «2 aktiv» found no room beside a symbol alone. White on terracotta, the
   checked chip's pair; the ring in the card's colour lifts it off the
   button's border. */
.entry-filter-badge {
  position: absolute;
  top: -0.45rem;
  right: -0.45rem;
  min-width: 1.2rem;
  height: 1.2rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--terracotta);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.2rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--surface);
}

/* Under the bar the way an open tile's body stands under its head: a rule,
   then the groups, set in to the tiles' own 0.8rem. The groups are
   fieldsets, unframed by the base rule, each legend a line above its
   controls, as the entry form's own legends stand, and in the day heading's
   caption voice (.entry-day-head): the base legend's weight and colour at
   its size. */
.entry-filter-panel {
  margin-top: 0.6rem;
  padding: 0.9rem 0.2rem 0.2rem;
  border-top: 1px solid var(--border);
}
.entry-filter-group + .entry-filter-group { margin-top: 0.9rem; }
.entry-filter-group > legend { margin-bottom: 0.4rem; font-size: 0.9rem; }
.entry-filter-group > .manage-new { margin-bottom: 0; }
/* The chips wrap like a line's own (.item-chips). */
.entry-filter-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }
/* «Suchen in» with nothing ticked searches all four texts, and shows it: each
   chip wears the terracotta border, the tip screen's mark for «this
   applies, nobody chose it» (.chip.tip-recommended), not the fill of a
   choice, which would read as four filters set (Andreas, 06.10.2026). Once
   one is ticked the search reads that one only, and the others go plain. */
.entry-filter-texts:not(:has(input:checked)) > .chip { border-color: var(--terracotta); }
.entry-filter-panel .form-actions { margin-top: 0.9rem; }

/* The sheets on an entry, one row each: a small picture, what it is, and the
   cross that takes it out of the list. The picture and the words are one
   link, so the whole row is the tap target - a three-rem thumbnail on its
   own is one a thumb mostly misses. A PDF wears its type as a word in the
   picture's place; an <img> on one is a broken image. */
.entry-receipts { margin: 0 0 0.8rem; }
.entry-receipts-title { margin: 0 0 0.3rem; font-size: 0.85rem; }
.entry-receipt-list { gap: 0.5rem; }
.entry-receipt-link { display: flex; flex: 1 1 auto; min-width: 0;
                      gap: 0.6rem; align-items: center; }
.entry-receipt-list img, .entry-receipt-kind {
  flex: 0 0 auto;
  width: 3rem;
  height: 3rem;
  border-radius: 0.3rem;
}
.entry-receipt-list img { object-fit: cover; }
.entry-receipt-kind {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  font-size: 0.75rem;
  color: var(--muted);
}
.entry-receipt-name { min-width: 0; overflow-wrap: anywhere; font-size: 0.9rem; }

/* The receipt pop-up (Task 7): a picture at full size, over the page
   instead of a new tab someone has to swipe back out of. Shared by
   _entry_form.html and _customs_receipts.html, one copy each - see
   scan.js's own lookup for why neither page can get by with a single
   page-level dialog. */
dialog.receipt-view {
  max-width: min(46rem, calc(100vw - 2rem));
  /* img { width: 100% } inside a fit-content box has nothing to resolve
     against, so a picture that fails to decode collapses to its broken-image
     icon rather than to anything readable - a floor keeps the cross above it
     reachable even then, not just when every receipt is a good photograph. */
  min-width: 12rem;
  min-height: 8rem;
  padding: 0;
  border: none;
  border-radius: var(--radius);
  background: var(--surface);
}
dialog.receipt-view::backdrop { background: rgb(0 0 0 / 0.7); }
dialog.receipt-view img { display: block; width: 100%; height: auto; }
/* The pop-up's two controls, «Sichern» then the X, in the corner over the
   picture (C14 sections 3 and 5). A fixed circle each with the symbol in
   its middle: the «×» glyph in a padded button was an oval with the cross
   on the font's baseline, visibly off centre (Andreas, 21.09.2026).
   Plain button {} fills every button terracotta, which would read as a call
   to action on top of somebody's receipt. The dark circle rather than
   var(--muted): these sit on a photograph of unknown colour, and the fill
   is what keeps them above the 3:1 non-text contrast floor. Compound,
   (0,2,0), against the base control rule's padding and border and plain
   button's fill (both 0,0,1) and any link colour (at most 0,1,1); the
   symbol's own size wins over `.icon`'s 1em (0,1,0) as `.receipt-view-tools
   .icon` (0,2,0). */
.receipt-view-tools {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  display: flex;
  gap: 0.4rem;
}
.receipt-view-tools > .receipt-view-save,
.receipt-view-tools > .receipt-view-close {
  display: grid;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  padding: 0;
  background: rgb(0 0 0 / 0.55);
  border: 1px solid transparent;
  border-radius: 50%;
  color: #fff;
}
.receipt-view-tools .icon { width: 1.2rem; height: 1.2rem; }

/* The receipt card sits under the amount it fills in, not above the form
   (_entry_form.html's own comment, receipt position 17.09.2026); the layout
   is the same manage-new row the rate line uses. */
.entry-scan { gap: 0.5rem; flex-wrap: wrap; }
/* The card's line sits like the rest of the card's rows, not in a gap of its
   own: a refusal that floats has more weight on the page than in the reading
   (Andreas, 19.09.2026). A refusal on this line has been a box since C15
   (the shared .error rule); now that base sits below screens (C23), the
   general rule can no longer reach up and win by weight, so the margin
   comes back through the compound .entry-scan-status.error rule below. */
.entry-scan-status { flex: 1 1 100%; margin: 0.2rem 0 0; font-size: 0.9rem; }
.entry-scan-status.error { margin-top: 0; }
.entry-scan-clear { flex: 0 0 auto; }
.entry-scan-warnings:not(:empty) { flex: 1 1 100%; }
.entry-scan-warnings p { margin: 0.2rem 0 0; color: var(--terracotta-ink); font-size: 0.9rem; }

/* The gate before the first photograph leaves the device. Full width inside
   the card and set off by a border: it is the only place a person is told
   that the picture itself is kept and not merely read, so it has to stop the
   eye rather than sit in the row of controls. */
.scan-consent {
  flex: 1 1 100%;
  margin-top: 0.4rem;
  padding: 0.8rem;
  border: 1px solid var(--border);
  border-radius: 0.4rem;
  background: var(--bg);
}
.scan-consent p { margin: 0 0 0.6rem; font-size: 0.9rem; }
.scan-consent button { margin-right: 0.4rem; }

/* The exchange-rate row: a sentence, or the field between the two currencies
   once «Kurs ändern» is pressed (see _entry_form.html). It carries its own
   bottom margin now that it is no manage-new row. */
.entry-rate { margin: 0 0 1rem; font-size: 0.9rem; color: var(--muted); }
.entry-rate > p { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 0.6rem; margin: 0; }
.entry-rate-input { flex: 0 1 7rem; min-width: 0; }

/* A button that reads as a word of the sentence it stands in: the boxed
   button beside the one-line rate stood taller than the line itself. */
.text-button {
  padding: 0;
  border: none;
  background: none;
  color: var(--terracotta-ink);
  font-size: inherit;
  line-height: inherit;
}

/* Currency, amount and tip belong on one line, and they want very different
   widths - three letters against two sums. The basis is what makes all
   three fit a 390 px phone; at 8rem the tip wrapped onto a row of its own,
   where it read as a second subject rather than as part of the sum. */
.entry-money > label { flex: 1 1 5rem; }
.entry-money > label:has(> .entry-currency) { flex: 0 0 6rem; }

/* The figures as large as the customs app sets its «$ 0.00»: they are what
   gets read back against the paper. Measured at 390 px, the three still
   share one row, and «CHF» still clears the list's arrow. */
.entry-money input, .entry-money select { font-size: 1.3rem; }

/* The title takes the whole first row; date and category share the next. */
.entry-basics > .entry-title { flex: 1 1 100%; }

/* The title is read back against the paper like the figures are, so it is
   set like them (Andreas, 19.09.2026). Date and category stay at the base
   size - three large fields over two rows push the rest of the mask off a
   390 px phone. The selector is compounded rather than left at
   `.entry-title input`: at equal weight a later rule in this sheet would
   win, which is what the search cross cost a round ago. */
.entry-basics > .entry-title input { font-size: 1.3rem; }

/* The time needs five characters and a clock (C29 section 4), so it takes
   less room than the date beside it. */
.entry-basics > .entry-time { flex: 0 1 6.5rem; }

/* The entry's own budget flag and invitation, for 'factors' mode (see
   _entry_form.html's .entry-flags): one under the other, first in the
   payment section. */
.entry-flags {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  font-size: 0.9rem;
}
/* The explanation on the same line as the flag it explains, one shade back:
   two words after a middle dot read as an aside, a paragraph of its own read
   as a second subject (Andreas, 19.09.2026). Inside the <label>, so a tap on
   the words still takes the tick with it. */
.flag-hint { color: var(--muted); font-weight: 400; }

/* A section of the entry form (_entry_form.html): customs, the split, the
   payment and the distribution, each a <details> that starts open. Boxed
   one level lighter than .tile itself - a border and a radius mark
   it as its own group without a second card sitting inside the entry's own
   card - and titled the way the history line under the card is: the
   sprite's chevron before a quiet word, right while shut, down while open.
   The same space under a section as over it: with a margin on top only, a
   folded block sat on whatever came next and read as its start. The history
   is the one section outside the form: beside it in the row's body
   (_entry_row_body.html), which since C22 section 1 stands between it and
   the tile - so .entry-body, not .tile, is its parent here. */
.entry-form > .entry-section, .entry-body > .entry-section { margin: 1.4rem 0; }
.entry-section {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0 0.8rem;
}
.entry-section > summary {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.6rem 0;
  cursor: pointer;
  list-style: none;
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--muted);
}
.entry-section > summary::-webkit-details-marker { display: none; }
.entry-section > summary::marker { content: ""; }
.entry-section[open] > summary { border-bottom: 1px solid var(--border); }
.entry-section > summary > .tile-fold { width: 1.1em; height: 1.1em; }
.entry-section[open] > summary > .tile-fold { transform: rotate(90deg); }
/* One distance between a section's title and the first thing it holds, and
   between the groups inside it - sibling margins collapse into one. */
.entry-section > :not(summary) { margin: 0.8rem 0; }

/* The single-item question: the legend on its own row, the two answers one
   under the other - each a phrase since C14 section 2, no longer two short
   words side by side - then the hint for the case neither answer fits and
   the button that goes to lines. */
.entry-customs-single {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  align-items: center;
}
.entry-customs-single legend { flex: 1 1 100%; }
.entry-customs-single > label { flex: 1 1 100%; }
.entry-customs-single > .aside { flex: 1 1 100%; margin: 0.3rem 0 0; font-size: 0.85rem; }

/* The rate choice with a line under each option: one option per row, the
   line set in to start under the word rather than under the radio. Stands
   on its own rather than sharing the single-item rule above - three options
   each with their own hint read taller than the single-item block ever
   does, and stacking rather than flowing is the point here, not an override
   of it. */
.entry-customs-rate {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 0.5rem;
}
.entry-customs-rate legend { flex: none; }
.customs-hint { margin: 0.1rem 0 0 1.6rem; font-size: 0.85rem; }

/* The foreign tax field, its own "x % rechnen" shortcut and the button that
   fills it - one wrapping row, the same manage-new shape the rest of the
   form's rows already use. */
.entry-customs-tax { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: end; }
.entry-customs-tax > label { flex: 1 1 10rem; min-width: 0; }
.entry-customs-tax-shortcut { flex: 0 1 6rem; }
.entry-customs-tax-shortcut input, .entry-customs-tax-amount { min-width: 0; }
.entry-customs-tax-apply { flex: 0 0 auto; }
.entry-customs-tax > .aside { flex: 1 1 100%; margin: 0.2rem 0 0; font-size: 0.85rem; }

/* One participant per line: the tick and the name to the left, the factor
   that person carries to the right, close enough together to be read as
   one statement. */
.entry-participants .list, .entry-payers .list { gap: 0.1rem; }

.entry-participants li, .entry-payers li {
  gap: 0.6rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
}

/* A row's padding is the air between two rows, not between a row and the
   heading above the first or the total under the last. */
.entry-participants li:first-child, .entry-payers li:first-child { padding-top: 0; }
.entry-participants li:last-child, .entry-payers li:last-child { border-bottom: none; padding-bottom: 0; }
.entry-participants li > input { flex: 0 0 5rem; min-width: 0; text-align: right; }

/* The words over the factors list: whose names, and over the column of
   boxes on the right, «Faktor» - said once, the way .item-head names a
   line's boxes. */
.participant-head {
  display: flex;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.4rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.participant-head > span:last-child { flex: 0 0 5rem; text-align: right; }

/* A payer's row: the name as the row's subject, then Rest, the currency and
   the box, pushed to one right edge - five names of five lengths still leave
   one column of sums to read down. */
.entry-payers li { flex-wrap: nowrap; }
.payer-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
              white-space: nowrap; }
.payer-rest { flex: none; padding: 0.45rem 0.7rem; font-size: 0.9rem; }
.payer-currency { flex: none; font-size: 0.9rem; color: var(--muted); }
.entry-payers li > input { flex: 0 0 7rem; min-width: 0; text-align: right; }
/* The line under the payers, as close to the last row as a heading is to the
   first; a paragraph's own margin put it a line away. */
.entry-payers > p { margin: 0.4rem 0 0; font-size: 0.9rem; font-variant-numeric: tabular-nums; }

/* A refused field's line in a row that sets its fields side by side (C25
   section 2): validate.js puts it right after the field, which in these
   rows makes it a flex item wedged beside the field or its button. A row
   of its own under the row instead, after everything else in it. */
.entry-participants li > .field-error,
.entry-payers li > .field-error,
.entry-rate > p > .field-error,
.customs-good > form > .field-error,
.balance-rate-day > .field-error { flex: 1 1 100%; order: 1; }
.entry-payers li:has(> .field-error),
.balance-rate-day:has(> .field-error) { flex-wrap: wrap; }
.balance-rate-day:has(> .field-error) { align-self: stretch; }

/* The bulk actions, folded away over the lines: one row per kind, its name
   on the left and its buttons after it. Folded because open they were eight
   buttons standing over every line list (Andreas, 19.09.2026). Since C28
   section 6 the fold looks like a folded tile - frame, head, title, quiet
   line, chevron - but through classes of its own rather than `.tile` and
   its family, which the rules above share with it one at a time; a fold
   nested inside an entry card carrying `.tile` itself collided with every
   selector in the app scoped to "the card's own tile" (fix round 1,
   28.09.2026). */
.entry-items-bulk { margin-bottom: 0.8rem; }
.entry-items-bulk-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.5rem;
  margin-top: 0.4rem;
}
.entry-items-bulk-row > span { flex: 0 0 7rem; font-size: 0.85rem; color: var(--muted); }

/* A line of its own under «für alle» and «für mich» (Andreas, 28.09.2026).
   On a phone it takes the row's whole width, where «für mich» already
   wraps to: set in beside the label, five names took three lines at 390px.
   Wider, it starts where the buttons start - the 7rem label and the row's
   0.5rem gap. */
.entry-items-bulk-chips {
  flex: 1 0 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
@media (min-width: 46rem) {
  .entry-items-bulk-chips { padding-left: calc(7rem + 0.5rem); }
}
.entry-items-bulk-body > p { margin: 0.5rem 0 0; font-size: 0.85rem; }

/* On a phone every row stacks (C29 section 1, Andreas, 29.09.2026): the
   caption on a line of its own and the controls under it from the row's
   left edge, so no button stands in a column the next line leaves - «für
   mich» used to wrap under «Personen» while «für alle» stood beside it. It
   replaces the customs row's own grid (C15 section 2). Wider, the 7rem
   caption column stays and a row's buttons fit beside it.

   width < 46rem, not max-width: 46rem (fix wave, 29.09.2026): max-width
   and the chip indent's own min-width above both match at exactly 46rem, so
   the caption stacked while the chips were set in to the buttons' column
   regardless - spec 1.2 wants exactly one of the two rule sets at every
   width, including 46rem itself. */
@media (width < 46rem) {
  .entry-items-bulk-row > span { flex-basis: 100%; }
}

/* The words over the column of lines, on the same four columns as a line so
   each stands over the box it names; the fourth is the cross's. */
.item-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 3.5rem 6rem 2rem;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.item-head > span:nth-child(2), .item-head > span:nth-child(3) { text-align: right; }

/* An item line: what it was, how many, what it cost, and who it was for -
   the people on a second line, because five names never fit beside three
   fields on a phone. */
.item-rows { gap: 0.6rem; }

/* .item-rows in the selector, not .item-row alone: the generic `.list li`
   row above weighs a class and an element, so a bare `.item-row` loses to
   it and every field of a line stacked full-width under the one before. */
.item-rows > .item-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 3.5rem 6rem 2rem;
  gap: 0.4rem;
  align-items: center;
  padding: 0.5rem 0 0.6rem;
  border-bottom: 1px solid var(--border);
}

.item-rows > .item-row:last-child { border-bottom: none; }

/* Air over «Position hinzufügen». With no line yet, .item-rows collapses to
   nothing and the button came to sit against the column headings; the margin
   belongs on the button rather than on the list so that it holds in both
   states (Andreas, 19.09.2026). Measured against .item-row's own top
   padding, 0.5rem - the air a first row already gets from itself once one
   exists. */
.entry-items > .entry-item-add { margin-top: 0.5rem; }

.item-quantity, .item-amount { text-align: right; }

/* Every cell placed by hand. Auto-placement puts the cross after the two
   full-width rows below it, where it came out stranded and centred under
   the chips - it belongs at the end of the line it removes. */
.item-rows > .item-row > .item-remove { grid-column: 4; grid-row: 1; }
.item-chips { grid-column: 1 / -1; grid-row: 2; }

/* What a line answers for itself - budget, rate, invitation - on one wrapping
   row under its people, always in view (spec section 4), pinned to its own
   grid row like the chips above it. */
.item-flags {
  grid-column: 1 / -1;
  grid-row: 3;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 1rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.item-flags > label { font-size: inherit; color: inherit; }
/* The rate's list beside its word rather than under it, the way a tick stands
   beside its own; caption-over-field is for fields a row wide. */
.item-flags > label:has(> select) { flex-direction: row; align-items: center; gap: 0.4rem; }
.item-flags select {
  font-size: 0.85rem;
  padding: 0.3rem 1.8rem 0.3rem 0.5rem;
  background-position: right 0.4rem center;
}
/* A line nobody gave a rate yet, on a trip that declares (Andreas,
   18.10.2026): the card says «Zoll?», and on a long receipt this is the line
   it means. Only the border's colour, as for a refused field. */
.item-flags select[data-rate-required]:has(> option[value=""]:checked) {
  border-color: var(--terracotta);
}
/* What the lines miss of the receipt's amount (entry.js's showLinesGap), in
   the colour of the card's «Positionen?». */
.entry-lines-gap-slot > .entry-lines-gap { margin: 0.5rem 0 0; color: var(--terracotta-ink); }

/* A refusal about a line (C25 section 2). validate.js puts it right after
   the line's amount; here it takes a row of its own under the three
   fields, across the whole line, with the people and the flags one row
   lower while it stands. Auto-placed it came out under the flags, in the
   first column alone. */
.item-rows > .item-row > .field-error { grid-column: 1 / -1; grid-row: 2; }
.item-rows > .item-row:has(> .field-error) > .item-chips { grid-row: 3; }
.item-rows > .item-row:has(> .field-error) > .item-flags { grid-row: 4; }

.item-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

/* A name one taps to put a line on somebody's account. The tick itself is
   laid over the whole chip rather than hidden: it stays a real checkbox
   for the keyboard and for a screen reader, and the pill is its hit area.
   Never display:none - that would take it out of the accessibility tree
   and out of reach of every test that checks one. */
.chip {
  position: relative;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  font-size: 0.9rem;
  color: var(--muted);
}

.chip > input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* The disabled field's cursor, which stood later than this rule and won. */
.chip > input:disabled { cursor: not-allowed; }

.chip:has(> input:checked) {
  background: var(--terracotta);
  border-color: transparent;
  color: #fff;
}

/* A bulk chip whose person is on some lines but not all (Andreas,
   28.09.2026): neither the filled «on every line» nor the plain «on none». */
.chip:has(> input:indeterminate) {
  border-style: dashed;
  border-color: var(--terracotta-ink);
  color: var(--terracotta-ink);
}

.chip:has(> input:focus-visible) { outline: 2px solid var(--gold); outline-offset: 2px; }

/* The cross that takes a line away, and the two that fill every line at
   once: tools in a row, not offers. */
.item-remove {
  padding: 0.2rem 0.5rem;
  background: none;
  border-color: transparent;
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
}

.item-remove:hover { color: var(--danger); }

/* What each person will owe once this is saved, recomputed as it is typed.
   Two columns where the receipt is in another currency, one where it is
   not: the same number twice is not a second column, and the server marks
   which case this is (routes/entries.py's preview fragment). */
.preview-list { list-style: none; margin: 0; padding: 0; }
/* Stands where the rows would have stood, so it reads as the answer
   to the same question. */
.preview-note { margin: 0; }

.preview-list li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 0.6rem;
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}

.preview-list li:last-child { border-bottom: none; }
.preview-list li:first-child { padding-top: 0; }
.preview-list .preview-receipt { color: var(--muted); }
.preview-list.same-currency .preview-receipt { display: none; }
/* The budget bar under a row, where that person has set one. The row is a
   grid, not a flex line, so .budget-bar's own `flex: 1 1 100%` says nothing
   here and the bar would sit in the first column under the name. */
.preview-list .budget-bar { grid-column: 1 / -1; }

/* The note is the last thing on the form and the only field one writes a
   sentence into. */
.entry-note textarea { min-height: 4rem; resize: vertical; }
.entry-form > .entry-note { display: flex; margin-top: 1.4rem; }

/* The address and its map link on one line (C29 section 4): the field
   takes what is left, the link is a square beside it. A refusal
   validate.js puts after the label takes a line of its own, as it does in
   .manage-new. Spaced like a .manage-new row since C30: it stands among
   those rows now, under the receipt, not over the note any more. */
.entry-form > .entry-address {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.5rem;
  margin: 0 0 1rem;
}
.entry-address > label { flex: 1 1 auto; }
.entry-address > .field-error, .entry-address > .entry-locate-note { flex: 1 1 100%; }
/* Drawn with the tip screen's .tip-locate, but a symbol at every width and
   the map link's size beside it (Andreas, 06.10.2026). */
.entry-locate { flex: none; width: 2.75rem; height: 2.75rem; padding: 0; }
.entry-map {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--terracotta-ink);
}

/* The refusal over the buttons (see _entry_form.html), and the two buttons
   on one line: save first, delete right beside it. Pushed to opposite ends
   of the card the two read as belonging to different things, and on the wide
   column delete then sits under the far edge of the form with nothing near
   it. */
.entry-form > .entry-error { margin-top: 1.4rem; }
.entry-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
  margin-top: 1.2rem;
}

/* On a phone the save and what resets the form share the first line, and
   a saved entry's delete takes the whole line under them (Andreas,
   06.10.2026, second round). The reset only ever narrows the save when it
   appears: the row grows no line and the delete never moves.
   The reset is as wide as its label and the save takes the rest. A third of
   the row, as it was first, left «Zurücksetzen» 82 px for 98 px of label on
   an iPhone, and it ran up against its right border (third round).
   Without the first rule the global button[type="submit"] rule above would
   run the save the full width and leave the reset no room beside it.
   `button.draft-clear`, not just `.draft-clear`, on purpose: the first rule
   counts the same two classes plus the button type in its own specificity
   via :not()'s argument, so a selector with one class less would lose to it
   regardless of source order. */
@media (max-width: 46rem) {
  .entry-actions > button:not(.destructive) { flex: 1 1 0; min-width: 0; }
  .entry-actions > button.draft-clear { flex: none; }
  .entry-actions > button.destructive { flex: 1 1 100%; }
}

/* The same row .entry-actions is: what empties the form as wide as its
   label, the save with the rest. Its own class rather than .entry-actions,
   which also carries the entry's delete and the line of its own for it. */
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.8rem; }

@media (max-width: 46rem) {
  .form-actions { align-items: stretch; }
  /* button:not(.draft-clear), not button[type="submit"]: tip.html's
     take-over button is an Alpine button (type="button", @click="takeOver()"),
     never a submit, and still has to take the same share as the
     submit buttons customs.html and balance.html put in this same wrapper -
     .form-actions only ever holds these two buttons, so "not the clear
     button" picks out the same one either way. Compound regardless, because
     button[type="submit"]:not(.secondary, .destructive, .item-remove)
     (app.css:422) sets width: 100% at one element plus three classes - a
     bare .form-actions > button would lose and leave the save on a row of
     its own. */
  .form-actions > button:not(.draft-clear) { flex: 1 1 0; width: auto; min-width: 0; }
  .form-actions > button.draft-clear { flex: none; }
}

/* The tip screen's draft, offered once on the entry screen. */
.tip-draft-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1rem;
  padding: 0.7rem 0.8rem;
  background: var(--surface);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
}

.tip-draft-text { flex: 1 1 12rem; margin: 0; }

.tip-draft-dismiss {
  padding: 0.2rem 0.5rem;
  background: none;
  border-color: transparent;
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
}


/* The history under the form is a section like the form's own, shut by
   default; what it holds is set a step smaller. */
.entry-history > .history-list { font-size: 0.85rem; }

/* A table in three columns - what happened, who, when. Each event spans
   them and keeps its own rule under it; subgrid is what starts every
   event's name at one edge. The generic `.list li` row (a flex row with its
   ends pushed apart) reaches in here through the entries list around the
   card, hence display, alignment and gaps set outright. */
.history-list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) auto;
  column-gap: 1rem;
  list-style: none;
  padding: 0;
}
.history-list > li {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  column-gap: 1rem;
  row-gap: 0.2rem;
  justify-content: normal;
  align-items: baseline;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
}
.history-list > li:last-child { border-bottom: none; }
.history-by { color: var(--muted); }
.history-list time { text-align: right; }

/* What an edit changed, under the name and the time: the field in the
   name's column, the values beside it. fit-content(40%) rather than
   max-content: the kind column ("Wiederhergestellt" is the widest word this
   list ever shows) already eats into the width name and time leave for this
   grid, and a field column sized by its own widest label on top of that
   could push the value column to almost nothing - measured at 390 px, down
   to 30 px. Capped at 40%, the field column gives way once its natural
   width would take more than that, and the value column keeps the rest. */
.history-changes {
  grid-column: 2 / -1;
  display: grid;
  grid-template-columns: fit-content(40%) minmax(0, 1fr);
  column-gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
  color: var(--muted);
}
.history-changes > li { display: contents; }

/* A name, a field or a value that still does not fit breaks inside a long
   word rather than running past the card's own border - the note in
   "Zimmer mit Blick auf den See, Frühstück inbegriffen" is exactly the kind
   of value this is for. */
.history-field, .history-values, .history-by { overflow-wrap: anywhere; }

/* --- Balance ------------------------------------------------------------ */
}

/* Base, in the middle of the screens: an element's default. */
@layer base {
/* Like h1: a section name, not a headline. The browser's 1.5em put «Zahlungen»
   at the weight of the trip itself. */
h2 { font-size: 1.15rem; }
}

@layer screens {
/* Rows with a rule between them rather than a gap: the settlement is read
   down a column, and a column needs a line to be read down. */
.balance-rows, .balance-transfers, .balance-payments, .balance-categories,
.balance-budgets { gap: 0; }

.balance-row {
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}

.balance-rows > li:last-child, .balance-transfers > li:last-child,
.balance-payments > li:last-child, .balance-categories > li:last-child,
.balance-budgets > li:last-child { border-bottom: none; }

/* The name takes what is left, which puts state and sum at a common right
   edge whatever the names are. */
.balance-name { flex: 1 1 auto; min-width: 0; }
.balance-state { flex: none; font-size: 0.9rem; color: var(--muted); }
.balance-amount { flex: none; font-variant-numeric: tabular-nums; }

/* A column, not two cells shoved rightwards by whatever the amount left
   over: at flex: none alone, .balance-state and .balance-amount above only
   take the width their own row's content needs, so the state column starts
   wherever that row's amount happens to be wide - and an even row with no
   amount cell at all sits flush against the container's edge, a whole
   amount-column further right than every other row (see the empty <span>
   this macro's else branch adds in _transfers.html). min-width floors an
   ordinary amount at the same box width, which lines the state column up
   for the common case. Below that floor, the box grows with --amount-chars
   (routes/balance.py's own amount_chars, set on this list's own ul - C16
   section 3): the widest figure across every row and every opened group's
   members, so a four-digit balance no longer pushes its own row's state
   left of every other row's, the way a wide currency code already could
   (fix round 1: this was always a per-row trade-off before). FACTOR
   measured in WebKit (22.09.2026) against a trip whose balance runs to
   four digits, at 390px and 1100px - identical at both, since nothing
   here scales with viewport: a Range over each span's text, content width
   over character count. The four-digit rows ("1'884.96 USD", "1'256.64
   USD") measured 8.69px/char = 0.5434em; the three-digit ones (the trip's
   own "628.32 USD", and the brief's own "258.92 USD" injected the same
   way) measured 9.01px/char = 0.5633em, the widest of the four reference
   amounts named in the brief ("1'260.00 USD", "60.00 USD", "1'200.00 USD",
   "258.92 USD" all checked); "60.00 USD" measured 0.5581em. 0.5633em
   rounds up to 0.58em. What holds in every case is the amount's own right
   edge, since .balance-amount is
   always the row's last flex child - on one line, and since C13 on two:
   at 320px a four-digit amount too wide for the line wraps onto a line of
   its own, and the justify-content rule below puts it at the row's right
   edge there rather than at its left. Compound rather than bare .balance-state/
   .balance-amount: not to outrank the two rules above - they set flex/
   font-size/color, this sets min-width/text-align, so there is nothing
   there to lose a cascade tie against - but to scope the min-width to this
   one list. .balance-amount is reused unscoped by .balance-transfers (this
   file, transfer row) and .balance-payments (balance.html) for a plain
   amount cell with no state beside it, and neither of those rows should
   gain it. .balance-rows .balance-state is (0,2,0) against .balance-state's
   own (0,1,0) regardless, so this would still win on specificity alone
   even where it does clash. The «Budgets» tile's rows take the same two
   columns for «noch»/«überzogen» and the rest (24.09.2026), with the
   widest rest as their own --amount-chars. */
.balance-rows .balance-state, .balance-budgets .balance-state { min-width: 5.5rem; text-align: right; }
.balance-rows .balance-amount, .balance-budgets .balance-amount {
  min-width: max(5.25rem, calc(var(--amount-chars, 0) * 0.58em));
  text-align: right;
}

/* C13 section 2: whatever a row wraps onto its next line - the amount
   alone, or the state together with it - packs at the row's right edge,
   where every other amount in the column ends. justify-content rather than
   an auto margin on each cell: two auto margins on one line split its free
   space between them, which left a wrapped state floating halfway along
   (measured 21.09.2026). On a line that fits, the name's flex-grow takes
   all the free space first, so nothing moves. (0,2,0) against .list li's
   and .balance-group > summary's (0,1,1), both of which set
   space-between - under which a lone wrapped amount sat at the left. */
.balance-rows .balance-row, .balance-budgets .balance-row { justify-content: flex-end; }

/* Owing and being owed are the two halves of the same screen and have to be
   told apart at a glance. --olive was declared for a positive signal at the
   top of this file and had until now no rule to its name; the red is the
   same one every removal wears, at the weight of a statement rather than of
   a warning - nobody is at fault for owing. */
.balance-credit .balance-amount { color: var(--olive); }
.balance-debit .balance-amount { color: var(--danger); }

/* The rate the settlement converts at, read top-down (C15 section 4): the
   day, the rate it gives, and «Kurs neu abrufen» where a new fetch can
   change it. A column rather than the row it was: the day applies the
   moment it changes, so no button stands beside the field any more. None
   of these selectors shares an element with a later, equally specific
   rule here. */
.balance-rate { display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem; }
.balance-rate form { display: flex; align-items: end; gap: 0.4rem; }
.balance-rate-line { margin: 0; font-size: 0.9rem; font-variant-numeric: tabular-nums; }

/* The day's caption belongs to the rate line above it (C16 section 4), so it
   stands closer than the column's own 0.4rem gap between rows. (0,2,0)
   against `.caption`'s own margin (0,1,0) - the compound selector, not file
   order, is what wins this one (C16 fix round 1: file order alone is not a
   rule CLAUDE.md's own «Interface» section trusts). */
.balance-rate > .balance-rate-hint { margin: -0.2rem 0 0; }

/* «Kurs neu abrufen» failed (C15 fix round): the error box's own margin is
   0 (the shared .error:not(.field-error) rule far above, near the top of
   this file), which left it touching the rate block above - the same
   0.4rem this column's own rows keep apart, restored here rather than in
   .balance-rate itself, which the error box stands outside of. (0,2,0),
   equal to the shared rule's own (0,2,0) for .error:not(.field-error) -
   this rule wins only because it stands later in the file (both classes
   here, .balance-rate and .balance-rate-error, so no :not() is needed to
   reach the same specificity). */
.balance-rate + .balance-rate-error { margin-top: 0.4rem; }

/* «Offene Zahlungen» over the switch, 1rem under the rate block. (0,1,0)
   against .caption's margin 0 at (0,1,0): this one stands later. With no
   rate block the caption is the tile body's first child, and
   .tile-body > :first-child (0,2,0) takes its top margin back to 0. */
.balance-transfers-caption { margin: 1rem 0 0.3rem; }
.tile-body > .balance-transfers-caption:first-child { margin-top: 0; }

/* Two pictures of one settlement, so the pair reads as one control with a
   side chosen rather than as two links that happen to sit together. */
.balance-view, .balance-by {
  display: inline-flex;
  margin: 0 0 0.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.balance-view a, .balance-by a { padding: 0.4rem 0.9rem; color: var(--muted); }
.balance-view a.active, .balance-view a.active:hover,
.balance-by a.active, .balance-by a.active:hover { background: var(--terracotta); color: #fff; }

/* A transfer's second line: what the figure is in the trip's currency, the
   switch between the two currencies and «Bezahlt». A line of its own, so a
   phone never has to fit five things across one row; the note keeps the
   left edge, the controls the right. */
.balance-transfer-tools {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem 0.6rem;
  min-width: 0;
}
.balance-transfer-tools > .aside { margin-right: auto; font-size: 0.85rem; }
.balance-transfer-tools button { padding: 0.25rem 0.6rem; font-size: 0.85rem; }

/* The two currencies as one control with a side chosen, like .balance-view
   above; the fill comes from button[aria-pressed="true"]. */
.balance-currency {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.balance-currency button { border: none; border-radius: 0; }
.balance-currency button:disabled { opacity: 0.4; cursor: default; }

/* A payment group's own row doubles as a <summary>: closed, it is a row
   like any other, so opening the trip does not change what a group without
   groups looks like. Open, it sets its members off with a rule rather than
   the card border .tile uses - this list is read down a column,
   not browsed row by row, and a border per group would break that column
   into boxes. */
/* .list li (above) is the only rule in this file that turns a balance row
   into a flex container, and it is keyed to the li element. This row is a
   <summary>, a <details> child rather than an <li> (_transfers.html's group
   block), so .list li never matched it: name/state/amount sat as plain
   inline content, where min-width and text-align do nothing and
   .balance-name's flex: 1 1 auto had no flex context to grow inside - the
   state/amount column this file builds above never reached a group's own
   row (fix round 1). display/wrap/align/justify copied from .list li; gap
   is not (C24 section 8) - every summary this rule matches is itself a
   summary.balance-row (_transfers.html's group rows, balance.html's
   category rows), so it takes .balance-row's own 0.6rem here too, the same
   as every li row does now that the screens layer lets .balance-row beat
   .list li. */
.balance-group > summary, .balance-category > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
}
.balance-group > summary::-webkit-details-marker,
.balance-category > summary::-webkit-details-marker { display: none; }

/* .list li also matches the plain <li> that wraps this <details> - it has
   no class of its own, only an element name, but .list li is keyed to the
   element too. That makes the <li> a row-direction flex container holding
   one item, and align-items only stretches a flex item's *cross* axis
   (here, vertical); width is the *main* axis, which a lone item with the
   default flex: 0 1 auto sizes to its own content rather than filling.
   Without this the <details> - and the <summary> row inside it - stayed
   exactly one child's content narrower than every plain row, so the
   alignment the two rules above restore on the summary's own cells was
   still measured against the wrong right edge (fix round 1). */
.balance-rows details.balance-group { flex: 1 1 auto; min-width: 0; }
/* A fold's own name stands one chevron and its gap (.group-fold,
   .category-fold, .balance-row's 0.6rem) in from the row's edge; the names
   inside it stand one such step further (Andreas, 28.09.2026: a fixed
   1.2rem put them left of the name they belong under). */
.balance-group-members { margin-left: calc(2 * (1em + 0.6rem)); gap: 0; }
.balance-group-members > li:last-child { border-bottom: none; }

/* «Ausgaben» (C21 section 5). Its folds share the group folds' summary
   row (the two .balance-category > summary selectors above, in the same
   rules as .balance-group's) without sharing the group's class, which
   tests and styles read as «a group of people». What .balance-rows gives
   its own folds, this list needs as well, keyed to its own class: the
   <details> fills the flex <li> .list li makes of its parent - (0,2,1)
   against .list li's (0,1,1), the same as .balance-rows details.balance-group. */
.balance-categories details.balance-category { flex: 1 1 auto; min-width: 0; }
.balance-categories > .balance-total { font-weight: 600; }

/* The chevron before a category says it opens and turns when it does, the
   way a tile's own does (.tile-fold). Nothing else styles an icon in this
   list. */
.balance-category > summary > .category-fold { flex: none; color: var(--muted); transition: transform 0.15s; }
.balance-category[open] > summary > .category-fold { transform: rotate(90deg); }

/* A group's own row in «Salden» (C28 section 1.4): the chevron before its
   name that «Ausgaben»' categories have, and on a plain row an empty box
   of the chevron's width, so the flex gap after it and the name start at
   the same x on every row. */
.balance-group > summary > .group-fold { flex: none; color: var(--muted); transition: transform 0.15s; }
.balance-group[open] > summary > .group-fold { transform: rotate(90deg); }
.balance-rows .group-fold-space { flex: none; width: 1em; }

/* The percent in a column of its own (Andreas, 23.09.2026), the way the
   balances keep «bekommt»/«schuldet» in one (.balance-rows .balance-state
   and .balance-amount above): a floor for the percent, and every amount in
   the tile - category or party - as wide as its widest figure
   (routes/balance.py's category_chars), so the percent's column does not
   move with its own row's amount. (0,2,0) against .balance-state's and
   .balance-amount's (0,1,0); scoped to this list for the same reason the
   balances' pair is. */
.balance-categories .balance-state { min-width: 3.5rem; text-align: right; }
.balance-categories .balance-amount {
  min-width: max(5.25rem, calc(var(--amount-chars, 0) * 0.58em));
  text-align: right;
}

/* One participant's own budget, shown as a bar under their name in the
   «Budgets» tile and under their row in the split preview
   (schplit/budget.py's Bar, _budget_bar.html's budget_bar macro).
   flex: 1 1 100% pushes it onto a line of its own under the name, whatever
   `.balance-row`'s own flex-wrap already did with what stands before it.
   The track and the fill are two elements on purpose: the state colour
   lives on the fill alone, so the outer .budget-bar keeps the one, fixed
   class name tests/test_budget_routes.py counts - a modifier on the outer
   element would make that count depend on whose spending is past 80
   percent today. */
.budget-bar { flex: 1 1 100%; margin-top: 0.3rem; font-size: 0.8rem; }
.budget-bar-track {
  height: 0.35rem;
  border-radius: var(--radius);
  background: var(--border);
  overflow: hidden;
}
.budget-bar-fill { height: 100%; background: var(--olive); border-radius: var(--radius); }
.budget-bar-fill.budget-bar-warn { background: var(--gold); }
.budget-bar-fill.budget-bar-over { background: var(--danger); }
.budget-bar-label { display: block; margin-top: 0.15rem; color: var(--muted); }

/* A zero basis rather than the plain .balance-name's own "auto": with a
   long pair of names and a date, the browser's line-breaking otherwise
   counts that content's own width before any item shrinks, pushes the
   amount or the «×» form onto a line of its own to make room, and never
   asks .balance-name's min-width: 0 to give any of it back. At a basis of
   0 the name commits nothing to that count, flex-grow fills what is left,
   and only then does the row's own text wrap inside it if it must. */
.balance-payments .balance-name { flex-basis: 0; }

/* Deleting a payment is a correction on one row, not an offer the page
   makes - it wears the size of the line it stands in. The «×» keeps the
   look it has on an entry's lines (.item-remove), which this rule would
   otherwise shrink. */
.balance-payments button { padding: 0.25rem 0.6rem; font-size: 0.85rem; }
.balance-payments .item-remove { padding: 0.2rem 0.5rem; font-size: 1.2rem; }
.balance-payments .aside { font-size: 0.85rem; }

/* What a payment in francs is in the trip's currency, and at which rate - a
   line of its own under name and amount. */
.balance-payment-rate { flex: 1 1 100%; }

/* On the button itself before Task 9 (the two buttons were bare siblings,
   not yet a flex row) - moved to the wrapper now that .form-actions holds
   both, or the clear button beside it would sit 0.8rem higher than the
   save it shares a row with. */
.balance-payment-form > .form-actions { margin-top: 0.8rem; }

/* Sharing the settlement: the button, what it says afterwards, and the box
   that appears only where neither navigator.share nor the clipboard exists. */
/* «Text teilen» and «JSON teilen» on one row (C28 section 1.5), the gap a
   form's action row keeps between two buttons (.form-actions). */
.balance-share-row { display: flex; flex-wrap: wrap; gap: 0.8rem; }
.balance-share-status { color: var(--muted); }
.balance-share-fallback { display: block; width: 100%; min-height: 8rem; margin-top: 0.6rem; }

/* The block that starts with the «Text teilen»/«JSON teilen» row stands
   1rem under what precedes it - the 1rem .balance-share itself used to
   carry alone, before the row (C28 section 1.5). Nothing else sets a
   margin on it. */
.export-files { margin-top: 1rem; }

.export-files > .balance-share-row { margin-bottom: 0.6rem; }

/* «Auszug für»/«PDF teilen» is the last visible thing here whenever there
   is no error to show, so its own margin-bottom is the tile's whole
   trailing gap. .manage-new's generic 1rem (further down, "Managed
   lists") would otherwise stand on top of .tile-body's own
   padding-bottom - .export-row is a grandchild of .tile-body, so
   .tile-body > .manage-new:last-child never reaches it. Zeroed here, the
   tile ends at its own padding, the way every other tile does (C28
   section 1.5, fix round 2). */
.export-files > .export-row { margin-bottom: 0; }

/* .error itself carries margin: 0 (base layer), so the 0.5rem this row
   and the file error line had between them before C28 - back then
   .export-row's own margin-bottom - now belongs here instead, only where
   the error actually shows (C28 section 1.5, fix round 2). */
.export-files > .file-share-error { margin-top: 0.5rem; }

/* swap.js marks a form .is-busy while its request runs (busy() there). Its
   buttons dim and stop taking taps, so a second tap cannot post again -
   on the balance tab that drew a refusal over the success it just had,
   on the entries tab it saves a receipt twice (C22 section 6). A save
   button standing beside its form rather than inside it - people.html,
   _participants.html, _customs_goods.html - reaches it only through
   form="…", which the descendant selector below cannot see; busy() marks
   such a button is-busy in its own right, which is what the second half of
   this rule is for. form.is-busy button is (0,1,2); button.is-busy alone
   is (0,1,1); .balance-currency button:disabled (0,2,1) still outranks
   both and keeps its own 0.4. */
form.is-busy button, button.is-busy { pointer-events: none; opacity: 0.6; }

/* The participant card's own up/down arrows (_participants.html) sit in
   its <summary> and reach their form only through form="…", so busy()
   marks them is-busy in their own right, same as above - but a button
   inside a <summary> never toggles the fold itself (measured in C20), so
   letting a busy arrow keep taking the tap is safe, and leaving it
   pointer-events: none is not: the tap falls through to the summary
   instead and opens the card a move was never meant to open (final
   review, 23.09.2026). swap.js's listen() is what stops the tap posting a
   second time. summary button.is-busy is (0,1,2) - two elements, one
   class - and beats button.is-busy alone at (0,1,1), the rule that
   otherwise applies: the arrows never stand inside their form, so
   form.is-busy button (also (0,1,2), but not a match here at all) never
   meets them. */
summary button.is-busy { pointer-events: auto; }

/* --- Tip ---------------------------------------------------------------- */

/* The generic .manage-new (further down, "Managed lists") carries its own
   margin-bottom of 1rem - smaller than every margin-top below but not
   absent, so the visible gap after the bill (with tax, since C18 section
   1.1) and custom-amount rows used to depend on adjoining block margins
   collapsing to whichever was larger rather than on a number written here.
   Cancelled for this page's own two rows, so that gap is only ever the
   next block's own margin-top (task 9, after use). */
.tip-calculator > .manage-new { margin-bottom: 0; }

/* The two amount rows: a field, and whatever the number turned out to be
   wrong about underneath. The bill's own currency now leads the row as a
   <select> rather than trailing it as a span (task 9, after use), built as
   large as the amount fields themselves - like .entry-money's own
   currency/amount/tip row (:775 below). .tip-amount mirrors it with a
   locked box since C13: a disabled input that only ever reads the bill
   row's own choice (tip.html's :value), never a second select for the same
   value.

   The fields are matched by element rather than by class, the same shape
   .entry-money uses: the base rule for form controls (:331) reads
   `input:not([type="checkbox"], [type="radio"])`, and a :not() carries the
   weight of its heaviest argument - so it outweighs a bare class and left
   `.tip-bill-input` at 16 px, while the select beside it, matched there only
   as an element, took the 20.8 px asked for here. Andreas, 18.09.2026: the
   currency grew and the amount field did not. */
.tip-bill, .tip-amount { align-items: end; gap: 0.5rem; margin-top: 1.2rem; }
.tip-bill input, .tip-amount input, .tip-currency-select { font-size: 1.3rem; }
.tip-bill > label:has(> .tip-currency-select),
.tip-amount > label:has(> .tip-currency) { flex: 0 0 6rem; }

/* Bill and tax split what the currency leaves (C18 section 1.1): at 390 px
   the three stand in one row, each caption on one line; narrower, the tax
   wraps onto a line of its own. Not in equal halves, though (Andreas,
   22.09.2026): the sales tax is a part of the bill and never the longer
   figure, so the bill's basis is a digit wider than the tax's and both
   grow alike from there. (0,2,1) each against `.manage-new > label`'s 8rem
   basis (0,1,1). */
.tip-bill > label:has(> .tip-bill-input) { flex: 1 1 6.5rem; }
.tip-bill > label.tip-tax { flex: 1 1 5.5rem; }

/* The tip row's locked currency box (C13 section 6). Its tinted ground and
   its cursor come from the global input:disabled rule (near the end of
   this file); its muted text does not, because this box is not an
   unavailable choice - it shows the currency in force, in the colour of
   the figure beside it. (0,3,1) against input:disabled's (0,1,1).
   -webkit-text-fill-color and opacity for iOS Safari, which greys a
   disabled field's text through those two, where color alone does not
   reach. */
.tip-amount input.tip-currency:disabled {
  color: var(--text);
  -webkit-text-fill-color: var(--text);
  opacity: 1;
}
/* Flex basis only - the size, colour and margin come from .field-error
   since C15 (fix round 1: this rule's own font-size used to outrank
   .field-error's at (0,2,0) against (0,1,0)). */
.tip-bill > .error, .tip-amount > .error { flex: 1 1 100%; }

/* A list of country names since 18.09.2026, so the field takes what a name
   needs and leaves the rest of the row to the GPS button beside it, which is
   a help and not the point of the screen. */
.tip-country {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.6rem;
  margin-top: 1.2rem;
}

/* Twelve rem, where two letters took five: enough for a country's name, and
   on a phone that leaves the GPS button a row of its own - the field is what
   this row is about, the button is the shortcut to it. */
.tip-country > label { flex: 1 1 12rem; }

/* The entry form's locate button beside «Adresse» looks the same
   (Andreas, 06.10.2026). */
.tip-locate, .entry-locate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-width: 2.75rem;
  min-height: 2.75rem;
  background: none;
  color: var(--terracotta-ink);
  border-color: var(--border);
}
/* The map link beside it draws its pin at the same size: at the font's 1em
   it read smaller and fainter than the crosshair (Andreas, 06.10.2026). */
.tip-locate > .icon, .entry-locate > .icon, .entry-map > .icon { width: 1.3rem; height: 1.3rem; }

/* The words only where there is room (C18 section 1.3): on an upright
   phone the icon shares the country's line, named by the aria-label.
   (0,2,0) against nothing that sets this span's display. */
@media (max-width: 30rem) {
  .tip-locate > .tip-locate-label { display: none; }
}

.tip-locate:disabled, .entry-locate:disabled { color: var(--muted); cursor: default; }

/* Everything the country says about itself, on its own lines under the
   field: the convention in one sentence, and under it, folded, what is
   customary and when that was last checked. */
.tip-country > .tip-row-info, .tip-country > .tip-no-row,
.tip-country > .aside, .tip-country > .error { flex: 1 1 100%; }
.tip-row-info p { margin: 0.4rem 0 0; }
.tip-checked-on { font-size: 0.85rem; }
.tip-convention { font-weight: 600; }

/* What the country says about itself stands apart from the fields as a
   note - read, not filled in (Andreas, 19.09.2026). Tinted like the scan
   consent (.scan-consent): the page's own background on the card's
   lighter one. Compounded with .tip-country like the flex rule above it:
   .tip-no-row also carries .aside, and a bare class here would weigh no
   more than that one. The <p>'s own margins go; the padding is the space. */
.tip-country > .tip-row-info,
.tip-country > .tip-no-row {
  margin: 0;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: 0.4rem;
  background: var(--bg);
}

/* The fold of the country's note: laid out like a section title - the
   chevron before the words, right while shut, down while open - with the
   convention sentence keeping its own weight. */
.tip-row-info > summary {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
  list-style: none;
}
.tip-row-info > summary::-webkit-details-marker { display: none; }
.tip-row-info > summary::marker { content: ""; }
.tip-row-info > summary > .tile-fold { width: 1.1em; height: 1.1em; }
.tip-row-info[open] > summary > .tile-fold { transform: rotate(90deg); }

/* The two rows of buttons under their captions (C13 sections 5 and 7):
   each a fieldset whose legend is the caption, the buttons in an inner row
   rather than on the fieldset itself - that keeps the layout independent of
   how an engine treats display: flex on a fieldset, and the legend a plain
   line above. */
.tip-chips, .tip-round { display: block; margin-top: 1.2rem; }

/* The caption of a row of buttons, written like every field label on this
   screen (label:has(> input), app.css:536): small and muted - and not bold.
   The app-wide legend rule (app.css:563) sets 600 for the entry form's
   group headings; «Runden» wore that weight beside labels that carry none
   (Andreas, 21.09.2026). (0,1,1) against legend's own (0,0,1). 0.2rem is
   the gap a field label keeps to its field; a margin rather than padding,
   for the WebKit reason .entry-form legend's own comment records. */
.tip-calculator legend { font-weight: normal; margin-bottom: 0.2rem; }

/* One gap for every neighbour in both rows, across a line and between two
   (Andreas, 21.09.2026): the rounding buttons stood 0.2rem apart through a
   margin each, «auf» kept 0.3rem of its own on both sides plus the space
   between the two groups, while the chips beside them had 0.4rem. */
.tip-chips-row, .tip-round-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }

/* Buttons, not ticks under a pill: nothing is selected, each one simply
   fills the field - so shaped like every other button (C13 section 8), with
   --radius rather than the pill .chip keeps for the entry form's name
   chips (app.css:1286). The rounding buttons' vertical padding, so both
   rows stand equally tall; 0.6rem across, so Canada's five chips still
   share one line at 390 px. (0,2,0) against .chip's own (0,1,0). */
.tip-chips .chip { padding: 0.45rem 0.6rem; border-radius: var(--radius); font-size: 1rem; color: var(--text); }

/* Rounding: two directions and three steps, all of them toggles that say
   which one is on through aria-pressed. Outlined at rest, filled when
   pressed - see button[aria-pressed] above. No margin of their own: the
   row's gap is the only space between them (above). */
.tip-round button { padding: 0.45rem 0.9rem; }

/* The resting look is written as «not pressed» rather than as a plain
   .tip-round button: the two weigh the same, this rule stands later in the
   file, and a flat one would therefore win over button[aria-pressed] above
   and leave every toggle looking off however it was set. */
.tip-round button:not([aria-pressed="true"]) {
  background: none;
  color: var(--terracotta-ink);
  border-color: var(--border);
}

/* Each group stays whole and wraps as one: the directions, and «auf» with
   its steps (tip.html). Andreas, 19.09.2026: upright on a phone the row used
   to break between «auf» and its numbers. The same 0.4rem inside a group as
   between the two. */
.tip-round-row > .tip-round-group { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.4rem; }

/* What it comes to. Three rows read as one block, the total set apart by
   weight rather than by a second border - it is the same sum, said last. */
.tip-breakdown { margin: 1.2rem 0 0; }

.tip-breakdown > div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--border);
}

.tip-breakdown dt { color: var(--muted); }
.tip-breakdown dd { margin: 0; font-variant-numeric: tabular-nums; }
.tip-breakdown > div:last-child { border-bottom: none; font-weight: 600; }
.tip-breakdown > div:last-child dt { color: var(--text); }

/* On .tip-take-over itself before Task 9 - moved to the wrapper now that
   .form-actions holds it and the clear button as one flex row, or the
   clear button beside it would sit 1.2rem higher than the take-over it
   shares a row with. */
.tip-calculator > .form-actions { margin-top: 1.2rem; }

/* The tip screen's pair stands like every other form's (C14 section 6,
   Andreas 21.09.2026): the general .form-actions rules give «Zurücksetzen»
   its label's width and «Übernehmen» the rest. What they cannot give it is the
   height: «Übernehmen» is an Alpine button (type="button"), so the phone's
   tall submit rule (`button[type="submit"]:not(...)`) never reaches it, and
   «Zurücksetzen» stretches to whatever it is. This gives it the submit's
   padding. (0,3,1) against the base control rule's padding (0,0,1). */
@media (max-width: 46rem) {
  .tip-calculator > .form-actions > button.tip-take-over { padding-block: 1rem; }
}

.tip-saved-offline { color: var(--muted); }


/* --- Customs -------------------------------------------------------------- */

/* The close form stacks a label after a label with no wrapper between them
   (unlike balance's payment form, which puts its fields in a .manage-new
   row) - without a rule here they sit flush against each other, the same
   gap a group of fieldsets already gets below. The new-declaration form
   left this rule in C17 (Andreas, 22.09.2026, screenshot): its fieldsets
   hold only checkbox labels, which wrap side by side into rows, and the
   margin set every one after the first 0.6rem below it on the same row. */
.customs-close fieldset > label + label {
  margin-top: 0.6rem;
}

.customs-declarants { margin-top: 0.8rem; }
.customs-declarants label { padding: 0.25rem 0; }

/* The declaration page's own «Wer meldet mit» (C15 section 5): the same
   list as the new-declaration form, without the margin that form's second
   fieldset needs. #customs-people … (1,1,0) against .customs-declarants
   (0,1,0). Its hint's own margin is folded into the one-rhythm rule below
   (C16 section 6, addition). */
#customs-people .customs-declarants { margin-top: 0; }
#customs-people form > button { margin-top: 0.9rem; }

/* One rhythm for every checkbox group on the customs tab (C16, Andreas,
   22.09.2026): the legend 0.3rem over its hint, the hint 0.4rem over the
   first row of boxes - a fieldset is its own formatting context, so the
   hint's default 1em margins never collapsed and stood as they were.
   (0,2,1) against legend (0,0,1) and .aside's p default.

   Implementer's addition, verified empirically (pre-review fix,
   22.09.2026): the general checkbox label (`label:has(> input[type=
   "checkbox"])`, (0,1,2) - `:has()` takes its argument's own specificity,
   and `input[type="checkbox"]` is a type plus an attribute selector) is
   `inline-flex` and stays that way - the rows wrap, per the design. Left
   at its default `vertical-align: baseline`, though, it is the lone
   inline-level box on its own line right after the block-level hint, and
   baseline alignment against the line's own font metrics (the fieldset's
   inherited 1.5 line-height) left extra space around it that no margin
   rule could reach - not the checkbox's own 3px UA margin (reset made no
   difference) but the line box itself. `vertical-align: top` pins the row
   to the line box's top instead, which measured out flush: the fieldset's
   own bottom edge now lands on the last row's bottom edge exactly (0px
   residual, measured on the two groups this now reaches: the
   new-declaration form's declarants and #customs-people's - Task 7 later
   moved the purchase labels into `.customs-entry-grid`, off this rule).
   `display` is untouched, so multiple boxes still wrap onto one line
   where they fit.
   (0,1,3)/(1,1,1) against the general rule's (0,1,2) - each wins
   outright. The first of the two needs the extra `form` type selector
   for that: plain `.customs-new fieldset > label` ties the general rule
   at (0,1,2) and only wins by standing later in the file - `form.customs-new
   fieldset > label` gives it real weight instead (fix round 1, reviewer
   confirmed the tie by swapping the order in WebKit). */
.customs-new fieldset > legend { margin-bottom: 0.3rem; }
.customs-new fieldset > .aside,
#customs-people .customs-declarants > .aside,
#customs-entries form > fieldset > .aside { margin: 0 0 0.4rem; }
form.customs-new fieldset > label,
#customs-people .customs-declarants > label { vertical-align: top; }

/* «Eingeführte Einkäufe» (C16 section 6): the card's own 1rem
   (#customs-entries .tile-body > * + *) is the whole gap under the refresh
   button, and «Speichern» stands as far under the boxes as in «Wer meldet
   mit» - (1,1,2) against the bare button; the hint's own margin and the
   boxes' own alignment are set by the one-rhythm rule above. The noscript
   twin form carries the same fieldset. The question «Speichern» may ask is
   the modal dialog (C26 section 5.3), which takes no room in the card. */
#customs-entries form.customs-entries > button { margin-top: 0.9rem; }

/* «Eingeführte Einkäufe» in columns (C18 section 2.3, Andreas, 22.09.2026):
   one purchase per line on an upright phone, equal columns once two of
   14rem fit. An inner wrapper rather than a grid on the fieldset, for the
   reason the tip screen's rows record (.tip-chips-row). As grid items the
   labels are blockified from the general checkbox rule's inline-flex to
   flex, which lets a long title wrap inside its own cell. Nothing else
   sets display on this class. */
.customs-entry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 0.4rem 1rem;
}

/* The receipt folder's line under its list, or alone (C16 section 6): no
   paragraph margins of its own - the tile body's padding is the space under
   it - and the stack's 0.6rem after a list. (1,2,0) each; the second stands
   later. */
#customs-receipts .tile-body > .aside { margin: 0; }
#customs-receipts .customs-receipt-list + .aside { margin-top: 0.6rem; }

/* Every paragraph directly in the declaration's stack - the closed notice,
   the messages, «no goods yet» - drops its own margins, so the stack's
   0.6rem is the only space between two of its children (C15 section 5).
   (1,0,1) against p's user-agent margin and .aside (0,1,0). */
#customs-body > p { margin: 0; }

/* «bereits in «…»» after an entry in the new-declaration form (C15 section
   6): a note, a caption's size. (0,3,1) against .aside's colour only. */
.customs-new .customs-entries label > .aside { font-size: 0.85rem; }

/* The purchases fold's head (C28 section 2.2) reads like the legend over
   «Wer meldet mit» - small, muted, the name bold - with a chevron before
   it that turns when it opens, the way «Ausgaben»' categories do, and the
   count after it in the head's plain weight. Its margin is the legend's
   own (.customs-new fieldset > legend), so the hint stands as far under
   either heading. */
.customs-entries-fold > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.3rem;
  cursor: pointer;
  list-style: none;
  font-size: 0.85rem;
  color: var(--muted);
}
.customs-entries-fold > summary::-webkit-details-marker { display: none; }
.customs-entries-title { font-weight: 600; }
.customs-entries-chevron { flex: none; transition: transform 0.15s; }
.customs-entries-fold[open] > summary > .customs-entries-chevron { transform: rotate(90deg); }

/* The new-declaration form stacks a field, two fieldsets and the buttons
   with nothing between them. `.customs-new > * + *` outranks
   .customs-declarants (one class against one class, tied - this one stands
   later in the file, so it wins) - both set margin-top, and this is the
   one that should win. */
.customs-new > * + * { margin-top: 0.9rem; }

.customs-entry-drifted { color: var(--terracotta-ink); }

/* The goods list inside an opened receipt: the card's own padding
   (.tile) already sets it in from the summary's edge; this only
   adds the bottom space. On a closed declaration this list is all the fold
   holds. */
.tile > .customs-goods { padding: 0 0 0.9rem; font-variant-numeric: tabular-nums; }
.tile > .customs-goods-caption { margin: 0 0 0.3rem; }

/* On an open declaration the goods list follows the very same capture form
   the entries tab uses (_entry_form.html, included by _customs_goods.html) -
   without a line of its own between them, the first good's row starts right
   under «Speichern» and the two save actions on this card read as
   one. Set in like the card's own title line, not drawn edge to edge. Since
   C15 the caption «Angemeldeter Wert (CHF)» (.caption) stands first, so the
   space - and the separating line, moved from `.customs-goods` itself,
   which is no longer the form's immediate sibling - goes over it instead
   (tests/browser/test_customs.py's own
   test_the_goods_list_stands_apart_from_the_forms_save_button, updated to
   match).

   C22 section 3: the form no longer stands directly in `.tile` on its
   own - it is wrapped in `#customs-entry-N-body.entry-body`
   (_customs_entry_body.html), the same way C22 section 1 wrapped the
   entries tab's own row. `.entry-form` still matches (it is the wrapper's
   one child), but it is no longer `.customs-goods-caption`'s immediate
   sibling, so the first selector alone stopped matching - the same failure
   C22 section 1 found and fixed for `.entry-section` just above. `.lazy`
   is the group's placeholder before its first open (`_lazy.html`): without it the
   line would appear only once the fetch lands, and the caption would jump
   down under it. All three at (0,3,0), the wrapper's own class replacing
   `.entry-form` in the second and third. */
.tile > .entry-form + .customs-goods-caption,
.tile > .entry-body + .customs-goods-caption,
.tile > .lazy + .customs-goods-caption {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
}

/* Each good is a row of its own rather than a run-on flex line: what it is
   on the left, the correctable value and its save button on the right -
   readable at arm's length on a phone at an airport, which is the whole
   point of this screen. */
.customs-goods > li {
  display: block;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
}

.customs-goods > li:last-child { border-bottom: none; }

.customs-good > form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.6rem;
}

.customs-good-label { flex: 1 1 8rem; min-width: 0; font-weight: 600; }
.customs-good-amount { flex: none; font-variant-numeric: tabular-nums; }
.customs-good > form input { flex: 0 1 7rem; min-width: 0; text-align: right; }
.customs-good > form button { flex: none; padding: 0.45rem 0.7rem; font-size: 0.9rem; }

#customs-open .tile-body > .aside { margin: 0 0 0.6rem; font-size: 0.85rem; }

.customs-open-group + .customs-open-group { margin-top: 0.8rem; }
.customs-open-group h3 { margin: 0 0 0.3rem; font-size: 0.85rem; color: var(--terracotta-ink); }
.customs-open-group .list { gap: 0.2rem; }

/* The QuickZoll figures: a label and a value at a common right edge with a
   copy button past it, one row per figure - the same shape .balance-row
   already gives a settlement line. */
.customs-figures { gap: 0; }

.customs-figure {
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}

.customs-figures > li:last-child { border-bottom: none; }

.customs-figure-label { flex: 1 1 auto; min-width: 0; }
.customs-figure-value { flex: none; font-variant-numeric: tabular-nums; }

/* Obvious rather than the smallest control that would fit: this is what a
   person at a customs counter actually taps, under time pressure. */
.customs-figure button { flex: none; padding: 0.45rem 0.9rem; font-size: 0.85rem; }

/* A copy button keeps its width (C15 section 9): «Kopieren» and «Kopiert»
   share one grid cell inside the button, the one not in force only
   invisible, so the cell is as wide as the longer of the two. The grid is
   an inner span rather than the <button> itself - a button as a grid
   container is where engines have disagreed. visibility: hidden also drops
   a label from the button's accessible name. The failure text is the
   exception: it replaces both, and a refusal may widen the button. No rule
   here meets another at equal weight: all are new classes. */
.copy-labels { display: inline-grid; }
.copy-labels > span { grid-area: 1 / 1; }
.copy-labels > .copy-done { visibility: hidden; }
.copy-labels > .copy-failed { display: none; }
.copy-button[data-state="done"] .copy-idle { visibility: hidden; }
.copy-button[data-state="done"] .copy-done { visibility: visible; }
.copy-button[data-state="failed"] .copy-idle,
.copy-button[data-state="failed"] .copy-done { display: none; }
.copy-button[data-state="failed"] .copy-failed { display: inline; }

.customs-figure-heading { padding-top: 0.6rem; font-size: 0.85rem; font-weight: 600; }

/* The threshold arithmetic under the estimate (C17 section 4): each label on
   the left, its amount on the right, quiet - it explains the figure above,
   it is not one to act on. Never a copy button: see the template's own
   comment. `ul.customs-calc` (0,1,1) against `.list` (0,1,0), whose 1rem
   gap would set these rows as far apart as separate list entries;
   `ul.customs-calc > li` (0,1,2) against `.list li` (0,1,1), whose wrap
   would let a narrow screen push an amount under its label. */
ul.customs-calc {
  gap: 0.15rem;
  margin: 0.3rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}
ul.customs-calc > li { flex-wrap: nowrap; }
.customs-calc-label { flex: 1 1 auto; min-width: 0; }
.customs-calc-value { flex: none; font-variant-numeric: tabular-nums; }

/* The sum under its rule, like the last line of a receipt. (0,2,2) against
   the row rule above (0,1,2). */
ul.customs-calc > li.customs-calc-sum {
  border-top: 1px solid var(--border);
  padding-top: 0.15rem;
}

/* `p.customs-calc-note` (0,1,1) against `.caption`'s margin: 0 (0,1,0). */
p.customs-calc-note { margin-top: 0.3rem; }

.customs-notes { margin-top: 0.6rem; font-size: 0.85rem; }
.customs-notes p { margin: 0.2rem 0 0; }

/* The controls in the payment card, and now the entries card too (Task 4,
   fix round 20.09.2026, moved the refresh button up above its own list),
   follow one another as bare <form>s and sat flush under the field above
   them. Scoped to these two tiles rather than to .tile-body at large: every
   other tile in this app still holds a single form, and a global rule would
   move rows nobody asked about. The id always outranks a plain
   .customs-close/.customs-delete/.customs-take-over/.customs-refresh
   margin-top, so none of those classes carries one any more - it would be
   unreachable dead weight, shadowed everywhere they render (the first three
   only ever appear as a direct child of #customs-close .tile-body, the
   last also as one of #customs-entries .tile-body). Replaces what used to
   be a 1.4rem/1rem mix with one 1rem gap between whichever of a card's
   controls render next to each other. */
#customs-close .tile-body > * + *, #customs-entries .tile-body > * + * { margin-top: 1rem; }

/* «Korrektur speichern» under the fields, the card's usual 1rem away. */
.customs-close > .customs-correct { margin-top: 1rem; }

@media (max-width: 46rem) {
  /* .customs-refresh > button carries .secondary, and .customs-delete's
     button (from _confirm.html's confirm_form) is either type="button" or,
     without JavaScript, .destructive - both excluded on purpose from
     button[type="submit"]:not(.secondary, .destructive, .item-remove)
     (app.css:422), which is why neither ever got a width here. Each is the
     only action on its row and earns the same full width that rule gives
     everything else. */
  .customs-refresh > button, .customs-delete > form > button { width: 100%; }

  /* The «Bezahlung» card's row (C15 section 8): the card's action with two
     shares, «Löschen» with one, the shape «Speichern | Entfernen» has.
     The close button is a direct child, and .form-actions' own
     button:not(.draft-clear) share is the rest of the row beside a reset,
     so it gets its two shares here: (0,2,1) like that rule, and later. The
     take-over and delete wrappers need theirs here too, (0,2,0) each, no
     competing rule sets flex on them.
     «Löschen»'s trigger is a type="button" and never got the submit rule's
     padding-block, so it gets it here to stand as tall as its neighbour:
     (0,2,2) against .destructive's (0,1,0) and button's (0,0,1). */
  .customs-close-actions > button[type="submit"] { flex: 2 1 0; }
  .customs-close-actions > .customs-take-over { flex: 2 1 0; min-width: 0; }
  .customs-close-actions > .customs-delete { flex: 1 1 0; min-width: 0; }
  .customs-close-actions > .customs-delete > form > button { padding-block: 1rem; }
  .customs-close > .customs-correct { width: 100%; }
}

/* The QuickZoll walkthrough (_customs_quickzoll.html): one <section> per
   step, numbered in its own heading text rather than an <ol> - a step that
   renders nothing (quickzoll.step6_empty on a declaration with no
   borderline goods) must not renumber the ones after it. Steps 3 and 5
   reuse .customs-figure/.customs-figures as-is: the same copyable row,
   because these are the same figures _customs_result.html already shows,
   asked for again in QuickZoll's own order. */
.quickzoll-step + .quickzoll-step { margin-top: 1.2rem; }
.quickzoll-step h3 { margin: 0 0 0.3rem; }
.quickzoll-step p { margin: 0.3rem 0; }

.quickzoll-counts { display: flex; flex-wrap: wrap; gap: 0.3rem 1.4rem; }
.quickzoll-counts strong { font-variant-numeric: tabular-nums; }

.quickzoll-echo {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.quickzoll-not-allowed {
  margin-top: 1.4rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

/* The receipt folder (_customs_receipts.html, Task 9): a thumbnail-led row
   rather than .customs-good's own label-first line - the picture is the
   point of this list, so it leads it rather than merely illustrating it. */
.customs-receipt-list > li {
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
}
.customs-receipt-list > li:last-child { border-bottom: none; }

.customs-receipt {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
/* Task 7: the picture is now wrapped in a link (the pop-up's trigger), so
   the thumbnail's own sizing moves onto that wrapper - .customs-receipt >
   img (the rule this replaces) no longer matches anything, the <img>
   sitting one level deeper since the wrap. */
.customs-receipt-link {
  display: inline-flex;
  flex: none;
  width: 3rem;
  height: 3rem;
  border-radius: 4px;
  overflow: hidden;
}
.customs-receipt-link > img { width: 100%; height: 100%; object-fit: cover; }
/* Fix round 1: a PDF's word, in the same 3rem tile the picture would have
   filled - the same look .entry-receipt-kind already gives it, sized to
   this list's own thumbnail instead of that one's. */
.customs-receipt-kind {
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  font-size: 0.75rem;
  color: var(--muted);
}
.customs-receipt-shop { flex: 1 1 8rem; min-width: 0; font-weight: 600; }
.customs-receipt-day { flex: none; color: var(--muted); }
.customs-receipt-amount { flex: none; font-variant-numeric: tabular-nums; }


/* --- Managed lists: trips, people, participants -------------------------- */
}

/* Base, in the middle of the screens: an element's default and the
   managed-list row's own margin, which the tile forms take back to 0. */
@layer base {
/* Trips, people and participants are tiles now (.tile-form, .tile-actions);
   what stays here is the row that enters a new one and the short fields. */
h3 { font-size: 1rem; }
.manage-new { margin-bottom: 1rem; }
}

@layer screens {
/* Inside the account tile, below the password form: it ends the session,
   not anything about this trip. */
.sign-out { margin-top: 1.2rem; }
.sign-out p.aside { margin-top: 0.6rem; }

/* The password card's rhythm (C28 section 3.6): «Passwort» stands as far
   above the first field as one field stands above the next, and the
   button as far below the last as a participant's «Speichern» -
   .tile-actions' own margin, now that the button stands outside the form.
   Margins on the legend and between labels, not flex on the fieldset: how
   an engine lays out a flex fieldset with a legend is its own affair (the
   tip calculator's comment on this). */
.account-password > legend { margin-bottom: 0.8rem; }
.account-password > label + label { margin-top: 0.8rem; }

/* A new password's sentence under the person's card (C28 section 4.3):
   the accepted save shuts the card, so the sentence stands outside it, set
   in by the card's own side padding and a little under its border. */
.people-list .person-message { flex: 1 1 100%; margin: 0.4rem 0.8rem 0; font-size: 0.9rem; }

/* Three letters, two letters, one factor: fields whose content has a known
   short length, and a box the width of the row promises otherwise. The
   label around one is narrowed with it - the box alone would shrink inside
   a caption still claiming eight rem. */
.manage-new > label:has(> .field-code), .manage-new > label:has(> .field-factor) { flex: 0 1 6rem; }
.field-code { text-transform: uppercase; }

/* The country is a list of names now, not two letters (18.09.2026), so it
   asks for the room a name needs rather than the six rem a code takes. It
   is a width, not a fix: .manage-new's own min-width: 0 above already keeps
   the longest option - «Vereinigte Arabische Emirate» - from pushing the
   row past a phone's edge, whatever basis stands here. */
.manage-new > label:has(> .field-country) { flex: 1 1 12rem; }
.field-factor, .field-budget { text-align: right; }

/* A participant's row in an admin's list (C18 section 3.2, moved inward on
   23.09.2026): the two arrows stand in the card's own head line now. They
   used to have a column each beside the card, which took 6 rem off the
   width of every card in the list - Andreas: the narrower cards read
   worse than the arrows read useful.

   The head line takes a fourth column for them - name, (amount), arrows,
   chevron - so the chevron moves out of the third column into the fourth.
   Since C28 section 3.3 the arrows span the title and the quiet line under
   it and stretch to their height: a folded participant is as tall as every
   other folded tile with a quiet line, the arrows sit inside it with the
   head's own padding above and below, and the quiet line keeps to the
   name's two columns so it never runs under them.

   The two forms the buttons post (participant-up/-down) stand outside the
   card, empty: they carry no control of their own and must take no room. */
.participant-row > .participant-up, .participant-row > .participant-down { display: contents; }

.participant-row > .tile > summary {
  grid-template-columns: minmax(0, 1fr) auto auto auto;
}
.participant-row > .tile > summary > .tile-fold { grid-column: 4; }
.participant-row > .tile > summary > .tile-meta { grid-column: 1 / 3; }

.participant-moves {
  grid-column: 3;
  grid-row: 1 / span 2;
  align-self: stretch;
  display: grid;
  grid-template-columns: 2.75rem 2.75rem;
  gap: 0.4rem;
}
.participant-move-up { grid-column: 1; }
.participant-move-down { grid-column: 2; }

/* An arrow: an icon a thumb can hit, 44 px square, outlined like every
   .secondary. (0,2,1) against the browser's own default button padding
   and against `.secondary` (0,1,0); the phone's full-width submit rule
   excludes .secondary already. The height goes: the button stretches to
   the two lines, which at 16 px body text and 1.5 line height measure
   24 + 2.4 + 20.4 = 46.8 px - still a thumb's 44. */
.participant-moves > button.participant-move {
  display: grid;
  place-items: center;
  width: 2.75rem;
  padding: 0;
}
.participant-move > .icon { width: 1.2rem; height: 1.2rem; }

/* The factor on a line of its own (C28 section 3.3), at the width a factor
   box has everywhere else (plan ruling R3): stretched across the card, a
   box for «1» reads as a text field. */
.tile-form > label:has(> .field-factor) { align-self: flex-start; width: 6rem; }

/* --- Settings ------------------------------------------------------------ */

/* Two languages, one of them current: the current one is the disabled
   button, because there is nowhere to switch to. It wears the fill anyway -
   what it means here is «you are reading this one», not «this is broken». */
.lang-switch { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 1.4rem; }
.lang-switch button { padding: 0.35rem 0.8rem; font-size: 0.9rem; }

.lang-switch button:not(:disabled) {
  background: none;
  color: var(--terracotta-ink);
  border-color: var(--border);
}

.lang-switch button:disabled { cursor: default; }

/* The page's two parts (C18 section 4.1): this trip, then the device and
   whoever is signed in on it - set apart the way the admins' section used
   to be. */
.settings-part + .settings-part {
  margin-top: 2.4rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--border);
}
.settings-part > h2 { margin-top: 0; }

/* The one moment the invitation link is readable. It is long, it has to be
   selectable by hand where the clipboard is refused, and it must never be
   cut off - so it wraps rather than scrolls. */
.link-fresh {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.8rem;
  padding: 0.7rem 0.8rem;
  background: var(--surface);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
}

.link-fresh code {
  flex: 1 1 100%;
  font-size: 0.85rem;
  overflow-wrap: anywhere;
  user-select: all;
}

/* «Dieser Link wird nur jetzt angezeigt» belongs to the two buttons above
   it the way a hint belongs to its field (.tile-form > .aside, 0.3rem):
   the row gap of 0.6rem, taken back by half (C28 section 3.2). */
.link-fresh p { flex: 1 1 100%; margin: -0.3rem 0 0; font-size: 0.85rem; }

/* The hint under «Neuen Link erzeugen» (C28 section 3.2): no bottom margin
   of its own, so where it is the tile's last line - no link left to revoke
   - the tile ends at its own padding; where the revoke button follows, the
   gap a card keeps above its buttons. */
#link .tile-body > p.aside { margin: 0.6rem 0 0; }
#link .tile-body > p.aside + form { margin-top: 0.8rem; }

/* The trip's fields (C28 section 3.1, _trip_fields.html): currency and
   country, start and end in two equal columns at every width. The row of
   .manage-new they stood in gave each label a flex basis, and the last
   one of a wrapped row took all that was left - «Ende» stood twice as wide
   as «Beginn» on a Mac. min-width: 0 on the fields for the reason
   .manage-new's own rule gives: an input's intrinsic width would
   otherwise hold the column open. */
.trip-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.8rem; }
.trip-grid > label > input, .trip-grid > label > select { min-width: 0; }


/* Signing out is not what the bar is for. It sat in the nav as the one
   filled button on the page, louder than the two places it stands beside;
   here it wears their voice and their weight. */
.topbar nav form { margin-left: auto; }

.topbar nav button {
  padding: 0;
  background: none;
  border: none;
  color: var(--muted);
}

.topbar nav button:hover { color: var(--text); }


/* --- The two doors: login and «ich bin ...» ------------------------------ */

.login-form { display: flex; flex-direction: column; align-items: start; gap: 0.8rem; max-width: 20rem; }
.login-form > label { align-self: stretch; }

/* One name per row, each row a target a thumb can hit. */
.who-pick { display: flex; flex-direction: column; gap: 0.1rem; margin-bottom: 1.2rem; }

.who-pick > label {
  padding: 0.6rem 0.2rem;
  border-bottom: 1px solid var(--border);
}

.who-pick > label:last-child { border-bottom: none; }
.who-or { margin: 1.2rem 0; color: var(--muted); }
}

/* Base, at the end: the general disabled field, which the budget's and
   the tip's locked currency boxes override. */
@layer base {
/* A field that cannot be typed into has to look like one. Unticking a
   participant disables the box holding their factor, and three inert boxes
   sat in a column looking exactly as writable as the two live ones. Buttons
   are left out on purpose: the language switch disables the button for the
   language already being read, where «off» would be the wrong word for it.

   The colour alone rather than the shorthand: a select's arrow is a
   background image, and `background` would take it off. */
input:disabled, select:disabled, textarea:disabled {
  background-color: var(--bg);
  color: var(--muted);
  cursor: not-allowed;
}
}
