/* ============================================================================
 * Korzaro — canonical .ohl-* component families.
 *
 * Every family here is showcased in the living styleguide
 * (/admin/superadmin/design/). Rules:
 *   - colors ONLY via rgb(var(--ohl-*)) tokens (brand_tokens.css);
 *   - font sizes on the canonical scale 11 / 12 / 14 / 16 / 22 px;
 *   - border radii from the set 4 / 6 / 8 / 10 px + full (pill / circle).
 * The design-drift pre-commit gate warns on violations (warn-first).
 *
 * Dark mode needs no rules here — semantic tokens flip in html.dark.
 * ========================================================================= */

/* -- Disclosure reset — the <summary> elements we style ourselves ---------- */
/* Six components are native <details>: the range picker, a finding row, the
   memory add form, a collapsed card, the small in-detail disclosures and the
   anchor list beside a help article. Each has to hide the browser's own marker
   and declare the summary clickable, and each used to carry its own copy of
   those three lines. The reset is the same for all six, so it is stated once;
   what each summary then LOOKS like stays with its own component. */

.ohl-range > summary,
.ohl-qsum,
.ohl-qdisc > summary,
.ohl-wcard > summary,
.ohl-help-toc-anchors > summary {
  list-style: none;
  cursor: pointer;
}

.ohl-range > summary::-webkit-details-marker,
.ohl-qsum::-webkit-details-marker,
.ohl-qdisc > summary::-webkit-details-marker,
.ohl-wcard > summary::-webkit-details-marker,
.ohl-help-toc-anchors > summary::-webkit-details-marker {
  display: none;
}

/* -- Card — the basic content container (mockup .card) -------------------- */

/* ==========================================================================
 * 4. CARDS
 * ========================================================================== */
.ohl-card {
  background: rgb(var(--ohl-surface));
  border: 0;
  border-radius: 8px;
  box-shadow: none;
  margin-bottom: 24px;
}

.ohl-card[id] {
  scroll-margin-top: 80px;
}

/* The reference stacks the card's caption under its title rather than beside
   it, which is what gives each card its two-line opening. */
.ohl-card-hd {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 24px 24px 0;
  border-bottom: 0;
  flex-wrap: wrap;
  column-gap: 10px;
  row-gap: 2px;
}

.ohl-card-hd h2 {
  font-size: 16px;
  font-weight: 800;
  margin: 0;
  /* The title must not shrink below its own width — next to a long caption
     it used to compress and break onto two lines ("Stav feedů"). */
  flex-shrink: 0;
  color: rgb(var(--ohl-ink));
  flex: 0 0 100%;
}

/* Muted caption — the same quiet 12px line in four places: beside a card
   title, under a tile's number, naming a finding's source, and counting a
   pager's window. They were four separate rules with identical bodies; one
   caption that drifts into four is how a vocabulary stops being a vocabulary. */
.ohl-card-sub,
.ohl-tile-sub,
.ohl-qsrc,
.ohl-pager-info {
  color: rgb(var(--ohl-muted));
  font-size: 12px;
}

.ohl-card-bd {
  padding: 20px 24px 24px;
}

/* Source chips separate metadata visually; punctuation adds no information. */
.ohl-card-sub > .ohl-src { margin-inline-end: 6px; }

/* A compact summary belongs beside the title, above its table. */
.ohl-card-hd--summary { justify-content: space-between; align-items: flex-start; gap: 12px 24px; }
.ohl-card-heading { flex: 1 1 240px; min-width: 0; }
.ohl-card-summary { margin: 0; text-align: right; }
.ohl-card-summary dt { font-size: 12px; color: rgb(var(--ohl-muted)); }
.ohl-card-summary dd { margin: 2px 0 0; font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; }
.ohl-card-summary small { display: block; font-size: 12px; font-weight: 400; color: rgb(var(--ohl-muted)); }

@media (max-width: 520px) {
  .ohl-card-summary { text-align: left; }
}

/* -- KPI tiles (mockup .tiles / .tile) ------------------------------------ */

/* ==========================================================================
 * 5. KPI ROW — white cards on the grey ground.
 *
 * The reference stands these numbers bare on the page, and that is how they
 * were built first. Mira's call on 5. 9. 2026: they are information blocks
 * like any other and belong on white, which is also what makes the grey do
 * its job across the whole screen instead of only around the charts.
 * ========================================================================== */
.ohl-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  /* Auto-fit still hands each track its content's minimum, so a long amount
     could push the row wider than the page. */
  grid-auto-columns: minmax(0, 1fr);
  /* Tighter than the 24px between page blocks: a KPI strip is one row of
     related numbers, and at 24px the cards got narrow enough that a qualifier
     like "(s DPH)" fell to its own line and made one card taller than its
     neighbours. */
  gap: 16px;
  margin-bottom: 24px;
  background: transparent;
  border-radius: 0;
  overflow: visible;
}

/* Exactly six tiles read as 3+3, never 5+1.
   `auto-fit` hands out as many tracks as happen to fit, which at 1440px was
   five — leaving the sixth number alone on a second row and pushing everything
   below it down the page (Codex audit, 5. 9. 2026, N07). A count this specific
   needs a count-aware rule, so the grid is matched by how many tiles it holds
   rather than by a class each caller has to remember to set. */
