/* Content Agent — design system.
   One place for tokens and components; templates should use classes, not inline styles. */

:root {
  /* Brand */
  --navy: #20254d;
  --coral: #c15f55;

  /* Surfaces */
  --bg: #f5f6f9;
  --card: #fff;
  --sidebar: #1a1e40;
  --sidebar-ink: #c9cde6;

  /* Ink */
  --ink: #1c2038;
  --ink-2: #4a5068;
  --muted: #6b7280;
  --line: #e4e6ee;

  /* Status */
  --ok: #15803d;
  --ok-bg: #ecfdf3;
  --warn: #b45309;
  --warn-bg: #fffbeb;
  --danger: #b91c1c;
  --danger-bg: #fef2f2;
  --hot-bg: #fff0f5;

  /* Shape + depth */
  --r-sm: 6px;
  --r: 10px;
  --r-lg: 14px;
  --shadow: 0 1px 2px rgba(16, 20, 45, .06), 0 4px 12px rgba(16, 20, 45, .05);
  --shadow-lg: 0 20px 60px rgba(16, 20, 45, .28);

  /* Rhythm */
  --sp-1: .25rem;
  --sp-2: .5rem;
  --sp-3: .75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;

  --sidebar-w: 232px;
  --sidebar-w-collapsed: 64px;
}

* { box-sizing: border-box; }

/* Visible to a screen reader, invisible on screen — for a column that needs a name but not a
   heading (the actions column) and for icon-only buttons. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Stop iOS/Android from re-flowing text at its own discretion — the layout below already
   scales with the viewport, and a silent 20% bump breaks every measured column. */
html {
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
  /* The closed drawer is parked off-screen to the RIGHT (transform: translateX(100%)), and a
     fixed element that sticks out still counts towards the document's scroll width — measured
     at 444px of sideways scroll on a 360px phone, on every page that has a drawer. `clip`
     rather than `hidden` on purpose: `hidden` would turn the root into a scroll container and
     break every `position: sticky` in the app. */
  overflow-x: clip;
}

body {
  margin: 0;
  font-family: 'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  background: var(--bg);
  color: var(--ink);
  font-size: clamp(.92rem, .9rem + .12vw, 1rem);
  line-height: 1.5;
}

img, video, svg { max-width: 100%; }

a { color: var(--navy); }

/* ---------- App shell ----------
   `dvh` after `vh` everywhere a panel is meant to be exactly one screen tall: on a phone the
   browser chrome comes and goes, and 100vh leaves the bottom of the panel under the toolbar. */
.shell { display: flex; min-height: 100vh; min-height: 100dvh; }

.sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  background: var(--sidebar);
  color: var(--sidebar-ink);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  padding-bottom: env(safe-area-inset-bottom);
  transition: width .15s ease, flex-basis .15s ease;
}
.sidebar .brand {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-4) var(--sp-3);
  color: #fff; font-weight: 700; letter-spacing: .2px; white-space: nowrap;
}
.sidebar nav { display: flex; flex-direction: column; gap: 2px; padding: var(--sp-2); }
.sidebar nav a {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: .55rem .7rem; border-radius: var(--r-sm);
  color: var(--sidebar-ink); text-decoration: none; font-size: .93rem; white-space: nowrap;
}
.sidebar nav a:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.sidebar nav a.active { background: var(--coral); color: #fff; font-weight: 600; }
.sidebar nav a .ico { width: 1.2rem; text-align: center; flex: none; }
.sidebar .sep {
  margin: var(--sp-3) var(--sp-4) var(--sp-1);
  font-size: .68rem; text-transform: uppercase; letter-spacing: .6px; opacity: .55;
}
.sidebar .foot { margin-top: auto; padding: var(--sp-3) var(--sp-4); font-size: .8rem; }
.sidebar .foot a { color: #fff; }
.sidebar .collapse {
  background: transparent; border: none; color: var(--sidebar-ink);
  cursor: pointer; padding: var(--sp-2) var(--sp-4); text-align: left; font-size: .8rem;
}

/* Collapsed state (desktop) */
body.nav-collapsed .sidebar { width: var(--sidebar-w-collapsed); flex-basis: var(--sidebar-w-collapsed); }
body.nav-collapsed .sidebar .label,
body.nav-collapsed .sidebar .sep,
body.nav-collapsed .sidebar .brand span,
body.nav-collapsed .sidebar .foot .who { display: none; }
body.nav-collapsed .sidebar nav a { justify-content: center; }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* The topbar carries a heading AND per-page actions. On a narrow screen those two cannot share
   one line, so the bar wraps instead of squeezing the title into an unreadable stub. */
.topbar {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; row-gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  background: var(--card); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
.topbar h1 {
  font-size: 1.15rem; margin: 0; font-weight: 700;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar .spacer { flex: 1; }
.topbar .burger {
  display: none; background: none; border: none; color: var(--ink);
  padding: .3rem; cursor: pointer; line-height: 0;
}
/* The page's own actions. `contents` on a wide screen: the box does not exist and the buttons
   are direct children of the bar, exactly as they were before it was introduced. It becomes a
   real, scrollable strip only on a phone — see the rule at 640px. */
.topbar-actions { display: contents; }

.content { padding: var(--sp-5); width: 100%; max-width: 1600px; margin: 0 auto; }
/* Notifications sit over the bottom-right corner. Without this the last row of a form lives
   underneath them and cannot be clicked at all — the class is set by the mailbox itself, so
   the room is only taken when there is something to show. */
body.has-notes .content { padding-bottom: 14rem; }
/* …and inside the drawer, which scrolls independently of the page. Only ever on the element
   that actually SCROLLS: the wizard body is `overflow: hidden` and lays its pane out with
   flex, so padding there stole 224px of the pane's height and clipped the step instead. */
/* …and capped against the SCREEN, because padding is the one thing flexbox cannot shrink.
   `min-height: 0` lets the pane's content box go to zero, but 14rem of padding still holds the
   box open — so on a phone in landscape the step pane ended 87px BELOW the bottom of the screen
   and that last strip of it could not be reached by any amount of scrolling. */
body.has-notes .wz-pane,
body.has-notes .drawer .body:not(.wizard) { padding-bottom: min(14rem, 22dvh); }
/* Folded, the whole mailbox is one bell-sized pill, so the room reserved for it has to shrink
   with it. Without this the page kept a 14rem hole at the bottom for a stack that is not there
   — on a phone that hole is a fifth of the screen, and folding is the DEFAULT there. */
body.has-notes.notes-folded .content,
body.has-notes.notes-folded .wz-pane,
body.has-notes.notes-folded .drawer .body:not(.wizard) { padding-bottom: 4.5rem; }
/* A very wide monitor is not an excuse for 40-word lines, but 1600px wastes a 4K screen when
   the page is a dense table. Give those the extra room, keep the reading measure sane. */
@media (min-width: 1900px) { .content { max-width: 1800px; } }

/* ---------- Cards ---------- */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-4); box-shadow: var(--shadow);
}
/* On a phone the card's own 24px of side padding is 12% of the screen spent on white space —
   and it is spent TWICE, because the page already has its own. Everything inside gets those
   16px back: the list switcher stops being cut off at „Archiwum", and a topic gets a longer
   line to be read on. */
@media (max-width: 640px) { .card { padding: var(--sp-3); } }
.card > h2 { font-size: 1rem; margin: 0 0 var(--sp-3); }
h1 { font-size: clamp(1.2rem, 1.1rem + .4vw, 1.5rem); margin: 0 0 var(--sp-4); }
h2 { font-size: 1rem; margin: 0 0 var(--sp-2); }
.muted { color: var(--muted); }
.small { font-size: .84rem; }

/* ---------- Stat tiles ---------- */
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp-4); }
.stat {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--sp-4); text-align: center; text-decoration: none; color: inherit;
  box-shadow: var(--shadow); display: block;
}
.stat:hover { border-color: var(--navy); }
.stat .n { font-size: 1.9rem; font-weight: 700; line-height: 1.1; }
.stat .l { color: var(--muted); font-size: .85rem; margin-top: var(--sp-1); }

