/* ===========================================================================
 * BlurPaper's own stylesheet (BP-707): the shop, /author, the /paper office,
 * /connect and marketing. Linked after /styles.css only on sites where the
 * `paper` application is installed (app.json "styles", BP-640, docs/APPS.md
 * «An application's own stylesheet»). docs/PAPER-APP.md «The stylesheet».
 *
 * These rules came out of public/styles.css unchanged and in their original
 * order. What stayed there, and why, is in PAPER-APP.md. In short: a rule that
 * another site uses, or one whose winner would change by being moved after
 * everything that stays.
 * ======================================================================== */

.paper-brand { font-weight: 650; text-decoration: none; color: var(--ink); }

.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; display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }

.paper-who a { color: var(--muted); }

/* BP-565: the office had no way to sign out of /paper. */
.paper-signout { margin: 0; }

.paper-signout button {
  padding: 0.3rem 0.8rem; border: 1px solid var(--line); border-radius: 6px;
  background: transparent; color: var(--ink); font: inherit; font-size: 0.85rem; cursor: pointer; text-decoration: none;
}

.paper-signout button:hover { border-color: var(--accent); color: var(--accent); }

.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; }

/* BP-318: proposals waiting on the office — a count in the bar, a line under it. */
.paper-count {
  display: inline-block; min-width: 1.3rem; padding: 0 0.35rem; margin-left: 0.15rem;
  border-radius: 999px; background: var(--accent); color: var(--bg);
  font-size: 0.75rem; font-weight: 650; line-height: 1.3rem; text-align: center;
}

.paper-notice {
  margin: 0; padding: 0.55rem 1.2rem;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg));
}

.paper-notice a { color: var(--ink); font-weight: 600; text-decoration: none; }

.paper-notice a:hover { text-decoration: underline; }

.filter-row label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.8rem; color: var(--muted); }

.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-products { list-style: none; margin: 0; padding: 0.5rem 1rem; }

.order-product { display: flex; gap: 0.75rem; align-items: baseline; padding: 0.15rem 0; }

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

.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-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; }

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

.shipment-form { padding: 0.8rem 1rem 1rem; }

.shipment-grid {
  display: grid; gap: 0.7rem 1rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

.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); }

@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 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;
  /* It may shrink (BP-458). The bar holds the brand and three 3rem tiles, which
     on a phone under ~370px is more than the screen; without this the tiles
     were pushed off the right edge and the search field, placed from that
     edge, stopped short of its button. */
  min-width: 0;
  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; }

/* When the brand shrinks, the wordmark is what gives way: the picture scales
   down inside its box, from the left, rather than being squashed or cut
   mid-letter (BP-458). */
.shop-wordmark {
  height: 2rem; width: auto; display: block;
  min-width: 0; flex: 0 1 auto;
  object-fit: contain; object-position: left center;
}

/* 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 search in the header (BP-457, app/functions/_paper-shop-search.js).
 *
 * The magnifier is the cart's twin tile, and the field opens LEFTWARDS from it:
 * absolutely placed against the bar's right end, beside the three tiles (9rem),
 * its width animated from nothing. On a phone the open field covers the
 * wordmark — there is no room for both, and the one being typed into wins.
 * Closed, it is `visibility: hidden` as well as zero wide, so it takes no tab
 * stop and no screen reader finds an invisible field. Not `[hidden]`: that is
 * `display: none` (D145), and nothing animates out of `display: none`. */
.shop-search-open {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3rem; height: 3rem; flex: 0 0 auto;
  padding: 0; border: 0; cursor: pointer;
  background: var(--teal); color: #fff;
  border-radius: 0;
}

.shop-search-open[aria-expanded="true"] { background: var(--dark); }

.shop-search-input {
  box-sizing: border-box; width: 100%; height: 100%; margin: 0;
  padding: 0 1rem;
  border: 2px solid var(--dark); border-radius: 0;
  background: var(--paper); color: var(--dark);
  font: 400 1.05rem/1 var(--sans);
}

/* A magnifier: a ring and a handle, the same mask technique and one-line rule
 * as `.cart-glyph`, with a drawn ring for the browsers without masks. */
.search-glyph {
  width: 1.5rem; height: 1.5rem; position: relative; display: inline-block;
}

.search-glyph::before {
  content: ''; position: absolute; top: 0.1rem; left: 0.1rem;
  width: 0.85rem; height: 0.85rem;
  border: 0.18rem solid currentColor; border-radius: 50%;
}

.search-glyph {
  --search-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.9" stroke-linecap="round"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M15.4 15.4l5.1 5.1"/></svg>');
}

@supports (mask-image: var(--search-svg)) or (-webkit-mask-image: var(--search-svg)) {
  .search-glyph::before { content: none; }

  .search-glyph {
    background: currentColor;
    -webkit-mask: var(--search-svg) center / contain no-repeat;
            mask: var(--search-svg) center / contain no-repeat;
  }
}

/* The genre menu (BP-537, app/functions/_paper-shop-genres.js).
 *
 * A fourth tile, right of the magnifier, the same teal square; the menu hangs
 * under the bar's right end, closed until the tile is pressed — the owner asked
 * for it «not quite visible», so nothing about the catalogue changes until
 * somebody opens it. With the tile there the search field opens one tile
 * further left (`:has`), so it still starts at the magnifier. The badge is the
 * cart's, counting the genres picked: a closed menu still filtering the
 * catalogue says so on the tile. */
.shop-genre-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;
  border-radius: 0;
}

.shop-genre-open[aria-expanded="true"] { background: var(--dark); }

.shop-bar:has(.shop-genre-open) .shop-search { right: 12rem; }

/* The author tile (BP-546) is the genre tile's twin and carries its class, so
   one tile of either kind is the rule above; both is one tile more. */
.shop-bar:has(.shop-genre-open + .shop-genre-open) .shop-search { right: 15rem; }

.genre-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);
}

.genre-badge[hidden] { display: none; }

/* Three books on a shelf, the last one leaning: the mask technique and the
 * one-line rule of `.cart-glyph`, with two bars for the browsers without masks. */
.genre-glyph {
  width: 1.5rem; height: 1.5rem; position: relative; display: inline-block;
}

.genre-glyph::before {
  content: ''; position: absolute; top: 0.2rem; bottom: 0.2rem; left: 0.25rem;
  width: 0.3rem; background: currentColor;
  box-shadow: 0.45rem 0.15rem 0 currentColor;
}

.genre-glyph {
  --genre-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"><rect x="3" y="4" width="4.2" height="16" rx="0.6"/><rect x="9.2" y="7" width="4.2" height="13" rx="0.6"/><path d="M15.4 7.9l4-1.1 3.2 12.4-4 1.1z"/></svg>');
}

@supports (mask-image: var(--genre-svg)) or (-webkit-mask-image: var(--genre-svg)) {
  .genre-glyph::before { content: none; }

  .genre-glyph {
    background: currentColor;
    -webkit-mask: var(--genre-svg) center / contain no-repeat;
            mask: var(--genre-svg) center / contain no-repeat;
  }
}

.shop-genres {
  position: absolute; top: 100%; right: 0; z-index: 21;
  box-sizing: border-box; width: min(26rem, 100%);
  max-height: min(70vh, 34rem); overflow-y: auto;
  padding: 1rem 1rem 1.1rem;
  background: var(--paper); color: var(--dark);
  box-shadow: var(--shadow-lg);
}

.shop-genres[hidden] { display: none; }

.shop-genres-head {
  margin: 0 0 0.7rem;
  font: 700 0.78rem/1.2 var(--sans); letter-spacing: 0.06em; text-transform: uppercase;
}

.shop-genres-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.4rem;
}

.shop-genre-pick {
  padding: 0.35rem 0.75rem;
  border: 1px solid #c9c9c9; border-radius: 999px;
  background: var(--paper); color: var(--dark);
  font: 400 0.95rem/1.25 var(--sans); cursor: pointer;
}

.shop-genre-pick:hover { border-color: var(--teal); }