.ohl-tiles:has(> :nth-child(6):last-child) {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Eight tiles read as 4+4, never 7+1 — the same reason as the six above, one
   row further along. Customers' retention strip is eight since 9. 9. 2026, and
   auto-fit put seven of them in the first row and left the eighth alone beside
   a hole two thirds of the page wide (Mira). */
.ohl-tiles:has(> :nth-child(8):last-child) {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* Four related KPIs stay balanced at tablet widths. Auto-fit otherwise makes
   a 3+1 row around 580px, even though the same four values read cleanly as a
   2×2 block. */
@media (max-width: 760px) {
  .ohl-tiles:has(> :nth-child(4):last-child) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .ohl-tiles:has(> :nth-child(6):last-child),
  .ohl-tiles:has(> :nth-child(8):last-child) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Below the phone breakpoint every tile row is a single column; this repeats
   the rule from there because `:has()` outranks the plain `.ohl-tiles`. */
@media (max-width: 520px) {
  .ohl-tiles:has(> :nth-child(4):last-child),
  .ohl-tiles:has(> :nth-child(6):last-child),
  .ohl-tiles:has(> :nth-child(8):last-child) {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Compact KPIs beside a chart. The wrapper is the stretched right column of
   .ohl-duo; the grid (.ohl-tiles--3up) then grows below its one shared window
   caption. */
.ohl-tile-stack-wrap {
  display: flex;
  flex-direction: column;
}

/* The row's window, said once above its tiles instead of inside every title. */
.ohl-tiles-note {
  margin: 0 0 8px;
  color: rgb(var(--ohl-muted));
  font-size: 12px;
}

/* Every tile in a strip wears the section's colour on its top edge — ALL of
   them, not a chosen few (Mira, 2026-09-01). Half a row marked and half not
   read as a distinction nobody could name; the row is one row. Outside a hue
   scope the accent stands in. */
.ohl-tile {
  background: rgb(var(--ohl-surface));
  border: 0;
  border-radius: 8px;
  padding: 20px 24px;
  box-shadow: none;
}

.ohl-tile-label {
  font-size: 12px;
  text-transform: none;
  letter-spacing: 0;
  color: rgb(var(--ohl-muted));
  font-weight: 500;
}

.ohl-tile-value {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 6px;
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 8px 0 6px;
  font-variant-numeric: tabular-nums;
  color: rgb(var(--ohl-ink));
}

/* A qualifier that rides beside the value ("505 069 Kč (s DPH)") says what the
   number IS, so it belongs to the number (Mira, 2026-08-20). It may drop to its
   own line on a narrow tile, but never breaks apart mid-qualifier. */
.ohl-tile-sub--inline { white-space: nowrap; }

/* On a phone the qualifier gives up its nowrap rather than the number giving
   up digits. */
@media (max-width: 520px) {
  .ohl-tile-sub--inline { white-space: normal; }
}

.ohl-tile-comparison {
  font-size: 12px;
  line-height: 1.45;
  color: rgb(var(--ohl-muted));
  font-variant-numeric: tabular-nums;
}

.ohl-tile-comparison strong { color: rgb(var(--ohl-ink)); }

/* What the metric counts, for tiles whose name does not say it (the Clarity UX
   signals). Set apart from the sub line by a hairline so the tile reads as
   "number, its context, then its definition". */
.ohl-tile-help {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid rgb(var(--ohl-line));
  font-size: 12px;
  line-height: 1.35;
  color: rgb(var(--ohl-muted));
}

/* Five tiles that read as 3 above and 2 below, filling one .ohl-duo column
   (Mailing's send quality). Auto-fit decides the column count from the
   available width, which in a half-width column reflows to 2+2+1 and leaves
   the block ragged; this row states its three columns instead. The rows share
   the column's height so the block ends level with the cards beside it, rather
   than stopping short and leaving a hole in the page (Mira, 2026-08-20). At
   phone widths three columns would squeeze the numbers, so the row drops to
   two — height matching goes with it, since nothing sits alongside any more. */
.ohl-tiles--3up {
  flex: 1;
  /* Six tracks, three tiles: a tile spans two of them, so a row left with two
     tiles can take three each and fill the width instead of ending in a hole
     (Mira, 9. 9. 2026). */
  grid-template-columns: repeat(6, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  margin-bottom: 0;
}

.ohl-tiles--3up > * { grid-column: span 2; }

/* A tile that starts a row and has exactly one tile after it — the pair that
   would otherwise leave a third of the row empty. */
.ohl-tiles--3up > :nth-child(3n + 1):nth-last-child(2),
.ohl-tiles--3up > :nth-child(3n + 1):nth-last-child(2) ~ * {
  grid-column: span 3;
}

.ohl-tiles--3up .ohl-tile {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

@media (max-width: 640px) {
  .ohl-tiles--3up {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: auto;
  }
  .ohl-tiles--3up > *,
  .ohl-tiles--3up > :nth-child(3n + 1):nth-last-child(2),
  .ohl-tiles--3up > :nth-child(3n + 1):nth-last-child(2) ~ * {
    grid-column: auto;
  }
}

/* Four tiles that read as a 2x2 block filling one .ohl-duo column — SEO's rank
   counts beside the share-of-voice trend. Same reason as --3up one rule up:
   auto-fit decides its columns from the available width and in a half-width
   column would put all four in a row of hairline-thin cards. The rows share the
   column's height so the block ends level with the chart beside it. */
.ohl-tiles--2up {
  flex: 1;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  margin-bottom: 0;
}

.ohl-tiles--2up .ohl-tile {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Finance cards split into two columns: last 30 days (left), rolling year
   (right), divided by a hairline in the card's channel hue. */
.ohl-tiles--fin { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }

/* The two amounts sit on ONE baseline (Mira, 17. 9. 2026): the smaller
   rolling-year figure used to hang from the top of the row, so its feet
   floated above the 30-day amount's and the pair read as two heights of one
   number. Baseline alignment is what makes them one line. It also means the
   two columns no longer share a top or a bottom, so the hairline between them
   is drawn on the row itself, full height, rather than as either column's
   edge - hung on a column it would start or stop wherever that column does. */
.ohl-fin { position: relative; display: flex; gap: 28px; align-items: baseline; }
.ohl-fin-col { flex: 1; min-width: 0; }

/* The card is the container, not the window: the grid packs as many finance
   cards per row as fit, so a card is narrow on a wide screen just as easily as
   on a small one. Asking the WINDOW how much room these two columns have was
   how a full amount overflowed its column at 1440px, while the phone layout
   the window query did reach was fine all along. */
.ohl-tiles--fin .ohl-tile { container-type: inline-size; }

/* The finance split keeps its hairline: the two columns are two windows on
   the same metric, not two unrelated numbers. The columns share the width
   equally, so the middle of the row is the middle of the gap. A card without
   a year column (history shorter than a month of comparison) draws none. */
.ohl-fin:has(.ohl-fin-col--year)::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  border-left: 1px solid rgb(var(--ohl-line));
}

/* Below this the two amounts stop fitting side by side: at 1440px a card is
   332px wide and a 27px "892 392 Kč" needs 142px in a 128px column. Whole
   amounts and the currency stay - money is not a place to shrink type or cut
   with an ellipsis - so the pair stacks and the hairline turns horizontal.
   Declared AFTER the base rules on purpose: same specificity, so source order
   decides. */
@container (max-width: 380px) {
  .ohl-fin { flex-direction: column; gap: 10px; }
  .ohl-fin:has(.ohl-fin-col--year)::before { content: none; }
  .ohl-fin-col--year {
    border-top: 1px solid rgb(var(--ohl-line));
    padding-top: 10px;
  }
}

/* On a phone the finance cards also stop sharing a row. The stacking of the
   two windows inside a card is the container query above, which reaches this
   case too — this rule only decides how many cards fit across. */
@media (max-width: 520px) {
  .ohl-tiles--fin { grid-template-columns: minmax(0, 1fr); }
}

/* Bigger headline number on the finance cards — they carry the money story.
   The 30-day column is what matters, so the rolling-year value stays smaller. */
.ohl-tiles--fin .ohl-tile-value {
  font-size: 27px;
}
.ohl-fin-col--year .ohl-tile-value {
  font-size: 18px;
}

/* The finance cards are the one row of tiles that wears its hue as a wash:
   a hub of white cards on grey read as "too white" (Mira, 17. 9. 2026), and
   the money row at the top is where a little colour breaks it without a slab
   anywhere. Each card takes the quiet background of its hue and paints its
   caption in the ink, the way the area tiles below name their section; the
   figures stay in body ink. No accent edge — three edges side by side read as
   a fence, and the briefing card keeps that device to itself. The hues are
   sand / mint / lilac on purpose: sky would have doubled the briefing beside
   the chart, and sand is Obchod's own colour, which the revenue card already
   wore as a caption. The hairline between the two windows follows the hue so
   it does not cut a tinted card in grey. */
.ohl-tiles--fin .ohl-tile {
  background: rgb(var(--ohl-hue-bg));
}
.ohl-tiles--fin .ohl-tile-label {
  color: rgb(var(--ohl-hue));
  font-weight: 700;
}
.ohl-tiles--fin .ohl-fin::before,
.ohl-tiles--fin .ohl-fin-col--year {
  border-color: rgb(var(--ohl-hue) / 0.25);
}

/* -- Delta badge — small trend indicator (mockup .d) ---------------------- */

/* ==========================================================================
 * 12. CHANGE MARKERS — the reference writes a change as a small coloured
 *     figure led by a triangle, not as a filled pill.
 * ========================================================================== */
.ohl-delta {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  border-radius: 6px;
  padding: 0;
  font-variant-numeric: tabular-nums;
  background: transparent;
}

.ohl-delta--up {
  color: rgb(var(--ohl-success));
  background: rgb(var(--ohl-success-bg));
}

.ohl-delta--down {
  color: rgb(var(--ohl-error));
  background: rgb(var(--ohl-error-bg));
}

.ohl-delta--flat {
  color: rgb(var(--ohl-muted));
  background: rgb(var(--ohl-bg));
}

/* -- Status dot — connection / health state (mockup .dot) ----------------- */

.ohl-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  margin-right: 6px;
}

.ohl-dot--ok   { background: rgb(var(--ohl-source-ok)); }
.ohl-dot--warn { background: rgb(var(--ohl-source-warn)); }
.ohl-dot--err  { background: rgb(var(--ohl-source-err)); }
.ohl-dot--idle { background: rgb(var(--ohl-source-idle)); }
/* Not a health state but an unread one: something arrived since the reader
   last looked. Accent rather than a traffic-light colour, because nothing is
   wrong. */
.ohl-dot--accent { background: rgb(var(--ohl-accent)); margin-right: 0; }

/* -- Badge — textual state label (mockup .badge) --------------------------- */

.ohl-badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 500;
  border-radius: 999px;
  padding: 6px 9px;
}

.ohl-badge--ok     { color: rgb(var(--ohl-success)); background: rgb(var(--ohl-success-bg)); }
.ohl-badge--warn   { color: rgb(var(--ohl-warning)); background: rgb(var(--ohl-warning-bg)); }
.ohl-badge--err    { color: rgb(var(--ohl-error));   background: rgb(var(--ohl-error-bg)); }
.ohl-badge--muted  { color: rgb(var(--ohl-muted));   background: rgb(var(--ohl-bg)); }
.ohl-badge--accent { color: rgb(var(--ohl-accent));  background: rgb(var(--ohl-accent-soft)); }

/* -- Alert — a proactive "look at this" banner ----------------------------- */
/* The product's core act is surfacing something worth attention, so an alert is
   a first-class component, not a styled card. The left rule carries the state
   colour; the body stays on the surface so a row of alerts reads calmly. */

.ohl-alert {
  display: flex;
  align-items: baseline;
  gap: 10px;
  background: rgb(var(--ohl-surface));
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 10px;
  box-shadow: var(--ohl-shadow);
  padding: 16px 18px;
  margin-bottom: 1rem;
  font-size: 14px;
  color: rgb(var(--ohl-ink));
}

/* The level is already spelled out by the badge inside the alert, so the row
   itself stays quiet — a coloured rule on top of a coloured badge only made the
   strip glare (Mira, 2026-08-31). */

.ohl-alert-bd { flex: 1; }

.ohl-alert-act {
  white-space: nowrap;
  font-size: 12px;
}

/* -- Briefing rows (dashboard teaser) -------------------------------------- */
/* Flat rows, not cards-in-a-card: no shadow, a light hover wash and a nudge
   arrow. The channel chip inside the row already carries the colour, so the
   row has no rule of its own (Mira, 2026-08-31).

   The hairline between rows is gone too (Mira, 6. 9. 2026): eight rules in one
   card drew a grid around six sentences and made the block look denser than it
   is. Space separates them now — which is why the row keeps a little more of
   it, and why the hover wash matters more than it did: it is the only thing
   left that says where one row ends. */

.ohl-brief-list { padding: 2px 0 0; }

.ohl-brief-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  /* Tighter than the 10px it opened with (Mira, 7. 9. 2026): seven rows at a
     comfortable reading size do not need the airiest possible spacing to be
     told apart, and the card was taller than the chart beside it. The touch
     rule below puts the finger-sized row back on a coarse pointer. */
  padding: 7px 8px;
  border-radius: 6px;
  color: rgb(var(--ohl-ink));
  text-decoration: none;
  font-size: 14px;
}

/* The two halves of the briefing card: what is wrong RIGHT NOW, and what the
   dated issue said. They used to be two separate blocks on the page — a live
   problem competing with the week's editorial for the top of the screen, and
   the same event able to appear in both (Mira's brief, 6. 9. 2026). One card,
   two named groups, so a Wednesday outage does not wait for Monday. */
/* A section heading straight on the grey ground: a title with its date, and
   nothing pretending to be a panel around them. The briefing's issue line used
   to be a card with a header and no body, which is why it looked glued to its
   own bottom edge (Codex reaudit, 6. 9. 2026, R16). Any heading that stands
   outside a content card belongs to this class rather than borrowing an empty
   one. */
.ohl-sect--issue {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0 0 16px;
}

@media (max-width: 720px) {
  /* On a phone the date takes its own line with room of its own, rather than
     being held on the right at any cost. */
  .ohl-sect--issue { row-gap: 8px; }
  .ohl-sect--issue > * { flex-basis: 100%; }
}

.ohl-brief-now {
  /* Centred over the rows it heads (Mira, 7. 9. 2026). It was indented to the
     row text a day earlier; centred it reads as a divider between the two
     halves of the card rather than as a label belonging to the first row. */
  margin: 12px 0 2px;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgb(var(--ohl-muted));
}

.ohl-brief-now:first-child { margin-top: 0; }

@media (max-width: 720px), (pointer: coarse) {
  /* Same contract as the other tap targets further down: a row that is also a
     link has to be hittable, and 7px of padding leaves it ~34px tall. */
  .ohl-brief-row { padding: 12px 8px; }
}
.ohl-brief-row:hover { background: rgb(var(--ohl-hue-bg) / 0.5); }

.ohl-brief-txt { flex: 1; min-width: 0; }

/* Nudge arrow — appears on hover to say the whole row is one click target.
   The briefing rows and the table rows use the same mark. */
.ohl-brief-arw,
.ohl-row-arw {
  color: rgb(var(--ohl-muted));
  opacity: 0;
  transition: opacity 0.12s ease;
}

.ohl-brief-row:hover .ohl-brief-arw { opacity: 1; }

/* The week's tasks are numbered (Mira, 17. 9. 2026): a filled disc in the
   accent, sized to the first line of text so a task that wraps keeps its
   number at the top, not floating beside the middle. The row aligns to the
   top for the same reason; the even-duo rule below that centres rows is
   overridden here on purpose. */
.ohl-brief-task { align-items: flex-start; }
.ohl-duo--even .ohl-brief-task { align-items: flex-start; }
.ohl-brief-num {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgb(var(--ohl-accent));
  color: rgb(var(--ohl-accent-ink));
  font-size: 11px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* 14px text on a 22px disc: the text line is ~21px tall, so the disc sits on
   the first line without a nudge. */
.ohl-brief-task .ohl-brief-txt { line-height: 22px; }

/* -- Buttons (mockup .btn) -------------------------------------------------- */

/* ==========================================================================
 * 14. SECONDARY BUTTONS — white with a hairline, the way the reference sets
 *     its quiet actions beside the one filled call to action.
 * ========================================================================== */
.ohl-btn {
  display: inline-block;
  border: 1px solid rgb(var(--ohl-line));
  background: rgb(var(--ohl-surface));
  color: rgb(var(--ohl-ink));
  border-radius: 4px;
  padding: 14px 24px;
  font-size: 14px;
  font-family: var(--ohl-font-sans);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
  font-weight: 700;
}

/* Hover on a quiet button is a neutral deepening, not a wash of pale blue. The
   blue tint read as a selected state on a button that was only being pointed
   at. */
.ohl-btn:hover {
  border-color: rgb(var(--ohl-mist-400) / 0.5);
  background: rgb(var(--ohl-mist-400) / 0.12);
  color: rgb(var(--ohl-ink));
}

/* Blue identifies navigation and selection. Green commits a positive action;
   neutral controls only move around; red removes or rejects.

   ONE green, and it means "this submits the form". There used to be a second,
   paler one for repeated confirmations in table rows, and nobody could say
   which was which (Mira, 7. 9. 2026) - least of all when both appeared on the
   same screen. A row of green Save buttons is also the wrong signal: green is
   supposed to pick the one action out of the page, and repeated it becomes
   wallpaper. Row-level and secondary actions are neutral now. */
.ohl-btn--primary {
  background: rgb(var(--ohl-success));
  border-color: rgb(var(--ohl-success));
  color: rgb(var(--ohl-surface));
}

/* Hover moves the whole surface, not the text. Underlining was all a coloured
   button did when you pointed at it, which reads as a link rather than a
   control (Mira, 7. 9. 2026). The overlay only DARKENS the fill, so white text
   on it can never be worse than the registered pair at rest - which is why the
   contrast registry still measures the resting colours. */
.ohl-btn--primary:hover,
.ohl-btn--accent:hover,
.ohl-btn--danger:hover {
  background-image: linear-gradient(
    rgb(var(--ohl-ink) / 0.14),
    rgb(var(--ohl-ink) / 0.14)
  );
}

/* A filled button that GOES somewhere. Blue is navigation everywhere else in
   the app (links, the chosen tab), so a call to action that opens a section
   wears the same blue rather than the green that submits a form — and rather
   than the white of a neutral control, which on the News page read as a
   disabled button (Mira, 17. 9. 2026, with OutOfDark's filled CTA as the
   reference). The pair accent-ink on accent is already in the contrast
   registry as "filled accent selection". */
.ohl-btn--accent {
  background: rgb(var(--ohl-accent));
  border-color: rgb(var(--ohl-accent));
  color: rgb(var(--ohl-accent-ink));
}

/* Compact size for a button that sits inside prose (a note's CTA) rather than
   at the foot of a form. The arrow icon rides on the text line. */
.ohl-btn--sm {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  font-size: 14px;
}
.ohl-btn--sm .material-symbols-outlined { font-size: 16px; }

.ohl-chip--on {
  background: rgb(var(--ohl-accent));
  border-color: transparent;
  color: rgb(var(--ohl-accent-ink));
  font-weight: 600;
}

/* -- Icon button — a control whose whole label is its icon ------------------
   Used by the menu toggles in the header and the sidebar. It is a <button>,
   not a clickable <span>: those could not be reached by Tab, said nothing to a
   screen reader and showed no focus, so on a phone the closed menu had no
   operable way back (Codex audit, 5. 9. 2026, N01). The 32px box also clears
   the 24px minimum WCAG 2.2 asks of a small target. */

.ohl-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  min-height: 32px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: rgb(var(--ohl-muted));
  cursor: pointer;
}

.ohl-iconbtn:hover {
  background: rgb(var(--ohl-mist-400) / 0.12);
  color: rgb(var(--ohl-ink));
}

/* -- Chip — filter / toggle pill (mockup .chip) ---------------------------- */

.ohl-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.ohl-chip {
  display: inline-block;
  font-size: 14px;
  border: 1px solid rgb(var(--ohl-line));
  background: rgb(var(--ohl-surface));
  border-radius: 999px;
  padding: 8px 16px;
  color: rgb(var(--ohl-muted));
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s;
  border-color: transparent;
  font-weight: 700;
}

/* -- Range switcher + chart (trends over the stored daily series) ---------- */
/* One button that names the active window, opening a menu of the presets and
   the custom range. It used to be all eight presets spelled out in a row, which
   spent a whole line saying what one word says (Mira, 2026-08-31). Still a
   native <details>, so it needs no JS; still one control per page, now at the
   top right where GA4 and every other analytics tool puts it. */

.ohl-range {
  position: relative;
  display: inline-block;
}

.ohl-range-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 12px;
  background: rgb(var(--ohl-surface));
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 8px;
  box-shadow: var(--ohl-shadow);
  font-size: 14px;
  font-weight: 600;
  color: rgb(var(--ohl-ink));
  white-space: nowrap;
}

.ohl-range-btn:hover {
  border-color: rgb(var(--ohl-mist-400) / 0.6);
  background: rgb(var(--ohl-surface));
  color: rgb(var(--ohl-ink));
}
.ohl-range-ico { color: rgb(var(--ohl-muted)); flex-shrink: 0; }
/* The caret turns over when the menu is open — the one state cue the button
   has, since the label itself never changes on opening. */
.ohl-range[open] .ohl-range-caret { transform: rotate(180deg); }

.ohl-range-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  min-width: 274px;
  padding: 6px;
  background: rgb(var(--ohl-surface));
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 8px;
  /* The one exception to the flat rule: a menu floating over content has to
     read as detached, and the demo lifts its dropdowns the same way. */
  box-shadow: 0 8px 24px rgb(31 31 31 / 0.12);
  border-color: transparent;
}

.ohl-range-opt {
  display: block;
  padding: 7px 10px;
  border-radius: 6px;
  font-size: 14px;
  color: rgb(var(--ohl-ink));
  text-decoration: none;
  white-space: nowrap;
}

.ohl-range-opt:hover { background: rgb(var(--ohl-bg)); }

.ohl-range-opt.is-active {
  background: rgb(var(--ohl-accent-soft));
  color: rgb(var(--ohl-accent));
  font-weight: 600;
}

.ohl-range-sep {
  margin: 6px 4px 4px;
  padding-top: 8px;
  border-top: 1px solid rgb(var(--ohl-line));
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: rgb(var(--ohl-muted));
}

/* Two dates on ONE line with the dash between them, and the button on its
   own below: stacked into a 210px menu they read as three unrelated controls
   instead of one range (Mira, 7. 9. 2026). The menu widens only as much as the
   pair needs, and the fields share the row evenly so neither one dictates it. */
.ohl-range-form {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 6px;
  padding: 6px 4px 2px;
}

.ohl-range-form input[type="date"] { min-width: 0; }
.ohl-range-form button { grid-column: 1 / -1; justify-self: stretch; }

/* Why the range the reader typed could not be used. Sits under the two date
   fields, inside the menu, which opens by itself when this is present. */
.ohl-range-error {
  padding: 0 4px 4px;
  max-inline-size: 260px;
}

/* Inside a hue scope the active preset wears the channel colour, exactly as
   the chips did before. */
.ohl-hue .ohl-range-opt.is-active {
  background: rgb(var(--ohl-hue-bg));
  color: rgb(var(--ohl-hue));
}

/* -- Page header extras — period switcher beside the title, sources under it - */
/* The title line carries the CONTROLS: the period switcher on the far right,
   where every analytics tool puts it. Where the numbers come from and how
   current they are is a caption, not a control, so it wraps onto its own line
   under the title (Mira, 10. 9. 2026) — beside the heading it read as part of
   the section's name, and it was the first thing dropped when the row ran out
   of width. */

.ohl-titlerow { flex-wrap: wrap; }

/* A whole line of its own, under everything else on the title row. */
.ohl-titlerow > .ohl-pagehead-srcs {
  flex-basis: 100%;
  margin: 2px 0 0;
  font-weight: 400;
}

.ohl-pagehead {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-grow: 1;
  min-width: 0;
  margin-left: 12px;
}

.ohl-pagehead-srcs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

.ohl-pagehead-fresh {
  font-size: 12px;
  font-weight: 400;
  color: rgb(var(--ohl-muted));
  white-space: nowrap;
}

/* The switcher owns the right edge whether or not the section has chips. */
.ohl-pagehead > .ohl-range { margin-left: auto; flex-shrink: 0; }

/* The chips used to be hidden below the sidebar breakpoint, because they were
   competing with the switcher for one row. On a line of their own they cost
   the controls nothing and stay readable at every width — they simply wrap. */

/* The line/bar charts inherit currentColor, so the section palette owns the
   data line while the chrome below stays neutral grey. */
.ohl-chart-bd {
  color: rgb(var(--ohl-accent));
  padding: 10px 14px 6px;
  /* Floor so a chart squeezed into a half-width column (a duo) can't collapse
     into a flat sliver; the SVG keeps its aspect and centres in the taller box. */
  min-height: 180px;
}

/* Chart chrome: neutral axis / grid / labels so the tinted line or bars carry
   the only colour on the chart (the data, not the scaffolding). */
.ohl-chart-grid {
  stroke: rgb(var(--ohl-line));
  stroke-width: 1;
  stroke-opacity: 0.6;
}
.ohl-chart-axis { stroke: rgb(var(--ohl-muted) / 0.55); stroke-width: 1; }
.ohl-chart-lbl  { fill: rgb(var(--ohl-muted)); }

/* An SVG scales its whole coordinate system, text included: the picture is
   drawn once in viewBox units and then shrunk or grown to whatever card it
   lands in. A 13-unit label reads as 13 CSS px in a card that fits the box 1:1
   and as 6 px in one half that size — so the size in units has to move the
   OTHER way as the card gets smaller.

   The rung is chosen by the CARD, not by the window (`.ohl-chart-bd` below is
   a CSS container). That distinction is the whole point: three charts share a
   row in Propagace and two share one everywhere else, so the same 1440px
   window hands out cards of 304 and 503 px. A window-width breakpoint gets
   that wrong by construction, and did — it is why a separate narrow geometry
   had to exist at all.

   These are font sizes in viewBox units, not CSS px, which is why the numbers
   look wrong for a font size. Keep them in step with CHART_LABEL_SIZE and
   CHART_LABEL_SIZE_NARROW in console/panels/common.py: the renderer spaces the
   date ticks for the largest rung and marks the extras it draws for the widest
   one, and those two ends have to be the ends of THIS ladder. */
.ohl-chart-bd {
  container-type: inline-size;
}

/* Narrowest rung: one of three cards across a 1440px row, about 304 px, and a
   phone's card. The right-hand percent axis and the read-out ride the
   same ladder — they were left behind on 12 units and rendered at 5,5 CSS px
   on a phone while the left axis grew (Codex audit, 6. 9. 2026, Q02). Every rung is a value from the canonical type scale —
   they are viewBox units here, not CSS px, but there is no reason to leave the
   scale to say so, and the design gate reads them as sizes either way.

   MEASURED 9. 9. 2026 on the Dashboard, and the narrow end of this rung does
   not reach 12 CSS px: a 375px phone gives a 315px card and 11.9 px, a 360px
   phone 293px and 11.3, a 320px phone 254px and 9.8. A further rung cannot fix
   it inside the canonical scale — the only value above 27 is 28, which lands at
   10.2 on that 254px card. Closing the gap means either a new value in the
   scale or a narrower viewBox for phone cards, and the narrower box was tried
   and removed for good reasons (see CHART_LABEL_SIZE_NARROW in
   console/panels/common.py). Both are design decisions, not drift (mega check
   MC-38). */
.ohl-chart-lbl,
.ohl-chart-share-lbl,
.ohl-chart-readout { font-size: 27px; }

/* Labels the renderer drew for a card wider than this one: hidden by default,
   revealed by the rung that has room for them. These have to come BEFORE the
   @container blocks — a plain rule written after one still wins over it, which
   is how they spent their first measurement doing nothing. */
.ohl-chart-lbl--mid,
.ohl-chart-lbl--alt,
.ohl-chart-dates--medium,
.ohl-chart-dates--wide { display: none; }
.ohl-chart-dates { font-variant-numeric: tabular-nums; }
.ohl-chart-dates--compact { display: inline; }

@container (min-width: 432px) {
  .ohl-chart-lbl,
  .ohl-chart-share-lbl,
  .ohl-chart-readout { font-size: 18px; }

  .ohl-chart-lbl--mid, .ohl-chart-dates--medium { display: inline; }
  .ohl-chart-dates--compact { display: none; }
}

/* The middle band is wide enough that a single rung would render 11.6 px at
   one end and 14.6 at the other; this splits it so no card is far from 12.

   The numbers are the CONTAINER's width — the card's inner width, 28px less
   than the card. Calibrating them against the card put Propagace's 471px card
   (443 inside) below the first rung and drew its axis at 17 px (Codex audit,
   6. 9. 2026, Q02). */
@container (min-width: 532px) {
  .ohl-chart-lbl,
  .ohl-chart-share-lbl,
  .ohl-chart-readout { font-size: 16px; }
}

@container (min-width: 592px) {
  .ohl-chart-lbl,
  .ohl-chart-share-lbl,
  .ohl-chart-readout { font-size: 12px; }

  /* One SVG serves every card, so the extra dates are drawn everywhere and
     appear only where they fit. */
  .ohl-chart-lbl--alt, .ohl-chart-dates--wide { display: inline; }
  .ohl-chart-dates--medium { display: none; }
}

/* Ratio overlay (share on a right-hand percent axis, e.g. PNO over revenue
   bars): dashed and thin so it reads as the second story on the chart, in the
   PPC hue because that is where the cost side comes from — the bars keep the
   section tint, and two identically coloured series would be unreadable. */
.ohl-chart-share {
  fill: none;
  stroke: rgb(var(--ohl-ch-ppc) / 0.8);
  stroke-width: 1.6;
  stroke-dasharray: 5 4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ohl-chart-share-dot { fill: rgb(var(--ohl-ch-ppc) / 0.8); }
/* Axis labels are text: full strength, because 0.85 measured 4.25:1. */
.ohl-chart-share-lbl { fill: rgb(var(--ohl-ch-ppc)); }

/* Estimated remainder of the running month's revenue bar. The actual amount
   stays solid below it; the low-opacity dotted cap is deliberately quieter. */
.ohl-chart-forecast {
  fill: currentColor;
  fill-opacity: 0.08;
  stroke: currentColor;
  /* The dotted estimate still has to be a line someone can see: 0.45 measured
     1.94:1 against the card. Registered in core/contrast.py. */
  stroke-opacity: 0.72;
  stroke-width: 1.4;
  stroke-dasharray: 1 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* -- Source chip — names the source a block comes from, in its channel hue -- */
/* Identity, not judgment: states keep .ohl-badge--{ok,warn,err}. Channel
   tokens live in brand_tokens.css (Layer 3b). */

.ohl-src {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  border-radius: 6px;
  padding: 1px 8px;
  white-space: nowrap;
}

.ohl-src--traffic  { color: rgb(var(--ohl-ch-traffic));  background: rgb(var(--ohl-ch-traffic-bg)); }
.ohl-src--seo      { color: rgb(var(--ohl-ch-seo));      background: rgb(var(--ohl-ch-seo-bg)); }
.ohl-src--ppc      { color: rgb(var(--ohl-ch-ppc));      background: rgb(var(--ohl-ch-ppc-bg)); }
.ohl-src--social   { color: rgb(var(--ohl-ch-social));   background: rgb(var(--ohl-ch-social-bg)); }
.ohl-src--mailing  { color: rgb(var(--ohl-ch-mailing));  background: rgb(var(--ohl-ch-mailing-bg)); }
.ohl-src--feeds    { color: rgb(var(--ohl-ch-feeds));    background: rgb(var(--ohl-ch-feeds-bg)); }
.ohl-src--reviews  { color: rgb(var(--ohl-ch-reviews));  background: rgb(var(--ohl-ch-reviews-bg)); }
.ohl-src--ux       { color: rgb(var(--ohl-ch-ux));       background: rgb(var(--ohl-ch-ux-bg)); }
.ohl-src--commerce { color: rgb(var(--ohl-ch-commerce)); background: rgb(var(--ohl-ch-commerce-bg)); }
.ohl-src--products { color: rgb(var(--ohl-ch-products)); background: rgb(var(--ohl-ch-products-bg)); }
.ohl-src--customers { color: rgb(var(--ohl-ch-customers)); background: rgb(var(--ohl-ch-customers-bg)); }
.ohl-src--content  { color: rgb(var(--ohl-ch-content));  background: rgb(var(--ohl-ch-content-bg)); }
.ohl-src--competitors { color: rgb(var(--ohl-ch-competitors)); background: rgb(var(--ohl-ch-competitors-bg)); }
.ohl-src--web      { color: rgb(var(--ohl-ch-web));      background: rgb(var(--ohl-ch-web-bg)); }
/* The finance row's own hue (see .ohl-hue--margin); named in every family so
   the design-drift gate reads it as one identity. */
.ohl-src--margin   { color: rgb(var(--ohl-ch-margin));   background: rgb(var(--ohl-ch-margin-bg)); }
.ohl-src--competitors { color: rgb(var(--ohl-ch-competitors)); background: rgb(var(--ohl-ch-competitors-bg)); }

/* -- Area tiles — the dashboard's "Všechny oblasti" grid ------------------- */
/* One tile per connected area, each wearing its channel hue and clickable as a
   whole (Mira, 2026-07-21 — replaced the four-column table). Flex rows, not a
   grid: a short last row stretches to the full width instead of leaving a hole
   (11 areas as 5+5+1 left a jagged edge — Mira, 2026-08-19). The server picks
   5 or 4 per row, whichever fills the rows more evenly (--cols-4); auto-fit
   would pack eight into a row and squeeze every number. Narrower windows step
   down to four / three / two. */

.ohl-areas {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}

/* The basis subtracts its share of the gaps: n cards leave n-1 gaps of 24px,
   so each card is 100%/n minus (n-1)/n of a gap. With the old 12px gap the
   rounded 10px stand-in was close enough; at 24px it overflows and the row
   drops a card. */
.ohl-areas .ohl-area {
  flex: 1 1 calc(100% / 5 - 19.2px);
  min-width: 0;
}
.ohl-areas--cols-4 .ohl-area { flex-basis: calc(100% / 4 - 18px); }

@media (max-width: 1400px) { .ohl-areas .ohl-area { flex-basis: calc(100% / 4 - 18px); } }
@media (max-width: 1080px) { .ohl-areas .ohl-area { flex-basis: calc(100% / 3 - 16px); } }
@media (max-width: 720px)  { .ohl-areas .ohl-area { flex-basis: calc(100% / 2 - 12px); } }
/* Below that, two columns cost more than they give: the number, its change and
   the caption all had to be cut to fit. One column keeps them whole. */
@media (max-width: 520px) {
  .ohl-areas .ohl-area { flex-basis: 100%; }
  .ohl-tiles { grid-template-columns: minmax(0, 1fr); }
}

/* The channel hue rides on the top edge only: a pastel fill made the colour do
   two jobs at once — name the area AND draw the eye — so it did neither
   (Mira, 2026-08-31). On white, the numbers carry the card and the hue is a
   label. */
/* ==========================================================================
 * 6. SECTION CARDS on the dashboard grid — white, rounded, roomy, and with
 *    no coloured edge: the demo has no such device anywhere. The section hue
 *    survives where the demo puts colour, on the title.
 * ========================================================================== */
.ohl-area {
  display: block;
  background: rgb(var(--ohl-surface));
  border: 0;
  border-radius: 8px;
  /* Twelve of these sit in one grid, so the vertical padding is paid twelve
     times over: 24px top and bottom made the block taller than the whole rest
     of the hub (Mira, 6. 9. 2026). The horizontal padding is untouched — that
     is what keeps the number off the card's edge. */
  padding: 18px 24px;
  color: inherit;
  text-decoration: none;
  box-shadow: none;
}

/* The card has no border any more, so hover is the channel tint alone — the
   three border declarations that used to be here sat before `border: 0` and
   were wiped by it, which is a rule that reads as if it does something. */
.ohl-area:hover {
  background: rgb(var(--ohl-hue) / 0.04);
}

.ohl-area-hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 7px;
}

.ohl-area-title {
  font-size: 12px;
  font-weight: 700;
  color: rgb(var(--ohl-hue));
}

.ohl-area .ohl-row-arw { color: rgb(var(--ohl-hue) / 0.65); font-size: 12px; }

.ohl-area-val {
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: rgb(var(--ohl-ink));
  margin-top: 6px;
}

/* Two readings stacked (the two review portals) keep the headline size of a
   single one - the score IS the number the tile is about. Only the line
   spacing opens up so the two rows do not collide. */
.ohl-area-val--lines {
  line-height: 1.35;
  letter-spacing: 0;
}

.ohl-area-line { white-space: nowrap; }

/* A companion number that belongs TO the headline beside it rather than on a
   row of its own — "z toho 108 poprvé", "1 255 zhlédnutí" (Mira, 2026-08-22).
   Muted and small so it reads as the footnote it is, and never bold: at tile
   width three stacked numbers pushed the tile taller than its neighbours. */
.ohl-area-aside {
  font-size: 11px;
  font-weight: 400;
  color: rgb(var(--ohl-muted));
}

.ohl-area-desc {
  font-size: 11px;
  line-height: 1.35;
  color: rgb(var(--ohl-muted));
}

.ohl-area-note {
  margin-top: 7px;
  font-size: 11px;
  line-height: 1.35;
  color: rgb(var(--ohl-muted));
}

/* -- Duo — two cards (or tables) side by side on a wide screen ------------- */

/* Two blocks side by side always match height — both stretch to the taller one
   in the row, so a pair never leaves one card short with a ragged gap beside it
   (Mira, 2026-07-18). Cards top-align their content; the extra height is padding. */
/* Every top-level block is its own white card with real air around it; that
   spacing is what lets the grey ground read as contrast rather than as a
   hairline between two white sheets. */
/* minmax(0, 1fr), not 1fr: a grid column is min-width:auto by default, so a
   wide table or a nowrap number inside one card widened the column, the grid
   and finally the document. The card scrolls its own table instead. */
.ohl-duo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px;
  align-items: stretch;
}

.ohl-duo > * {
  min-inline-size: 0;
}

/* A short column that belongs BESIDE a chart rather than above it: the list
   bars sit on the chart's middle line instead of hugging the top of a column
   twice their height (Mira, 9. 9. 2026). */
.ohl-duo > .ohl-mid { justify-content: center; }

/* Both columns end on the same line, in EVERY duo — the supporting numbers
   beside a chart stopped short of it and the pair finished in a ragged step
   (Mira, 7. 9. 2026). The grid already stretched its own children, so a card
   that IS a column ended flush; a card sitting inside a wrapper (a tint layer,
   or a stack of two blocks) kept its content height and the wrapper held the
   empty space instead. The wrapper becomes a column and its LAST card takes
   the slack — last, not every card, so a short block beside a long one is not
   inflated to match it. A column of tiles gets the same treatment: "beside the
   chart" is what Mira asked about, not "in a card". */
.ohl-duo > *:not(.ohl-card) {
  display: flex;
  flex-direction: column;
}

.ohl-duo > *:not(.ohl-card) > .ohl-card:last-child,
.ohl-duo > *:not(.ohl-card) > .ohl-tiles:last-child {
  flex: 1 1 auto;
}

.ohl-duo > *:not(.ohl-card) > .ohl-card:last-child {
  display: flex;
  flex-direction: column;
}

/* Inside a duo the 24px under a card belongs to the grid, not to the card: a
   column ending in a card sat 24px higher than one ending in tiles, so the
   pair still finished in a step. Zeroed here, the row height IS the content
   height and both columns end on it; the gap below the pair comes from the
   duo. Cards STACKED inside a column keep their margin — that one is spacing
   between two blocks, not a trailing gap. */
.ohl-duo { margin-bottom: 24px; }

.ohl-duo > .ohl-card,
.ohl-duo > *:not(.ohl-card) > .ohl-card:last-child,
.ohl-duo > *:not(.ohl-card) > .ohl-tiles:last-child {
  margin-bottom: 0;
}

@media (max-width: 920px) {
  .ohl-duo {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Three narrow cards that answer one question together (order statuses, payment
   mix, shipping mix). Same rules as the duo: minmax(0, 1fr) so a wide table
   scrolls inside its own card instead of widening the page, and the row's
   bottom gap belongs to the grid.

   Three columns only from 1500px. Measured on the live page (2026-09-09): the
   widest of the three tables needs 410px of card body, which with the card's
   padding, two 24px gaps and the page's own margins lands just under 1500 -
   below that the payment and shipping tables scrolled sideways inside their
   own cards, which is the opposite of the airy row this exists for. */
.ohl-trio {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  align-items: stretch;
  margin-bottom: 24px;
}

.ohl-trio > * { min-inline-size: 0; }
.ohl-trio > .ohl-card { margin-bottom: 0; }

@media (max-width: 1499px) {
  .ohl-trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 920px) {
  .ohl-trio { grid-template-columns: minmax(0, 1fr); }
}

/* Chart card header: title on top, the range switcher (period chips) below it,
   both left-aligned. The chart sits in a half-width column, so an inline
   right-aligned switcher would be cramped; stacking keeps them one element. */
/* A COLUMN flex container that is also allowed to wrap breaks into a second
   COLUMN once its content is taller than the box - and the title's
   `flex: 0 0 100%` means 100 % of the HEIGHT here, so it always was. The
   metadata landed in a column beside the card, 310px past its right edge, and
   widened the whole document (Codex audit, 5. 9. 2026, N07). Stacking is what
   this header wants, so it stacks and does not wrap. */
.ohl-card-hd--chart {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 8px;
}

.ohl-card-hd--chart h2 {
  flex: 0 0 auto;
}
/* Page heading of a tabbed hub (Nastavení, Super admin, details). It uses the
   existing 22px heading step so a page name is visibly above its 16px card
   titles without introducing another one-off size. */
.ohl-card-hd--page h2 { font-size: 22px; }
/* Body of a tabbed hub — the card's inset below the heading. It sat in
   admin_tabs.css while the strip lived inside the card; the strip is outside
   now, so this is the card's business and belongs beside the rest of the frame
   (one class, one file — the design-drift gate refuses the split). */
.ohl-tabs-content { padding: 16px 24px 24px; }
/* A hub whose body is already a stack of cards (Propagace) wears no frame of
   its own — a card around cards is the same border-within-a-border that pulling
   the tab strip out was meant to end (Mira, 2026-09-03). The heading then sits
   on the page instead of on a card edge, so it drops the card's side padding
   and its bottom rule, and the body drops the card's inset. */
.ohl-hub--bare > .ohl-card-hd { padding: 0 0 10px; border-bottom: 0; }
.ohl-hub--bare > .ohl-tabs-content { padding: 0; }
/* A card that switches its OWN span (Konkurence counts whole years, not the
   shared 7/30/90 days) keeps a chip row under its title. The shared period
   switcher is not one of these — it sits in the section header. */
.ohl-hd-range { width: 100%; }
.ohl-hd-range .ohl-chips { margin-bottom: 0; }

/* Cap the trend chart's height so a card can't stretch it into a tall wall on
   wide screens; the flat viewBox keeps it a trend band.

   The cap is doing more than it looks. An SVG scales its WHOLE coordinate
   system, so without it a card on a 2000px monitor draws the same chart at
   1.7x: 20px axis text, 3.3px strokes, a curve that reads as clumsy rather
   than as bigger. It was lifted for the hub's chart on 6. 9. 2026 to fill the
   card's height and taken back the same afternoon for exactly that reason —
   no exceptions to it any more. */
.ohl-chart-bd svg { max-height: 235px; }

/* Composition ring and its numbers, one half of the card each (Mira,
   8. 9. 2026). A ring sized to the smallest thing it could be, tucked beside a
   legend that then left the card half empty, was using neither: the picture is
   the reason this card is not just a table, so it gets a half. */
.ohl-split {
  display: flex;
  align-items: center;
  gap: 24px;
}

/* Both halves start from nothing and split what there is, so they end up equal
   — until the numbers need more than half (below ~1200 px), where the rows'
   own minimum takes what it needs and the ring gives way. A legend scrolled
   sideways to reach its change column is worse than a smaller ring; a ring
   under about 104 px is no longer a shape anyone can read. */
.ohl-split-ring {
  flex: 1 1 0;
  min-width: 104px;
  display: flex;
  justify-content: center;
}

/* Past this the ring stops being a diagram and starts being a decoration, and
   the numbers beside it fall too far from their keys. */
.ohl-split-ring svg { max-width: 300px; }

/* A flex item's minimum is its content, and a table of amounts inside one is
   wide — without this the rows push the ring off its own card. */
.ohl-split > .ohl-tblwrap {
  flex: 1 1 0;
  min-width: min-content;
}

/* Two rings in ONE card must be the same size, whatever their legends measure.
   A ring that splits the leftover space grows into whatever its own table does
   not need, so the shorter legend got the bigger ring and the card read as if
   its left half mattered more (Mira, 2026-09-09: at 1920px the delivery-country
   ring came out 173px against the sales-channel one at 126px). Pinning both to
   a fixed box takes the legends out of it. The size is the smaller of the two
   measured there, rounded up - the wider legend loses nothing and the narrower
   one gives back what it had only borrowed. */
.ohl-split--paired .ohl-split-ring { flex: 0 0 128px; }

/* With the ring no longer giving way, the legend has to. Its min-content width
   would otherwise push past its own column and run under the neighbouring half
   on a narrow screen; inside a paired card it scrolls in its own box instead,
   the way every other wide table on the page already does. */
.ohl-split--paired > .ohl-tblwrap { min-width: 0; }

/* The name beside its key stays on one line; broken over two ("Google / Ads")
   it stops looking like one legend entry and the rows lose their alignment
   with the ring. The amounts hold the same line for the same reason.

   The columns also sit closer than in a table you read on its own: four
   nowrap columns at the usual spacing claim two thirds of the card and leave
   the ring at its floor. A legend is a key to the picture, not a table the
   picture is attached to. */
.ohl-split .ohl-table td {
  white-space: nowrap;
  padding: 10px 12px;
}

.ohl-split .ohl-table td:first-child { padding-left: 0; }
.ohl-split .ohl-table td:last-child { padding-right: 0; }

@media (max-width: 640px) {
  .ohl-split { flex-direction: column; align-items: stretch; }
  .ohl-split-ring { flex: 0 0 auto; }
}

/* Slice colours and the key that stands beside each name. One token per slot
   drives both, so a key cannot end up a different colour from its slice — the
   composition bar pairs its fills with its swatches for the same reason. The
   slot number comes from the renderer (donut_slot), never from counting rows
   twice. Which token is which colour, and why these three, is in
   brand_tokens.css; the contrast gate measures each of them against the card. */
.ohl-donut-seg--1 { stroke: rgb(var(--ohl-part-1)); }
.ohl-donut-seg--2 { stroke: rgb(var(--ohl-part-2)); }
.ohl-donut-seg--3 { stroke: rgb(var(--ohl-part-3)); }
.ohl-swatch--part1 { background: rgb(var(--ohl-part-1)); }
.ohl-swatch--part2 { background: rgb(var(--ohl-part-2)); }
.ohl-swatch--part3 { background: rgb(var(--ohl-part-3)); }

/* The number and its change, on one line above the chart. */
.ohl-metric-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding-bottom: 0;
}

/* Chart beside a taller block: the columns match height (.ohl-duo stretches),
   so the chart card grows. The SVG keeps its aspect (never stretches — that
   distorted the text and stroke widths), so it is centred in the grown body
   rather than filling and deforming. */
.ohl-chart-card { display: flex; flex-direction: column; }
.ohl-chart-card .ohl-chart-bd { flex: 1; display: flex; flex-direction: column; justify-content: center; }
/* The hub's revenue chart is not a section chart: it wore Obchod's sand and
   read as gloomy beside the pastel money row (Mira, 17. 9. 2026). Aqua is the
   one ramp that neither the three cards above, the briefing beside it nor the
   dashed PNO line on the same plot already use — lilac would have merged with
   that line and sky doubled the briefing. Set on the card, not as a channel
   tint: a new slug would need a hue and a chip too, and this is one chart.
   Scoped to the hub's own modifier — .ohl-chart-card alone also dresses the
   SEO and Propagace charts, which keep their section tints. */
.ohl-chart-card--hero .ohl-chart-bd { --ohl-chart-ink: rgb(var(--ohl-aqua-600)); color: var(--ohl-chart-ink); }

/* Companion lines: the other tracked competitors behind our own share. They
   are data, not decoration — a rival's line sits ON the current series' fill,
   and seen through that fill at 0.75 opacity it measured 2,49:1 where 3:1 is
   the floor (Codex reaudit, 6. 9. 2026, R06), so none of them is faded.

   Each carries a SLOT: one of the three shared part colours crossed with a
   dash pattern. One grey for all of them was readable with a single rival and
   unusable with five, which is what Konkurence draws (Mira, 9. 9. 2026). Two
   signals rather than one — hue AND shape — so the lines stay separable for a
   reader who cannot tell the hues apart. Our own line keeps the section colour
   and the thickest stroke; it is still the one the chart is about. */
.ohl-chart-companion {
  stroke: rgb(var(--ohl-muted));
}

.ohl-chart-companion--slot1 { stroke: rgb(var(--ohl-part-1)); }
.ohl-chart-companion--slot2 { stroke: rgb(var(--ohl-part-2)); }
.ohl-chart-companion--slot3 { stroke: rgb(var(--ohl-part-3)); }
.ohl-chart-companion--slot4 { stroke: rgb(var(--ohl-part-1)); stroke-dasharray: 6 3; }
.ohl-chart-companion--slot5 { stroke: rgb(var(--ohl-part-2)); stroke-dasharray: 6 3; }
.ohl-chart-companion--slot6 { stroke: rgb(var(--ohl-part-3)); stroke-dasharray: 6 3; }

/* A lone point of a companion is a dot, not a stroke: it needs the slot colour
   as a FILL, and no dash pattern applies to it. */
circle.ohl-chart-companion--slot1 { fill: rgb(var(--ohl-part-1)); }
circle.ohl-chart-companion--slot2 { fill: rgb(var(--ohl-part-2)); }
circle.ohl-chart-companion--slot3 { fill: rgb(var(--ohl-part-3)); }
circle.ohl-chart-companion--slot4 { fill: rgb(var(--ohl-part-1)); }
circle.ohl-chart-companion--slot5 { fill: rgb(var(--ohl-part-2)); }
circle.ohl-chart-companion--slot6 { fill: rgb(var(--ohl-part-3)); }

/* The invisible per-day column that carries the value. Reachable by Tab, so it
   needs a focus ring the reader can see — an invisible element with focus and
   no ring is a keyboard trap in practice (Codex audit, 5. 9. 2026, N06). The
   ring is drawn INSIDE the plot, because an SVG child cannot paint outside the
   viewBox. */
/* The focused day, spelled out at the top of the plot. Empty until a day is
   chosen by keyboard or tap, so a chart nobody is interrogating shows nothing
   extra (Codex reaudit, 6. 9. 2026, R05). */
.ohl-chart-readout {
  fill: rgb(var(--ohl-ink));
  font-weight: 700;
}

.ohl-chart-hit:focus-visible {
  fill-opacity: 0.08;
  outline: none;
  stroke: rgb(var(--ohl-ink));
  stroke-width: 1.5;
  stroke-dasharray: 3 2;
}

/* The living guide is a page like any other and has to survive a phone. Its
   demo rows let the width samples (494 px, 741 px) and the example URL table
   push the document to 773 px at a 390 px viewport — the guide that documents
   the mobile contract was the one screen breaking it (Codex reaudit,
   6. 9. 2026, R18). The samples keep their real widths, they just stop forcing
   the page open: the row scrolls its own content instead. */
@media (max-width: 720px) {
  .ohl-sg-row {
    flex-wrap: wrap;
  }

  .ohl-sg-row > * {
    min-width: 0;
  }

  .ohl-sg-demo {
    max-inline-size: 100%;
    overflow-x: auto;
  }
}

/* Chart legend: each key is DRAWN the way its series is drawn — a short line
   in the chart's own colour, dashed for the reference window, thin and neutral
   for a companion (Mira, 9. 9. 2026). The words that stood here described the
   picture the reader was already looking at. The text stays muted so it labels
   without competing with the data; the swatch takes the section's ink from the
   tint on the chart body, so it cannot drift from the line it explains. */
.ohl-chart-legend {
  margin: 10px 0 0;
  font-size: 12px;
  color: rgb(var(--ohl-muted));
  text-align: center;
}

.ohl-chart-key {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.ohl-chart-key + .ohl-chart-key { margin-left: 16px; }

.ohl-chart-key-line {
  display: block;
  width: 20px;
  border-top: 2px solid var(--ohl-chart-ink, rgb(var(--ohl-accent)));
}

/* The current window: the plain rule above already draws it — named so the
   template can state which series every key stands for. */
.ohl-chart-key-line--now { border-top-style: solid; }

.ohl-chart-key-line--prev {
  border-top-width: 1.5px;
  border-top-style: dashed;
}

/* Same stroke as .ohl-chart-companion above, and the same slot: a key drawn in
   a colour the line does not have is worse than no key. */
.ohl-chart-key-line--companion { border-top-width: 1.75px; }
.ohl-chart-key-line--slot1 { border-top-color: rgb(var(--ohl-part-1)); }
.ohl-chart-key-line--slot2 { border-top-color: rgb(var(--ohl-part-2)); }
.ohl-chart-key-line--slot3 { border-top-color: rgb(var(--ohl-part-3)); }
.ohl-chart-key-line--slot4 { border-top-color: rgb(var(--ohl-part-1)); border-top-style: dashed; }
.ohl-chart-key-line--slot5 { border-top-color: rgb(var(--ohl-part-2)); border-top-style: dashed; }
.ohl-chart-key-line--slot6 { border-top-color: rgb(var(--ohl-part-3)); border-top-style: dashed; }

/* Horizontal ranking bar — share-of-voice as a list where each row's fill
   length reads the share at a glance and our own row is highlighted in the hue. */
.ohl-rank { display: flex; flex-direction: column; gap: 8px; }
.ohl-rank-row {
  display: grid;
  grid-template-columns: minmax(110px, 1.2fr) 3fr auto;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  color: rgb(var(--ohl-ink));
}
.ohl-rank-track { background: rgb(var(--ohl-bg)); border-radius: 6px; height: 14px; overflow: hidden; }
.ohl-rank-fill { display: block; height: 100%; background: rgb(var(--ohl-muted) / 0.5); border-radius: 6px; }
.ohl-rank-fill--us { background: rgb(var(--ohl-accent)); }
.ohl-rank-val { font-variant-numeric: tabular-nums; color: rgb(var(--ohl-muted)); }

/* Stacked composition bar — segments that sum to one whole (e.g. a contact
   list split into deliverable / blocked / unsubscribed). */
.ohl-stack { display: flex; height: 14px; border-radius: 6px; overflow: hidden; background: rgb(var(--ohl-bg)); }
.ohl-stack-seg { height: 100%; }
.ohl-stack-key { display: inline-block; width: 9px; height: 9px; border-radius: 9999px; margin-right: 2px; }

/* Hairline between touching segments. Green and amber carry almost the same
   luminance (measured 1.00:1 in dark mode), so where two segments meet the
   boundary simply vanishes — WCAG 1.4.11 wants adjacent graphics to be
   separable. A separator in the card's own surface splits them without
   touching the state colours, which mean the same thing everywhere else. */
.ohl-stack-seg + .ohl-stack-seg,
.ohl-bar-seg + .ohl-bar-seg {
  border-left: 1px solid rgb(var(--ohl-surface));
}

/* Segment and its key dot carry one colour each — same reason as the severity
   fills above: a key that stops matching its bar misleads. */
.ohl-stack-seg--ok,    .ohl-stack-key--ok    { background: rgb(var(--ohl-success)); }
.ohl-stack-seg--warn,  .ohl-stack-key--warn  { background: rgb(var(--ohl-warning)); }
/* Full opacity, not half: at 50 % the unsubscribed share washed out to 1.9:1
   against the surface around it, which is under the bar for a graphic that
   has to be seen at all. */
.ohl-stack-seg--muted, .ohl-stack-key--muted { background: rgb(var(--ohl-muted)); }

/* Subtle highlight for the newest row in a table (e.g. the last campaign),
   in the section hue so it reads as "freshest", not as an alert. */
.ohl-tr-latest > td { background: rgb(var(--ohl-hue-bg) / 0.6); }

/* One list's composition: heading + stacked bar + a spelled-out breakdown, so
   the numbers are readable without hovering. */
.ohl-list-health { margin-bottom: 16px; }
.ohl-list-health:last-child { margin-bottom: 0; }
.ohl-list-health-hd { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; font-size: 14px; }

/* Segmented value bar (stock capital by time since last sale): one row coloured
   left→right from fresh (the section hue) to the most-frozen (warning). Segment
   widths are inline, computed from the data. */
.ohl-bar {
  display: flex;
  height: 26px;
  border-radius: 8px;
  overflow: hidden;
  margin: 2px 0 14px;
}
.ohl-bar-seg { min-width: 2px; }

/* Severity fills, shared by the bar and its legend swatch below. Written twice
   they could drift, and a legend whose colour no longer matches the bar is
   worse than no legend. */
.ohl-bar-seg--sev0, .ohl-swatch--sev0 { background: rgb(var(--ohl-hue) / 0.28); }
.ohl-bar-seg--sev1, .ohl-swatch--sev1 { background: rgb(var(--ohl-warning) / 0.4); }
.ohl-bar-seg--sev2, .ohl-swatch--sev2 { background: rgb(var(--ohl-warning) / 0.65); }
.ohl-bar-seg--sev3, .ohl-swatch--sev3 { background: rgb(var(--ohl-warning)); }

/* Legend swatch matching a bar segment's colour. */
.ohl-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 4px;
  margin-right: 6px;
  vertical-align: -1px;
}

/* Purchase funnel drawn, not tabulated. One row per step: its name, a track
   that is always the full width, and the numbers on the right.

   The FILLED part of each track is the share of the previous step, so every
   track is read against the same 0-100 % scale and the emptiest one marks the
   step that loses the most. Widths against the first step were tried and thrown
   away on 2026-09-09: in a shop where 1,9 % of product views reach a cart they
   collapse into four identical hairlines, which cannot tell a step that keeps
   71 % from one that keeps 47 %. The empty remainder of the track is the part
   that dropped out, which is what makes the drawing a funnel and not a bar. */
.ohl-funnel { display: flex; flex-direction: column; gap: 10px; }
.ohl-funnel-step {
  display: grid;
  grid-template-columns: minmax(120px, 1.1fr) minmax(90px, 2fr) minmax(140px, auto);
  align-items: center;
  gap: 12px;
}
.ohl-funnel-name { font-size: 14px; }
.ohl-funnel-track {
  display: flex;
  height: 26px;
  border-radius: 6px;
  overflow: hidden;
  background: rgb(var(--ohl-bg));
}
.ohl-funnel-bar {
  min-width: 3px;
  border-radius: 6px;
  background: rgb(var(--ohl-hue) / 0.55);
}
/* The step that fell furthest below its own history keeps the warning colour
   the badge beside it already uses, so the drawing and the label agree. */
.ohl-funnel-step--warn .ohl-funnel-bar { background: rgb(var(--ohl-warning)); }
.ohl-funnel-figures {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 8px;
  font-variant-numeric: tabular-nums;
}
.ohl-funnel-count { font-size: 16px; font-weight: 600; }
.ohl-funnel-kept { font-size: 12px; color: rgb(var(--ohl-muted)); }

/* Narrow screens have no room for three columns; the bar keeps its own line
   under the name and the figures follow it. */
@media (max-width: 720px) {
  .ohl-funnel-step { grid-template-columns: 1fr auto; }
  .ohl-funnel-track { grid-column: 1 / -1; }
}

.ohl-tiles > .ohl-hue { display: flex; }
.ohl-tiles > .ohl-hue > .ohl-tile { flex: 1; }

/* Briefing rows spread to fill the card's height so it matches the chart. This
   is the whole of what --even still adds: equal card heights below became the
   plain duo's job. */
.ohl-duo--even .ohl-brief-list { flex: 1; display: flex; flex-direction: column; }
.ohl-duo--even .ohl-brief-row { flex: 1; align-items: center; }

/* Chart line tint per channel (overrides the accent default above).
   The set stays complete even where no chart asks for the hue yet: .ohl-hue--
   and .ohl-src-- are assembled from the same channel slugs at runtime, and a
   tint missing from only this one family would render the next chart grey
   with nothing to say why. Each names its ink in --ohl-chart-ink as well as in
   `color`, so the legend key below the plot can paint a line in the channel's
   colour while its own text stays muted (competitors was listed twice here
   until 9. 9. 2026). */
.ohl-tint--traffic     { --ohl-chart-ink: rgb(var(--ohl-ch-traffic)); color: var(--ohl-chart-ink); }
.ohl-tint--seo         { --ohl-chart-ink: rgb(var(--ohl-ch-seo)); color: var(--ohl-chart-ink); }
.ohl-tint--ppc         { --ohl-chart-ink: rgb(var(--ohl-ch-ppc)); color: var(--ohl-chart-ink); }
.ohl-tint--social      { --ohl-chart-ink: rgb(var(--ohl-ch-social)); color: var(--ohl-chart-ink); }
.ohl-tint--mailing     { --ohl-chart-ink: rgb(var(--ohl-ch-mailing)); color: var(--ohl-chart-ink); }
.ohl-tint--feeds       { --ohl-chart-ink: rgb(var(--ohl-ch-feeds)); color: var(--ohl-chart-ink); }
.ohl-tint--reviews     { --ohl-chart-ink: rgb(var(--ohl-ch-reviews)); color: var(--ohl-chart-ink); }
.ohl-tint--ux          { --ohl-chart-ink: rgb(var(--ohl-ch-ux)); color: var(--ohl-chart-ink); }
.ohl-tint--commerce    { --ohl-chart-ink: rgb(var(--ohl-ch-commerce)); color: var(--ohl-chart-ink); }
.ohl-tint--customers   { --ohl-chart-ink: rgb(var(--ohl-ch-customers)); color: var(--ohl-chart-ink); }
.ohl-tint--products    { --ohl-chart-ink: rgb(var(--ohl-ch-products)); color: var(--ohl-chart-ink); }
.ohl-tint--content     { --ohl-chart-ink: rgb(var(--ohl-ch-content)); color: var(--ohl-chart-ink); }
.ohl-tint--competitors { --ohl-chart-ink: rgb(var(--ohl-ch-competitors)); color: var(--ohl-chart-ink); }
.ohl-tint--web         { --ohl-chart-ink: rgb(var(--ohl-ch-web)); color: var(--ohl-chart-ink); }
.ohl-tint--margin      { --ohl-chart-ink: rgb(var(--ohl-ch-margin)); color: var(--ohl-chart-ink); }

/* -- Hue scope — a whole page (or card) carries its channel's pastel ------- */
/* Wrap section content in `ohl-hue ohl-hue--<slug>`: hero tiles, the active
   range chip and hued card headers pick the channel color up from --ohl-hue /
   --ohl-hue-bg, so a section reads in its identity without per-component
   classes. Identity only — states keep green/amber/red (same rule as .ohl-src). */

.ohl-hue--traffic  { --ohl-hue: var(--ohl-ch-traffic);  --ohl-hue-bg: var(--ohl-ch-traffic-bg); }
.ohl-hue--seo      { --ohl-hue: var(--ohl-ch-seo);      --ohl-hue-bg: var(--ohl-ch-seo-bg); }
.ohl-hue--ppc      { --ohl-hue: var(--ohl-ch-ppc);      --ohl-hue-bg: var(--ohl-ch-ppc-bg); }
.ohl-hue--social   { --ohl-hue: var(--ohl-ch-social);   --ohl-hue-bg: var(--ohl-ch-social-bg); }
.ohl-hue--mailing  { --ohl-hue: var(--ohl-ch-mailing);  --ohl-hue-bg: var(--ohl-ch-mailing-bg); }
.ohl-hue--feeds    { --ohl-hue: var(--ohl-ch-feeds);    --ohl-hue-bg: var(--ohl-ch-feeds-bg); }
.ohl-hue--reviews  { --ohl-hue: var(--ohl-ch-reviews);  --ohl-hue-bg: var(--ohl-ch-reviews-bg); }
.ohl-hue--ux       { --ohl-hue: var(--ohl-ch-ux);       --ohl-hue-bg: var(--ohl-ch-ux-bg); }
.ohl-hue--commerce { --ohl-hue: var(--ohl-ch-commerce); --ohl-hue-bg: var(--ohl-ch-commerce-bg); }
/* Not a section: the gross-margin card on the hub. Revenue wears Obchod's sand
   and margin after ads PPC's lilac, and the card between them needed a third
   hue that is neither the briefing's sky nor a section it would be mistaken
   for (Mira, 17. 9. 2026). Mint is the SEO ramp, but this slug is deliberately
   its own name so nothing reads the margin as an SEO number — the very mix-up
   the Codex audit of 5. 9. 2026 (N20) took out of the card. The pair lives in
   brand_tokens.css so dark mode flips it with the rest. */
.ohl-hue--margin   { --ohl-hue: var(--ohl-ch-margin);   --ohl-hue-bg: var(--ohl-ch-margin-bg); }
/* Every section in the rail now owns a ramp; nothing borrows (Mira,
   7. 9. 2026: three sections wearing the same red-brown made the charts on
   Obchod, Zákazníci and Sociální sítě look like one section seen three times).
   Twelve sections, twelve ramps: Obchod sand, Zákazníci rose, Produkty indigo,
   Sociální sítě plum, Recenze peach, Obsah lime, Mailing green, Konkurence
   slate, Návštěvnost sky, SEO mint, Feedy aqua, PPC lilac. Mailing takes the
   success ramp on purpose - "delivered" is the one channel where green means
   the same thing twice. Web and UX are not rail sections and keep sharing
   slate and plum, where nobody sees the two side by side. */
.ohl-hue--customers { --ohl-hue: var(--ohl-ch-customers); --ohl-hue-bg: var(--ohl-ch-customers-bg); }
.ohl-hue--products { --ohl-hue: var(--ohl-ch-products); --ohl-hue-bg: var(--ohl-ch-products-bg); }
.ohl-hue--web      { --ohl-hue: var(--ohl-ch-web);      --ohl-hue-bg: var(--ohl-ch-web-bg); }
.ohl-hue--content  { --ohl-hue: var(--ohl-ch-content);  --ohl-hue-bg: var(--ohl-ch-content-bg); }
.ohl-hue--competitors { --ohl-hue: var(--ohl-ch-competitors); --ohl-hue-bg: var(--ohl-ch-competitors-bg); }

.ohl-hue .ohl-chip--on {
  background: rgb(var(--ohl-hue-bg));
  border-color: rgb(var(--ohl-hue) / 0.35);
  color: rgb(var(--ohl-hue));
}

/* Hued card — the header carries the scope's channel wash (dashboard areas). */
.ohl-card--hue .ohl-card-hd {
  background: rgb(var(--ohl-hue-bg) / 0.55);
  border-bottom-color: rgb(var(--ohl-hue) / 0.2);
  border-radius: 10px 10px 0 0;
}

/* -- Section heading — chapters inside a long multi-source page ------------ */
/* PPC / Feedy / SEO stack several platforms; a heading between card groups
   says whose numbers follow without spending a whole card on it. */

.ohl-sect {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 26px 0 12px;
}

.ohl-sect h2 {
  font-size: 16px;
  font-weight: 700;
  margin: 0;
  color: rgb(var(--ohl-ink));
}

/* -- Table (mockup table) --------------------------------------------------- */

/* A dashboard framed from another origin (product analytics). Its own page
   scrolls inside the frame, so the height is ours to choose; 78vh keeps the
   tab strip above it visible instead of pushing it off the screen. */
.ohl-embed {
  inline-size: 100%;
  block-size: 78vh;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 8px;
  background: rgb(var(--ohl-surface));
}

.ohl-tblwrap {
  overflow-x: auto;
  /* The wrapper has to be allowed to be narrower than its table, or the table
     widens the card instead of scrolling inside it. */
  max-inline-size: 100%;
}

.ohl-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  color: rgb(var(--ohl-ink));
}

/* ==========================================================================
 * 8. TABLES
 * ========================================================================== */
/* 12px rather than 11: even bold, this is small text and WCAG asks 4.5:1 of
   it. Paired with --ohl-muted it measures 5.6:1; the lighter grey the redesign
   briefly used measured 2.66:1 (Codex audit, 5. 9. 2026, N03). */
.ohl-table th {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: rgb(var(--ohl-muted));
  text-align: left;
  padding: 16px;
  border-bottom: 1px solid rgb(var(--ohl-line));
  white-space: nowrap;
  font-weight: 700;
  border-bottom-color: rgb(var(--ohl-line));
}

/* Data text is 14px, the same size as the rest of the page. It sat at 12 —
   the size of a caption — while the cards and buttons around it grew, so the
   numbers a reader came for were the smallest thing on the screen (Codex
   audit, 5. 9. 2026, N19). 12px stays for what is genuinely secondary: column
   names, captions, the note under a value. */
.ohl-table td {
  padding: 14px 16px;
  border-bottom: 1px solid rgb(var(--ohl-line));
  vertical-align: middle;
  font-size: 14px;
  color: rgb(var(--ohl-ink));
  border-bottom-color: rgb(var(--ohl-line));
}

/* The metric dictionary is several group tables (one card each). Fixed layout
   with shared column widths keeps every table's columns in the same place, so
   the cards read as one grid; top-align lines the first line of tall cells up. */
.ohl-table--dict {
  table-layout: fixed;
}
.ohl-table--dict td {
  vertical-align: top;
}
.ohl-table--dict th:nth-child(1),
.ohl-table--dict td:nth-child(1) { width: 16%; }
.ohl-table--dict th:nth-child(2),
.ohl-table--dict td:nth-child(2) { width: 15%; }
.ohl-table--dict th:nth-child(3),
.ohl-table--dict td:nth-child(3) { width: 49%; }
.ohl-table--dict th:nth-child(4),
.ohl-table--dict td:nth-child(4) { width: 14%; }
.ohl-table--dict th:nth-child(5),
.ohl-table--dict td:nth-child(5) { width: 6%; }
/* The value column carries composite figures (a share plus a count); let them
   wrap inside the fixed width instead of overflowing into the definition. The
   extra .ohl-table selector out-specifies the base .ohl-num nowrap rule below. */
.ohl-table.ohl-table--dict td.ohl-num {
  white-space: normal;
}
/* On a phone the shared grid stops being a grid. Five fixed columns inside a
   295px card squeeze to nothing and the content leaves the cells - measured
   9. 9. 2026 at 375px: the table drew 295px wide while its content needed 326,
   so the "not built yet" badge sat outside its column (mega check MC-38).
   Below the breakpoint the columns size to their content instead and the table
   scrolls inside .ohl-tblwrap, which is what that wrapper is for. Alignment
   across cards is worth less than legible cells. */
@media (max-width: 700px) {
  .ohl-table--dict {
    table-layout: auto;
    min-inline-size: 620px;
  }
  .ohl-table--dict th,
  .ohl-table--dict td {
    width: auto;
  }
}

/* Two stacked tables in one card (the device split above the new-vs-returning
   split) read as one grid: a fixed layout with shared widths lines their
   columns up, the same trick as the metric dictionary above. */
.ohl-table--aligned { table-layout: fixed; }
.ohl-table--aligned th:nth-child(1),
.ohl-table--aligned td:nth-child(1) { width: 40%; }
.ohl-table--aligned th:nth-child(2),
.ohl-table--aligned td:nth-child(2) { width: 22%; }
.ohl-table--aligned th:nth-child(3),
.ohl-table--aligned td:nth-child(3) { width: 20%; }
.ohl-table--aligned th:nth-child(4),
.ohl-table--aligned td:nth-child(4) { width: 18%; }

/* Competitor roster. Auto layout gave the name column everything the short
   numeric columns did not need, which pushed the two social columns so narrow
   that their figures wrapped onto separate lines (Mira, 2026-07-31). Fixed
   widths halve the name and hand the space to the platforms, so Instagram and
   YouTube read side by side. Top-aligned because the social cells carry a
   second, muted line. */
.ohl-table--roster { table-layout: fixed; }
.ohl-table--roster td { vertical-align: top; }
.ohl-table--roster th:nth-child(1),
.ohl-table--roster td:nth-child(1) { width: 24%; }
.ohl-table--roster th:nth-child(2),
.ohl-table--roster td:nth-child(2) { width: 11%; }
.ohl-table--roster th:nth-child(3),
.ohl-table--roster td:nth-child(3) { width: 10%; }
.ohl-table--roster th:nth-child(4),
.ohl-table--roster td:nth-child(4) { width: 11%; }
.ohl-table--roster th:nth-child(5),
.ohl-table--roster td:nth-child(5),
.ohl-table--roster th:nth-child(6),
.ohl-table--roster td:nth-child(6) { width: 22%; }

/* Settings rows hold forms and a list of addresses, so they are tall; centring
   them left the name floating in mid-air next to its own row. */
.ohl-table--settings td { vertical-align: top; }

/* Addresses of one competitor, side by side instead of one per line. */
.ohl-idlist {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
}

.ohl-table tr:last-child td {
  border-bottom: 0;
}

.ohl-table th:first-child, .ohl-table td:first-child { padding-left: 0; }
.ohl-table th:last-child, .ohl-table td:last-child { padding-right: 0; }

/* Text can wrap; identifiers, dates and amounts remain scannable. */
.ohl-table--orders { min-width: 900px; table-layout: fixed; }
.ohl-table--orders th:nth-child(1) { width: 13%; }
.ohl-table--orders th:nth-child(2) { width: 12%; }
.ohl-table--orders th:nth-child(3) { width: 24%; }
.ohl-table--orders th:nth-child(4) { width: 18%; }
.ohl-table--orders th:nth-child(5) { width: 9%; }
.ohl-table--orders th:nth-child(6) { width: 12%; }
.ohl-table--orders th:nth-child(7) { width: 12%; }
.ohl-table--orders td:nth-child(3) { overflow-wrap: anywhere; }
.ohl-table tr[data-row-link] { cursor: pointer; }
.ohl-table tr[data-row-link]:focus-within { background: rgb(var(--ohl-bg)); }

/* Numeric column — right-aligned, tabular figures. */
.ohl-num {
  font-variant-numeric: tabular-nums;
}

.ohl-table td.ohl-num,
.ohl-table th.ohl-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Long entry-page URL: keep it to one line and clip with an ellipsis so the
   table stays scannable; the full path is in the cell's title tooltip. */
.ohl-cell-url {
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Overview rows deep-link to their section: an arrow appears and the row lifts
   on hover, so the click target reads as one. */
.ohl-row-link {
  text-decoration: none;
  color: rgb(var(--ohl-accent));
  font-weight: 600;
}

/* A link inside a table cell reads as plain text until the pointer proves
   otherwise; underlining on hover is what says "this goes somewhere" (Mira,
   2026-07-25). Applies to every in-table link, not just the order number. */
.ohl-table a:hover,
.ohl-row-link:hover {
  text-decoration: underline;
}

.ohl-table tbody tr:hover { background: rgb(var(--ohl-bg)); }
.ohl-table tbody tr:hover .ohl-row-arw { opacity: 1; }

/* A row worth a second look — the weakest sub-rating — gets the quiet amber
   wash, never the red of a confirmed problem (a thin sample can read low by
   chance). Holds its tint on hover so the signal does not vanish. */
.ohl-table tbody tr.ohl-row--flag,
.ohl-table tbody tr.ohl-row--flag:hover { background: rgb(var(--ohl-warning-bg)); }

/* The row currently singled out in a chart. Accent, not amber: this is a
   selection the reader made, not a problem the data reported. */
.ohl-table tbody tr.ohl-row--on,
.ohl-table tbody tr.ohl-row--on:hover { background: rgb(var(--ohl-accent-soft)); }

/* -- Note — quiet dashed hint box (mockup .note) ---------------------------- */

.ohl-note {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
  background: rgb(var(--ohl-bg));
  border: 1px dashed rgb(var(--ohl-line));
  border-radius: 8px;
  padding: 8px 12px;
  margin-bottom: 16px;
}

/* The one line a suspended project says on every screen. Same shape as a note,
   in the warning colour: it is not an error and not decoration - it explains
   why the product stopped doing things. */
.ohl-note--warn {
  color: rgb(var(--ohl-ink));
  background: rgb(var(--ohl-warning-bg));
  border-style: solid;
  border-color: rgb(var(--ohl-warning));
}

/* -- Read-out list — the written decision lines beside a chart or tiles ------ */

.ohl-readout {
  margin: 0;
  padding-left: 18px;
  list-style: disc outside;
}
.ohl-readout li { margin: 0 0 8px; }
.ohl-readout li:last-child { margin-bottom: 0; }

/* -- Forms — settings/source editing (canonical form family) ---------------- */

.ohl-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 640px;
}

.ohl-form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* The 14px above is a FLEX GAP, so it reaches only the form's own children.
   Rows nested one level down — inside a <details> section, a language column,
   a fieldset — got nothing, and a label sat straight against the input above
   it. The second rule takes the margin back off the direct children, which
   already have the gap; without it they would get both. */
.ohl-form .ohl-form-row + .ohl-form-row { margin-top: 14px; }
.ohl-form > .ohl-form-row + .ohl-form-row { margin-top: 0; }

/* Two independent forms one after another — the e-mail editor renders one per
   language — read as a single run-on block with nothing between them. */
.ohl-form + .ohl-form { margin-top: 24px; }

/* A form holding two language columns side by side. 640px is right for one
   column of prose; the same width around two of them squeezes both into a
   gutter on a desktop that has room to spare. */
.ohl-form--wide { max-width: 1100px; }

/* Field label — a form row, a switch, and the two filter toolbars all name
   their control the same way. */
.ohl-form-row label,
form label[for],
.ohl-switch-label,
.ohl-queue-area label,
.ohl-report-filters label {
  font-size: 16px;
  font-weight: 600;
  color: rgb(var(--ohl-ink));
}

/* Text controls — ONE family, one look, listed by type.
   The reference draws a field as a white block with a hairline all round, 6px
   corners and 36px tall; fields are the one place in this design that still
   carries a border, because a white field on a white card has nothing else to
   stand on.
   This list used to exist twice — once scoped to `.ohl-form-row` and once bare
   — with different corners and padding, so the same field looked different
   depending on which form it sat in, and `url` was in neither: "Web e-shopu"
   in Nový projekt rendered as an invisible box (Codex audit, 5. 9. 2026, N08).
   A type left off this list is a field with no design at all, so a new input
   type is added HERE and nowhere else. */
.ohl-range-btn,
input[type="text"],
input[type="search"],
input[type="email"],
input[type="url"],
input[type="tel"],
input[type="password"],
input[type="number"],
input[type="date"],
select,
textarea {
  font-family: var(--ohl-font-sans);
  font-size: 14px;
  font-weight: 500;
  color: rgb(var(--ohl-ink));
  background: rgb(var(--ohl-surface));
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 6px;
  /* A third shorter than it was (Mira, 7. 9. 2026). 36px of box around one
     word of text is a button's worth of weight given to a control that is only
     being read most of the time; a form of them looked heavier than the page
     it sat on. The touch rule further down puts the finger-sized field back on
     a coarse pointer. */
  padding: 5px 12px;
  min-height: 30px;
}

/* A filter sits in a toolbar row rather than in a form, so it stays compact. */
.ohl-queue-area select,
.ohl-report-filters select,
.ohl-report-filters input[type="search"] {
  padding: 6px 10px;
}

/* Number inputs (recipe thresholds) are short — a full-width box would imply
   an essay. Wide enough for a three-digit day count. */
.ohl-form-row input[type="number"] {
  max-width: 8rem;
}

.ohl-hint {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
  margin: 0;
}

.ohl-field-error {
  font-size: 12px;
  color: rgb(var(--ohl-error));
  margin: 0;
}

.ohl-form-actions {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}
.ohl-form-actions--between { align-items: center; justify-content: space-between; }
.ohl-form-actions--end { justify-content: flex-end; }

/* Super admin › Briefing: a compact Markdown editor beside a scannable list. */
.ohl-guidance-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 16px;
}
.ohl-guidance-toolbar--topics { margin-top: 28px; }
.ohl-guidance-search { display: flex; gap: 8px; }
.ohl-guidance-layout {
  display: grid;
  grid-template-columns: minmax(210px, 0.7fr) minmax(0, 1.7fr);
  gap: 18px;
  align-items: start;
}
.ohl-guidance-list { display: flex; flex-direction: column; gap: 6px; }
.ohl-guidance-choice {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 11px 12px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 8px;
  color: rgb(var(--ohl-ink));
  text-decoration: none;
}
.ohl-guidance-choice:hover,
.ohl-guidance-choice.is-active {
  border-color: rgb(var(--ohl-accent-line));
  background: rgb(var(--ohl-accent-soft));
}
.ohl-guidance-choice.is-off { color: rgb(var(--ohl-muted)); }
.ohl-guidance-choice span { font-size: 12px; color: rgb(var(--ohl-muted)); }
.ohl-markdown-editor { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; line-height: 1.55; }
.ohl-library-rules { padding-bottom: 24px; border-bottom: 1px solid rgb(var(--ohl-line)); }
@media (max-width: 760px) {
  .ohl-guidance-layout { grid-template-columns: 1fr; }
  .ohl-form-actions--between { align-items: flex-start; flex-direction: column; }
}

.ohl-guide {
  margin: 6px 0;
  padding-left: 19px;
  font-size: 14px;
  line-height: 1.7;
  /* The admin theme resets list markers globally; restore numbers so an ordered
     guide reads as 1. 2. 3. (and each item stays referable by its number). */
  list-style: decimal outside;
}

.ohl-guide li {
  margin-bottom: 4px;
}
.ohl-guide a,
.ohl-modal--drawer .ohl-hint a {
  color: rgb(var(--ohl-accent));
  text-decoration: underline;
  text-underline-offset: 3px;
  overflow-wrap: anywhere;
}

/* -- Switch toggle — ported from OutOfDark's guide (2026-07-17) ------------
   There it is Unfold's native boolean rendering; our hand-rolled forms need
   it as a component. The input stays real (accessibility, form POST) and is
   only visually replaced by the track. */

.ohl-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
}

