/* Layout and component styling. Colours live in theme.css. */

* { box-sizing: border-box; }

/* `100%` alone resolves against the browser's *layout* viewport, which on
   iOS Safari is taller than the area actually visible when its address bar
   and toolbar are showing — the fixed tab bar (`.tab-bar`, CF-MOB-004) then
   sits below the fold until a scroll collapses that chrome. `100dvh` tracks
   the visible area as the toolbar shows/hides; `100%` stays as the fallback
   for a browser that does not support the dynamic unit. */
html, body {
  height: 100%;
  height: 100dvh;
  margin: 0;
  overflow-x: hidden; /* nothing here is meant to scroll sideways at all */
}

/* The page never magnifies by itself (CF-MOB-010). `manipulation` keeps
   panning and the user's own pinch but drops the double-tap zoom, which on a
   phone fired on any quick second tap — a pill, a tab — and left the page
   enlarged with the tab bar and the field's reset control partly off screen.
   It is inherited as an upper bound, so it cannot loosen the stricter values
   further down: the viewer's `pan-y pinch-zoom` (whose own double-tap zoom is
   a `dblclick` handler, CF-VIEW-036) and the date scale's `none` are unchanged. */
html { touch-action: manipulation; }

body {
  background: var(--page-bg);
  color: var(--text);
  font: 15px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
  overflow: hidden; /* the two panes scroll, the page does not */
}

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

/* ---- The waiting state (CF-OFF-013, CF-OFF-014) ----------------------- */

/* The mark shown in both waits. CF-OFF-014 requires the waiting state and the
   reconnect cover to match in size *and* treatment, so all three figures live
   here once and both sites read them. `index.html` also carries the size as
   width/height attributes, which is not duplication to be tidied away: those
   reserve the space before this stylesheet has loaded, which is exactly the
   moment the splash exists to cover. Keep them equal to this. */
:root {
  --wait-icon-size: 96px;
  --wait-icon-radius: 22%;
  --wait-icon-opacity: 0.9;
}

/* Displaced by the interface's own arrival rather than by any script: once
   `#app` has a child, this stops matching. That is the whole removal
   mechanism — nothing has to run for it to go away, and nothing has to run
   for it to stay, which is what makes it able to explain a client whose code
   never ran at all. */
#boot {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  background: var(--page-bg);
  color: var(--text-dim);
  /* Above nothing in particular — the app is empty while this shows — but
     stated so a slow module that paints early cannot land on top of it. */
  z-index: 1;
}

/* `#boot` precedes `#app`, so this looks forward with `:has()` rather than
   using a sibling combinator, which only ever matches backwards. */
#boot:has(~ #app:not(:empty)) { display: none; }

#boot img {
  width: var(--wait-icon-size);
  height: var(--wait-icon-size);
  /* Both waits stack the icon above wrapping text in a column. `flex-shrink`
     would let a long message on a narrow screen squeeze the icon out of
     square; an explicit width and height resist that on their own here, but
     the guard is stated so the square does not depend on that subtlety. */
  flex-shrink: 0;
  border-radius: var(--wait-icon-radius);
  opacity: var(--wait-icon-opacity);
}

#boot p {
  margin: 0;
  /* Breathing rather than spinning: a spinner claims progress it cannot
     measure, and this is a wait whose length the client does not know. */
  animation: boot-pulse 1.6s ease-in-out infinite;
}

/* One language is shown, chosen by the `lang` the inline script in index.html
   has already resolved. CSS rather than script: see index.html's own note. */
#boot .boot-nl { display: none; }
:lang(nl) #boot .boot-en { display: none; }
:lang(nl) #boot .boot-nl { display: inline; }

@keyframes boot-pulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  #boot p { animation: none; }
}

/* ---- Overall layout (CF-LAY-001, CF-LAY-002) ------------------------- */

#app {
  display: grid;
  grid-template-columns: var(--bar-width) 1fr;
  height: 100%;
  height: 100dvh;
  /* Clearance *beyond* the bottom safe area, for a control standing on that
     edge (CF-MOB-008, CF-LAY-004). The inset alone puts it flush against the
     home indicator, and a touch at its lower edge then competes with the
     swipe that closes or switches the app. `* 100` clamped by `min()` makes
     this all-or-nothing: the full gap on a device with a bottom inset,
     exactly 0 where there is none (a browser toolbar already separating the
     control from the edge), so the screen room is only spent where the
     gesture area actually is. Declared here rather than on the tab bar
     because the sidebar's foot row stands on the same edge in the two-pane
     layout of a phone held on its side. */
  --edge-gesture-gap: min(env(safe-area-inset-bottom, 0px) * 100, 0.75rem);
  /* The account control's place (CF-ACC-006), declared here rather than on
     `.account` because the sections have to leave room for it:
     `--account-room` is what a section keeps free at its right, beyond the
     right inset, for the head — its offset from the edge, its own width, and
     the same offset again as a gap. */
  --account-offset-top: 0.5rem;
  --account-offset-right: 0.6rem;
  --account-size: 2rem;
  --account-room: calc(2 * var(--account-offset-right) + var(--account-size));
}

/* Below the mobile width threshold, `TabBar` hides every panel but the
   active one (CF-MOB-001) — the desktop two-column grid would otherwise
   still reserve `--bar-width` for a `.bar` that is empty air half the time,
   and give the visible panel only what is left. One column, one panel
   showing at a time. `:has()` rather than a class on `#app`: the tab bar
   already carries its own placement as a class (`.tab-bar-bottom` /
   `.tab-bar-rail`), and stating the mobile condition once there and reusing
   it here avoids the two ever disagreeing about what "mobile" means. */
#app:has(.tab-bar:not([hidden])) {
  grid-template-columns: 1fr;
}

/* ---- The device's own edges (CF-LAY-004) ------------------------------ */

/* Under `viewport-fit=cover` (index.html) the page runs to the true screen
   edge: under the status bar of an installed app, and beside the sensor
   housing and into the rounded corners of a phone on its side. A section's
   background may reach there, and does; its content may not. So each section
   is padded by the safe-area inset of every edge it actually touches, and
   which edges those are depends on the layout: in the two-pane layout the
   sidebar touches the left edge and not the right, the results pane the
   right and not the left; in narrow-screen mode each section is the whole
   screen. The edge a tab bar stands on is the tab bar's to clear
   (CF-MOB-009), and its rules further down override these.
   The side insets matter mostly in the two-pane layout, not the rail: at
   the current figures a phone on its side is wider than the narrow-screen
   threshold. */
.bar {
  display: flex;
  flex-direction: column;
  min-height: 0;              /* or the label area cannot scroll */
  background: var(--panel-bg);
  border-right: 1px solid var(--panel-border);
  /* The bottom is the foot row with the cog: a control, so a margin beyond
     the inset rather than flush against the home indicator. */
  padding:
    env(safe-area-inset-top, 0px)
    0
    calc(env(safe-area-inset-bottom, 0px) + var(--edge-gesture-gap))
    env(safe-area-inset-left, 0px);
}

/* The right padding also clears the account control (`.account`), which is
   fixed to the window's top-right corner and would otherwise sit over the
   first row's last thumbnail (CF-ACC-006). Stated here as one padding rather
   than as a later override, so there is a single place holding the pane's
   insets. */
.results-pane {
  min-height: 0;
  overflow-y: auto;
  padding:
    calc(1rem + env(safe-area-inset-top, 0px))
    calc(var(--account-room) + env(safe-area-inset-right, 0px))
    calc(4rem + env(safe-area-inset-bottom, 0px))
    calc(1.25rem + var(--results-pane-edge-left, 0px));
}

/* Narrow-screen mode: every section is the whole screen, so the sidebar now
   touches the right edge too, and the account control stands over its search
   field — which keeps the same room free for it as the results pane does.
   The results pane touches the left edge only under a bottom bar; the rail
   stands there otherwise. */
#app:has(.tab-bar:not([hidden])) .bar {
  padding-right: env(safe-area-inset-right, 0px);
}
#app:has(.tab-bar:not([hidden])) .search-area {
  padding-right: var(--account-room);
}
#app:has(.tab-bar-bottom) .results-pane {
  --results-pane-edge-left: env(safe-area-inset-left, 0px);
}