.shop-genre-pick[aria-pressed="true"] { background: var(--teal); border-color: var(--teal); color: #fff; }

/* A pen nib, for the author tile (BP-546): the same technique as the shelf. */
.author-glyph {
  width: 1.5rem; height: 1.5rem; position: relative; display: inline-block;
}

.author-glyph::before {
  content: ''; position: absolute; top: 0.2rem; left: 0.55rem;
  width: 0.4rem; height: 1.1rem; background: currentColor;
}

.author-glyph {
  --author-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="M12 21.5L6 11.5 12 2.5l6 9z"/><path d="M12 21.5v-8.2"/><circle cx="12" cy="11.6" r="1.6"/></svg>');
}

@supports (mask-image: var(--author-svg)) or (-webkit-mask-image: var(--author-svg)) {
  .author-glyph::before { content: none; }

  .author-glyph {
    background: currentColor;
    -webkit-mask: var(--author-svg) center / contain no-repeat;
            mask: var(--author-svg) center / contain no-repeat;
  }
}

/* «Изчисти филтъра» (BP-546) — a button, where it was a quiet «Покажи всички»
   link the owner did not read as one. The same pill as a chip, with a cross;
   `.on-plate` is the catalogue's, on the dark plate. */
.shop-filter-clear {
  display: inline-flex; align-items: center; gap: 0.4rem;
  margin-top: 0.9rem; padding: 0.35rem 0.85rem;
  border: 1px solid var(--dark); border-radius: 999px;
  background: var(--paper); color: var(--dark);
  font: 600 0.95rem/1.25 var(--sans); cursor: pointer;
}

.shop-filter-clear::before { content: '\2715'; font-size: 0.8em; }

.shop-filter-clear:hover { background: var(--dark); color: #fff; }

.shop-filter-clear[hidden] { display: none; }

.shop-filter-clear.on-plate { margin-top: 0.7rem; border-color: var(--light); background: transparent; color: #fff; }

.shop-filter-clear.on-plate:hover { background: #fff; color: var(--dark); }

/* The book flying into the cart (BP-450, app/functions/_paper-fly-to-cart.js).
   A copy of the cover over everything, never in the way of a press; the
   script moves it, this only places it. The cart button bounces when it lands. */
.cart-fly {
  position: fixed; z-index: 1000; pointer-events: none;
  object-fit: contain; transform-origin: center;
  will-change: transform, opacity;
}

.shop-cart-open.is-bumped { animation: cart-bump 0.45s ease; }

@media (prefers-reduced-motion: reduce) {
  .shop-cart-open.is-bumped { animation: none; }
}

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

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

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

.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 signed-out «Вход» is a link; drawn as the button beside it would be
   (the framework's button shape), not as underlined text (BP-578). */
.auth-page a.connect-go { display: block; box-sizing: border-box; width: 100%; padding: 0.5rem 1rem; border: 1px solid var(--teal); border-radius: 6px; text-align: center; text-decoration: none; }

/* The asking app's mark, white, before «Продължи към …» (BP-579). */
.auth-page button.connect-go { display: inline-flex; align-items: center; justify-content: center; gap: 0.6rem; }

.connect-mark { width: 1.4rem; height: 1.4rem; flex: none; }

.connect-logo { display: block; width: 15rem; max-width: 70%; height: auto; margin: 0 auto 1.5rem; }

.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;
}

a.buy-bar:hover { filter: brightness(1.08); }

.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;
}

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

/* A search that finds one book, or none, would leave the plate a strip with the
 * page's white under it (BP-455). */
.book-list { min-height: 70vh; }

.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;
}

/* The catalogue's search result lines (BP-455). The field itself is in the
 * header since BP-457 — `.shop-search`. */
.book-search-count { margin: 1rem 0 0; color: var(--light); font: 400 0.95rem/1.2 var(--sans); }

.book-search-count:empty { margin: 0; }

/* The picked genres, under the count (BP-537), and the way back to all. The
 * sticky bar would cover the list's heading when a pick scrolls it into view;
 * `scroll-margin-top` keeps it below the bar. */
.book-genre-line { margin: 0.5rem 0 0; color: var(--light); font: 400 0.95rem/1.3 var(--sans); }

.book-genre-line[hidden] { display: none; }

.book-genre-line strong { color: #fff; font-weight: 600; }

/* The genres and the authors picked, and «Изчисти филтъра» under them (BP-546). */
.book-filter-line[hidden] { display: none; }

.book-list { scroll-margin-top: 3.5rem; }

.book-search-none {
  margin: 0; padding: 2rem 1rem; text-align: center;
  color: #fff; font: 400 1.1rem/1.4 var(--sans);
}

@media (max-width: 48rem) {
  .book-card-head { min-height: 0; padding-inline: 1rem; }

  .book-card-cover img { aspect-ratio: auto; }
}

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

/* 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;
  position: relative;   /* the «С автограф» mark's anchor (BP-465) */
}

/* «С автограф» (BP-465, D388): every copy of the book is signed by its author.
   A small teal mark in the cover's corner — not over the title, never in the
   way of a press. */
.book-card-signed {
  position: absolute; top: 0.5rem; left: 0.5rem; z-index: 1;
  padding: 0.3rem 0.6rem; border-radius: 999px;
  background: var(--teal); color: #fff;
  font: 700 0.7rem/1 var(--sans); letter-spacing: 0.04em; text-transform: uppercase;
  pointer-events: none;
}

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

.book-card .buy-bar .price-tag { padding: 0.6rem 0.8rem; gap: 0.7rem; }

@media (max-width: 64rem) {
  .book-card .buy-bar .price-tag {
    border-left: 0; border-top: 2px solid rgb(255 255 255 / 45%);
    justify-content: center;
  }
}

/* The old price hangs BELOW the card, in the gap between rows (BP-449): in the
 * flow it made a discounted card taller, and its bar sat higher than the bars
 * beside it. The row gap (2.5rem, 2rem on a tablet) is taller than the line. */
.book-card-was {
  position: absolute; top: 100%; left: 0; right: 0;
  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; }

/* «от Автор» under the title, a link to the author's page (BP-576). */
.product-byline {
  margin: 0; padding: 0 1.2rem 1.4rem;
  background: var(--dark); color: var(--light);
  font: 400 1.05rem/1.4 var(--sans);
}

.product-byline a { color: #fff; text-decoration: underline; text-underline-offset: 0.2em; }

.product-byline a:hover { color: var(--light); }

.product-sub:not(:empty) + .product-byline { margin-top: -0.9rem; }

/* «Следвай автора» / «Уведоми ме» (BP-583): one line of address and a button. */
.follow-form { display: grid; gap: 0.5rem; margin: 1rem 0 0; max-width: 34rem; }

.follow-form label { display: grid; gap: 0.35rem; font-weight: 600; }

.follow-form input[type="email"] { width: 100%; padding: 0.6rem 0.75rem; font: inherit; }

.follow-form button { justify-self: start; }

.product-cta .follow-form { margin-top: 0.75rem; }

.author-page .follow-form { margin: 2rem auto 0; padding: 0 1rem; }

.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;
}

/* Under the price (BP-465): the signature, and — only when few, only when true —
   how many copies are left. */
.autograph-note { margin: 0.5rem 0 0; color: var(--teal); font: 700 0.95rem/1.4 var(--sans); }

.autograph-note::before { content: "\270D\FE0E\00A0"; }

.book-left { margin: 0.3rem 0 0; color: var(--red); font: 700 0.95rem/1.4 var(--sans); }

/* «N души поръчаха … през последните 7 дни» (BP-586): a fact, not an alarm. */
.book-week { margin: 0.3rem 0 0; color: var(--teal); font: 600 0.95rem/1.4 var(--sans); }

.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; }

/* A collection (BP-419): the books it holds, as covers in a row under the
   price, and on each of those books a line naming the collection. The cards
   are a grid, so three books sit in a row and eight wrap. */
.product-collection,
.product-part-of {
  width: var(--measure); max-width: 100%;
  margin-inline: auto; padding: 1.5rem 1.2rem 0;
  text-align: center;
}

.collection-books {
  display: grid; gap: 1.2rem;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  margin-top: 1rem;
}

.collection-book { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }

.collection-book img { width: 100%; max-width: 12rem; height: auto; }

.collection-book figcaption { display: flex; flex-direction: column; gap: 0.15rem; }

.collection-book a { color: inherit; font-weight: 700; }

.collection-sub { opacity: 0.75; font-size: 0.9rem; }

.collection-price { font-weight: 700; }

.collection-saving { margin: 1.2rem 0 0; font-weight: 700; }

.product-part-of p { margin: 0.4rem 0; }

/* A series or a selection (BP-479): the same cards as a collection. In a
   series the book on screen is in the row too, marked, so the reader sees
   where it stands; its place label («Том II») is above each title. */
.series-place { margin: 0.4rem 0 0; font-weight: 700; }

.series-about { margin: 0.4rem auto 0; opacity: 0.8; }

.series-variant { margin: 1.6rem 0 0.2rem; font-size: 1.05rem; }

.collection-book .series-label { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.75; }

.collection-book.is-this img { outline: 3px solid var(--teal, #00c6cf); outline-offset: 3px; }

.collection-book.is-this .series-this { font-weight: 700; }

.product-series h2 a { color: inherit; }

/* BP-570: an author's page — who they are, above their books. */
.author-page-about { margin: 0 0 2rem; }

.author-page h2 { font: 400 1.5rem/1.2 var(--serif); margin: 0 0 1rem; }

.series-offers { margin: 1rem 0 0; font-size: 1.05rem; }

/* The office's side of it: the books in a collection, in the book editor. */
.collection-list { display: grid; gap: 0.6rem; padding-left: 1.4rem; }

.collection-list li::after { content: ""; display: block; clear: both; }

.collection-list form { display: inline; }

@media (max-width: 64rem) {
  .product-features { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 48rem) {
  .product-features { grid-template-columns: minmax(0, 1fr); }
}

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

@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;
  }
}

.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);
}

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

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