.ohl-switch input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 36px;
  height: 20px;
  margin: 0;
  cursor: pointer;
}

.ohl-switch-track {
  flex: none;
  width: 36px;
  height: 20px;
  border-radius: 9999px;
  background: rgb(var(--ohl-line));
  position: relative;
  transition: background 0.15s;
}

.ohl-switch-track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 9999px;
  background: rgb(var(--ohl-surface));
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
  transition: transform 0.15s;
}

.ohl-switch input:checked + .ohl-switch-track {
  background: rgb(var(--ohl-success));
}

.ohl-switch input:checked + .ohl-switch-track::after {
  transform: translateX(16px);
}

.ohl-switch input:focus-visible + .ohl-switch-track {
  outline: 2px solid rgb(var(--ohl-accent));
  outline-offset: 1px;
}

/* -- Empty state — ported from OutOfDark's changelist pattern (2026-07-17) --
   Icon + title + body (+ optional CTA) instead of a bare paragraph. */

.ohl-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 2.5rem 2rem;
  gap: 4px;
}

/* Icon glyph stays on the 22px type scale; the visual weight comes from the
   circle behind it (width/height are layout, not typography). */
.ohl-empty-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 9999px;
  background: rgb(var(--ohl-bg));
  font-size: 22px;
  color: rgb(var(--ohl-muted));
}

