/*
 * Baseline stylesheet.
 *
 * Component styles do NOT live here — they will live in the `style` table and be
 * assembled per request. That engine is not built yet; see docs/ROADMAP.md.
 * This file is the ground the site stands on: reset, typography, the handful of
 * rules the seed components use, and the admin panel (docs/ADMIN.md).
 */

:root {
  --ink: #14161a;
  --muted: #5c6470;
  --line: #e3e6ea;
  --bg: #ffffff;
  --accent: #2b5cff;
  --radius: 10px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e8eaee;
    --muted: #9aa3b0;
    --line: #262b33;
    --bg: #101317;
    --accent: #7d9bff;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

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

.layout { max-width: 68rem; margin: 0 auto; padding: 0 1.25rem; }

.site-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem 0;
  border-bottom: 1px solid var(--line);
}
.site-header .brand { font-weight: 650; font-size: 1.15rem; text-decoration: none; color: var(--ink); }
.site-header nav { display: flex; gap: 1rem; }

main { padding: 2rem 0 3rem; }

.site-footer { padding: 1.5rem 0; border-top: 1px solid var(--line); color: var(--muted); font-size: 0.9rem; }

h1 { font-size: 1.9rem; line-height: 1.2; margin: 0 0 0.75rem; }
h2 { font-size: 1.15rem; margin: 0 0 0.35rem; }

.hero p { color: var(--muted); max-width: 42rem; }

.button {
  display: inline-block;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  text-decoration: none;
}

.products { display: grid; gap: 1rem; }

.card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
}
.card h2 a { text-decoration: none; }
.card .summary { color: var(--muted); margin: 0.25rem 0 0.5rem; }
.price { font-variant-numeric: tabular-nums; margin: 0 0 0.5rem; }

.badge { font-size: 0.8rem; padding: 0.15rem 0.5rem; border-radius: 999px; border: 1px solid var(--line); }
.badge.in { color: #1a7f4b; }
.badge.out { color: #a13b3b; }

.notice, .empty { color: var(--muted); }

.is-highlighted { color: var(--accent); }


/* ===========================================================================
 * Admin panel.
 *
 * Here rather than in the `style` table for one reason: the admin has to be
 * usable while the style engine does not exist, and it has to keep working if
 * someone breaks their own styles. It is the tool you fix the site with.
 * =========================================================================== */

.admin { display: flex; flex-direction: column; min-height: 100vh; }

.admin-bar {
  display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap;
  padding: 0.6rem 1.2rem;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 92%, var(--ink));
}
.admin-brand { font-weight: 600; text-decoration: none; color: var(--ink); }
.admin-nav { display: flex; gap: 1rem; flex-wrap: wrap; }
.admin-nav a { color: var(--muted); text-decoration: none; }
.admin-nav a:hover { color: var(--accent); }
.admin-signout { margin-left: auto; display: flex; align-items: center; gap: 0.75rem; }
.admin-who { color: var(--muted); font-size: 0.85rem; }
.admin-who em { font-style: normal; padding: 0.1rem 0.4rem; border: 1px solid var(--line); border-radius: 999px; }
/* BP-565: «Sign out» was a bare link at the far end of the bar, and was not found. */
.admin-signout button {
  padding: 0.3rem 0.8rem; border: 1px solid var(--line); border-radius: 6px;
  background: transparent; color: var(--ink); font: inherit; font-size: 0.85rem; cursor: pointer; text-decoration: none;
}
.admin-signout button:hover { border-color: var(--accent); color: var(--accent); }
.admin-main { flex: 1; padding: 1.5rem 1.2rem; max-width: 68rem; width: 100%; margin: 0 auto; }
.admin-foot { padding: 1rem 1.2rem; border-top: 1px solid var(--line); color: var(--muted); }

.admin-login { max-width: 22rem; margin: 6rem auto; padding: 0 1rem; }

.page-head { display: flex; align-items: baseline; gap: 1rem; margin-bottom: 1.25rem; }
.page-head h1 { margin: 0; flex: 1; }

.panel {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1rem 1.15rem; margin-bottom: 1.25rem;
}
.panel h2 { margin-top: 0; font-size: 1.05rem; }
.panel h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); margin: 1rem 0 0.4rem; }
.panel.warn { border-color: #d9a441; }
.panel.notice { color: var(--muted); }

/* The component preview: one component, on its own, editable by clicking.
   Resizable rather than tall-by-guess — a nav bar and a landing page are not
   the same shape, and the frame has no way to know which it is holding. */
.preview-frame {
  width: 100%; height: 32rem; min-height: 8rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); resize: vertical; overflow: auto; display: block;
}

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: 0.75rem; margin-bottom: 1.5rem; }
.stat { border: 1px solid var(--line); border-radius: var(--radius); padding: 0.75rem 0.9rem; }
.stat b { display: block; font-size: 1.7rem; font-variant-numeric: tabular-nums; }
.stat span { color: var(--muted); font-size: 0.85rem; }

/* Forms ------------------------------------------------------------------ */

.field { margin-bottom: 1rem; }
.field label { display: block; font-weight: 500; margin-bottom: 0.3rem; }
.field input[type="text"], .field input[type="email"], .field input[type="password"],
.field input[type="number"], .field input[type="date"], .field select, .field textarea {
  width: 100%; padding: 0.5rem 0.6rem;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--ink); font: inherit;
}
.field input[type="checkbox"] { width: auto; }
.field .req { color: #a13b3b; }
.help { color: var(--muted); font-size: 0.85rem; margin: 0.3rem 0 0; }

.mono, textarea.mono, input.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; }
textarea[data-editor] { min-height: 24rem; line-height: 1.45; tab-size: 2; }

/* `hidden` means hidden.
 *
 * The attribute's styling is a UA rule — `[hidden] { display: none }` at the
 * bottom of the cascade — so ANY author rule that sets `display` on the same
 * element beats it, and the element stays on screen with its intent written
 * plainly in the markup. It is invisible in the HTML, invisible in the diff,
 * and visible only on the page.
 *
 * This shop was caught by it four times: `.cart-panel`, `.cart-badge`,
 * `.consent`, `.pay-choice` — each of which grew a `display` and then needed a
 * hand-written `[hidden] { display: none }` beside it to stay hideable — and
 * `.book-card` was about to be the fifth. Those patches are gone with this
 * rule and the next one cannot be needed.
 *
 * `!important` is the point rather than a shortcut. `hidden` is a statement
 * about the document, not a suggestion about its appearance, and there is no
 * design in which an element carrying it should be seen. See D145. */
[hidden] { display: none !important; }