.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; }

.gallery-row { display: flex; gap: 1rem; align-items: flex-start; }

@media (max-width: 40rem) {
  .gallery-row { display: block; }

  .gallery-row img { width: 100%; }

  .gallery-row img + img { margin-top: 1rem; }
}

.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 account button, and the panel behind it ------------------------------
 *
 * Deliberately the cart button's twin: same 3rem tile, same square corners for
 * the same reason (the framework's `button` rule sets a 6px radius for the
 * admin panel, and a shop control built on a <button> inherits it — D147).
 * Two controls of the same size at the same end of the same bar is the whole
 * design; one of them being 6px rounder would read as sloppiness rather than as
 * a decision.
 *
 * The glyph is a mask over a stroked SVG, the same technique and the same
 * one-unbroken-line rule as `.cart-glyph`: a CSS string may be continued with a
 * backslash before a newline, and that is exactly what an editor or a heredoc
 * quietly eats (CLAUDE.md §8). The fallback for the ~2% without mask support is
 * a drawn circle and shoulders rather than a solid teal rectangle, which reads
 * as nothing at all.
 */
.shop-auth-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;
  border-radius: 0;
}

.auth-glyph {
  width: 1.6rem; height: 1.6rem; position: relative; display: inline-block;
}

.auth-glyph::before {
  content: ''; position: absolute; top: 0.05rem; left: 50%;
  width: 0.62rem; height: 0.62rem; margin-left: -0.31rem;
  border: 0.19rem solid currentColor; border-radius: 50%;
}

.auth-glyph::after {
  content: ''; position: absolute; left: 0.1rem; right: 0.1rem; bottom: 0.1rem;
  height: 0.62rem;
  border: 0.19rem solid currentColor; border-bottom: 0;
  border-radius: 0.7rem 0.7rem 0 0;
}

.auth-glyph {
  --auth-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"><circle cx="12" cy="8" r="3.6"/><path d="M4.4 20.2c0-4.1 3.4-6.2 7.6-6.2s7.6 2.1 7.6 6.2"/></svg>');
}

@supports (mask-image: var(--auth-svg)) or (-webkit-mask-image: var(--auth-svg)) {
  .auth-glyph::before, .auth-glyph::after { content: none; }

  .auth-glyph {
    background: currentColor;
    -webkit-mask-image: var(--auth-svg); mask-image: var(--auth-svg);
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-size: contain; mask-size: contain;
  }
}

/* The panel is the cart panel's shape so the shop has one drawer, not two.
 * `[hidden]` above already hides it — nothing here may set `display`, or the
 * rule at the top of this file stops being able to (D145).
 * Since BP-454 the cart's in behaviour too: `100dvh` high, its width, the
 * cart's backdrop behind it, and only `.auth-panel-body` scrolls. */
.auth-panel {
  position: fixed; top: 0; right: 0; z-index: 30;
  height: 100vh; height: 100dvh;
  width: min(24rem, 100%);
  display: flex; flex-direction: column;
  background: var(--paper); color: var(--dark);
  box-shadow: var(--shadow-lg);
  font-family: var(--sans);
}

.auth-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 1.2rem; background: var(--teal); color: #fff;
  flex: 0 0 auto;
}

.auth-panel-head h2 { margin: 0; font: 700 1.15rem/1 var(--sans); text-transform: uppercase; }

.auth-panel-body {
  padding: 1.2rem 1.2rem max(1.2rem, env(safe-area-inset-bottom));
  overflow-y: auto; overscroll-behavior: contain;
  flex: 1 1 auto; min-height: 0;
}

.auth-panel-body label { display: block; margin-top: 0.75rem; font-size: 0.9rem; }

.auth-social { margin: 0 0 0.6rem; }

/* A rule with a word sitting on it. The pseudo-elements draw the line so the
 * word needs no background colour to punch a hole in it — which would have to
 * match the panel's, and would be wrong the day the panel's changes. */
.auth-or {
  display: flex; align-items: center; gap: 0.75rem;
  margin: 1rem 0; color: var(--muted); font-size: 0.85rem;
}

.auth-or::before, .auth-or::after {
  content: ''; flex: 1 1 auto; height: 1px; background: var(--light);
}

/* The page behind a drawer does not scroll. Set on <body> by
 * app/functions/paper-auth-panel.js. */
body.auth-panel-open { overflow: hidden; }

/* Always exactly the height of the screen, the footer always on it (BP-452).
 * `100dvh` is the screen a phone shows NOW, address bar included or not;
 * `bottom: 0` alone followed the layout viewport, which on a phone can be
 * taller than what is visible, and the red button sat under the toolbar. The
 * list is the only part that scrolls — see `.cart-lines`. */
.cart-panel {
  position: fixed; top: 0; right: 0; z-index: 30;
  height: 100vh; height: 100dvh;
  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-empty { padding: 2rem 1.2rem; text-align: center; }

/* The list is the only part of the panel that scrolls, so the footer — total
   and red button — stays on the screen however long the basket (BP-452).
   `min-height: 0` says so outright rather than relying on `overflow` to make
   the flex minimum zero, and `overscroll-behavior` keeps a flick at the end of
   the list from scrolling the page behind. */
.cart-lines {
  list-style: none; margin: 0; padding: 0;
  overflow-y: auto; overscroll-behavior: contain;
  flex: 1 1 auto; min-height: 0;
}

.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; }

/* The bundle discount above the line's price (BP-548): the full price struck
   through, and the percentage. */
.cart-line-was { margin: 0 0 0.1rem; font-size: 0.78rem; color: var(--muted); }

.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-panel-foot {
  flex: 0 0 auto;
  padding: 1.2rem 1.2rem max(1.2rem, env(safe-area-inset-bottom));
  border-top: 1px solid var(--light);
}

/* The backdrop behind the open panel (BP-452). A click on it closes the panel
   (`data-cart-action="close"`) and does not reach the page under it. */
.cart-scrim {
  position: fixed; inset: 0; z-index: 29;
  background: rgb(0 0 0 / 45%);
}

/* The page behind the open panel does not scroll, as behind the sign-in
   drawer. Set by app/functions/paper-shop-shell.js. */
body.cart-panel-open { overflow: hidden; }

.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); }

.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);
}

.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); }

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

.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-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;
}

.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; }

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

@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-head h1, .receipt-head h1 { font-size: 1.8rem; }

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

  .product-head h1 { font-size: 1.6rem; padding: 1rem 0.8rem; }

  .product-author { flex-direction: column; align-items: center; text-align: center; }
}

.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-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; }

/* BP-112. What a signed-in customer sees on /ebook/:key or /audiobook/:key when
   the book is not in their library. Shared by both templates, so it is named for
   the reader and used by the player too rather than duplicated under a second
   name that would drift. */
.ebook-denied { margin: 2rem auto; max-width: 34rem; text-align: center; }

.ebook-denied-lead { margin: 0 0 0.5rem; font: 700 1.15rem/1.35 var(--sans); }

.ebook-denied-ways {
  display: flex; flex-wrap: wrap; gap: 1rem;
  align-items: center; justify-content: center; margin: 0;
}

.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; }

/* BP-436: whose copy this is, on every page of a paid book. Faint, in the
   bottom margin, above the text layer so a screenshot of the page carries it. */
.ebook-mark {
  position: absolute; left: 0; right: 0; bottom: .25rem; z-index: 3;
  text-align: center; font-size: .7rem; line-height: 1; letter-spacing: .02em;
  color: rgba(0, 0, 0, .45); pointer-events: none; user-select: none;
}

.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-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);
}

.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;
}

/* A heading a link jumps to («Редактирай книгата», #promyana, BP-551) lands
   below the sticky shop bar, not under it. */
.author-panel h2[id] { scroll-margin-top: 4rem; }

/* ---------------------------------------------------------------------------
   THE AUTHOR PANEL IS NOT A FORM.

   `.profile-page` is a 26rem column, and rightly so: it was built for the
   customer's own profile, which is one form with nothing to browse and nothing
   to compare. The author panel borrowed it and then grew ten tables — sales,
   history, contracts, stock by place — so every one of them was being asked to
   fit a width designed to hold a single input. Numbers wrapped, headings broke
   mid-word, and the columns stopped lining up.

   So the author screens add `.author-panel` beside it. The narrow column stays
   exactly as it is for `paper-profile` and for every `.auth-page`; only the
   pages that have something to compare get room to compare it in.

   62rem rather than the full width: a table of five columns read edge to edge
   on a desktop monitor is its own kind of unreadable, and the eye loses the row.
   --------------------------------------------------------------------------- */