.ohl-empty-title {
  font-size: 16px;
  font-weight: 600;
  color: rgb(var(--ohl-ink));
  margin: 6px 0 0;
}

.ohl-empty-body {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
  max-width: 380px;
  margin: 0;
}

/* -- External link with an arrow — ported from OutOfDark (2026-07-17) ------ */

.ohl-ext-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: rgb(var(--ohl-accent));
  text-decoration: none;
}

.ohl-ext-link:hover {
  text-decoration: underline;
}

.ohl-ext-link .material-symbols-outlined {
  font-size: 14px;
}

/* ==========================================================================
 * Ported from OutOfDark's design guide, second wave (Mira, 2026-07-17):
 * generic primitives stocked ahead of need, in Korzaro's own tokens. Widgets
 * that need server machinery (searchable select, multiselect dropdown) are
 * deliberately NOT here — they land with their first consumer.
 * ========================================================================= */

/* -- Danger button — destructive actions only ------------------------------ */

.ohl-btn--danger,
.ohl-btn--danger:hover {
  background: rgb(var(--ohl-error));
  border-color: rgb(var(--ohl-error));
  color: rgb(var(--ohl-surface));
}

/* -- Reject button — refusal, disconnection or ending an activity -----------
   A quieter red than permanent removal, with the same size and type as the
   positive action beside it. The label names the consequence. */