/* `.bar`'s and `.results-pane`'s own `display` (flex, block) would otherwise
   win over the UA stylesheet's `[hidden] { display: none }` — an attribute
   selector has the same specificity as a class one, and these are declared
   later, so without this a panel `TabBar` hides (`../ui/tabBar.js`) stays
   laid out and visible underneath the one meant to replace it. */
.bar[hidden], .results-pane[hidden], .settings-panel[hidden] { display: none; }

/* ---- Account control (CF-ACC-001 … CF-ACC-006) ------------------------ */

/* Fixed to the window's top-right rather than placed in a header, because
   there is no header: the desktop chrome is a cog at the foot of the sidebar
   and the mobile chrome is a tab bar. Adding a whole bar for one control would
   cost every panel a strip of vertical space, which is the scarce direction
   here — the results grid is what wants the height.
   Its offsets are the ordinary spacing added to the top and right safe-area
   insets (CF-ACC-006), as the viewer's controls do (`--viewer-edge-*`): the
   spacing then stands as a margin beyond a status bar or sensor housing, and
   is exactly the ordinary spacing where the inset is 0. The sections pad
   themselves by the same insets, so the head keeps its place relative to
   the content it stands over.
   `z-index: 30` is above the panels and below the viewer, the run-log modal
   (both 50) and the reconnect overlay (90): those cover the interface
   deliberately, and a head floating over an open document would be chrome
   asserting itself over the thing the user actually opened. */
.account {
  position: fixed;
  top: calc(var(--account-offset-top) + env(safe-area-inset-top, 0px));
  right: calc(var(--account-offset-right) + env(safe-area-inset-right, 0px));
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--account-size);
  height: var(--account-size);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--text-dim);
  cursor: pointer;
}

.account:hover {
  background: var(--accent-soft);
  color: var(--accent);
}

.account-icon {
  width: 1.35rem;
  height: 1.35rem;
  display: block;
}

/* The mock state, drawn as a dashed ring around the head: the archive on
   screen is not the real one. Dashed rather than a colour alone, so the
   distinction survives the high-contrast theme and does not rely on colour
   vision — it is the only thing telling a user they are looking at fabricated
   documents. */
.account-mock {
  border: 1px dashed var(--text-faint);
  color: var(--text-faint);
}


/* The desktop cog (`.settings`, `src/ui/settings.js`) and the mobile
   Settings tab (`.settings-panel`) are two presentations of the same theme
   choice, never both reachable at once: whichever the tab bar is showing has
   already answered "where do I change the theme", and a cog surviving at the
   foot of the Search tab's sidebar would be a second, redundant answer sitting
   below a scroll a thumb might not even reach. */
#app:has(.tab-bar:not([hidden])) .settings { display: none; }

/* ---- Mobile tab bar (CF-MOB-001 … CF-MOB-009) ------------------------- */

/* Room for the bar is reserved with padding on the panels rather than the
   bar overlapping them (CF-MOB-009), unlike the viewer's overlay (`.viewer` is `position:
   fixed`) — a tab bar covering the last row of results or the bottom of the
   label area would hide exactly the content a thumb is about to reach for. */
#app:has(.tab-bar-bottom) .bar,
#app:has(.tab-bar-bottom) .results-pane,
#app:has(.tab-bar-bottom) .settings-panel {
  padding-bottom: calc(0.75rem + var(--tab-bar-size, 3.2rem));
}

#app:has(.tab-bar-rail) .bar,
#app:has(.tab-bar-rail) .results-pane,
#app:has(.tab-bar-rail) .settings-panel {
  padding-left: calc(0.75rem + var(--tab-bar-size, 3.2rem));
}

.tab-bar {
  position: fixed;
  z-index: 10;
  display: flex;
  background: var(--panel-bg);
  border-color: var(--panel-border);
}

.tab-bar[hidden] { display: none; }

.tab-button {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;         /* a target a thumb can hit without aiming */
  border: 0;
  background: none;
  color: var(--text-dim);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.tab-button.selected {
  color: var(--accent);
  font-weight: 600;
}

/* A phone held upright: the bar spans the bottom edge, tabs share the width. */
.tab-bar:not(.tab-bar-rail) {
  left: 0;
  right: 0;
  bottom: 0;
  flex-direction: row;
  border-top: 1px solid var(--panel-border);
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--edge-gesture-gap));
}

/* A phone on its side: the bar stands on the left edge, tabs share the
   height. Sized by its own label text (`width: max-content`) rather than a
   fixed guess — three short words is cheap to fit, unlike CasaFluxFE's seven
   pages, which had to drop labels here entirely to stay a thumb-width strip.
   Keeping the labels is worth the extra few rem: "which tab am I in" stays
   answered without learning icons that don't exist yet (§Framework, no icon
   set built). */
.tab-bar-rail {
  left: 0;
  top: 0;
  bottom: 0;
  flex-direction: column;
  width: max-content;
  border-right: 1px solid var(--panel-border);
  padding-left: env(safe-area-inset-left, 0px);
  /* A phone on its side keeps its home indicator along the bottom edge, and
     the rail runs down to it: without this its last button reaches under
     the indicator. */
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--edge-gesture-gap));
}

.tab-bar-rail .tab-button {
  padding: 0 1.1rem;
}

/* Only ever shown in narrow-screen mode, so it is the whole screen and
   touches every edge (CF-LAY-004); the tab bar's rules above override the
   edge it stands on — the bottom, or the left for the rail. The right keeps
   the account control's room free, as the results pane does (CF-ACC-006). */
.settings-panel {
  min-height: 0;
  overflow-y: auto;
  padding:
    calc(1rem + env(safe-area-inset-top, 0px))
    calc(var(--account-room) + env(safe-area-inset-right, 0px))
    calc(1rem + env(safe-area-inset-bottom, 0px))
    calc(1.25rem + env(safe-area-inset-left, 0px));
}

.settings-panel-head {
  font-weight: 600;
  margin-bottom: 0.75rem;
}

.settings-panel .settings-themes { gap: 0.5rem; }
.settings-panel .settings-theme { font-size: 1rem; padding: 0.5rem 0.4rem; }

/* A second radio group (language) below the first (theme) in the same
   popover/panel — a plain heading, unlike `.settings-popover-head`, which
   is laid out `space-between` for its close button and only belongs on the
   very first heading. */
.settings-popover .settings-section-head {
  margin-top: 1rem;
  margin-bottom: 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
}
.settings-panel .settings-section-head { margin-top: 1rem; }

/* ---- Search field (CF-SRCH-001 … CF-SRCH-003) ------------------------ */

.search-area {
  padding: 0.75rem;
  border-bottom: 1px solid var(--panel-border);
}

/* The field is one editable line. Recognised tokens are drawn as pills in a
   layer *behind* a plain text input, rather than as elements the input has to
   flow around — the caret, selection, undo and native text editing all have to
   keep working, and only a real input gives that. The two layers share the same
   font metrics and padding so the drawn tokens sit exactly under their
   characters. */
