body {
  margin: 0;
  background: var(--dusk);
  color: var(--ink);
  font-family: var(--font-sans);
}

.punt-toolbar {
  display: flex;
  /* fox.css v31's .fox-toolbar stacks its children; everything else here - the
     gap, the centred alignment, .punt-nav's `flex: 1` - was written for a row,
     and one button per line pushed the page down by a third of the screen. */
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  padding: 10px 20px;
  position: sticky;
  top: 0;
  z-index: 10;
  /* fox.css also gives .fox-toolbar `max-width: 760px; margin: 24px auto 0`,
     which nothing here used to override: 24px of empty space above the bar on
     every screen, and a toolbar narrower than .punt-main below it. */
  max-width: none;
  margin: 0;
  /* It is sticky, and it used to be transparent - the list scrolled through it.
     Only visible once the nav stops wrapping and starts sliding underneath. */
  background: var(--dusk);
}

.punt-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 1.1rem;
}

.punt-brand-icon svg {
  display: block;
}

.punt-nav {
  display: flex;
  gap: 4px;
  /* `flex: 1` is `1 1 0%`, which lets the nav be squeezed to a narrow column
     and wrap one link per line before any button gives up a pixel. Basing it on
     its content instead means the buttons wrap to a second toolbar row, which
     is what the mobile breakpoint below already assumes. */
  flex: 1 1 auto;
  flex-wrap: wrap;
}

.punt-nav-link {
  padding: 6px 12px;
  border-radius: var(--radius-ui);
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 600;
}

.punt-nav-link:hover {
  background: var(--dusk);
}

.punt-nav-link.active {
  background: var(--ice);
  color: var(--ink);
}

/* fox.css styles .fox-btn:focus-visible but nothing ever styled these, and a
   nav that scrolls sideways on a phone needs the focused link to be findable. */
.punt-nav-link:focus-visible {
  outline: 2px solid var(--ice);
  outline-offset: 2px;
}

/* Above the mobile breakpoint the menu is dissolved: the „…" goes away and the
   four buttons become direct children of the toolbar row again. */
.punt-toolbar-actions,
.punt-toolbar-actions-body {
  display: contents;
}

.punt-toolbar-actions-summary {
  display: none;
}

/* `display: contents` above would beat the UA stylesheet's `[hidden]`, and
   app.js un-hides the wrapper only once there is a session. Higher specificity
   than the rule inside the breakpoint too, so this holds at every width. */
.punt-toolbar-actions[hidden] {
  display: none;
}

.punt-main {
  max-width: 860px;
  margin: 0 auto;
  padding: 20px 16px 60px;
}

.punt-page h2 {
  margin-top: 0;
}

.punt-subheading {
  margin-top: 40px;
  border-top: 1px solid var(--hairline);
  padding-top: 20px;
}

.punt-hint {
  color: var(--ink-soft);
  font-size: 0.9rem;
}

.punt-footer {
  color: var(--ink-soft);
  font-size: 0.75rem;
  text-align: center;
  padding: 0.5rem;
}

/* The page nav's other home. Below the mobile breakpoint app.js moves #punt-nav
   in here (see placeNav() in app.js); above it this is empty and hidden, and
   the header is exactly the row it always was. Fixed rather than sticky: the
   point is that the tabs are reachable without scrolling back up. */
.punt-bottom-bar {
  position: fixed;
  inset: auto 0 0 0;
  /* The same layer as the sticky toolbar. The „…" menu is 11 and hangs off the
     top of the screen, and a <dialog> is in the top layer above both. */
  z-index: 10;
  display: flex;
  background: var(--dusk);
  border-top: 1px solid var(--hairline);
  /* viewport-fit=cover in index.html is what makes the inset non-zero; without
     the padding the last row of tabs sits under the home indicator. */
  padding: 4px 4px calc(4px + env(safe-area-inset-bottom, 0px));
}

/* `display: flex` above would beat the UA stylesheet's [hidden], and above the
   breakpoint this element is empty - same trick and same reason as
   .punt-toolbar-actions[hidden]. */
.punt-bottom-bar[hidden] {
  display: none;
}

.punt-bottom-bar .punt-nav {
  flex: 1 1 auto;
  justify-content: space-around;
  gap: 0;
}

/* No height in px: at data-text-size="xlarge" the labels grow and the bar has
   to grow with them rather than clip. 44px is a touch target, not a size - the
   same one .punt-icon-btn already sets. */
.punt-bottom-bar .punt-nav-link {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  /* Narrow, because the five labels only just fit a 360px screen: at 8px each
     side they are edge to edge and a mis-tap lands on the next page. */
  padding: 6px 6px;
  font-size: 0.8rem;
}

.punt-empty {
  color: var(--ink-soft);
  font-style: italic;
}

/* Visible to screen readers only - see live-region.js. */
.punt-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.punt-add-form {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
}

.punt-add-form .fox-input {
  flex: 1;
}

/* Packing tree */

ul.punt-tree {
  list-style: none;
  padding-left: 0;
  margin: 0;
}