.ohl-btn--reject {
  color: rgb(var(--ohl-error));
  border-color: rgb(var(--ohl-error));
  background: rgb(var(--ohl-error-bg));
}

/* Pointed at, the quiet red commits to being red. Same pair the danger button
   is measured on, so the hover state needs no registry entry of its own. */
.ohl-btn--reject:hover {
  background: rgb(var(--ohl-error));
  border-color: rgb(var(--ohl-error));
  color: rgb(var(--ohl-surface));
}

/* -- Input width tiers — width hints by expected content length ------------
   OutOfDark's 4-tier system without the mixin machinery: a class on the
   input or select. No class = full width of the form (the default).
   Widths match OOD's guide (124 / 247 / 494 / 741 px), capped to the row. */

.ohl-form-row .ohl-input-micro { width: 124px; max-width: 100%; }  /* counts, days */
.ohl-form-row .ohl-input-small { width: 247px; max-width: 100%; }  /* codes, keys */
/* A select is as wide as the longest option needs and no wider - it has no
   text to type into it, so a field-width box around "Česky" was empty space
   pretending to be an input (Mira, 7. 9. 2026). */
.ohl-form-row select.ohl-input-small {
  width: auto;
  min-width: 180px;
  max-width: 100%;
  /* The row is a stretching column, so `width: auto` alone still filled it. */
  align-self: flex-start;
}
.ohl-form-row .ohl-input-half { width: 494px; max-width: 100%; }   /* names, titles */
.ohl-form-row .ohl-input-long { width: 741px; max-width: 100%; }   /* URLs, queries */

/* A drawer is 640px wide where a page form is 741px, so the absolute tiers land
   one step too wide inside it: "half" filled 83 % of the panel and stopped
   meaning half (Mira, 7. 9. 2026). Only this tier is restated - "long" is meant
   to fill the width, and the two narrow tiers are already narrow enough. */
.ohl-modal--drawer .ohl-form-row .ohl-input-half { width: 50%; }

/* -- Input with a unit suffix (currency, percent, days) -------------------- */

.ohl-input-suffix {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ohl-input-suffix-unit {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
  flex: none;
}

/* -- Tag pills — free-form labels, softer than status badges --------------- */

.ohl-tag {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 9999px;
  font-size: 12px;
  color: rgb(var(--ohl-ink));
  background: rgb(var(--ohl-bg));
}

.ohl-tag--ok { background: rgb(var(--ohl-success-bg)); }
.ohl-tag--warn { background: rgb(var(--ohl-warning-bg)); }
.ohl-tag--err { background: rgb(var(--ohl-error-bg)); }
.ohl-tag--accent { background: rgb(var(--ohl-accent-soft)); }

/* -- Button spinner — a running action shows motion ------------------------
   OutOfDark measured people clicking on into a dimmed "working" button:
   dimmed reads as frozen, motion reads as working. Ported with its
   reduced-motion guard. */

.ohl-btn-spinner {
  flex-shrink: 0;
  display: inline-block;
  width: 12px;
  height: 12px;
  vertical-align: -2px;
  margin-right: 6px;
  border: 2px solid rgb(var(--ohl-line));
  border-top-color: rgb(var(--ohl-muted));
  border-radius: 9999px;
  animation: ohl-spin 0.7s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .ohl-btn-spinner { animation: none; }
}

/* -- Empty-state CTA — the optional "add the first one" action ------------- */

.ohl-empty-cta {
  margin-top: 10px;
}

/* -- Modal — native <dialog>, no JS framework ------------------------------
   showModal() gives focus trap and ESC for free; the tiny open/close wiring
   and backdrop dismissal live in the shared behaviour script. */

.ohl-modal {
  border: none;
  border-radius: 10px;
  padding: 0;
  /* Unfold's CSS reset zeroes dialog margins — put the UA centering back. */
  margin: auto;
  width: calc(100% - 2rem);
  max-width: 440px;
  background: rgb(var(--ohl-surface));
  color: rgb(var(--ohl-ink));
  box-shadow: 0 10px 40px rgb(0 0 0 / 0.25);
}

/* A deep-linked drawer receives programmatic focus so its title is announced.
   Keep that invisible; keyboard focus remains visible on the controls inside. */
.ohl-modal:focus { outline: none; }

.ohl-modal::backdrop {
  background: rgb(0 0 0 / 0.4);
}

/* Wide variant for modals carrying a form. */
.ohl-modal--lg {
  max-width: 640px;
}

.ohl-modal-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 13px 18px;
  border-bottom: 1px solid rgb(var(--ohl-line));
}

.ohl-modal-hd h2 {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
}

.ohl-modal-close {
  border: none;
  background: none;
  cursor: pointer;
  color: rgb(var(--ohl-muted));
  font-size: 16px;
  line-height: 1;
  padding: 4px;
}

.ohl-modal-bd {
  padding: 18px;
}

.ohl-modal-ft {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 13px 18px;
  border-top: 1px solid rgb(var(--ohl-line));
}

/* -- Finding queue — the queue + history tabs: expandable rows --------------
   One component for both tabs (admin/console/_finding.html): a compact summary
   line that opens to the full detail — and, in the queue, the decision form.
   Native <details>, no JS (same pattern as the range picker). The head and
   every summary share one grid so the columns line up down the list. */

.ohl-queue {
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 10px;
  overflow: hidden;
}

.ohl-qhead,
.ohl-qsum {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) 190px 118px 28px;
  gap: 12px;
  align-items: center;
  padding: 10px 14px;
}

.ohl-qhead {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgb(var(--ohl-muted));
  font-weight: 600;
  background: rgb(var(--ohl-bg));
  border-bottom: 1px solid rgb(var(--ohl-line));
}

.ohl-qrow { border-bottom: 1px solid rgb(var(--ohl-line)); background: rgb(var(--ohl-surface)); }
.ohl-qrow:last-child { border-bottom: 0; }
.ohl-qrow:hover { background: rgb(var(--ohl-bg)); }
/* Open row gets a soft accent wash so the expanded one stands apart from the
   collapsed list. After :hover so it wins at equal specificity even while
   hovered. Shared by every accordion (Inbox, Web, memory). */
.ohl-qrow[open] { background: rgb(var(--ohl-accent-soft)); }

.ohl-qsum:focus-visible {
  outline: 2px solid rgb(var(--ohl-accent));
  outline-offset: -2px;
}

.ohl-qcell { min-width: 0; }

.ohl-qmain { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ohl-qtitle { font-size: 16px; font-weight: 600; line-height: 1.45; color: rgb(var(--ohl-ink)); overflow-wrap: anywhere; }
.ohl-qimpact {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ohl-qarea { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ohl-qarea-name { color: rgb(var(--ohl-muted)); font-size: 12px; font-weight: 600; }
.ohl-qsrc,
.ohl-qstate,
.ohl-qstate-note { color: rgb(var(--ohl-muted)); font-size: 12px; }
/* First-seen date, inline after the headline — small, muted, not bold. */
.ohl-qwhen {
  margin-left: 8px;
  font-size: 12px;
  font-weight: 400;
  color: rgb(var(--ohl-muted));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ohl-qchev { display: flex; justify-content: flex-end; color: rgb(var(--ohl-muted)); }
.ohl-qchev .material-symbols-outlined {
  font-size: 22px;
  transition: transform 0.15s ease;
}
.ohl-qrow[open] .ohl-qchev .material-symbols-outlined { transform: rotate(180deg); }

.ohl-qbd {
  position: relative;
  padding: 18px 20px 20px;
  border-top: 1px dashed rgb(var(--ohl-line));
}

.ohl-qbody {
  margin: 0 112px 18px 0;
  font-size: 14px;
  line-height: 1.65;
  color: rgb(var(--ohl-ink));
}
.ohl-qdecision-actions { position: absolute; top: 12px; right: 16px; }
.ohl-qdecision-actions [value="accepted"]:is(:hover, :focus-visible) { color: rgb(var(--ohl-success)); background: rgb(var(--ohl-success-bg)); }
.ohl-qdecision-actions [value="snoozed"]:is(:hover, :focus-visible) { color: rgb(var(--ohl-warning)); background: rgb(var(--ohl-warning-bg)); }
.ohl-qdecision-actions [value="rejected"]:is(:hover, :focus-visible) { color: rgb(var(--ohl-error)); background: rgb(var(--ohl-error-bg)); }
.ohl-finding-form { margin: 0; }
.ohl-qnext { max-width: 760px; margin-bottom: 16px; }
.ohl-qnext .ohl-brief-action,
.ohl-qnext .ohl-brief-steps { font-size: 14px; }
.ohl-qfooter {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 20px;
  margin-top: 14px;
}
.ohl-qevidence {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 4px 10px;
  flex: 1 1 420px;
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: rgb(var(--ohl-muted));
}
.ohl-qevidence-label { flex: none; font-weight: 600; color: rgb(var(--ohl-muted)); }
.ohl-qevidence .ohl-qlines { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0; }
.ohl-qevidence a { color: rgb(var(--ohl-accent)); }
.ohl-qevidence-detail { flex-basis: 100%; padding-left: 42px; font-size: 12px; }
.ohl-qfeedback { margin: 0 0 0 auto; }
.ohl-qfeedback[open] {
  flex: 1 0 100%;
  margin-left: 0;
  padding: 14px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 8px;
  background: rgb(var(--ohl-surface));
}
.ohl-qfeedback .ohl-form-row { margin: 12px 0 0; }
.ohl-qfeedback textarea { width: 100%; }
.ohl-qfeedback > .ohl-btn { margin-top: 12px; }
.ohl-qwatcher,
.ohl-qnote { margin: 10px 0 0; }
.ohl-qreopen { margin: 12px 0 0; }

.ohl-qfacts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  margin-top: 10px;
}
/* The full list of affected pages inside an open finding — scrollable so a
   long list does not push the rest of the page away, each row a link straight
   to the live page so a fix is one click from here. */
.ohl-pagelist {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 240px;
  overflow-y: auto;
  margin-top: 4px;
}
.ohl-pagelist a {
  font-size: 12px;
  /* Body-ink + underline, not an accent colour: the open row's accent-soft wash
     is light enough that even the full accent only reaches ~3:1 on it (measured),
     under the 4.5:1 small-text bar. Ink clears 10:1 on both themes; the underline
     is what marks it a link. (accent-ink was the original bug — near-white text
     on a light background, invisible.) */
  color: rgb(var(--ohl-ink));
  text-decoration: underline;
  overflow-wrap: anywhere;
}
.ohl-qfact-lbl {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgb(var(--ohl-muted));
  margin-bottom: 2px;
}
.ohl-qfact p { margin: 0; font-size: 14px; color: rgb(var(--ohl-ink)); }

/* Undo bar above the queue: one-click revert right after an answer. */
.ohl-undo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 14px;
  padding: 10px 14px;
  background: rgb(var(--ohl-accent-soft));
  border: 1px solid rgb(var(--ohl-accent-line));
  border-radius: 8px;
}
.ohl-undo-msg { font-size: 14px; color: rgb(var(--ohl-ink)); }
.ohl-undo form { margin: 0; }

/* Queue filter bar: quick-filter chips left, the area dropdown right. */
.ohl-queue-filters {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
}
.ohl-queue-area { display: flex; align-items: center; gap: 8px; }

/* Narrow screens: drop the column head, stack the summary into two rows. */
@media (max-width: 720px) {
  .ohl-qhead { display: none; }
  .ohl-qsum { grid-template-columns: 1fr auto; gap: 6px 10px; }
  .ohl-qprio { order: 1; }
  .ohl-qstate { order: 2; justify-self: end; }
  .ohl-qmain { order: 3; grid-column: 1 / -1; }
  .ohl-qarea { order: 4; grid-column: 1 / -1; }
  .ohl-qchev { display: none; }
}

/* Date-led cell (Historie leads with the date instead of priority). */
.ohl-qdate { font-size: 12px; color: rgb(var(--ohl-muted)); font-variant-numeric: tabular-nums; }

/* Whole-kind mute toggle — a quiet checkbox above the actions, only meaningful
   with "Neřešit" (a per-tenant "this behaviour is fine for us"). */

/* Small disclosures inside a detail (add-note / more-options): the summary
   reads as a quiet link, not a native triangle. */
.ohl-qdisc { margin-top: 10px; }
.ohl-qdisc > summary {
  font-size: 12px;
  font-weight: 600;
  color: rgb(var(--ohl-accent));
  width: fit-content;
}

/* A summary that IS a table cell's own label, not a "how this is calculated"
   affordance. It keeps the accent colour so it still reads as clickable, but
   takes the table's own size: at 12px the category names looked like a footnote
   beside the amounts they belong to (Mira, 2026-09-09). */
.ohl-qdisc--cell { margin-top: 0; }
.ohl-qdisc--cell > summary {
  font-size: 14px;
  font-weight: 400;
}

/* Card header carrying its own headline numbers on the right. The title and
   its source chip stack on the left, the numbers sit opposite them, so the two
   figures the card is about are readable without entering the table. */
.ohl-card-hd--stats {
  flex-wrap: nowrap;
  align-items: flex-start;
  justify-content: space-between;
  column-gap: 24px;
}

.ohl-card-hd--stats h2 { flex: 0 0 auto; }

.ohl-hd-titles {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.ohl-hd-stats {
  display: flex;
  gap: 32px;
  text-align: right;
  flex-shrink: 0;
}

.ohl-hd-stat-label {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
}

.ohl-hd-stat-value {
  font-size: 22px;
  font-weight: 800;
  color: rgb(var(--ohl-ink));
}

.ohl-hd-stat-sub {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
}

/* Narrow enough and the numbers drop under the title rather than squeezing it. */
@media (max-width: 720px) {
  .ohl-card-hd--stats { flex-wrap: wrap; }
  .ohl-hd-stats { text-align: left; gap: 24px; }
}

/* Pager under a list — page window + prev/next. */
.ohl-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
}
.ohl-pager-nav { display: flex; align-items: center; gap: 10px; }

/* Briefing: visible editorial, evidence and steps with quiet response controls. */
.ohl-brief-reading { max-width: 1000px; }
.ohl-brief-intro { padding: 22px 24px; margin-bottom: 28px; border-radius: 10px; background: rgb(var(--ohl-editorial-bg)); color: rgb(var(--ohl-ink)); }
.ohl-brief-intro p { margin: 0; font-size: 16px; line-height: 1.7; }
.ohl-brief-followup { margin: 12px 0 16px; line-height: 1.65; }
.ohl-brief-followup-panel { margin-top: 12px; padding: 16px 18px; border: 1px solid rgb(var(--ohl-accent-line)); border-radius: 8px; background: rgb(var(--ohl-accent-soft)); }
.ohl-brief-followup-panel .ohl-brief-followup { margin: 0; }
.ohl-brief-followup-panel .ohl-brief-steps { margin-top: 0; }
.ohl-brief-recommendations { overflow: hidden; margin-bottom: 20px; }
.ohl-brief-item { padding: 24px; scroll-margin-top: 80px; }
.ohl-brief-recommendations > .ohl-brief-item + .ohl-brief-item { border-top: 1px solid rgb(var(--ohl-line)); }
.ohl-brief-item-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
.ohl-brief-heading { margin: 0; font-size: 16px; font-weight: 600; line-height: 1.55; color: rgb(var(--ohl-ink)); }
.ohl-brief-item-actions { display: flex; flex-shrink: 0; gap: 4px; margin-left: auto; }
.ohl-brief-item-actions .material-symbols-outlined { font-size: 18px; }
.ohl-brief-item-actions [aria-pressed="true"] { color: rgb(var(--ohl-ink)); background: rgb(var(--ohl-line) / 0.5); }
.ohl-brief-item-actions [value="done"]:is(:hover, :focus-visible) { color: rgb(var(--ohl-success)); background: rgb(var(--ohl-success-bg)); }
.ohl-brief-item-actions [value="later"]:is(:hover, :focus-visible) { color: rgb(var(--ohl-warning)); background: rgb(var(--ohl-warning-bg)); }
.ohl-brief-item-actions [value="irrelevant"]:is(:hover, :focus-visible) { color: rgb(var(--ohl-error)); background: rgb(var(--ohl-error-bg)); }
.ohl-brief-body { margin: 14px 0 18px; font-size: 16px; line-height: 1.65; }
.ohl-brief-next { border-left: 2px solid rgb(var(--ohl-line)); padding-left: 16px; margin-bottom: 18px; }
.ohl-brief-next h4 { margin: 0 0 6px; font-size: 14px; font-weight: 600; color: rgb(var(--ohl-ink)); }
.ohl-brief-action { margin: 0; font-size: 16px; line-height: 1.65; }
.ohl-brief-steps { margin: 8px 0 0; padding-left: 20px; list-style: decimal; font-size: 16px; line-height: 1.65; }
.ohl-brief-steps li + li { margin-top: 6px; }
.ohl-brief-article {
  margin: 0 0 18px;
  padding: 14px 16px;
  border: 1px solid rgb(var(--ohl-accent-line));
  border-radius: 8px;
  background: rgb(var(--ohl-accent-soft));
  font-size: 14px;
  line-height: 1.6;
}
.ohl-brief-article p { margin: 4px 0; color: rgb(var(--ohl-muted)); }
.ohl-brief-article a { color: rgb(var(--ohl-accent)); font-weight: 600; }
.ohl-brief-response-form { margin-top: 14px; }
.ohl-brief-response-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.ohl-brief-response-more { margin-top: 16px; }
.ohl-brief-response-more > summary { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: rgb(var(--ohl-muted)); font-size: 14px; list-style: none; }
.ohl-brief-response-more > summary::-webkit-details-marker { display: none; }
.ohl-brief-response-more > summary .material-symbols-outlined { font-size: 16px; }
.ohl-brief-response-more[open] > summary { margin-bottom: 12px; }
.ohl-brief-response-more > .ohl-feedback-trigger {
  width: 32px;
  height: 32px;
  justify-content: center;
  border-radius: 8px;
  transition: color 0.15s ease, background 0.15s ease;
}
.ohl-brief-response-more > .ohl-feedback-trigger .material-symbols-outlined { font-size: 18px; }
.ohl-brief-response-more > .ohl-feedback-trigger:is(:hover, :focus-visible) {
  color: rgb(var(--ohl-accent));
  background: rgb(var(--ohl-accent-soft));
}
.ohl-brief-response-more .ohl-form-row { margin-bottom: 8px; }
.ohl-brief-response-more .ohl-btn { align-self: flex-start; }
.ohl-brief-item-footer { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: 12px 24px; }
.ohl-brief-evidence { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; margin: 0; font-size: 14px; line-height: 1.7; }
.ohl-brief-evidence > span:first-child { color: rgb(var(--ohl-muted)); }
.ohl-brief-evidence-links { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: 4px 14px; }
.ohl-brief-evidence a { color: rgb(var(--ohl-accent)); }
.ohl-brief-evidence a:hover { text-decoration: underline; }
.ohl-brief-evidence-links a + a::before { content: "·"; margin-right: 14px; color: rgb(var(--ohl-muted)); }
.ohl-brief-item-footer > .ohl-brief-response-more { margin: 0 0 0 auto; }
.ohl-brief-item-footer > .ohl-brief-response-more[open] { order: 3; flex-basis: 100%; margin-left: 0; padding: 16px; border: 1px solid rgb(var(--ohl-line)); border-radius: 8px; background: rgb(var(--ohl-surface)); }
.ohl-brief-decisions { background: rgb(var(--ohl-surface)); border: 1px solid rgb(var(--ohl-line)); border-radius: 8px; }
.ohl-brief-decisions > p { margin: 0; padding: 16px; border-bottom: 1px solid rgb(var(--ohl-line)); font-size: 14px; color: rgb(var(--ohl-muted)); line-height: 1.65; }
.ohl-brief-decision { scroll-margin-top: 80px; }
.ohl-brief-decision + .ohl-brief-decision { border-top: 1px solid rgb(var(--ohl-line)); }
.ohl-brief-decision > summary { display: flex; align-items: baseline; gap: 16px; padding: 16px; cursor: pointer; list-style: none; font-size: 14px; }
.ohl-brief-decision > summary::-webkit-details-marker { display: none; }
.ohl-brief-decision-state { margin-left: auto; flex-shrink: 0; color: rgb(var(--ohl-muted)); font-size: 12px; }
.ohl-brief-decision-body { padding: 0 24px 24px; }
.ohl-brief-archive-link { margin: 24px 0 0; font-size: 14px; }
@media (max-width: 600px) {
  .ohl-brief-item, .ohl-brief-intro { padding: 20px; }
  .ohl-brief-item-header { flex-wrap: wrap; gap: 8px; }
  .ohl-brief-item-header .ohl-brief-heading { flex-basis: 100%; }
  .ohl-brief-decision > summary { flex-wrap: wrap; gap: 8px; }
  .ohl-brief-item-footer { gap: 10px 16px; }
  .ohl-qbody { margin-right: 0; }
  .ohl-qdecision-actions { position: static; justify-content: flex-end; margin: -6px 0 8px auto; }
}

.ohl-brief-archive .ohl-card-bd { padding: 0; }
.ohl-brief-archive-row {
  display: grid;
  grid-template-columns: minmax(145px, 0.35fr) minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  padding: 14px 20px;
  border-top: 1px solid rgb(var(--ohl-line));
  color: rgb(var(--ohl-ink));
  text-decoration: none;
}
.ohl-brief-archive-row:first-child { border-top: 0; }
.ohl-brief-archive-row:hover { background: rgb(var(--ohl-bg)); }
.ohl-brief-archive-row:hover .ohl-brief-arw { opacity: 1; }
.ohl-brief-archive-date { color: rgb(var(--ohl-muted)); font-size: 14px; }
.ohl-brief-archive-title { min-width: 0; }
.ohl-brief-archive-title small {
  display: block;
  margin-top: 3px;
  color: rgb(var(--ohl-muted));
}

@media (max-width: 720px) {
  .ohl-brief-archive-row { grid-template-columns: 1fr auto; gap: 6px 12px; }
  .ohl-brief-archive-date { grid-column: 1; grid-row: 1; }
  .ohl-brief-archive-title { grid-column: 1; grid-row: 2; }
  .ohl-brief-archive-row .ohl-brief-arw { grid-column: 2; grid-row: 1 / span 2; }
}

/* Queue variant of the finding row: no priority/date lead column and no
   separate state column (Codex, 2026-07-18) — "Naléhavé" is a red left edge and
   the state sits inline by the source, so the name and impact get the width.
   Historie keeps the 5-column, date-led base grid. */
.ohl-qhead--queue,
.ohl-qsum--queue { grid-template-columns: minmax(0, 1fr) 240px 28px; }
.ohl-qrow--live { border-left: 3px solid transparent; }
.ohl-qrow--live.ohl-qrow--err { border-left-color: rgb(var(--ohl-error)); }

/* ==========================================================================
 * Touch targets — the phone is a real way in (audit P2-19, 2026-07-22)
 * --------------------------------------------------------------------------
 * The weekly briefing arrives by e-mail and links straight back here, so a
 * fair share of visits start on a phone. The compact controls are right at a
 * desk and wrong in a hand: a button is ~28px tall and a filter pill ~23px,
 * both well under the ~44px a fingertip reliably hits, and mobile Safari
 * zooms the whole page in on focus of any field whose text is under 16px —
 * after which the layout stays shifted.
 *
 * Two queries on purpose. ``max-width`` so narrowing a desktop window shows
 * exactly what a phone gets (and so it is verifiable without a phone);
 * ``pointer: coarse`` so a tablet or a touchscreen laptop gets it at full
 * width, where the width test alone would miss it. Nothing above this size on
 * a mouse-driven screen changes.
 * ========================================================================= */

@media (max-width: 720px), (pointer: coarse) {
  .ohl-btn,
  .ohl-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
  }

  .ohl-btn { padding: 10px 16px; font-size: 14px; }
  .ohl-chip { padding: 0 16px; font-size: 14px; }

  /* 16px is the iOS threshold, not a taste call — one pixel less and Safari
     zooms the page on focus. */
  input[type="text"],
  input[type="search"],
  input[type="email"],
  input[type="url"],
  input[type="tel"],
  input[type="password"],
  input[type="number"],
  input[type="date"],
  select,
  textarea {
    font-size: 16px;
    min-height: 44px;
  }

  .ohl-form-row textarea { min-height: 88px; }

  /* Konkurence controls (added 2026-07-30, missed by this layer until the
     mega check). The closeness dropdown was 11px text in a ~19px box, which
     Safari zooms on focus, and the ✕ that detaches a profile was a few pixels
     of padding — the smallest target in the app sitting on an action that
     cannot be undone. */
  .ohl-chip-select,
  .ohl-chip-add {
    font-size: 16px;
    min-height: 44px;
    padding: 0 12px;
  }

  .ohl-chip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    padding: 0;
    font-size: 16px;
  }
}