.actions { display: flex; gap: 0.75rem; margin-top: 1.25rem; }
button { font: inherit; padding: 0.5rem 1rem; border-radius: 6px; border: 1px solid var(--accent); background: var(--accent); color: #fff; cursor: pointer; }
/* Two different disabled states, and they had one appearance between them.
   `opacity: 0.6` was written for the second or so a form spends in flight; on an
   order that ALREADY has a waybill it left ПОДГОТВИ looking very much like a
   button somebody should press. A permanently disabled control is drawn flat and
   grey so that "there is nothing to do here" is legible across a room. */
button:disabled { background: none; border-color: var(--line); color: var(--muted); cursor: not-allowed; }
form.is-busy button:disabled:not(.link) {
  background: var(--accent); border-color: var(--accent); color: #fff;
  opacity: 0.6; cursor: progress;
}
button.link, button.link-danger { background: none; border: none; padding: 0; color: var(--muted); text-decoration: underline; }
button.link-danger { color: #a13b3b; }
form.inline { display: inline-block; margin-right: 0.75rem; }
form.danger { margin-top: 1.5rem; }

/* Messages painted by app/functions/admin-form.js ------------------------- */

.form-message { padding: 0.55rem 0.8rem; border-radius: 6px; margin: 0 0 1rem; }
.form-message.ok { background: color-mix(in srgb, #1a7f4b 12%, transparent); color: #1a7f4b; }
.form-message.error { background: color-mix(in srgb, #a13b3b 12%, transparent); color: #a13b3b; }
.field-error { color: #a13b3b; font-size: 0.85rem; margin: 0.3rem 0 0; }
.has-error { border-color: #a13b3b !important; }
.template-errors { margin-top: 0.5rem; color: #a13b3b; font-size: 0.85rem; }
.template-errors ul { margin: 0; padding-left: 1.1rem; }
.is-dirty .actions::after { content: "unsaved"; color: var(--muted); font-size: 0.85rem; align-self: center; }

/* Tables ----------------------------------------------------------------- */

.grid { width: 100%; border-collapse: collapse; }
.grid th, .grid td { text-align: left; padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.grid th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.grid td.num, .grid td.right { text-align: right; font-variant-numeric: tabular-nums; }
.grid form { display: inline; }

.tag { font-size: 0.75rem; padding: 0.1rem 0.45rem; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); }
.tag.published { color: #1a7f4b; }
.tag.draft { color: var(--muted); }
.tag.error { color: #a13b3b; }
.chip { display: inline-block; font-size: 0.85rem; padding: 0.15rem 0.5rem; margin: 0 0.3rem 0.3rem 0; border: 1px solid var(--line); border-radius: 999px; text-decoration: none; color: inherit; }

.muted { color: var(--muted); }
.list { margin: 0; padding-left: 1.1rem; }
.log { margin: 0.3rem 0; font-size: 0.9rem; }
.pager { color: var(--muted); font-size: 0.9rem; }
.row-card h2 { margin: 0 0 0.2rem; font-size: 1rem; }


/* ===========================================================================
 * The paper order admin (docs/PAPER-ADMIN.md).
 *
 * Here rather than in the `style` table for the same reason the admin panel is:
 * it is a tool people use to move money and book couriers, and it has to keep
 * working while the style engine does not exist and if someone breaks their own
 * styles. Built on the same variables as everything above, so it follows the
 * light and dark palettes without a second definition.
 * =========================================================================== */

.paper { display: flex; flex-direction: column; min-height: 100vh; }

.paper-bar {
  display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap;
  padding: 0.6rem 1.2rem;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 92%, var(--ink));
  position: sticky; top: 0; z-index: 5;
}
.paper-nav { display: flex; gap: 1rem; flex-wrap: wrap; }
.page-head .head-side { display: flex; align-items: center; gap: 0.75rem; }

/* Filters ------------------------------------------------------------------ */

.filters { padding: 0.75rem 1rem; }
.filter-row { display: flex; gap: 0.75rem; align-items: flex-end; flex-wrap: wrap; }
.filter-row input[type="text"], .filter-row select {
  padding: 0.4rem 0.55rem; border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--ink); font: inherit; min-width: 11rem;
}
.filter-row .chip { align-self: center; }

/* One order ---------------------------------------------------------------- */

.orders { display: flex; flex-direction: column; gap: 0.9rem; margin: 0.5rem 0 1.5rem; }

.order {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: color-mix(in srgb, var(--bg) 97%, var(--ink));
  overflow: hidden;
}
.order.odd { background: var(--bg); }

.order-field .mark { flex: 0 0 auto; color: var(--muted); }
.order-field .value { min-width: 0; overflow-wrap: anywhere; }

.order .legacy { margin: 0; padding: 0.4rem 1rem; font-size: 0.8rem; border-bottom: 1px solid var(--line); }

.order-product .qty { color: var(--muted); flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.order-product .title { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
/* A product whose page is known is an <a class="title">, one whose page is not
   is a <span class="title">. Both keep the row's layout because the selector
   above is a class; only the link takes the underline, and only on hover, so a
   long order does not read as a wall of blue. */
a.title { color: inherit; text-decoration: none; }
a.title:hover, a.title:focus-visible { text-decoration: underline; color: var(--link, #0645ad); }
.order-product.empty { color: var(--muted); font-size: 0.9rem; }
.order-product .money { text-align: right; }

.money { font-variant-numeric: tabular-nums; white-space: nowrap; }

td.money, th.money { text-align: right; }

.order-total {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 0.5rem 1rem; border-top: 1px solid var(--line);
  font-weight: 600;
}

.order-speditor .tracking { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; }
.order .form-message { margin: 0.4rem 1rem; }

.order-shipment .shipment-title { font-weight: 600; }

.shipment-form .notice { margin: 0 0 0.8rem; }
.shipment-grid .field { display: flex; flex-direction: column; gap: 0.25rem; margin: 0; }
.shipment-grid .field > span { font-size: 0.8rem; color: var(--muted); }
.shipment-grid .field small { font-size: 0.75rem; }
/* An address is longer than a name; giving it the whole row stops it being the
   one field nobody can read what they typed into. */
.shipment-grid .wide { grid-column: 1 / -1; }
.shipment-form .actions { margin-top: 0.9rem; }

/* The two place fields: a search box, and the courier's own list under it. The
   SELECT is the answer — it holds the ids a shipment is actually made with — so
   it is the emphasised control, and the search box above it reads as the way to
   reach it rather than as a competing field. Nothing typed into the search box
   is submitted or stored; see app/functions/paper-orders.js. */
.shipment-grid .field.place select {
  border-color: color-mix(in srgb, var(--line) 40%, var(--accent));
}
.shipment-grid .field.place .place-search {
  font-size: 0.85rem;
  background: color-mix(in srgb, var(--bg) 94%, var(--ink));
}
/* The office dropdown and the address box are alternatives, and the toggle above
   them says which one this parcel uses. Which is hidden is set only by the
   FUNCTION — with no JavaScript neither is, and the form is the one migration
   012 shipped, with both fields visible. `display: flex` on `.field` used to
   need `[hidden] { display: none }` restated here to beat it; the global rule
   near the top of this file does that for every element now (D145). */
/* A delivery to an address has no office to choose. The dimming stays for the
   moment between the toggle changing and the field being hidden, and for a form
   whose office field is on screen but not usable. */
.shipment-grid .field.place.is-address select,
.shipment-grid .field.place.is-address .combo-button,
.shipment-grid .field.place.is-address .place-search { opacity: 0.45; }

/* The office/address toggle: two radios drawn as one segmented control. Real
   radios, because this is the control that decides where a parcel goes and it
   has to work in a form that is submitted without JavaScript — the appearance is
   CSS over the inputs, never a div pretending to be one. */
.shipment-grid .field.dest .toggle {
  display: flex; border: 1px solid var(--line); border-radius: 4px; overflow: hidden;
}
.shipment-grid .field.dest .toggle label {
  flex: 1; margin: 0; text-align: center; cursor: pointer;
  padding: 0.35rem 0.5rem; font-size: 0.9rem;
  background: color-mix(in srgb, var(--bg) 94%, var(--ink));
}
.shipment-grid .field.dest .toggle label + label { border-left: 1px solid var(--line); }
/* The input itself is off-screen rather than `display: none`, so it keeps its
   place in the tab order and can still be reached from the keyboard. */
.shipment-grid .field.dest .toggle input {
  position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none;
}
.shipment-grid .field.dest .toggle label:has(input:checked) {
  background: var(--accent); color: var(--bg); font-weight: 500;
}
.shipment-grid .field.dest .toggle label:has(input:focus-visible) {
  outline: 2px solid var(--accent); outline-offset: -2px;
}

/* A place field is one control: its search box is moved inside the list it
   searches (migrations 015 and 016, app/functions/paper-orders.js). Both fields,
   both forms. The <select> is still the control that is submitted — it is hidden,
   not removed, and only once the JavaScript that replaces it has actually run and
   added `has-combo`. Without that class, and so without JavaScript, everything
   below is inert and the field is the plain box-above-a-dropdown it has always
   been. */
.shipment-grid .field.place.has-combo > select { display: none; }
.shipment-grid .field.place .combo { position: relative; }
.shipment-grid .field.place .combo-button {
  width: 100%; text-align: left; padding: 0.35rem 1.6rem 0.35rem 0.5rem;
  background: var(--bg); color: var(--ink); font: inherit; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--line) 40%, var(--accent));
  border-radius: 4px;
  /* One line, ellipsised. A town with its disambiguator is long, and a control
     that grows to fit reflows the whole grid. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The caret, so a closed combobox reads as something that opens. */
.shipment-grid .field.place .combo::after {
  content: "▾"; position: absolute; right: 0.5rem; top: 0.4rem;
  pointer-events: none; color: var(--muted); font-size: 0.8rem;
}
/* Nothing chosen: the placeholder — or, on an order with no waybill, the
   customer's own words — is greyed, because it is a thing to match and not a
   thing that was matched. */
.shipment-grid .field.place .combo.is-empty .combo-button { color: var(--muted); }
/* An address delivery has no office to choose. */
.shipment-grid .field.place .combo-button[disabled] { cursor: default; }

.shipment-grid .field.place .combo-panel {
  position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 2px);
  display: flex; flex-direction: column; gap: 0.3rem; padding: 0.4rem;
  background: var(--bg); border: 1px solid var(--line); border-radius: 4px;
  box-shadow: 0 6px 18px rgb(0 0 0 / 18%);
}
.shipment-grid .field.place .combo-panel .place-search { width: 100%; }
.shipment-grid .field.place .combo-list {
  list-style: none; margin: 0; padding: 0;
  max-height: 14rem; overflow-y: auto;
}
.shipment-grid .field.place .combo-option {
  padding: 0.25rem 0.4rem; border-radius: 3px; cursor: pointer;
  font-size: 0.9rem;
}
.shipment-grid .field.place .combo-option:hover,
.shipment-grid .field.place .combo-option.is-active {
  background: color-mix(in srgb, var(--bg) 88%, var(--accent));
}
.shipment-grid .field.place .combo-option.is-chosen { font-weight: 600; }
.shipment-grid .field.place .combo-empty {
  list-style: none; padding: 0.35rem 0.4rem;
  color: var(--muted); font-size: 0.8rem;
}

/* Nothing has been edited, so there is nothing to send to a courier about a
   parcel that already exists. The hint says which of the two it is, because a
   disabled button with no reason next to it reads as broken. */
.shipment-form .actions { display: flex; align-items: center; gap: 0.7rem; }
.payout-run .confirm {
  display: flex; gap: 0.6rem; align-items: flex-start;
  padding: 0.75rem 0.9rem; border: 1px solid #d9a441; border-radius: var(--radius);
  background: color-mix(in srgb, #d9a441 8%, transparent);
}
.payout-run .confirm input { margin-top: 0.25rem; }
.payout-run .actions { margin-top: 0.75rem; }

/* The storefront -----------------------------------------------------------
 *
 * The customer-facing shop (docs/PAPER-SHOP.md). Here rather than in the `style`
 * table for the same reason as everything above it: that engine is not built
 * (docs/ROADMAP.md), and a product page with no stylesheet is not a page
 * anybody can be shown.
 *
 * ## The colours are the shop's own
 *
 * Every value below comes from the Angular app's `src/scss/variables.scss` —
 * #00c6cf, #3B3B3B, #FF2929, #E3E0F3, a 68.75rem measure and a 0.2rem radius —
 * read out of the project rather than sampled off a screenshot. See D76.
 *
 * ## Why it declares its own tokens
 *
 * The stylesheet above has a `prefers-color-scheme: dark` block, and the shop
 * must not follow it. This is not a themeable surface: its dark bands are dark
 * BECAUSE the page around them is white, and a viewer whose laptop is in dark
 * mode would otherwise get a dark band on a dark page, which is no band at all.
 * So `.shop` sets what it needs and inherits no colour. The admin panel and the
 * framework's own pages are untouched and still follow the system.
 */

.shop {
  --teal: #00c6cf;
  --dark: #3b3b3b;
  --light: #e3e0f3;

  /* The one token below that is NOT from `variables.scss`, because upstream has
     no name for it: it is the grey the cover photographs are plated with. Read
     out of the files themselves — every corner of every `... - сив фон.jpg` is
     #262626 — and not sampled off a screenshot, which is the same discipline as
     the rest of this block and matters more here, since being one shade out is
     exactly what this colour must not be. See D140. */
  --plate: #262626;
  --red: #ff2929;
  --paper: #ffffff;
  --measure: 68.75rem;
  --r: 0.2rem;
  --shadow: 0 1px 3px 0 rgb(0 0 0 / 30%);
  --shadow-lg: 0 4px 12px 0 rgb(0 0 0 / 70%);

  /* Named but never loaded upstream either — the live site falls back to the
     system faces, so naming them first and a generic second is the same result
     there and a better one on a machine that happens to have them. Loading a
     web font would mean a second origin in the CSP and a request the framework
     does not otherwise make. See D76. */
  --sans: 'Open Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --serif: 'Playfair Display', 'Noto Serif', Georgia, 'Times New Roman', serif;

  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: var(--paper);
  color: var(--dark);
  font-family: var(--sans);
}

.shop a { color: inherit; }

/* The bar ------------------------------------------------------------------ */

.shop-search {
  position: absolute; top: 0; right: 9rem;
  height: 3rem; width: 0; margin: 0;
  overflow: hidden; visibility: hidden;
  transition: width 0.25s ease, visibility 0s linear 0.25s;
}
.shop-search.is-open {
  width: min(22rem, calc(100% - 9rem));
  visibility: visible;
  transition: width 0.25s ease, visibility 0s;
}
@media (prefers-reduced-motion: reduce) {
  .shop-search, .shop-search.is-open { transition: none; }
}

.shop-bar:has(.shop-genre-open) .shop-search.is-open { width: min(22rem, calc(100% - 12rem)); }
.shop-bar:has(.shop-genre-open + .shop-genre-open) .shop-search.is-open { width: min(22rem, calc(100% - 15rem)); }
/* On a phone, five tiles leave the open field 120px. While somebody types,
   the filter tiles step aside (their menus close on the press anyway), and
   the field opens from 9rem as it did before them. */
@media (max-width: 30rem) {
  .shop-bar:has(.shop-search.is-open) .shop-genre-open { display: none; }
  /* The pair in `:has()` lifts this to the specificity of the two-tile rule
     above, which it must beat. */
  .shop-bar:has(.shop-genre-open + .shop-genre-open, .shop-genre-open) .shop-search.is-open { right: 9rem; width: calc(100% - 9rem); }
}

@keyframes cart-bump {
  0% { transform: scale(1); }
  35% { transform: scale(1.25); }
  65% { transform: scale(0.92); }
  100% { transform: scale(1); }
}
/* The badge is `hidden` until there is something in the basket, so an empty
   cart shows no number at all rather than a nought. */
/* The pages that were living on the gutter. Their heads are full-bleed bands —
   the product page's teal tagline strip, the funnel's hero — so the top is
   deliberately flush against the bar, and only the bottom is restored, to keep
   the last paragraph off the footer.

   Every other shop page was checked one by one rather than assumed. `.legal`,
   `.checkout`, `.receipt`, `.auth-page`, `.profile-page` and `.aphorisms` end
   in 3.5–4rem of their own and needed nothing; `.ebook`, `.audio`, `.bonuses`
   and `.signed` ended in 1rem, which only looked right because `main` was
   adding 3rem under it, and each of those four now says 3rem itself at its own
   rule. Written out because "I checked the others" is worth nothing to the next
   reader unless it says which. */
.funnel { padding-bottom: 3rem; }

/* No padding under the product page, and the last band supplies its own.
 *
 * The page used to end in ordinary content on white, so 3rem of the page's own
 * padding was the gap before the dark footer. It ends in a full-bleed COLOURED
 * band now — the lilac excerpt offer, or the near-black author, or the white
 * offers — and 3rem under a painted band is a white stripe between two colours,
 * which reads as a rendering fault rather than as breathing room.
 *
 * So the bottom padding moves onto whatever is last. `.product > *:last-child`
 * is one point more specific than the band's own class, so it also raises
 * `.product-offers` from the 1.5rem that is right in the middle of a page to
 * the 3rem that is right at the end of one. Every other band already says 3rem
 * and this changes nothing for them. */
.product { padding-bottom: 0; }
.product > *:last-child { padding-bottom: 3rem; }

.shop-foot {
  background: var(--dark); color: #fff;
  padding: 2rem 1.2rem; text-align: center;
  font-family: var(--serif);
}

.shop-foot .help { margin: 0.6rem 0 0; }
.shop-foot .link { color: #fff; opacity: 0.75; }

/* The consent banner --------------------------------------------------------
 *
 * Shown by app/functions/paper-shop-shell.js, and only when there is a pixel
 * configured and nobody has answered yet.
 *
 * A bar along the bottom rather than a modal over the middle: nothing behind it
 * is blocked, so a visitor who wants to read about a book before deciding about
 * cookies can. Above the cart panel's z-index would be wrong for the same
 * reason — the panel is what somebody pressed a button to open, and this is not.
 */
.consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex; flex-wrap: wrap; gap: 1rem;
  align-items: center; justify-content: center;
  padding: 1rem 1.2rem;
  background: var(--dark); color: #fff;
  font: 0.85rem/1.5 var(--sans);
  box-shadow: var(--shadow-lg);
}

/* «Приемам» in BlurPaper's teal (BP-464): the owner's choice, deliberately not the
   shop's red buy colour (D385, D387). */
.consent .buy { padding: 0.55rem 1.4rem; font-size: 0.8rem;
  background: var(--teal); border-color: var(--teal); color: #fff; }

/* The measure -------------------------------------------------------------- */

.book-list-head,
.product-main,
.product-features,
.product-band,
.product-tiers,
.product-signed,
.funnel-head,
.funnel-hero,
.funnel-features,
.funnel-packs,
.funnel-gallery,
.funnel-signed,
.funnel-author,
.funnel-testimonials,
.books {
  width: var(--measure); max-width: 100%;
  margin-inline: auto;
  padding-inline: 1.2rem;
}

/* The buy bar --------------------------------------------------------------
 *
 * The shop's signature: one red band carrying the call to action and the price
 * together. `39⁰⁰лв` is three spans from `price_parts`, split in the resolver
 * because a template has no expressions (D2).
 */

/* The red button (BP-462). In this shop `class="buy"` means the red button, and
   until BP-462 it was red only inside the containers that said so — so the sales
   letter's «ВЗЕМИ СЕГА», the landing page's «ДА, ИСКАМ…», the voucher, login,
   registration and contact buttons fell through to the framework's bare `button`
   rule and were the office's blue (D143, D147, D385). `:where()` adds no
   specificity: this beats `button` and loses to every rule below that styles a
   `.buy` on purpose. Colours only — shapes are each control's own. The cookie
   banner's «Приемам» is left as it was: how prominent «accept» looks is a
   consent question, not a styling one. */
.shop :where(.buy:not(.consent-actions .buy)) {
  background: var(--red); border-color: var(--red); color: #fff;
}
.shop :where(.buy:not(.consent-actions .buy)):hover { filter: brightness(1.08); }
.shop :where(.buy[disabled]) { background: var(--light); border-color: var(--light); cursor: not-allowed; }
/* «Продължи към …» and «Вход» on /connect are BlurPaper's teal, not the shop's
   red: the page is BlurPaper vouching for a person, not a sale (BP-577, D454).
   Above it, the logo of the app that asks. */
.auth-page .connect-go { background: var(--teal); border-color: var(--teal); color: #fff; }
.buy-bar .buy {
  display: flex; align-items: center; justify-content: center;
  flex: 0 1 22rem;
  padding: 1.1rem 1.5rem;
  background: none; border: 0; color: inherit; cursor: pointer;
  font: 700 1.35rem/1.1 var(--sans);
  text-transform: uppercase; letter-spacing: 0.03em;
  text-align: center;
}
/* The pressable half is the half that lights up. On the product page and on a
   catalogue card the bar is a <div> holding a button and a price, so brightening
   the whole band would offer a press on the price tag that does not exist. The
   unpriced card's bar IS a link, and keeps the whole-bar hover. */
.buy-bar .buy:hover { filter: brightness(1.08); }
.buy-bar .price-tag {
  display: flex; align-items: center; gap: 0.9rem;
  margin: 0; padding: 0.75rem 1.5rem;
  border-left: 2px solid rgb(255 255 255 / 45%);
}

/* A large whole number with the stotinki raised beside it and the unit under
   them. `align-items: flex-start` on the fraction is what stacks the two
   without either of them moving the baseline of the whole. */
.price-tag .amount {
  display: inline-flex; align-items: flex-start;
  font: 700 2.1rem/1 var(--sans);
}
.price-tag .amount.second { font-size: 1.55rem; opacity: 0.92; }
.price-tag .unit { text-transform: lowercase; }

/* Every price in the shop, in one shape -------------------------------------
 *
 * Bulgaria pays in euro, so the euro amount is the large one and it comes
 * first. The lev amount follows it, smaller and quieter, because for another
 * while it is still the number printed on the back of a book.
 *
 * `.money` / `.money-alt` are NOT new classes. The back office has drawn every
 * amount that way since the changeover — see the rules near `.order-product`,
 * whose comment says exactly why the lev half is second — and the storefront
 * was doing the opposite of its own application. Reusing the pair means the two
 * halves of this shop cannot drift apart, and the day the lev amount comes off
 * the screen for good it comes off in one place.
 *
 * The only thing the storefront changes is the arrangement: in a table the lev
 * amount sits UNDER the euro one (`display: block`), and here it sits beside
 * it. Scoped on `.shop`, the shell's own root class, so nothing under /paper
 * moves.
 *
 * `nowrap` comes off the pair and goes onto each half. A cart line at 320px has
 * to be allowed to break BETWEEN the two amounts; neither amount may break
 * inside itself.
 *
 * `em`, not `rem`, so the pair scales with whatever holds it — a 2.4rem tile
 * price and a 0.85rem cart line each get a proportionate second number.
 *
 * There is a second way a price is drawn, and it did not change: `.price-tag`
 * above, the red bar's signature, where the stotinki are raised beside a large
 * whole number. Only the ORDER of its two amounts moved — the euro one is
 * `.amount` now and the lev one is `.amount.second`, which is the class that
 * makes it small. */
.shop .money { white-space: normal; }
.shop .money-alt {
  display: inline-block; margin-left: 0.4rem;
  font-size: 0.78em; font-weight: 400; opacity: 0.7; color: inherit;
  white-space: nowrap;
}

.book-list-head .muted { color: var(--light); opacity: 0.75; }

/* Four across on a wide screen rather than upstream's two.
 *
 * Upstream shows two enormous cards, which is right for a landing page showing
 * a handful of titles and wrong for a hundred and two: it turns the catalogue
 * into fifty screens of scrolling. The card keeps its proportions and the grid
 * decides how many fit. See D76.
 *
 * A COUNT rather than `auto-fill` with a minimum, and the difference matters.
 * `repeat(auto-fill, minmax(15rem, 1fr))` let the viewport decide how many
 * columns there were, so the number changed at whatever width the arithmetic
 * happened to land on — five across on a wide desktop, two on a tablet — and
 * neither of those was a decision anybody made. Three widths now, and they are
 * the three that were asked for: four above 1024px, three from 768 to 1024,
 * one below 768. */
.books {
  display: grid; gap: 2.5rem 1.5rem;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  padding-top: 1rem;
}

/* 1024px. Three across, which at this width is three cards of a size worth
   looking at rather than four squeezed ones. */
@media (max-width: 64rem) {
  .books { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2rem 1.25rem; }
}

/* 768px. One across — not two narrow ones. A phone showing a single cover at
   the width of the screen is showing the book; two half-width covers are two
   thumbnails of it.

   The cover runs the whole width of the screen (BP-447, the owner,
   2026-09-30): the grid has no padding, and the words and the bar get theirs
   back, so only the picture touches the edges. It used to sit inside 1rem of
   padding, capped at 70vh in a 3:4 letterbox — a 290px book on a 390px phone.
   `aspect-ratio: auto` because one card per row has no neighbour to line up
   with.

   `min-height: 0` on the head because there is nothing left to line up: one
   card per row means no neighbour whose title could push a cover out of step,
   and reserving three lines above every cover on a phone is three lines of
   scrolling bought for nothing. */
@media (max-width: 48rem) {
  .books {
    grid-template-columns: minmax(0, 1fr);
    gap: 2.5rem;
    padding-inline: 0;
  }
  }

/* The card's bar is smaller than the product page's, and pinned to the bottom so
   a row of cards with titles of different lengths still lines its bars up. */
.book-card .buy-bar { margin: auto 0 0; }
/* Larger since BP-447 (the owner: «larger "купи" buttons everywhere… larger
   texts and prices»). Between 768 and 1024px three cards share a row, and the
   larger button and price no longer fit side by side, so the bar stacks there
   as it does on a phone. */
.book-card .buy-bar .buy { flex: 1 1 auto; padding: 0.95rem 0.8rem; font-size: 1.15rem; }
.book-card .price-tag .amount { font-size: 1.75rem; }
.book-card .price-tag .amount.second { font-size: 1.3rem; }
@media (max-width: 64rem) {
  .book-card .buy-bar { flex-direction: column; }
  }
.book-card .buy-bar.quiet { background: var(--light); color: var(--dark); }
/* The phone's 1rem either side of the bar, AFTER the rule above: in the 48rem
 * block higher up it lost to `margin: auto 0 0` on source order, and the bar ran
 * to the edges of the screen (BP-448, seen on the live page). */
@media (max-width: 48rem) {
  .book-card .buy-bar { margin-inline: 1rem; }
}

.follow-form .help { margin: 0; font-size: 0.85rem; }
.product-cta .buy-bar { margin-block: 0.75rem; }

/* A series or a selection on a page of its own (BP-484): the same cards. */
.series-page .series-kind { margin: 0; text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.85rem; opacity: 0.75; }
/* The reassurance row ------------------------------------------------------ */

/* Three widths, as the owner set them (BP-446): every bullet in one row above
   1024px, two across from 768 to 1024, one below 768. A book carries four;
   `grid-auto-flow: column` keeps «all in one row» true of three or five too.
   `auto-fit, minmax(11rem, 1fr)` let the arithmetic choose three-and-one on a
   tablet, which nobody chose. The breakpoints are the ones `.books` uses. */
.product-features {
  display: grid; gap: 1.5rem;
  grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  padding-block: 1rem 3rem;
  text-align: center;
}
.feature { display: flex; flex-direction: column; align-items: center; gap: 0.9rem; }

/* Only a name that matched above sets `--icon`, and only those get the mask: an
   unset mask paints the whole box, so keying the rule on the eight names rather
   than on `[data-icon]` is what keeps an unknown value a disc and not a square. */
.feature p {
  margin: 0; color: var(--teal);
  font: 700 0.95rem/1.35 var(--sans);
  text-transform: uppercase; letter-spacing: 0.03em;
}

/* The bundles -------------------------------------------------------------- */

.product-tiers { padding-block: 2.5rem; }
.product-excerpt .buy {
  display: inline-block; text-decoration: none;
  padding: 0.9rem 2.2rem; border: 0; border-radius: var(--r);
  background: var(--teal); color: #fff; cursor: pointer;
  font: 700 1.15rem/1 var(--sans); text-transform: uppercase; letter-spacing: 0.03em;
}
.product-excerpt .buy:hover { filter: brightness(1.08); }

/* «ЧЕТИ БЕЗПЛАТНО», under the buy bar.
 *
 * Quiet on purpose. It sits directly under the red band and it is the only
 * other thing to press on that part of the page, so a second solid button would
 * be two calls to action arguing — and the one that has to win is the one that
 * sells a book. An outline in the shop's teal reads as an alternative rather
 * than as a rival.
 *
 * The same offer is made again as a full band at the bottom of the page, where
 * it IS the only thing to press and is drawn solid. Same words, same address,
 * two weights, because the two places are answering different moments.
 *
 * `border-radius` and `background` and `color` are all stated, even the ones
 * that happen to be the inherited value — this is an <a> rather than a
 * <button>, so nothing leaks into it today, and D147 is about what happens when
 * somebody changes that. */
.product-try .try {
  display: inline-block; text-decoration: none;
  padding: 0.7rem 1.8rem;
  border: 2px solid var(--teal); border-radius: var(--r);
  background: none; color: var(--teal);
  font: 700 1rem/1 var(--sans); text-transform: uppercase; letter-spacing: 0.03em;
  transition: background 0.15s ease, color 0.15s ease;
}
.product-try .try:hover { background: var(--teal); color: #fff; }

/* The prose and the pictures ----------------------------------------------- */

.product-gallery, .product-signed { padding-block: 0 2.5rem; }
.product-gallery .help { text-align: center; margin-bottom: 1.5rem; }

/* The gallery is a rectangle (BP-428, app/handlers/_paper-gallery.js).
 *
 * The server has already put the pictures into rows. Each <img> carries
 * `style="flex: <width/height> 1 0"` and its real width and height attributes,
 * so within a row the width is shared in proportion to each picture's
 * proportions, and `height: auto` then gives every picture in the row the same
 * height. Every row fills the page's width, so the whole is a rectangle and
 * nothing is cropped, at any width, with no script.
 *
 * `min-width: 0` is load-bearing: a flex item's default minimum is its
 * content's width, and an <img> with width="1538" would refuse to shrink
 * below 1538 px and push the row off the page.
 *
 * On a phone a row of three would be three thumbnails, so each picture takes
 * the whole width instead, as the old shop did on mobile. */
.gallery { display: flex; flex-direction: column; gap: 1rem; }
.gallery img {
  display: block; min-width: 0; height: auto;
  border-radius: var(--r); box-shadow: var(--shadow);
}
.auth-panel-body input:not([type="hidden"]) {
  width: 100%; box-sizing: border-box; margin-top: 0.25rem;
}
.auth-panel-body .buy { width: 100%; margin-top: 1rem; }
.auth-panel-body .help { margin: 0.6rem 0 0; }

/* The two provider buttons carry their brand colours, and nothing else in this
 * shop does. That is the point: somebody scans for the button they always use
 * and finds it by colour before they read a word of it. */
.social {
  width: 100%; display: inline-flex; align-items: center; justify-content: center;
  gap: 0.5rem; padding: 0.6rem 1rem; cursor: pointer;
  font: 600 0.95rem/1.2 var(--sans);
  border-radius: var(--r);
}
.social.google {
  background: #fff; color: #3c4043; border: 1px solid #dadce0;
}
.social.facebook {
  background: #1877f2; color: #fff; border: 1px solid #1877f2;
}

/* The cart panel -----------------------------------------------------------
 *
 * Drawn by app/functions/paper-shop-shell.js; the frame is in the component.
 */
.cart-close {
  background: none; border: 0; color: #fff; cursor: pointer;
  font-size: 1.6rem; line-height: 1; padding: 0 0.25rem;
}

.cart-line-was .tag { font-weight: 700; }
.cart-step-remove:hover { border-color: var(--red); color: var(--red); }

/* The pair is written into the <strong>, so the lev half climbs back out of the
   bold the total is set in. The same for the checkout's, further down. */
.cart-total .money-alt { font-weight: 400; }

.cart-panel-foot .buy {
  width: 100%; padding: 0.9rem 1rem;
  background: var(--red); color: #fff; border: 0; border-radius: var(--r);
  font: 700 1.05rem/1 var(--sans); text-transform: uppercase; cursor: pointer;
}
.cart-panel-foot .buy[disabled] {
  background: var(--light); color: #fff; cursor: not-allowed;
}


/* Checkout and the receipt --------------------------------------------------
 *
 * Two pages that are almost all form and almost no brand, so they take the
 * shop's colours and none of its drama: the red bar is reserved for the one
 * button on each page that commits to something.
 *
 * `.panel` and `.grid` already exist for the admin, higher up this file and in
 * the admin's palette. These rules re-state background and border inside
 * `.shop` rather than inheriting them, for the same reason `.shop` declares its
 * own tokens: the baseline follows `prefers-color-scheme` and this design's
 * white ground is not a preference (D76).
 */
.checkout, .receipt {
  max-width: var(--measure);
  margin-inline: auto;
  padding: 2rem 1.2rem 3.5rem;
  font-family: var(--sans);
}

.shop .panel {
  background: var(--paper);
  color: var(--dark);
  border: 1px solid var(--light);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  padding: 1.5rem;
  margin-bottom: 1.5rem;
}

.checkout-summary .checkout-lines,
.checkout-summary .help { padding-inline: 1.5rem; }
.checkout-summary .help { padding-bottom: 1.2rem; }

/* The courier is a choice between three, and upstream asks it as three radios
   on one line. A <select> hides two of the three answers behind a press, which
   on the field that decides whether an address or an office is wanted is the
   one place in this form that should not need opening. */
.consent-line .link { color: var(--teal); }

/* The commit. Full width and red, the same signature as the product page's buy
   bar, so the last press looks like the first one. */
.checkout-form .buy,
.checkout-empty .buy {
  display: block;
  width: 100%;
  margin-top: 0.5rem;
  padding: 0.9rem 1rem;
  font: 700 1.05rem/1 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--paper);
  background: var(--red);
  border: 0;
  border-radius: var(--r);
  cursor: pointer;
  text-align: center;
  text-decoration: none;
}
.checkout-empty .buy { max-width: 16rem; margin-inline: auto; }
.checkout-form.is-busy .buy { opacity: 0.6; }

.checkout-line-price .money-alt, .checkout-summary-total .money-alt { font-weight: 400; }
/* The receipt --------------------------------------------------------------- */

.receipt .grid { width: 100%; border-collapse: collapse; }
.receipt .grid td, .receipt .grid th {
  padding: 0.6rem 0.5rem;
  border-bottom: 1px solid var(--light);
  text-align: left;
}
.receipt .grid .num { text-align: right; white-space: nowrap; }
.receipt .grid tfoot th {
  border-bottom: 0;
  border-top: 2px solid var(--dark);
  font: 700 1.1rem/1.2 var(--sans);
  /* The admin's `.grid th` is a muted heading colour; here the sum is the point
     of the page, so both halves of the row take the shop's ink. */
  color: var(--dark);
}
.receipt .price-eur { font-size: 0.85em; opacity: 0.65; font-weight: 400; }
.receipt .link { color: var(--teal); font-weight: 600; }

/* The form's own messages, painted by app/functions/admin-form.js. Styled here
   because they appear in the shop and the admin's versions are the admin's
   colours. */
.checkout .form-message {
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  border-radius: var(--r);
  font-size: 0.95rem;
}
.checkout .form-message.error { background: var(--red); color: var(--paper); }
.checkout .form-message.ok { background: var(--light); color: var(--dark); }
.checkout .field-error { margin: 0.3rem 0 0; color: var(--red); font-size: 0.85rem; }
.checkout .has-error { border-color: var(--red); }

.pay-state[data-pay="paid"] { border-left-color: #2f7d4f; background: rgb(47 125 79 / 8%); }
.pay-state[data-pay="awaiting"] { border-left-color: #b8860b; background: rgb(184 134 11 / 8%); }
.pay-state[data-pay="abandoned"] { border-left-color: var(--red); background: rgb(214 40 40 / 7%); }

/* An epay request past the date it was issued with. Not a value of
   `payment_status` — the column still says `awaiting` until epay says otherwise
   — but it must not be amber, because amber here reads as "still fine" and this
   one is not (app/resolvers/paper-sale.js, migration 057). */
.pay-state[data-pay="expired"] { border-left-color: var(--red); background: rgb(214 40 40 / 7%); }

/* Narrow ------------------------------------------------------------------- */

@media (max-width: 40rem) {
  .checkout, .receipt { padding: 1.5rem 0.8rem 2.5rem; }
  .shop .panel { padding: 1rem; }

  /* In a column `flex: 0 1 22rem` is a HEIGHT: the product page's «Поръчай
     сега» was a 352px red square on every phone (BP-451). Its own height here. */
  .buy-bar .buy { flex: 0 0 auto; }
  /* On a phone the card is the width of the screen, so the bar is as large
     as the product page's (BP-447) rather than shrunk. */
  .book-card .buy-bar .buy { font-size: 1.4rem; padding: 1.1rem 1rem; }
  .book-card .price-tag .amount { font-size: 2.2rem; }
  .book-card .price-tag .amount.second { font-size: 1.6rem; }

  }

/* ---------------------------------------------------------------------------
   The ebook reader (docs/PAPER-DIGITAL.md)

   The stage is a positioning context with three stacked layers: the
   illustration, the tint over it, and the grid of tiles on top. All three are
   created by `app/functions/paper-ebook.js`; the classes are here because a
   layer with no rules is an invisible bug.
   --------------------------------------------------------------------------- */

.ebook { max-width: 70rem; margin: 0 auto; padding: 1rem 1rem 3rem; }
.ebook-turn[disabled] { opacity: 0.45; cursor: default; }
.ebook-after .buy {
  display: inline-block; padding: 0.9rem 2rem;
  border: 0; border-radius: var(--r); background: var(--red); color: #fff;
  text-decoration: none; font: 700 1rem/1 var(--sans);
  text-transform: uppercase; letter-spacing: 0.03em;
}
.ebook-after .buy:hover { filter: brightness(1.08); }

.ebook-denied .muted { margin: 0 0 1.5rem; color: #666; }
.ebook-denied-ways .buy {
  display: inline-block; padding: 0.9rem 2rem;
  border: 0; border-radius: var(--r); background: var(--red); color: #fff;
  text-decoration: none; font: 700 1rem/1 var(--sans);
  text-transform: uppercase; letter-spacing: 0.03em;
}
.ebook-denied-ways .buy:hover { filter: brightness(1.08); }

@media (max-width: 40rem) {
  .ebook { padding: 0.5rem; }
  }


.auth-form input[type="email"],
.auth-form input[type="password"],
.auth-form input[type="text"],
.auth-form input:not([type]) {
  display: block;
  width: 100%;
  margin-bottom: 1rem;
  padding: 0.6rem 0.7rem;
  font: 400 1rem/1.3 var(--sans);
  color: var(--dark);
  background: var(--paper);
  border: 1px solid var(--light);
  border-radius: var(--r);
}

/* A consent box is a sentence with a checkbox in it, not a labelled field, so
   it opts out of the uppercase treatment above. */
.auth-form .check {
  display: flex;
  gap: 0.55rem;
  align-items: flex-start;
  margin: 0 0 0.85rem;
  font: 400 0.85rem/1.45 var(--sans);
  text-transform: none;
  letter-spacing: normal;
}
.auth-form .check input { margin-top: 0.2rem; flex: none; }

/* The aphorisms.
   Each one is a picture with the sentence drawn into it — two shapes, 1200x628
   and 1280x1280 — so the column is sized for the wider of them and the square
   ones simply sit taller. `aspect-ratio` is deliberately not set: the mirror
   does not carry the dimensions, and guessing one would reserve the wrong box
   for two of the fourteen. See docs/DECISIONS.md D110. */
.aphorisms { max-width: 46rem; margin: 0 auto; padding: 1.5rem 1rem 4rem; }
.auth-form .buy { width: 100%; margin-top: 0.5rem; }

.auth-page .help, .profile-page .help { text-align: center; }

.profile-page .panel { margin-bottom: 1.5rem; }

/* Centring is right for a login box and wrong for data. `.auth-head` and
   `.help` are centred a few rules above for the form pages; here they are read
   left to right alongside the tables they introduce. */
.author-panel .auth-head,
.author-panel .auth-head h1,
.author-panel .help { text-align: left; }

.author-panel .auth-head { margin-bottom: 1.25rem; }

/* Rows of figures that are compared down a column, so they line up. `.grid`
   already does this for `td.num`; the shop's tables reach here through
   `.money` as well. */
.author-panel .money,
.author-panel td.num { font-variant-numeric: tabular-nums; }

/* A table may run wider than the phone; the page body may not. Each one gets
   its own horizontal scroll rather than pushing the whole layout sideways,
   which is the rule every other wide thing in this file follows. `display:
   block` is what makes a <table> scrollable at all, and it costs the column
   alignment — so it is confined to the width where the alternative is text
   leaving its box. */
@media (max-width: 45rem) {
  /* The one column that must be allowed to wrap: a book title is long, it is
     the only thing on the row a person reads rather than scans, and holding it
     on one line is what makes the scroll bar appear in the first place. */
  .author-panel table td.title,
  .author-panel table td:first-child { white-space: normal; min-width: 9rem; }
}

/* The illustrations on «Нова книга» (BP-359): the same box as the cover, a
   multi-file input, and a grid of what is already there, each with its own
   «Махни». The office's «Предложени книги» shows the same pictures as thumbs. */
.genre-pick-custom { margin-top: 0.6rem; }
/* A list the author grows a box at a time (BP-366): each row a box and its
   «Махни», «Добави ред» under them. */
.book-form .line-list { margin: 0 0 0.9rem; }
.book-form .line-list-title { display: block; margin-bottom: 0.3rem; font: 600 0.8rem/1.4 var(--sans); }
.cover-part .help { margin: 0; }
.cover3d-pose.failed img { visibility: hidden; }

.cover3d-scene.failed img { visibility: hidden; }

/* The marketing report (BP-325, and the dashboard on top of it, BP-344). It
   lives in apps/marketing, which may ship JavaScript but not yet stylesheets
   (docs/ROADMAP.md «Application assets other than JavaScript»), so its rules
   are here. Everything below is layout and colour only: with none of it the
   page is still a stack of headings, lists and tables that says the same
   things in the same order. */
.mkt-thumb { display: block; width: 6rem; height: 6rem; object-fit: cover; border-radius: 4px; margin-bottom: 0.3rem; }

.mkt-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 1rem; }
.mkt-head h1 { margin-bottom: 0.2rem; }
.mkt-head p { margin: 0; max-width: 46rem; }

.mkt-card.ok { border-left-color: #16a34a; }
.mkt-card.warn { border-left-color: #d97706; }
.mkt-card.muted { border-left-color: var(--light); }
.mkt-card .muted { font-size: 0.82rem; }

.mkt-alert.warn { border-left-color: #d97706; }

.mkt-best { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: 0.75rem; }
.mkt-best-card { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.8rem; border: 1px solid var(--light); border-radius: 6px; }
.mkt-best-card strong { font: 600 1rem/1.3 var(--sans); overflow-wrap: anywhere; }
.mkt-best-figure { font: 700 1.25rem/1.2 var(--sans); }
.mkt-best-card .muted { font-size: 0.82rem; overflow-wrap: anywhere; }
.mkt-headline { font-weight: 600; }

/* Two bars a day. The heights are percentages computed in the resolver — the
   template cannot divide, and must not learn to (CLAUDE.md §2). */
.mkt-trend { list-style: none; margin: 0; padding: 0; display: flex; align-items: flex-end; gap: 0.25rem; overflow-x: auto; }
.mkt-trend-day { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; min-width: 1.4rem; flex: 1 1 0; }
.mkt-bars { display: flex; align-items: flex-end; gap: 2px; height: 5rem; width: 100%; justify-content: center; }
/* «Докъде четат» (BP-459): one bar per section, its share of the page's openings. */
.mkt-reach { display: block; width: 8rem; height: 0.6rem; background: var(--light); border-radius: 2px; overflow: hidden; }
.mkt-reach-bar { display: block; height: 100%; background: var(--teal); }
.mkt-bar { width: 45%; min-height: 1px; border-radius: 2px 2px 0 0; background: var(--light); }
.mkt-bar.spend { background: #475569; }
.mkt-bar.orders { background: #16a34a; }
.mkt-trend-label { font: 500 0.7rem/1 var(--sans); opacity: 0.7; }

/* A folded panel keeps its panel look, and its heading stops being a block so
   the disclosure arrow sits beside it rather than above it. */
details.panel > summary { cursor: pointer; }
details.panel > summary h2, details.panel > summary h3 { display: inline; }
.profile-page form.inline { text-align: center; }

/* The owned-products grid reuses the catalogue's card wholesale — same widths,
   same cover box — and adds one thing the catalogue has no use for: the grey
   plate over a book that cannot be opened yet. `Очаква плащане` and
   `ПРЕДпродажба` were an absolutely-positioned half-transparent overlay in the
   old thumbnail, and that is what this is. */
.book-card-cover.owned { position: relative; }
/* No cover mirrored yet: the plate still has to be readable, so the box needs a
   height of its own rather than collapsing to the height of the text. */
.book-card-cover.owned:not(:has(img)) { min-height: 8rem; background: var(--light); }
.book-card-cover.owned .buy { text-decoration: none; }
/* The audiobook player. One row — a button, a bar, two clocks — which is the
   whole interface: everything else it does is fetching, and fetching has no
   pixels. The old player drew the same three things and put them in that order.
   `tabular-nums` on the clock because it ticks once a second and a proportional
   font makes the whole row twitch. */
.audio { max-width: 46rem; margin: 0 auto; padding: 1rem 1rem 3rem; }
.audio-seek[disabled] { opacity: 0.45; cursor: default; }
.product-sample .ebook { padding: 0 1rem 1rem; }
.product-sample-buy .buy {
  padding: 0.9rem 2rem; border: 0; border-radius: var(--r);
  background: var(--red); color: #fff; cursor: pointer;
  font: 700 1rem/1 var(--sans); text-transform: uppercase; letter-spacing: 0.03em;
}
.product-sample-buy .buy:hover { filter: brightness(1.08); }

.product-listen .audio { flex: 1 1 auto; min-width: 0; max-width: none; margin: 0; padding: 0; }
.product-listen .product-sample-close { flex: 0 0 auto; padding: 0.35rem 0.75rem; line-height: 1; }

@media (max-width: 40rem) {
  .product-listen [data-audio-play] { padding: 0.5rem 0.75rem; }
}

/* --- Bonus illustrations (/bonuses/:key/:order) ----------------------------
   The grid is images and nothing else, so it is sized by the pictures rather
   than by a column count: a book with four illustrations and one with nineteen
   both read as a wall of drawings. `is-full` greys out what cannot be added
   until something is removed - it is a class and not `disabled`, because a
   disabled checkbox posts nothing. */
.bonuses { max-width: 1100px; margin: 0 auto; padding: 1rem 1rem 3rem; }
/* --- the signed illustration (/get-bonuses) ------------------------------ */
.signed { max-width: 1100px; margin: 0 auto; padding: 1rem 1rem 3rem; }
/* --- the legal documents (/terms, /confidentiality) -----------------------
 *
 * Long-form reading, so the only thing that matters is line length: `65ch`
 * rather than a pixel width, because the limit is words per line and not
 * millimetres. Everything else is the baseline stylesheet doing its job.
 *
 * `scroll-margin-top` on the headings is what makes `#razdel-6` and `#cookies`
 * usable — without it the browser puts the target's first pixel at the top of
 * the viewport, under nothing, and the heading reads as cut off.
 */
.legal { max-width: 65ch; margin: 0 auto; padding: 1.5rem 1.25rem 4rem; }
.legal h1 { font-size: 1.6rem; line-height: 1.3; margin: 0 0 1.5rem; }
.shop-langs [aria-current] { font-weight: 700; }

/* --- the blog (/blog, /blog/:key, and its editor) --------------------------
 *
 * docs/PAPER-BLOG.md. A reading column like the legal pages: the limit that
 * matters is words per line, so the article is `46rem` and its type is a size
 * up from the shop's. The pieces that are the shop's own — `.buy-bar`,
 * `.price-tag`, the book cover — are reused in the compact form the catalogue
 * card uses, so a book inside an article is recognisably the same book.
 *
 * A picture in a post is never wider than the column and never upscaled; a
 * cover in the list is cropped to 3:2 because the list is a rhythm, and the
 * post itself shows the cover whole.
 */
.blog, .blog-post { max-width: 46rem; margin: 0 auto; padding: 1.5rem 1.25rem 4rem; }
.blog-card-text p:last-child { margin-bottom: 0; }
.blog-book .buy-bar .buy { flex: 1 1 auto; padding: .95rem .8rem; font-size: 1.15rem; }
.blog-book .buy-bar .price-tag .amount { font-size: 1.75rem; }
.blog-book .buy-bar .price-tag .amount.second { font-size: 1.3rem; }
.blog-book .buy-bar.quiet { background: var(--light); color: var(--dark); }
/* The editor. The text is monospaced because the marks are the structure:
   a writer lines up «- » and «## » by eye. */
.funnel-form label.check {
  grid-template-columns: auto minmax(0, 1fr); align-items: start;
  gap: 0.6rem; font-weight: 400; font-size: 0.9rem;
}
.funnel-form label.check input { margin-top: 0.15em; }
.packs {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  margin-bottom: 2rem;
}
.pack .muted, .pack .help { font-size: 0.9rem; }
/* The excerpt --------------------------------------------------------------
 *
 * /cheti-bezplatno/:key was a frame onto a PDF that has never existed (D130).
 * It renders the page reader now, so what used to be styled here is styled with
 * the reader — except the one thing the reader does not have, which is the way
 * out to the book the excerpt is advertising. */
.funnel-after { text-align: center; }
.funnel-after { margin-block: 2rem 3rem; }
.funnel-after .buy {
  display: inline-block; padding: 0.9rem 2rem;
  border-radius: var(--r); background: var(--red); color: #fff;
  text-decoration: none; font: 700 1rem/1 var(--sans); text-transform: uppercase;
}

.funnel-gallery .help { text-align: center; margin-bottom: 1.5rem; }
/* ---------------------------------------------------------------------------
   The document editor — /admin/documents (BP-120, docs/DOCUMENTS.md §8).

   The tree is built by app/functions/admin-documents.js, so every class here is
   named there. Nothing is positioned absolutely and nothing has a fixed height:
   a document nests arbitrarily and a leaf may be a paragraph.
   --------------------------------------------------------------------------- */

.doc-tabs { display: flex; gap: 0.35rem; margin-bottom: 0.75rem; align-items: center; }
.doc-tab, .doc-format {
  font: inherit; font-size: 0.85rem; padding: 0.3rem 0.75rem; cursor: pointer;
  background: none; border: 1px solid var(--line); border-radius: var(--r); color: inherit;
}
.doc-tab.is-on { background: var(--line); font-weight: 600; }
.doc-format { margin-left: auto; }

.doc-preview { font-size: 0.8rem; word-break: break-all; color: var(--muted); }

.doc-tree { font-size: 0.9rem; }
.doc-branch { display: flex; flex-direction: column; gap: 0.4rem; }
.doc-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0.4rem; }
.doc-key { flex: 0 0 12rem; padding-top: 0.25rem; }
.doc-key-input { width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.8rem; }
.doc-value { flex: 1 1 16rem; min-width: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; }
textarea.doc-value { min-height: 4rem; resize: vertical; }
.doc-value.is-bad { border-color: var(--bad, #b3261e); }
.doc-readonly { background: var(--line); }
.doc-null { flex: 1 1 16rem; padding: 0.25rem 0; color: var(--muted); font-style: italic; }

.doc-controls { display: flex; gap: 0.25rem; align-items: flex-start; }
.doc-type { font-size: 0.75rem; }
.doc-remove, .doc-add {
  font: inherit; font-size: 0.8rem; line-height: 1; padding: 0.3rem 0.5rem; cursor: pointer;
  background: none; border: 1px solid var(--line); border-radius: var(--r); color: var(--muted);
}
.doc-add { align-self: flex-start; margin-top: 0.2rem; }

/* The nesting is the only thing that says what belongs to what, so it is a rule
   and an indent rather than a heading — a document ten levels deep would
   otherwise be ten headings and no shape. */
.doc-nested { flex: 1 1 100%; margin-left: 1rem; padding-left: 0.75rem; border-left: 2px solid var(--line); }

@media (max-width: 46rem) {
  .doc-key { flex-basis: 100%; }
  .doc-nested { margin-left: 0.25rem; }
}

/* A cover an author uploaded — on their own screen and in the office queue.
   Bounded in CSS as well as on the way in: `core/uploads.js` caps the pixels,
   but a correctly-sized 1600px cover still has to sit in a table cell. */
/* «За автора» on the office's mirror of «Моите данни» (BP-377): one string,
   a paragraph per line, shown with its lines. */
.cover-preview {
  max-width: 12rem; height: auto; display: block;
  border: 1px solid var(--line); border-radius: var(--r);
}

/* «Наличности» (BP-416): the cover beside each book, and the same cover
   enlarged in a popover. The browser opens and closes it; no script. */
.oc-row.is-busy { opacity: 0.6; }
/* A front cover is portrait: bound its height, not its width (BP-503). */
.oc-portrait img { width: auto; max-width: 100%; max-height: 10rem; }
.oc-upload .button { cursor: pointer; white-space: nowrap; }
.cp-part.has { border-style: solid; }
.cp-part.is-busy { opacity: 0.6; }
.cp-part .button { cursor: pointer; white-space: nowrap; }
@keyframes raffle-spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------------------
   TYPING ON THE PAGE (docs/PAPER-TOOLS.md).

   The input the keystrokes actually arrive in. It is off-screen rather than
   `display: none` or `hidden`, because a hidden control cannot take focus and
   an unfocused one receives nothing — and a real focused control is what gets
   dead keys, the Bulgarian phonetic layout, dictation and a tablet's software
   keyboard, none of which a key event on an SVG element gives.

   `opacity: 0` and a 1px box rather than a huge negative offset: some browsers
   scroll the page to a focused control, and one parked at -9999px takes the
   whole window with it on every keystroke.
   --------------------------------------------------------------------------- */
.print-typing-input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; border: 0; margin: 0;
  opacity: 0;
  resize: none;
  overflow: hidden;
  white-space: pre;
}

/* The page has to be positioned for the input above to sit inside it. */
.print-preview { position: relative; }

/* THE SELECTION HANDLES (BP-541): a blue bar the height of the line at each
   end of a selection, the start's knob above it and the end's below, as in
   Google Docs. The layer is a zero-sized origin the handles are placed from
   (`drawHandles`); each handle is wider than it looks, for a finger. */
.print-handles { position: absolute; left: 0; top: 0; width: 0; height: 0; z-index: 3; }
.print-handle { position: absolute; width: 28px; margin-left: -14px; touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.print-handle.is-held { cursor: grabbing; }
.print-handle-stem { position: absolute; left: 13px; top: 0; bottom: 0; width: 2px; background: #1a73e8; }
.print-handle-knob { position: absolute; left: 7px; width: 14px; height: 14px; border-radius: 50%; background: #1a73e8; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
.print-handle.is-start .print-handle-knob { top: -13px; }
.print-handle.is-end .print-handle-knob { bottom: -13px; }
/* A finger needs more than a 14px knob: an invisible ring around it. */
.print-handle-knob::after { content: ''; position: absolute; inset: -10px; border-radius: 50%; }

/* The drawn page is PAPER (BP-521): white whatever the site's theme. Without
   it the SVG took the dark theme's background and the book's black text sat
   on near-black — the page was unreadable in dark mode. */
.print-preview-page { background: #ffffff; }

/* Comments in the margin (BP-521): a column beside the page, only while it has
   cards; a card sits level with its words and is pushed down, never up. */
.print-preview.has-margin { display: grid; grid-template-columns: minmax(0, 1fr) 15rem; column-gap: 0.75rem; align-items: start; }
.print-preview.has-margin > svg { grid-column: 1; grid-row: 1; }
.print-margin { grid-column: 2; grid-row: 1; position: relative; }
.print-margin-note { position: absolute; left: 0; right: 0; display: flex; flex-direction: column; gap: 0.15rem; text-align: left; font-size: 0.8rem; line-height: 1.3; padding: 0.35rem 0.5rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line, #d8d8d8); border-left: 3px solid rgba(255, 190, 0, 0.9); border-radius: 4px; cursor: pointer; }
.print-margin-note.is-suggestion { border-left-color: #1e8449; }
.print-margin-note.is-open { box-shadow: 0 0 0 2px rgba(255, 190, 0, 0.7); }
.print-margin-who { opacity: 0.7; font-size: 0.72rem; }
@media (max-width: 900px) {
  .print-preview.has-margin { display: block; }
  .print-margin { display: none; }
}

/* The caret. Painted in its own layer, never into the hover rectangles — those
   are sorted biggest-first so the pointer finds the smallest, and anything
   added to that list joins the ordering (docs/PAPER-TOOLS.md). */
@keyframes print-caret-blink { 50% { opacity: 0; } }

.print-caret { animation: print-caret-blink 1.1s step-end infinite; }

@media (prefers-reduced-motion: reduce) {
  .print-caret { animation: none; }
}

/* The ink layer of the writing page (BP-569): the caret and the line being
   typed on, over the page in an SVG of their own and on a compositor layer of
   their own, so a keystroke or a blink repaints one line, not ~1800 letters.
   Same viewBox and width as the page, so its units are the page's. */
.print-ink { position: absolute; left: 0; top: 0; overflow: hidden; pointer-events: none; will-change: transform; }

/* While typing is on, the page is a text surface rather than a drawing one. */
.print-preview.is-typing svg { cursor: text; }

/* The typing toolbar. A pressed button has to LOOK pressed: a toggle that
   does not show its state is one somebody has to try in order to read. */
.print-toolbar { display: flex; gap: 0.35rem; flex-wrap: wrap; margin: 0.5rem 0; }
.print-toolbar button { min-width: 2.5rem; }
.print-toolbar button.is-on {
  background: var(--accent, #4a7ab8);
  color: #fff;
  border-color: var(--accent, #4a7ab8);
}

/* The value controls, which are labels wrapping an input rather than buttons:
   a size is eleven points and a colour is a particular red, so they show a
   VALUE and not a state (BP-274). Small type on the label, so the toolbar
   stays one row of controls rather than becoming a form. */
.print-toolbar label {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8rem;
  white-space: nowrap;
}
.print-toolbar input[type="number"] { width: 4.5rem; }
.print-toolbar input[type="color"] {
  width: 2.2rem;
  height: 1.8rem;
  padding: 0;
  border: 1px solid var(--line, #ccc);
  background: none;
}

/* A colour well cannot be empty, so «no colour set» is shown by turning the
   control down rather than by a value that reads as black on purpose. */
.print-toolbar input[type="color"].is-unset { opacity: 0.45; }

/* The find bar. One row of controls that wraps, like the toolbar above it,
   and boxes wide enough to hold a name rather than a word (BP-275). */
.print-find {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-wrap: wrap;
  margin: 0.25rem 0 0.5rem;
  font-size: 0.85rem;
}
.print-find label { display: inline-flex; align-items: center; gap: 0.3rem; }
.print-find input[type="search"], .print-find input[type="text"] { width: 10rem; }
.print-find .notice { flex-basis: 100%; margin: 0.2rem 0 0; }
.print-find-case { white-space: nowrap; }

/* COMMENTS ON THE BOOK (BP-280). The editor's notes, beside the page.

   A list rather than bubbles pinned to the margin: a printed page is already
   as wide as the panel it sits in, and a margin note would either overlap the
   text or shrink the book. The highlight on the page and the row in the list
   are the pair — pressing either lights the other. */
.print-comments {
  margin: 0.25rem 0 0.5rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--line, #d8d8d8);
  border-radius: 4px;
  font-size: 0.85rem;
}
.print-comments-head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.print-comments-done { margin-left: auto; white-space: nowrap; }
/* ◀ ▶ and the batch answers (BP-514), small like the buttons on a comment. */
.print-comments-steps { display: inline-flex; gap: 0.3rem; flex-wrap: wrap; }
.print-comments-steps button { font-size: 0.8rem; padding: 0.1rem 0.45rem; }
/* The suggesting switch (BP-515): green when on, the colour a suggestion's new
   words are drawn in, so the mode is visible at a glance. */
.print-suggesting { display: inline-flex; align-items: center; gap: 0.25rem; white-space: nowrap; }
.print-suggesting:has(input:checked) { color: #1e8449; font-weight: 600; }
/* «@» picks a person (BP-518): a short column of addresses under the box. */
.print-mentions { display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem; margin: 0.2rem 0; }
.print-mentions button { font-size: 0.8rem; padding: 0.1rem 0.45rem; }
.print-version-diff { margin-top: 0.5rem; }
.print-version-change { margin: 0.4rem 0; padding-left: 0.6rem; border-left: 2px solid var(--line, #d8d8d8); }
.print-version-change del { display: block; color: #c0392b; white-space: pre-wrap; }
.print-version-change ins { display: block; color: #1e8449; white-space: pre-wrap; }
/* The outline (BP-519): headings indented by level, each a quiet button. */
.print-outline { margin: 0.4rem 0; }
.print-outline-list { list-style: none; margin: 0.3rem 0; padding: 0; max-height: 18rem; overflow-y: auto; }
.print-outline-list button { background: none; border: 0; padding: 0.1rem 0; color: inherit; cursor: pointer; text-align: left; }
.print-outline-list button:hover { text-decoration: underline; }
.print-outline-level-2 { padding-left: 1rem; }
.print-outline-level-3 { padding-left: 2rem; }
.print-outline-level-4 { padding-left: 3rem; }
/* The right-click menu (BP-519): placed at the pointer, above everything. */
.print-context-menu { position: fixed; z-index: 1000; display: flex; flex-direction: column; min-width: 12rem; padding: 0.25rem 0; background: var(--bg); color: var(--ink); border: 1px solid var(--line, #d8d8d8); border-radius: 6px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25); }
.print-context-menu[hidden] { display: none; }
.print-context-menu button { background: none; border: 0; color: inherit; text-align: left; padding: 0.35rem 0.8rem; cursor: pointer; }
.print-context-menu button:hover:not(:disabled) { background: rgba(0, 198, 207, 0.15); }
.print-context-menu button:disabled { opacity: 0.45; cursor: default; }

.print-comment-new { margin: 0.4rem 0; }
.print-comment-new label { display: block; }
.print-comment-new textarea { width: 100%; box-sizing: border-box; }

.print-comment-list { list-style: none; margin: 0.4rem 0 0; padding: 0; }

.print-comment {
  padding: 0.4rem 0.5rem;
  border-left: 3px solid rgba(255, 190, 0, 0.9);
  margin-bottom: 0.35rem;
  background: rgba(255, 215, 0, 0.08);
}
/* The one being read, matching the stronger highlight on the page. */
.print-comment.is-open { background: rgba(255, 215, 0, 0.22); }
/* Closed: still listed when somebody asks for them, and visibly done. */
.print-comment.is-done { border-left-color: var(--line, #d8d8d8); background: none; opacity: 0.7; }
.print-comment.is-done .print-comment-note { text-decoration: line-through; }
/* The text it was about is gone. Not an error — the comment is still what
   somebody wrote, and the grey line says it has nowhere to point. */
.print-comment.is-lost { border-left-style: dashed; border-left-color: #999; }

.print-comment-top { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.print-comment-who { font-size: 0.78rem; font-weight: 600; opacity: 0.85; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.print-comment-acts { display: inline-flex; gap: 0.2rem; flex: none; }
.print-comment-act { width: 1.7rem; height: 1.7rem; padding: 0; font-size: 0.95rem; line-height: 1; border-radius: 50% !important; border: 1px solid var(--line); background: none; color: inherit; }
.print-comment-act:hover { background: rgba(0, 198, 207, 0.15); border-color: #00c6cf; color: #00a3ab; }
.print-comment-act[aria-label="Отхвърли"]:hover { background: rgba(255, 41, 41, 0.12); border-color: #ff2929; color: #ff2929; }
.print-comment-quote {
  display: block;
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  font-style: italic;
  text-align: left;
  color: inherit;
  cursor: pointer;
}
.print-comment-quote:hover { text-decoration: underline; }
.print-comment-note { margin: 0.2rem 0; white-space: pre-wrap; }
.print-comment-buttons { margin: 0.2rem 0 0; display: flex; gap: 0.35rem; }
.print-comment-buttons button { font-size: 0.8rem; padding: 0.1rem 0.4rem; }
/* The thread under a comment (BP-509): indented with its own rule, so an answer
   never reads as a second note from the editor. */
.print-comment-replies { list-style: none; margin: 0.3rem 0 0 0.6rem; padding: 0 0 0 0.6rem; border-left: 2px solid var(--line, #d8d8d8); }
.print-comment-reply { margin: 0.25rem 0; }
.print-comment-reply button { font-size: 0.75rem; padding: 0 0.3rem; }
.print-comment-reply-text { width: 100%; box-sizing: border-box; }
/* A suggestion (BP-512): the words that go struck in red, the words that come
   underlined in green — the marks every editor already reads as "change this". */
.print-comment-suggestion { margin: 0.25rem 0; }
.print-comment-suggestion del { color: #c0392b; }
.print-comment-suggestion ins { color: #1e8449; }
.print-comment-new input[data-role="comment-suggestion"] { width: 100%; box-sizing: border-box; }

/* ---------------------------------------------------------------------------
 * CHARTS — /paper/charts and /author/charts.
 *
 * The pictures are inline `<svg>` built by the resolver
 * (app/handlers/_paper-charts.js): the geometry is computed on the server and
 * the template loops over rectangles. So there is nothing here that positions
 * anything — every x, y, width and height is an attribute in the HTML. What
 * lives here is COLOUR, which is the one part of a chart that belongs in a
 * stylesheet: a hex code in a database row or a resolver is a decision nobody
 * can find again, and dark mode could not follow it.
 *
 * Each chart scales to its column through the viewBox — `width: 100%` and
 * `height: auto` — so there is no measuring step and no resize handler, and
 * the same numbers serve a phone, a laptop and a sheet of A4.
 * ------------------------------------------------------------------------- */

.chart { margin: 0.6rem 0 0; }

.chart svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* The gridlines, and the axis they end at. The baseline is darker because it
   is the zero the bars stand on, and a chart whose zero is as faint as its
   guides invites the eye to take the lowest guide for the floor. */
.chart .grid { stroke: var(--line); stroke-width: 1; }
.chart .grid.base { stroke: var(--muted); }

.chart text { fill: var(--muted); font: 11px system-ui, -apple-system, "Segoe UI", sans-serif; }
.chart .tick { text-anchor: end; }
.chart .xlabel { text-anchor: middle; }
.chart .rank-label { text-anchor: end; fill: var(--ink); }
.chart .rank-value { text-anchor: start; }

/* The bars.
 *
 * `settled` is solid and `awaiting` is the same hue at a third of the weight,
 * because they are two states of ONE amount and not two quantities: the pale
 * part sits on top of the solid part and the whole bar is what was ordered.
 * Two unrelated colours would read as two series.
 *
 * `lost` is the only warm colour on the page, and it is used once — money that
 * came back and will never arrive. Keeping it unique is what makes it mean
 * something when it appears. */
.chart .bar { shape-rendering: crispEdges; }
.chart .bar.settled  { fill: var(--accent); }
.chart .bar.awaiting { fill: var(--accent); opacity: 0.3; }
.chart .bar.copies   { fill: var(--accent); opacity: 0.75; }
.chart .bar.book     { fill: var(--accent); opacity: 0.8; }
.chart .bar.genre    { fill: var(--accent); opacity: 0.55; }
.chart .bar.lost     { fill: #c2683f; }
/* The row the page is already filtered to. Full strength, so a filtered
   ranking says which bar you are inside without a second legend. */
.chart .bar.chosen   { fill: var(--accent); }

.chart a:hover .bar { opacity: 1; }

/* The share bar is one wide rectangle cut into parts, so its segments need no
   gap and must not round — a radius on a 4% sliver eats the sliver. */
.chart.share svg { max-height: 34px; }

.chart-legend .swatch { width: 0.8rem; height: 0.8rem; border-radius: 3px; background: var(--accent); }
.chart-legend .swatch.awaiting { opacity: 0.3; }
.chart-legend .swatch.lost { background: #c2683f; }
.chart-figures .n { display: block; font-size: 1.45rem; font-weight: 700; line-height: 1.2; }
.chart-figures .k { font-size: 0.8rem; color: var(--muted); }

/* The period buttons. A chip that is the current view is filled rather than
   outlined — the same shape, so the row still reads as one set of choices. */
.chart-periods { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0 0 0.6rem; }
.chart-periods .chip.on { background: var(--accent); border-color: var(--accent); color: #fff; }

@media print {
  .chart-periods, .filters { display: none; }
  .chart svg { break-inside: avoid; }
}

.hf-pages li.ok { color: inherit; }
.hf-pages li.error { color: #b3261e; }
@page hand-font { size: A4 portrait; margin: 0; }

@page deposit { size: A4 landscape; margin: 20mm 0 20mm 18mm; }
/* The whole document prints on the deposit page, and the office page around the
   form adds no spacing: a page change, or a few millimetres of padding past the
   last page, would print an extra blank sheet (BP-412). */
@media print {
  body:has(.deposit-protocol) .paper, body:has(.deposit-protocol) .paper-main,
  body:has(.deposit-protocol) .admin-page, body:has(.deposit-protocol) .deposit-protocol { margin: 0; padding: 0; min-height: 0; }
}
/* ---------------------------------------------------------------------------
   THE WRITING PAGE, LAID OUT LIKE GOOGLE DOCS (BP-526) — /write.
   A page of its own: a top bar with the book's name and save state, a toolbar
   fixed under it, three columns (outline · the page on grey · comments) and a
   status bar. Every colour is the theme's, so the dark theme works; the page
   itself is paper and stays white (BP-521).
   --------------------------------------------------------------------------- */
/* BP-574: BlurPaper's colours on the writing page — the shop's tokens
   (`.shop` above: #00c6cf, #3b3b3b, #e3e0f3, the #262626 plate, 0.2rem
   corners, Open Sans and Playfair), not Google Docs' blue and grey. Like the
   shop it does not follow the system's dark mode: the pages are paper on the
   shop's dark plate either way. The site's own tokens are redefined here, so
   every button, field and menu inside takes the brand without a rule each. */
.gd {
  --accent: #00c6cf; --ink: #3b3b3b; --muted: #6b6f76; --line: #e3e0f3; --bg: #ffffff;
  --gd-canvas: #262626; --gd-chrome: #ffffff; --gd-on-canvas: #e3e0f3; --gd-on-canvas-muted: #a9a6b8;
  --gd-teal-dark: #00a3ab;
  min-height: 100vh; display: flex; flex-direction: column; background: var(--gd-canvas); color: var(--ink);
  font-family: 'Open Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
}
.gd button { border-radius: 0.2rem; }
/* BP-582: where the editor's bars and columns stick — under whatever is held
   above them. On the office's /write nothing is (0). On the author's
   /author/write the editor is inside the shop, and the shop's header (3rem,
   sticky at 0) stays on the screen: at top 0 the editor's bar slid over it on
   the first scroll and the BlurPaper header was gone. */
.gd { --gd-top: 0px; }
.shop .gd { --gd-top: 3rem; }
.shop:has(.gd) .shop-bar { z-index: 35; }
.gd-bar { position: sticky; top: var(--gd-top); z-index: 30; display: flex; align-items: center; gap: 0.75rem; padding: 0.4rem 0.9rem; background: var(--gd-chrome); border-bottom: 3px solid var(--accent); }
.gd-bar { min-height: 4.25rem; box-sizing: border-box; }
.gd-back { display: flex; color: #4285f4; padding: 0.25rem; border-radius: 50%; }
.gd-back:hover { background: rgba(127, 127, 127, 0.15); }
/* BP-535: the name and the menus under it, as in Google Docs. */
.gd-head { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.gd-name-row { display: flex; align-items: center; gap: 0.75rem; min-width: 0; }
.gd-title { font: inherit; font-family: 'Playfair Display', 'Noto Serif', Georgia, serif; font-size: 1.2rem; min-width: 6rem; max-width: 28rem; width: 100%; padding: 0.05rem 0.35rem; border: 1px solid transparent; border-radius: 4px; background: none; color: inherit; }
.gd-title:hover { border-color: var(--line); }
.gd-title:focus { outline: none; border-color: var(--accent); }
.gd-saved { font-size: 0.8rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gd-menus { display: flex; flex-wrap: wrap; gap: 0.05rem; }
.gd-menu { position: relative; }
.gd-menu > button { font-size: 0.86rem; padding: 0.1rem 0.45rem; border: 0; border-radius: 4px; background: none; color: inherit; cursor: pointer; }
.gd-menu > button:hover, .gd-menu > button[aria-expanded="true"] { background: rgba(127, 127, 127, 0.18); }
.gd-menu-list { position: absolute; top: calc(100% + 2px); left: 0; z-index: 50; display: flex; flex-direction: column; min-width: 15rem; padding: 0.35rem 0; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); }
.gd-menu-list > [role="menuitem"] { display: flex; justify-content: space-between; gap: 2rem; padding: 0.4rem 1.1rem; font-size: 0.88rem; text-align: left; text-decoration: none; border: 0; background: none; color: inherit; cursor: pointer; }
.gd-menu-list > [role="menuitem"]:hover { background: rgba(127, 127, 127, 0.15); }
.gd-menu-list kbd { font: inherit; font-size: 0.78rem; color: var(--muted); }
.gd-menu-list hr { margin: 0.3rem 0; border: 0; border-top: 1px solid var(--line); }
.gd-people { font-size: 0.8rem; max-width: 22rem; margin: 0; }
/* The person's role on the book (BP-598): «Редактор» / «Автор». */
.gd-role { font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: 999px; background: #fff4d6; color: #7a5a00; white-space: nowrap; }
.gd-role.is-author { background: #dff6f7; color: #00686d; }
/* «История на версиите» on the writing page (BP-599): a drawer over the right column. */
.gd-versions { position: fixed; top: calc(var(--gd-top, 0px) + 4rem); right: 0; bottom: 2.2rem; width: min(30rem, 100vw); z-index: 40; overflow-y: auto; background: #fff; color: #222; border-left: 1px solid #d8d8d8; box-shadow: -6px 0 18px rgba(0,0,0,0.12); padding: 0.75rem 1rem; font-size: 0.9rem; }
.gd-versions[hidden] { display: none; }
.gd-versions-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; }
.gd-versions button { font-size: 0.8rem; }
.gd-versions-compare { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: 0.5rem 0; }
.gd-versions-compare select { max-width: 13rem; }
.gd-versions-list { list-style: none; padding: 0; margin: 0.5rem 0; }
.gd-version { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.45rem 0.4rem; border-bottom: 1px solid #eee; }
.gd-version.is-viewed { background: #fff8e1; }
.gd-version.is-named strong { color: #00686d; }
.gd-version-buttons { display: flex; gap: 0.5rem; align-items: center; }
.gd-diff { border: 1px solid #e4e4e4; border-radius: 6px; margin: 0.5rem 0; }
.gd-diff-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.4rem; padding: 0.4rem 0.5rem; background: #f6f6f6; border-bottom: 1px solid #e4e4e4; }
.gd-diff-list { list-style: none; margin: 0; padding: 0; max-height: 50vh; overflow-y: auto; }
.gd-diff-item { padding: 0.4rem 0.6rem; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
.gd-diff-item.is-at { background: #eefbfb; outline: 2px solid #00c6cf; outline-offset: -2px; }
.gd-diff-where { font-size: 0.75rem; color: #666; }
.gd-diff-text { margin: 0.2rem 0 0; white-space: pre-wrap; }
.gd-diff-text del, .gd-diff-item.is-removed .gd-diff-text { background: #fde2e2; color: #9b1c1c; text-decoration: line-through; }
.gd-diff-text ins, .gd-diff-item.is-added .gd-diff-text { background: #dcf5e3; color: #11602d; text-decoration: none; }
.gd-diff-item.is-moved .gd-diff-text { background: #eef0ff; }
.gd-diff-item.is-restyled .gd-diff-text { border-left: 3px solid #c9a227; padding-left: 0.4rem; }
/* A suggestion of any kind on its card (BP-600): before → after, as «Сравни» draws it. */
.print-comment-patch { margin: 0.25rem 0; }
.print-comment-patch .help { margin: 0; }
.print-patch-line { margin: 0.15rem 0; white-space: pre-wrap; }
.print-patch-line em { font-style: normal; font-size: 0.75rem; opacity: 0.8; }
.print-patch-line del { color: #e57373; text-decoration: line-through; }
.print-patch-line ins { color: #4fb36a; text-decoration: underline; }
.print-patch-line.is-restyled span { border-bottom: 2px dotted #c9a227; }
.gd-save { display: flex; align-items: center; gap: 0.6rem; margin: 0; }
.gd-auto { font-size: 0.82rem; color: var(--muted); white-space: nowrap; }
.gd-tools { position: sticky; top: calc(var(--gd-top) + 4.25rem); z-index: 25; background: var(--gd-chrome); border-bottom: 1px solid var(--line); }
.gd-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.15rem; padding: 0.25rem 0.9rem; }
/* Always there on this page, as in Google Docs: the module does not hide it in
   its write-only shape (a [hidden] rule here could not win — the global
   `[hidden] { display: none !important }` is deliberate). */
.gd-toolbar button, .gd-toolbar select, .gd-toolbar input, .gd-suggest { font-size: 0.9rem; min-height: 1.9rem; padding: 0.1rem 0.5rem; border: 1px solid transparent; border-radius: 4px; background: none; color: inherit; cursor: pointer; }
.gd-toolbar button:hover, .gd-toolbar select:hover { background: rgba(127, 127, 127, 0.15); }
.gd-toolbar button[aria-pressed="true"] { background: rgba(0, 198, 207, 0.18); color: var(--gd-teal-dark); }
.gd-toolbar input[type="color"] { width: 2rem; padding: 0.1rem; }
.gd-size { width: 4rem; border-color: var(--line) !important; text-align: center; }
.gd-sep { width: 1px; align-self: stretch; margin: 0.3rem 0.35rem; background: var(--line); }
.gd-find { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; padding: 0.2rem 0.9rem 0.35rem; font-size: 0.85rem; }
.gd-find[hidden] { display: none; }
.gd-find input[type="search"], .gd-find input[type="text"] { width: 12rem; padding: 0.2rem 0.4rem; border: 1px solid var(--line); border-radius: 4px; background: var(--bg); color: var(--ink); }
.gd-find button { font-size: 0.82rem; min-height: 1.7rem; padding: 0.1rem 0.55rem; border: 1px solid var(--line); border-radius: 4px; background: none; color: inherit; cursor: pointer; }
.gd-find button:hover { background: rgba(127, 127, 127, 0.15); }
/* The dashed outlines of passages are a layout aid of the settings screen; a
   page being written is just the page. */
.gd .print-preview-page rect[stroke-dasharray] { stroke: transparent; }
/* BP-574: the comments are the column LEFT of the pages (`.gd-right` by its
   old name — the markup is the template's), the outline the one on the right.
   By area, so «Изглед» can drop either and the rest close up. The comments
   stay beside the pages down to a tablet's width: under the text they were
   past the end of a 300-page book. Only on a phone do they go under it. */
.gd-main { flex: 1; display: grid; grid-template-columns: 19rem minmax(0, 1fr) 13rem; grid-template-areas: "notes page outline"; gap: 1rem; padding: 1.25rem 1rem; align-items: start; }
.gd-right { grid-area: notes; }
.gd-canvas { grid-area: page; }
.gd-left { grid-area: outline; }
.gd-left, .gd-right { position: sticky; top: calc(var(--gd-top) + 8rem); max-height: calc(100vh - 10.5rem - var(--gd-top)); overflow-y: auto; font-size: 0.88rem; color: var(--gd-on-canvas); }
/* Изглед → hide a side column; the page takes its room. */
.gd.gd-no-left .gd-left, .gd.gd-no-right .gd-right { display: none; }
.gd.gd-no-left .gd-main { grid-template-columns: 19rem minmax(0, 1fr); grid-template-areas: "notes page"; }
.gd.gd-no-right .gd-main { grid-template-columns: minmax(0, 1fr) 13rem; grid-template-areas: "page outline"; }
.gd.gd-no-left.gd-no-right .gd-main { grid-template-columns: minmax(0, 1fr); grid-template-areas: "page"; }
.gd-left .print-outline-list { max-height: none; }
.gd-left .print-outline-list button { color: var(--gd-on-canvas); }
.gd-left summary { color: var(--gd-on-canvas-muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; cursor: pointer; }
.gd-comment-head .help, .gd-comment-head .notice { color: var(--gd-on-canvas-muted); }
.gd-canvas { max-width: 52rem; width: 100%; margin: 0 auto; }
.gd .print-preview-page { box-shadow: 0 4px 12px 0 rgb(0 0 0 / 70%); }
/* BP-535: every page in one scroll (`pageScroll` in paper-tools-body.js). A slot
   has the page's shape before anything is drawn in it, so the scroll is the
   book's true length and does not jump as pages are drawn. Zoom sets
   --gd-page-width; «fit» leaves it unset. */
.gd-pages { position: relative; width: var(--gd-page-width, 100%); margin: 0 auto; }
.gd.is-zoomed .gd-canvas { max-width: none; overflow-x: auto; }
.gd-page { position: relative; margin: 0 auto 1.25rem; background: #fff; box-shadow: 0 4px 12px 0 rgb(0 0 0 / 70%); scroll-margin-top: calc(var(--gd-top) + 9rem); cursor: text; }
.gd-find-close { margin-left: auto; }
.gd-loading { display: flex; flex-direction: column; align-items: center; gap: 1rem; padding: 6rem 1rem; color: var(--gd-on-canvas); text-align: center; }
.gd-loading[hidden] { display: none; }
.gd-loading p { margin: 0; font-size: 0.95rem; }
.gd-spinner { width: 3rem; height: 3rem; border-radius: 50%; border: 4px solid rgba(0, 198, 207, 0.25); border-top-color: var(--accent); animation: gd-spin 0.9s linear infinite; }
@keyframes gd-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .gd-spinner { animation-duration: 3s; } }
.gd-page .print-preview-page { display: block; box-shadow: none; }
/* BP-536: the selection on a page is the editor's own (blue, drawn in the SVG).
   The browser's — with a touch screen's handles and copy menu — is turned off:
   it selected glyphs the editor never saw, and its menu covered the toolbar.
   A long press is answered by the editor's own menu instead. */
.gd-pages { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
/* The threads are in the right column; the margin cards would repeat them. */
.gd .print-preview.has-margin { display: block; }
.gd .print-margin { display: none; }
.gd-right .print-comments { border: 0; padding: 0; background: none; }
/* BP-574: the cards beside the pages (`commentRail` in paper-tools-body.js).
   With them, the column scrolls with the pages instead of on its own, and its
   head stays at the top of the screen over the cards. Without them (the
   column under the pages on a narrow screen) none of this applies. */
.gd-right.has-rail { position: static; max-height: none; overflow: visible; }
.gd-right.has-rail .gd-comment-head { position: sticky; top: calc(var(--gd-top) + 8rem); z-index: 3; max-height: 50vh; overflow-y: auto; padding-bottom: 0.4rem; background: var(--gd-canvas); }
.gd-right.has-rail .print-comments { position: relative; }
.gd-rail { position: absolute; top: 0; left: 0; right: 0; list-style: none; margin: 0; padding: 0; }
.gd-right:not(.has-rail) .gd-rail { display: none; }
.gd-rail > .print-comment, .gd-rail > .gd-rail-new { position: absolute; left: 0; right: 0; margin: 0; padding: 0.45rem 0.6rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-right: 3px solid rgba(255, 190, 0, 0.9); border-radius: 0.2rem; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 30%); transition: transform 0.15s ease; }
.gd-rail .help { color: var(--muted); }
.gd-rail > .print-comment.is-done { opacity: 0.75; border-right-color: var(--line); }
.gd-rail > .print-comment.is-folded { cursor: pointer; }
.gd-rail > .print-comment.is-folded:hover { box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18); }
/* Folded: who and what, as Google Docs shows a card that is not chosen. */
.gd-rail > .is-folded .print-comment-quote,
.gd-rail > .is-folded .print-comment-buttons,
.gd-rail > .is-folded .print-comment-replies { display: none; }
.gd-rail > .is-folded .print-comment-note { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
/* Chosen: level with its words, out of the column towards them. */
.gd-rail > .print-comment.is-open { z-index: 2; transform: translateX(0.75rem); background: var(--bg); border-right-color: var(--accent); box-shadow: 0 4px 12px 0 rgb(0 0 0 / 70%); }
/* The comment being written, beside its words, in front of the cards. */
.gd-rail > .gd-rail-new { z-index: 3; transform: translateX(0.75rem); border-right-color: var(--accent); box-shadow: 0 4px 12px 0 rgb(0 0 0 / 70%); }
.gd-rail > .gd-rail-new .help { margin-top: 0; }
/* «+ Коментар», on the column's edge next to the page, level with the selection. */
.gd-rail-add { position: absolute; right: -0.5rem; z-index: 4; padding: 0.3rem 0.7rem; font-size: 0.85rem; font-weight: 600; border-radius: 1rem !important; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 30%); }
.gd-rail-add[hidden] { display: none; }
.gd-pager { display: flex; justify-content: center; align-items: center; gap: 0.6rem; padding: 0.6rem; font-size: 0.85rem; }
.gd-status { position: sticky; bottom: 0; z-index: 20; display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; padding: 0.3rem 0.9rem; font-size: 0.8rem; color: var(--muted); background: var(--gd-chrome); border-top: 1px solid var(--line); }
.gd-status button { font-size: 0.8rem; padding: 0.1rem 0.5rem; }
.gd-status > .gd-pager { margin-left: auto; padding: 0; gap: 0.3rem; font-size: 0.8rem; }
.gd-status > .gd-pager label, .gd-status > .gd-pager span { white-space: nowrap; }
.gd-status > .gd-pager input { width: 2.6rem; padding: 0 0.2rem; text-align: center; }
.gd-status > .gd-pager button { padding: 0 0.4rem; }
/* One line, always: a long status is cut, and the whole of it is its title. */
@media (min-width: 1100px) {
  .gd-status { flex-wrap: nowrap; }
  .gd-status > span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .gd-status > button, .gd-status > .gd-pager { flex: none; }
}
.gd-passage { position: fixed; right: 1rem; bottom: 3rem; z-index: 40; width: 22rem; max-height: calc(100vh - 8rem); overflow-y: auto; padding: 1rem; background: var(--bg); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25); }
.gd-passage[hidden] { display: none; }
.gd-passage h3 { margin: 0 0 0.5rem; font-size: 0.95rem; }
.gd-passage label { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin: 0.35rem 0; font-size: 0.85rem; }
.gd-passage label:has(textarea) { flex-direction: column; align-items: stretch; }
.gd-passage label:has(input[type="checkbox"]) { justify-content: flex-start; }
.gd-passage textarea { width: 100%; font: inherit; font-size: 0.9rem; }
.gd-passage input[type="text"] { width: 10rem; }
.gd-passage button { font-size: 0.8rem; padding: 0.25rem 0.6rem; margin: 0.15rem 0.1rem 0 0; }
.gd-empty { max-width: 40rem; margin: 4rem auto; padding: 1.5rem; background: var(--bg); border: 1px solid var(--line); border-radius: 8px; }
/* A laptop: the outline goes under the pages, the comments stay beside them. */
@media (max-width: 1250px) {
  .gd-main, .gd.gd-no-left .gd-main { grid-template-columns: 16rem minmax(0, 1fr); grid-template-areas: "notes page" ". outline"; }
  .gd.gd-no-right .gd-main, .gd.gd-no-left.gd-no-right .gd-main { grid-template-columns: minmax(0, 1fr); grid-template-areas: "page" "outline"; }
  .gd-left { position: static; max-height: none; }
}
/* A phone: one column, the comments as a list under the page. */
@media (max-width: 760px) {
  .gd-main, .gd.gd-no-left .gd-main, .gd.gd-no-right .gd-main { grid-template-columns: minmax(0, 1fr); grid-template-areas: "page" "notes" "outline"; }
  .gd-left, .gd-right { position: static; max-height: none; }
}
/* THE EXPLORER (BP-604, shared since BP-608): folders and files worked like
   Windows Explorer — the tree on the left, the open folder on the right.
   app/functions/_explorer.js draws it, for «Моите документи» (/dokumenti),
   BlurFolder and any later use (docs/EXPLORER.md). It lives here because every
   site links this file, so an application's page gets it with nothing to add. */
/* The shop is white by design, not by preference (D76), and the menu and the
   dialog hang off <body>, outside `.shop`: so the explorer names its own
   colours rather than borrowing the admin's, which follow the dark scheme. An
   application gives it other colours by setting these four on
   `.its-page .docs-frame, .docs-menu, .docs-dialog-scrim` in its own sheet. */
.docs-page, .docs-frame, .docs-menu, .docs-dialog-scrim { --docs-bg: #ffffff; --docs-ink: #3b3b3b; --docs-line: #e3e0f3; --docs-muted: #6f6c7d; }
.docs-frame { border: 1px solid var(--docs-line); border-radius: 8px; background: var(--docs-bg); color: var(--docs-ink); overflow: hidden; }
.docs-frame.is-busy { cursor: progress; }
.docs-frame.is-busy .docs-main { opacity: 0.7; }
.docs-bar { display: flex; align-items: center; gap: 0.35rem; padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--docs-line); }
.docs-nav { min-width: 2rem; min-height: 2rem; padding: 0 0.4rem; border: 1px solid transparent; border-radius: 4px; background: none; color: inherit; font-size: 1.05rem; cursor: pointer; }
.docs-nav:hover:not(:disabled) { background: rgba(127, 127, 127, 0.15); }
.docs-nav:disabled { opacity: 0.35; cursor: default; }
.docs-path { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 0.1rem; padding: 0.15rem 0.4rem; border: 1px solid var(--docs-line); border-radius: 4px; }
.docs-crumb { padding: 0.15rem 0.35rem; border: 1px solid transparent; border-radius: 3px; background: none; color: inherit; font-size: 0.9rem; cursor: pointer; white-space: nowrap; max-width: 14rem; overflow: hidden; text-overflow: ellipsis; }
.docs-crumb:hover { background: rgba(127, 127, 127, 0.15); }
.docs-crumb-sep { color: var(--docs-muted); }
.docs-search { flex: 0 1 14rem; min-width: 7rem; padding: 0.3rem 0.5rem; border: 1px solid var(--docs-line); border-radius: 4px; background: var(--docs-bg); color: inherit; font-size: 0.9rem; }
.docs-toolrow { display: flex; align-items: center; gap: 0.35rem; padding: 0.35rem 0.6rem; border-bottom: 1px solid var(--docs-line); }
.docs-tools { flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }
.docs-tools button, .docs-tree-toggle { min-height: 2rem; padding: 0.15rem 0.7rem; border: 1px solid var(--docs-line); border-radius: 4px; background: none; color: inherit; font-size: 0.88rem; cursor: pointer; }
.docs-tools button:hover, .docs-tree-toggle:hover { background: rgba(127, 127, 127, 0.12); }
.docs-tools button.primary { background: #00c6cf; border-color: #00c6cf; color: #fff; }
.docs-tools button.docs-view[aria-pressed="true"] { background: rgba(0, 198, 207, 0.16); border-color: #00a3ab; }
.docs-spacer { flex: 1 1 auto; }
.docs-tree-toggle { display: none; }
.docs-body { display: grid; grid-template-columns: 15rem minmax(0, 1fr); min-height: 26rem; }
.docs-tree { border-right: 1px solid var(--docs-line); padding: 0.4rem 0.25rem; overflow: auto; max-height: 38rem; font-size: 0.9rem; }
.docs-tree-row { display: flex; align-items: center; gap: 0.3rem; padding: 0.22rem 0.4rem; border-radius: 4px; cursor: pointer; white-space: nowrap; }
.docs-tree-row:hover { background: rgba(127, 127, 127, 0.12); }
.docs-tree-row.is-here { background: rgba(0, 198, 207, 0.18); }
.docs-tree-name { overflow: hidden; text-overflow: ellipsis; }
.docs-caret { width: 1rem; padding: 0; border: 0; background: none; color: var(--docs-muted); font-size: 0.75rem; cursor: pointer; }
.docs-icon { width: 1.15rem; height: 1.15rem; flex: 0 0 auto; }
.docs-icon-folder { color: #e9b949; }
.docs-icon-doc { color: #4a7bd0; }
.docs-icon-trash, .docs-icon-people, .docs-icon-home, .docs-icon-cloud, .docs-icon-box { color: var(--docs-muted); }
.docs-icon-image { color: #2e9d6a; }
.docs-icon-pdf { color: #d0453b; }
.docs-icon-audio, .docs-icon-video { color: #7a4fd1; }
.docs-icon-archive, .docs-icon-font, .docs-icon-file { color: #8a6d3b; }
.docs-icon-book { color: #00a3ab; }
.docs-main { position: relative; padding: 0.6rem; overflow: auto; max-height: 38rem; outline: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.docs-main:focus-visible { box-shadow: inset 0 0 0 2px rgba(0, 198, 207, 0.5); }
.docs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: 0.4rem; align-content: start; }
.docs-grid .docs-item { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; padding: 0.6rem 0.35rem 0.5rem; border: 1px solid transparent; border-radius: 6px; text-align: center; cursor: default; }
.docs-grid .docs-item:hover { background: rgba(127, 127, 127, 0.1); }
.docs-tile-icon { position: relative; display: block; }
.docs-tile-icon .docs-icon { width: 3rem; height: 3rem; }
.docs-thumb { display: block; width: 4.5rem; height: 4.5rem; object-fit: contain; border-radius: 4px; background: rgba(127, 127, 127, 0.08); }
.docs-tile-icon .docs-role { position: absolute; left: 50%; bottom: -0.2rem; transform: translateX(-50%); }
.docs-tile-icon .docs-unread { position: absolute; top: 0; right: -0.2rem; }
.docs-grid .docs-name { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 0.86rem; line-height: 1.25; word-break: break-word; }
.docs-item.is-selected { background: rgba(0, 198, 207, 0.16) !important; border-color: rgba(0, 163, 171, 0.55) !important; }
.docs-item.is-drop, .docs-tree-row.is-drop, .docs-crumb.is-drop { background: rgba(0, 198, 207, 0.3) !important; outline: 2px dashed #00a3ab; outline-offset: -2px; }
.docs-role { padding: 0 0.35rem; border-radius: 999px; font-size: 0.65rem; line-height: 1.3rem; background: var(--docs-bg); border: 1px solid var(--docs-line); color: var(--docs-muted); white-space: nowrap; }
.docs-role.is-shared { color: #7a4fd1; border-color: #c9b5f2; }
.docs-unread { display: inline-block; width: 0.6rem; height: 0.6rem; border-radius: 50%; background: #e8483c; vertical-align: middle; }
.docs-meta { font-size: 0.72rem; color: var(--docs-muted); }
.docs-rename { width: 100%; padding: 0.15rem 0.3rem; border: 1px solid #00a3ab; border-radius: 3px; background: var(--docs-bg); color: inherit; font: inherit; font-size: 0.86rem; text-align: inherit; }
.docs-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.docs-table th { text-align: left; font-weight: 400; color: var(--docs-muted); border-bottom: 1px solid var(--docs-line); padding: 0; }
.docs-sort { width: 100%; padding: 0.35rem 0.5rem; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.docs-sort:hover { background: rgba(127, 127, 127, 0.1); }
.docs-table td { padding: 0.32rem 0.5rem; border-bottom: 1px solid var(--docs-line); white-space: nowrap; }
.docs-table tr.docs-item { cursor: default; }
.docs-table tr.docs-item:hover { background: rgba(127, 127, 127, 0.08); }
.docs-cell-name { display: flex; align-items: center; gap: 0.45rem; white-space: normal !important; }
.docs-cell-name .docs-name { overflow: hidden; text-overflow: ellipsis; }
.docs-empty { margin: 3rem 1rem; text-align: center; color: var(--docs-muted); }
.docs-status { min-height: 1.4rem; margin: 0; padding: 0.3rem 0.7rem; border-top: 1px solid var(--docs-line); font-size: 0.85rem; color: var(--docs-muted); }
.docs-status.is-error { color: #c0392b; }
.docs-menu { position: fixed; z-index: 1000; min-width: 13rem; padding: 0.3rem 0; border: 1px solid var(--docs-line); border-radius: 6px; background: var(--docs-bg); color: var(--docs-ink); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); font-size: 0.9rem; }
.docs-menu button { display: flex; width: 100%; justify-content: space-between; gap: 1.5rem; padding: 0.4rem 0.9rem; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.docs-menu button:hover:not(:disabled), .docs-menu button:focus-visible { background: rgba(0, 198, 207, 0.15); outline: none; }
.docs-menu button:disabled { opacity: 0.4; cursor: default; }
.docs-menu button.is-danger { color: #c0392b; }
.docs-menu kbd { font: inherit; font-size: 0.75rem; color: var(--docs-muted); }
.docs-menu-line { height: 1px; margin: 0.25rem 0; background: var(--docs-line); }
.docs-dialog-scrim { position: fixed; inset: 0; z-index: 1001; display: flex; align-items: center; justify-content: center; padding: 1rem; background: rgba(0, 0, 0, 0.35); }
.docs-dialog { width: min(28rem, 100%); max-height: 80vh; display: flex; flex-direction: column; padding: 1rem; border-radius: 8px; background: var(--docs-bg); color: var(--docs-ink); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25); }
.docs-dialog h2 { margin: 0 0 0.6rem; font-size: 1.05rem; overflow-wrap: anywhere; }
.docs-picks { flex: 1 1 auto; overflow: auto; border: 1px solid var(--docs-line); border-radius: 4px; padding: 0.25rem; }
.docs-pick { display: flex; align-items: center; gap: 0.4rem; width: 100%; padding: 0.3rem 0.5rem; border: 0; border-radius: 3px; background: none; color: inherit; font: inherit; font-size: 0.9rem; text-align: left; cursor: pointer; }
.docs-pick:hover:not(:disabled) { background: rgba(127, 127, 127, 0.12); }
.docs-pick.is-picked { background: rgba(0, 198, 207, 0.2); }
.docs-pick:disabled { opacity: 0.4; cursor: default; }
.docs-dialog-buttons { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 0.8rem; }
.docs-dialog-buttons button { min-height: 2.1rem; padding: 0.2rem 0.9rem; border: 1px solid var(--docs-line); border-radius: 4px; background: none; color: inherit; cursor: pointer; }
.docs-dialog-buttons button.primary { background: #00c6cf; border-color: #00c6cf; color: #fff; }
.docs-dialog-buttons button:disabled { opacity: 0.5; cursor: default; }
/* A phone: the tree folds away behind «Папки», the folder takes the width. */
@media (max-width: 700px) {
  .docs-body { grid-template-columns: minmax(0, 1fr); }
  .docs-tree { display: none; border-right: 0; border-bottom: 1px solid var(--docs-line); max-height: 40vh; }
  .docs-frame.is-tree-open .docs-tree { display: block; }
  .docs-tree-toggle { display: inline-block; }
  .docs-bar { flex-wrap: wrap; }
  .docs-search { flex: 1 1 100%; order: 3; }
  .docs-grid { grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr)); }
  .docs-table th:nth-child(3), .docs-table td:nth-child(3), .docs-table th:nth-child(4), .docs-table td:nth-child(4) { display: none; }
  .docs-main, .docs-tree { max-height: none; }
}