.profile-page.author-panel {
  max-width: 62rem;
  padding-inline: 1.25rem;
}

/* The author's «Нова книга» form (BP-316). It takes the shop's field style,
   `.auth-form` — label above, input the full width of the column — and this
   keeps that column readable: an input 62rem wide is a line the eye cannot
   follow, and a title field that long invites a paragraph. The file input and
   the submit button are not covered by `.auth-form`, so they are given the
   same rhythm here. */
.author-panel .book-form { max-width: 36rem; margin-bottom: 1.5rem; }

.author-panel .book-form input[type="file"] {
  display: block;
  width: 100%;
  margin-bottom: 1rem;
  font: 400 0.95rem/1.3 var(--sans);
}

.author-panel .book-form button[type="submit"] { margin-top: 0.25rem; }

@media (max-width: 45rem) {
  .profile-page.author-panel { padding-inline: 1rem; }

  .author-panel table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .author-panel table th,
  .author-panel table td { white-space: nowrap; }
}

/* The cover on «Нова книга» (BP-317): a box inside the form, the tick box for
   a BlurPaper cover, the picture input, and the front and 3D covers side by
   side once they exist. The office's attach control on «Предложени книги»
   borrows the pair. */
.book-form .cover-choice {
  margin: 0.5rem 0 1.25rem;
  padding: 0.9rem 1rem 0.4rem;
  border: 1px solid var(--light);
  border-radius: var(--r);
}

.book-form .cover-choice legend {
  padding: 0 0.35rem;
  font: 600 0.8rem/1.4 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.cover-pair { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-end; margin: 0.5rem 0 0.75rem; }

.cover-pair figure { margin: 0; }

.cover-pair img { display: block; max-width: 12rem; max-height: 16rem; height: auto; }

.cover-pair figcaption { font: 400 0.8rem/1.4 var(--sans); color: var(--muted); margin-top: 0.3rem; }

/* The two 3D previews and the pick (BP-319): pages open, pages shut. The whole
   card is the label, so a press anywhere on the picture picks it. */
.cover-styles { margin: 0.5rem 0 0.75rem; }

.cover-style-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; }

.cover-style {
  display: flex; flex-direction: column; align-items: center; gap: 0.4rem;
  padding: 0.6rem; border: 2px solid var(--line); border-radius: 0.5rem; cursor: pointer;
}

.cover-style img { display: block; width: 100%; max-width: 12rem; height: auto; min-height: 6rem; object-fit: contain; }

.cover-style span { display: flex; align-items: center; gap: 0.4rem; font: 500 0.9rem/1.3 var(--sans); }

.cover-style:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg)); }

/* The author's preview of the book page (BP-370): a bar above the real
   product page, which says what this is and leads back to the form. */
.book-preview-bar {
  max-width: 72rem;
  margin: 1rem auto;
  padding: 0.75rem 1rem;
  border: 1px dashed var(--muted);
  border-radius: var(--r);
}

.book-preview-bar p { margin: 0.2rem 0; }

.book-form .preview-actions { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }

/* «Жанрове» (BP-368): the add form on one line, and each genre's name as a
   box with «Запиши» beside it, inside the table cell. */
.genre-add { display: flex; gap: 0.5rem; align-items: flex-end; flex-wrap: wrap; margin: 0.75rem 0; }

.genre-add label { flex: 1 1 14rem; margin: 0; }

.genre-rename { display: flex; gap: 0.5rem; align-items: center; margin: 0; }

.genre-rename input { flex: 1 1 auto; min-width: 8rem; margin: 0; }

/* «Формат» (BP-362): «Друг размер» opens width and height side by side.
   `[hidden]` is restated because `display: flex` would otherwise beat it. */
.book-form .size-custom { display: flex; gap: 0.75rem; }

.book-form .size-custom > label { flex: 1 1 0; min-width: 0; }

.book-form .size-custom[hidden] { display: none; }

/* The genre field (BP-529): a box per genre, drawn as chips — on «Нова книга»
   and in the office's «Жанрове на книгите» dialog. The first ticked chip is
   the main genre and says so («основен», `is-main` from _genre-chips.js);
   past five the rest are disabled. `--light` exists only inside `.shop`, so
   the office pages fall back to the root `--line`. */
.genre-pick { margin: 0 0 1rem; padding: 0; border: 0; min-width: 0; }

.genre-pick legend { padding: 0; margin-bottom: 0.1rem; font: 600 0.8rem/1.4 var(--sans); text-transform: uppercase; letter-spacing: 0.03em; }

.genre-pick-note { margin: 0 0 0.45rem; font: 400 0.8rem/1.4 var(--sans); color: var(--muted); }

.genre-pick-list { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }

/* centre: the «Друг жанр» field is taller than a chip, and a chip beside it must not stretch */
.genre-pick-list label {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  padding: 0.25rem 0.65rem;
  border: 1px solid var(--light, var(--line));
  border-radius: 999px;
  font: 400 0.85rem/1.3 var(--sans);
  /* not the form labels' small capitals: these are names to read, many at once */
  text-transform: none;
  letter-spacing: normal;
  cursor: pointer;
}

.genre-pick-list input { width: auto; margin: 0; accent-color: var(--accent); }

.genre-pick-list label:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }

.genre-pick-list label.is-main { font-weight: 600; }

.genre-pick-list label.is-main::after {
  content: "основен";
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font: 600 0.65rem/1.4 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.genre-pick-list label:has(input:disabled) { opacity: 0.5; cursor: default; }

.genre-pick-other { border-style: dashed !important; }

/* «Друг жанр…» and its field side by side (BP-531): the field sits in the row of
   chips, as wide as a long genre name, and grows to the line on a phone. */
.genre-pick-list .genre-pick-typed {
  flex: 1 1 12rem; max-width: 18rem; min-width: 0; width: auto; margin: 0;
  padding: 0.25rem 0.65rem; border-radius: 999px; font: 400 0.85rem/1.3 var(--sans);
}

/* «Жанрове»: a genre an author typed, waiting for «Одобри». */
.genre-pending { display: inline-block; margin-left: 0.35rem; padding: 0 0.45rem; border-radius: 999px; background: #fff3cd; color: #7a5b00; font-size: 0.75rem; font-weight: 600; }

.genre-pending-note { padding: 0.6rem 0.8rem; border-left: 3px solid #e0b100; background: color-mix(in srgb, #e0b100 10%, transparent); }

.genre-approve { display: inline; margin-right: 0.75rem; }

/* «Жанрове на книгите» (BP-529): the table, and the one dialog that edits a row. */
.book-genres-tools { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: end; }

.book-genres-tools label { display: grid; gap: 0.2rem; min-width: min(100%, 20rem); }

.book-genres-list tr[hidden] { display: none; }

.book-genres-cell { line-height: 1.6; }

.genre-main { display: inline-block; padding: 0 0.5rem; border-radius: 999px; background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); font-weight: 600; font-size: 0.85rem; }

.genre-rest { color: var(--muted); font-size: 0.85rem; }

.book-genres-dialog { width: min(42rem, calc(100vw - 2rem)); border: 1px solid var(--light, var(--line)); border-radius: var(--r); padding: 1.25rem; }

.book-genres-dialog::backdrop { background: rgb(0 0 0 / 0.45); }

.book-genres-dialog h2 { margin: 0 0 0.75rem; font-size: 1.15rem; }

.book-genres-actions { display: flex; gap: 1rem; align-items: center; margin: 0.5rem 0 0; }

.book-form .line-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.4rem; }

.book-form .line-row input { flex: 1 1 auto; min-width: 0; margin: 0; }

.book-form .line-row button { flex: 0 0 auto; }

.book-form .illustrations-choice,
.book-form .cover-parts-choice,
.book-form .landing-choice {
  margin: 0.5rem 0 1.25rem;
  padding: 0.9rem 1rem 0.4rem;
  border: 1px solid var(--light);
  border-radius: var(--r);
}

.book-form .illustrations-choice legend,
.book-form .cover-parts-choice legend,
.book-form .landing-choice legend {
  padding: 0 0.35rem;
  font: 600 0.8rem/1.4 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.illustration-list { list-style: none; padding: 0; margin: 0.5rem 0 0.75rem; display: grid; grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: 0.75rem; }

.illustration-list li { display: flex; flex-direction: column; gap: 0.3rem; font: 400 0.8rem/1.3 var(--sans); color: var(--muted); overflow-wrap: anywhere; }

.illustration-list img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 4px; }

.illustration-list button { align-self: flex-start; }

.illustration-thumbs { display: flex; flex-wrap: wrap; gap: 0.4rem; }

/* «Нова книга»: the optional parts of the cover (BP-504) — one small field
   each, the picture under it once it is sent. Edges are thin strips, so the
   picture is bounded on both sides rather than given a ratio. */
.cover-part-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 0.9rem; margin: 0.5rem 0 0.75rem; }