/* ---------- Buttons ---------- */
button, .btn {
  background: var(--navy); color: #fff; border: 1px solid transparent;
  padding: .45rem .8rem; border-radius: var(--r-sm); cursor: pointer;
  font: inherit; font-size: .88rem; text-decoration: none; display: inline-flex;
  align-items: center; gap: .35rem; white-space: nowrap;
}
button:hover, .btn:hover { filter: brightness(1.08); }
button.ghost, .btn.ghost { background: #eef0f6; color: var(--ink); border-color: var(--line); }
button.danger, .btn.danger { background: var(--danger); color: #fff; }
button.coral, .btn.coral { background: var(--coral); color: #fff; }
button:disabled { opacity: .55; cursor: default; }
/* A button whose work is running. Disabled AND spinning: the work now happens in the
   background, so the request returns at once and there is nothing else on the button to say
   "still going" — which is how the same generation got ordered twice. */
button.working, .btn.working { opacity: .75; cursor: progress; }
button.working::before, .btn.working::before {
  content: ""; width: .85em; height: .85em; flex: none; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: ca-spin .8s linear infinite;
}
@keyframes ca-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  button.working::before, .btn.working::before { animation: none; }
}
.btn-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
button.small, .btn.small { padding: .3rem .6rem; font-size: .82rem; }
/* A form that saves itself does not need a button saying so. Kept in the markup and hidden only
   when JavaScript is running, so the page still works without it. */
.js .save-inline { display: none; }

/* ---------- Forms ---------- */
input, textarea, select {
  width: 100%; padding: .5rem .6rem; border: 1px solid var(--line);
  border-radius: var(--r-sm); font: inherit; background: #fff; color: var(--ink);
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--navy); outline-offset: -1px; }
select.auto, input.auto { width: auto; }
label { display: block; font-size: .84rem; color: var(--muted); margin: var(--sp-3) 0 var(--sp-1); }
/* A tick plus a SENTENCE. Not flex: flexbox turns every run of text into its own item, so
   „Dołączaj <strong>ton głosu</strong> (brand/voice.md) — do treści postów" was laid out as
   three narrow columns, each wrapping on its own — unreadable on a phone and merely odd on a
   laptop. Normal inline flow wraps the way a sentence should; the hanging indent keeps the
   second line under the first letter instead of under the box. */
.check {
  display: block; margin: 0; color: var(--ink);
  padding-left: 1.6rem; text-indent: -1.6rem;
}
.check input { width: auto; margin-right: .45rem; vertical-align: -.15em; text-indent: 0; }
/* Some of these labels carry a whole block (a paragraph, a description). Those opt back into a
   two-column layout by wrapping their text in one element, which is a single flex item. */
.check > span, .check > div { display: inline-block; text-indent: 0; vertical-align: top; }
form.inline { display: inline; }
.row { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }

/* ---------- Tables (responsive: become cards on narrow screens) ----------
   Between the two worlds — a tablet or a small laptop — a ten-column table is still a table,
   just a wide one. It scrolls inside its own wrapper rather than stretching the page. */
.table-wrap { width: 100%; overflow-x: auto; }
table { width: 100%; border-collapse: separate; border-spacing: 0; }
th, td { text-align: left; padding: .62rem .8rem; border-bottom: 1px solid var(--line); font-size: .9rem; }
/* The header stays put while the rows move under it. A ten-row table where the columns scroll
   out of sight is a table you have to remember rather than read. */
th {
  position: sticky; top: 0; z-index: 2;
  background: #f8f9fc; border-bottom: 1px solid var(--line);
  font-size: .74rem; text-transform: uppercase; letter-spacing: .5px;
  color: var(--muted); font-weight: 600; white-space: nowrap;
}
tbody td { height: 3.1rem; }  /* one row, one height — a list you can scan down */
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: #f8f9fc; }
/* The whole row reacts, so it has to look like one thing rather than a strip of cells. */
tbody tr { transition: background .12s ease; }
tr.hot { background: var(--hot-bg); }
td.nowrap, th.nowrap { white-space: nowrap; }
/* `display: flex` takes the cell out of the table's own sizing, and from that moment
   `tbody td { height: 3.1rem }` is a FIXED height rather than the row's minimum: the four
   buttons of a Harmonogram row wrapped and were painted 107px below their own cell — on a
   1280px laptop, not only on a phone. The rhythm stays as a floor; the box grows. */
td.actions {
  display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center;
  height: auto; min-height: 3.1rem;
}
td.actions form { margin: 0; }
/* Actions belong at the end of the row, and the row must not grow to fit them: one obvious verb
   plus a menu for everything that is rare or irreversible. Three equal buttons per row was how
   "delete" ended up the same size and the same distance away as "open". */
th.col-actions, td.col-actions { text-align: right; white-space: nowrap; width: 1%; }
td.col-actions .row-tools {
  display: inline-flex; gap: var(--sp-2); align-items: center; justify-content: flex-end;
}
/* A table column is as wide as the layout gives it; a `nowrap` button inside one simply sticks
   out (measured: 74px past its cell on the schedule). Inside a cell, buttons may wrap. */
td button, td .btn { white-space: normal; max-width: 100%; }
td form { min-width: 0; }

/* The graphic of a scheduled post, as a thumbnail. A class rather than an inline style, so the
   card layout on a phone can make it smaller without fighting the specificity of `style=`. */
.sched-thumb {
  width: 70px; display: block; border: 1px solid var(--line); border-radius: 6px;
}

/* Studio topic: the whole subject is always rendered (never cut with "…"); it is clamped to
   two lines and the row unfolds on click, so a long topic costs no vertical space by default. */
.studio-subject { min-width: 20ch; }
.subject-clamp {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
}
tr.row-open .subject-clamp { -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; }
tr.is-clamped { cursor: pointer; }
/* A lone „▾" under a cut-off sentence reads as a rendering artefact, not as an offer. It costs
   a line either way — so the line says what tapping it does. */
tr.is-clamped .studio-subject::after {
  content: "▾ pokaż całość"; display: block; margin-top: .1rem;
  color: var(--muted); font-size: .74rem;
}
tr.is-clamped.row-open .studio-subject::after { content: "▴ zwiń"; }

@media (max-width: 860px) {
  .table-wrap { overflow-x: visible; }  /* cards don't scroll sideways */
  table, thead, tbody, tr, td { display: block; width: 100%; }
  thead { display: none; }
  tbody tr {
    border: 1px solid var(--line); border-radius: var(--r);
    margin-bottom: var(--sp-3); padding: var(--sp-2); background: var(--card);
  }
  td { border: none; padding: .35rem .5rem; display: flex; gap: var(--sp-3); flex-wrap: wrap; }
  /* `tbody td { height: 3.1rem }` is what makes a TABLE scannable — one row, one height. In card
     mode it is a fixed height on a box that is now a LINE OF A CARD, and `height` (not
     `min-height`) does not grow: the four buttons of a Harmonogram row wrapped to three rows
     inside a 50px cell and simply spilled out — „🗑 Usuń" was rendered below the card, on the
     page background, still clickable and looking like it belonged to nothing.
     Written as `tbody td`, not `td`: the rule it has to beat is `tbody td`, and a bare `td` in a
     media query loses that on specificity — which is exactly how this survived a release that
     had already fixed it for the Studio queue alone. */
  tbody td { height: auto; }
  /* A chip is one long unbreakable word by default ("🕒 czeka 01.01 09:00"), and in card mode
     the label eats 40% of the row — measured at 134px of spill. Let them wrap. */
  td > * { min-width: 0; max-width: 100%; }
  .pill, .badge, .chip { white-space: normal; overflow-wrap: anywhere; }
  td::before {
    content: attr(data-label); flex: 0 0 40%;
    font-size: .76rem; text-transform: uppercase; letter-spacing: .4px; color: var(--muted);
  }
  /* A cell with no label must not reserve the label column. `attr()` on a missing attribute is
     an empty string, not "no pseudo-element" — so every unlabelled table (the key/value ones in
     Ustawienia above all) kept an empty 40% strip: measured at 251px of a 744px iPad row. */
  td:not([data-label])::before { content: none; }
  td.actions { flex-wrap: wrap; }
  /* In card mode a row is a card, so the actions column stops being a narrow column at the end
     of a line: full width, aligned with everything else in the card. `width: 1%` left "Otwórz"
     28px wide with its label wrapped down the side. */
  td.col-actions { width: auto; text-align: left; }
  td.col-actions .row-tools { justify-content: flex-start; }

  /* ----- The Studio queue is a LIST OF TOPICS, not a form printed once per topic -----
     Generic card mode turns every cell into a labelled line, which is right for the key/value
     tables in Ustawienia and wrong here: one topic filled a whole 360x740 phone, its subject
     was the second line of six, under an uppercase „TEMAT", and „KTO DODAŁ / system" got the
     same weight as the topic itself. Five topics were five screens of scrolling.

     Nothing is hidden — the same six facts are here. They are ranked: the subject leads at full
     width, the stage and the platforms share the line under it, and provenance drops to one
     small footer line. Three lines instead of six, and the thing you scan for is on top. */
  #studio-rows tr {
    display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .55rem;
    padding: var(--sp-3);
  }
  /* `width: auto` matters: the generic card rule above sets `td { width: 100% }`, which keeps
     every cell on a line of its own no matter what flex says — the six-line card again.
     Measured before: 239px per topic, of which about 100px was empty. */
  #studio-rows td {
    display: flex; align-items: center; gap: .3rem; padding: 0; flex: 0 0 auto;
    width: auto;
  }
  #studio-rows td::before { content: none; }
  #studio-rows .studio-subject {
    order: -1; flex: 1 1 100%; display: block;
    font-size: .98rem; font-weight: 600; line-height: 1.35;
  }
  /* Two lines of a 30-character phone line is 60 characters, and a topic is a whole sentence:
     „Dlaczego ukończony kurs cyberbezpieczeństwa nie mówi nic o…" is where the cut landed —
     the list said what the topic was ABOUT and never what it SAID. Four lines hold the ordinary
     topic whole, so the „pokaż całość" line is the exception rather than the rule. On a wide
     screen the column is three times as long and two lines are already the whole sentence. */
  #studio-rows .subject-clamp { -webkit-line-clamp: 4; line-clamp: 4; }
  /* The stage badge and the platform pills read as themselves; a label above them would only
     repeat what the badge says. */
  #studio-rows td[data-label="Platformy"] { flex: 1 1 auto; flex-wrap: wrap; }
  /* Provenance, author and date: still there, still readable, no longer competing with the
     topic. One line, small, muted — the labels come back as words, because „system" on its own
     says nothing. */
  #studio-rows td[data-label="Pochodzenie"],
  #studio-rows td[data-label="Kto dodał"],
  #studio-rows td[data-label="Dodano"] {
    font-size: .78rem; color: var(--muted); gap: .25rem;
  }
  #studio-rows td[data-label="Kto dodał"]::before { content: "dodał:"; }
  #studio-rows td[data-label="Dodano"]::before { content: "dodano:"; }
  #studio-rows td[data-label="Kto dodał"]::before,
  #studio-rows td[data-label="Dodano"]::before {
    flex: 0 0 auto;
    font-size: .78rem; text-transform: lowercase; letter-spacing: 0; color: var(--muted);
  }
  /* „Otwórz" and the ⋯ menu end the card, pushed to the right of the footer line — and pinned
     there: without a floor of their own they were the items flexbox chose to squeeze when a
     long author name shared the line, and „Otwórz" came out as a 28px sliver. */
  #studio-rows td.col-actions { margin-left: auto; flex: 0 0 auto; }

  /* ----- Harmonogram: a scheduled post is WHAT goes out and WHEN -----
     Ten columns became ten labelled lines: 514px per post on an 844px phone, with „TEMAT" as
     the seventh of them and „DODANO / 2026-08-16 15:12" given the same weight as the date the
     thing actually publishes. Same treatment as the Studio queue: the subject leads, the
     publication time reads as a time, the provenance drops to a footer, and the buttons get a
     row of their own instead of spilling out of the card. */
  #schedule-rows tr {
    display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .55rem;
    padding: var(--sp-3);
  }
  #schedule-rows td {
    display: flex; align-items: center; gap: .3rem; padding: 0; flex: 0 0 auto; width: auto;
  }
  #schedule-rows td::before { content: none; }
  /* The subject is also the way IN to the copy (a <details> holding the editor), so it is a
     block: opened, the textarea needs the whole width of the card. */
  #schedule-rows td[data-label="Temat"] {
    order: -3; flex: 1 1 100%; display: block; font-size: .96rem; font-weight: 600;
    line-height: 1.35;
  }
  #schedule-rows td[data-label="Temat"] summary { font-weight: 600; }
  #schedule-rows td[data-label="Temat"] textarea { font-weight: 400; }
  /* When it goes out — the one fact a schedule exists to state. `display: block` on purpose:
     as a flex box the date and the hour were two items and wrapped to two lines inside their
     own pill, which is how a schedule came to read „2099-01-01 / 10:00". */
  #schedule-rows td[data-label="Publikacja"] {
    order: -2; display: block; padding: .1rem .55rem; border-radius: 999px;
    background: var(--ok-bg); color: var(--ok); font-size: .82rem; white-space: nowrap;
  }
  #schedule-rows td[data-label="Publikacja"]::before { content: "🗓 "; }
  #schedule-rows td[data-label="Publikacja"] br { display: none; }
  #schedule-rows td[data-label="Publikacja"] strong { margin-left: .3rem; }
  #schedule-rows td[data-label="Platforma"] { order: -2; font-weight: 600; font-size: .86rem; }
  /* Everything that is context rather than decision: one small, quiet line. */
  #schedule-rows td[data-label="Tryb"],
  #schedule-rows td[data-label="Kanał"],
  #schedule-rows td[data-label="Grafika"],
  #schedule-rows td[data-label="Dodano"],
  #schedule-rows td[data-label="Kto dodał"],
  #schedule-rows td[data-label="Akceptacja"] {
    order: 1; font-size: .78rem; color: var(--muted); gap: .25rem; white-space: nowrap;
  }
  #schedule-rows td[data-label="Grafika"] .sched-thumb { width: 40px; }
  #schedule-rows td[data-label="Kanał"]::before { content: "kanał:"; }
  #schedule-rows td[data-label="Grafika"]::before { content: "grafika:"; }
  #schedule-rows td[data-label="Dodano"]::before { content: "dodano:"; }
  #schedule-rows td[data-label="Kto dodał"]::before { content: "dodał:"; }
  #schedule-rows td[data-label="Akceptacja"]::before { content: "zatwierdził:"; }
  /* The generic label is an uppercase column heading; these are words in a sentence. */
  #schedule-rows td::before {
    flex: 0 0 auto; font-size: .78rem; text-transform: lowercase; letter-spacing: 0;
    color: var(--muted);
  }
  /* Publishing is not a footnote on a metadata line — it gets the bottom of the card, where a
     thumb expects it. Aligned RIGHT, and that is structural rather than taste: `.menu-pop` is
     right-aligned to its own ⋯, so a ⋯ sitting 150px from the left edge hangs its 208px menu
     14px off the screen — measured on a 390px phone, with no scrolling to reveal it. */
  #schedule-rows td[data-label="Akcje"] {
    order: 2; flex: 1 1 100%; flex-wrap: wrap; justify-content: flex-end; gap: var(--sp-2);
    margin-top: var(--sp-1); padding-top: var(--sp-2); border-top: 1px solid var(--line);
  }
  #schedule-rows td[data-label="Akcje"] .row-tools { justify-content: flex-end; }
}