.search-field {
  position: relative;
  display: block;
  padding: 0;
  min-height: 2.5rem;
  background: var(--page-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  cursor: text;
  overflow: hidden;
}

.search-field:focus-within { border-color: var(--accent); }

/* A line that cannot be searched with yet is marked, and the results go on
   showing the last question that was finished (CF-SRCH-045). */
.search-field.incomplete { border-color: var(--warn); }

.pill-underlay,
.search-input {
  /* Any difference here shows up as drift between a token and its text. */
  font: inherit;
  font-size: 0.95rem;
  line-height: 1.6;
  padding: 0.45rem 0.6rem;
  white-space: pre;
  letter-spacing: normal;
}

/* iOS zooms the page into any text field focused with a font smaller than
   16px, and leaves it zoomed after the field is left (CF-MOB-010). 0.95rem is
   15.2px, so on a touch screen the field is set at 16px — both halves of the
   rule at once, since the underlay must keep the input's exact metrics. The
   viewport's `maximum-scale=1` would also suppress it, but on Android that
   disables the user's pinch altogether. */
@media (pointer: coarse) {
  .pill-underlay,
  .search-input { font-size: max(16px, 0.95rem); }
}

.pill-underlay {
  position: absolute;
  inset: 0;
  overflow: hidden;
  color: transparent;          /* the input draws the text; this draws behind it */
  pointer-events: none;
  user-select: none;
}

.search-input {
  position: relative;
  display: block;
  width: 100%;
  border: 0;
  background: transparent;
  color: var(--text);
  overflow-x: auto;
}
.search-input:focus { outline: none; }

/* Token backgrounds. Drawn on the underlay, so they sit behind the glyphs.
   The field's own background is `--page-bg`, so these must read against that
   rather than against the panel — `--pill-bg` is almost exactly `--page-bg` in
   the dark theme and the highlighting was invisible. */
.tok {
  border-radius: 999px;
  padding: 0.05rem 0;
  box-decoration-break: clone;
}
/* The spread is capped below the single space of real text separating two
   terms (this file's own note above: "the gap between terms is a single
   space, narrower than any button") — at 0.28em two adjacent pills' spreads
   overlapped that space entirely and touched, reported directly by the
   user from a screenshot. 0.2em leaves a visible gap between pills while
   still reading as padding around one, not a name printed on bare
   background — the alignment this comment's caller enforces is with the
   underlying text's box geometry, which this value does not touch. */
.tok-label,
.tok-quoted {
  background: var(--tok-label-bg);
  box-shadow: 0 0 0 0.2em var(--tok-label-bg);
}
.tok-date {
  background: var(--tok-date-bg);
  box-shadow: 0 0 0 0.2em var(--tok-date-bg);
}
/* Operators and brackets are structure, not content. They are left as plain
   text so the eye reads the pills as the terms and the rest as the shape of
   the question. */
.tok-operator,
.tok-open,
.tok-close { font-weight: 700; }
/* A word naming no known label, and a date still being typed: both are marked
   so the user can see the field did not recognise them yet. */
.tok-text { border-bottom: 2px wavy var(--danger); }
.tok-partial-date { border-bottom: 2px dashed var(--warn); }

/* The terms of the current line, listed below the field so each can be removed
   with one press (CF-SRCH-050).

   Not an `x` drawn on the token itself: the underlay has to stay glyph-for-glyph
   aligned with the input beneath it, so nothing there may occupy width. A button
   placed inside a token covers its last characters, and one hanging past the
   right edge lands on the following word — the gap between terms is a single
   space, narrower than any button. Both were built and both were unreadable. */
/* A term the user has pressed. One at a time, so only one close control has to
   exist — which is what lets it fit in a line whose drawn text must stay aligned
   with the text typed beneath it. */
.tok-selected {
  position: relative;
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: 999px;
}

/* The control sits just past the term's last character, in the gap the
   following space already provides. It is `position: absolute`, so it occupies
   no width of its own and pushes nothing out of register. */
.tok-x {
  position: absolute;
  top: 50%;
  left: 100%;
  transform: translate(0.15em, -50%);
  width: 1.15em;
  height: 1.15em;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-text);
  font: inherit;
  font-size: 0.8em;
  line-height: 1;
  cursor: pointer;
  z-index: 4;
}

.tok-x:hover { background: var(--danger); }

/* An operator left with nothing to work on. Marked rather than removed: which
   side of an `and` the user meant to keep is not knowable (CF-SRCH-054). */
.tok-dangling {
  color: var(--danger);
  text-decoration: underline wavy var(--danger);
  text-underline-offset: 0.2em;
}

.search-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 0.5rem;
  font-size: 0.8rem;
  color: var(--text-dim);
}

.linkish {
  border: 0;
  background: none;
  padding: 0;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
}

/* ---- Suggestions (CF-SRCH-004, CF-SRCH-029) -------------------------- */

.suggest-wrap { position: relative; }

.suggestions {
  position: absolute;
  z-index: 20;
  left: 0;
  right: 0;
  margin-top: 0.25rem;
  max-height: 17rem;
  overflow-y: auto;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px var(--thumb-shadow);
  padding: 0.2rem;
}