/* Collapsed card (audit 2026-07-25, P3-02): a whole ohl-card as native
   <details>, no JS — the summary reuses the card header, a chevron says there
   is more. Born for Nastavení → Hlídače (18 full config cards read as a wall)
   and reused for the PPC raw audit log under the grouped events. Anything that
   must not be hidden (a form error, a pager deep-link) renders with `open`. */
.ohl-wcard > summary .ohl-card-hd { border-bottom: none; padding-bottom: 24px; }
.ohl-wcard[open] > summary .ohl-card-hd { border-bottom: 1px solid rgb(var(--ohl-line)); }
.ohl-wchev {
  margin-left: auto;
  color: rgb(var(--ohl-muted));
  transition: transform 0.15s;
}
.ohl-wcard[open] .ohl-wchev { transform: rotate(180deg); }

/* -- Editable settings table (Settings › Competitors) ----------------------
   The table itself is the form: the name is typed in the cell and closeness
   is a chip-shaped select, both committed by one Save above the table. The
   inputs therefore have to read as text until touched, or every row shouts
   "form" and the list stops being scannable. */
.ohl-card-actions {
  margin-left: auto;
  display: flex;
  gap: 8px;
}
.ohl-chip-select {
  font-size: 11px;
  font-weight: 600;
  border: 0;
  border-radius: 6px;
  padding: 2px 6px;
  color: rgb(var(--ohl-muted));
  background: rgb(var(--ohl-bg));
  cursor: pointer;
}
/* Direct competition is the sharper state and says so in colour. */
.ohl-chip-select:has(option[value="direct"]:checked) {
  color: rgb(var(--ohl-accent));
  background: rgb(var(--ohl-accent-soft));
}
/* Detach ✕ inside a profile chip, and the + that opens the add dialog. */
.ohl-chip-x {
  border: 0;
  background: none;
  padding: 0 0 0 6px;
  font: inherit;
  color: inherit;
  opacity: 0.55;
  cursor: pointer;
}
.ohl-chip-x:hover { opacity: 1; }
.ohl-chip-add {
  font-size: 11px;
  font-weight: 600;
  border: 1px dashed rgb(var(--ohl-line));
  border-radius: 6px;
  padding: 1px 8px;
  background: none;
  color: rgb(var(--ohl-muted));
  cursor: pointer;
}
.ohl-chip-add:hover {
  border-style: solid;
  color: rgb(var(--ohl-accent));
}

/* Touch-sized links inside tables, on narrow screens only.
   ------------------------------------------------------------------
   The mobile sweep measured 41 links on UX and 16 on Produkty whose own box is
   smaller than 32 px in both directions — they are numbers, so a link labelled
   "5" is nine pixels wide and there is nothing to aim at with a thumb. Only the
   TAP AREA grows: the digit keeps its size, the desktop table is untouched, and
   the padding is symmetrical so a column of numbers stays aligned
   (ClickUp 869en0vbx). */
@media (max-width: 720px) {
  .ohl-table td a,
  .ohl-table th a {
    display: inline-flex;
    align-items: center;
    justify-content: inherit;
    min-width: 32px;
    min-height: 32px;
    padding: 0 6px;
    /* Cancels the padding for layout so the column keeps its width; only the
       hit area sticks out. */
    margin: 0 -6px;
  }
}

/* -- Keyboard floor: one focus ring, and a way past the sidebar ------------- */
/* Measured 2026-09-01 by walking the tab order: the Dashboard had 42 stops and
   not one of them wore our ring — four rules covered form fields, the switch
   and the finding row, and everything else fell back to whatever the browser
   draws. That default IS visible (checked with a real Tab press, not a
   programmatic focus, which does not trigger :focus-visible), but it is a 1px
   hairline in a colour the operating system picks, so the app looked focused in
   one place and borrowed in another. On the Inbox that left 69 buttons —
   including "Hotovo" and "Neřešit" — without a ring of our own.

   `:where()` keeps the specificity at zero, so any component that needs a
   different offset (a full-width row clipping an outside ring) still wins by
   simply existing, without !important. */

:where(a, button, summary, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid rgb(var(--ohl-accent));
  outline-offset: 2px;
}

/* Windows high-contrast mode replaces every colour the page asks for with the
   user's own, and it ignores box-shadow entirely — a ring drawn as a shadow
   disappears there, which is why this one is an outline. The system colour is
   what such a user chose to mean "focus", so we hand the ring over rather than
   insisting on the brand blue. */
@media (forced-colors: active) {
  :where(a, button, summary, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
    outline-color: Highlight;
  }
}

/* Skip link: the first stop on every page, invisible until it is focused.
   Not `display: none` — a hidden element is not focusable, so it would never
   be reachable at all. It is parked off-screen and comes back on focus. */
/* Present for a screen reader, absent for the eye. Used where a name has to
   exist for navigation but would be a repeated banner on screen — the
   dashboard's own title, which the logo, the sidebar and the browser tab
   already say (Mira via Codex reaudit, 6. 9. 2026). Not `display: none`,
   which would take it away from the reader too. */
.ohl-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.ohl-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 10px 16px;
  background: rgb(var(--ohl-surface));
  color: rgb(var(--ohl-accent));
  border: 1px solid rgb(var(--ohl-accent));
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
}

.ohl-skip:focus {
  left: 8px;
  top: 8px;
}

/* -- Keyboard shortcuts ----------------------------------------------------- */
/* No styles for the ⌘K window: that one is Unfold's, filled by a search
   callback, and restyling it here would fork a component we do not own.

   One exception, and it is a correction of our own doing. The global focus ring
   below lands on that window's search field too, and the window clips it top and
   bottom — the reader sees two stray vertical bars beside the field rather than
   a ring (reported 2026-09-02, measured: our accent, 2px, offset 2px). The field
   keeps no ring of its own: it is the only thing in the window to type into, it
   is focused the moment the window opens, and the text caret is the indicator —
   which is exactly the case WCAG 2.4.11 excepts. Same call, same reason, as the
   window we did not end up building. */
#search-input-command:focus-visible {
  outline: none;
}

/* Key cap — the shortcut as the reader sees it on their own keyboard. The text
   is written by script from one formatter, never typed into a template. */
.ohl-kbd {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 4px;
  background: rgb(var(--ohl-bg));
  color: rgb(var(--ohl-muted));
  font-family: var(--ohl-font-sans);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}

/* The finding the keyboard is standing on. Accent, like a chosen table row and
   for the same reason: this is where the reader put themselves, not something
   the data reported. */
.ohl-qrow.is-cursor { background: rgb(var(--ohl-accent-soft)); }

/* -- News — the release log at /admin/news/ -------------------------------- */
/* Page-scoped on purpose: these live in admin/news.html only. The block at the
   foot of the menu borrows Unfold's sidebar markup instead, so it keeps
   matching the sections above it. */

/* Shaped after OutOfDark's release log (Mira, 17. 9. 2026): date and headline
   on one line at the same size, split by a middle dot, notes parted by a short
   centred rule rather than a full-width one, and the call to action filled and
   on the right, where the eye lands after reading. */
.ohl-news-item {
  padding: 20px 0;
}

/* The rule between two notes is 75% wide and centred: a divider inside the
   card, not a card edge. */
.ohl-news-item + .ohl-news-item {
  position: relative;
}
.ohl-news-item + .ohl-news-item::before {
  content: "";
  position: absolute;
  top: 0;
  left: 12.5%;
  right: 12.5%;
  border-top: 1px solid rgb(var(--ohl-line));
}

.ohl-news-item:first-child { padding-top: 0; }
.ohl-news-item:last-child { padding-bottom: 0; }

.ohl-news-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px;
}

.ohl-news-date,
.ohl-news-sep {
  color: rgb(var(--ohl-muted));
  font-size: 18px;
  font-variant-numeric: tabular-nums;
}

.ohl-news-title {
  margin: 0;
  color: rgb(var(--ohl-ink));
  font-size: 18px;
  font-weight: 600;
}

.ohl-news-body {
  margin-top: 8px;
  color: rgb(var(--ohl-ink));
  font-size: 14px;
  line-height: 1.6;
}

.ohl-news-body p { margin: 0 0 8px; }
.ohl-news-body p:last-child { margin-bottom: 0; }
.ohl-news-body ul,
.ohl-news-body ol { margin: 0 0 8px; padding-left: 20px; list-style: disc; }
.ohl-news-body ol { list-style: decimal; }
.ohl-news-body li { margin: 2px 0; }
.ohl-news-body a { color: rgb(var(--ohl-accent)); text-decoration: underline; }

.ohl-news-cta {
  display: flex;
  justify-content: flex-end;
  margin: 12px 0 0;
}

@media (max-width: 720px) {
  /* On a phone the date takes its own line above the headline; the dot has
     nothing to sit between. */
  .ohl-news-meta { flex-direction: column; align-items: flex-start; gap: 2px; }
  .ohl-news-sep { display: none; }
  .ohl-news-date { font-size: 14px; }
  .ohl-news-title { font-size: 16px; }
}

/* -- In-app help (/admin/help/) -------------------------------------------
   One family, .ohl-help-*, across the four help templates: the topic tile on
   the landing grid, the rail beside an article, the article text, and a search
   result. It is shared vocabulary, so it owes the styleguide a rendered
   example and has one (category B of core/design_registry.py caught the first
   attempt to treat these as page-scoped).
   Everything structural around them — the card, the buttons, the empty state —
   comes from the guide unchanged.

   Help is the one screen in the app that is read rather than scanned, so it is
   also the one that sets its own type: the article runs at 16px on a 72ch
   measure, one step above the 14px the tables and panels use. Every size here
   is still on the canonical scale (Mira, 7. 9. 2026). */

/* The search box in the page header. It owns the right edge of that row the
   same way the period switcher does on a section, and it is a field with a
   glyph rather than a field plus a button: one input submits on Enter by
   itself. */
.ohl-help-search {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  flex: 0 1 300px;
  min-width: 0;
  height: 36px;
  padding: 0 12px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 8px;
  background: rgb(var(--ohl-surface));
}

.ohl-help-search:focus-within {
  border-color: rgb(var(--ohl-accent));
}

/* The magnifier is the form's submit control, drawn as a glyph. It has no box
   of its own — the field around it is the box. */
.ohl-help-search-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  padding: 0;
  border: 0;
  background: none;
  font-size: 18px;
  color: rgb(var(--ohl-muted));
  cursor: pointer;
}

.ohl-help-search-icon:hover { color: rgb(var(--ohl-accent)); }

.ohl-help-search-icon .material-symbols-outlined { font-size: 18px; }

/* Written through the wrapper on purpose. Every text field in the app takes a
   border, a radius, padding and a minimum height from the type selector up in
   the fields section, and a bare class does not outrank a type selector — so
   the input kept drawing its own box INSIDE this one and the reader saw two
   frames, one within the other (Mira, 8. 9. 2026). Here the box belongs to the
   wrapper; the input is only the text inside it. */
.ohl-help-search .ohl-help-search-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  min-height: 0;
  border: 0;
  border-radius: 0;
  padding: 0;
  background: transparent;
  font-size: 14px;
  font-weight: 400;
  color: rgb(var(--ohl-ink));
}

/* The ring belongs to the box around the field, not to the bare input inside
   it — two rings, one inside the other, is what a default outline draws here. */
.ohl-help-search .ohl-help-search-input:focus { outline: none; }

/* Same two queries as the touch section above. The minimum height it puts on
   every field would only make the input taller than the box holding it, so the
   BOX grows to the finger-sized target instead — and the text keeps the 16px
   that stops mobile Safari zooming the page on focus. */
@media (max-width: 720px), (pointer: coarse) {
  .ohl-help-search { height: 44px; }
  .ohl-help-search .ohl-help-search-input { font-size: 16px; }
}

/* The way back, over the title of an article or a result list. Quiet: it is
   orientation, and the reader who wants it already knows what it says. */
.ohl-help-crumbs {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 12px;
  font-size: 12px;
  color: rgb(var(--ohl-muted));
}

.ohl-help-crumbs a {
  color: rgb(var(--ohl-muted));
  text-decoration: none;
}

.ohl-help-crumbs a:hover {
  color: rgb(var(--ohl-accent));
  text-decoration: underline;
}

/* The number in front of a topic — on the tile, on the article's own title and
   beside a topic in the rail. One number per topic, in the three places the
   reader meets it. */
.ohl-help-num {
  color: rgb(var(--ohl-muted));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  margin-inline-end: 6px;
}

/* Three columns at most, counted rather than fitted: an `auto-fill` track laid
   five and six tiles across a wide screen and broke every summary into six
   two-word lines. The number of columns is a reading decision, not a function
   of the monitor. */
.ohl-help-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

@media (min-width: 700px) {
  .ohl-help-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1280px) {
  .ohl-help-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.ohl-help-tile {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 18px 20px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 10px;
  background: rgb(var(--ohl-bg));
  color: rgb(var(--ohl-ink));
  text-decoration: none;
}

.ohl-help-tile:hover {
  border-color: rgb(var(--ohl-accent-line));
  background: rgb(var(--ohl-accent-soft));
}

/* A disc, not a bare glyph: at three columns the icons carry the rhythm of the
   grid, and on the grey tile a bare outline read as a smudge. */
.ohl-help-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  font-size: 22px;
  color: rgb(var(--ohl-accent));
  background: rgb(var(--ohl-accent-soft));
}

.ohl-help-tile:hover .ohl-help-tile-icon {
  background: rgb(var(--ohl-surface));
}

.ohl-help-tile-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.ohl-help-tile-title {
  font-size: 16px;
  font-weight: 700;
  line-height: 1.35;
}

.ohl-help-tile-sum {
  font-size: 14px;
  line-height: 1.5;
  color: rgb(var(--ohl-muted));
}

/* The rail sits beside the article on a desktop and splits around it below
   1100px: the anchors stay on top where they are a summary of what follows,
   the fifteen other topics move under the text where they are a way on. One
   breakpoint, and `display: contents` on the wrapper is what lets the two
   halves land on either side of the article without duplicating the markup. */
.ohl-help-layout {
  display: grid;
  gap: 24px;
}