/* ---------- Segmented control (which LIST am I looking at) ----------
   Three lists is navigation, not filtering, so it looks like navigation: one control, one
   choice lit, counts inside. Fourteen chips in a row said "filter everything" and answered
   nothing — measured: 28 controls above the first table row, on a screen 820px tall. */
.seg {
  display: inline-flex; background: #eef0f6; border: 1px solid var(--line);
  border-radius: 999px; padding: 3px; gap: 2px; max-width: 100%; overflow-x: auto;
  scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }
.seg a {
  padding: .35rem .9rem; border-radius: 999px; text-decoration: none; white-space: nowrap;
  color: var(--ink-2); font-size: .88rem; font-weight: 500;
}
.seg a:hover { color: var(--ink); }
.seg a.on { background: var(--card); color: var(--ink); box-shadow: var(--shadow); font-weight: 600; }
/* The gap is set here, not left to the whitespace in the markup: at this size the space between
   a word and a digit collapses to nothing on a phone and the tab reads „Opublikowane0". */
.seg a .n { color: var(--muted); font-variant-numeric: tabular-nums; margin-left: .3rem; }
.seg a.on .n { color: var(--ink-2); }
/* Three tabs, one row, on the narrowest phone: 0.9rem of padding each puts „Archiwum" off the
   edge, and a control that has to be swiped to be discovered is a control nobody discovers. */
@media (max-width: 640px) {
  .seg { display: flex; width: 100%; }
  .seg a { padding: .35rem .6rem; font-size: .85rem; flex: 0 1 auto; }
}
@media (max-width: 400px) {
  .seg a { padding: .35rem .45rem; font-size: .8rem; }
}

/* One line of controls above a list: search on the left, narrowing on the right. The spacing is
   the component's own (it used to be an inline style on the form), so a phone can have less of
   it — see the rule below. */
.filters {
  display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap;
  margin: var(--sp-4) 0;
}
.filters .grow { flex: 1 1 14rem; min-width: 0; }
.filters .field { display: flex; align-items: center; gap: var(--sp-2); }
.filters .field label { margin: 0; font-size: .82rem; white-space: nowrap; }
.filters select { min-width: 12rem; }
/* On a phone the filter bar is one line or it is a second screen: search and the narrowing
   share the row, and the submit button goes (Enter submits, the select submits on change).
   Measured before: the first table row started 517px down a 844px screen — nothing to see. */
@media (max-width: 640px) {
  .filters { gap: var(--sp-2); margin: var(--sp-3) 0; }
  .filters select { min-width: 0; }
  .filters .grow { flex: 1 1 7rem; }
  .filters .field { flex: 1 1 9rem; min-width: 0; }
  /* `select.auto` means "as wide as your longest option", and the longest option here is
     „⚡ Sugestie do treści (0)". That is what put the narrowing on a SECOND line — 56px of a
     740px phone, above every list, for a control that fits beside the search box. Inside the
     filter bar the select takes the room the bar gives it and no more. */
  .filters .field select.auto { width: 100%; flex: 1 1 auto; }
  /* Not just inside `.filters` any more: the same promise („this part is for a wide screen")
     is needed by any label that only earns its room when there is room. */
  .only-wide { display: none; }
}
/* The twin of `.only-wide`: a shorter word for the same button where the long one would push
   the next control off the screen. One of the two is always showing — nothing is ever hidden
   outright, which is what separates this from a disappearing control. */
.only-narrow { display: none; }
@media (max-width: 640px) { .only-narrow { display: inline; } }

/* ---------- Row menu (⋯) ----------
   `<details>` on purpose: no JavaScript to open it, it closes with Escape, and a page that is
   swapped in place cannot lose its click handler — the bug that made the old drawer backdrop
   silently stop closing. */