.suggestion {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  padding: 0.3rem 0.45rem;
  border: 0;
  border-radius: calc(var(--radius) - 0.2rem);
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.suggestion[aria-selected="true"] { background: var(--accent-soft); }
.suggestion:hover { background: var(--accent-soft); }

.suggestion .count { color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* An approximate match is worth showing differently from one actually typed;
   the ordering already keeps the two groups apart (CF-SRCH-029). */
.suggestion.approx .name { color: var(--text-dim); font-style: italic; }

.suggest-group {
  padding: 0.35rem 0.45rem 0.2rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

/* ---- Date range (CF-DATE-001 … CF-DATE-007, CF-DATE-013) ------------- */

.date-range {
  position: relative;
  padding: 0.7rem 0.75rem 0.5rem;
  border-bottom: 1px solid var(--panel-border);
}

.date-range-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-dim);
}

.date-range-label { font-variant-numeric: tabular-nums; }

/* When a range is set it is the answer to a question the user asked, so it
   reads as active rather than as a caption. */
.date-range.filtered .date-range-label {
  color: var(--accent);
  font-weight: 600;
}

/* Only worth offering once the range is actually narrower than the full
   span — otherwise there is nothing for it to reset. */
.date-range-reset-range {
  display: none;
  font-size: inherit;
}

.date-range.filtered .date-range-reset-range { display: inline; }

.date-quick-filters {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  margin-left: auto;
  /* "latest(" and ")" name what the links inside mean; they are text, not
     controls, so they share the row's dim caption colour rather than the
     link colour the day/week/month buttons use. */
  color: var(--text-dim);
}

.date-quick-filter-links {
  display: flex;
  gap: 0.4rem;
}

.date-quick-filter { font-size: inherit; }

/* A date term already in the search text (CF-DATE-013): the track, dots,
   year-range label and quick-filter links give way to a single "Reset date"
   control. Hidden with visibility/opacity rather than display, so the panel
   keeps its height and the label area below it does not jump. */
.date-range.date-taken .date-range-label,
.date-range.date-taken .date-quick-filters,
.date-range.date-taken .date-track,
.date-range.date-taken .date-ticks {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

/* Centred over the whole panel, not just the head row, since everything
   else in the panel is hidden while this shows — an absolute overlay is
   what makes "centred both ways" mean the panel as a whole rather than the
   thin row this button happens to share markup with. */
.date-reset {
  position: absolute;
  inset: 0;
  margin: auto;
  width: fit-content;
  height: fit-content;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

.date-range.date-taken .date-reset {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}

.date-reset:disabled {
  cursor: default;
  opacity: 0.5;
  text-decoration: none;
}

.date-track {
  position: relative;
  height: 2.1rem;
  /* Room for a dot's own half-width (see `.date-dot`'s `margin-left:
     -0.425rem`) at each end of the track, so a dot sitting at its year's 0%
     or 100% position does not have its outer half sitting flush against the
     sidebar's own edge — on a phone that put the rightmost dot right at the
     screen border, too close to grab without also catching the edge-swipe
     gesture the browser reads there. */
  margin: 0 0.5rem;
  cursor: pointer;
  touch-action: none;       /* or a drag scrolls the sidebar instead */
}

.date-histogram {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.date-rail {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--pill-border);
}

.date-selected {
  position: absolute;
  bottom: 0;
  height: 2px;
  background: var(--accent);
}

.date-dot {
  position: absolute;
  bottom: 1px;
  width: 0.85rem;
  height: 0.85rem;
  margin-left: -0.425rem;
  transform: translateY(50%);
  padding: 0;
  border: 2px solid var(--panel-bg);
  border-radius: 50%;
  background: var(--accent);
  cursor: grab;
  box-shadow: 0 1px 3px var(--thumb-shadow);
}

.date-dot:hover { transform: translateY(50%) scale(1.15); }
.date-dot.dragging { cursor: grabbing; transform: translateY(50%) scale(1.2); }

.date-ticks {
  position: relative;
  height: 1rem;
  margin-top: 0.15rem;
}

/* Ticks are positioned where their year actually sits on the compressed axis,
   not at even intervals along the track. */
.date-tick {
  position: absolute;
  transform: translateX(-50%);
  font-size: 0.66rem;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The end labels are pinned to the ends of the track rather than centred on
   their band. A centred label at ~98% would put half of "2026" past the right
   edge of the sidebar, where `overflow-x: hidden` on the page simply clips it
   — the label would be there and unreadable, which is worse than the missing
   label this fixes. Both keep the `left` the element already carries, so a
   band that is nowhere near the edge is unaffected. */
.date-tick.date-tick-last {
  left: auto !important;
  right: 0;
  transform: none;
}

.date-tick.date-tick-first {
  left: 0 !important;
  transform: none;
}

/* ---- Label area (CF-LBL-001 … CF-LBL-007) ---------------------------- */

.label-area {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.75rem;
}

.label-area-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.5rem;
  font-size: 0.8rem;
  color: var(--text-dim);
}

/* Inline, flowing like text — not a single-column list (CF-LBL-002). */
.label-flow {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

/* ---- Settings (CF-SET-001 … CF-SET-005) ------------------------------ */

/* A plain flex child after `.label-area`, not `position: sticky` or fixed —
   `.label-area` already takes `flex: 1` and scrolls within the remaining
   space (CF-LAY-002), so this row is naturally pinned below it without the
   cog ever entering the scrolling content. A row rather than a bare cog since
   CF-DOC-010: the unparseable-documents link sits centred in the same strip,
   the cog pinned to its side. */
.settings {
  position: relative;         /* the popover is positioned against this */
  flex: none;
  display: flex;
  align-items: center;
  border-top: 1px solid var(--panel-border);
  padding: 0.4rem;
}

/* Centred by taking the row's remaining space either side of the cog
   (`flex: 1` with no sibling before it), rather than `text-align: center` —
   that would centre within the row's own content width, not the row itself,
   which is not the same thing once the cog's width is subtracted. */
.unparseable-link,
.backlog-link {
  flex: 1;
  border: 0;
  background: none;
  color: var(--text-dim);
  font-size: 0.85rem;
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
}

.unparseable-link:hover,
.backlog-link:hover { background: var(--accent-soft); color: var(--text); }

/* CF-BKL-001: shown only while something is waiting, and drawn to catch the
   eye a little more than the unparseable link beside it — that one describes
   the archive, this one asks for something (SPEC.md §Documents waiting to be
   filed). The accent colour and weight do that without a badge. Sized to its
   own text rather than sharing the row: "3 to review" is short, and an equal
   share would wrap the unparseable link's longer label onto two lines. */
.backlog-link {
  flex: none;
  color: var(--accent);
  font-weight: 600;
}

.settings-cog {
  border: 0;
  background: none;
  color: var(--text-dim);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius);
  cursor: pointer;
}

.settings-cog:hover,
.settings-cog[aria-expanded="true"] { background: var(--accent-soft); color: var(--text); }

/* Anchored above the cog rather than below it: the cog sits at the very
   bottom of the sidebar, and a popover opening downward would be drawn
   outside the viewport. */
.settings-popover {
  position: absolute;
  z-index: 20;
  left: 0.4rem;
  bottom: 100%;
  margin-bottom: 0.4rem;
  width: 16rem;
  max-width: calc(100vw - 2rem);
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px var(--thumb-shadow);
  padding: 0.6rem;
}

.settings-popover-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
}

.settings-close {
  border: 0;
  background: none;
  color: var(--text-dim);
  font: inherit;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}

.settings-close:hover { color: var(--danger); }

.settings-themes {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.settings-theme {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.3rem;
  border-radius: calc(var(--radius) - 0.2rem);
  font-size: 0.88rem;
  cursor: pointer;
}

.settings-theme:hover { background: var(--accent-soft); }

/* A plain text link below the theme/language sections, not another radio
   group — `display: block` gives it the same section-to-section spacing the
   radio groups get from `.settings-section-head`'s own margin, without
   sharing that class's bold heading look, which a clickable link should not
   have. */
.settings-run-log {
  display: block;
  margin-top: 1rem;
  font-size: 0.85rem;
}

/* ---- Run log modal ---------------------------------------------------- */

/* Centred overlay, not a small anchored popover: the log's content is prose
   meant to be read rather than a short list of choices, so it gets the
   viewer's own backdrop-and-centred-box shape instead of `.settings-popover`'s
   anchored one. */
.run-log-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--on-document-backdrop);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

.run-log-modal[hidden] { display: none; }

/* Wider than the 40rem it was: a log entry names an incoming filename, the
   archive name it became and the destination folder, and at 40rem every one of
   those wrapped. `min()` rather than a plain width so it still shrinks to the
   window on a laptop or a phone — this takes the horizontal room where there
   is room to take, and asks for none where there is not. */
.run-log-panel {
  width: min(64rem, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px var(--thumb-shadow);
}

.run-log-head {
  flex: none;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--panel-border);
  font-weight: 600;
}

.run-log-close {
  border: 0;
  background: none;
  color: var(--text-dim);
  font-size: 1.2rem;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}

.run-log-close:hover { color: var(--danger); }

.run-log-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 1rem;
}

/* The parsed log (CF-LOG-003, CF-LOG-004). Monospace is applied to the
   filenames alone (`.run-log-name`) rather than to the whole block: names are
   compared character by character and want it, prose and notes do not and are
   harder to read in it. */
.run-log-text {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text);
}

.run-log-intro,
.run-log-empty {
  margin: 0 0 1.25rem;
  color: var(--text-dim);
  white-space: pre-line;   /* the intro's own line breaks, without a <pre>'s rigidity */
}

.run-log-run + .run-log-run { margin-top: 1.25rem; }

/* Sticky so the run a long entry belongs to stays named while it is read —
   the modal body is what scrolls, and a heading that scrolls away leaves the
   entries under it undated. */
.run-log-run-head {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 0 0.4rem;
  padding: 0.15rem 0;
  background: var(--panel-bg);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  border-bottom: 1px solid var(--panel-border);
}

/* Tag in its own column, stages in the other. `start` rather than `baseline`
   so a multi-stage entry keeps its tag beside the first stage instead of
   floating to the middle. */
.run-log-entry {
  display: flex;
  align-items: start;
  gap: 0.5rem;
  padding: 0.3rem 0;
}

.run-log-entry + .run-log-entry { border-top: 1px solid var(--panel-border); }

.run-log-entry-plain {
  display: block;
  color: var(--text-dim);
}

/* A fixed width, so the stage columns of consecutive entries line up and the
   eye can run down them. It has to hold the longest category word in *either*
   language — 'gearchiveerd' (nl) is longer than 'duplicate' (en), and the
   English width alone truncated 'duplicate' to 'duplic…'. Overflow is still
   ellipsised rather than removed: an unknown category from the filing task
   could be longer than anything here, and a clipped word beats a tag that
   shoves the filenames out of their column. */
.run-log-tag {
  flex: none;
  width: 8.5em;
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--text-dim);
  font-size: 0.72rem;
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Colour carries the four categories' different weights: a filing is routine,
   a duplicate or a backlog entry is something that did not fully succeed, and
   a flag is the one asking the reader for a decision. Only the two existing
   accent/danger pairs are used — a `--warning` of its own would mean four new
   values in each of the four theme blocks (theme.css) to distinguish two
   categories that both mean "look at this", which the weight below already
   separates from "filed". An unknown category keeps the neutral
   `.run-log-tag` above (see runLogModal.js). */
.run-log-tag-filed     { color: var(--accent); }
.run-log-tag-duplicate,
.run-log-tag-backlog   { color: var(--text-dim); font-weight: 600; }
.run-log-tag-flag      { color: var(--danger); font-weight: 600; }

/* The stage geometry is declared here, on the ancestor of both the stages and
   the note, so all three rules below read from one definition. */
.run-log-entry-body {
  --stage-arrow: 1rem;
  --stage-gap: 0.35rem;
  --stage-indent: calc(var(--stage-arrow) + var(--stage-gap));
  min-width: 0;   /* or a long filename pushes the flex row wider than the panel */
  flex: 1;
}

/* Each stage on its own line, so the second and third read as continuations of
   one journey rather than as separate entries.
   `--stage-indent` is how far the arrow and its gap push a stage's name to the
   right; the arrow's width and the first stage's padding are both written from
   it, so the arrowed and arrowless lines cannot drift out of alignment. */
.run-log-stage {
  display: flex;
  gap: var(--stage-gap);
}

/* A fixed width rather than the glyph's own, so the indent is the same measure
   on every line whatever the arrow character happens to render as. */
.run-log-arrow {
  flex: none;
  width: var(--stage-arrow);
  color: var(--text-faint);
}

/* `anywhere` rather than `break-word`: an archive filename is one long token
   with no spaces to break at, and it has to be breakable mid-word or it sets
   the panel's minimum width and the modal scrolls sideways. */
.run-log-name {
  min-width: 0;
  font-family: ui-monospace, monospace;
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}