ul.punt-tree ul.punt-tree {
  padding-left: 26px;
  margin-top: 4px;
}

.punt-tree-item {
  margin: 4px 0;
}

.punt-tree-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-radius: var(--radius-ui);
  border: 1px solid transparent;
  background: var(--snow);
  flex-wrap: wrap;
}

.punt-tree-row-actions {
  display: flex;
  gap: 6px;
  flex-wrap: nowrap;
  margin-left: auto;
}

.punt-inline-add {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 4px 0 4px 26px;
}

.punt-inline-add .fox-input {
  flex: 1;
  min-width: 80px;
}

.punt-tree-row.punt-drop-before {
  border-top: 2px solid var(--moss);
}

.punt-tree-row.punt-drop-after {
  border-bottom: 2px solid var(--moss);
}

.punt-tree-row.punt-drop-into {
  border-color: var(--aurora, var(--moss));
  background: var(--dusk);
}

/* The row being carried, while the pointer is down on its handle. */
.punt-tree-row.punt-dragging {
  opacity: 0.55;
}

.punt-drag-handle {
  cursor: grab;
  color: var(--ink-soft);
  user-select: none;
  /* Load-bearing, and the whole of issue #1 on the CSS side: without it a
     finger on the handle scrolls the page and the browser cancels the pointer
     stream mid-drag, so pointermove never reports the move. */
  touch-action: none;
  /* A ⠿ with 2px of padding is not a thumb target. The same 44px minimum every
     .punt-icon-btn on the row already meets. */
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

.punt-drag-handle:active,
.punt-tree-row.punt-dragging .punt-drag-handle {
  cursor: grabbing;
}

.punt-item-text {
  flex: 1;
  min-width: 80px;
}

.punt-buy-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.85rem;
  color: var(--ink-soft);
  white-space: nowrap;
}

.punt-icon-btn {
  border: 1px solid var(--hairline);
  background: var(--snow);
  border-radius: var(--radius-ui);
  cursor: pointer;
  padding: 4px 8px;
  line-height: 1;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

.punt-icon-btn:hover {
  background: var(--dusk);
}

/* Questions */

.punt-question-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 16px;
}

.punt-question-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.punt-question-section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.punt-question-section-title-input {
  flex: 1 1 auto;
  font-weight: 600;
}

.punt-question-section-actions {
  display: flex;
  gap: 2px;
}

/* The shared inline-add row is indented for the packing tree; inside a question
   section it should line up with the question rows instead. */
.punt-question-section .punt-inline-add {
  margin-left: 0;
}

/* Header for the unsectioned bucket - a label, not an editable section. */
.punt-question-section-label {
  margin: 6px 0 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.punt-question-section-picker {
  font-size: 0.85rem;
}

.punt-question-section-picker select {
  margin-left: 4px;
}

.punt-question-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px;
  background: var(--snow);
  border-radius: var(--radius-card);
  border: 1px solid var(--hairline);
}

.punt-question-text-input {
  flex: 1 1 220px;
}

.punt-question-ratings {
  display: flex;
  gap: 10px;
  font-size: 0.85rem;
}

.punt-rating select {
  margin-left: 4px;
}

.punt-item-links {
  flex-basis: 100%;
}

.punt-item-links summary {
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 0.85rem;
  padding: 8px 4px;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.punt-item-link-checklist {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
  max-height: 160px;
  overflow-y: auto;
}

.punt-item-link-checklist label {
  padding: 4px 2px;
}

/* Notes */

.punt-notes-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.punt-notes-preview {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  padding: 12px 16px;
  background: var(--snow);
  overflow-y: auto;
}

/* Everything the markdown renderer can emit. fox.css styles the common
   elements, but only inside its own containers, and it has nothing for the
   GFM-specific output (tables, task lists, fenced code) - so the preview box
   carries its own. All scoped to .punt-notes-preview: note content must never
   restyle the app around it. */

.punt-notes-preview > :first-child {
  margin-top: 0;
}

.punt-notes-preview > :last-child {
  margin-bottom: 0;
}

.punt-notes-preview hr {
  border: 0;
  border-top: 1px solid var(--hairline);
  margin: 16px 0;
}

.punt-notes-preview table {
  border-collapse: collapse;
  width: 100%;
  margin: 12px 0;
}

.punt-notes-preview th,
.punt-notes-preview td {
  border: 1px solid var(--hairline);
  padding: 4px 8px;
  text-align: left;
}

.punt-notes-preview th {
  background: var(--dusk);
}

.punt-notes-preview code {
  background: var(--dusk);
  border-radius: var(--radius-ui);
  padding: 1px 4px;
  font-size: 0.9em;
}

.punt-notes-preview pre {
  background: var(--dusk);
  border-radius: var(--radius-ui);
  padding: 8px 12px;
  overflow-x: auto;
}

/* The <pre> scrolls, the <code> inside it must not add its own chrome. */
.punt-notes-preview pre code {
  background: none;
  padding: 0;
}

.punt-notes-preview blockquote {
  margin: 12px 0;
  padding-left: 12px;
  border-left: 3px solid var(--ice);
  color: var(--ink-soft);
}

.punt-notes-preview ul,
.punt-notes-preview ol {
  padding-left: 22px;
}

/* Task lists carry their own checkbox, so the bullet would be a second marker. */
.punt-notes-preview ul:has(> li > input[type="checkbox"]) {
  list-style: none;
  padding-left: 4px;
}

.punt-import-export {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.punt-export-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.punt-import-details textarea,
.punt-import-details input[type="file"] {
  display: block;
  width: 100%;
  margin-top: 8px;
  box-sizing: border-box;
}

/* Shopping */

.punt-shopping-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.punt-shopping-tree .punt-tree-item.punt-item-parent > .punt-shopping-row {
  font-weight: 700;
}

.punt-shopping-tree .punt-tree-item.punt-item-buy > .punt-shopping-row {
  font-style: italic;
}

/* flex-start, not center: a long item name wraps to two or three lines, and
   centring put the checkbox and the price against the middle one. */
.punt-shopping-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 4px 6px;
}

.punt-shopping-label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  flex: 1;
  min-width: 0;
}