.cover-part { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }

.cover-part label { margin: 0; }

.cover-part figure { margin: 0; }

.cover-part img { display: block; max-width: 100%; max-height: 9rem; width: auto; height: auto; border: 1px solid var(--line); border-radius: 4px; }

/* «Книга от всички страни» (BP-320): a grid of poses, each with its download. */
.cover3d-poses { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 0.75rem; }

.cover3d-pose { margin: 0; display: flex; flex-direction: column; gap: 0.4rem; }

.cover3d-pose img { display: block; width: 100%; aspect-ratio: 1; object-fit: contain; min-height: 6rem; }

.cover3d-pose figcaption { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.4rem; font: 500 0.85rem/1.3 var(--sans); }

/* «Книга на фон от илюстрация» (BP-321): the book's pictures as a row of
   choices, the poses as tick boxes, and the results at the frame's own shape. */
.cover3d-backgrounds { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); gap: 0.6rem; }

.cover3d-background { position: relative; display: flex; flex-direction: column; gap: 0.3rem; cursor: pointer; font: 500 0.8rem/1.3 var(--sans); }

.cover3d-background input { position: absolute; inset: 0.4rem auto auto 0.4rem; }

.cover3d-background img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 4px; outline: 3px solid transparent; outline-offset: 1px; }

.cover3d-background input:checked ~ img { outline-color: var(--accent, #2563eb); }

.cover3d-background span { overflow-wrap: anywhere; }

.cover3d-scene-poses { display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; }

.cover3d-scenes { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.75rem; }

.cover3d-scene { margin: 0; display: flex; flex-direction: column; gap: 0.4rem; }

.cover3d-scene img { display: block; width: 100%; aspect-ratio: var(--scene-ratio, 4 / 5); object-fit: contain; background: #0001; }

.cover3d-scene figcaption { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.4rem; font: 500 0.85rem/1.3 var(--sans); }

/* The six figures the page exists for. `auto-fit` rather than a fixed count,
   so a phone gets one column without a second rule saying so. */
.mkt-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0.75rem; }

.mkt-card {
  display: flex; flex-direction: column; gap: 0.15rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--light); border-left-width: 4px; border-radius: 6px;
}

.mkt-card-label { font: 600 0.78rem/1.2 var(--sans); text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.7; }

.mkt-card-figure { font: 700 1.6rem/1.15 var(--sans); }

/* What needs doing. A list, so it is still a list with no stylesheet at all. */
.mkt-alerts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }

.mkt-alert { padding: 0.55rem 0.8rem; border-radius: 6px; border-left: 4px solid var(--light); }

.cover-attach { margin: 1rem 0; padding-top: 0.75rem; border-top: 1px solid var(--light); }

.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;
}

.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-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; }

/* «ЧЕТИ БЕЗПЛАТНО» and «СЛУШАЙ БЕЗПЛАТНО», opened on the product page itself
 * (BP-427, `app/functions/_paper-samples.js`).
 *
 * The excerpt is a <dialog> over the page: the reader inside it is the same
 * `paper-ebook` the excerpt's own page draws, so its rules above apply and
 * only the padding changes. Its head sticks while the pages scroll, because
 * «Затвори» is the way back and must not scroll away with the book. On a phone
 * it is the whole screen: a book page in a box with a margin round it is a
 * smaller book page.
 *
 * The listen is a bar fixed to the bottom of the screen, so it keeps playing
 * while the visitor reads the page that sells the book. `hidden` is honoured
 * by BP-86's `[hidden] { display: none !important }`, so nothing here has to
 * fight `display: flex` to keep it shut.
 *
 * Colours are stated on every control, for the reason `.ebook-turn` gives.
 *
 * After the reader's and the player's own rules, because these are overrides
 * of them, and the tests that read a rule by its name take the first one. */
.product-sample {
  width: min(70rem, calc(100vw - 2rem)); max-width: none;
  max-height: calc(100dvh - 2rem);
  padding: 0; border: 0; border-radius: var(--r);
  background: #fff; color: var(--dark); overflow: auto;
}

.product-sample::backdrop { background: rgb(0 0 0 / 55%); }

.product-sample-head {
  position: sticky; top: 0; z-index: 3;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0.75rem 1rem; background: #fff; border-bottom: 1px solid rgb(0 0 0 / 12%);
}

.product-sample-title {
  margin: 0; color: var(--teal);
  font: 700 1rem/1.2 var(--sans); text-transform: uppercase; letter-spacing: 0.03em;
}

.product-sample-close {
  padding: 0.45rem 1rem; border: 1px solid rgb(0 0 0 / 18%); border-radius: 0.5rem;
  background: #fff; color: var(--dark); cursor: pointer; font: inherit;
}

.product-sample-buy { margin: 0; padding: 0 1rem 1.5rem; text-align: center; }

.product-listen {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.5rem 1rem calc(0.5rem + env(safe-area-inset-bottom));
  background: #fff; color: var(--dark);
  box-shadow: 0 -2px 12px rgb(0 0 0 / 18%);
}

.product-listen-title {
  flex: 0 0 auto; margin: 0; color: var(--teal);
  font: 700 0.85rem/1.2 var(--sans); text-transform: uppercase; letter-spacing: 0.03em;
}

.product-listen .audio-bar { flex-wrap: nowrap; padding: 0; }

.product-listen .audio-seek { min-width: 3rem; }

.product-listen .audio-status { min-height: 0; font-size: 0.85rem; }

.product-listen .audio-status:empty { display: none; }

@media (max-width: 40rem) {
  .product-sample {
    width: 100vw; height: 100dvh; max-height: none; margin: 0; border-radius: 0;
  }

  .product-listen { gap: 0.5rem; padding-inline: 0.5rem; }

  .product-listen-title { display: none; }
}

.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; }

.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; }

.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; }

/* «Услуги за автори» (/uslugi, BP-491/492): built like a book's product page.
   The title and subtitle are the product page's own `.product-head`; below it,
   a stack of full-width strips, each kind of service on a ground of its own,
   all the shop's own colours (`.shop` tokens, which do not follow dark mode,
   on purpose): the readers' lilac, the cover plate, the teal of
   «ЧЕТИ БЕЗПЛАТНО», lilac again for Редакция (the product page repeats it too,
   never next to itself), and the buy bar's red for «Как да поръчате». The strip
   paints the full width; `.services-band` is the column inside it. */
.services-band { width: 50rem; max-width: 100%; margin-inline: auto; padding-inline: 1.2rem; }

.services-strip { padding-block: 2.5rem 3rem; }

.services-strip h2 { margin: 0 0 1.2rem; font: 400 2rem/1.2 var(--serif); }

.services-strip ul { margin: 0; padding-left: 1.2rem; font-size: 1.1rem; }

.services-strip li { margin: 0 0 .6rem; }

.services-strip p { margin: 0; font-size: 1.1rem; }