/* The first stage carries no arrow, so it is padded by exactly what the arrow
   plus its gap occupies on the others — the same two values, not a guess at
   their sum. That is what puts every filename in an entry on one left edge;
   without it the first name starts a character earlier than the ones below it
   and the column reads as ragged. */
.run-log-stage:first-child,
.run-log-note {
  padding-left: var(--stage-indent);
}

.run-log-note {
  margin-top: 0.15rem;
  color: var(--text-dim);
  font-size: 0.78rem;
  /* Long unbroken paths appear here as well as in a stage — a `dupes …` note
     names the file it duplicates. Without this the note sets the panel's width
     from its longest word and the modal scrolls sideways on a phone. */
  overflow-wrap: anywhere;
}

/* On a phone the tag and the filenames cannot share a row: 8.5em of tag out of
   a ~19rem panel leaves the names a column too narrow to read, and an
   unbreakable path then pushes the panel wider than the window. Stacking them
   gives the names the full width. The condition is the tab bar's own
   visibility, which is what "mobile" already means everywhere else in this
   file (see `#app:has(.tab-bar…)` above) rather than a second width threshold
   that could disagree with `mobileLayout.js`. */
body:has(.tab-bar:not([hidden])) .run-log-entry {
  flex-direction: column;
  gap: 0.15rem;
}

body:has(.tab-bar:not([hidden])) .run-log-tag {
  width: auto;
  align-self: start;
}

/* ---- Reconnect overlay (CF-OFF-003 … CF-OFF-005) ---------------------- */

/* Covers and dims the whole interface — a higher z-index than the viewer
   and the run-log modal, since a client that cannot reach the backend has
   nothing underneath worth interacting with, whatever else happened to be
   open when the connection was lost. No control is offered here (CF-OFF-003):
   there is no local action that changes when the backend answers again. */
.reconnect-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  /* Column, since the icon (CF-OFF-014) sits above the message. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  background: var(--on-document-backdrop);
  color: var(--on-document-text);
}

.reconnect-overlay[hidden] { display: none; }

/* The same mark as the waiting state, not merely the same file: every figure
   comes from the same variables, so the two cannot drift (CF-OFF-014). See
   `#boot img` for why the shrink guard is stated. */
.reconnect-icon {
  width: var(--wait-icon-size);
  height: var(--wait-icon-size);
  flex-shrink: 0;
  border-radius: var(--wait-icon-radius);
  opacity: var(--wait-icon-opacity);
}

.reconnect-message {
  font-size: 1.1rem;
}

/* ---- Signed-out notice (CF-AUTH-008) ---------------------------------- */

/* A pill at the top centre that covers nothing beyond itself. Above the
   viewer and the run-log modal (50) and the reconnect overlay (90): the way
   back to Auth must stay reachable whatever else is open. Below the update
   prompt (95), which asks for an answer before anything else. */
.signed-out-notice {
  position: fixed;
  top: calc(0.5rem + env(safe-area-inset-top, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 92;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  /* `max-content` because a box at `left: 50%` otherwise shrinks to fit the
     half of the window to its right, which on a phone wraps the message into
     four lines.
     The maximum keeps the account head's room free on *both* sides, since
     the pill is centred: at phone width it would otherwise run into the head
     in the top-right corner. The message wraps instead. */
  width: max-content;
  max-width: calc(100vw - 2 * var(--account-room)
    - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
  padding: 0.3rem 0.35rem 0.3rem 0.85rem;
  border: 1px solid var(--panel-border);
  border-radius: 999px;
  background: var(--panel-bg);
  color: var(--text);
  font-size: 0.85rem;
  line-height: 1.3;
  box-shadow: 0 2px 8px var(--thumb-shadow);
}

.signed-out-notice[hidden] { display: none; }

.signed-out-notice-message {
  min-width: 0;
}

.signed-out-notice-button {
  flex-shrink: 0;
  -webkit-appearance: none;
  appearance: none;
  padding: 0.3rem 0.8rem;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-text);
  font: inherit;
  white-space: nowrap;
  cursor: pointer;
}

.signed-out-notice-button:hover { filter: brightness(1.08); }

/* ---- Update prompt (CF-OFF-007 … CF-OFF-010) --------------------------- */

.update-prompt {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--on-document-backdrop);
  padding: 1.5rem;
}

.update-prompt[hidden] { display: none; }

.update-panel {
  width: 24rem;
  max-width: 100%;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px var(--thumb-shadow);
  padding: 1.25rem;
}

.update-title {
  font-weight: 600;
  margin-bottom: 0.5rem;
}

.update-body {
  margin: 0 0 1rem;
  color: var(--text-dim);
  font-size: 0.9rem;
  line-height: 1.4;
}

.update-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 1rem;
}

.update-restart {
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-text);
  padding: 0.45rem 0.9rem;
  font: inherit;
  cursor: pointer;
}

.update-restart:hover { filter: brightness(1.08); }

/* ---- Pills (CF-LBL-009 … CF-LBL-012) --------------------------------- */

scanbox-pill {
  position: relative;         /* the `x` is positioned against this */
  display: inline-flex;
  align-items: stretch;
  max-width: 100%;
  border-radius: 999px;       /* rounded left and right ends */
  background: var(--pill-bg);
  border: 1px solid var(--pill-border);
  overflow: hidden;
  font-size: 0.82rem;
  line-height: 1.6;
}

scanbox-pill .pill-body {
  display: inline-flex;
  align-items: stretch;
  gap: 0.3rem;
  min-width: 0;
  color: var(--pill-text);
  font: inherit;
  cursor: pointer;
  /* A press-and-hold on this element is a gesture the app reads itself
     (CF-LBL-026), not a request to select its text. Without this the browser's
     own long-press-to-select still races it: the hold reaches the exclude
     threshold, but the platform's own selection highlight and, on iOS, its
     copy/lookup callout land on top of it at the same time. `-webkit-touch-
     callout` is the iOS-specific half `user-select: none` does not cover. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

scanbox-pill .pill-text {
  padding: 0.05rem 0.1rem 0.05rem 0.6rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Sized by its content, so it is never wider than that number needs
   (CF-LBL-010). Positioned so the `x` can be laid over exactly this chip
   rather than over a guess at its width. */
scanbox-pill .pill-count {
  position: relative;
  display: flex;
  align-items: center;
  padding: 0.05rem 0.45rem;
  color: var(--pill-count-text);
  font-variant-numeric: tabular-nums;
  font-size: 0.92em;
}

/* Only a chip that actually holds a number is painted. */
scanbox-pill .pill-count:not([hidden]) { background: var(--pill-count-bg); }

/* The `x` lives inside the count chip, so it inherits the chip's width and
   position for free — no second copy of the chip's geometry to keep in step. */
scanbox-pill .pill-count .pill-x {
  position: absolute;
  inset: 0;
}

/* The `x` is drawn over the right end of the pill rather than beside it
   (CF-LBL-020), so revealing it changes nothing about the pill's size.

   It used to grow the pill from zero width on hover. That reflows the row, and
   a pill near the right edge no longer fitted and wrapped to the next line —
   out from under the pointer, so hover ended, so it sprang back. The pills that
   could never be dismissed were exactly the rightmost on each row.

   Reserving the space permanently also fixes that and was tried: it cost a
   fifth of the sidebar's density, taking the same fifty labels from twenty rows
   to twenty-five, to hold room for a glyph that is only ever visible under the
   pointer. Overlaying costs nothing and keeps CF-LBL-010 honest — the count
   part stays exactly as wide as its number needs. */
scanbox-pill .pill-x {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  padding-inline: 0.4rem;
  background: var(--pill-count-bg);
  color: var(--pill-count-text);
  font: inherit;
  font-size: 1.05em;
  /* `line-height: 1` would box the glyph tightly and centre that box, not the
     mark. The `×` sits entirely above the baseline — its ink runs from about
     7.2px up to 0.6px up at this size — so centring its line box leaves the
     mark visibly high. Inheriting the pill's line-height gives the glyph the
     same half-leading above and below that the count has, which puts the ink
     where the digits are. */
  line-height: inherit;
  cursor: pointer;
  visibility: hidden;
}

/* The `x` replaces the number rather than covering it: two things stacked in
   one place read as a mistake, and the count is not useful at the moment the
   user has decided to act on the pill (CF-LBL-022).

   The number keeps its space while hidden — `visibility`, not `display` — so
   the pill does not resize (CF-LBL-020), and the `x` stretches across exactly
   that space so the chip stays a whole shape rather than leaving a blank stub
   where the digits were.

   Wrapped in `@media (hover: hover)` rather than left unconditional with a
   later `@media (hover: none)` override: a device with no hover has nothing
   to reveal the `x` with (a long-press reaches the same exclude action
   instead, CF-LBL-026), so it should never gain `.pill-x` visibility in the
   first place, not gain it and then have it taken away by a second rule.
   The override approach was tried and, on iOS Safari, was seen to leave the
   `x` showing on whichever pill a tap's *release point* lands on once the
   label area rebuilds mid-tap (the result of adding/excluding a label,
   `labelArea.js`'s `_render`) — the platform resolves its own tap-driven
   `:focus-within` against the DOM current at release, not the element
   originally pressed, and a same-position replacement pill inherits it.
   `:hover`/`:focus-within` never being live in the CSS at all for this
   device class removes the state for that resolution to land on. */
@media (hover: hover) {
  scanbox-pill:hover .pill-x,
  scanbox-pill:focus-within .pill-x {
    visibility: visible;
  }

  scanbox-pill:hover .pill-number,
  scanbox-pill:focus-within .pill-number {
    visibility: hidden;
  }
}

scanbox-pill .pill-x:hover { color: var(--danger); }
scanbox-pill .pill-x[hidden] { display: none; }

/* A pill with no number still needs its `x`, so the chip stays in the layout
   as an empty, transparent box just wide enough to hold one. It is not
   `display: none`, or the `x` inside it would go with it. */
scanbox-pill .pill-count[hidden] {
  display: flex;
  padding: 0;
  min-width: 1.5rem;
}

/* A pill in the search field: the query, not an offer. */
scanbox-pill[context="search"] {
  background: var(--pill-search-bg);
  border-color: transparent;
}
scanbox-pill[context="search"] .pill-body { color: var(--pill-search-text); }
scanbox-pill[context="search"] .pill-x {
  background: transparent;
  color: var(--pill-search-text);
}
scanbox-pill[context="search"] .pill-count:not([hidden]) {
  background: var(--pill-search-count-bg);
  color: var(--pill-search-count-text);
}

/* An excluded label reads as a negation rather than a selection. */
scanbox-pill[negated] {
  background: var(--pill-not-bg);
  border-color: var(--pill-not-border);
}
scanbox-pill[negated] .pill-body { color: var(--pill-not-text); }
scanbox-pill[negated] .pill-x {
  background: transparent;
  color: var(--pill-not-text);
}
scanbox-pill[negated] .pill-text::before { content: "−"; margin-right: 0.25rem; }

/* ---- Results (CF-RES-001 … CF-RES-006) ------------------------------- */

.results-head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 0.9rem;
  color: var(--text-dim);
  font-size: 0.85rem;
}