/* Pinned like every other control in this file - left to shrink, the box
   squashes to a sliver next to a long name. The nudge lines it up optically
   with the first line of text now that the row aligns to the top. */
.punt-shopping-label > input[type="checkbox"] {
  flex: 0 0 auto;
  margin-top: 4px;
}

/* min-width: 0 is what actually lets the label give way; the parent having it
   is not enough, the text has to be an element that can shrink too. */
.punt-shopping-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The shop and price of the effective source, pushed to the right so the
   column of prices can be scanned without reading the item names.

   Capped and truncated for the same reason as the deadline summaries below:
   the shop field takes a pasted URL, and nowrap text has its whole width as a
   minimum size, so without a cap the span refused to shrink and overflowed
   across the item name. The full text stays reachable as a title.

   The cap is relative because .punt-shopping-label is `flex: 1` - which is
   `1 1 0%`, so the label is sized from what is left over rather than from its
   own content. A flat 20em is 320px, which on a 330px phone row was the whole
   row: the label grew into the two pixels that remained, the name wrapped one
   character per line and the pinned checkbox overflowed across the price.
   A percentage keeps at least 55% of the row for the item at any width. */
.punt-shopping-price {
  margin-left: auto;
  white-space: nowrap;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
  flex: 0 1 auto;
  min-width: 0;
  max-width: min(20em, 45%);
  overflow: hidden;
  text-overflow: ellipsis;
}

.punt-view-switch {
  display: flex;
  gap: 4px;
  margin-bottom: 12px;
}

.punt-view-switch .fox-btn[aria-pressed="true"] {
  font-weight: 700;
  text-decoration: underline;
}

.punt-item-sources,
/* On the shopping page the budget/size control sits below the row rather
   than inline in it, unlike its packing-page use inside .punt-tree-row - so
   it needs the same block-level indent the sources editor already has. */
.punt-shopping-tree > .punt-tree-item > .punt-budget-size {
  margin: 0 0 6px 26px;
  font-size: 0.9em;
}

.punt-item-sources > summary {
  cursor: pointer;
  opacity: 0.75;
}

.punt-source-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px 0 6px 4px;
}

.punt-source-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.punt-source-product,
.punt-source-shop {
  flex: 1 1 12ch;
  min-width: 10ch;
}

.punt-source-url {
  flex: 1 1 12ch;
  min-width: 10ch;
}

/* Text decoration is a button, not a link, on this row - the same reasoning
   .punt-icon-btn already applies to the delete button next to it. */
a.punt-icon-btn {
  text-decoration: none;
  color: inherit;
}