.menu { position: relative; display: inline-block; }
.menu > summary {
  list-style: none; cursor: pointer; width: 32px; height: 32px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-2); border: 1px solid transparent; font-size: 1.1rem; line-height: 1;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover { background: #eef0f6; border-color: var(--line); color: var(--ink); }
.menu[open] > summary { background: #eef0f6; border-color: var(--line); }
.menu-pop {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 30; min-width: 13rem;
  max-width: min(20rem, calc(100vw - 2rem));
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow); padding: var(--sp-1); text-align: left;
  /* The menu lives inside `td.col-actions`, which is `nowrap` so that a row's buttons never
     wrap — and that inherited straight into the menu, where the items are whole sentences:
     „🔀 Zastąp innym postem" was rendered 155px wider than the menu holding it, sticking out
     past the right edge of a 390px phone and making the page scroll sideways. Here a line
     wraps. */
  white-space: normal;
}
/* Placed by base.html in the viewport (ADR-061): out of `.table-wrap`, whose sideways scroll
   clipped the menu and made the last rows' menus open inside the table. */
.menu-pop.floating { position: fixed; right: auto; z-index: 320; }
.menu-pop form { margin: 0; }
.menu-pop button, .menu-pop .btn {
  width: 100%; justify-content: flex-start; background: transparent; color: var(--ink);
  border: none; padding: .5rem .6rem; border-radius: var(--r-sm); font-size: .88rem;
  /* `.btn` is `nowrap` by design — in a row. In the menu it is a line of a list. */
  white-space: normal; text-align: left;
}
.menu-pop button:hover, .menu-pop .btn:hover { background: #f2f4f9; filter: none; }
.menu-pop button.danger { background: transparent; color: var(--danger); }
.menu-pop button.danger:hover { background: var(--danger-bg); }
.menu-pop .sep { height: 1px; background: var(--line); margin: var(--sp-1) 0; }

/* ---------- Confirm dialog ----------
   The browser's own confirm() prints the server address in its title bar and cannot say what is
   about to be lost. This one names the topic and counts what goes with it. */
.ask-back {
  position: fixed; inset: 0; background: rgba(20, 22, 40, .5); z-index: 600;
  display: flex; align-items: center; justify-content: center; padding: var(--sp-4);
}
.ask-back[hidden] { display: none; }
.ask {
  background: var(--card); border-radius: var(--r-lg); box-shadow: var(--shadow-lg);
  width: min(30rem, 100%); padding: var(--sp-5); animation: ask-in .12s ease-out;
}
@keyframes ask-in { from { transform: translateY(6px); opacity: .6; } }
@media (prefers-reduced-motion: reduce) { .ask { animation: none; } }
.ask h2 { margin: 0 0 var(--sp-2); font-size: 1.05rem; }
.ask p { margin: 0 0 var(--sp-2); color: var(--ink-2); font-size: .92rem; }
.ask .what {
  background: #f7f8fc; border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3); font-size: .88rem; color: var(--ink-2);
  margin-bottom: var(--sp-3);
}
.ask footer { display: flex; gap: var(--sp-2); justify-content: flex-end; margin-top: var(--sp-4); }

/* ---------- Badges / pills ---------- */
.pill, .badge {
  display: inline-block; padding: .12rem .5rem; border-radius: 999px;
  font-size: .74rem; background: #eef0f6; color: var(--ink-2); white-space: nowrap;
}
/* Workflow stages (core/stages.py): the colour walks from "just an idea" to "ready to go". */
.badge.st-temat { background: #eef2ff; color: #3730a3; }
.badge.st-zajawka { background: #f5f3ff; color: #6d28d9; }
.badge.st-grafika { background: #fdf2f8; color: #9d174d; }
.badge.st-platformy { background: #ecfeff; color: #0e7490; }
.badge.st-tresc { background: var(--warn-bg); color: var(--warn); }
.badge.st-poprawki { background: #fff7ed; color: #c2410c; }
.badge.st-publikacja { background: var(--ok-bg); color: var(--ok); }
/* End of the road, not a stage: a published topic must not look like one still to publish. */
.badge.st-opublikowany { background: var(--navy); color: #fff; }
/* Also not a stage: a topic somebody marked as a repeat. Muted on purpose — it is a note about
   why this row is in the archive, not work waiting for anyone. */
.badge.st-odrzucony { background: #f3f4f6; color: #6b7280; }

.badge.idea { background: #eef2ff; color: #3730a3; }
.badge.draft { background: #f3f4f6; color: #374151; }
.badge.review { background: var(--warn-bg); color: var(--warn); }
.badge.sched { background: var(--ok-bg); color: var(--ok); }
.badge.hot { background: #ffe4ef; color: #9d174d; }

/* ---------- Messages ---------- */
.toast {
  background: var(--warn-bg); border: 1px solid #f0d98a; color: #7a5b00;
  padding: .6rem .9rem; border-radius: var(--r-sm); margin-bottom: var(--sp-4);
}
/* The result of what you just did. It STAYS until dismissed: the four-second toast was fine
   for "saved", and useless for "LinkedIn: 18.08 09:00 · Instagram: nie publikujemy" — the one
   sentence that says what the button actually did was gone before it could be read. */
.flash {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  background: var(--card); border: 1px solid var(--line); border-left: 3px solid var(--navy);
  color: var(--ink); padding: .7rem .9rem; border-radius: var(--r-sm);
  margin-bottom: var(--sp-4); box-shadow: var(--shadow); font-size: .92rem;
}
.flash.ok { border-left-color: var(--ok); }
.flash.bad { border-left-color: var(--danger); background: var(--danger-bg); font-weight: 600; }
/* The page flash sticks under the top bar (ADR-061). After a full-page submit the page returns
   to where the person was, and a flash drawn at the top of the page was off screen for anyone
   working in the lower rows. Sticky, not fixed: at the top of the page it takes its own room
   and covers nothing; scrolled down, it stays in view until closed. */
.flash-dock {
  position: sticky; top: calc(var(--topbar-h, 3.6rem) + var(--sp-2)); z-index: 25;
  margin-bottom: var(--sp-4);
}
.flash-dock .flash { margin-bottom: 0; box-shadow: var(--shadow-lg); font-size: .95rem; }
.flash .x {
  background: transparent; border: none; color: var(--muted); font-size: 1.1rem;
  padding: 0 .2rem; cursor: pointer; line-height: 1; margin-left: auto;
}
.flash .x:hover { color: var(--ink); filter: none; }
.mockbar {
  background: var(--warn-bg); border: 1px solid #f0d98a; color: #7a5b00;
  padding: .4rem .9rem; border-radius: var(--r-sm); margin-bottom: var(--sp-4); font-size: .88rem;
}
/* It says the same thing on every page and it is true all day; on a phone it was wrapping to two
   lines and taking 62px above every list for a standing fact. */
@media (max-width: 640px) {
  .mockbar { padding: .3rem .6rem; margin-bottom: var(--sp-2); font-size: .8rem; }
}
.empty { text-align: center; color: var(--muted); padding: var(--sp-6) var(--sp-4); }
.empty .big { font-size: 2rem; display: block; margin-bottom: var(--sp-2); }
.empty h3 { margin: 0 0 var(--sp-2); color: var(--ink); font-size: 1rem; }
.empty p { margin: 0 auto var(--sp-4); max-width: 34rem; }

/* ---------- Post preview (the copy step) ----------
   The step used to be seven textareas stacked to 3227px — 5.3 screens for three platforms, so
   the Instagram version was four screens below the fold. One platform at a time, shown the way
   a reader will see it, with the text editable in place for the typo you spot while reading. */
.tabs {
  display: flex; gap: var(--sp-1); border-bottom: 1px solid var(--line);
  margin-bottom: var(--sp-4); overflow-x: auto; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  background: transparent; color: var(--ink-2); border: none; border-bottom: 2px solid transparent;
  border-radius: 0; padding: .55rem .9rem; font-size: .92rem; white-space: nowrap;
}
.tabs button:hover { background: #f5f6fa; filter: none; }
.tabs button.on { color: var(--ink); border-bottom-color: var(--coral); font-weight: 600; }
.tabs button .dot { font-size: .8rem; }

.preview { display: grid; grid-template-columns: minmax(0, 190px) minmax(0, 1fr); gap: var(--sp-4); }
/* Stacked, the thumbnail stops being a thumbnail: a 4:5 picture at full width is 490px tall on a
   390px phone — 58% of the screen, above the text that is the whole point of the step. It keeps
   the width it has on a wide screen and sits in the middle, so the copy starts a swipe in. */
@media (max-width: 620px) {
  .preview { grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); }
  .preview > div:first-child { display: grid; justify-items: center; }
  .preview > div:first-child > * { width: 100%; max-width: 220px; }
}
.preview-img {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--r);
  border: 1px solid var(--line); background: #f0f1f5;
}
.preview-card {
  border: 1px solid var(--line); border-radius: var(--r); background: var(--card);
  padding: var(--sp-4); min-width: 0;
}
.preview-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.preview-head .who { font-weight: 600; font-size: .9rem; }
.preview-head .when { color: var(--muted); font-size: .8rem; }
.preview-body { white-space: pre-wrap; overflow-wrap: anywhere; font-size: .94rem; line-height: 1.6; }
.preview-body .tag { color: var(--navy); }
.preview-note {
  margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--line);
  color: var(--muted); font-size: .84rem;
}
.preview-edit { width: 100%; min-height: 18rem; font-size: .94rem; line-height: 1.6; }

/* How long a post should be, chosen per platform. "Krótszy" is not something a model obeys the
   same way twice; "60–100 słów" is — so the range is ON the option, not in a tooltip and not
   only next to whichever one is already picked. A phone has no hover, so a `title` there is a
   number nobody can read. */
.len-pick {
  margin: 0 0 var(--sp-4); padding: var(--sp-3);
  border: 1px solid var(--line); border-radius: var(--r); background: #fbfbfe;
}
.len-pick + .len-pick { margin-top: calc(-1 * var(--sp-2)); }
.len-head {
  display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap;
  margin-bottom: var(--sp-2);
}
.len-head .len-name { font-size: .9rem; font-weight: 600; }
.len-head .len-now { font-size: .82rem; color: var(--muted); }
.len-opts { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: stretch; }
.len-opts .len-form { margin: 0; display: flex; }
/* Two lines, so the range never competes with the word for width: the label reads first and
   the number is there to be checked against it. */
.len-opt {
  display: flex; flex-direction: column; align-items: flex-start; gap: .1rem;
  padding: .35rem .7rem; border-radius: var(--r-sm); text-align: left;
  background: #eef0f6; color: var(--ink); border: 1px solid var(--line); white-space: nowrap;
}
.len-opt:hover { background: #e6e9f2; filter: none; }
.len-opt .l { font-size: .86rem; font-weight: 600; }
.len-opt .w { font-size: .74rem; color: var(--muted); font-variant-numeric: tabular-nums; }
/* Chosen: the same coral "this is the one" as the slot picker and the picture cards. */
.len-opt.on, .len-opt.on:hover {
  background: var(--coral); border-color: var(--coral); color: #fff;
}
.len-opt.on .w { color: rgba(255, 255, 255, .85); }
/* The free-hand number sits with the three levels, not below them: it is a fourth answer to the
   same question, and it only makes sense while the other three ranges are on screen. */
.len-own { flex-direction: column; align-items: flex-start; gap: .1rem; }
.len-own-lab { margin: 0; font-size: .74rem; color: var(--muted); }
.len-own-row { flex-wrap: nowrap; gap: .35rem; }
.len-num { width: 6rem; min-width: 0; }
.len-note { margin: var(--sp-2) 0 0; }

/* Live channel indicator: grey while reconnecting, green (and quietly breathing) when the
   page is being told about changes as they happen. */
.live-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--muted); opacity: .45;
  flex: 0 0 auto; margin-right: var(--sp-2);
}
.live-dot.on { background: #2e9e6b; opacity: 1; animation: live-pulse 2.4s ease-in-out infinite; }
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .live-dot.on { animation: none; } }

/* Where a picture comes from: one block, wherever it is asked. */
.art-src { display: grid; gap: var(--sp-2); min-width: 0; }
.art-src .dw-hint { margin: .2rem 0 0; }
/* A file input sizes itself to the name of the chosen file, and `select.auto`/`input.auto` mean
   "as wide as the content" — inside a drawer column that is a spill, not a preference. */
.art-src > *, .art-src input, .art-src select, .art-src button { max-width: 100%; min-width: 0; }
.art-src .row { min-width: 0; }
.art-src input[type=file] { width: 100%; font-size: .8rem; }

/* The designer's files, as pictures. Choosing "linkedin_v3_final.png" from a <select> is
   choosing blind — and these are exactly the files someone was paid to make. */
.lib-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: var(--sp-2); margin-top: var(--sp-1);
}
.lib-grid form { display: contents; }
.lib-item {
  display: grid; gap: .25rem; padding: .3rem; width: 100%; height: auto; min-width: 0;
  background: var(--card); color: var(--ink); border: 1px solid var(--line);
  border-radius: var(--r-sm); cursor: pointer; text-align: left; white-space: normal;
}
.lib-item:hover { border-color: var(--navy); filter: none; }
.lib-item img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 4px; }
.lib-item .name {
  font-size: .72rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}

/* What the graphic button will actually draw — said before it is pressed. */
.art-for {
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: .55rem .8rem; margin-bottom: var(--sp-3);
  font-size: .84rem;
}

/* A step whose work is happening right now, elsewhere. */
.wz-working {
  display: flex; align-items: center; gap: var(--sp-2);
  background: var(--warn-bg); border: 1px solid #f0d98a; color: #7a5b00;
  padding: .6rem .9rem; border-radius: var(--r-sm); margin-bottom: var(--sp-3);
}
.wz-working { align-items: flex-start; }
.wz-working .dot {
  width: 10px; height: 10px; border-radius: 50%; background: #d19a00; margin-top: .35rem;
  animation: live-pulse 1.4s ease-in-out infinite; flex: none;
}
/* Where the run has got to. A single "trwa…" tells you nothing over three to six minutes; a
   checklist tells you whether the picture is being drawn or the cards are being composed. */
.wz-working .art-steps {
  list-style: none; margin: .45rem 0 .5rem; padding: 0; display: grid; gap: .15rem;
  font-size: .84rem;
}
.wz-working .art-steps li { opacity: .55; }
.wz-working .art-steps li.done { opacity: .8; }
.wz-working .art-steps li.on { opacity: 1; font-weight: 600; }

/* ---------- Notifications (the mailbox) ----------
   Bottom right, above every panel, because it is the only place the app says what came of the
   work you ordered. Nothing here disappears on its own: a card goes when someone closes it.
   Folded, the whole stack becomes one small bell — visible, but out of the way. */
.notes {
  position: fixed; right: var(--sp-4); z-index: 500;
  /* The home indicator on an iPhone and the gesture bar on Android both live in the bottom
     ~34px of the screen, and a swipe that starts there belongs to the system, not to us — the
     bell sat under it and could not be tapped. */
  bottom: calc(var(--sp-4) + env(safe-area-inset-bottom));
  width: min(360px, calc(100vw - 2 * var(--sp-4)));
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.notes[hidden] { display: none; }
.notes-head {
  display: flex; align-items: center; gap: var(--sp-2);
  background: var(--navy); color: #fff; border-radius: 999px;
  padding: .2rem .3rem .2rem .6rem; box-shadow: var(--shadow-lg); font-size: .8rem;
}
.notes-head .spacer { flex: 1; }
.notes-fold, .notes-all {
  background: transparent; border: none; color: #fff; cursor: pointer;
  font: inherit; padding: .25rem .5rem; border-radius: 999px; white-space: nowrap;
}
.notes-fold:hover, .notes-all:hover { background: rgba(255, 255, 255, .14); filter: none; }
.notes-list {
  display: flex; flex-direction: column; gap: var(--sp-2);
  max-height: min(60vh, 520px); overflow-y: auto;
}
.notes.folded .notes-list, .notes.folded .notes-all { display: none; }
/* Folded it is a bell, so it takes a bell's worth of room. Without this the box kept its 360px
   and the empty half of it still sat over the page, catching taps meant for what is underneath. */
.notes.folded { width: auto; }

.note {
  background: var(--card); border: 1px solid var(--line); border-left: 3px solid var(--muted);
  border-radius: var(--r); padding: .6rem .7rem; box-shadow: var(--shadow-lg);
  font-size: .86rem; user-select: text;
}
.note.working { border-left-color: var(--warn); }
.note.done { border-left-color: var(--ok); }
.note.error { border-left-color: var(--danger); background: #fdecec; }
.note-head { display: flex; align-items: flex-start; gap: .4rem; }
.note-head strong { flex: 1; min-width: 0; font-size: .88rem; line-height: 1.3; }
.note-ico { flex: none; }
.note-x {
  background: transparent; border: none; color: var(--muted); cursor: pointer;
  font-size: 1.1rem; line-height: 1; padding: 0 .15rem; flex: none;
}
.note-x:hover { color: var(--ink); filter: none; }
.note-body { margin: .3rem 0 0; color: var(--ink-2); overflow-wrap: anywhere; }
.note-foot {
  margin-top: .4rem; display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap;
  font-size: .78rem;
}
.note-who { color: var(--muted); }
.note-go { font-weight: 600; text-decoration: none; }
.note-go:hover { text-decoration: underline; }

/* Something is in flight. Replaces the blocking overlay: it informs without taking the app
   away, because the work now happens in the background either way. */
.working-pill {
  display: inline-flex; align-items: center; gap: .3rem; flex: none;
  background: var(--warn-bg); border: 1px solid #f0d98a; color: #7a5b00;
  border-radius: 999px; padding: .1rem .55rem; font-size: .78rem; white-space: nowrap;
}
.working-pill[hidden] { display: none; }

/* On a phone the stack is full width, so two notifications cover half the screen — the list of
   topics, the step being filled in, the row menu that was just opened. That is why it starts
   FOLDED here (see caNotesFold in base.html): the bell with its count stays on screen, nothing
   is dropped, and the work stays visible. Opened, it is capped against the SCREEN with dvh —
   `vh` on a phone means the viewport with the browser chrome hidden, which is not the height
   anything is actually laid out in. */
@media (max-width: 640px) {
  .notes {
    left: var(--sp-3); right: var(--sp-3); width: auto;
    bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
  }
  .notes-list { max-height: min(45dvh, 320px); }
  /* Folded: the box is the pill, not a full-width bar with a pill in it. */
  .notes.folded { left: auto; }
}
/* A short screen (a phone in landscape) has the same problem measured the other way: 60vh of
   393px is 236px of notifications over a drawer that has 393px in total. */
@media (max-height: 520px) {
  .notes-list { max-height: min(50dvh, 260px); }
}
/* A tablet is neither: 768x1024 is wide enough to miss every phone rule and small enough that
   520px of cards is half the screen. Opened by hand there, the stack stays a panel — not a
   second page. */
@media (max-width: 1100px) and (pointer: coarse) {
  .notes-list { max-height: min(42dvh, 360px); }
}

/* Toast stack (JS-driven) */
/* Transient confirmations live at the TOP right; the bottom-right corner belongs to the
   notification stack, which waits to be read and must never be covered by a message that is
   about to vanish anyway. */
#toasts { position: fixed; right: var(--sp-4); top: 4.2rem; z-index: 300; display: grid; gap: var(--sp-2); }
#toasts .toast {
  box-shadow: var(--shadow-lg); margin: 0; background: var(--card); border-color: var(--line);
  color: var(--ink); max-width: min(480px, 90vw); display: flex; gap: var(--sp-2);
  align-items: flex-start; user-select: text;
}
/* A failure is not a notification you glance at — it is text someone needs to read and often
   paste back to me, so it is loud, it wraps, and it waits for a click. */
#toasts .toast.bad { background: #fdecec; border-color: #f0b4b4; color: #7a2020; }
#toasts .toast .x {
  background: transparent; border: none; color: inherit; cursor: pointer; font-size: 1.1rem;
  line-height: 1; padding: 0 .1rem; opacity: .7; flex: 0 0 auto;
}
#toasts .toast .x:hover { opacity: 1; }

.post-body {
  white-space: pre-wrap; background: #fafbfd; border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: var(--sp-3); font-size: .9rem; margin: var(--sp-2) 0;
}

/* ---------- Mobile sidebar ----------
   Off-canvas with a real scrim: tapping the page behind the menu must close it (that is what
   every phone user tries first), and the page underneath must not scroll while it is open. */
.nav-scrim {
  /* `none` by default and switched on only where the menu is off-canvas: the scrim belongs to
     that state, and the pair of rules has to flip at the SAME breakpoint as the sidebar itself
     (they used to be written as 900px and 901px, which is fine until the breakpoint gains a
     second condition and one half of the pair forgets it). */
  display: none;
  position: fixed; inset: 0; z-index: 190; background: rgba(20, 22, 40, .45);
  opacity: 0; pointer-events: none; transition: opacity .16s ease;
}
body.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }

/* Off-canvas menu. The width alone was the wrong test for a TABLET held in the hand: an iPad in
   landscape is 1024px, so the fixed 232px rail stayed — 23% of the screen given over to seven
   links, permanently, in front of a seven-column table that had 768px left to live in. A
   coarse pointer means a screen somebody is holding; there the menu is a place you go, not a
   thing that watches you work. A 1100px cap keeps touch-screen laptops and desktop monitors out
   of it. */
@media (max-width: 900px), (max-width: 1100px) and (pointer: coarse) {
  .nav-scrim { display: block; }
  .sidebar {
    position: fixed; z-index: 200; left: 0; top: 0; transform: translateX(-100%);
    width: min(var(--sidebar-w), 82vw); flex-basis: auto;
    transition: transform .18s ease;
  }
  /* The desktop "collapse" state has no meaning off-canvas — the menu is either open or gone. */
  body.nav-collapsed .sidebar { width: min(var(--sidebar-w), 82vw); }
  body.nav-collapsed .sidebar .label,
  body.nav-collapsed .sidebar .sep,
  body.nav-collapsed .sidebar .brand span,
  body.nav-collapsed .sidebar .foot .who { display: initial; }
  body.nav-collapsed .sidebar nav a { justify-content: flex-start; }
  .sidebar .collapse { display: none; }
  body.nav-open .sidebar { transform: translateX(0); box-shadow: var(--shadow-lg); }
  .topbar .burger { display: inline-block; }
  .content { padding: var(--sp-4); }
}

/* A sticky bar that wraps is a sticky bar that eats the screen: three actions on /studio made
   it three rows — measured at 173px of a 740px phone, 23% of everything, on every page and at
   every scroll position. One row now, with the actions swiped sideways; the same answer the
   step strip and the settings categories already give to the same problem. */
@media (max-width: 640px) {
  .topbar { flex-wrap: nowrap; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); }
  /* …but the row still has to say WHICH page this is. With `min-width: 0` the title was the one
     thing that shrank (the buttons in the strip do not), so /studio rendered a heading reading
     "S…" — a stub that names nothing. A floor here pushes the overflow into the strip, which is
     the element built to be scrolled. */
  .topbar h1 { flex: 0 1 auto; min-width: 7rem; }
  .topbar-actions {
    display: flex; align-items: center; gap: var(--sp-2);
    flex: 0 1 auto; min-width: 0; overflow-x: auto; padding-bottom: 2px;
    scrollbar-width: thin; -webkit-overflow-scrolling: touch;
  }
  /* Inside the strip nothing may shrink, or the buttons would squash instead of scrolling. */
  .topbar-actions > *, .topbar-actions form > * { flex: 0 0 auto; }
  /* A strip you have to swipe is still a strip where the second action is off-screen. Trimming
     the padding and the label (see `.only-wide` in the markup) fits two of the three actions
     into a 390px phone, which is the difference between "there is more here" and "there is
     one button". */
  .topbar-actions .btn, .topbar-actions button { padding: .45rem .6rem; }
}

/* Touch input: 44px is the smallest target a finger hits reliably. Link-styled buttons inside
   a sentence are excluded — padding them out would break the line they sit in. */
@media (pointer: coarse) {
  button:not(.linky):not(.x), .btn, .sidebar nav a, .wz-step { min-height: 44px; }
  input:not([type=checkbox]):not([type=radio]):not([type=file]), select { min-height: 44px; }
  .check input { width: 1.15rem; height: 1.15rem; }
  /* `.x` is excluded above because most of them dismiss a note inside a sentence. These two do
     not: they are the way OUT of a panel that covers the whole phone, and at 25x22px they were
     the smallest target in the app. */
  .drawer > header .x, .zoom .x {
    min-width: 44px; min-height: 44px; display: inline-flex;
    align-items: center; justify-content: center;
  }
  /* The components added with the Studio rebuild are links and summaries, so the button rule
     above never reached them: measured 32px tall for the list switcher and 32×32 for the ⋯
     menu — both under the 40px a finger needs. */
  .seg a { min-height: 40px; display: inline-flex; align-items: center; }
  .menu > summary { width: 44px; height: 44px; }
  .tabs button { min-height: 44px; }
}
/* Anything under 16px makes iOS zoom the whole page when the field takes focus. */
@media (max-width: 640px) {
  input, select, textarea { font-size: 16px; }
  #toasts { left: var(--sp-3); right: var(--sp-3); top: 3.6rem; }
}

/* ---------- Drawer (side panel for working on a post) ---------- */
.drawer-back {
  position: fixed; inset: 0; background: rgba(20, 22, 40, .45);
  z-index: 250; opacity: 0; pointer-events: none; transition: opacity .16s ease;
}
.drawer-back.open { opacity: 1; pointer-events: auto; }

/* Width: the drawer is where the actual work happens (copy, graphics, scheduling), so it takes
   as much of a big screen as it can without swallowing the queue behind it — and the whole
   screen on a phone, where a thin sliver of background is just a mis-tap waiting to happen. */
.drawer {
  position: fixed; top: 0; right: 0; height: 100vh; width: min(1180px, 94vw);
  background: var(--card); z-index: 260; box-shadow: var(--shadow-lg);
  transform: translateX(100%); transition: transform .18s ease;
  display: flex; flex-direction: column;
}
.drawer.open { transform: translateX(0); }
.drawer { height: 100dvh; }
.drawer > header {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); background: var(--navy); color: #fff; flex: none;
}
.drawer > header .spacer { flex: 1; }
.drawer > header .x {
  background: transparent; border: none; color: #fff; font-size: 1.4rem;
  cursor: pointer; line-height: 1; padding: 0 .3rem;
}
/* By default the drawer body simply scrolls (the review card lives here too). */
.drawer .body {
  flex: 1; min-height: 0; overflow: auto; padding: var(--sp-4);
  padding-bottom: calc(var(--sp-5) + env(safe-area-inset-bottom));
}
/* The WIZARD is different: it puts a header at the top that must not move, and scrolls only
   the step underneath. Two `position: sticky` blocks used to do that job inside this padded,
   scrolling box — which is exactly why a strip of content showed between them and a gap sat
   under the nav bar: sticky pins to the SCROLLPORT, above the container's own padding.
   The class is set by the drawer's own JS, so this does not depend on :has(). */
.drawer .body.wizard {
  overflow: hidden; padding: 0; display: flex; flex-direction: column;
}
.drawer .body.wizard > .flash { margin: var(--sp-3) var(--sp-4) 0; flex: none; }
/* Held in the hand, the drawer IS the screen. A sliver of the page behind it only invites
   mis-taps — and on a tablet that sliver was 46px of a list whose rows are all clickable, right
   under the thumb that holds the device. Same breakpoint as the off-canvas menu, deliberately:
   below it nothing on the page is meant to be usable while a topic is open. */
@media (max-width: 900px), (max-width: 1100px) and (pointer: coarse) {
  .drawer { width: 100vw; }
}
@media (max-width: 640px) {
  .drawer .body { padding: var(--sp-3); }
  /* The drawer's own bar carries a word („Temat") and the way out. The wizard header underneath
     already names the topic, so this strip is worth exactly the 44px its close button needs —
     padding on top of that is 16px of a 740px screen spent twice over. */
  .drawer > header { padding: 0 var(--sp-3); }
}
@media (min-width: 1700px) { .drawer { width: min(1400px, 84vw); } }
.drawer .body > * { min-width: 0; }
/* Safety net: a card is 1536px wide, so ANY unconstrained graphic in the drawer would push
   the layout (and the page) sideways. Nothing here may be wider than its column. */
.drawer img { max-width: 100%; height: auto; }
.drawer h3 { font-size: .92rem; margin: 0 0 var(--sp-2); }
.drawer .blk { margin-top: var(--sp-3); border-top: 1px solid var(--line); padding-top: var(--sp-3); }
.drawer .blk > summary { cursor: pointer; font-weight: 600; font-size: .88rem; }
.drawer .thumbrow { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: var(--sp-2); }
.drawer .thumbrow form { margin: 0; }
.drawer .thumbrow img {
  width: 64px; height: 40px; object-fit: cover; border: 2px solid var(--line);
  border-radius: var(--r-sm); cursor: pointer;
}
.drawer .subtle { font-size: .8rem; color: var(--muted); }
.drawer .footer-actions {
  border-top: 1px solid var(--line); padding-top: var(--sp-3); margin-top: var(--sp-4);
  display: flex; gap: var(--sp-2); flex-wrap: wrap;
}

/* ---------- Drawer content: numbered sections, one save per section ---------- */
/* The review card's own two columns. `minmax(0,…)` matters: without it a long unbroken word
   or a wide image blows the column out and the halves overlap. */
.dw-cols {
  display: grid; grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: var(--sp-5); align-items: start;
}
@media (max-width: 980px) { .dw-cols { grid-template-columns: minmax(0, 1fr); } }
/* No `position: sticky` here: it would pin to the scrollport, above the body's own padding,
   and page content would scroll visibly through the gap — the bug this release removed from
   the wizard. */
.dw-left { min-width: 0; }
/* The preview is a thumbnail, not the main event — the copy has to stay readable next to it.
   Click it to open the full-size image in a new tab. */
.dw-left .dw-img {
  display: block; width: 100%; max-width: min(240px, 100%); height: auto; max-height: 190px;
  object-fit: cover; background: #f0f1f5;
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
.dw-right { min-width: 0; }
.dw-right textarea, .dw-right input, .dw-right select { max-width: 100%; }
.dw-single { grid-column: 1 / -1; max-width: 860px; min-width: 0; }

.dw-sec { border: 1px solid var(--line); border-radius: var(--r); padding: var(--sp-4); }
.dw-sec + .dw-sec { margin-top: var(--sp-4); }
.dw-sec > h3 { margin: 0 0 var(--sp-2); font-size: .95rem; }
.dw-sec.dw-danger { border-color: #f3d0d0; }
/* The step the user is actually here to do — visually obvious on open. */
.dw-sec.dw-primary { border-color: var(--coral); box-shadow: 0 0 0 3px rgba(193, 95, 85, .08); }
.dw-tag {
  display: inline-block; margin-left: .4rem; padding: .05rem .45rem; border-radius: 999px;
  background: var(--coral); color: #fff; font-size: .66rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .3px; vertical-align: middle;
}
.dw-hint { color: var(--muted); font-size: .82rem; margin: 0 0 var(--sp-2); }
.dw-ok {
  background: var(--ok-bg); border: 1px solid #bbf7d0; color: var(--ok);
  border-radius: var(--r-sm); padding: .5rem .7rem; font-size: .86rem; margin: 0 0 var(--sp-2);
}
.dw-actions { display: flex; justify-content: flex-end; margin-top: var(--sp-3); }
.dw-alt {
  margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px dashed var(--line);
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
}
.dw-alt .dw-hint { margin: 0; flex: 1; min-width: 220px; }
.dw-final {
  margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--line);
  display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center;
}
/* ---------- Image preview (lightbox) ----------
   Above the drawer (260) so a graphic can be inspected while working on the topic, below the
   blocking loader (900) so a generation in progress still owns the screen. */
.zoom {
  position: fixed; inset: 0; z-index: 400; background: rgba(14, 16, 32, .85);
  display: flex; align-items: center; justify-content: center; padding: var(--sp-5);
  cursor: zoom-out;
}
.zoom[hidden] { display: none; }
.zoom figure { margin: 0; max-width: 100%; max-height: 100%; text-align: center; }
.zoom img {
  max-width: min(1600px, 94vw); max-height: 84vh; max-height: 84dvh; width: auto; height: auto;
  border-radius: var(--r); box-shadow: var(--shadow-lg); background: #0e1020; display: block;
}
.zoom figcaption { color: #e7e9f5; font-size: .88rem; margin-top: var(--sp-3); }
.zoom .x {
  position: absolute; top: var(--sp-4); right: var(--sp-4);
  background: rgba(255, 255, 255, .12); color: #fff; border: none;
  font-size: 1.6rem; line-height: 1; padding: .2rem .6rem; border-radius: var(--r-sm);
}
/* Anything that opens in the preview says so on hover. */
.zoomable { cursor: zoom-in; }

/* A button that reads as a link — for "go to step 2" inside a sentence. */
button.linky {
  background: none; border: none; padding: 0; color: var(--navy);
  text-decoration: underline; font-size: inherit; cursor: pointer;
}
button.linky:hover { filter: none; color: var(--coral); }

.dw-adv { margin-top: var(--sp-3); }
.dw-adv > summary { cursor: pointer; font-size: .84rem; color: var(--muted); }

/* Cadence slot picker — shows WHEN you are meant to publish, not just a blank calendar. */
.slot-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--sp-2); margin-top: var(--sp-2);
}
/* Free / taken / chosen must differ by COLOUR, not by a few percent of opacity: a greyed-out
   slot read almost the same as a free one. Free is calm green, taken is amber, chosen is the
   brand coral — and every one of them keeps its contrast on hover (a chosen slot used to turn
   into white text on a near-white background). */
.slot {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: var(--ok-bg); color: #14532d; border: 1px solid #bbf7d0;
  border-radius: var(--r-sm); padding: .4rem .3rem; cursor: pointer; font-size: .78rem;
}
.slot:hover:not(:disabled) { border-color: var(--ok); background: #dcfce7; color: #14532d; }
.slot .t { font-weight: 700; font-size: .95rem; }
.slot .who { font-size: .62rem; text-transform: uppercase; letter-spacing: .3px; }
/* A free, unchosen slot says nothing — and an empty line under the time would misalign the row. */
.slot .who:empty { display: none; }

.slot.taken {
  background: var(--warn-bg); color: #92400e; border-color: #f0d98a;
  cursor: not-allowed; text-decoration: line-through;
}
.slot.taken:hover:not(:disabled) { background: #fef3c7; border-color: var(--warn); color: #92400e; }
/* In the wizard a taken slot is clickable — you take it and its occupant steps aside. */
.wiz .slot.taken { cursor: pointer; text-decoration: none; border-style: dashed; }

/* Chosen: highest specificity so hover can never wash it out. */
.slot.picked, .slot.picked:hover, .slot.taken.picked, .slot.taken.picked:hover {
  background: var(--coral); border: 1px solid var(--coral); border-style: solid; color: #fff;
  box-shadow: 0 0 0 3px rgba(193, 95, 85, .22);
}

/* Legend for the grid — what the colours mean, said once. */
.slot-legend {
  display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center;
  font-size: .76rem; color: var(--muted); margin-top: var(--sp-2);
}
.slot-legend span { display: inline-flex; align-items: center; gap: .3rem; }
.slot-legend i {
  width: .7rem; height: .7rem; border-radius: 3px; display: inline-block; border: 1px solid;
}
.slot-legend .free { background: var(--ok-bg); border-color: #bbf7d0; }
.slot-legend .busy { background: var(--warn-bg); border-color: #f0d98a; }
.slot-legend .mine { background: var(--coral); border-color: var(--coral); }

/* ---------- Studio wizard: a fixed header, and the step scrolling under it ---------- */
.wz-head {
  flex: none; background: var(--card); border-bottom: 1px solid var(--line);
  padding: var(--sp-3) var(--sp-4) var(--sp-2);
  display: grid; gap: var(--sp-2);
}
.wz-head .wz-title { display: grid; gap: 2px; min-width: 0; }
.wz-subject {
  font-weight: 700; font-size: .95rem; min-width: 0;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden;
}
.wz-status { font-size: .78rem; color: var(--muted); }
/* The road is a horizontal strip at every width: eight steps stacked vertically ate a third of
   a laptop screen and the whole of a phone. */
.wz-steplist {
  display: flex; gap: .3rem; overflow-x: auto; padding-bottom: 2px;
  scrollbar-width: thin; -webkit-overflow-scrolling: touch;
}
.wz-steplist .wz-step { flex: 0 0 auto; width: auto; }
.wz-steplist .wz-step .l { white-space: nowrap; }
.wz-step {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  background: transparent; color: var(--ink); border: 1px solid transparent;
  border-radius: var(--r-sm); padding: .4rem .5rem; cursor: pointer;
  font-size: .87rem; text-align: left;
}
.wz-step:hover { background: #eef0f6; filter: none; }
.wz-step .n {
  width: 1.45rem; height: 1.45rem; flex: none; border-radius: 50%;
  background: #eef0f6; color: var(--ink-2);
  display: inline-flex; align-items: center; justify-content: center; font-size: .74rem;
}
.wz-step.done .n { background: var(--ok); color: #fff; }
.wz-step.on { background: var(--navy); color: #fff; }
.wz-step.on .n { background: var(--coral); color: #fff; }
.wz-step.locked { opacity: .45; cursor: not-allowed; }
/* A step the automat could not finish. Coral, never green: the tick is what stops people
   looking, and a half-written topic needs to be looked at. */
.wz-step.failed .n { background: var(--coral); color: #fff; }
.wz-step.failed .l { color: var(--coral); }
.wz-step.on.failed .n { background: #fff; color: var(--coral); }
.wz-failed {
  margin: .2rem 0 .8rem; padding: .5rem .7rem; border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--coral) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--coral) 40%, transparent);
  font-size: .9rem;
}

/* The step pane is the only thing that scrolls, and it is a query container: cards inside it
   lay themselves out by how much room the PANE has, not how wide the window is (the pane sits
   in a drawer, so the two differ a lot). */
.wz-pane {
  flex: 1; min-height: 0; min-width: 0; overflow: auto;
  padding: var(--sp-4);
  padding-bottom: calc(var(--sp-5) + env(safe-area-inset-bottom));
  container-type: inline-size; container-name: pane;
}
@media (max-width: 640px) { .wz-pane { padding: var(--sp-3); } }
/* A phone held sideways is 393px TALL. The drawer's own chrome — its bar, the subject, the step
   navigation and the strip of steps — measured 240px of that, leaving 153px for the work. Same
   header, tighter: one line of subject and smaller padding buy back about a third of it. */
@media (max-height: 520px) {
  .drawer > header { padding: var(--sp-2) var(--sp-3); }
  .wz-head { padding: var(--sp-2) var(--sp-3); gap: var(--sp-1); }
  .wz-subject { -webkit-line-clamp: 1; line-clamp: 1; }
  .wz-status { display: none; }  /* the same words are on the step itself */
}
/* A phone held UPRIGHT has the same problem in the other direction: the drawer bar, the subject,
   the status line, the navigation and the strip of steps came to 240px of a 740px screen before
   the step itself began. The subject keeps both its lines — it is the only thing on screen that
   says which topic this is — and the line that merely repeats the lit step goes. */
@media (max-width: 640px) {
  .wz-head { padding: var(--sp-2) var(--sp-3); gap: 6px; }
  .wz-status { display: none; }
}
/* Step navigation sits in the header, above the step: most visits change nothing, and those
   people should not have to scroll past a screenful of copy to find "Dalej". */
.wz-nav { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
/* Autosave feedback — the only proof the user gets that a change stuck. */
.wz-saved { font-size: .78rem; color: var(--muted); white-space: nowrap; }
.wz-saved.ok { color: var(--ok); }
.wz-saved.err { color: var(--danger); }
.wz-saved.dirty { color: var(--warn); }

/* The picture idea, in words, above the pictures. It is the step's real decision — the images
   below it are only the consequence — so it gets a panel of its own. */
.wz-idea {
  border: 1px solid var(--line); border-left: 3px solid var(--coral);
  border-radius: var(--r-sm); padding: var(--sp-3); margin-bottom: var(--sp-4);
  background: #fbfbfe;
}
.wz-idea h4 { margin: 0 0 var(--sp-2); font-size: .88rem; }
.wz-idea .wz-why {
  margin: 0 0 var(--sp-2); font-size: .85rem; color: var(--ink-2);
  border-left: 2px solid var(--line); padding-left: var(--sp-2);
}
.wz-idea textarea { min-height: 6rem; }
/* The proposals from one round: all of them, always, with the current one lit. A list that
   shows only "the others" silently consumes whatever you move away from. */
.wz-idea-alts, .wz-picks-list { display: grid; gap: var(--sp-2); margin-top: var(--sp-2); }
.wz-idea-alts form, .wz-picks-list form { margin: 0; }

/* A picture idea is one to three sentences plus a reason and a source. As a tile it was cut at
   150 characters and the other two fields were not shown at all, so the choice was being made
   on half a sentence. Full card, and the button is a control inside it — a card cannot BE a
   button and also contain one. */
.wz-idea-cards { display: grid; gap: var(--sp-3); margin-top: var(--sp-2); }
.wz-idea-card {
  margin: 0; padding: .7rem .8rem; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--card);
}
.wz-idea-card.picked {
  border-color: var(--coral); background: #fdf1f0;
  box-shadow: 0 0 0 2px rgba(193, 95, 85, .18);
}
.wz-idea-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.wz-idea-head .n {
  flex: none; width: 1.3rem; height: 1.3rem; border-radius: 50%; background: #fff;
  border: 1px solid var(--line); display: inline-flex; align-items: center;
  justify-content: center; font-size: .72rem; color: var(--ink-2);
}
.wz-idea-card.picked .n { background: var(--coral); border-color: var(--coral); color: #fff; }
.wz-idea-scene { margin: .5rem 0; font-size: .9rem; line-height: 1.5; color: var(--ink); }
.wz-idea-card .wz-why { margin: 0 0 .3rem; }
.wz-idea-foot { margin-top: .5rem; gap: var(--sp-2); flex-wrap: wrap; }
/* Where the scene comes from in the material — the only field that can be checked against the
   fact sheet, so it reads as evidence rather than as prose. */
.wz-src {
  margin: 0 0 var(--sp-2); font-size: .8rem; color: var(--muted); line-height: 1.45;
}
/* A proposal is a sentence, not a chip: let it wrap and read left-aligned. */
.wz-option {
  width: 100%; text-align: left; white-space: normal; display: flex; gap: var(--sp-2);
  align-items: flex-start; font-size: .84rem; line-height: 1.35; padding: .5rem .7rem;
  background: #eef0f6; color: var(--ink); border: 1px solid var(--line);
}
.wz-option:hover { background: #e6e9f2; filter: none; }
.wz-option .n {
  flex: none; width: 1.3rem; height: 1.3rem; border-radius: 50%; background: #fff;
  border: 1px solid var(--line); display: inline-flex; align-items: center;
  justify-content: center; font-size: .72rem; color: var(--ink-2);
}
.wz-option .t { flex: 1; min-width: 0; }
/* "carried from an earlier round" tag — right-aligned so the sentence still leads. */
.wz-option .chip { flex: none; align-self: center; margin-left: auto; }
/* Chosen: the same coral the slot picker uses, so "this is the one" reads the same everywhere. */
.wz-option.picked, .wz-option.picked:hover {
  background: #fdf1f0; border-color: var(--coral); color: var(--ink);
  box-shadow: 0 0 0 2px rgba(193, 95, 85, .18);
}
.wz-option.picked .n { background: var(--coral); border-color: var(--coral); color: #fff; }

/* ---------- What a post is FOR (step 2) ----------
   Four cards, not a dropdown: the choice changes how the text is written, so the difference
   between the options has to be readable at the moment of choosing. */
.kind-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--sp-3); margin-top: var(--sp-2);
}
.kind {
  display: grid; gap: .25rem; margin: 0; cursor: pointer; position: relative;
  border: 1px solid var(--line); border-radius: var(--r); padding: .7rem .8rem;
  background: var(--card); color: var(--ink);
}
.kind:hover { border-color: var(--navy); }
.kind input { position: absolute; opacity: 0; width: 0; height: 0; }
.kind-head { font-weight: 700; font-size: .92rem; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.kind-goal { font-size: .84rem; color: var(--ink-2); }
.kind-conv { font-size: .76rem; color: var(--muted); }
/* Exactly one card in a group may look chosen. `.picked` is set by the drawer's own JS from
   the checked radio (never rendered by the server: saving here does not re-render the step, so
   a server-rendered class went on marking the PREVIOUS choice and two cards lit up at once).
   `:has()` does the same thing on its own where it is supported — the two always agree because
   both read the same radio. */
.kind:has(input:checked), .kind.picked {
  border-color: var(--coral); background: #fdf1f0; box-shadow: 0 0 0 2px rgba(193, 95, 85, .18);
}
/* Said in words as well as in colour: a border is not an answer to "which one is on?". */
.kind-mark {
  display: none; margin-left: auto; font-size: .72rem; font-weight: 700;
  color: var(--coral); white-space: nowrap;
}
.kind:has(input:checked) .kind-mark, .kind.picked .kind-mark { display: inline; }
/* The register choice is prose, not a label — give it room to read. */
.rt-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

/* Plan against reality. Shown where it can still change something — the moment of choosing. */
.mix-box {
  border: 1px solid var(--line); border-left: 3px solid var(--navy); background: #fbfbfe;
  border-radius: var(--r-sm); padding: .6rem .8rem; margin-bottom: var(--sp-3);
}
.mix-why { font-size: .86rem; font-weight: 600; margin-bottom: .5rem; }
.mix-bars { display: grid; gap: .3rem; }
.mix-row { display: grid; grid-template-columns: minmax(0, 9rem) 1fr auto; gap: .5rem; align-items: center; font-size: .8rem; }
.mix-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mix-bar { position: relative; height: 1rem; background: #eef0f6; border-radius: 999px; overflow: hidden; }
.mix-bar i { position: absolute; top: 0; bottom: 0; left: 0; display: block; border-radius: 999px; }
.mix-bar .plan { background: #c9cfe6; }
.mix-bar .real { background: var(--navy); height: .5rem; top: .25rem; }
.mix-num { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 520px) {
  .mix-row { grid-template-columns: 1fr auto; }
  .mix-bar { grid-column: 1 / -1; }
}

/* The register switch (real-time vs evergreen) — a different question from the kind. */
.rt-box { margin-top: var(--sp-3); border-top: 1px dashed var(--line); padding-top: var(--sp-3); }

.wz-shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-3); }
.wz-shots figure { margin: 0; }
.wz-shots img {
  width: 100%; height: auto; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: #f0f1f5; display: block;
}
/* A thumbnail that is still being drawn must LOOK like one. The first view of a topic renders
   its card server-side (seconds on that machine), and an <img> with nothing behind it is
   indistinguishable from a broken one — which is exactly how "grafiki nie widać" was reported
   for a picture that was merely on its way. The frame keeps its space and shimmers until the
   image reports back, so waiting looks like waiting. */
/* Why the last attempt produced no picture — kept on the step, selectable, not a passing note. */
.art-fail {
  margin-top: .45rem; padding: .5rem .7rem; border-radius: var(--r-sm);
  background: #fdecec; border: 1px solid #f0b4b4; color: #7a2020; font-size: .85rem;
  user-select: text;
}

.shot-wrap { position: relative; display: block; min-height: 90px; border-radius: var(--r-sm); }
.shot-wrap.loading { background: #eef0f5; overflow: hidden; }
.shot-wrap.loading::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, .75) 50%, transparent 80%);
  transform: translateX(-100%); animation: shot-shimmer 1.3s ease-in-out infinite;
}
.shot-wrap.loading .shot-note { display: block; }
.shot-note {
  display: none; position: absolute; inset: auto 0 0 0; text-align: center;
  font-size: .72rem; color: var(--muted); padding: .3rem; z-index: 1;
}
@keyframes shot-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .shot-wrap.loading::after { animation: none; } }

.wz-shots figcaption {
  font-size: .78rem; color: var(--muted); margin-top: .2rem;
  display: flex; align-items: center; gap: .35rem; flex-wrap: wrap;
}
.wz-shots figcaption .pill { font-size: .68rem; }
.wz-shots .dw-adv { margin-top: .35rem; }
.wz-shots form { margin: 0; }
/* `select.auto` sizes itself to its longest option, and a designer's file name is long: measured
   at 417px inside a 269px column, which pushed the whole step sideways. Inside a platform column
   every control is capped at the column — the full name still shows in the option list. */
.wz-shots .dw-adv input, .wz-shots .dw-adv select { max-width: 100%; width: 100%; }
.wz-shots .dw-adv button { max-width: 100%; }
.wz-shots .dw-adv input[type=file] { font-size: .72rem; }
.wz-shots .dw-adv select { text-overflow: ellipsis; }
.thumbrow img.picked { border-color: var(--coral); }

/* Earlier artworks: a thumbnail you can enlarge, with its own "use this one" action —
   the picture and the decision are separate clicks, so looking is never a commitment. */
.wz-versions {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
  gap: var(--sp-3); margin-top: var(--sp-2);
}
.wz-versions figure { margin: 0; display: grid; gap: .3rem; justify-items: center; }
.wz-versions img {
  width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block;
  border: 2px solid var(--line); border-radius: var(--r-sm); background: #f0f1f5;
}
.wz-versions figure.picked img { border-color: var(--coral); }
.wz-versions figcaption { font-size: .78rem; color: var(--muted); }
.wz-versions form { margin: 0; }

.wz-picks { display: flex; gap: var(--sp-4); flex-wrap: wrap; }
.wz-pick { gap: .4rem; }

/* One card per platform, read top to bottom: the graphic (what the reader sees first), how to
   change it, then the copy at FULL width. The previous side-by-side grid squeezed the textarea
   into ~330px inside the drawer while giving the thumbnail a fixed 200px — the copy is the
   thing being edited here, so it gets the whole width and the picture stays a preview. */
.wz-cards { display: grid; gap: var(--sp-4); }
.wz-card > h3 { margin-bottom: var(--sp-3); }
.wz-card-art { margin: 0 0 var(--sp-3); max-width: 340px; }
.wz-card-art img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: #f0f1f5;
}
.wz-card-art figcaption { font-size: .78rem; color: var(--muted); margin-top: var(--sp-2); }
.wz-card textarea { width: 100%; max-width: 100%; min-height: 16rem; resize: vertical; }
/* When the STEP PANE (not the window) is wide enough, the preview and its caption sit side by
   side. A container query is the honest test here: the pane lives inside a drawer, so the
   viewport width says nothing about how much room this card actually has. Where container
   queries are unsupported the card simply stays stacked, which is the safe layout anyway. */
@container pane (min-width: 620px) {
  .wz-card-art {
    max-width: none; display: grid; grid-template-columns: 300px minmax(0, 1fr);
    gap: var(--sp-4); align-items: center;
  }
  /* Cap the measure: on a 2560px screen the caption column is otherwise 700px of one-line prose. */
  .wz-card-art figcaption { margin-top: 0; max-width: 48ch; }
}

/* The snapshot link that is deliberately NOT in the post: Instagram makes no url clickable and
   the other two are believed to charge reach for one, so a person pastes it as the first comment.
   Presented as something to COPY, not to read — hence the monospace-ish field and the button. */
.wz-aside {
  margin-top: var(--sp-3); padding: var(--sp-3);
  border: 1px dashed var(--line); border-radius: var(--r-sm); background: #fbfbfe;
}
.wz-aside-head { font-size: .82rem; color: var(--muted); margin-bottom: var(--sp-2); }
.wz-aside-row { align-items: center; gap: var(--sp-2); flex-wrap: nowrap; }
.wz-aside-row + .wz-aside-row { margin-top: var(--sp-2); }
/* Which of the two snapshots this is. Fixed width so the two rows line up and the eye can
   compare them without reading the URLs. */
.wz-aside-tag {
  flex: 0 0 8.5rem; font-size: .78rem; font-weight: 600; color: var(--ink-2);
}
/* Two lines exactly: the lead-in and the address. Read-only, so it never grows and nobody edits
   a UTM by accident — a copy box is for copying.
   TWO class names on purpose: the post's own editor sets `.wz-card textarea { min-height: 16rem }`
   and this box lives inside that card, so a single class loses on specificity and the copy box
   came out 256px tall. `min-height` is reset explicitly for the same reason. */
.wz-aside .wz-aside-text {
  flex: 1; min-width: 0; resize: none; font-size: .78rem; line-height: 1.35;
  min-height: 0; height: 3.2rem; padding: .3rem .45rem;
  background: #fff; overflow-wrap: anywhere;
}
.wz-aside .btn { flex: 0 0 auto; padding: .35rem .55rem; }
@container pane (max-width: 480px) {
  .wz-aside-row { flex-wrap: wrap; }
  .wz-aside-tag { flex: 1 1 100%; }
}

/* Plan a date or publish now — a two-way switch, not a hidden menu. */
/* Five options (date / now / done by hand / not at all / leave it) do not fit one phone row,
   and a pill strip that overflows hides the last choice — which here is "nie publikuję". */
.wz-switch { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; margin: var(--sp-2) 0; }
.wz-switch label { margin: 0; padding: .35rem .9rem; cursor: pointer; font-size: .86rem; color: var(--ink); }
.wz-switch label:has(input:checked) { background: var(--navy); color: #fff; }
.wz-switch input { position: absolute; opacity: 0; width: 0; }
/* A platform that is being left alone or skipped stays readable but stops competing for
   attention with the ones that are actually going out. */
.pub-row.pub-off { opacity: .72; }

/* ---------- Scheduling wizard ---------- */
.wiz-steps { list-style: none; margin: 0 0 var(--sp-4); padding: 0; display: grid; gap: .3rem; }
.wiz-steps li {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: .4rem .6rem; border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: .86rem;
}
.wiz-steps li .n {
  width: 1.4rem; height: 1.4rem; flex: none; border-radius: 50%; background: #eef0f6;
  display: inline-flex; align-items: center; justify-content: center; font-size: .74rem;
}
.wiz-steps li .p { font-weight: 600; }
.wiz-steps li .w { margin-left: auto; color: var(--muted); }
.wiz-steps li.done { background: var(--ok-bg); border-color: #bbf7d0; }
.wiz-steps li.done .n { background: var(--ok); color: #fff; }
.wiz-steps li.now { border-color: var(--coral); }
.wiz-steps li.now .n { background: var(--coral); color: #fff; }
.pill.ok { background: var(--ok-bg); color: var(--ok); }
.pill.wait { background: var(--warn-bg); color: var(--warn); }

/* --- Press sources behind a topic (step 1) --------------------------------- */
.src-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.src {
  border: 1px solid var(--line); border-left: 3px solid var(--line);
  border-radius: var(--r-sm); padding: .55rem .7rem; background: #fbfbfd;
}
.src.fetched { border-left-color: var(--ok); }
.src.blocked, .src.failed { border-left-color: var(--warn); }
.src-head { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: baseline; }
.src-head a { font-weight: 600; word-break: break-word; }
.src-facts {
  white-space: pre-wrap; font-size: .82rem; line-height: 1.45; margin: .4rem 0 0;
  font-family: inherit; color: var(--ink-2);
}
/* The long read, in our own words: prose, so it wraps like prose (the fact sheet is a card and
   keeps its line breaks; a digest formatted the same way reads as a wall). */
.src-digest {
  font-size: .82rem; line-height: 1.55; margin: .4rem 0 0; color: var(--ink-2);
  max-height: 22rem; overflow-y: auto;
}
.src details summary { cursor: pointer; font-size: .82rem; color: var(--muted); margin-top: .3rem; }
/* "This topic is one sentence" — a warning where the hook is made, not a blocker. */
.wz-thin {
  margin-top: .7rem; padding: .6rem .8rem; border-radius: var(--r);
  border: 1px solid #e6c98a; background: #fdf6e6; color: var(--ink); font-size: .86rem;
  line-height: 1.5;
}
.wz-thin form { margin: 0; }
.src-actions { margin-top: .45rem; gap: var(--sp-2); flex-wrap: wrap; }
.src-actions form { margin: 0; }
.chip {
  font-size: .72rem; padding: .12rem .5rem; border-radius: 999px;
  background: #eef0f6; color: var(--ink-2); border: 1px solid var(--line);
}
.chip.ok { background: var(--ok-bg); color: var(--ok); border-color: #bbf7d0; }
.chip.warn { background: var(--warn-bg); color: var(--warn); border-color: #fde68a; }

/* --- Artwork state, said in words (step 3) -------------------------------- */
.art-state {
  border: 1px solid var(--line); border-left: 3px solid var(--muted);
  border-radius: var(--r-sm); padding: .5rem .7rem; margin: .6rem 0;
  font-size: .88rem; background: #fbfbfd;
}
.art-state.ok { border-left-color: var(--ok); }
.art-state.warn { border-left-color: var(--warn); background: var(--warn-bg); }
.art-state .subtle { display: block; }
.img-fail {
  display: block; padding: .6rem; border: 1px dashed var(--warn);
  border-radius: var(--r-sm); color: var(--warn); font-size: .8rem; background: var(--warn-bg);
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Co recenzent zgłosił do gotowego tekstu (ADR-039). Zwinięte, bo w normalnym dniu uwagi są już
   naprawione i to jest dowód, a nie zadanie — ale dowód musi dać się otworzyć. */
.wz-review {
  margin: 0 0 var(--sp-3); padding: .5rem .7rem; border-radius: var(--r-sm);
  background: var(--warn-bg); border: 1px solid #f0d98a; font-size: .84rem;
}
.wz-review > summary { cursor: pointer; color: #7a5b00; font-weight: 600; }
.wz-review ul { margin: .45rem 0 0; padding-left: 1.1rem; color: var(--ink-2); }
.wz-review li + li { margin-top: .25rem; }
/* „Sprawdzone i czysto" to inna informacja niż „nikt nie sprawdzał", więc ma własne zdanie. */
.wz-review-ok { margin: 0 0 var(--sp-3); font-size: .82rem; color: var(--ok); }
/* Mgła powyżej progu: nadal informacja, nie błąd — stąd ostrzeżenie, nie czerwień. */
.wz-review-ok.wz-fog-warn { color: var(--warn, #b4690e); }