.results-head .total { color: var(--text); font-size: 1rem; font-weight: 600; }

.thumb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: 0.9rem;
}

.thumb {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  color: var(--text-dim);
  cursor: pointer;
  text-align: center;
}

/* Sized to match the canvas/img it wraps, so a thumbnail-label overlay
   (`.thumb-labels`, below) can position against exactly the picture rather
   than the whole tile, which also includes the date underneath. */
.thumb-frame { position: relative; }

.thumb canvas,
.thumb .thumb-img {
  width: 100%;
  aspect-ratio: 1 / 1.414;    /* A4, which is what the archive is */
  display: block;
  background: var(--thumb-bg);
  border: 1px solid var(--thumb-border);
  border-radius: 0.25rem;
  box-shadow: 0 1px 3px var(--thumb-shadow);
}

.thumb:hover canvas { border-color: var(--accent); }

.thumb .date {
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}

/* ---- Thumbnail labels (CF-RES-018 … CF-RES-021) ----------------------- */

/* Only the labels not already in the current search are ever drawn — the
   searched-for labels are on every result and would add nothing
   (CF-RES-019). Small pills rather than the label area's full pill treatment
   (`scanbox-pill`): chosen over "small text" and "a corner badge" after
   comparing all three live against real archive data (TASKS.md, "Labels on
   thumbnails") — text stayed compact but truncated illegibly on a
   heavily-labelled document, and a badge hid the labels behind a hover/tap
   that a text or pill treatment does not need. */

.thumb-labels {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
  padding: 0.3rem;
  background: linear-gradient(to top, var(--on-document-scrim), transparent);
  border-radius: 0 0 0.25rem 0.25rem;
  pointer-events: none;      /* the tile underneath stays clickable */
}

.thumb-label-pill {
  padding: 0.05rem 0.35rem;
  border-radius: 999px;
  background: var(--on-document-pill-bg);
  color: var(--on-document-pill-text);
  font-size: 0.6rem;
  line-height: 1.4;
  white-space: nowrap;
}

.empty, .failure {
  padding: 2rem 0;
  color: var(--text-dim);
  max-width: 34rem;
}

.failure { color: var(--danger); }

.sentinel { height: 1px; }

.loading {
  padding: 1rem 0;
  color: var(--text-faint);
  font-size: 0.85rem;
}

/* ---- Filename lists: unparseable documents (CF-DOC-006 … CF-DOC-012) and
   the backlog (CF-BKL-001 … CF-BKL-008) — `FilenameList` ------------------ */

/* Reuses `.results-head`'s layout; only the second child differs (a plain
   count there, a back link here) — `margin-left: auto` pushes it to the
   row's far side without a second flex rule set. */
.filename-pane .filename-back { margin-left: auto; }

.filename-list {
  display: flex;
  flex-direction: column;
}

/* A `<button>`, not a plain row (CF-DOC-011, CF-BKL-004: every document in
   either list can be opened in the viewer, since every one carries an `id`)
   — the reset undoes the button-ness visually so it still reads as a plain
   list row at rest, and `:hover`/`:focus-visible` is what actually signals
   "this opens something," the same idiom `.thumb` uses in the results grid. */
.filename-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--panel-border);
  background: none;
  font: inherit;
  text-align: left;
  padding: 0.45rem 0.3rem;
  cursor: pointer;
}

.filename-row:hover,
.filename-row:focus-visible { background: var(--accent-soft); }

.filename-name {
  font-size: 0.88rem;
  color: var(--text);
  overflow-wrap: anywhere;   /* a raw filename has no spaces to wrap at */
}

/* The dog-eared-sheet silhouette `drawPlaceholder` draws on a results-grid
   tile (`placeholder.js`), at list size — `currentColor` (set inline, per
   `fileTypeGlyph`) drives both the outline and the label so the two always
   match without the SVG repeating the colour. */
.filetype-glyph {
  flex: none;
  position: relative;
  width: 1.4rem;
  height: 1.7rem;
}

.filetype-glyph svg { width: 100%; height: 100%; }

.filetype-glyph-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.2rem;
  text-align: center;
  font-size: 0.4rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  /* Longer fallback type names (`typeLabel`'s subtype-derived tail, up to 4
     characters) must not overflow the glyph's width. */
  overflow: hidden;
  white-space: nowrap;
}

/* ---- Viewer (CF-VIEW-001 … CF-VIEW-012) ------------------------------- */

/* No permanent top bar (unlike the earlier design): the date and labels live
   behind `.viewer-info-toggle` instead, and previous/next are a gesture
   rather than a button pair — both traded for screen room, since the viewer
   is often the smallest window on the smallest device this runs on. */