.ohl-help-rail {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

@media (max-width: 1099px) {
  .ohl-help-rail { display: contents; }
  .ohl-help-toc-anchors { order: 1; }
  .ohl-help-article { order: 2; }
  .ohl-help-toc-siblings { order: 3; }
}

@media (min-width: 1100px) {
  .ohl-help-layout {
    grid-template-columns: 232px minmax(0, 1fr);
    align-items: start;
  }

  .ohl-help-rail {
    position: sticky;
    top: 90px;
    max-height: calc(100vh - 110px);
    overflow-y: auto;
  }
}

.ohl-help-toc {
  padding: 14px 16px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 10px;
  background: rgb(var(--ohl-bg));
}

.ohl-help-toc-hd {
  display: block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: rgb(var(--ohl-muted));
  margin-bottom: 10px;
}

/* The anchors are a <details> so a phone can fold them away; on a desktop it
   simply stands open. */
.ohl-help-toc-anchors[open] > .ohl-help-toc-hd { margin-bottom: 10px; }
.ohl-help-toc-anchors > .ohl-help-toc-hd { margin-bottom: 0; }

.ohl-help-toc-list {
  list-style: disc;
  margin: 0;
  padding-left: 18px;
}

.ohl-help-toc-more {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ohl-help-toc li { margin-bottom: 2px; }

.ohl-help-toc li::marker { color: rgb(var(--ohl-muted)); }

.ohl-help-toc a {
  display: block;
  padding: 4px 0;
  font-size: 14px;
  line-height: 1.4;
  color: rgb(var(--ohl-ink));
  text-decoration: none;
}

.ohl-help-toc a:hover { color: rgb(var(--ohl-accent)); }

/* The article body is the one place in the app where raw <h2>/<p>/<ul> land
   from Markdown, so the element selectors below are scoped to it and stop
   there. */
.ohl-help-article {
  font-size: 16px;
  line-height: 1.7;
  color: rgb(var(--ohl-ink));
  max-width: 72ch;
  counter-reset: ohl-help-section;
}

/* Section numbers come from the topic's own number and a counter, never from
   the Markdown: a heading cannot then say "1.4" while the rail says "1.5". */
.ohl-help-article h2 {
  font-size: 22px;
  font-weight: 800;
  line-height: 1.3;
  margin: 40px 0 12px;
  scroll-margin-top: 90px;
  counter-increment: ohl-help-section;
  counter-reset: ohl-help-sub;
}

.ohl-help-article h2::before {
  content: counter(ohl-help-topic) "." counter(ohl-help-section) ". ";
  color: rgb(var(--ohl-muted));
}

.ohl-help-article h2:first-of-type { margin-top: 8px; }

.ohl-help-article h3 {
  font-size: 18px;
  font-weight: 700;
  line-height: 1.4;
  margin: 28px 0 8px;
  scroll-margin-top: 90px;
  counter-increment: ohl-help-sub;
}

.ohl-help-article h3::before {
  content: counter(ohl-help-topic) "." counter(ohl-help-section) "." counter(ohl-help-sub) ". ";
  color: rgb(var(--ohl-muted));
}

.ohl-help-article h4 {
  font-size: 16px;
  font-weight: 700;
  margin: 20px 0 6px;
}

.ohl-help-article p { margin: 0 0 14px; }

.ohl-help-article ul,
.ohl-help-article ol {
  margin: 0 0 14px;
  padding-left: 26px;
  list-style: revert;
}

.ohl-help-article li { margin-bottom: 7px; }

.ohl-help-article li::marker { color: rgb(var(--ohl-muted)); }

.ohl-help-article a { color: rgb(var(--ohl-accent)); }

.ohl-help-article code {
  font-size: 14px;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgb(var(--ohl-mist-100));
}

.ohl-help-article blockquote {
  margin: 0 0 14px;
  padding: 12px 16px;
  border-left: 3px solid rgb(var(--ohl-accent-line));
  border-radius: 4px;
  background: rgb(var(--ohl-accent-soft));
}

.ohl-help-article blockquote p:last-child { margin-bottom: 0; }

.ohl-help-article table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 14px;
  font-size: 14px;
}

.ohl-help-article th,
.ohl-help-article td {
  text-align: left;
  padding: 9px 12px;
  border-bottom: 1px solid rgb(var(--ohl-line));
}

.ohl-help-article hr {
  border: 0;
  border-top: 1px solid rgb(var(--ohl-line));
  margin: 28px 0;
}

/* The title carries the whole width of the text column, not just its own line:
   the rule under it is what separates the article from the chrome above and
   lets the sections below start without a second one. */
.ohl-help-title {
  margin: 0 0 24px;
  padding: 0 0 16px;
  border-bottom: 1px solid rgb(var(--ohl-line));
  font-size: 28px;
  line-height: 1.2;
  font-weight: 800;
}

.ohl-help-results {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ohl-help-result {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 16px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 10px;
  color: rgb(var(--ohl-ink));
  text-decoration: none;
}

.ohl-help-result:hover { border-color: rgb(var(--ohl-accent-line)); }

.ohl-help-result-title {
  font-size: 16px;
  font-weight: 700;
}

.ohl-help-result-snippet {
  font-size: 14px;
  line-height: 1.5;
  color: rgb(var(--ohl-muted));
}

/* The "?" beside a page title. Quiet until hovered — it is a way out of the
   screen, not part of what the screen is saying. */
.ohl-pagehead-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The glyph stays 16 px, the tap area is 32×32 (WCAG 2.2 target size) — on a
     phone the bare glyph was unhittable, and the mobile sweep reported it on 36
     of 38 screens (mega check 2026-09-02). The negative margins take that
     padding back out of the layout, so the icon still sits 3 px from the last
     letter of the heading — flush against it, the icon read as part of the
     title rather than as a way out of the screen (Mira, 2026-09-02) — and the
     header row keeps its height. The right side keeps a real 8 px so the icon
     never sticks out of whatever wrapper the header hands it. */
  min-width: 32px;
  min-height: 32px;
  margin: -8px 0 -8px -5px;
  color: rgb(var(--ohl-muted));
  text-decoration: none;
}

.ohl-pagehead-help:hover { color: rgb(var(--ohl-accent)); }

.ohl-pagehead-help .material-symbols-outlined { font-size: 16px; }

/* ==========================================================================
 * 2. TYPEFACE
 * ========================================================================== */
/* Unfold sets its own font on <body>, so the token alone never reaches the
   page — measured, not assumed. Icon ligatures keep their own family, or
   every icon renders as the word behind it. */
body, body * {
font-family: var(--ohl-font-sans);
}

/* Manrope's 400 is noticeably lighter than the stack it replaces, so at our
   14px base the page read as washed out. 500 is the weight that carries it. */
body {
font-weight: 500;
}

.material-symbols-outlined, .material-symbols-outlined * {
font-family: "Material Symbols Outlined";
}

/* ==========================================================================
 * 3. SHELL — sidebar and content well
 * ========================================================================== */
#nav-sidebar {
background: rgb(var(--ohl-bg));
  border-right: 0;
  /* Fixed, not draggable. The rail held whatever width someone had once pulled
     it to, which is a setting nobody wants to make and everybody can break
     (Mira, 5. 9. 2026). !important because the width is written inline. */
  width: 288px !important;
}

/* The drag handle goes with it. */
#nav-sidebar > [class*="cursor-col-resize"] {
display: none;
}

/* No rules in the menu. The groups are already told apart by their uppercase
   headings and the space around them, and where two groups happened to be
   empty the rules stacked up as a pair of stray lines. */
#nav-sidebar-inner hr {
display: none;
}

/* The project switcher's outline was drawn in the faintest grey in the palette
   and disappeared into the rail; it is a control and has to look like one. The
   colour is the neutral ramp's mid step held back to 60 %, which reads as an
   edge on grey without turning into a box. */
#nav-sidebar-inner [class~="border"][class*="cursor-default"] {
border-color: rgb(var(--ohl-mist-400) / 0.6);
}

#nav-sidebar-inner {
background: rgb(var(--ohl-bg));
  padding-left: 0;
  padding-right: 0;
}

/* The brand row sat on the top edge of the page: 40px tall around 28px type
   left the logotype ~9px from the top while the picker below it had 30px to
   the first group heading. The row now carries the same 30px above the
   letters as the picker has below it (Mira, 17. 9. 2026). */
#nav-sidebar-inner > div:first-child { padding-top: 20px; }

/* The appearance row inside the account menu. Three named choices wanted 294px
   in a popover the rail gives 262, so "Systemovy" hung 19px past the edge of
   the sidebar (Mira, 7. 9. 2026). Upstream's own fallback drops the words below
   a 240px container, which this menu never reaches - it overflowed at full size
   instead of falling back, and dropping the words is the wrong trade anyway:
   naming the three choices is why they are there.

   Shrinking the row is what fits it. Same three words, one step down the type
   scale, tighter icons and padding - 250px measured, with room left for a
   longer word in another language. */
#ohl-account-menu [role="radiogroup"] {
  gap: 2px;
}

#ohl-account-menu [role="radiogroup"] > button {
  gap: 6px;
  padding-left: 6px;
  padding-right: 6px;
  font-size: 12px;
}

#ohl-account-menu [role="radiogroup"] .material-symbols-outlined {
  font-size: 18px;
  width: 18px;
}

/* Everything below is scoped to the scrolling menu list. The rail also holds
   the brand row, the project picker and the account row, and those are not
   menu items — an unscoped rule reached the unread-count badge and the
   account name and mangled both. */
/* The demo's section labels: small, loud, above the items they head. The gap
   above one used to be 26px, which on a 900px-tall rail meant three headings
   spent 78px separating four groups nobody had trouble telling apart — and the
   menu still did not fit on a large monitor (Mira, 6. 9. 2026). 14px is enough
   to read as a break; below ~10px the heading starts to belong to the item
   above it instead. */
#nav-sidebar-inner h2, #nav-sidebar-inner [class*="overflow-auto"] [class*="select-none"][class*="text-sm"]:not(a) {
margin: 0 32px;
  padding: 14px 0 6px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: rgb(var(--ohl-star-nav-heading));
}

/* Items run the full width of the rail so the active one can be a white
   panel rather than a pill floating inside a gutter. */
/* The reference keeps its menu quiet: items in a medium weight and neutral
   grey, thin outline icons, and colour reserved for the one you are on. Ours
   had every item bold with an indigo icon, so the current page had nothing
   left to stand out with. */
#nav-sidebar-inner [class*="overflow-auto"] a[class*="h-["] {
height: auto;
  /* 48px per row became 42px (6. 9. 2026) and then 34px (17. 9. 2026), both
     Mira's call: seventeen rows at 42px ran to 934px and scrolled on a 900px
     screen. The row keeps the full 22px icon plus 6px of air above and below
     — a 32px target with a 1px margin either side — so the saving is still the
     slack, not the target. Below this the icons start to touch. */
  margin: 1px 12px;
  padding: 5px 20px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  color: rgb(var(--ohl-star-nav));
}

#nav-sidebar-inner [class*="overflow-auto"] a[class*="h-["] .material-symbols-outlined {
width: 22px;
  margin-right: 16px;
  font-size: 22px;
  color: rgb(var(--ohl-star-nav));
  /* Material Symbols is a variable font; the lighter axis matches the hairline
     icons of the reference instead of the default chunky stroke. */
  font-variation-settings: "wght" 300;
}

#nav-sidebar-inner [class*="overflow-auto"] ol {
padding-left: 0;
  padding-right: 0;
}

/* The reference lifts the whole open section onto a white rounded card that
   floats on the sidebar's grey, and marks the current item inside it with
   colour alone. Ours has no expandable groups, so the card goes around the
   group that contains the current page. */
/* Flush with the left edge of the page and rounded only on the right, the way
   the reference draws it (Mira, 6. 9. 2026). Inset on both sides the panel
   read as a floating chip that someone had cropped; running off the edge it
   reads as the open section of the rail, which is what it is.

   Anchored to the list's id, not to "the scroller": since the News wrapper of
   10. 9. 2026 there are two elements whose class says overflow-auto, and the
   old selector matched the list itself as a "group with the active page" —
   the card went around all three groups instead of the one (Mira,
   17. 9. 2026). */
#nav-sidebar-apps > div:has(a.active) {
background: rgb(var(--ohl-surface));
  border-radius: 0 10px 10px 0;
  margin: 0 12px 6px 0;
  padding: 4px 0 10px;
}

#nav-sidebar-apps > div:has(a.active) h2 {
margin-left: 20px;
  margin-right: 20px;
}

/* The current page is the one loud thing in the rail: full colour, the
   heaviest weight in the menu, and a soft block behind it. Colour and weight
   alone left the active row hard to pick out of twelve — and impossible for
   anyone who does not separate this blue from the ink beside it (Codex audit,
   5. 9. 2026, N18). The block is what the eye finds; the colour confirms it. */
#nav-sidebar-inner [class*="overflow-auto"] a[class*="h-["].active {
background: rgb(var(--ohl-accent-soft));
  color: rgb(var(--ohl-accent));
  font-weight: 800;
}

#nav-sidebar-inner [class*="overflow-auto"] a[class*="h-["].active .material-symbols-outlined {
color: rgb(var(--ohl-accent));
  font-variation-settings: "wght" 500;
}

/* The group's own items sit inside the card, so they lose the outer gutter the
   items on the grey still need. */
#nav-sidebar-apps > div:has(a.active) a[class*="h-["] {
margin-left: 10px;
  margin-right: 10px;
  border-radius: 6px;
}

#nav-sidebar-inner a.active .material-symbols-outlined {
color: rgb(var(--ohl-accent));
}

#nav-sidebar-inner [class*="overflow-auto"] a[class*="h-["]:hover, #nav-sidebar-inner [class*="overflow-auto"] a[class*="h-["]:hover .material-symbols-outlined {
color: rgb(var(--ohl-accent));
}

/* The unread counter keeps Unfold's own pill — the theme's badge shape is for
   content badges, and forcing it here made a two-digit number wrap. */
#nav-sidebar-inner .sidebar-badge {
margin: 0 0 0 8px;
  padding: 0 4px;
  font-size: 11px;
}

/* Hidden while the rail is on screen — see the comment in welcomemsg.html for
   why this is keyed to a measured mark rather than to a width. Default is
   visible so a failure cannot strand anyone without a menu.

   The width test is NOT the condition, it is a floor. Below 1280px the rail is
   an overlay that Unfold reopens and recloses, and hiding the button there made
   the header change height at the moment the rail opened — which resized the
   document, which fired Unfold's own window-resize handler, which closes the
   rail under 1280. The first tap opened the menu and shut it again in the same
   frame (Codex audit, 6. 9. 2026, Q01; my regression, live for an hour). Below
   that width the button simply always stays, so opening the rail changes no
   layout and there is no loop to start. */
@media (min-width: 1280px) {
  body.ohl-rail-open .ohl-railbtn {
    display: none;
  }
}

/* The row is as tall as whatever is left in it — the menu button when the rail
   is collapsed, nothing at all when it stands open — so the bar disappears on
   its own instead of being switched off at a width. No :has(), no breakpoint,
   nothing to keep in step with the rail's own rules. */
.ohl-header--bare > div {
  height: auto;
  min-height: 0;
  padding-top: 0;
  padding-bottom: 0;
}

/* Every gap between two blocks of the hub is the same 24px (Mira, 6. 9. 2026):
   above the finance cards, between them and the chart row, and between that
   row and the tile grid. The chart row's own 24px margin already IS that gap,
   so the heading adds none of its own — the extra 24px it used to carry
   doubled the space and the grid sat 85px under the chart while the chart sat
   24px under the money row (Mira, 17. 9. 2026). What the heading holds now is
   only the period label, which belongs to the tiles: 8px above them, not the
   20px a card body keeps under a real title. */
.ohl-card--plain > .ohl-card-hd { padding-top: 0; }
.ohl-card--plain > .ohl-card-bd { padding-top: 8px; }

/* Content sits further from the rail than from the right edge — the asymmetry
   is what gives the page its unhurried feel on a wide screen.

   The header band gets the SAME horizontal inset, which is why the two live in
   one rule. They are siblings sharing an outer gutter, but only the column
   carried this inset, so every page title started 40px left of the page it
   names and the H1 looked like it had slipped out of the layout (Mira,
   7. 9. 2026). Change one side here and the other moves with it. */
#content,
#header-inner {
  padding-left: 56px;
  padding-right: 35px;
}

#content {
  padding-top: 24px;
  padding-bottom: 32px;
}

/* On a phone the same gutters ate 91px of a 358px column, so the first chart
   had 239px to live in. The outer layout already supplies 16px; the shell adds
   nothing horizontal below the tablet breakpoint. */
@media (max-width: 720px) {
  #content,
  #header-inner {
    padding-left: 0;
    padding-right: 0;
  }

  #content {
    padding-top: 16px;
    padding-bottom: 24px;
  }
}

body {
background: rgb(var(--ohl-bg));
}

/* Unfold wraps the whole content column in a hard white panel, so the page's
   grey only ever showed under the sidebar and every card sat white on white.
   The column steps back to the ground; the cards on it are the white. The
   header band above it is the same panel and goes the same way — in the
   reference the page title sits directly on the grey. */
#page > div[class~="bg-white"], #page div[class~="bg-white"][class*="z-40"] {
background: transparent;
}

/* ==========================================================================
 * 6b. HIGHLIGHT CARD — the demo's one filled indigo panel with white type.
 * ========================================================================== */
/* A tint, not a slab. Filled with the accent at full strength it was the
   loudest thing on the page and the eye went nowhere else (Mira, 5. 9. 2026).
   The pale wash still sets the block apart from the white cards around it, and
   a left edge in the accent keeps it from reading as just another card. */
.ohl-card--highlight {
background: rgb(var(--ohl-accent-soft));
  border-left: 3px solid rgb(var(--ohl-accent));
}

.ohl-card--highlight .ohl-card-hd h2, .ohl-card--highlight .ohl-card-hd h2 a {
color: rgb(var(--ohl-accent));
}

.ohl-card--highlight .ohl-brief-row:hover {
background: rgb(var(--ohl-surface) / 0.6);
}

/* The briefing's dates sit in the corner opposite its title instead of on a
   line of their own beneath it (Mira, 7. 9. 2026). The card is the widest
   block on the hub, so the space was already there - card titles take the
   whole line by default precisely because most captions are longer than this
   one and would squash the title. */
.ohl-card--highlight > .ohl-card-hd h2 {
  flex: 0 1 auto;
}

.ohl-card--highlight > .ohl-card-hd .ohl-card-sub {
  margin-left: auto;
  text-align: right;
}

@media (max-width: 720px) {
  /* On a phone the two do not share a line; a date pinned to the right edge of
     its own row would just look lost. */
  .ohl-card--highlight > .ohl-card-hd .ohl-card-sub {
    margin-left: 0;
    text-align: left;
  }
}

.ohl-table td strong, .ohl-table td b {
color: rgb(var(--ohl-ink));
  font-weight: 700;
}

/* ==========================================================================
 * 10. PAGE HEADER — the demo gives the page title its own generous band
 *     (28px, the header standing well clear of the first card).
 * ========================================================================== */
h1[class*="text-important"] {
font-size: 28px;
  font-weight: 700;
  /* The title element is a flex box whose whole ancestor chain is 20px tall,
     sized for the old 16px heading; at 28px the letters were cut off at the
     top. The line box and the rows around it have to grow with it. */
  line-height: 1.5;
  overflow: visible;
  min-height: 50px;
}

/* THIS is what was cutting the title, and it took a while to find: the text is
   not a child of the h1, it sits in a span that carries a 20px line box and
   clips whatever overflows it (the utility that gives long titles their
   ellipsis). At 16px nothing overflowed; at 28px the descenders were sliced
   off at the baseline. The span keeps its clipping — that is what truncates a
   long name — but its line box now fits the type it holds. */
h1[class*="text-important"] > span {
line-height: 1.5;
}

header .container [class*="items-center"] {
  min-height: 50px;
  overflow: visible;
}

/* On a phone the header row wraps rather than pushing the period switcher off
   the side of the document. */
@media (max-width: 720px) {
  header .container [class*="items-center"] {
    flex-wrap: wrap;
    row-gap: 8px;
  }

  h1[class*="text-important"],
  h1[class*="text-important"] > span {
    min-width: 0;
  }

  /* The row unfold wraps the title in has no class of ours to hang a rule on,
     so it is addressed by what it contains. It does not wrap, and the period
     switcher inside it must not shrink, so on a long section name the switcher
     was pushed past the right edge of the document (Codex audit, 5. 9. 2026,
     N02). On a phone the switcher gets its own line instead. */
  /* Measured on 6. 9. 2026: this never matched. The row's ancestry is not
     `header .container` on every screen, so the rule sat there looking
     correct while the row stayed nowrap and the title was clipped to
     "Návš…" (R11). Matched by what it CONTAINS instead — there is exactly
     one page header per page, so the bare `div` is precise in effect. */
  div:has(> .ohl-pagehead) {
    flex-wrap: wrap;
    row-gap: 8px;
  }

  /* Once the row wraps, the title owns line one and must not be squeezed by
     anything that used to share it. */
  h1[class*="text-important"] {
    flex-shrink: 0;
    max-width: 100%;
  }

  /* The header owns its own row on a phone, and inside it the period comes
     first with the freshness under it. Sharing the title's line left
     Návštěvnost 84,6 px for a 28px heading — the page was called "Návš…"
     while the switcher beside it had 177,9 px (Codex reaudit, 6. 9. 2026,
     R11). Everything about the phone header is in THIS block; a second copy
     lower in the file is how one of the two silently stops applying. */
  .ohl-pagehead {
    margin-left: 0;
    flex-basis: 100%;
    flex-wrap: wrap;
    row-gap: 6px;
  }

  .ohl-pagehead-srcs {
    order: 2;
    flex-basis: 100%;
  }

  .ohl-pagehead > .ohl-range {
    order: 1;
    margin-left: 0;
  }
}

/* The utility class on the header pins it to 72px, which a 28px title fills
   edge to edge; the band grows with the title instead. */
header .container[class*="h-["], header > div[class*="h-["] {
height: auto;
  min-height: 96px;
  padding: 30px 0 25px;
}

/* ==========================================================================
 * 11. GROUPING — in the reference every block is its own white card on the
 *     grey ground. A card that only wraps a grid of cards would put white on
 *     white and swallow the ones inside it, so it steps back to the ground.
 * ========================================================================== */
/* Marked in the template, not guessed from what the card contains: the
   styleguide shows the areas grid as a demo INSIDE a real card, and a
   content-based rule stripped that card's background too. */
.ohl-card--plain {
background: transparent;
}

.ohl-card--plain > .ohl-card-hd, .ohl-card--plain > .ohl-card-bd {
padding-left: 0;
  padding-right: 0;
}

/* The arrow is drawn from --rise / --fall, never from --up / --down. Those two
   mean GOOD and BAD (rising PNO is a --down), so an arrow taken from them
   printed "down +4,3 p. b." and said the opposite of the figure beside it.
   Colour keeps carrying good/bad, the arrow carries direction, and neither
   lies. A metric that did not move gets no arrow at all. */
.ohl-delta--rise::before, .ohl-delta--fall::before {
margin-right: 2px;
  font-size: 11px;
  font-weight: 700;
  vertical-align: 0.5px;
}

.ohl-delta--rise::before {
content: "\2191";
}

.ohl-delta--fall::before {
content: "\2193";
}

/* ==========================================================================
 * 13. CHART FURNITURE
 * ========================================================================== */
/* The band under the line is decoration; the hover targets sit above it. */
.ohl-chart-area {
pointer-events: none;
}


