/*
 * 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; }
.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-brand { font-weight: 650; text-decoration: none; color: var(--ink); }
.paper-nav { display: flex; gap: 1rem; flex-wrap: wrap; }
.paper-nav a { color: var(--muted); text-decoration: none; }
.paper-nav a:hover { color: var(--accent); }
.paper-who { margin-left: auto; color: var(--muted); font-size: 0.85rem; }
.paper-who em { font-style: normal; padding: 0.1rem 0.4rem; border: 1px solid var(--line); border-radius: 999px; }
.paper-main { flex: 1; width: 100%; max-width: 78rem; margin: 0 auto; padding: 1.5rem 1.2rem 3rem; }

.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 label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.8rem; color: var(--muted); }
.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-head {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.1rem 1rem;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--line);
}
.order-cell { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; font-size: 0.92rem; }
.order-cell > div { overflow-wrap: anywhere; }
.order-label {
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted);
}
.order-where { grid-column: span 2; }

/* Each delivery line says which of the three it is. The marker is the quiet
   half — the operator is scanning for the value, and having to work out whether
   a middle line is an office or a street is what the label removes. */
.order-field { display: flex; gap: 0.35rem; align-items: baseline; }
.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-products { list-style: none; margin: 0; padding: 0.5rem 1rem; }
.order-product { display: flex; gap: 0.75rem; align-items: baseline; padding: 0.15rem 0; }
.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; }

/* The lev amount, under the euro one. Second, smaller and grey, because it is
   there to be checked against rather than read: euro is what the customer pays
   and what the courier is told to collect. */
.money-alt {
  display: block; font-size: 0.78rem; font-weight: 400;
  color: var(--muted); 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-foot {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  flex-wrap: wrap; padding: 0.6rem 1rem; border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 88%, var(--ink));
}
.order-speditor { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
.order-speditor .tracking { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; }
.order-actions { display: flex; gap: 0.5rem; margin-left: auto; }
.order-actions form { margin: 0; }
.order-actions button { padding: 0.4rem 0.9rem; font-size: 0.85rem; }
.order .form-message { margin: 0.4rem 1rem; }

/* The shipment accordion.
   `<details>` does the opening and closing, so there is no state to style around
   and no script to load — only the marker, which browsers draw differently
   enough to be worth replacing. */
.order-shipment { border-top: 1px solid var(--line); }
.order-shipment > summary {
  display: flex; align-items: baseline; gap: 0.5rem; cursor: pointer;
  padding: 0.55rem 1rem; font-size: 0.9rem; list-style: none;
  background: color-mix(in srgb, var(--bg) 94%, var(--ink));
}
.order-shipment > summary::-webkit-details-marker { display: none; }
.order-shipment > summary::before {
  content: "▸"; color: var(--muted); font-size: 0.8rem;
  transition: transform 0.12s ease; display: inline-block;
}
.order-shipment[open] > summary::before { transform: rotate(90deg); }
.order-shipment > summary:hover { background: color-mix(in srgb, var(--bg) 86%, var(--ink)); }
.order-shipment .shipment-title { font-weight: 600; }

.shipment-form { padding: 0.8rem 1rem 1rem; }
.shipment-form .notice { margin: 0 0 0.8rem; }
.shipment-grid {
  display: grid; gap: 0.7rem 1rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.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; }
.shipment-form.is-unchanged button[type="submit"] { opacity: 0.55; cursor: not-allowed; }

.pager-nav { display: flex; align-items: center; gap: 1rem; justify-content: center; }

/* Payouts ------------------------------------------------------------------ */

.payout-run { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.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; }

@media (max-width: 720px) {
  .order-head { grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); }
  .order-where { grid-column: 1 / -1; }
  .order-foot { flex-direction: column; align-items: stretch; }
  .order-actions { margin-left: 0; }
}

/* 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 ------------------------------------------------------------------ */

/* The bar is 3rem tall and has no padding, so it is exactly as tall as the
   logo standing in it and the logo starts at the very edge of the screen.
   Both of those are the Angular header, which is a row of 3rem squares with
   nothing around them (`main/header/header.component.html`: `width-3rem` on a
   `background-styled` button, and `.upper-header` with no padding at all).
   Height comes from the tiles rather than from a `height` here, so the bar can
   never be shorter than the thing inside it. */
.shop-bar {
  display: flex; align-items: center;
  position: sticky; top: 0; z-index: 20;
  background: var(--paper);
  box-shadow: var(--shadow);
}

/* The brand, and the only thing on the left of the bar since BP-82. It takes
   the spare width `.shop-nav` used to take, which is what keeps the cart button
   pinned right. */
.shop-brand {
  display: inline-flex; align-items: center;
  gap: 0.5rem;
  margin-right: auto;
  text-decoration: none;
}

/* The brand is two files: the mark, then the wordmark.
 *
 * It was a CSS triangle plus the site's name in text until BP-82, defended here
 * as "one less request, and it cannot 404". The shape was a triangle and
 * BlurPaper's mark is a folded page, so it was not the brand — it was a
 * brand-shaped placeholder, wrong in a way no visitor would ever report.
 *
 * The tile is not a decorative panel behind the logo — it is the half of the
 * logo the file does not contain. `BlurPaperLogoInner.png` is a white folded
 * page on transparency, so on this white bar it renders as nothing whatsoever;
 * the Angular header solved it the same way, by drawing the mark inside a solid
 * brand-coloured button.
 *
 * Every number here is transcribed from that header rather than chosen:
 * `width-3rem` on the button, `padding-07` inside it (3 − 1.4 = 1.6rem of
 * image), and a `height-3rem padding-half` box around the wordmark, which is
 * 2rem of picture. Square corners, because the tile now sits in the corner of
 * the viewport and a radius there leaves two white notches against the edge —
 * `background-styled` upstream has no radius either. */
.shop-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3rem; height: 3rem; flex: 0 0 auto;
  background: var(--teal);
}
.shop-mark img { width: 1.6rem; height: auto; display: block; }

.shop-wordmark { height: 2rem; width: auto; display: block; }

/* The cart button is the brand tile's mirror: the same 3rem square in the same
   colour, at the other end of the same bar. It was a bare teal glyph on white,
   which read as a stray mark rather than as a control — the pair is what makes
   both of them read as the two ends of a header. Upstream's cart is the same
   `background-styled ... color-white width-3rem` button as the logo's, so this
   is a transcription too and not a new idea.
   `position: relative` is the badge's anchor. */
.shop-cart-open {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative;
  width: 3rem; height: 3rem; flex: 0 0 auto;
  padding: 0; border: 0; cursor: pointer;
  background: var(--teal); color: #fff;
  /* The framework's `button` rule sets `border-radius: 6px` for the admin
     panel, and a shop control built on a <button> inherits it. `.shop-mark` is
     a <span> and was square, so this was the same 3rem teal tile at the other
     end of the same bar with rounded corners — the kind of difference that
     reads as sloppiness rather than as a decision.

     The second time the admin's `button` rule has come through into this shop:
     D143 was its `color: #fff`, which painted two controls white on white. See
     D147. */
  border-radius: 0;
}