.punt-source-price-input {
  width: 9ch;
  flex: 0 0 auto;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* An explicit width for the same reason as the price input: fox.css gives
   every .fox-select width: 100%, which as a flex-basis claims the whole row
   and pushed the delete button onto a line of its own. */
.punt-source-currency {
  flex: 0 0 auto;
  width: 10ch;
}

/* An explicit width for the third time on this row, and for the reason
   .punt-source-currency above already documents. */
.punt-source-variant-select {
  flex: 0 0 auto;
  /* Wide enough for „ohne Variante" plus the dropdown arrow - a truncated
     placeholder is the one option nobody can guess the rest of. */
  width: 16ch;
}

/* The add row has no checkbox in front of it - the indent keeps its fields
   lined up with the ones above. */
.punt-source-add {
  padding-left: 20px;
}

/* One named Variante. The rule down the left is what says where it begins and
   ends: „Drucker 1" and „Drucker 2" are three rows each, and without it the six
   read as one list with two labels dropped into the middle. */
.punt-source-variant {
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-left: 3px solid var(--ice);
  padding-left: 12px;
}

.punt-source-variant-head {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

/* A name somebody typed, so it can be long. Breaking beats overflowing, the
   same call .punt-shop-group > h3 makes. */
.punt-source-variant-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.punt-source-variant-total {
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* Smaller than a fox-btn's default: it sits on a heading between two icon
   buttons, not in the row of page actions at the top. */
.punt-source-variant-pick {
  padding: 2px 8px;
  font-size: 0.9em;
}

.punt-shop-group {
  margin-bottom: 20px;
}

/* Normalised to a hostname by groupByShop(), but a shop can still be named
   something long enough to need breaking rather than overflowing. */
.punt-shop-group > h3 {
  margin-bottom: 4px;
  overflow-wrap: anywhere;
}

.punt-shop-group-none > h3 {
  opacity: 0.75;
  font-style: italic;
}

.punt-shop-total,
.punt-total {
  font-variant-numeric: tabular-nums;
}

.punt-total {
  font-weight: 700;
  margin-top: 12px;
}

/* The running total once it has passed the optional Gesamtbudget - the same
   colour the destructive dialog buttons use rather than a new warning red. */
.punt-total.punt-over-budget {
  color: var(--dawn);
}

.punt-budget-total {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

.punt-budget-total span {
  opacity: 0.75;
}

/* An explicit width for the same reason .punt-source-currency needs one:
   fox.css gives every .fox-select width: 100%, which as a flex-basis claims
   the whole row and put the currency on a line of its own under the amounts. */
.punt-budget-total select {
  flex: 0 0 auto;
  width: 10ch;
}

.punt-budget-total input {
  /* Wide enough for what a stay actually costs. At 7ch the spinner took
     enough of the field that a three-digit budget was drawn as "35(". */
  width: 10ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Dialog */

.punt-dialog {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  padding: 20px;
  max-width: 420px;
  width: calc(100vw - 32px);
  color: var(--ink);
  background: var(--snow);
  box-shadow: 0 8px 30px rgba(38, 52, 63, 0.15);
}

.punt-dialog::backdrop {
  background: rgba(38, 52, 63, 0.35);
}

.punt-dialog-title {
  margin: 0 0 8px;
  font-size: 1.05rem;
}

.punt-dialog-message {
  margin: 0 0 14px;
  color: var(--ink-soft);
}

.punt-dialog textarea,
.punt-dialog input {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 14px;
}

.punt-dialog-actions {
  display: flex;
  /* The "Schlüssel erstellt" dialog carries four choices, which is more than a
     phone's width fits on one row - without wrapping, the last one is simply
     unreachable. */
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

.punt-dialog-btn-danger {
  background: var(--dawn);
  border-color: var(--dawn);
  color: var(--snow);
}

.punt-dialog-btn-danger:hover {
  background: var(--dawn);
  filter: brightness(0.9);
}

/* Login, only ever rendered in local dev - in production auth.js has already
   navigated to the identity provider before the page gets a chance to paint. */
.punt-login-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 320px;
  margin-bottom: 12px;
}

.punt-login-error {
  color: var(--dawn);
  min-height: 1.2em;
}

/* What is not on the server yet (issue #2). Ordinary text most of the time;
   only the parked state is a button, because only it needs a decision. */

.punt-sync-status {
  font-size: 0.85rem;
  color: var(--ink-soft);
  white-space: nowrap;
}

.punt-sync-status.punt-sync-offline {
  font-weight: 600;
}

.punt-sync-parked {
  border-color: var(--dawn);
  color: var(--dawn);
  font-weight: 600;
}

.punt-parked-list {
  list-style: none;
  padding: 0;
  margin: 0 0 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 40vh;
  overflow-y: auto;
  text-align: left;
}

.punt-parked-reason {
  color: var(--ink-soft);
  font-size: 0.85rem;
}

/* Konflikte (Einstellungen). Everything in this list has already been decided,
   so it is styled as a record rather than as something demanding attention -
   the unseen ones get a rule down the side and nothing louder. The toolbar
   already carries the count; this is where the detail lives. */

.punt-sync-conflicts {
  color: var(--dawn);
  font-weight: 600;
  text-decoration: none;
}

.punt-conflict-list {
  list-style: none;
  padding: 0;
  margin: 0 0 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.punt-conflict-list li {
  padding-left: 12px;
  border-left: 3px solid transparent;
}

.punt-conflict-unseen {
  border-left-color: var(--dawn) !important;
}

.punt-conflict-outcome {
  color: var(--ink-soft);
}

/* The value that lost. Monospaced because it is a quotation of what was typed,
   not prose - and pre-wrap so a note keeps its line breaks. */
.punt-conflict-discarded {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  white-space: pre-wrap;
  margin: 4px 0;
}

.punt-conflicts-earlier summary {
  cursor: pointer;
  color: var(--ink-soft);
}

/* Termine, the countdown and the Fristen (issue #11). */

/* The toolbar chip. Quiet on purpose: it sits between the nav and the sync
   indicator, and it must never be the loudest thing up there. */
.punt-countdown {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-soft);
  font-size: 0.85rem;
  white-space: nowrap;
}

.punt-countdown svg {
  display: block;
  flex: none;
}

.punt-appointment {
  background: var(--snow);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  padding: 14px 16px;
  margin-bottom: 12px;
}

/* Large, because this is the one number the page exists to hold - it should be
   readable from across the room and without hunting for it. */
.punt-appointment-label {
  margin: 0;
  font-size: 1.35rem;
}

.punt-appointment-when {
  margin: 4px 0 0;
  font-size: 1.1rem;
}

.punt-appointment-countdown {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 8px 0 0;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--dawn);
}

.punt-appointment-countdown svg {
  display: block;
  flex: none;
}

.punt-countdown-past {
  color: var(--ink-soft);
  font-weight: 400;
}

.punt-appointment-edit summary,
.punt-deadline summary,
.punt-budget-size summary {
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 0.85rem;
  padding: 8px 4px;
  /* 44px so it is still a thumb-sized target, which is what the phone this
     mostly runs on needs. */
  min-height: 44px;
  display: flex;
  align-items: center;
}

.punt-appointment-edit-row,
.punt-deadline-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.punt-timezone-input {
  max-width: 12em;
}

.punt-appointment-form {
  flex-wrap: wrap;
}

/* Two checkboxes in the app, in two different pages, wanting the same row:
   label beside the box, softened, a little smaller than body text. */
.punt-exact-toggle,
.punt-webmcp-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-soft);
  font-size: 0.9rem;
  margin-bottom: 12px;
}

/* A browser with no document.modelContext disables the box rather than hiding
   it, so the setting stays findable and says why it is doing nothing. */
.punt-webmcp-toggle:has(:disabled) {
  opacity: 0.6;
}

/* The Zugangsschlüssel list. One row per key: what it is for, then the details
   that identify it, then the way to get rid of it. On a phone the meta line
   wraps under the name rather than squeezing the name to nothing - which name
   it is, is the part being read. */
.punt-api-keys {
  margin-bottom: 12px;
}

.punt-api-key {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
}

.punt-api-key-label {
  font-weight: 600;
}

.punt-api-key-meta {
  color: var(--ink-soft);
  font-size: 0.85rem;
  /* Takes the rest of the row so the revoke button is pushed to the end, and
     is the first thing to wrap when there is no rest to take. */
  flex: 1 1 100%;
}

/* Stacked, not a row: a label, a field, a label, a select and a button do not
   share a line on a phone, and this form is used about twice a year. */
.punt-api-key-new {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  margin-bottom: 12px;
  max-width: 32rem;
}

.punt-api-key-new label {
  color: var(--ink-soft);
  font-size: 0.9rem;
}

.punt-api-key-new .fox-btn {
  align-self: flex-start;
  margin-top: 4px;
}

@media (min-width: 40rem) {
  .punt-api-key-meta {
    flex: 1 1 auto;
  }
}

/* The pairing QR in the "Schlüssel erstellt" dialog.

   The width is not decoration. A connect link is ~100 characters, which at the
   error-correction level the centre logo forces (H) is a 61-module code; below
   about 240px the modules stop being wide enough for a camera to separate,
   and that floor was measured by decoding a render, not guessed. 280px keeps
   margin for a phone held at an angle, in bad light, off a glossy screen. */
.punt-qr {
  align-self: center;
  width: 280px;
  max-width: 100%;
  margin: 4px 0;
}

.punt-qr svg {
  display: block;
  width: 100%;
  height: auto;
  /* The code needs a light ground to decode whatever the page theme is doing;
     uqr draws white modules, so the plate under it must be white too. */
  background: #fff;
  border-radius: 8px;
}

.punt-qr-caption {
  color: var(--ink-soft);
  font-size: 0.85rem;
  margin: 0;
  text-align: center;
}

.punt-connect {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* The Serveradresse is long and must be readable in one piece to be checked
   against what was pasted into Claude - so it gets the full column and the
   monospace treatment rather than being truncated to fit a row. */
#punt-mcp-url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  max-width: 32rem;
}

/* The four steps inside the "Mit Claude verbinden" dialog. Numbered because
   the order matters: the header has to go in before the connector is saved. */
.punt-mcp-steps {
  margin: 0 0 4px;
  padding-left: 1.4em;
  text-align: left;
  color: var(--ink);
  font-size: 0.9rem;
  line-height: 1.5;
}

.punt-mcp-steps li + li {
  margin-top: 6px;
}

/* Closed, it is one short phrase and rides along on the row - the Packliste is
   a list to scan, and „Ohne Frist" on a line of its own under every single
   entry doubles the height of the whole page to say nothing. Open, it takes the
   full width, because three controls do not fit beside the text field. */
.punt-deadline,
.punt-budget-size {
  flex: 0 1 auto;
  min-width: 0;
}

.punt-deadline[open],
.punt-budget-size[open] {
  flex-basis: 100%;
}

/* Capped and truncated, because the item's own name is what the row is for.
   „3 Wochen vor Indikationsgespräch (bis 02.09.2026)" is longer than most
   entries on the list, and left to itself it squeezed the text field down to a
   few characters - the summary carries the full phrase as a title, and opening
   it shows the whole thing anyway. */
.punt-deadline > summary,
.punt-budget-size > summary {
  display: block;
  max-width: 20em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 44px;
  padding: 0 4px;
}

.punt-tree-row .punt-item-text {
  min-width: 10em;
}

.punt-deadline-weeks {
  width: 5em;
}

.punt-deadline-label {
  color: var(--ink-soft);
  font-size: 0.85rem;
}

/* A row that has a Frist, or a budget/size, says so without being opened. */
.punt-deadline.punt-has-deadline > summary,
.punt-budget-size.punt-has-budget-size > summary {
  color: var(--ink);
}

.punt-budget-size-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

/* An explicit width for the third time, for the reason .punt-source-currency
   and .punt-budget-total select already carry: fox.css gives every .fox-select
   width: 100%, which as a flex-basis claims the whole row - here it put the
   currency, and with it the Grösse, on lines of their own under the budget. */
.punt-budget-size-row select {
  flex: 0 0 auto;
  width: 10ch;
}

.punt-budget-input {
  width: 7em;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* A small basis that grows, rather than a width, so the last field on the row
   narrows instead of wrapping: at 10em the three fields overflow a phone once
   the shopping page's 26px indent is paid for, and flex wraps a line before it
   shrinks anything on it. The cap keeps it at its old size on a wide row. */
.punt-size-input {
  flex: 1 1 6ch;
  max-width: 10em;
}

.punt-due-group {
  border-left: 3px solid var(--ice);
  padding-left: 12px;
  margin-bottom: 16px;
}

.punt-due-group h3 {
  margin: 0 0 4px;
  font-size: 1rem;
}

/* Overdue is the one thing here allowed a colour, and it is the muted rose the
   destructive buttons use rather than a warning red. Nothing on this page is an
   emergency; the point is only that the eye finds it first. */
.punt-due-overdue {
  border-left-color: var(--dawn);
}

.punt-due-overdue h3 {
  color: var(--dawn);
}

.punt-due-items {
  margin: 0;
  padding-left: 20px;
}

.punt-due-meta {
  color: var(--ink-soft);
  font-size: 0.85rem;
}

/* The Darstellung block on the settings page. Each control gets a real visible
   <label> rather than the aria-label the theme select used to carry alone:
   there are six of them now, and six unlabelled dropdowns in a column is a
   puzzle whichever way you are reading it. */
.punt-display-settings p {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.punt-setting-label {
  min-width: 11rem;
  font-weight: 600;
}

/* ---------------------------------------------- reading comfort (Darstellung)

   Text size, line spacing, letter spacing and the body typeface, as four
   attributes on <html> - the same mechanism `data-theme` already uses, and set
   before the first paint by the inline snippet in index.html so a reload does
   not flash the smaller version first.

   Text size is one declaration and nothing else, because every font-size in
   this file and in fox.css is rem or em: there is not a single px size to miss.
   That is also why an accessibility overlay widget would have been the wrong
   answer here - the ones that exist walk the DOM setting inline styles, which
   route() then wipes on every navigation. A stylesheet rule survives it. */
html[data-text-size="large"] {
  font-size: 112.5%;
}

html[data-text-size="xlarge"] {
  font-size: 125%;
}

/* On `body`, not on `*`: both inherit, so everything that has not asked for
   its own value picks them up, and the handful of fox.css components that HAVE
   asked keep theirs - which is the wanted behaviour, not a gap. */
html[data-line-height="loose"] body {
  line-height: 1.8;
}

/* `em`, so the spacing tracks whatever the text size above ended up being. */
html[data-letter-spacing="wide"] body {
  letter-spacing: 0.05em;
}

/* Sylexiad Serif Medium, which fox.css already self-hosts in all four faces
   next to Atkinson Hyperlegible - both are designed for dyslexic and low-vision
   readers, so this is a choice between two good answers rather than a fallback.
   No @font-face and no new request here; only the swap.

   Scoped to `body` on purpose. The prep PDF names --font-serif directly in
   pdf-templates.js and must not follow this, and --font-mono stays mono.

   fox.css (since v39) reads h1-h6's font as var(--font-heading, var(--font-sans)),
   left unset so the fallback resolves against whatever --font-sans means in a
   given subtree. This rule never touches --font-sans, so without --font-heading
   here headings would keep resolving to Atkinson while body text went serif -
   the same mismatch fox.css's own v39 release fixed for its data-font
   alternates below. Those three need no such rule: they repoint --font-sans
   itself, so the fallback already lands on them. */
html[data-font="serif"] {
  --font-heading: var(--font-serif);
}
html[data-font="serif"] body {
  font-family: var(--font-serif);
}

/* Reduced-stimulus mode (issue #10).
   `.punt-reduced` sits on <html> - put there by the inline snippet in
   index.html before the first paint - and `.punt-editing` next to it, so
   everything below is one stylesheet and no page module has to know about any
   of it. Nothing is removed from the markup; the mode is entirely a question of
   what is drawn. */

/* Reduced-stimulus tracks the theme choice too, with its own muted palette per
   side rather than one palette forced over both - so it needs the same two-layer
   mechanism fox.css itself uses: a `prefers-color-scheme` rule for "system", and
   an explicit `[data-theme]` rule for a forced choice. Each layer also needs
   higher CSS specificity than fox's equivalent layer, or the muted palette here
   would lose to fox's ordinary one - see the specificity note below each rule. */

/* Light: default, or explicitly chosen.
   Beats fox's `:root[data-theme="light"]` (0,2,0) with 0,2,1. */
html.punt-reduced,
html.punt-reduced[data-theme="light"] {
  /* The fox palette, taken down to something that does not ask for attention.
     The body text stays readable on purpose - it is the accents, the surfaces
     and the borders that come down, not the words. */
  --dusk: #ececeb;
  --snow: #f5f5f4;
  --ink: #3a3a38;
  --ink-soft: #70706d;
  --hairline: #dcdcda;
  --ice: #e3e3e0;
  --moss: #8f9b8f;
  --dawn: #96797a;
}

/* Dark: system preference, when nothing overrides it.
   Fox's own system-dark rule is `:root:where(:not([data-theme="light"]))` -
   `:where()` contributes no specificity, so that rule is effectively 0,1,0.
   `html.punt-reduced:not([data-theme])` is 0,2,1 and wins. */
@media (prefers-color-scheme: dark) {
  html.punt-reduced:not([data-theme]) {
    --dusk: #1c1c1b;
    --snow: #242423;
    --ink: #e4e4e2;
    --ink-soft: #a8a8a5;
    --hairline: #333331;
    --ice: #2c2c2a;
    --moss: #7d8a7d;
    --dawn: #8a6f70;
  }
}

/* Dark: explicitly chosen.
   Beats fox's `:root[data-theme="dark"]` (0,2,0) with 0,2,1. */
html.punt-reduced[data-theme="dark"] {
  --dusk: #1c1c1b;
  --snow: #242423;
  --ink: #e4e4e2;
  --ink-soft: #a8a8a5;
  --hairline: #333331;
  --ice: #2c2c2a;
  --moss: #7d8a7d;
  --dawn: #8a6f70;
}

/* Decoration: the fox mark, the drag handles, the shadow under a dialog. */
.punt-reduced .punt-brand-icon,
.punt-reduced .punt-drag-handle {
  display: none;
}

.punt-reduced .punt-dialog {
  box-shadow: none;
}

.punt-reduced .punt-nav-link.active {
  font-weight: 700;
}

/* Making a PDF is a task for a good day, and it needs the server anyway. */
.punt-reduced .punt-prep-pdf-btn {
  display: none;
}

/* The countdown in the toolbar (issue #11). Information, but also pressure, and
   this mode exists to lower exactly that - the Termine page still has it for
   whoever goes looking. The paw goes with the rest of the decoration. */
.punt-reduced .punt-countdown,
.punt-reduced .punt-appointment-countdown svg {
  display: none;
}

.punt-edit-btn[aria-pressed="true"],
.punt-reduced-btn[aria-pressed="true"] {
  font-weight: 700;
  text-decoration: underline;
}

/* Only shown when there is something to unhide. */
html:not(.punt-reduced) .punt-edit-btn {
  display: none;
}

/* Everything that reorganises rather than records. Adding an item, typing in
   it and ticking "zu kaufen" stay; moving, indenting, deleting, rating,
   regrouping and comparing shop prices come back with "Bearbeiten". */
.punt-reduced:not(.punt-editing) .punt-tree-row-actions,
/* The question row's delete button hangs directly off the row rather than off
   an actions wrapper the way the packing tree's do. */
.punt-reduced:not(.punt-editing) .punt-question-row > .punt-icon-btn,
.punt-reduced:not(.punt-editing) .punt-question-ratings,
.punt-reduced:not(.punt-editing) .punt-question-section-actions,
.punt-reduced:not(.punt-editing) .punt-question-section-picker,
.punt-reduced:not(.punt-editing) .punt-item-links,
.punt-reduced:not(.punt-editing) .punt-deadline,
.punt-reduced:not(.punt-editing) .punt-budget-size,
.punt-reduced:not(.punt-editing) .punt-item-sources,
.punt-reduced:not(.punt-editing) .punt-shopping-actions,
.punt-reduced:not(.punt-editing) .punt-import-export {
  display: none;
}

.punt-nav-more {
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* Movement. This used to be unconditional on the system's own
   prefers-reduced-motion, on the reasoning that someone who has asked their OS
   for less movement has already answered the question. Mostly true, and still
   the default - but not always: a borrowed machine, or a phone left on the
   setting for one app, and there was no way to say otherwise from inside punt.

   So: `data-motion="reduced"` calms movement down on a system that never asked,
   and the media query below stays exactly as unconditional as it was for the
   system that did.

   The missing third state is deliberate. "Voll" - override the OS and animate
   anyway - cannot be built here, because fox.css carries its own
   `@media (prefers-reduced-motion: reduce) { * { transition: none !important } }`
   and no later author rule can hand an element back a transition that an
   !important one has taken away. See the comment on `motion` in
   preferences.js; scoping fox.css's rule is what would unlock it.

   Deliberately independent of the Reizarm toggle: that mode is about colour,
   decoration and a trimmed nav, this is only about movement. See the comment on
   `motion` in preferences.js. */
html[data-motion="reduced"] *,
html[data-motion="reduced"] *::before,
html[data-motion="reduced"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* One row on a phone, and a short one. What used to happen here instead: the
   nav wrapped one link per line, the PDF button took a full row of its own, and
   the whole bar was three rows deep and sticky - a third of the screen gone
   before the list started. Now the four buttons live behind „…", the brand is
   gone, and the nav has left the header entirely for .punt-bottom-bar, so all
   that is left up here is the countdown and that menu. */
@media (max-width: 640px) {
  .punt-toolbar {
    flex-wrap: nowrap;
    gap: 8px;
    padding: 6px 12px;
  }
  /* The whole brand goes, fox and wordmark both: the app is already open, and
     with the nav gone to the bottom bar the row is countdown and „…" - the one
     purely decorative thing in it is the last that should cost a phone's width.
     Hiding .punt-brand rather than its two children also stops the empty flex
     box from still eating a toolbar gap. */
  .punt-brand {
    display: none;
  }
  /* Clear of the bar fixed to the bottom edge - on .punt-main alone the release
     line in .punt-footer would still end up underneath it. */
  body {
    padding-bottom: calc(3.5rem + env(safe-area-inset-bottom, 0px));
  }
  /* viewport-fit=cover lets the page into the notch, so the two edges that were
     free before have to be bought back here. */
  .punt-toolbar {
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
  }
  .punt-main {
    padding-left: max(16px, env(safe-area-inset-left));
    padding-right: max(16px, env(safe-area-inset-right));
  }
  /* The countdown keeps only „noch ~5 Wochen" here. The toolbar does not wrap
     and the chip does not shrink, so a long label would push „…" off the edge;
     the Termine page says which Termin it is in full anyway. */
  .punt-countdown-label {
    display: none;
  }
  .punt-countdown {
    flex: 0 0 auto;
  }
  .punt-nav {
    /* A flex item will not shrink below its content unless told it may. */
    min-width: 0;
    flex-wrap: nowrap;
    /* The five labels fit a 360px screen at 0.8rem; at data-text-size="xlarge"
       they do not, and sliding sideways is a better answer than clipping. */
    overflow-x: auto;
    /* Firefox, and then WebKit/Blink: a scrollbar under the tabs is noise. */
    scrollbar-width: none;
  }
  .punt-nav::-webkit-scrollbar {
    display: none;
  }
  .punt-nav-link {
    white-space: nowrap;
  }
  .punt-toolbar .fox-btn {
    padding: 6px 10px;
  }

  /* Here it is a menu again. */
  .punt-toolbar-actions {
    display: block;
    position: relative;
    flex: 0 0 auto;
    margin-left: auto;
  }
  /* Stripped of the UA button chrome the same way .punt-nav-more is: in this
     bar it reads as a nav affordance, not as a fifth thing to press. */
  .punt-toolbar-actions-summary {
    display: inline-flex;
    align-items: center;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 700;
    padding: 4px 10px;
    border-radius: var(--radius-ui);
    color: var(--ink-soft);
    cursor: pointer;
  }
  .punt-toolbar-actions-summary:focus-visible {
    outline: 2px solid var(--ice);
    outline-offset: 2px;
  }
  .punt-toolbar-actions-body {
    display: none;
  }
  .punt-toolbar-actions[data-open] .punt-toolbar-actions-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    /* Over the page rather than in the bar: opening it must not make the
       header taller, which is the whole point of the menu. */
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    z-index: 11;
    padding: 10px;
    background: var(--snow);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-card);
  }
  .punt-notes-split {
    grid-template-columns: 1fr;
  }
  #punt-notes-input {
    min-height: 200px;
  }
  ul.punt-tree ul.punt-tree {
    padding-left: 14px;
  }
  .punt-inline-add {
    margin-left: 14px;
  }

  /* A phone row is about 330px. „Canon SELPHY CP1500 · officeworld.ch ·
     94.90 CHF" beside „Mobiler Mini-Drucker (A7 Kompaktformat)" does not fit
     at any split, and an ellipsis eats from the right - so what disappears is
     the price, the one thing the row is scanned for. The source gets a line of
     its own under the name, indented to line up with it the way the Quellen
     disclosure already is. */
  .punt-shopping-row {
    flex-wrap: wrap;
  }
  .punt-shopping-price {
    flex-basis: 100%;
    /* Not `auto`: on a line of its own the source is read with the name above
       it rather than scanned down a column, and 26px is the indent
       .punt-item-sources already uses. */
    margin-left: 26px;
    max-width: none;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  /* An item with no source would still open a flex line and pay the row gap
     for it - a blank 8px under every entry that has nothing to say. */
  .punt-shopping-price:empty {
    display: none;
  }
}