.viewer {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--on-document-backdrop);

  /* The top-corner geometry, in one place: `.viewer-close` and
     `.viewer-docnav-next` share the top-right corner and must not overlap
     (CF-VIEW-023), so the docnav's offset is derived from the close
     control's size and inset rather than restating them. Two rules holding
     one number disagree the first time either is touched — and here that
     disagreement is invisible until a click lands on the wrong control. */
  --viewer-chrome-inset: 0.75rem;
  --viewer-close-size: 2rem;
  --viewer-chrome-gap: 0.5rem;

  /* Where a control near each edge stands (CF-VIEW-020). The viewer
     (`position: fixed; inset: 0`, above) runs edge to edge under whatever the
     device draws there — the status bar at the top, the home indicator at the
     bottom, the sensor housing and rounded corners at the sides of a phone
     held on its side — so the chrome's ordinary spacing is added to each
     edge's own safe-area inset rather than measured from the true edge. The
     spacing is kept on top of the inset, not replaced by it: a control that
     merely stops at the inset sits flush against the home indicator, and a
     touch at its lower edge then competes with the swipe that closes the app
     — the fault CF-MOB-008 fixed for the tab bar. Each inset is 0 on a device
     with none, so these are exactly `--viewer-chrome-inset` there. */
  --viewer-edge-top: calc(var(--viewer-chrome-inset) + env(safe-area-inset-top, 0px));
  --viewer-edge-right: calc(var(--viewer-chrome-inset) + env(safe-area-inset-right, 0px));
  --viewer-edge-bottom: calc(var(--viewer-chrome-inset) + env(safe-area-inset-bottom, 0px));
  --viewer-edge-left: calc(var(--viewer-chrome-inset) + env(safe-area-inset-left, 0px));
}

.viewer[hidden] { display: none; }

.viewer-close {
  position: absolute;
  /* The edge offsets rather than plain `0.75rem`: placed only `0.75rem` down
     from the true top edge, this sat half behind a phone's status bar. */
  top: var(--viewer-edge-top);
  right: var(--viewer-edge-right);
  /* Above `.viewer-docnav` (z-index 2), not level with it. The two share this
     corner, and the close control is the one that must always be hit: it is
     the viewer's permanent dismissal and the element `open()` focuses,
     whereas the document-walker is a shortcut with the drag as its
     alternative. `.viewer-docnav-next` reserves this control's width in its
     own `right` offset so they do not actually overlap (CF-VIEW-023) — this
     higher stacking is the belt to that braces, covering a corner case the
     offset alone cannot: a device whose text scaling makes the close control
     wider than the gap reserved for it. */
  z-index: 3;
  width: var(--viewer-close-size);
  height: var(--viewer-close-size);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--on-document-scrim);
  color: var(--on-document-text);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}

.viewer-close:hover { background: var(--on-document-scrim-strong); }

.viewer-body {
  position: relative;
  height: 100%;
  overflow: hidden;      /* clips the track to one page at a time (CF-VIEW-013) */
  /* `pan-y pinch-zoom` rather than `pan-y`: without `pinch-zoom` the browser
     withholds the second pointer of a pinch and the gesture is never reported
     at all (CF-VIEW-031). `pan-y` still yields vertical scrolling to whatever
     is under the finger, and the horizontal drag remains the page-swipe. */
  touch-action: pan-y pinch-zoom;
}

.viewer-track {
  position: relative;
  width: 100%;
  height: 100%;
}

/* Both pages stack in the same place and move independently by their own
   `transform` (`viewer.js`'s `_prepareNeighbour`/`_playSlide`/`_playSnapBack`)
   rather than sharing one track transform — a slide needs the outgoing and
   incoming picture on screen at once mid-transition, on whichever side each
   is entering or leaving from, which a single shared offset cannot express
   for both directions at once (see `viewer.js`'s own top comment).
   `transform` rather than `left`: the property the browser can animate on
   its own thread, so the slide stays smooth even while the main thread is
   busy drawing the neighbour's canvas. */
.viewer-page {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1rem;
  will-change: transform;
}

.viewer-page canvas,
.viewer-page .viewer-image {
  max-width: min(60rem, 100%);
  max-height: 100%;
  background: var(--thumb-bg);
  border-radius: 0.25rem;
  box-shadow: 0 8px 40px var(--on-document-scrim);
}

/* While zoomed (CF-VIEW-031) the picture is magnified by the `scale()` in the
   transform `_applyZoom` writes. The element's own box is left exactly as it
   was — `scale` is a visual transform and does not affect layout, so touching
   `max-width` here would magnify twice. `.viewer-body` already clips, so the
   enlarged picture cannot escape the viewer.
   The rounding and shadow go, because they draw the edge of a sheet lying on a
   surface, and once the picture is larger than its frame there is no such edge
   on screen to draw. */
.viewer-page.zoomed canvas,
.viewer-page.zoomed .viewer-image {
  border-radius: 0;
  box-shadow: none;
}

/* No transition on the zoom transform: it tracks the fingers, and an eased
   transform would lag a pinch by its own duration. */
.viewer-page canvas,
.viewer-page .viewer-image { transition: none; }

.viewer-page .viewer-image {
  object-fit: contain;
}

/* A text document (CF-VIEW-027) fills the page and scrolls inside it, rather
   than taking the canvas's fixed A4-ish aspect ratio: text has no page size of
   its own, and a CSV is as long as it is. */
/* `align-self: stretch` rather than a percentage height: the page is a grid
   whose items are centred, and `height: 100%` against a centred item resolves
   to zero on the first layout. The slot then had no height at all, so its
   background painted nothing — the results grid showed through the table — and
   the content overflowed instead of scrolling. Stretching to the grid area is
   what actually gives it the page's height; `min-height: 0` then lets it be
   shorter than its own content, which is what leaves `overflow` something to
   do. Both were reported live, as one symptom each (CF-VIEW-030). */
/* The margins are the device's safe-area insets (CF-VIEW-020). A picture is
   centred and seldom reaches an edge, and where it does, the status bar or
   home indicator drawn over its margin hides nothing that matters. This panel
   stretches to the page's full height, though, so without them its first line
   — a CSV's sticky column names — sat behind the status bar, and its last
   behind the home indicator even when scrolled to the end. Margins rather than
   the page's own padding: `viewer.js`'s `_drawPdfPage` sizes the PDF render
   box as the viewer less a fixed 2 × 1rem, which is this page's padding, so
   padding the page by the insets would clamp a canvas drawn at the old size
   on one axis only and distort it. */
.viewer-page .viewer-text-slot {
  width: min(60rem, 100% - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));
  margin:
    env(safe-area-inset-top, 0px)
    env(safe-area-inset-right, 0px)
    env(safe-area-inset-bottom, 0px)
    env(safe-area-inset-left, 0px);
  align-self: stretch;
  justify-self: center;
  min-height: 0;
  overflow: auto;
  background: var(--paper-bg);
  color: var(--paper-text);
  border-radius: 0.25rem;
  box-shadow: 0 8px 40px var(--on-document-scrim);
  /* The text is selectable, and a drag inside it should select rather than
     step to the next document — `_isBackdropClick` makes the matching
     exception for clicks. */
  touch-action: pan-y;
}

.viewer-page .viewer-text-slot[hidden] { display: none; }

.viewer-text {
  padding: 1.25rem 1.5rem;
  color: var(--paper-text);
}