/* A cart.
 *
 * The rule below this one used to be the whole of it: two pseudo-elements, a
 * bottomless rounded box and a pair of box-shadow dots, defended as "drawn in
 * CSS for the same reason as the wordmark's triangle". Nobody read it as a
 * cart. Two rectangles and two circles is a shopping trolley only if you are
 * already expecting one, and the person who has to be told what the button does
 * is exactly the person who has never seen this shop before. The Angular header
 * used Font Awesome's `faShoppingCart` and it was legible at a glance.
 *
 * So it is a real cart now: a handle, a basket, two wheels. It is a mask rather
 * than an `<img>` or an inline `<svg>` for three separate reasons.
 *
 *   - `currentColor`. A mask paints `background`, so the glyph is teal in the
 *     bar and inherits whatever colour a future button gives it. An `<img>`
 *     is a fixed colour, and re-colouring one means a second file.
 *   - The database stays out of it. The shell component names `.cart-glyph`
 *     and nothing else, so the shape lives in the stylesheet with the rest of
 *     the chrome rather than as markup in a row (CLAUDE.md §2 is about code,
 *     but the habit is the same one).
 *   - No request. The old rule's one real argument — it cannot 404 — survives
 *     intact, because the path is inline here.
 *
 * The geometry is drawn here rather than copied from an icon set: Font
 * Awesome's free icons are CC BY 4.0 and want attribution, and a handle, a
 * trapezium and two circles are not worth a licence file.
 *
 * `@supports` keeps the old drawing as the fallback. Masks are ~98% of
 * browsers; the 2% that would otherwise get a solid teal rectangle — which
 * reads as nothing at all — get the abstract cart they had yesterday. */
.cart-glyph {
  width: 1.7rem; height: 1.35rem; position: relative; display: inline-block;
}
.cart-glyph::before {
  content: ''; position: absolute; inset: 0 0 0.4rem 0;
  border: 0.19rem solid currentColor; border-top-width: 0;
  border-radius: 0 0 var(--r) var(--r);
}
.cart-glyph::after {
  content: ''; position: absolute; left: 0.28rem; right: 0.28rem; bottom: 0;
  height: 0.34rem; border-radius: 50%;
  box-shadow: -0.28rem 0 0 currentColor, 0.28rem 0 0 currentColor;
}

/* The path is a custom property so the two mask declarations below cannot
   drift apart, and it is one unbroken line on purpose: a CSS string may be
   continued with a backslash before a newline, but that is exactly the kind of
   thing an editor, a codec or a heredoc quietly eats (CLAUDE.md §8). `stroke`
   with no `fill`, so the mask is the outline of a cart and not a solid blob. */
.cart-glyph {
  --cart-svg: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 3.5h2.4l2.3 10.7h10l2.5-7.9H5.6"/><circle cx="9" cy="18.6" r="1.6"/><circle cx="16" cy="18.6" r="1.6"/></svg>');
}

@supports (mask-image: none) or (-webkit-mask-image: none) {
  .cart-glyph {
    width: 1.55rem; height: 1.55rem;
    background: currentColor;
    -webkit-mask: var(--cart-svg) center / contain no-repeat;
            mask: var(--cart-svg) center / contain no-repeat;
  }
  .cart-glyph::before, .cart-glyph::after { content: none; }
}

/* The badge is `hidden` until there is something in the basket, so an empty
   cart shows no number at all rather than a nought. */
/* Pinned to the tile's top-right corner rather than sitting beside the glyph,
   because the tile is a fixed 3rem square now and a sibling would either stretch
   it or fall outside it. Upstream hangs it in the same corner
   (`top-right-0 border-radius-7rem` on the cart button). */
.cart-badge {
  position: absolute; top: 0; right: 0;
  min-width: 1.25rem; height: 1.25rem; padding: 0 0.3rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--red); color: #fff;
  font: 700 0.75rem/1 var(--sans);
}

/* No padding, and this line is load-bearing.
 *
 * The shop was reported as having "unneeded white space" under the header and
 * above the footer on the catalogue, and the catalogue has no such padding —
 * what shows through is `main { padding: 2rem 0 3rem }` near the top of this
 * file, the framework's rule for its own document-shaped pages. On a page whose
 * first element paints itself edge to edge, a white gutter around it is not
 * spacing, it is a seam: 2rem of white between a white bar and a dark section,
 * looking for all the world like something failed to load.
 *
 * So the shop's main owns no spacing and every shop page brings its own. Most
 * already do (`.legal`, `.checkout`, `.auth-page`, `.aphorisms`, `.ebook`,
 * `.audio`, `.bonuses`, `.signed`); the two full-bleed ones did not, and get it
 * below. If you add a shop page, give its root element padding — nothing else
 * will.
 *
 * The framework's rule is left alone. The admin panel and the demo site are
 * documents in a white page and the gutter is right for them; it is only wrong
 * here, so it is only overridden here. */
.shop-main { flex: 1 1 auto; padding: 0; }

/* 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);
}

.consent-text { margin: 0; max-width: 46rem; }

.consent-actions { display: flex; gap: 0.6rem; flex: 0 0 auto; }

.consent .buy { padding: 0.55rem 1.4rem; font-size: 0.8rem; }

.consent-deny {
  padding: 0.55rem 1.4rem;
  border: 1px solid rgba(255, 255, 255, 0.4); border-radius: 2px;
  background: none; color: #fff; cursor: pointer;
  font: 700 0.8rem/1 var(--sans); text-transform: uppercase;
}

.consent-deny:hover { border-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).
 */

.buy-bar {
  display: flex; align-items: stretch; justify-content: center;
  gap: 0; margin: 2rem 0;
  background: var(--red); color: #fff;
  text-decoration: none;
  border-radius: var(--r);
  overflow: hidden;
}

.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); }
a.buy-bar: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 .frac {
  display: inline-flex; flex-direction: column; align-items: flex-start;
  margin-left: 0.1em;
  font-size: 0.44em; line-height: 1.05; font-weight: 700;
}
.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;
}

/* The catalogue ------------------------------------------------------------
 *
 * Dark, as upstream sets it: the covers are the page, and a dark ground is what
 * lets a hundred of them sit together without every white cover fighting the
 * one beside it.
 *
 * `--plate` and not `--dark`, and the difference between them is the whole
 * point. The cover files are photographs already plated on #262626, so a
 * section painted #3b3b3b turns every card into a visible rectangle sitting on
 * a slightly different rectangle — a hundred and two of them, in a grid, which
 * is the kind of wrongness that looks like a rendering fault. Matching the
 * ground to the plate makes the card the picture and nothing else. It is why
 * `.book-card-cover img` below carries no drop shadow. */