.services-print   { background: #e5e2f5; color: var(--plate); }

.services-prepress { background: var(--plate); color: #fff; }

.services-proofreading { background: var(--teal); color: var(--plate); }

.services-editing { background: #e5e2f5; color: var(--plate); }

.services-order   { background: var(--red); color: #fff; }

.services-order a { font-weight: 700; }

/* Text on one side, the service's icon on the other, swapping sides from strip
   to strip (BP-493). The markup is the same in every strip, text first; the
   even strips only move the icon to the left with `order`. The icon is drawn
   in strokes of `currentColor`, so it is the strip's text colour. Two equal
   halves, the icon centred in its own; on a phone it stays beside the text,
   smaller, and the columns swap with it. */
.services-band {
  display: grid; grid-template-columns: 1fr 1fr;
  align-items: center; gap: 2rem;
}

.services-icon {
  justify-self: center;
  width: 9rem; height: 9rem;
  fill: none; stroke: currentColor; stroke-width: 2.5;
  stroke-linecap: round; stroke-linejoin: round;
}

.services-icon circle { fill: currentColor; stroke: none; }

.services-strip:nth-of-type(even) .services-icon { order: -1; }

@media (max-width: 600px) {
  .services-band { grid-template-columns: 1fr auto; gap: 1rem; }

  .services-strip:nth-of-type(even) .services-band { grid-template-columns: auto 1fr; }

  .services-icon { width: 4.5rem; height: 4.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 language switch (BP-497, D409): under the legal links, and only on a
   page that exists in more than one language. The language being read is
   plain bold text, not a link to itself. */
.shop-langs {
  display: flex; flex-wrap: wrap; gap: .4rem 1rem;
  justify-content: center; margin-top: .6rem; font-size: .9rem;
}

.blog-head h1, .blog-post-head h1 { font-family: var(--serif); font-size: 2.1rem; line-height: 1.2; margin: 0 0 .5rem; }

.blog-lede { font-size: 1.12rem; line-height: 1.55; color: var(--muted); margin: 0 0 1.75rem; }

.blog-meta { color: var(--muted); font-size: .9rem; margin: 0 0 .9rem; }

.blog-crumbs { margin: 0 0 .5rem; font-size: .9rem; }

.blog-list { display: grid; gap: 2.25rem; }

.blog-card { display: grid; grid-template-columns: 15rem 1fr; gap: 1.4rem; align-items: start; }

.blog-card-cover img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--r); }

.blog-card-title { font-family: var(--serif); font-size: 1.4rem; line-height: 1.25; margin: 0 0 .35rem; }

.blog-card-title a { color: inherit; text-decoration: none; }

.blog-card-title a:hover { text-decoration: underline; }

.blog-card-summary { margin: 0 0 .5rem; line-height: 1.55; }

@media (max-width: 40rem) {
  .blog-card { grid-template-columns: 1fr; gap: .8rem; }
}

.blog-post-cover { display: block; width: 100%; height: auto; border-radius: var(--r); margin: 0 0 2rem; }

.blog-body { font-size: 1.08rem; line-height: 1.72; }

.blog-body h2 { font-family: var(--serif); font-size: 1.55rem; line-height: 1.3; margin: 2.4rem 0 .8rem; }

.blog-body h3 { font-size: 1.2rem; line-height: 1.35; margin: 1.8rem 0 .5rem; }

.blog-body p, .blog-body ul, .blog-body ol { margin: 0 0 1.15rem; }

.blog-body li { margin-bottom: .35rem; }

.blog-body blockquote { margin: 1.75rem 0; padding: .2rem 0 .2rem 1.25rem; border-left: 3px solid var(--teal); font-style: italic; color: var(--dark); }

.blog-body blockquote p { margin: 0; }

.blog-body hr { border: 0; border-top: 1px solid var(--line); margin: 2.25rem 0; }

.blog-figure { margin: 2rem 0; }

.blog-figure img { display: block; max-width: 100%; height: auto; margin: 0 auto; border-radius: var(--r); }

.blog-figure figcaption { color: var(--muted); font-size: .9rem; text-align: center; margin-top: .55rem; line-height: 1.45; }

.blog-book {
  display: grid; grid-template-columns: 8.5rem 1fr; gap: 1.4rem; align-items: center;
  margin: 2rem 0; padding: 1.1rem; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--r); box-shadow: var(--shadow);
}

.blog-book-cover img { display: block; width: 100%; height: auto; }

.blog-book-title { font-family: var(--serif); font-size: 1.25rem; line-height: 1.3; margin: 0 0 .25rem; }

.blog-book-title a { color: inherit; text-decoration: none; }

.blog-book-title a:hover { text-decoration: underline; }

.blog-book-sub, .blog-book-author { margin: 0 0 .35rem; color: var(--muted); font-size: .95rem; line-height: 1.4; }

.blog-book .buy-bar { margin: .9rem 0 0; max-width: 21rem; }

.blog-book .buy-bar .price-tag { padding: .45rem .7rem; gap: .55rem; }

@media (max-width: 30rem) {
  .blog-book { grid-template-columns: 6rem 1fr; gap: .9rem; padding: .9rem; }

  .blog-book .buy-bar { flex-direction: column; }

  .blog-book .buy-bar .price-tag { border-left: 0; border-top: 2px solid rgb(255 255 255 / 45%); justify-content: center; }
}

.blog-more-posts { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--line); }

.blog-more-posts h2 { font-family: var(--serif); font-size: 1.3rem; margin: 0 0 .8rem; }

.blog-more-posts ul { list-style: none; padding: 0; margin: 0; display: grid; gap: .6rem; }

.blog-more-posts .blog-meta { margin: 0 0 0 .4rem; }

.blog-feed { margin-top: 2.75rem; font-size: .9rem; color: var(--muted); }

.blog-preview-note { margin-bottom: 1.75rem; }

/* The table of contents (BP-443): a quiet box, numbered, above the text. */
.blog-toc { margin: 0 0 2rem; padding: 1rem 1.25rem; background: var(--paper); border: 1px solid var(--line); border-radius: var(--r); }

.blog-toc-title { margin: 0 0 .4rem; font-weight: 700; font-size: .95rem; }

.blog-toc ol { margin: 0; padding-left: 1.3rem; line-height: 1.6; }

.blog-body h2[id], .blog-body h3[id] { scroll-margin-top: 1.5rem; }

.blog-preview-note ul { margin: .4rem 0 0; }

/* A buyer's stars, and the summary under the product page's heading (BP-468). */
.review-stars { color: #f5a623; letter-spacing: 0.05em; }

.review-summary { margin: -0.4rem 0 1rem; color: #f5a623; font: 700 1rem/1.4 var(--sans); }

/* Readers' opinions, on the author's and the office's screens (BP-466). */
.review-card { display: flex; gap: 1rem; align-items: flex-start; padding: 0.9rem 0; border-top: 1px solid var(--light); }

.review-card p { margin: 0 0 0.4rem; }

.review-avatar { width: 3.5rem; height: 3.5rem; flex: 0 0 auto; border-radius: 50%; object-fit: cover; }

.blog-edit-body { font: .95rem/1.55 ui-monospace, 'Cascadia Mono', Consolas, monospace; min-height: 26rem; }

.blog-edit-cover img { display: block; width: 100%; max-width: 26rem; height: auto; border-radius: var(--r); }

.blog-edit-tools { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }

.blog-help ul { margin: .5rem 0 0; padding-left: 1.2rem; line-height: 1.6; }

.blog-help code { font-size: .9em; padding: 0 .25em; background: rgb(0 0 0 / 5%); border-radius: 3px; }

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

/* A version with the price and the button first (BP-463, `compact_first`).
 * At 390px the plain sales letter's first screen was the title and a large
 * cover: no price, and «ВЗЕМИ СЕГА» at the bottom edge or below it. Here the
 * price and the button open the offer, the lower button is not repeated, and on
 * a phone the cover shrinks to sit BESIDE them: `.funnel-offer` becomes
 * `display: contents` so its children are grid cells — the quick block next to
 * the cover, the blurb and the points below at full width. */
.funnel-quick { margin: 0 0 1.4rem; }

.funnel-price { margin: 0 0 0.7rem; font: 700 1.7rem/1.1 var(--sans); }

.funnel-price span { font-size: 0.95rem; font-weight: 400; opacity: 0.7; }

.funnel-sales.is-compact .funnel-offer > .funnel-cta { display: none; }

@media (max-width: 46rem) {
  .funnel-sales.is-compact .funnel-head { padding-block: 1.2rem 0.4rem; }

  .funnel-sales.is-compact .funnel-head h1 { font-size: 1.9rem; }

  .funnel-sales.is-compact .funnel-hero {
    grid-template-columns: minmax(0, 40%) minmax(0, 1fr);
    gap: 1rem 1.1rem; align-items: center;
    padding-block: 0.6rem 2rem;
  }

  .funnel-sales.is-compact .funnel-cover { grid-column: 1; grid-row: 1; max-width: none; margin: 0; }

  .funnel-sales.is-compact .funnel-offer { display: contents; }

  .funnel-sales.is-compact .funnel-quick { grid-column: 2; grid-row: 1; margin: 0; }

  .funnel-sales.is-compact .funnel-offer > :not(.funnel-quick) { grid-column: 1 / -1; }

  .funnel-sales.is-compact .funnel-quick .funnel-cta { width: 100%; padding: 0.9rem 0.6rem; font-size: 1rem; }

  .funnel-sales.is-compact .funnel-price { font-size: 1.45rem; }
}

.funnel-packs { padding-block: 2.5rem; }

.funnel-packs h2 { margin: 0 0 1.5rem; text-align: center; font: 400 2rem/1.2 var(--serif); }

.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 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 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-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); }

/* The author's picture on «Моите данни» (BP-376). */
.author-photo {
  display: block;
  width: 160px;
  max-width: 100%;
  height: auto;
  border-radius: 8px;
  margin: 0 0 0.75rem;
}

/* «Вашата заявка»: a refused applicant opens what they sent (BP-383). */
.refused-request { margin-top: 0.8rem; border-top: 1px solid var(--line); padding-top: 0.6rem; }

.refused-request > summary { cursor: pointer; font-weight: 600; }

.refused-request form { margin-top: 0.8rem; }

.about-text {
  white-space: pre-line;
}

.cover-thumb {
  width: 3rem; height: auto; max-height: 4.5rem; object-fit: cover;
  border: 1px solid var(--line); border-radius: calc(var(--r) / 2);
  float: left; margin: 0 0.6rem 0.2rem 0;
}

/* The button floats, not the picture inside it: a float inside an inline
   button stays in the button, and the title then sat under the cover. */
.stock-book { min-width: 16rem; vertical-align: top; }

.stock-book::after { content: ""; display: block; clear: both; }

.stock-cover {
  float: left; margin: 0 0.7rem 0.3rem 0; padding: 0;
  background: none; border: 0; cursor: zoom-in;
}

.stock-cover .cover-thumb { float: none; margin: 0; display: block; }

.stock-subtitle { display: block; font-weight: 400; }

/* «Оригинални корици» (BP-500): one row per book — the front to recognise
   it by, the recorded whole cover, and its own upload. A row is also the
   drop target, so it shows when a file is over it. */
.oc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }

.oc-row {
  display: grid; align-items: center; gap: 0.8rem;
  grid-template-columns: 3rem minmax(10rem, 1fr) minmax(8rem, 18rem) auto;
  padding: 0.5rem 0.7rem; background: var(--paper);
  border: 1px solid var(--line); border-left: 4px solid var(--red);
  border-radius: var(--r);
}

.oc-row.has-original { border-left-color: var(--teal); }

.oc-row.is-over { outline: 2px dashed var(--accent); outline-offset: 2px; }

.oc-front img { width: 3rem; height: auto; display: block; border-radius: calc(var(--r) / 2); }

.oc-name strong { display: block; }

.oc-original img {
  width: 100%; max-height: 8rem; object-fit: contain; display: block;
  border: 1px solid var(--line); border-radius: calc(var(--r) / 2);
}

.oc-none { color: var(--red); font-weight: 600; }

.oc-upload { display: flex; flex-direction: column; align-items: flex-end; gap: 0.2rem; }

@media (max-width: 40rem) {
  .oc-row { grid-template-columns: 3rem 1fr; }

  .oc-original, .oc-upload { grid-column: 1 / -1; align-items: stretch; }
}

/* «Части на корицата» (BP-504): a book's row holds six cells, one per part,
   each its own drop target. The front is the one required, so a book without
   it keeps the red edge «Лицеви корици» gave it. */
.cp-row {
  display: grid; gap: 0.6rem; padding: 0.6rem 0.7rem; background: var(--paper);
  border: 1px solid var(--line); border-left: 4px solid var(--red); border-radius: var(--r);
}

.cp-row.has-original { border-left-color: var(--teal); }

.cp-name { display: flex; gap: 0.6rem; align-items: center; min-width: 0; }

.cp-name strong { display: block; }

.cp-catalogue { width: 2.4rem; height: auto; border-radius: calc(var(--r) / 2); }

.cp-parts { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0.5rem; }

.cp-part {
  display: flex; flex-direction: column; align-items: center; gap: 0.3rem; min-width: 0;
  padding: 0.35rem; border: 1px dashed var(--line); border-radius: calc(var(--r) / 2);
}

.cp-part.is-over { outline: 2px dashed var(--accent); outline-offset: 2px; }

.cp-label { font: 600 0.75rem/1.3 var(--sans); text-align: center; }

.cp-thumb { height: 7rem; display: flex; align-items: center; justify-content: center; max-width: 100%; }

.cp-thumb img { max-width: 100%; max-height: 7rem; width: auto; height: auto; display: block; border: 1px solid var(--line); }

.cp-part:not(.cp-front) .oc-none { color: var(--muted); font-weight: 400; }

@media (max-width: 52rem) {
  .cp-parts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.cover-dialog {
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 0.8rem; background: var(--bg, #fff);
  max-width: min(92vw, 36rem); max-height: 92vh; overflow: hidden;
}

.cover-dialog::backdrop { background: rgb(0 0 0 / 0.55); }

.cover-dialog img {
  display: block; max-width: 100%; max-height: calc(92vh - 6.5rem);
  height: auto; margin: 0 auto 0.6rem;
}

.cover-dialog button { display: block; margin: 0 auto; }

/* ---------------------------------------------------------------------------
   The raffle (BP-232).

   The wheel is a CSS animation rather than a `setInterval` writing
   `style.transform`, which is what the Angular screen it replaces did. The
   browser runs this one off the main thread, so it stays smooth while the
   action is in flight — and it costs no timer to cancel when the page goes.

   The reduced-motion query is not decoration. Seven seconds of spinning is the
   exact thing somebody switches that setting on to stop, and the module drops
   the wait to nothing to match, so the answer arrives immediately rather than
   after an animation nobody is being shown.
   --------------------------------------------------------------------------- */
.raffle-wheel {
  width: 12rem; height: 12rem; margin: 1rem auto; display: block;
  border-radius: 50%;
  border: 0.6rem solid var(--line);
  border-top-color: var(--accent, #4a7ab8);
}

.raffle-wheel.is-spinning { animation: raffle-spin 0.7s linear infinite; }

@media (prefers-reduced-motion: reduce) {
  .raffle-wheel.is-spinning { animation: none; }
}

.raffle-winner {
  font-size: 2rem; font-weight: 600; text-align: center;
  margin: 0.5rem 0; overflow-wrap: anywhere;
}

.raffle-position { text-align: center; color: var(--muted); }

/* The history. `tabular-nums` so the counts line up down the column. */
.raffle-history td, .raffle-history th { vertical-align: top; }

.raffle-history .raffle-count { font-variant-numeric: tabular-nums; }

.raffle-entrants { color: var(--muted); overflow-wrap: anywhere; }

/* The paragraph-style dropdown. It says what the ABZAC is, so it sits with the
   look controls but reads as a choice rather than a toggle (BP-276). */
.print-style select { max-width: 9rem; }

/* Version history (BP-517): the list, and a comparison as struck old words and
   underlined new ones — the same marks as a suggestion. */
.print-versions { margin: 0.4rem 0; padding-left: 1.2rem; }

.print-versions button { font-size: 0.8rem; padding: 0.05rem 0.4rem; }

/* ---------------------------------------------------------------------------
   Gift vouchers (BP-283) — docs/PAPER-VOUCHERS.md

   A voucher is a catalogue row with no cover file, so it is drawn rather than
   photographed. In CSS, for the same reason the reassurance icons are: a
   picture per amount is four files to keep in step with four rows, and an image
   or a webfont is a dependency and a build step this project does not have
   (CLAUDE.md §1).

   The plate borrows the shop's one grey (#262626, the colour the covers are
   plated with and the catalogue is painted with, D140) so a voucher card sits
   in the grid at the same weight as a book.
   --------------------------------------------------------------------------- */
.voucher-plate {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  aspect-ratio: 3 / 4;
  padding: 1rem;
  text-align: center;
  background: #262626;
  color: #fff;
  /* The same 3/4 box the covers letterbox into, so a row mixing books and
     vouchers keeps one baseline. */
  border-radius: 2px;
  text-decoration: none;
}

.voucher-plate-mark {
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.75;
}

.voucher-plate-sum {
  font-size: 1.9rem;
  font-weight: 700;
  line-height: 1.1;
}

.voucher-plate-alt { font-size: 0.85rem; opacity: 0.7; }

/* The custom-amount product, and a spent or expired voucher. Both are still
   plates and neither is an error state, so the difference is a ground and not
   a colour that shouts. */
.voucher-plate-custom { background: #04c6cf; }

.voucher-plate-dead { background: #555; }

/* The plate on the voucher's own two pages is wider than a card's and does not
   pretend to be a book. */
.voucher-buy .voucher-plate,
.voucher-view .voucher-plate {
  aspect-ratio: auto;
  min-height: 9rem;
  max-width: 22rem;
  margin: 1rem auto;
}

.voucher-eyebrow {
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.7;
}

.voucher-sub { margin: 0.25rem 0 0; }

.voucher-amount-row { display: flex; align-items: baseline; gap: 0.4rem; }

.voucher-amount-row input { max-width: 8rem; }

.voucher-amount-unit { font-size: 1.2rem; font-weight: 600; }

.voucher-worth { margin: 0 0 0.6rem; font-size: 1.1rem; }

.voucher-added { margin: 0.5rem 0 0; }

.voucher-steps { margin: 0.4rem 0; padding-left: 1.2rem; }

.voucher-steps li { margin-bottom: 0.3rem; }

.voucher-code-value { margin: 0.2rem 0; font-size: 1.15rem; }

.voucher-code-value code { word-break: break-all; }

.voucher-note {
  margin: 1rem 0;
  font-style: italic;
  white-space: pre-wrap;
}

.voucher-state { font-weight: 600; }

/* One issued voucher on the receipt. The link is the gift, so it is allowed to
   wrap rather than be cut off — somebody is going to select it by hand. */
.voucher-issued { margin: 0.8rem 0; }

.voucher-issued-sum { margin: 0; font-size: 1.2rem; font-weight: 700; }

.voucher-issued-link { margin: 0.15rem 0; }

.voucher-issued-link a { word-break: break-all; }

/* The checkout's code field, folded away: most orders do not have one, and an
   always-open field invites everybody to wonder whether they are missing a
   discount. */
.voucher-field { margin: 0.6rem 0; }

.voucher-field summary { cursor: pointer; }

.voucher-field label { display: block; margin-top: 0.4rem; }

.chart-legend { display: flex; gap: 1rem; flex-wrap: wrap; margin: 0.5rem 0 0; padding: 0; list-style: none; }

.chart-legend li { display: flex; align-items: center; gap: 0.35rem; font-size: 0.85rem; color: var(--muted); }

.chart-legend b { color: var(--ink); font-weight: 600; }

/* A chart with nothing in it is a sentence, never an empty frame: an empty
   frame reads as "still loading" or as a bug, and "nothing sold that week" is
   neither of those. */
.chart-empty {
  margin: 0.6rem 0 0;
  padding: 1.2rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--muted);
  text-align: center;
}

/* The figures above the pictures. */
.chart-figures { display: flex; gap: 1.5rem; flex-wrap: wrap; margin: 0.4rem 0 0; padding: 0; list-style: none; }

.chart-figures li { min-width: 7rem; }

/* «Ръчен шрифт» (docs/PAPER-TOOLS.md). The template sheets are real A4 in
   millimetres; on screen they are shrunk, on paper they are exactly 210 × 297. */
.hf-sheets { display: flex; flex-wrap: wrap; gap: 0.75rem; }

.hf-howto ol, .hf-howto ul { margin: 0; padding-left: 1.4rem; }

.hf-howto li { margin: 0 0 0.5rem; line-height: 1.5; }

.hf-howto summary { cursor: pointer; }

.hf-sheets .hf-sheet { width: 100%; max-width: 360px; height: auto; border: 1px solid var(--line, #ddd); background: #fff; }

.hf-pages { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.6rem; }

.hf-pages li { display: grid; gap: 0.35rem; }

.hf-thumb { width: 100%; max-width: 360px; height: auto; border: 1px solid var(--line, #ddd); }

.hf-glyphs { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 0.35rem 0.9rem; }

.hf-char { display: flex; align-items: center; gap: 0.25rem; }

.hf-char.hf-signs { grid-column: 1 / -1; flex-wrap: wrap; }

.hf-char-label { width: 2.2rem; font-size: 1.1rem; text-align: center; color: var(--muted, #666); }

.hf-glyph { padding: 0; border: 1px solid var(--line, #ddd); background: #fff; border-radius: 4px; cursor: pointer; line-height: 0; }

.hf-glyph .hf-glyph-svg { width: 44px; height: 53px; display: block; }

.hf-signs .hf-glyph .hf-glyph-svg { width: 220px; height: 116px; }

.hf-glyph.is-clipped { border-color: #d33; box-shadow: 0 0 0 1px #d33 inset; }

.hf-glyph.is-off { opacity: 0.25; background: repeating-linear-gradient(45deg, #fff, #fff 6px, #eee 6px, #eee 12px); }

.hf-glyph.is-empty, .hf-glyph.is-unread { cursor: default; background: #f6f6f6; }

.hf-glyph.is-unread { border-style: dashed; }

.hf-preview { width: 100%; max-width: 1000px; height: auto; border: 1px solid var(--line, #ddd); background: #fff; }

.hf-print-root { display: none; }

@media print {
  html.hf-printing body > :not(.hf-print-root) { display: none !important; }

  html.hf-printing .hf-print-root { display: block; }

  html.hf-printing .hf-print-root .hf-sheet { display: block; width: 210mm; height: 297mm; page: hand-font; break-after: page; }

  html.hf-printing .hf-print-root .hf-sheet:last-child { break-after: auto; }
}

/* The National Library's deposit protocol (docs/PAPER-ADMIN.md §3o, BP-409/410),
   laid out as its own form, dep_form.docx: one table on A4 landscape, 277.6 mm,
   eleven grid columns in the form's twips (÷ 56.69 = mm), Times New Roman,
   Word's default cell margins (1.9 mm left and right). */
.deposit-protocol { background: #fff; color: #000; font-family: "Times New Roman", Times, serif; page: deposit; overflow-x: auto; }

.deposit-protocol p { margin: 0; }

.deposit-protocol u { text-underline-offset: 1.5pt; }

.dp-form { border-collapse: collapse; table-layout: fixed; width: 277.6mm; }

.dp-form col.g1 { width: 9.88mm; }

.dp-form col.g2 { width: 22.51mm; }

.dp-form col.g3 { width: 40.01mm; }

.dp-form col.g4 { width: 23.14mm; }

.dp-form col.g5 { width: 56.87mm; }

.dp-form col.g6 { width: 28.79mm; }

.dp-form col.g7 { width: 6.23mm; }

.dp-form col.g8 { width: 40.01mm; }

.dp-form col.g9 { width: 17.5mm; }

.dp-form col.g10 { width: 14.99mm; }

.dp-form col.g11 { width: 17.66mm; }

.dp-form td, .dp-form th { border: 0.5pt solid #000; padding: 0 1.9mm; vertical-align: top; text-align: center; font-weight: normal; }

.dp-top td { font-size: 9pt; line-height: 1.2; height: 62mm; }

.dp-top p { margin-bottom: 1.6mm; }

.dp-top .dp-label { font-size: 10pt; }

.dp-top .dp-to, .dp-top .dp-title { padding-top: 7mm; }

.dp-top .dp-from { padding-top: 0.5mm; }

.dp-top .dp-heading { font-size: 11pt; margin-bottom: 2.2mm; }

.dp-top .dp-accepted { margin-top: 12mm; text-align: left; padding-left: 7mm; }

.dp-heads th { font-size: 10pt; line-height: 1.15; padding-top: 0.5mm; padding-bottom: 0.5mm; overflow-wrap: anywhere; }

.dp-line td { font-size: 10pt; line-height: 1.2; height: 7.8mm; text-align: left; padding-top: 0.5mm; }

.dp-line td.dp-n { text-align: right; padding: 0.5mm 0.6mm 0 0; }

.dp-foot { font-size: 10pt; width: 277.6mm; margin-top: 9mm; break-inside: avoid; page-break-inside: avoid; }

.dp-foot p { padding-left: 2.7mm; margin-bottom: 4.5mm; }

.dp-foot .dp-sign { position: relative; height: 1.3em; }

.dp-foot .dp-gave { position: absolute; left: 91mm; }

.dp-foot .dp-took { position: absolute; left: 179mm; }

.deposit-protocol [contenteditable="true"]:focus { outline: 1px dashed #888; }

@media print {
  body:has(.deposit-protocol) { page: deposit; margin: 0; padding: 0; }
}

@media print {
  body:has(.deposit-protocol) .paper-bar,
  body:has(.deposit-protocol) .paper-notice,
  body:has(.deposit-protocol) .deposit-controls { display: none !important; }

  .deposit-protocol { overflow: visible; }

  .dp-form tr { break-inside: avoid; }
}

.print-people { list-style: none; padding: 0; margin: 0.5rem 0; }

.print-people li { padding: 0.35rem 0; border-bottom: 1px solid var(--line, #eee); }

.print-people .inline-form { display: inline; margin-left: 0.5rem; }

/* Notices (BP-602): the unread count on the account button, and /izvestiya. */
.shop-auth-open { position: relative; }

.notice-badge { background: #e8483c; }

.notice-list { list-style: none; padding: 0; margin: 0; }

.notice-item { display: flex; gap: 0.75rem; align-items: flex-start; justify-content: space-between; padding: 0.6rem 0; border-bottom: 1px solid var(--line, #eee); }

.notice-item.is-unread .notice-text { font-weight: 600; }

.notice-item.is-unread::before { content: ''; flex: 0 0 0.5rem; height: 0.5rem; margin-top: 0.45rem; border-radius: 50%; background: #00c6cf; }

.notice-body { flex: 1 1 auto; }

.notice-meta { font-size: 0.8rem; opacity: 0.75; }

.notice-item form { margin: 0; }

.profile-page.author-panel.docs-page { max-width: 76rem; }

.docs-hint { margin-top: 0.6rem; font-size: 0.82rem; }