.viewer-text-plain {
  margin: 0;
  font-family: ui-monospace, monospace;
  font-size: 0.85rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The table scrolls sideways inside its own box rather than widening the
   panel: a bank export can carry a dozen columns, and nothing here may make
   the page itself scroll horizontally. */
.viewer-text-table-wrap {
  overflow-x: auto;
}

.viewer-table {
  border-collapse: collapse;
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

/* Everything inside the sheet is drawn in the paper palette, not the app's own
   panel colours: this is ink on paper, and a panel colour here would be a
   fragment of the surrounding interface sitting inside the document. */
.viewer-table th,
.viewer-table td {
  padding: 0.25rem 0.7rem;
  border: 1px solid var(--paper-line);
  text-align: left;
  white-space: nowrap;
}

/* Sticky, so the column names stay readable while scrolling a long export —
   which is the whole reason for showing a CSV as a table rather than as text.
   Opaque, or the rows would show through as they pass underneath. */
.viewer-table th {
  position: sticky;
  top: 0;
  background: var(--paper-head-bg);
  font-weight: 600;
}

/* Zebra striping: a value is found by running along a row across several
   columns, and losing the row is the failure this prevents. */
.viewer-table tbody tr:nth-child(even) { background: var(--paper-stripe); }

.viewer-cell-number { text-align: right; }

.viewer-text-note {
  margin: 0.75rem 0 0;
  color: var(--text-dim);
  font-size: 0.8rem;
  font-style: italic;
}

.viewer-page .viewer-download {
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(60rem, 100%);
  aspect-ratio: 1 / 1.414;
  max-height: 100%;
  background: var(--placeholder-bg);
  border: 1px solid var(--placeholder-line);
  border-radius: 0.25rem;
  color: var(--on-document-text);
  font-weight: 600;
  text-decoration: none;
}

.viewer-page .viewer-download:hover {
  background: var(--placeholder-line);
}

.viewer-page .viewer-download[hidden] { display: none; }

/* The loading indicator (CF-VIEW-022). One per `.viewer-page`, not one per
   viewer, because the two slots load independently — the incoming page of a
   slide is fetched while the outgoing one is still on screen, and a single
   shared indicator could not say which of the two it belonged to.

   Centred over the slot rather than placed in its grid flow: it must sit in
   the same spot whether the slot behind it is blank (`_clearPicture` has run
   and nothing has arrived yet) or already showing something, and must not
   push the picture around when it appears or disappears. `pointer-events:
   none` keeps it out of the way of `_isBackdropClick` — an indicator is not
   a control, and a click landing on it while it happens to be up should
   reach whatever it is covering, exactly as it would a moment later. */
.viewer-loading {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  pointer-events: none;
  color: var(--on-document-text);
  font-size: 0.85rem;
}

.viewer-loading[hidden] { display: none; }

.viewer-spinner {
  width: 2rem;
  height: 2rem;
  border: 2px solid var(--on-document-scrim);
  border-top-color: var(--on-document-text);
  border-radius: 999px;
  animation: viewer-spin 0.8s linear infinite;
}

@keyframes viewer-spin {
  to { transform: rotate(360deg); }
}

/* A spinner is the one thing in this application that moves continuously, so
   it is also the one thing that has to answer this query. The indicator
   still appears and still says "Loading…" — only the rotation stops, which
   is the part that is decoration rather than information. */
@media (prefers-reduced-motion: reduce) {
  .viewer-spinner { animation: none; }
}

/* Hover-revealed arrows (CF-VIEW-012): a mouse has no swipe of its own, so
   the affordance a touch user gets from the image itself is given a visible
   control here instead, appearing only near the edge it belongs to rather
   than sitting on screen permanently. `@media (hover: none)` keeps this off
   a touch device entirely — there is no hover there to reveal it with, and a
   half-visible arrow that never quite appears is worse than none (the same
   reasoning as the label pill's touch menu, CF-LBL-024). */
.viewer-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 2.5rem;
  height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--on-document-scrim);
  color: var(--on-document-text);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s;
}

@media (hover: none) {
  .viewer-nav { display: none; }
}

.viewer-nav-prev { left: var(--viewer-edge-left); }
.viewer-nav-next { right: var(--viewer-edge-right); }

.viewer-nav:disabled { display: none; }

.viewer-nav:hover { background: var(--on-document-scrim-strong); }

/* Revealed by proximity to the edge it belongs to, not the whole viewer —
   hovering the middle of the image should not raise both arrows at once. */
.viewer-body:hover .viewer-nav-prev,
.viewer-nav-prev:focus-visible {
  opacity: 1;
}

.viewer-body:hover .viewer-nav-next,
.viewer-nav-next:focus-visible {
  opacity: 1;
}

/* Document-walker buttons: always jump to the next/previous document,
   skipping any remaining pages of a multi-page document — a distinct action
   from the page-walker arrows above, which is why the two are placed apart
   (top corners here, vertically centred there) rather than sharing one
   ambiguous control. Same hover-reveal idiom as the page-walkers on a
   hover-capable device (CF-VIEW-012's reasoning applies equally: a control
   that only sits there is competing with the page-walkers' identical shape),
   but a touch device has no hover to reveal a document-walker with, and
   unlike a page step — which touch already reaches via the drag/swipe
   falling through to the next document — jumping straight to another
   document has no other affordance on touch, so it stays permanently
   visible there instead of absent. */
.viewer-docnav {
  position: absolute;
  /* Same reasoning as `.viewer-close`'s `top`: without the status bar's own
     height added in, these sat half-hidden behind it on a phone. */
  top: var(--viewer-edge-top);
  z-index: 2;
  height: 2rem;
  padding: 0 0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--on-document-scrim);
  color: var(--on-document-text);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s;
}

.viewer-docnav-prev { left: var(--viewer-edge-left); }

/* Clear of `.viewer-close`, which occupies this same top-right corner: past
   that control's own inset and width, plus a gap between the two. Sharing
   the corner is what CF-VIEW-023 forbids — placed at the same `right` as
   the close control, these two sat exactly on top of one another, and
   since this button comes later in the markup it took every click aimed
   at the close control, which was then simply dead. The
   top-right corner is not negotiable for either: the close control belongs
   where a dismissal is looked for, and CF-VIEW-017 places the
   document-walker in the top corners deliberately, apart from the
   vertically-centred page-walkers. */
.viewer-docnav-next {
  right: calc(var(--viewer-edge-right) + var(--viewer-close-size) + var(--viewer-chrome-gap));
}

.viewer-docnav:disabled { display: none; }

.viewer-docnav:hover { background: var(--on-document-scrim-strong); }

.viewer-body:hover .viewer-docnav,
.viewer-docnav:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .viewer-docnav:not(:disabled) { opacity: 1; }
}

/* The info toggle and panel (CF-VIEW-009): the same bottom-overlay shape the
   results grid draws its label pills in (`.thumb-labels`), so date and
   labels read as "the same information, shown bigger" rather than a new
   idiom. */
.viewer-info-toggle {
  position: absolute;
  /* The edge offsets, as `.viewer-close` uses at the top: from the true
     bottom edge this sat on a phone's home indicator. */
  right: var(--viewer-edge-right);
  bottom: var(--viewer-edge-bottom);
  z-index: 2;
  width: 1.9rem;
  height: 1.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--on-document-scrim);
  color: var(--on-document-text);
  font: italic 700 0.85rem Georgia, serif;
  cursor: pointer;
}

/* `[hidden]` (CF-DOC-011: hidden for a standalone document, `viewer.js`'s
   `open`) would otherwise lose to this rule's own `display: flex` — same
   specificity, later in the cascade, the same bug already fixed for
   `.bar`/`.results-pane`/`.settings-panel` (TASKS.md, "Mobile layout"). */
.viewer-info-toggle[hidden] { display: none; }

.viewer-info-toggle:hover,
.viewer-info-toggle[aria-expanded="true"] { background: var(--on-document-scrim-strong); }

.viewer-info {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  /* The panel itself still reaches the true bottom edge, so its gradient runs
     under the home indicator rather than stopping in mid-air above it; only
     its content is lifted, by the same offset as the toggle beside it, so the
     labels and the download link clear the indicator and stay level with the
     toggle. The right padding is the toggle's reserved room. */
  padding:
    0.75rem
    calc(3rem + env(safe-area-inset-right, 0px))
    var(--viewer-edge-bottom)
    calc(1rem + env(safe-area-inset-left, 0px));
  background: linear-gradient(to top, var(--on-document-scrim-strong), transparent);
  border-radius: 0 0 0.25rem 0.25rem;
}

.viewer-info[hidden] { display: none; }

/* The per-document download (CF-VIEW-029). Pushed to the far end of the row so
   it does not sit among the labels, which are a different kind of thing — one
   is what the document *is*, the other is something to do with it. */
/* `:link` and `:visited` are both stated, and that is the point rather than
   redundancy. The bare class alone lost to the UA's own visited-link colour
   the moment the file had been downloaded once — dark blue on the near-black
   info bar, reported as unreadable. A visited style must be given explicitly
   to override it, and both states are given the same colour deliberately:
   "have I downloaded this before" is not something this control should
   announce. */
.viewer-info-download,
.viewer-info-download:link,
.viewer-info-download:visited {
  margin-left: auto;
  flex: none;
  color: var(--on-document-text);
  font-size: 0.8rem;
  white-space: nowrap;
  text-decoration: underline;
  cursor: pointer;
}

.viewer-info-download[hidden] { display: none; }

.viewer-info-download:hover,
.viewer-info-download:visited:hover { color: var(--accent); }

/* CF-VIEW-038. The basis is what makes the row wrap. At `flex: 1` it was 0, so the labels
   never claimed room of their own and on a phone were squeezed into whatever
   the date and the (unshrinkable) download link left — under 50px, one
   truncated pill per line. A real basis means that when date, labels and
   link cannot share a line, the link moves to the next one instead; the
   labels still grow into any room left, and wrap among themselves beyond it. */
.viewer-labels {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  flex: 1 1 10rem;
  min-width: 0;
}

.viewer-date {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--on-document-text);
  white-space: nowrap;
}