.book-list { background: var(--plate); color: #fff; padding-bottom: 3rem; }

.book-list-head { padding-block: 2.5rem 1.5rem; text-align: center; }
.book-list-head h1 {
  margin: 0; font: 400 2.6rem/1.15 var(--serif); color: #fff;
}
.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.

   `padding-inline: 1rem` rather than the 1.2rem the measure list gives every
   other band: at this width the cover runs the width of the grid, so this
   padding IS the gap between the picture and the edge of the screen.

   `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: 1rem;
  }
  .book-card-head { min-height: 0; }
  .book-card-cover img { max-height: 70vh; }
}

.book-card { display: flex; flex-direction: column; text-align: center; }

/* The words above the cover are a FIXED box, and that is the whole reason this
 * wrapper exists.
 *
 * The card is a flex column, so the cover starts wherever the title stopped: a
 * one-line title and a two-line title side by side put their covers at two
 * different heights, and a grid of covers that do not line up is the one thing
 * this page is for. `min-height` reserves two lines of title and one of
 * subtitle whether or not either is used, so a book with no subtitle leaves the
 * gap rather than pulling its cover up into it.
 *
 * A longer title is clamped and ellipsised rather than allowed a third line.
 * The full text is still the link's own text, so a hover, a screen reader and
 * «find on page» all get the whole title — only the drawing is cut.
 *
 *   two lines of 1.25rem/1.25  = 3.125rem
 *   the title's bottom margin  = 0.15rem
 *   one line of 0.95rem/1.3    = 1.235rem
 *   the subtitle's margin      = 0.5rem
 *                                -------
 *                                5.01rem
 */
.book-card-head {
  min-height: 5.01rem;
  display: flex; flex-direction: column; justify-content: flex-end;
}

.book-card-title {
  margin: 0 0 0.15rem; font: 400 1.25rem/1.25 var(--serif);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-clamp: 2; overflow: hidden;
}
.book-card-title a { text-decoration: none; }
.book-card-title a:hover { color: var(--teal); }
.book-card-sub {
  margin: 0 0 0.5rem; font: 400 0.95rem/1.3 var(--serif);
  color: var(--light); opacity: 0.8;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
  line-clamp: 1; overflow: hidden;
}

/* The sentinel `app/functions/paper-book-list.js` watches for. It is empty, and
   `hidden` until that file has cards left to reveal, so a page with no
   JavaScript has nothing here at all. */
.books-more { height: 2rem; }

.book-card-cover {
  display: block; margin: 0.6rem 0 0.9rem;
}
/* A fixed ratio, because the covers are not one shape: they are somebody
   else's photographs of somebody else's mockups, and a grid whose rows are as
   tall as their tallest picture reads as an accident. `contain` letterboxes
   rather than crops — cutting the top off a book cover is worse than a margin,
   and the letterbox bars are the section's ground, which is the plate's colour,
   so they do not show.

   No drop shadow. There was one, and it made sense while the ground was a
   different grey from the plate: it lifted the card off the page. Now that the
   two match it does the exact opposite — a shadow traces the edge of the JPEG,
   so the one thing left drawing a rectangle around every book would be the
   effect that was there to stop it looking like a rectangle. The hover lift
   stays; it is a movement, not an outline. */
.book-card-cover img {
  width: 100%; height: auto; display: block;
  aspect-ratio: 3 / 4; object-fit: contain;
  transition: transform 0.18s ease;
}
.book-card-cover:hover img { transform: translateY(-4px); }

/* 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; }
.book-card .buy-bar .buy { flex: 1 1 auto; padding: 0.7rem 0.6rem; font-size: 0.9rem; }
.book-card .buy-bar .price-tag { padding: 0.45rem 0.7rem; gap: 0.55rem; }
.book-card .price-tag .amount { font-size: 1.3rem; }
.book-card .price-tag .amount.second { font-size: 1rem; }
.book-card .buy-bar.quiet { background: var(--light); color: var(--dark); }

.book-card-was { margin: 0.5rem 0 0; font-size: 0.85rem; color: var(--light); }
.book-card-was s { opacity: 0.7; }

/* The product page --------------------------------------------------------- */

.product-head { text-align: center; }

/* The tagline strip: teal, full bleed, the line the book is sold on. */
.product-eyebrow {
  margin: 0; padding: 0.7rem 1.2rem;
  background: var(--teal); color: #fff;
  font: 400 1.35rem/1.3 var(--serif);
}
.product-eyebrow:empty { display: none; }

/* The title band. */
.product-head h1 {
  margin: 0; padding: 1.4rem 1.2rem;
  background: var(--dark); color: #fff;
  font: 700 2.6rem/1.15 var(--sans);
}
.product-sub {
  margin: 0; padding: 0 1.2rem 1.4rem;
  background: var(--dark); color: var(--light);
  font: 400 1.15rem/1.4 var(--serif);
}
.product-sub:empty { display: none; }

.product-main {
  display: grid; gap: 2.5rem;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  padding-block: 3rem;
}

/* The cover is second in the DOM's source order because the page is about the
   book before it is about the picture; on a wide screen it belongs on the right,
   which is what `order` is for. */
.product-cover { margin: 0; order: 2; }
.product-cover img {
  width: 100%; height: auto; display: block;
  filter: drop-shadow(var(--shadow-lg));
}

.product-buy { order: 1; }

/* The pitch: the argument for the book, set as upstream sets it — teal,
   uppercase, centred, and the largest thing in the column. */
.product-pitch { margin: 0 0 1.75rem; text-align: center; }
.product-pitch p {
  margin: 0; color: var(--teal);
  font: 700 1.85rem/1.25 var(--sans);
  text-transform: uppercase;
}

/* The prose, with the shop's red square instead of a bullet. */
.product-points { margin: 0; padding: 0; list-style: none; }
.product-points li {
  position: relative; padding-left: 1.6rem; margin-bottom: 1.1rem;
  font: 400 1.15rem/1.5 var(--serif);
}
.product-points li::before {
  content: ''; position: absolute; left: 0; top: 0.45em;
  width: 0.7rem; height: 0.7rem; background: var(--red);
}

/* The call to action, spanning the measure below both columns: the argument is
   made first, and then the price is asked for. */
.product-cta {
  width: var(--measure); max-width: 100%;
  margin-inline: auto; padding-inline: 1.2rem;
  text-align: center;
}
.product-cta .buy-bar { margin-block: 0.75rem; }

.product-was { margin: 0; font-size: 1rem; }
.product-was .price-was { color: var(--dark); opacity: 0.6; }

.product-price { font: 700 1.6rem/1 var(--sans); margin: 1rem 0; }

/* The reassurance row ------------------------------------------------------ */

.product-features {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  padding-block: 1rem 3rem;
  text-align: center;
}
.feature { display: flex; flex-direction: column; align-items: center; gap: 0.9rem; }

/* Upstream draws a Font Awesome glyph here at 5.5rem in the brand teal, and the
   name of the glyph is DATA: `salesBullets[].icon` is `faTruck`, `faThumbsUp`,
   `faGift`, `faSignature`, `faLaptopHouse`, `faMobileAlt`, `faBolt` or
   `faBookOpen` -- eight values across the whole catalogue, counted rather than
   guessed. There is no ninth to fall through to, so a value nobody drew shows as
   the plain teal disc below rather than as the wrong picture.

   Drawn here rather than fetched: a webfont is a dependency and a build step,
   and this project has neither (CLAUDE.md section 1). Each is one `--icon` mask
   over `currentColor`, the same shape the cart glyph uses, so the colour is
   inherited rather than restated.

   The `[data-icon]` selector is the whole mechanism: the template writes the
   stored name into the attribute and never chooses a class, so nothing here is
   an expression and nothing in the database is code. */
.feature-icon {
  width: 4.4rem; height: 4.4rem; flex: 0 0 auto;
  border-radius: 50%; background: var(--teal);
}

.feature-icon[data-icon="faTruck"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M1.6 5.4h10.9v9.2H1.6z"/><path d="M12.5 8.4h4.1l3.1 3.1v3.1h-7.2z"/><circle cx="6" cy="17.6" r="1.8"/><circle cx="16.4" cy="17.6" r="1.8"/></svg>'); }
.feature-icon[data-icon="faThumbsUp"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3.4 10.6h3.1v9.3H3.4z"/><path d="M6.5 11.2 10.4 3.6c1.6 0 2.5 1.1 2.2 2.5l-.5 2.9h5.2c1.3 0 2.1 1.1 1.9 2.2l-1.2 5.9c-.2 1-1.1 1.8-2.1 1.8H6.5z"/></svg>'); }
.feature-icon[data-icon="faGift"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3.2 10.8h17.6v8.9H3.2z"/><path d="M2.2 6.6h19.6v4.2H2.2z"/><path d="M12 6.6v13.1"/><path d="M12 6.6C10 6.6 7.1 6.4 7.1 4.5c0-1.3 1-1.9 2-1.9 2 0 2.9 2 2.9 4z"/><path d="M12 6.6c2 0 4.9-.2 4.9-2.1 0-1.3-1-1.9-2-1.9-2 0-2.9 2-2.9 4z"/></svg>'); }
.feature-icon[data-icon="faSignature"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M2.2 16.6c3 0 3.9-11.1 6.9-11.1 2 0 1.5 8-1 8-2 0-2-4 1-4 3.5 0 4.4 4 6.9 4 1.6 0 2.9-1 3.8-2"/><path d="M2.2 20.6h19.6"/></svg>'); }
.feature-icon[data-icon="faLaptopHouse"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M4.2 4.4h15.6v10.2H4.2z"/><path d="M2.2 19.6h19.6l-1.6-5H3.8z"/><path d="M8.6 12.2V8.4L12 5.8l3.4 2.6v3.8z"/></svg>'); }
.feature-icon[data-icon="faMobileAlt"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2.4h8c1.1 0 2 .9 2 2v15.2c0 1.1-.9 2-2 2H8c-1.1 0-2-.9-2-2V4.4c0-1.1.9-2 2-2z"/><path d="M10.2 5.4h3.6"/><circle cx="12" cy="18.3" r="1"/></svg>'); }
.feature-icon[data-icon="faBolt"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M13.6 2.4 5 13.6h5.6L9.5 21.6 19 10h-6z"/></svg>'); }
.feature-icon[data-icon="faBookOpen"] { --icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6.4C10 4.7 7.4 3.9 4 4.1v13.1c3.4-.2 6 .6 8 2.3 2-1.7 4.6-2.5 8-2.3V4.1c-3.4-.2-6 .6-8 2.3z"/><path d="M12 6.4v13.1"/></svg>'); }

/* 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. */
@supports (mask-image: none) or (-webkit-mask-image: none) {
  .feature-icon[data-icon="faTruck"],
  .feature-icon[data-icon="faThumbsUp"],
  .feature-icon[data-icon="faGift"],
  .feature-icon[data-icon="faSignature"],
  .feature-icon[data-icon="faLaptopHouse"],
  .feature-icon[data-icon="faMobileAlt"],
  .feature-icon[data-icon="faBolt"],
  .feature-icon[data-icon="faBookOpen"] {
    border-radius: 0; background: currentColor; color: var(--teal);
    -webkit-mask: var(--icon) center / contain no-repeat;
            mask: var(--icon) center / contain no-repeat;
  }
}
.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-tiers h2 {
  margin: 0 0 1.5rem; text-align: center;
  font: 400 2rem/1.2 var(--serif);
}
.tiers {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

/* Teal, like upstream's, and for a reason worth keeping: the tile is an OFFER,
   not a summary, so it has to read as one press rather than as a paragraph with
   a button under it. Everything inside is white on teal except the commit, which
   is the shop's red -- the same red as the buy bar above, so the page has one
   colour and only one that means "this is the thing that charges you". */
.tier {
  display: flex; flex-direction: column; gap: 0.45rem;
  padding: 1.5rem 1.2rem; text-align: center;
  border: 0; border-radius: var(--r);
  background: var(--teal); color: #fff;
}
.tier-qty {
  margin: 0;
  font: 700 1.6rem/1 var(--sans); text-transform: uppercase;
}

/* What it was, struck through. Upstream draws the line as a white div laid over
   the number; `text-decoration` is the same picture and survives a reflow. */
.tier-was {
  margin: 0; font: 400 1.05rem/1 var(--sans);
  text-decoration: line-through; text-decoration-color: rgb(255 255 255 / 70%);
  opacity: 0.85;
}
/* The struck price is a pair too. It is already dimmed as a whole, so the lev
   half does not dim again — two levels of 0.7 on a teal ground is a number
   nobody can read. */
.tier-was .money-alt { opacity: 1; }
/* The big number is the euro one. The small line under it used to be the euro
   amount and was called `tier-eur`; it holds levs now, so it is `tier-alt` —
   a class named after a currency it no longer carries is a comment that lies. */
.tier-total { margin: 0; font: 700 2.4rem/1 var(--sans); }
.tier-alt { margin: 0; font: 400 1rem/1 var(--sans); opacity: 0.85; }

/* What the bundle is FOR. Upstream's order: the discount, the signature, the
   drawings. The last two are only printed when the book actually carries the
   bonus -- see the template. */
.tier-perks {
  list-style: none; margin: 0.6rem 0 1rem; padding: 0;
  display: flex; flex-direction: column; gap: 0.35rem;
}
.tier-perks li {
  margin: 0; font: 700 0.95rem/1.3 var(--sans);
  text-transform: uppercase; letter-spacing: 0.02em;
}
.tier-perks li.tier-save { font-size: 1.2rem; }

.tier button {
  margin-top: auto; width: 100%;
  padding: 0.9rem 1rem; border: 0; border-radius: var(--r); cursor: pointer;
  background: var(--red); color: #fff;
  font: 700 1.15rem/1 var(--sans); text-transform: uppercase; letter-spacing: 0.03em;
}
.tier button:hover { filter: brightness(1.08); }

/* The free excerpt --------------------------------------------------------
 *
 * Upstream drops the reader itself into the middle of the product page, under
 * this heading, with a button that says «ЧЕТИ ОТКЪС». Here the press is a link
 * to /cheti-bezplatno/:key instead, because one page of a book is about 2.9 MB
 * and the product page is the page a stranger is most likely to open
 * (docs/PAPER-DIGITAL.md section 2).
 *
 * The lilac is upstream's own #e4e1f4, and it is the only band on the page that
 * is neither teal, red nor grey -- which is the point: it is the one offer that
 * costs nothing. */
.product-excerpt {
  padding-block: 2.5rem 3rem;
  text-align: center; background: #e4e1f4;
}
.product-excerpt h2 {
  margin: 0 0 1.2rem; font: 400 1.75rem/1.25 var(--serif); color: var(--dark);
}
.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. */
/* No negative margin pulling it up under the bar. The buy bar's own 2rem of
   bottom margin is the gap, and the two of them touching reads as one control
   with a tab stuck to it. */
.product-try { margin: 0 0 2rem; text-align: center; }
.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 bands ----------------------------------------------------------------
 *
 * Upstream's product page is a stack of full-bleed strips, each a different
 * ground, and the ground is what separates one argument from the next: the
 * drawings on near-black, the readers on lilac, the offer on white, the author
 * on black, the free excerpt on lilac again. Ours was one white page with
 * headings on it, which is legible and says nothing.
 *
 * The colours are upstream's own, read out of `dynamic_pages` rather than
 * picked. `rgba(0, 0, 0, .85)` over white is #262626 exactly -- the same value
 * the covers are plated with and the catalogue is painted with (D140), which is
 * not a coincidence: it is the one grey this shop has.
 *
 * `.product-band` is the measure inside a strip. The strip is the full width of
 * the window and paints; the band is the column and does not.
 */
.product-plate  { background: var(--plate); color: #fff; padding-block: 2.5rem 3rem; }
.product-voices { background: #e5e2f5; padding-block: 2.5rem 3rem; }
.product-offers { padding-block: 0.5rem 1.5rem; }
.product-about  { background: #222124; color: #fff; padding-block: 2.5rem 3rem; }

.product-plate h2, .product-about h2 { color: #fff; }

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

.product-gallery, .product-signed { padding-block: 0 2.5rem; }
.product-gallery h2, .product-signed h2, .product-testimonials h2,
.product-about h2 {
  margin: 0 0 1.2rem; text-align: center;
  font: 400 2rem/1.2 var(--serif);
}
.product-about h2 { text-align: left; margin-top: 0; }
.product-gallery .help { text-align: center; margin-bottom: 1.5rem; }

.gallery {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
}
.gallery img {
  width: 100%; height: auto; display: block;
  border-radius: var(--r); box-shadow: var(--shadow);
}

.product-author {
  display: flex; gap: 1.5rem; align-items: flex-start;
}
.product-author h2 { margin: 0 0 0.75rem; font: 400 1.7rem/1.2 var(--serif); }
.product-author p { margin: 0 0 0.75rem; font: 400 1.05rem/1.55 var(--serif); }
.author-image {
  width: 9rem; height: 9rem; border-radius: 50%; object-fit: cover;
  flex: 0 0 auto; box-shadow: var(--shadow);
}

.product-testimonials { padding-block: 0; }
.product-testimonials blockquote {
  margin: 0 0 1.5rem; padding: 1.5rem;
  border-left: 4px solid var(--teal); background: #fff;
  border-radius: 0 var(--r) var(--r) 0;
}
.product-testimonials blockquote:last-child { margin-bottom: 0; }
.product-testimonials blockquote p { margin: 0 0 0.75rem; font: 400 1.05rem/1.55 var(--serif); }
.product-testimonials blockquote img {
  width: 3rem; height: 3rem; border-radius: 50%; object-fit: cover; float: left;
  margin: 0 1rem 0.5rem 0;
}

/* The cart panel -----------------------------------------------------------
 *
 * Drawn by app/functions/paper-shop-shell.js; the frame is in the component.
 */
.cart-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 30;
  width: min(24rem, 100%);
  display: flex; flex-direction: column;
  background: var(--paper); color: var(--dark);
  box-shadow: var(--shadow-lg);
  font-family: var(--sans);
}

.cart-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 1.2rem; background: var(--teal); color: #fff;
}
.cart-panel-head h2 { margin: 0; font: 700 1.15rem/1 var(--sans); text-transform: uppercase; }
.cart-close {
  background: none; border: 0; color: #fff; cursor: pointer;
  font-size: 1.6rem; line-height: 1; padding: 0 0.25rem;
}

.cart-empty { padding: 2rem 1.2rem; text-align: center; }
.cart-lines { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1 1 auto; }

.cart-line {
  display: flex; gap: 0.75rem; align-items: center;
  padding: 0.75rem 1.2rem; border-bottom: 1px solid var(--light);
}
.cart-line-cover { width: 3rem; height: auto; flex: 0 0 auto; }
.cart-line-body { flex: 1 1 auto; min-width: 0; }
.cart-line-title { margin: 0 0 0.15rem; font-size: 0.9rem; font-weight: 600; }
.cart-line-price { margin: 0; font-size: 0.85rem; color: var(--teal); font-weight: 700; }
.cart-line-controls { display: flex; align-items: center; gap: 0.3rem; flex: 0 0 auto; }
.cart-line-qty { min-width: 1.5rem; text-align: center; font-weight: 700; }

.cart-step {
  width: 1.7rem; height: 1.7rem; padding: 0;
  border: 1px solid var(--light); border-radius: var(--r);
  background: var(--paper); color: var(--dark); cursor: pointer;
  font-size: 0.95rem; line-height: 1;
}
.cart-step:hover { border-color: var(--teal); color: var(--teal); }
.cart-step-remove:hover { border-color: var(--red); color: var(--red); }

.cart-panel-foot { padding: 1.2rem; border-top: 1px solid var(--light); }
.cart-total {
  display: flex; justify-content: space-between; align-items: baseline;
  margin: 0 0 0.75rem; font-size: 0.95rem;
}
.cart-total strong { font: 700 1.4rem/1 var(--sans); }
/* 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;
}
.cart-panel-foot .help { margin: 0.6rem 0 0; font-size: 0.8rem; text-align: center; }


/* 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);
}

.checkout-head, .receipt-head { text-align: center; margin-bottom: 2rem; }
.checkout-head h1, .receipt-head h1 {
  margin: 0 0 0.5rem;
  font: 400 2.4rem/1.15 var(--serif);
  color: var(--dark);
}
.receipt-eyebrow {
  margin: 0 0 0.5rem;
  font: 700 1.1rem/1 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--teal);
}

.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-empty { text-align: center; padding: 3rem 0; }

/* The basket on top and the form under it, which is upstream's order and not a
   preference: `checkout.component.html` puts an accordion titled «Продукти»
   above the step, closed. It was a column beside the form here, which reads
   fine on a desk and puts the thing somebody has just come from below the fold
   on a phone.

   Closed, but never silent about the money: the total is in the `<summary>`, so
   collapsing hides the LIST and not the number. A checkout that folds away what
   it is about to charge is not compact, it is evasive. */
.checkout-body { display: block; }

.checkout-summary { padding: 0; }
.checkout-summary summary {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; padding: 1rem 1.5rem; cursor: pointer;
  list-style: none;
}
.checkout-summary summary::-webkit-details-marker { display: none; }

/* The disclosure triangle, drawn rather than inherited, so it turns with the
   panel and sits where the eye is already looking. */
.checkout-summary summary::after {
  content: '';
  flex: 0 0 auto; margin-left: -0.4rem; order: 1;
  width: 0.5rem; height: 0.5rem;
  border-right: 2px solid var(--dark); border-bottom: 2px solid var(--dark);
  transform: rotate(45deg) translate(-0.15rem, -0.15rem);
  transition: transform 0.15s ease;
}
.checkout-summary[open] summary::after {
  transform: rotate(-135deg) translate(-0.1rem, -0.1rem);
}

.checkout-summary-title {
  flex: 1 1 auto;
  font: 700 1rem/1 var(--sans); text-transform: uppercase; letter-spacing: 0.03em;
}
.checkout-summary-total { font: 700 1.25rem/1 var(--sans); color: var(--teal); }
.checkout-summary .checkout-lines,
.checkout-summary .help { padding-inline: 1.5rem; }
.checkout-summary .help { padding-bottom: 1.2rem; }

/* Two columns: who you are, and where it goes. Upstream's
   `order-form.component.html` — `user-data-table` beside `econt-data-table`. */
.checkout-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0 1.5rem;
}
.checkout-column { min-width: 0; }
.checkout-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 0.75rem; }

.checkout-form label {
  display: block;
  margin-bottom: 1rem;
  font: 600 0.85rem/1.4 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--dark);
}
.checkout-form input,
.checkout-form select,
.checkout-form textarea {
  display: block;
  width: 100%;
  margin-top: 0.35rem;
  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);
  text-transform: none;
  letter-spacing: normal;
}
.checkout-form input:focus-visible,
.checkout-form select:focus-visible,
.checkout-form textarea:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 1px;
}
.checkout-form textarea { resize: vertical; }

/* 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. */
/* The question on the left, its three answers pushed to the right, one line.
 *
 * It was two lines — the label above, the radios under it — which made the
 * delivery question twice the height of the city field beside it in the other
 * column, and the two columns of the form stopped lining up halfway down.
 *
 * **There is no `<legend>` here any more, and that is the layout constraint
 * rather than a preference.** A `<legend>` is laid out by the browser as part
 * of the fieldset's border: it is not a flex item, it cannot be pushed to one
 * side, and `float: left; width: 100%` — which is what it used to say — is the
 * workaround that forces it onto a line of its own. The fieldset carries the
 * question as `aria-label` instead, so a screen reader still announces the
 * group by name, and the visible copy is a `<span>` marked `aria-hidden` so it
 * is not announced twice.
 *
 * Upstream does the same thing: three radios in one `<div class="flex">`, with
 * `[position]="directionEnum.HORIZONTAL"` on the first.
 *
 * Below 40rem the label goes back above the answers — a question and three
 * radios do not fit across a phone, and the alternative to wrapping is
 * truncating one of the couriers. */
.ship-choice {
  margin: 0 0 1rem; padding: 0; border: 0;
  display: block; min-width: 0;
}
.ship-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem;
}
.ship-legend {
  font: 600 0.85rem/1.4 var(--sans);
  text-transform: uppercase; letter-spacing: 0.03em; color: var(--dark);
  flex: 0 0 auto;
}
/* The answers, as a block, against the right edge. On the first option rather
   than on the legend so that a wrap puts all three under the question together
   instead of stranding one of them. */
.ship-row .ship-option:first-of-type { margin-left: auto; }
.checkout-form .ship-option {
  display: inline-flex; align-items: center; gap: 0.45rem;
  margin-bottom: 0;
  padding: 0.55rem 0.9rem;
  border: 1px solid rgb(0 0 0 / 12%); border-radius: var(--r);
  cursor: pointer;
  /* `.checkout-form label` uppercases every label in this form, and it is one
     specificity point above a bare class — so the courier's names and the two
     consents have to be reached through the form to get their own case back.
     Upstream writes them «Адрес», «Econt», «Speedy» and in a sentence. */
  font: 400 0.95rem/1 var(--sans); text-transform: none; letter-spacing: normal;
  color: var(--dark);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.ship-option:hover { border-color: rgb(0 0 0 / 28%); }
.ship-option input {
  display: inline; width: auto; margin: 0; padding: 0;
  accent-color: var(--teal);
}
/* `:has` rather than a class toggled by script: the state IS the radio, and a
   second copy of it in a class attribute is a second thing that can be wrong.
   The same reasoning as `.pay-option`, which this is the sibling of. */
.ship-option:has(input:checked) {
  border-color: var(--teal); background: rgb(0 198 207 / 8%);
}

/* Both are required, and both were missing. Upstream's `app-user-rights`
   refuses the form without them; here `required` refuses it in the browser and
   `app/handlers/order-place.js` refuses it again on the server, because a
   `boolean` field cannot be required by the validator (an unchecked box sends
   nothing, so absence has to mean false). */
.consent-lines { margin: 1.25rem 0 0; display: grid; gap: 0.5rem; }
.checkout-form .consent-line {
  display: flex; align-items: flex-start; gap: 0.6rem;
  margin: 0; cursor: pointer;
  font: 400 0.9rem/1.45 var(--sans); text-transform: none; letter-spacing: normal;
  color: var(--dark);
}
.consent-line input {
  display: inline; width: auto; flex: 0 0 auto;
  margin: 0.15rem 0 0; padding: 0;
  accent-color: var(--teal);
}
.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-summary h2 {
  margin: 0 0 1rem;
  font: 700 1rem/1 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.checkout-lines { list-style: none; margin: 0 0 1rem; padding: 0; }
.checkout-lines li {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--light);
  font-size: 0.95rem;
}
.checkout-line-title { flex: 1 1 auto; min-width: 0; }
.checkout-line-count { flex: 0 0 auto; color: var(--dark); opacity: 0.6; }
.checkout-line-price { flex: 0 0 auto; font-weight: 700; color: var(--teal); }
.checkout-line-price .money-alt, .checkout-summary-total .money-alt { font-weight: 400; }
.checkout-lines .checkout-line-total {
  border-bottom: 0;
  justify-content: space-between;
  padding-top: 0.9rem;
  font: 700 1.1rem/1 var(--sans);
  text-transform: uppercase;
}

/* 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-where h2 {
  margin: 0 0 0.75rem;
  font: 700 1rem/1 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.receipt-where p { margin: 0 0 0.4rem; }
.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); }

/* Paying --------------------------------------------------------------------
 *
 * Three ways to pay, and the shop does not ask. Upstream's
 * `payment.component.html` hides the chooser for a physical order
 * (`[hidden]="orderType === orderTypeEnum?.PHYSICAL"`) and auto-clicks
 * cash-on-delivery, so a buyer of a paper book was never asked either.
 *
 * The fieldset is `hidden` and its radios are NOT disabled, because a hidden
 * input still submits: `payment_method` still arrives as `cash`, and the
 * handler's three branches, the Stripe session and the epay packet are all
 * exactly where they were. Removing the markup would have been removing the
 * card and epay journeys, which is a different change from removing a control.
 *
 * These rules stay, and they are not dead: unhiding the fieldset is the whole
 * of turning the choice back on.
 */

.pay-choice {
  margin: 1.25rem 0; padding: 0; border: 0;
  display: grid; gap: 0.6rem;
}
.pay-choice legend {
  padding: 0; margin-bottom: 0.5rem;
  font: 600 0.85rem/1 var(--sans);
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--light);
}

.pay-option {
  display: flex; align-items: flex-start; gap: 0.7rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid rgb(0 0 0 / 12%); border-radius: var(--r);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.pay-option:hover { border-color: rgb(0 0 0 / 28%); }
.pay-option input { margin-top: 0.25rem; accent-color: var(--red); }
.pay-option span { display: flex; flex-direction: column; gap: 0.15rem; }
.pay-option strong { font: 600 1rem/1.2 var(--sans); }
.pay-option small { font-size: 0.85rem; color: var(--light); line-height: 1.4; }

/* `:has` rather than a class toggled by script: the state IS the radio, and a
   second copy of it in a class attribute is a second thing that can be wrong. */
.pay-option:has(input:checked) {
  border-color: var(--red); background: rgb(214 40 40 / 5%);
}

/* The receipt's payment banner. Three states, and the colour is the fastest
   part of it to read — somebody who has just typed a card number is looking for
   green before they are reading Bulgarian. */
.pay-state {
  margin: 0 0 1.25rem; padding: 0.8rem 1rem;
  border-radius: var(--r); border-left: 4px solid var(--light);
  background: rgb(0 0 0 / 4%); font-size: 0.95rem; line-height: 1.5;
}
.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: 60rem) {
  /* The order form is two columns on a desk and one on a phone. The basket is
     above the form at every width now, because it is above the step upstream
     and because it is collapsed -- a closed row costs nothing to scroll past,
     which is what the ordering rules here used to be buying. */
  .checkout-columns, .checkout-pair { grid-template-columns: 1fr; }
  /* The question goes back above its answers. Three couriers and a label do not
     fit across a phone, and the alternative to wrapping is truncating one of
     them — `margin-left: auto` comes off so the three sit together on the left
     rather than being pushed against the right edge under nothing. */
  .ship-row .ship-option:first-of-type { margin-left: 0; }
  .ship-legend { flex: 1 0 100%; }

  .product-main { grid-template-columns: 1fr; }
  /* Source order again: on one column the cover comes first, because a book is
     recognised before it is read about. */
  .product-cover { order: 1; max-width: 24rem; margin-inline: auto; }
  .product-buy { order: 2; }
  .product-head h1 { font-size: 2rem; }
  .product-eyebrow { font-size: 1.1rem; }
}

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

  /* The bar is not shrunk on a phone, and upstream does not shrink it either:
     a 3rem tile, 0.5rem, a 2rem wordmark and a 3rem tile is about 270px, which
     fits the narrowest phone in use with room to spare. Scaling it down would
     cost legibility to buy space nothing needs. */

  .book-list-head h1 { font-size: 2rem; }

  /* The bar stacks rather than shrinking: a call to action and a price squeezed
     onto one line on a phone are two things nobody can read. */
  .buy-bar { flex-direction: column; }
  .buy-bar .price-tag {
    border-left: 0; border-top: 2px solid rgb(255 255 255 / 45%);
    justify-content: center;
  }
  .book-card .buy-bar .buy { font-size: 0.8rem; padding: 0.55rem 0.4rem; }
  .book-card .price-tag .amount { font-size: 1.05rem; }
  .book-card .price-tag .amount.second { font-size: 0.85rem; }

  .product-head h1 { font-size: 1.6rem; padding: 1rem 0.8rem; }
  .product-author { flex-direction: column; align-items: center; text-align: center; }
}

/* ---------------------------------------------------------------------------
   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-head h1 { margin: 0 0 0.25rem; font: 400 1.8rem/1.2 var(--serif); }

.ebook-bar {
  display: flex; gap: 1rem; align-items: center; justify-content: center;
  padding: 0.75rem 0;
}
/* `color` is not optional here, and the reason is worth writing down because it
   has now cost two controls. The framework's own `button` rule near the top of
   this file paints every button `background: var(--accent); color: #fff`, for
   the admin. A shop control that repaints the background to white and says
   nothing about the colour is white text on white — invisible while it works,
   and LEGIBLE the moment it is disabled, because `button:disabled` sets a grey.
   So the state that reads as broken is the only state you can read. */
.ebook-turn {
  padding: 0.5rem 1.1rem; border: 1px solid rgb(0 0 0 / 18%);
  border-radius: 0.5rem; background: #fff; color: var(--dark);
  cursor: pointer; font: inherit;
}
.ebook-turn[disabled] { opacity: 0.45; cursor: default; }
.ebook-count { font-variant-numeric: tabular-nums; min-width: 5rem; text-align: center; }

/* Reserves its line whether or not it says anything, so the page does not jump
   when a load starts. */
.ebook-status { min-height: 1.5rem; margin: 0; text-align: center; color: #666; }

/* Only /cheti-bezplatno/:key renders this: the paid reader has nothing to sell
   the person already holding the book. `{{#if has_buy}}` in the template. */
.ebook-after { margin: 2rem 0 0; text-align: center; }
.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-reveal { display: flex; justify-content: center; padding: 1rem 0; }
.ebook-open {
  padding: 1rem 2rem; border: 0; border-radius: 1rem; cursor: pointer;
  font: 700 1.25rem/1 inherit; color: #fff; background: #b3121b;
}

.ebook-stage { position: relative; display: flex; overflow: hidden; }
.ebook-image {
  position: absolute; inset: 0;
  background-size: contain; background-repeat: no-repeat; background-position: center;
}
.ebook-tint { position: absolute; inset: 0; }
.ebook-grid { position: relative; z-index: 2; display: flex; width: 100%; }
.ebook-column { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }

/* Load-bearing rather than cosmetic. An inline SVG is `display: inline`, which
   gives every tile a text baseline and a few pixels of descender gap; eighty-four
   of those down a column and the page is visibly striped. */
.ebook-grid svg { display: block; width: 100%; height: auto; }

@media (max-width: 40rem) {
  .ebook { padding: 0.5rem; }
  .ebook-head h1 { font-size: 1.4rem; }
  .ebook-open { padding: 0.85rem 1.4rem; font-size: 1.05rem; }
}


/* ---------------------------------------------------------------------------
   Sign in, register, profile.

   The same narrow column as the checkout, because they are the same kind of
   page: one form, nothing to browse, nothing to compare. The inputs borrow the
   checkout's shape rather than restating it, so a change to one field style
   reaches every form the shop has.
   --------------------------------------------------------------------------- */

.auth-page, .profile-page {
  max-width: 26rem;
  margin: 0 auto;
  padding: 2.5rem 1rem 4rem;
}

.auth-head { text-align: center; margin-bottom: 1.75rem; }
.auth-head h1 {
  font: 400 1.6rem/1.2 var(--serif, var(--sans));
  margin: 0 0 0.4rem;
}

.auth-form label {
  display: block;
  margin-bottom: 0.3rem;
  font: 600 0.8rem/1.4 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--dark);
}
.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; }
.aphorisms-head { text-align: center; margin-bottom: 1.5rem; }
.aphorisms-head h1 { font: 400 1.6rem/1.2 var(--serif, var(--sans)); margin: 0 0 0.3rem; }