/* ==========================================================================
 * 15. REVIEWS TILE — score in the headline, portal beside it in body type.
 * ========================================================================== */
/* Exactly the caption size the tiles use underneath their numbers ("tržby
   mailingu", "PNO dle Zboží.cz"), so the portal reads as the label it is. */
.ohl-area-line-name {
margin-left: 8px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0;
  color: rgb(var(--ohl-muted));
}

.ohl-area-line-scale {
margin-left: 3px;
  font-size: 16px;
  font-weight: 600;
  color: rgb(var(--ohl-muted));
}

/* Settings collections share the native modal shell, presented as a drawer.
 * Source tiles identify services; watcher rows prioritise the rule condition. */
.ohl-settings { container-type: inline-size; }
.ohl-settings > .ohl-card-hd { display: none; }

.ohl-source-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.ohl-source-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  min-width: 0;
  padding: 20px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 10px;
  background: rgb(var(--ohl-surface));
  color: rgb(var(--ohl-ink));
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color .15s, box-shadow .15s;
}
.ohl-source-tile:hover {
  border-color: rgb(var(--ohl-accent));
  box-shadow: 0 3px 12px rgb(var(--ohl-ink) / .06);
}
.ohl-source-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 8px;
  background: rgb(var(--ohl-mist-0));
  color: rgb(var(--ohl-mist-700));
}
.ohl-source-logo img { width: 32px; height: 32px; object-fit: contain; }
.ohl-source-logo .material-symbols-outlined { font-size: 22px; }
.ohl-source-name { min-height: 42px; font-size: 14px; line-height: 1.5; font-weight: 700; }
.ohl-source-tile .ohl-badge { white-space: normal; text-align: left; }
.ohl-source-tile .ohl-badge .material-symbols-outlined,
.ohl-modal--drawer .ohl-badge .material-symbols-outlined,
.ohl-watch-states .material-symbols-outlined { font-size: 14px; margin-right: 4px; vertical-align: -2px; }
.ohl-source-heading { display: flex; align-items: center; gap: 12px; min-width: 0; }
.ohl-source-header-main {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 12px;
  flex: 1;
  min-width: 0;
}
.ohl-source-header-main > .ohl-badge { flex: 0 0 auto; }

.ohl-watch-group { margin-bottom: 26px; }
.ohl-watch-group-title { display: flex; align-items: center; gap: 10px; margin: 0 0 12px; font-size: 14px; font-weight: 600; }
.ohl-watch-group-title .ohl-src { padding: 6px; }
.ohl-watch-group-title .material-symbols-outlined { font-size: 16px; }
.ohl-watch-list { border: 1px solid rgb(var(--ohl-line)); border-radius: 10px; overflow: hidden; background: rgb(var(--ohl-surface)); }
.ohl-watch-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 22px;
  gap: 20px;
  align-items: center;
  width: 100%;
  padding: 22px 24px;
  background: rgb(var(--ohl-surface));
  border: 0;
  border-bottom: 1px solid rgb(var(--ohl-line));
  color: rgb(var(--ohl-ink));
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ohl-watch-row:last-child { border-bottom: 0; }
.ohl-watch-row:hover { background: rgb(var(--ohl-accent-soft)); }
.ohl-watch-row:focus-visible { outline-offset: -3px; }
.ohl-watch-copy { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ohl-watch-copy strong { font-size: 16px; font-weight: 600; line-height: 1.5; }
.ohl-watch-copy > span { font-size: 14px; color: rgb(var(--ohl-muted)); line-height: 1.6; }
.ohl-watch-states { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }
.ohl-watch-arrow { color: rgb(var(--ohl-muted)); font-size: 22px; }

.ohl-modal--drawer {
  width: min(100%, 640px);
  max-width: none;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0 0 0 auto;
  border-radius: 0;
}
.ohl-modal--drawer[open] { display: flex; flex-direction: column; }
.ohl-modal--drawer .ohl-modal-hd { padding: 24px; flex-shrink: 0; }
.ohl-modal--drawer .ohl-modal-hd h2 { font-size: 22px; line-height: 1.4; }
.ohl-modal--drawer .ohl-modal-close { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; padding: 0; }
.ohl-modal--drawer .ohl-modal-bd { flex: 1; min-height: 0; overflow-y: auto; padding: 24px; }
.ohl-modal--drawer .ohl-modal-ft { flex-shrink: 0; padding: 18px 24px; }
.ohl-modal--drawer .ohl-form { max-width: none; margin: 24px 0 0; }
/* One readable, unboxed description directly beneath its block heading.
   Field-specific detail lives in question-mark help, not in a second note. */
.ohl-description { margin: 8px 0 24px; max-width: 80ch; color: rgb(var(--ohl-muted)); font-size: 14px; line-height: 1.75; }
.ohl-tabs-content > .ohl-description:first-child { margin-top: 0; }
.ohl-source-description { margin: 0 0 28px; color: rgb(var(--ohl-muted)); font-size: 14px; line-height: 1.65; }
.ohl-settings-section { margin-top: 32px; }
.ohl-settings-section--first { margin-top: 0; }
.ohl-settings-section h3 { font-size: 16px; line-height: 1.5; font-weight: 700; margin: 0; }
.ohl-settings-section .ohl-form { margin-top: 16px; }
.ohl-settings-section .ohl-settings-facts { margin-top: 14px; }
.ohl-description + .ohl-form { margin-top: 0; }
.ohl-settings-facts { display: grid; gap: 10px; margin: 24px 0 0; }
.ohl-settings-facts > div { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding: 0; font-size: 14px; line-height: 1.6; }
.ohl-settings-facts dt { color: rgb(var(--ohl-muted)); }
.ohl-settings-facts dd { text-align: right; font-weight: 600; margin: 0; }
.ohl-source-message { margin: 10px 0 0; color: rgb(var(--ohl-muted)); font-size: 14px; line-height: 1.65; }
.ohl-source-consent > p { margin: 0 0 14px; color: rgb(var(--ohl-muted)); font-size: 14px; line-height: 1.65; }
.ohl-source-alternative { display: inline-block; margin-top: 14px; color: rgb(var(--ohl-ink)); font-size: 14px; text-decoration: underline; }
.ohl-source-separator { margin: 0 0 18px; color: rgb(var(--ohl-muted)); font-size: 14px; font-weight: 600; }
.ohl-source-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 16px; }
.ohl-source-actions form { margin: 0; }
.ohl-modal-ft--source { justify-content: space-between; }
.ohl-modal-ft-access,
.ohl-modal-ft-main { display: flex; align-items: center; gap: 8px; }
.ohl-modal-ft-access form { margin: 0; }
.ohl-source-access-link {
  color: rgb(var(--ohl-ink));
  font-size: 14px;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}
.ohl-source-key-request { margin-top: 20px; }
.ohl-source-key-request > p { margin: 0; color: rgb(var(--ohl-muted)); font-size: 14px; line-height: 1.65; }
.ohl-source-key-request .ohl-source-actions { margin-top: 12px; }
.ohl-source-copy-status:not(:empty) { margin-top: 10px; color: rgb(var(--ohl-success)); }
.ohl-settings-fields { border: 0; padding: 0; margin: 0; min-width: 0; }
.ohl-settings-fields > .ohl-form-grid { margin-top: 12px; }
.ohl-settings-parameters { display: grid; grid-template-columns: 1fr 1fr; gap: 24px 16px; margin: 24px 0; }
.ohl-settings-parameters .ohl-form-row { margin: 0; min-width: 0; }
.ohl-settings-parameters input[type="number"] { width: 100%; }
/* A native disclosure, so the help still works in a disabled fieldset and
   without JavaScript. `behaviors.js` adds opening on hover on top of it. */
.ohl-field-heading { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ohl-field-heading > label { margin: 0; min-width: 0; }
.ohl-field-help { flex: 0 0 auto; }
.ohl-field-help > summary {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; margin: 0;
  border-radius: 6px; color: rgb(var(--ohl-muted)); cursor: pointer;
  list-style: none;
}
.ohl-field-help > summary::-webkit-details-marker { display: none; }
/* Unfold adds its own fieldset chevron; the question mark is the only icon. */
.ohl-field-help > summary::after { content: none; }
.ohl-field-help > summary .material-symbols-outlined { font-size: 16px; }
.ohl-field-help > summary:hover,
.ohl-field-help[open] > summary { margin-bottom: 0; color: rgb(var(--ohl-accent)); background: rgb(var(--ohl-accent-soft)); }
/* The answer opens ABOVE ITS OWN ROW, always, and spans that row's width.
   Three placements were tried in one afternoon and the third is the one that
   matters: over the field (it covered the control it explained), beside the
   question mark (form rows are stacked, so a bubble taller than one label line
   lands on a neighbouring field) and beside the whole row (fine on a wide card,
   off the edge of the screen in a side panel). Every one of them put the answer
   somewhere else depending on where the row happened to sit, and that was the
   real complaint: it has to be in the same place every time (Mira, 7. 9. 2026).

   Above its own row is the one placement that does not move: it needs no width
   test and cannot escape sideways, because it starts where the row starts.

   It floats over whatever is above it, which is what a tooltip does - but it
   must never be mistaken for the CONTENT of a field, which is exactly how the
   first version failed. So it is narrower than the form, lifted off the page
   by a real shadow, and sits on the tinted ground rather than on the white a
   field uses. */
.ohl-field-help-body {
  position: absolute; z-index: 30;
  left: 0; bottom: calc(100% + 8px);
  width: min(340px, 100%);
  padding: 12px 14px; max-height: min(320px, 40vh); overflow: auto;
  font-size: 14px; font-weight: 400; line-height: 1.7; overflow-wrap: anywhere;
  color: rgb(var(--ohl-ink)); background: rgb(var(--ohl-bg));
  border: 1px solid rgb(var(--ohl-line)); border-radius: 8px;
  box-shadow: 0 8px 24px rgb(var(--ohl-ink) / 0.16);
}

/* No tick: a row has at most one question mark, so above the row IS above that
   mark. The mark also takes the accent colour while its answer is open, which
   is what pairs the two. */
.ohl-form-row { position: relative; }
.ohl-form-faq { margin-top: 24px; padding: 20px; background: rgb(var(--ohl-bg)); border-radius: 8px; }
details.ohl-form-faq > summary { cursor: pointer; font-size: 14px; font-weight: 600; color: rgb(var(--ohl-ink)); }
details.ohl-form-faq[open] > summary { margin-bottom: 16px; }
details.ohl-form-faq .ohl-form-faq { padding: 0; }
.ohl-form-faq-title { display: flex; align-items: center; gap: 8px; margin: 0 0 16px; font-size: 14px; font-weight: 600; color: rgb(var(--ohl-muted)); }
.ohl-form-faq-title .material-symbols-outlined { font-size: 16px; }
.ohl-form-faq dl { margin: 0; font-size: 14px; line-height: 1.7; }
.ohl-form-faq dt { margin: 0 0 6px; font-weight: 600; color: rgb(var(--ohl-ink)); }
.ohl-form-faq dd { margin: 0; color: rgb(var(--ohl-muted)); }
.ohl-form-faq dd + dt { margin-top: 18px; padding-top: 18px; border-top: 1px solid rgb(var(--ohl-line)); }


@container (min-width: 700px) { .ohl-source-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@container (min-width: 1100px) { .ohl-source-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
@container (max-width: 600px) {
  .ohl-watch-row { grid-template-columns: minmax(0, 1fr) 22px; padding: 18px; gap: 12px; }
  .ohl-watch-states { grid-column: 1; grid-row: 2; justify-content: flex-start; }
  .ohl-watch-arrow { grid-column: 2; grid-row: 1 / span 2; }
  .ohl-source-grid { gap: 12px; }
  .ohl-source-tile { padding: 16px; }
}
@container (max-width: 300px) { .ohl-source-grid { grid-template-columns: 1fr; } }
@media (max-width: 540px) {
  .ohl-settings-parameters { grid-template-columns: 1fr; }
  .ohl-modal--drawer .ohl-modal-hd,
  .ohl-modal--drawer .ohl-modal-bd { padding: 20px; }
  .ohl-modal-ft--source { align-items: stretch; flex-direction: column; }
  .ohl-modal-ft-access,
  .ohl-modal-ft-main { justify-content: flex-end; flex-wrap: wrap; }
  .ohl-settings-facts > div { grid-template-columns: minmax(0, 1fr) auto; gap: 12px; }
}
@media (pointer: coarse) { .ohl-field-help > summary { width: 44px; height: 44px; } }
@media (prefers-reduced-motion: reduce) { .ohl-source-tile { transition: none; } }

/* Related form fields share one row and keep readable labels at narrow widths. */
.ohl-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.ohl-form-grid .ohl-form-row { min-width: 0; }
.ohl-form-grid select { width: 100%; }
.ohl-qdisc > .ohl-form-grid { margin-top: 16px; }

/* The same campaign table serves the overview, catalog and activity detail.
   On a phone its real columns become labelled cells under the campaign name. */
.ohl-table--campaigns { width: 100%; }
.ohl-table--campaigns td:first-child { overflow-wrap: anywhere; }
.ohl-table--campaigns .ohl-num { white-space: nowrap; }
@media (max-width: 640px) {
  .ohl-form-grid { grid-template-columns: minmax(0, 1fr); }
  .ohl-table--campaigns thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .ohl-table--campaigns tbody { display: block; }
  .ohl-table--campaigns tr { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); padding: 16px 0; gap: 12px; border-bottom: 1px solid rgb(var(--ohl-line)); }
  .ohl-table--campaigns td { display: block; padding: 0; border: 0; text-align: left; min-width: 0; }
  .ohl-table--campaigns td:first-child, .ohl-table--campaigns td:nth-child(6) { grid-column: 1 / -1; }
  .ohl-table--campaigns td[data-label]::before { content: attr(data-label); display: block; font-size: 12px; color: rgb(var(--ohl-muted)); margin-bottom: 4px; }
  .ohl-table--campaigns tr:last-child { border-bottom: 0; }
}
/* Report filters: checked options stay open until the person finishes choosing. */
.ohl-report-filters { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; }
.ohl-report-filters :is(select, input[type="search"], summary) { min-height: 36px; }
.ohl-report-filters input[type="search"] { width: 260px; max-width: 100%; }
.ohl-multiselect { position: relative; font-size: 14px; }
.ohl-multiselect > summary { display: flex; align-items: center; justify-content: space-between; gap: 20px; cursor: pointer; border: 1px solid rgb(var(--ohl-line)); border-radius: 6px; padding: 6px 12px; background: rgb(var(--ohl-surface)); color: rgb(var(--ohl-ink)); list-style: none; }
.ohl-multiselect > summary::marker { content: ""; }
.ohl-multiselect > summary::-webkit-details-marker { display: none; }
.ohl-multiselect > summary::after { content: ""; flex: none; width: 7px; height: 7px; margin-top: -4px; border: solid currentColor; border-width: 0 2px 2px 0; transform: rotate(45deg); }
.ohl-multiselect[open] > summary::after { margin-top: 4px; transform: rotate(225deg); }
.ohl-multiselect-options { position: absolute; z-index: 20; top: 100%; left: 0; min-width: 220px; max-height: 320px; overflow-y: auto; padding: 12px; background: rgb(var(--ohl-surface)); color: rgb(var(--ohl-ink)); border: 1px solid rgb(var(--ohl-line)); border-radius: 6px; }
.ohl-multiselect-options label { display: flex; align-items: center; gap: 8px; padding: 6px 0; white-space: nowrap; }
@media (max-width: 600px) {
  .ohl-report-filters { position: relative; }
  .ohl-report-filters input[type="search"] { margin-left: 0; width: 100%; }
  .ohl-multiselect { position: static; }
  .ohl-multiselect-options { top: auto; left: 0; right: 0; min-width: 0; }
  .ohl-multiselect-options label { white-space: normal; }
}
@media (max-width: 720px), (pointer: coarse) {
  .ohl-report-filters :is(select, input[type="search"], summary) { min-height: 44px; }
}

/* ==========================================================================
 * LEFT RAIL — the menu and News scroll as one
 *
 * Unfold's own app list is the scroller (`h-0 grow overflow-auto`), so
 * anything placed after it in the rail gets pushed to the bottom of a tall
 * window: News ended up pinned above the account block, reading as a footer
 * rather than as the section it is. The wrapper in
 * templates/unfold/helpers/navigation.html is the scroller now, and the list
 * inside it goes back to its natural height so the two sit together and the
 * spare space falls below them (Mira, 10. 9. 2026).
 *
 * Written here rather than by editing Unfold's template: app_list.html is
 * theirs and stays theirs, so an upgrade replaces it without taking this with
 * it.
 * ========================================================================== */
.ohl-rail-scroll > #nav-sidebar-apps {
  height: auto;
  flex: 0 0 auto;
  overflow: visible;
}

/* News is a different kind of block from the menu groups above it — headlines,
   not places — so it stands a little further off than the groups stand from
   each other: 22px on top of the last group's own 6px, where the groups' gap
   is 6px (Mira, 17. 9. 2026). Written here because Unfold's prebuilt
   stylesheet carries only the utilities its own markup uses — a Tailwind
   margin class in our template resolves to nothing. */
.ohl-rail-news { margin-top: 22px; }

/* -- Section preview — the shape of a screen whose sources are not connected
   yet (console/preview.py, _preview.html). Replaces a lone sentence that told
   the reader nothing about what they would get. The art half is decoration:
   the blur is what makes it unreadable, so nothing here may be relied on to
   carry meaning. -------------------------------------------------------- */

.ohl-preview {
  position: relative;
  isolation: isolate;
  /* Cropped to roughly a screenful, so the call to action lands in the middle
     of what the reader can SEE. Left at its natural height, a section with
     three cards ran past the fold on a phone and the invitation to connect a
     source waited below it, under a screen of blurred shapes. */
  max-height: 74vh;
  overflow: hidden;
}

.ohl-preview-art {
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* Faded, not blurred. What gets blurred is decided one rule below, and the
     line it follows is the whole point of the preview: everything TRUE about
     this screen stays sharp (the card headings, the tile captions, the column
     names — they are the section's own, and they are the promise), while every
     stand-in for a figure is blurred. A reader can therefore read what they
     would get and cannot read a single number, because there is none. */
  opacity: 0.75;
  pointer-events: none;
  user-select: none;
  /* Fades into the page instead of stopping mid-card. The crop above has to
     cut somewhere, and a hard edge through a half-drawn table reads as a bug
     rather than as the edge of an illustration. */
  -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0) 68%, rgb(0 0 0 / 0) 100%);
  mask-image: linear-gradient(to bottom, rgb(0 0 0) 68%, rgb(0 0 0 / 0) 100%);
}

/* The invented half. Blur is belt and braces — these are bars, not glyphs, so
   there is nothing to read even sharp; it is what makes "this is not your
   data" legible at a glance across the whole screen. */
.ohl-preview-bar,
.ohl-preview-column {
  filter: blur(3px);
}

.ohl-preview-card {
  /* The art is a backdrop, so it does not compete with the card the reader is
     meant to act on. */
  box-shadow: none;
}

.ohl-preview-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 12px;
}

.ohl-preview-tile {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 8px;
}

/* 12px is what a real tile caption is (.ohl-tile-label). The preview sat a
   step below every size the app actually uses - captions at 11 where the app
   has 12, the paragraph at 12 where it has 14 - so the whole card read as
   fine print (Mira, 10. 9. 2026). */
.ohl-preview-tile-label {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
}

/* The stand-in for every figure: a bar, never a number. */
.ohl-preview-bar {
  height: 10px;
  width: 100%;
  max-width: 120px;
  border-radius: 4px;
  background: rgb(var(--ohl-line));
}

/* A stand-in paragraph, for the one screen whose content is prose: the
   briefing. Lines run the full width, so the cap the figure bars carry is
   lifted here. */
.ohl-preview-lines {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ohl-preview-lines .ohl-preview-bar {
  max-width: none;
}

.ohl-preview-bar--figure {
  height: 16px;
  max-width: 84px;
}

.ohl-preview-chart {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  height: 160px;
}

.ohl-preview-column {
  flex: 1;
  border-radius: 4px 4px 0 0;
  background: rgb(var(--ohl-accent-line));
}

.ohl-preview-table th {
  font-size: 12px;
  color: rgb(var(--ohl-muted));
}

/* The half that is actually read. Sits over the art, and carries its own
   ground so the blurred shapes never run into the text. */
.ohl-preview-veil {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.ohl-preview-call {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: 540px;
  padding: 28px 36px;
  text-align: center;
  background: rgb(var(--ohl-surface));
  border: 1px solid rgb(var(--ohl-line));
  border-radius: 10px;
}

.ohl-preview-title {
  font-size: 18px;
  /* 800 like every other heading the app draws (.ohl-card-hd h2). It sat at
     600 until Mira spotted it reading as a different typeface - same family,
     lighter cut, wrong company. */
  font-weight: 800;
  color: rgb(var(--ohl-ink));
  margin: 0;
}

/* The one paragraph here meant to be READ: it says what the section is for and
   whether connecting a source is worth it. 14px is the size the app gives text
   people read (.ohl-table td), not the 12px it keeps for captions and column
   headers. */
.ohl-preview-body {
  font-size: 14px;
  line-height: 1.5;
  color: rgb(var(--ohl-ink));
  margin: 0;
}

/* Stacked and equal width rather than side by side: the labels name real
   services, so they differ in length ("Napojit Shoptet" against "Napojit
   Google Analytics 4"), and a row of them wrapped into a ragged staircase.
   A section can offer up to three. */
.ohl-preview-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  margin-top: 10px;
  width: 100%;
  max-width: 280px;
}

/* A reader who asked for less motion and less visual noise gets the plain
   ground instead of a blurred picture behind the text. */
@media (prefers-reduced-motion: reduce) {
  .ohl-preview-art {
    display: none;
  }

  .ohl-preview-veil {
    position: static;
    padding: 0;
  }
}