.aphorism-list { list-style: none; margin: 0; padding: 0; }
.aphorism { margin: 0 0 1.5rem; }
.aphorism img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r);
  background: var(--light);
}

/* The contact form is the only one in this family with a paragraph in it, and
   26rem is a column for a password, not for a question. */
.auth-page.contact-page { max-width: 34rem; }

.auth-form textarea {
  display: block;
  width: 100%;
  margin-bottom: 1rem;
  padding: 0.6rem 0.7rem;
  font: 400 1rem/1.45 var(--sans);
  color: var(--dark);
  background: var(--paper);
  border: 1px solid var(--light);
  border-radius: var(--r);
  box-sizing: border-box;
  resize: vertical;
}

.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; }
.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; }
.owned-badge {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem;
  text-align: center;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 0.95rem;
  letter-spacing: 0.04em;
}
/* 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-head h1 { margin: 0 0 0.25rem; font: 400 1.8rem/1.2 var(--serif); }

.audio-bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding: 0.75rem 0;
}
.audio-play {
  flex: 0 0 auto;
  padding: 0.5rem 1.25rem;
  border: 1px solid var(--line);
  background: #fff;
  /* See `.ebook-turn` — the same white-on-white, on the play button. */
  color: var(--dark);
  cursor: pointer;
  letter-spacing: 0.04em;
}
.audio-seek { flex: 1 1 12rem; min-width: 8rem; }
.audio-seek[disabled] { opacity: 0.45; cursor: default; }
.audio-clock {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  color: #666;
}
.audio-sep { margin: 0 0.35rem; }
.audio-status { min-height: 1.5rem; margin: 0; color: #666; }

/* --- 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; }
.bonuses-head h1 { margin: 0 0 .25rem; }
.bonus-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); margin: 1rem 0; }
.bonus-card { background: #fff; border: 2px solid #e4e4e4; border-radius: .5rem; padding: .5rem; display: flex; flex-direction: column; gap: .5rem; }
.bonus-card.is-chosen { border-color: #c0392b; }
.bonus-card.is-full { opacity: .45; }
.bonus-image { width: 100%; height: auto; display: block; cursor: pointer; border-radius: .25rem; }
.bonus-pick { display: flex; align-items: center; gap: .5rem; font-weight: 600; }
.bonus-name { display: flex; flex-direction: column; gap: .25rem; font-size: .9rem; }
.bonus-name input { font-size: 1rem; padding: .4rem; width: 100%; box-sizing: border-box; }
.bonus-note { min-height: 1.4em; color: #c0392b; margin: .5rem 0; }
.bonuses-form button[type="submit"][disabled] { opacity: .5; cursor: not-allowed; }

/* --- the signed illustration (/get-bonuses) ------------------------------ */
.signed { max-width: 1100px; margin: 0 auto; padding: 1rem 1rem 3rem; }
.signed-head h1 { margin: 0 0 .25rem; }
.signed-list { display: flex; flex-direction: column; gap: 2rem; margin-top: 1.5rem; }
.signed-item { margin: 0; background: #fff; border: 2px solid #e4e4e4; border-radius: .5rem; padding: .75rem; }
.signed-canvas, .signed-fallback { display: block; width: 100%; height: auto; max-width: 100%; }
.signed-item figcaption { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-top: .75rem; }
.signed-wish { margin: 0; flex: 1 1 12rem; font-size: 1.1rem; }
.signed-plain { font-size: .9rem; }
.signed-note { min-height: 1.4em; color: #c0392b; margin: 1rem 0; }
.signed-item button[disabled] { opacity: .5; cursor: not-allowed; }

/* --- 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; }
.legal-body h2 { font-size: 1.15rem; margin: 2.5rem 0 .75rem; }
.legal-body p { margin: 0 0 .9rem; }
.legal-body [id] { scroll-margin-top: 1.5rem; }

/* The footer's legal links: one row on a wide screen, a stack on a phone,
   and never the run-together look of anchors separated by a space alone. */
.shop-legal {
  display: flex; flex-wrap: wrap; gap: .4rem 1.25rem;
  justify-content: center; margin-top: .8rem; font-size: .9rem;
}

/* The funnel ---------------------------------------------------------------
 *
 * Four pages that sell one book to somebody who arrived from an advert:
 * /:key, /:key/sales, /cheti-bezplatno/:key and /gift-book/:key. See
 * docs/PAPER-FUNNEL.md.
 *
 * They borrow the shop's type and colour and change the LAYOUT, because a
 * landing page is one column that keeps moving and a product page is a
 * reference. The pieces that are genuinely the same thing — .gallery, .tag,
 * .buy, blockquote — are reused rather than restyled, so a change to the
 * shop's buy button changes the funnel's too.
 *
 * The Angular pages hid the site navigation on all four (DEFAULT_MAIN_PAGE_
 * OPTIONS_HIDE), and so did this stylesheet, with a rule right here. BP-82
 * removed the header menu from every page, so the rule had nothing left to
 * hide and is gone. The reasoning it carried outlived it and is worth keeping:
 * somebody who arrived from an advert has one thing to do, and a nav bar is
 * four ways to leave. The brand and the footer stay, because a page selling
 * something has to say who is selling it.
 */

.funnel-head {
  padding-block: 2.5rem 1.5rem;
  text-align: center;
}
.funnel-eyebrow {
  margin: 0 0 0.6rem; color: var(--teal);
  font: 700 0.9rem/1.3 var(--sans);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.funnel-head h1 { margin: 0; font: 400 2.6rem/1.1 var(--serif); }
.funnel-sub { margin: 0.5rem 0 0; font: 400 1.2rem/1.4 var(--serif); opacity: 0.75; }

.funnel-hero {
  display: grid; gap: 2.5rem;
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  align-items: start;
  padding-block: 1.5rem 3rem;
}
@media (max-width: 46rem) {
  .funnel-hero { grid-template-columns: minmax(0, 1fr); }
  .funnel-cover { max-width: 16rem; margin-inline: auto; }
}
.funnel-cover { margin: 0; }
.funnel-cover img { width: 100%; height: auto; border-radius: var(--r); }

.funnel-shout {
  margin: 0 0 1.2rem;
  font: 700 1.8rem/1.15 var(--serif);
  color: var(--red);
}

.funnel-quote {
  margin: 0 0 1.5rem; padding: 0 0 0 1.2rem;
  border-left: 3px solid var(--red);
  font: italic 400 1.05rem/1.6 var(--serif);
  /* The blurb is one long paragraph from upstream with hard newlines in it.
   * `pre-line` keeps them without a template having to split anything (D2). */
  white-space: pre-line;
}

.funnel-points { margin: 0 0 1.5rem; padding: 0; list-style: none; }
.funnel-points li {
  position: relative; padding-left: 1.6rem; margin-bottom: 0.6rem;
  font: 400 1rem/1.5 var(--sans);
}
.funnel-points li::before {
  content: ""; position: absolute; left: 0; top: 0.45em;
  width: 0.55rem; height: 0.55rem; border-radius: 50%;
  background: var(--red);
}

/* The lead form ----------------------------------------------------------- */

.funnel-form { display: grid; gap: 0.9rem; margin-top: 1.5rem; }
.funnel-form label { display: grid; gap: 0.35rem; font: 700 0.9rem/1.3 var(--sans); }
.funnel-form input[type="text"],
.funnel-form input[type="email"] {
  padding: 0.7rem 0.8rem; border: 1px solid var(--light); border-radius: var(--r);
  font: 400 1rem/1.3 var(--sans); background: #fff;
}
.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; }
.funnel-form button[type="submit"] { padding: 0.9rem 1rem; font-size: 1.05rem; }

/* The sales letter's offer ------------------------------------------------- */

.funnel-cta { padding: 1rem 2rem; font-size: 1.15rem; }

.funnel-packs { padding-block: 2.5rem; }
.funnel-packs h2 { margin: 0 0 1.5rem; text-align: center; font: 400 2rem/1.2 var(--serif); }
.packs {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  margin-bottom: 2rem;
}
.pack {
  border: 2px solid var(--light); border-radius: var(--r);
  padding: 1.5rem 1rem; text-align: center; background: var(--paper);
}
.pack-qty {
  margin: 0 0 0.5rem; color: var(--teal);
  font: 700 1.1rem/1 var(--sans); text-transform: uppercase;
}
.pack-total { margin: 0 0 0.35rem; font: 700 1.7rem/1 var(--sans); }
.pack-total .price-eur { font-size: 1rem; font-weight: 400; opacity: 0.7; }
.pack .muted, .pack .help { font-size: 0.9rem; }
.pack button {
  margin-top: 1rem; width: 100%;
  padding: 0.75rem 1rem; border: 0; border-radius: var(--r); cursor: pointer;
  background: var(--red); color: #fff;
  font: 700 0.95rem/1.2 var(--sans); text-transform: uppercase;
}
.pack button:hover { filter: brightness(1.08); }

/* 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;
}

/* The gift ----------------------------------------------------------------- */

.gift-price {
  margin: 0 0 1rem;
  font: 700 2.2rem/1 var(--sans); color: var(--red);
}
.gift-price s { font-size: 1.1rem; font-weight: 400; opacity: 0.6; margin-left: 0.6rem; }
.gift-note { margin: 0 0 1.2rem; font: 400 1.05rem/1.5 var(--sans); }

/* The shared blocks, at funnel spacing ------------------------------------- */

.funnel-features { padding-block: 2.5rem; display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.funnel-gallery, .funnel-signed, .funnel-author, .funnel-testimonials {
  padding-block: 2.5rem;
}
.funnel-gallery h2, .funnel-signed h2, .funnel-author h2, .funnel-testimonials h2 {
  margin: 0 0 0.5rem; text-align: center; font: 400 2rem/1.2 var(--serif);
}
.funnel-gallery .help { text-align: center; margin-bottom: 1.5rem; }
.funnel-author { display: grid; gap: 1.5rem; grid-template-columns: minmax(0, 12rem) minmax(0, 1fr); align-items: start; }
.funnel-author h2 { grid-column: 1 / -1; }
@media (max-width: 46rem) { .funnel-author { grid-template-columns: minmax(0, 1fr); } }
.funnel-author .author-image { width: 100%; height: auto; border-radius: var(--r); }
