/*
 * Color palette
 *
 * Neutrals:  #111  #333  #555  #888  #9ca3af  #374151  #6b7280
 *            #d1d5db  #e5e7eb  #f9fafb  #f4f4f4
 * Success:   #16a34a  #86efac (border)  #f0fdf4 (background)
 * Error:     #dc2626  #b91c1c  #fca5a5 (border)  #fef2f2 (background)
 * Warning:   #d97706
 *
 * Responsive breakpoints (canonical scale — prefer these for new rules)
 *   768px — tablet / mobile-nav threshold (top nav collapses to hamburger; see nav.css)
 *   640px — phone-landscape / table→card transition (e.g. Mission Archive)
 *   480px — phone-portrait: stack two-column rows, drop address-box min-widths
 *   360px — small-phone padding trims
 * Existing pages also carry a few one-off breakpoints (900/720/540/520/420);
 * those are legacy and not worth churning, but new work should land on the
 * scale above so behaviour stays predictable across pages.
 */

* { margin: 0; padding: 0; box-sizing: border-box; }
img { height: auto; }
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

/* Tier-★ marker on the Email Group / Share nav links: a small star nav.js
   appends (keyed on the active group's capability) to flag a link whose page
   holds upgrade-locked teaser sections. The Email Group + Share links now show
   for every authenticated user (book-only reaches the teaser pages); the star
   is the only tier differentiator in the space-constrained nav. */
/* Circular star badge — mirrors the .tier-badge--pro palette (amber star on a
   light-yellow disc) so the nav marker reads like the on-page Pro tags.
   The star is a CENTERED SVG background-image of the disc, so the star and the
   circle belong to ONE element's raster and snap to the pixel grid together —
   they can never drift apart. (The old approach centered a ★ text glyph inside a
   flex box; the glyph rounded to the device-pixel grid independently of the disc,
   so depending on the badge's fractional x in the line the star landed up to 1px
   off-center — visibly different between the two nav teasers.)
   The disc is an atomic inline-block, so the active nav link's text-decoration
   underline SKIPS it (no line under the icon). `vertical-align: middle` + the
   1px top nudge keep the disc optically centered on the 14px nav text. */
.nav-tier-star {
  display: inline-block;
  width: 12px; height: 12px; margin-left: 4px;
  border-radius: 50%;
  background: #fef3c7 url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path fill="%2392400e" d="M6 1.7 7.01 4.61 10.09 4.67 7.64 6.53 8.53 9.48 6 7.72 3.47 9.48 4.36 6.53 1.91 4.67 4.99 4.61Z"/></svg>') center / 12px 12px no-repeat;
  vertical-align: middle;
  position: relative; top: -1px;
  text-decoration: none;
}

/* Tier upsell teaser (Standard Group). lockSection() in utils.js applies
   .pro-locked (grayed + inert) to a section and pins a .tier-badge to the far
   right of its header. Two kinds: a gold "Pro" pill (broadcast-only features) and
   a silver "Standard" + gold "Pro" pair (list-management features, unlocked by
   either add-on). Used by /pro/group-members, /dashboard, /pro/share, /add-content. */
/* Dim per direct child (skipping the branch that holds the tier badge) rather
   than on .pro-locked itself: opacity on the section would flatten the badge
   too — a child's opacity can't exceed its parent's — washing the Pro /
   Standard tags out. :has keeps the badge (and its header) full strength. */
.pro-locked { pointer-events: none; }
.pro-locked > *:not(:has(.tier-badge)) { opacity: 0.55; }
.pro-locked input, .pro-locked textarea, .pro-locked button, .pro-locked .btn-copy,
.pro-locked .btn-action, .pro-locked a { pointer-events: none; }
/* …but keep the teaser's NAVIGATION affordances live so a user without the
   feature can still go learn about / explore it: the "Go to …" action buttons
   (.btn-action) and the "how it works" / "manage" card-links (.card-link,
   .card-cta — e.g. the all-tier "Manage approved senders" CTA inside the
   Pro-locked Broadcast/Direct-Archive teasers). Clickable + full opacity (they
   read as enabled); the teaser's own controls — Copy buttons, inputs — stay
   disabled. Mirrors the .tier-badge--action re-enable below. Opacity override
   targets any direct child that IS or CONTAINS such a link, since some sit
   inside a wrapper div (so the dim rule above flattens the whole branch). */
.pro-locked .btn-action, .pro-locked .card-link, .pro-locked .card-cta { pointer-events: auto; cursor: pointer; }
.pro-locked > .btn-action, .pro-locked > .card-link, .pro-locked > .card-cta,
.pro-locked > *:has(.btn-action), .pro-locked > *:has(.card-link), .pro-locked > *:has(.card-cta) { opacity: 1; }
/* The re-enable above is for nav CTAs (anchors). A locked feature's OWN action
   buttons are disabled <button>s (e.g. /share's Post + Download Custom Flyer) —
   keep them inert so they don't read as clickable (no hover, no pointer). */
.pro-locked .btn-action:disabled { pointer-events: none; cursor: default; }
/* …but the preview thumbnails stay clickable so a no/standard-group user can
   still open the lightbox to see the full-size flyer / story image. */
.pro-locked #storyThumb, .pro-locked #printableThumb { pointer-events: auto; cursor: pointer; }
/* When the tier tag IS the upsell trigger (lockSection given an `upsell`), it
   renders as a <button>. Re-enable clicks inside the otherwise-inert teaser
   (must come after the .pro-locked pointer-events:none rules above) and strip
   native button chrome so it matches the span badge. */
.pro-locked .tier-badge--action { pointer-events: auto; }
.tier-badge--action { cursor: pointer; -webkit-appearance: none; appearance: none; border: 0; font-family: inherit; }
.tier-badge--action.tier-badge--standard { border: 1px solid #cbd5e1; }
.tier-badge--action.tier-badge--pro { border: 1px solid #fcd34d; }
.tier-badge--action:hover { filter: brightness(0.96); }
.tier-badge--action:focus-visible { outline: 2px solid #92400e; outline-offset: 1px; }
/* Leader "ask the owner" hint — informational, kept readable (full opacity)
   inside the dimmed teaser. Must come after the .pro-locked dim rule. */
.owner-hint { margin-top: 14px; color: #6b7280; font-size: 13px; font-style: italic; }
.pro-locked > .owner-hint { opacity: 1; }
/* Contextual Pro nudge beside the Standard owner's Copy Group button. */
.copy-list-nudge { display: inline-block; margin-top: 10px; background: none; border: none; padding: 0; color: #b45309; font-size: 13px; font-weight: 600; cursor: pointer; text-align: left; }
.copy-list-nudge:hover { text-decoration: underline; }

/* Add-a-book modal (multi-book). Injected + toggled via the `hidden` attribute
   by book-create.js; [hidden] must override the flex display. Mirrors the
   upsell-modal structure. */
.bc-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; }
.bc-modal[hidden] { display: none; }
.bc-modal__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }
.bc-modal__dialog { position: relative; background: #fff; border-radius: 10px; padding: 24px; max-width: 560px; width: calc(100% - 32px); max-height: calc(100vh - 32px); overflow-y: auto; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25); }
.bc-modal__close { position: absolute; top: 10px; right: 14px; background: none; border: none; font-size: 24px; line-height: 1; color: #9ca3af; cursor: pointer; }
.bc-modal__title { margin: 0 24px 0 0; font-size: 20px; }
.bc-modal__subtitle { color: #6b7280; font-size: 14px; margin-top: 4px; }
/* Two labeled sections mirroring /book/layout: "Printed book type & quality"
 * (the 4 Standard/Premium SKUs) and "Digital book type". */
.bc-section { margin-top: 16px; }
.bc-section__label { display: block; font-size: 13px; color: #374151; font-weight: 600; margin: 0 0 6px; }
.bc-modal__grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
/* Product cards mirror the Layout page's .ob-product-card chrome (2px border,
 * uppercase kind eyebrow, specs list, black-accented selection) so "Add a book"
 * reads as the same product picker the user meets again on /book/layout. */
.bc-card { position: relative; display: flex; flex-direction: column; gap: 6px; text-align: left; border: 2px solid #e5e7eb; border-radius: 8px; padding: 16px; background: #fff; cursor: pointer; transition: border-color 0.15s, background 0.15s; }
/* Re-declare the hover background: .bc-card is a <button>, so without this the
   global `button:hover { background:#333 }` leaks through and darkens the card.
   Exclude .is-selected so the chosen card keeps its #111 black-border emphasis. */
.bc-card:hover:not(.is-disabled):not(.is-selected) { border-color: #9ca3af; background: #fafafa; }
.bc-card.is-selected { border-color: #111; background: #f9fafb; }
.bc-card.is-disabled { opacity: 0.55; cursor: not-allowed; }
.bc-card__kind { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280; }
.bc-card.is-premium .bc-card__kind { color: #92400e; }
.bc-card__title { font-weight: 700; font-size: 15px; color: #111; }
.bc-card__price { font-size: 13px; font-weight: 700; color: #111; }
.bc-card__desc { font-size: 13px; color: #4b5563; line-height: 1.4; }
.bc-card__specs { margin: 0; font-size: 12px; line-height: 1.5; color: #4b5563; }
.bc-card__specs dt { display: inline; font-weight: 600; color: #6b7280; }
.bc-card__specs dd { display: inline; margin: 0 0 0 4px; }
.bc-card__specs dd::after { content: '\A'; white-space: pre; }
.bc-card__tag { position: absolute; top: 10px; right: 12px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #92400e; background: #fef3c7; padding: 2px 7px; border-radius: 999px; }
.bc-modal__actions { display: flex; align-items: center; gap: 10px; justify-content: flex-end; margin-top: 18px; }
.bc-modal__error { color: #b91c1c; font-size: 13px; margin-right: auto; }

/* Add-on upsell modal (dashboard). Toggled via the `hidden` attribute by
   upsell-modal.js; [hidden] must override the flex display. */
.upsell-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; }
.upsell-modal[hidden] { display: none; }
.upsell-modal__backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }
.upsell-modal__dialog { position: relative; background: #fff; border-radius: 10px; padding: 24px; max-width: 520px; width: calc(100% - 32px); max-height: calc(100vh - 32px); overflow-y: auto; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25); }
.upsell-modal__close { position: absolute; top: 10px; right: 14px; background: none; border: none; font-size: 24px; line-height: 1; color: #9ca3af; cursor: pointer; }
.upsell-modal__subtitle { color: #6b7280; font-size: 14px; margin-top: 4px; }
.upsell-modal__offers { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.upsell-modal__offer { width: 100%; }
/* In-modal Standard-vs-Pro comparison (upsell-modal.js renderComparison). Two
   columns for a book-only owner choosing a tier; one column ("what you gain with
   Pro") for a Standard owner or a Pro-only feature. Collapses to one column on
   narrow screens. */
.upsell-modal__compare { margin-top: 16px; }
.upsell-modal__compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.upsell-modal__compare-grid--single { grid-template-columns: 1fr; }
.upsell-modal__compare-col { position: relative; border: 1px solid #e5e7eb; border-radius: 8px; padding: 14px 12px; }
.upsell-modal__compare-col--popular { border-color: #f59e0b; }
.upsell-modal__compare-badge { position: absolute; top: -10px; right: 10px; background: #fef3c7; color: #92400e; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 8px; border-radius: 999px; }
.upsell-modal__compare-head { font-weight: 700; font-size: 15px; }
.upsell-modal__compare-tag { color: #6b7280; font-size: 12px; margin-top: 2px; }
.upsell-modal__compare-list { list-style: none; padding: 0; margin: 10px 0 0; }
.upsell-modal__compare-list li { font-size: 13px; line-height: 1.45; padding-left: 18px; position: relative; margin-bottom: 6px; }
.upsell-modal__compare-list li::before { content: '\2713'; position: absolute; left: 0; color: #16a34a; font-weight: 700; }
@media (max-width: 480px) { .upsell-modal__compare-grid { grid-template-columns: 1fr; } }
/* Transient confirmation toast (utils.js showToast). Hidden until the --show
   class is added; fades in/out. z-index above the upsell modal so a post-
   purchase toast is visible while the page reloads. */
.mml-toast { position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%) translateY(12px); z-index: 1100; background: #111827; color: #fff; font-size: 14px; font-weight: 600; padding: 12px 18px; border-radius: 999px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); opacity: 0; pointer-events: none; transition: opacity 0.2s ease, transform 0.2s ease; max-width: calc(100% - 32px); text-align: center; }
.mml-toast--show { opacity: 1; transform: translateX(-50%) translateY(0); }
.tier-badge {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; padding: 3px 8px; border-radius: 999px;
  white-space: nowrap; vertical-align: middle; line-height: 1;
}
/* Inline-in-running-text badges (e.g. dashboard/add-content section copy):
   drop the pill slightly so it sits near the text's optical center. middle
   reads high next to mixed-case body text; the earlier -0.26em overcorrected
   and read low, so -0.12em splits the difference. */
.section-desc .tier-badge { vertical-align: -0.12em; }
.tier-badge--pro { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
.tier-badge--standard { background: #eef2f7; color: #475569; border: 1px solid #cbd5e1; }
/* Header row holding an <h2>/<h3> with its .tier-badge trailing just to the
   right of the heading text (not pinned to the far edge of the row).
   line-height: 1 collapses the heading's leading so the (descender-free)
   heading text sits at the line-box center — without it the default ~1.33
   leading floats the text high in the box and align-items:center drops the
   small badge visibly low next to it. */
/* flex-wrap so the trailing badge drops below the heading on very narrow
   viewports (e.g. a long /share section title at 320px) instead of forcing
   horizontal overflow. row-gap keeps the wrapped badge tight to the heading. */
.tier-locked-header { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: 6px 12px; line-height: 1; }
/* align-items:center centers the badge on the heading's line box, but the box
   carries ~0.07em of ascender leading above the caps, so the badge floats
   slightly high above the (descender-free) heading text. Nudge it down to the
   caps' optical center. */
.tier-locked-header .tier-badge { transform: translateY(0.03em); }
/* A "works on Standard AND Pro" feature shows two pills (silver Standard +
   gold Pro) wrapped in a group; keep them snug and vertically aligned as one
   inline unit. */
.tier-badge-group { display: inline-flex; align-items: center; gap: 6px; }
.section-desc .tier-badge-group { vertical-align: -0.12em; }

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: #fff;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.main { flex: 1; padding: 48px 20px; }
.main--centered { display: flex; align-items: flex-start; justify-content: center; }

.container {
  background: white;
  padding: 48px;
  border-radius: 12px;
  border: 1px solid #e5e7eb;
  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
}
.container--centered { text-align: center; }
.container--narrow { max-width: 480px; }

h1 { font-size: 26px; color: #111; font-weight: 800; margin-bottom: 32px; }
h1.tight { margin-bottom: 8px; }
h2 { font-size: 17px; color: #111; font-weight: 700; margin-bottom: 16px; }
p { color: #555; margin-bottom: 24px; line-height: 1.5; }

.field-label { display: block; text-align: left; font-size: 14px; font-weight: 600; color: #374151; margin-bottom: 8px; }
.field-label--top { margin-top: 16px; }
.field-hint { display: block; text-align: left; font-size: 14px; color: #6b7280; margin-top: -4px; margin-bottom: 16px; padding-left: 16px; }
.field-error { color: #dc2626; font-size: 14px; display: block; margin-top: -4px; margin-bottom: 4px; }
.field-error:empty { margin: 0; }

input, textarea {
  width: 100%;
  padding: 14px 16px;
  border: 1.5px solid #d1d5db;
  border-radius: 8px;
  font-size: 16px;
  margin-bottom: 16px;
  outline: none;
  font-family: inherit;
  transition: border-color 0.2s;
}
input:focus, textarea:focus { border-color: #111; }
.form-tight input, .form-tight textarea { margin-bottom: 6px; }
input[readonly] { background: #f9fafb; color: #374151; cursor: default; border-color: #e5e7eb; }
input[readonly]:focus { border-color: #e5e7eb; }
input::placeholder { color: #9ca3af; }
input.invalid { border-color: #dc2626; }

button {
  padding: 12px 24px;
  background: #111;
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}
button:hover { background: #333; }
button:focus-visible { outline: 2px solid #111; outline-offset: 2px; }
button:disabled { background: #888; cursor: not-allowed; }

.btn-group { display: flex; gap: 8px; flex-wrap: wrap; }
.section { margin-bottom: 32px; }
.section:last-child { margin-bottom: 0; }
.section-title { font-size: 17px; font-weight: 700; color: #111; margin-bottom: 16px; }
.subtitle { font-size: 15px; color: #555; line-height: 1.5; margin-bottom: 32px; }
.section-desc { font-size: 14px; color: #555; margin-bottom: 12px; line-height: 1.5; }
.divider { border: none; border-top: 1px solid #e5e7eb; margin: 32px 0; }
/* When a .divider introduces a preview block (the content separator above the
 * previews on /book/cover + /book/title-pages), let the divider's own 32px
 * margin set the gap — zero the preview's top margin so they don't double up. */
.divider + .cover-previews-row,
.divider + .cover-preview,
.divider + .tp-preview-card { margin-top: 0; }
.back-link { display: inline-block; padding: 12px 0; margin-bottom: 12px; color: #555; text-decoration: none; font-size: 14px; }
.back-link::before { content: ''; display: inline-block; width: 6px; height: 6px; border-left: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg); margin-right: 6px; vertical-align: middle; position: relative; top: -1px; }
.muted-link { font-size: 14px; color: #6b7280; text-decoration: none; font-weight: 500; background: none; border: none; padding: 0; font-family: inherit; cursor: pointer; }
button.muted-link:hover { background: none; color: #111; }
.reset-link { font-size: 14px; color: #6b7280; text-decoration: none; background: none; border: none; padding: 0; font-family: inherit; cursor: pointer; }
.reset-link:hover { text-decoration: underline; background: none; }
.btn-text { background: none; border: none; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; text-align: inherit; text-decoration: none; }
.btn-text:hover { background: none; text-decoration: underline; }
.signup-link { font-size: 14px; color: #555; margin-bottom: 0; }
.signup-link a { color: #111; font-weight: 600; text-decoration: none; }
.signup-link a:visited { color: #111; }
.signup-link a:hover { text-decoration: underline; }
.howitworks-link, .howitworks-link:visited { color: #111; font-weight: 600; text-decoration: none; }
.howitworks-link:hover { text-decoration: underline; }
/* Section navigation / "how it works" text links (dashboard cards). Promoted
   here from dashboard's inline style so the class is fully defined where its
   .pro-locked clickability rules already live (near the top of this file). */
.card-link, .card-link:visited { display: inline-block; margin-top: 12px; font-size: 13px; font-weight: 600; color: #111; text-decoration: none; }
.card-link:hover { text-decoration: underline; }
/* Bordered icon CTA used inside cards/sections: the upload how-to cards on
   /add-content and the "Manage approved senders" CTAs on /add-content +
   /pro/group-members. Promoted from /add-content's inline style so it's reusable;
   kept live + full-opacity inside .pro-locked teasers via the rules near the
   top of this file (the group-members CTAs sit in the Pro-locked Broadcast/
   Direct-Archive blocks but approved senders is all-tier). */
.card-cta, .card-cta:visited {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 16px;
  background: #fff; color: #374151; border: 1px solid #d1d5db; border-radius: 6px;
  padding: 6px 14px; font-size: 14px; font-weight: 500; line-height: 1; text-decoration: none;
}
.card-cta:hover { background: #f9fafb; color: #111; }
.card-cta svg { display: block; flex: 0 0 auto; }
/* Disabled card-cta <button> (e.g. "Send test broadcast" inside a Pro-locked
   Broadcast teaser for a non-Pro list — lockSection disables it): faded text +
   border instead of the global button:disabled #888 box, and no hover shift. */
.card-cta:disabled, .card-cta:disabled:hover { background: #fff; color: #9ca3af; border-color: #e5e7eb; cursor: default; }
.info-msg { background: #f0fdf4; border: 1px solid #86efac; border-radius: 8px; padding: 12px 16px; font-size: 14px; color: #16a34a; margin-bottom: 16px; line-height: 1.5; }
.back-link:visited { color: #555; }
.back-link:hover { color: #111; }
.btn-action { display: inline-block; padding: 12px 24px; background: #111; color: white; border-radius: 8px; font-size: 15px; font-weight: 600; text-decoration: none; transition: background 0.2s; }
.btn-action:visited { color: white; }
.btn-action:hover { background: #333; }
/* Save / Cancel button pair on the book editor save points (layout, settings,
   cover, title pages): both start disabled and enable on the first edit in
   their section (2026-06-22 — replaced the "Unsaved changes" hint text + the
   "Reset to last saved values" link). .btn-action carries its own :hover, so an
   explicit disabled rule is needed or a disabled Save would darken on hover.
   The btn-link Cancel is muted when disabled (the gray button:disabled box
   doesn't apply to btn-link). */
.btn-action:disabled, .btn-action:disabled:hover { background: #888; cursor: not-allowed; }
.btn-link.btn-cancel-changes:disabled { color: #9ca3af; cursor: default; }
.btn-outline { display: inline-block; padding: 8px 16px; background: white; color: #111; border: 1.5px solid #111; border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none; transition: background 0.2s; }
.btn-outline:hover { background: #f4f4f4; }
.btn-edit { background: #6b7280; }
.btn-edit:hover { background: #374151; }
.btn-danger { background: #dc2626; color: white; }
.btn-danger:hover { background: #b91c1c; }
.btn-danger:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-outline-danger { background: white; color: #dc2626; border: 1.5px solid #dc2626; }
.btn-outline-danger:hover { background: #fef2f2; }

/* Approved-senders control — SHARED by /pro/group-members and /add-content
   (the same control over the same data). The section header matches whatever
   heading level the host page uses (h2 on group-members, h3 on add-content). */
.sender-section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.sender-section-header h1, .sender-section-header h2, .sender-section-header h3 { margin-bottom: 0; }
.sender-row { display: flex; gap: 8px; align-items: stretch; margin-bottom: 8px; }
.sender-row .btn-outline-danger { display: flex; align-items: center; min-height: 44px; }
.sender-row .sender-item { flex: 1; margin-bottom: 0; }
.sender-item { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 14px 16px; border: 1px solid #e5e7eb; border-radius: 8px; margin-bottom: 8px; font-size: 14px; color: #111; }
/* Let a long sender email shrink + wrap instead of pushing the badge past the
   viewport (caught by the responsive overflow guard at 320px). */
.sender-item > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.sender-item .sender-badge { flex-shrink: 0; }
.sender-badge { font-size: 14px; background: #f4f4f4; color: #6b7280; border-radius: 4px; padding: 2px 8px; }
.sender-add { display: flex; gap: 8px; margin-top: 10px; }
.sender-add input { margin-bottom: 0; flex: 1; font-size: 16px; padding: 10px 14px; }
.sender-add button { white-space: nowrap; padding: 10px 18px; font-size: 14px; }
.sender-toggle { background: none; border: none; color: #555; font-size: 14px; font-weight: 600; cursor: pointer; padding: 4px 0; margin-top: 8px; }
.sender-toggle:hover { color: #111; background: none; }
/* Shrink + center the approved-sender fields and the add/toggle controls so
   the list reads as a contained, centered group on /approved-senders.
   margin-left/right (not shorthand) preserves each rule's top gap. */
#approvedSendersList,
#approvedSendersBlock .sender-add { max-width: 420px; margin-left: auto; margin-right: auto; }
/* Breathing room between the section description and the first sender row. */
#approvedSendersList { margin-top: 24px; }
#approvedSendersBlock .sender-toggle { display: block; width: fit-content; margin-left: auto; margin-right: auto; }
.hint { font-size: 14px; color: #6b7280; margin-top: 8px; line-height: 1.5; }

.error { color: #dc2626; font-size: 14px; margin-top: 8px; }
.success { color: #16a34a; font-size: 14px; margin-top: 8px; }
p.error:empty, p.success:empty { display: none; }

.result {
  margin-top: 20px;
  padding: 16px;
  border-radius: 8px;
  display: none;
  font-size: 14px;
  line-height: 1.5;
  text-align: left;
}
.result.success { background: #f0fdf4; border: 1px solid #86efac; color: #16a34a; }
.result.success a { color: #16a34a; font-weight: 600; }
.result.success a:visited { color: #16a34a; }
.result.error { background: #fef2f2; border: 1px solid #fca5a5; color: #dc2626; }

/* Home newsletter signup band (sits between §6 and the final dark CTA). */
.newsletter-band { background: #fff; border-top: 1px solid #e5e7eb; padding: 56px 20px; }
.newsletter-inner { max-width: 640px; margin: 0 auto; text-align: center; }
.newsletter-title { font-size: 28px; font-weight: 800; color: #111; margin: 0 0 8px; }
.newsletter-sub { color: #555; font-size: 16px; margin: 0 0 24px; }
.newsletter-form { display: flex; gap: 10px; justify-content: center; align-items: center; flex-wrap: wrap; }
/* margin:0 cancels the global `input{margin-bottom:16px}` — in this
   align-items:center flex row that phantom margin centers the input's margin
   box, shoving its visible box up so the button looks taller and overhangs. */
.newsletter-input { flex: 1 1 280px; max-width: 360px; margin: 0; padding: 12px 14px; font-size: 15px; font-family: inherit; border: 1px solid #d1d5db; border-radius: 8px; }
.newsletter-btn { white-space: nowrap; cursor: pointer; border: none; font-family: inherit; }
/* Honeypot: off-screen + zero-size, hidden from humans; aria-hidden hides it
   from screen readers. NOT display:none (some bots skip those) and NOT
   .sr-only (that exposes it to assistive tech). */
.newsletter-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.newsletter-fineprint { color: #888; font-size: 13px; margin: 16px 0 0; }
.newsletter-fineprint a { color: #888; text-decoration: underline; }
#newsletterResult { margin: 16px auto 0; max-width: 360px; }
.badge-success { display: inline-block; background: #f0fdf4; color: #16a34a; border-radius: 4px; padding: 2px 8px; font-size: 12px; font-weight: 600; }
.badge-failed { display: inline-block; background: #fef2f2; color: #dc2626; border-radius: 4px; padding: 2px 8px; font-size: 12px; font-weight: 600; }

/* ── Skeleton loading ───────────────────────────────────────────────────────── */
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skeleton {
  background: linear-gradient(90deg, #f4f4f4 25%, #e5e7eb 50%, #f4f4f4 75%) !important;
  background-size: 200% 100% !important;
  animation: shimmer 1.5s infinite linear;
  color: transparent !important;
  pointer-events: none !important;
  cursor: default !important;
  border-color: #e5e7eb !important;
}
.skeleton::after { display: none !important; }
.skeleton * { color: transparent !important; }
/* Respect prefers-reduced-motion: freeze the shimmer to a static tint so the
   placeholder still reads as "loading" without the continuous animation. */
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none !important; background: #e9eaec !important; }
}

/* Empty skeleton placeholders for first-paint loading states.
   Pair with .skeleton for the shimmer animation. Pages add their own
   width/height inline where shape needs to differ from defaults. */
.skel-line { display: block; height: 14px; border-radius: 4px; margin-bottom: 8px; }
.skel-line--sm { height: 11px; }
.skel-line--lg { height: 22px; }
.skel-line--xl { height: 32px; margin-bottom: 16px; }
.skel-block { display: block; border-radius: 8px; margin-bottom: 16px; }

@media (max-width: 600px) { .container { padding: 32px; } .main { padding: 32px 16px; } }
@media (max-width: 480px) { .container { padding: 24px; } .main { padding: 32px 20px; } }
@media (max-width: 360px) { .container { padding: 16px; } .main { padding: 24px 12px; } }

/* ── Checkbox label ─────────────────────────────────────────────────────────── */
.checkbox-label { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 500; color: #374151; cursor: pointer; min-height: 44px; }
.checkbox-label input[type="checkbox"] { width: auto; margin: 0; accent-color: #111; cursor: pointer; }
.checkbox-label--right { width: fit-content; margin-left: auto; margin-bottom: 20px; }
.checkbox-label--center { width: fit-content; margin-left: auto; margin-right: auto; margin-bottom: 20px; }

/* ── Pagination ─────────────────────────────────────────────────────────────── */
.lookup-section { margin-bottom: 32px; }
.empty { color: #9ca3af; font-style: italic; text-align: center; padding: 32px 0; }
.page-info { color: #6b7280; font-size: 14px; }
.pagination { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; gap: 8px; }
.pagination button { padding: 12px 16px; font-size: 14px; font-weight: 500; width: auto; min-height: 44px; }
.pagination button:disabled { background: #e5e7eb; color: #9ca3af; cursor: not-allowed; }

/* ── Copy components ────────────────────────────────────────────────────────── */
.address-box {
  position: relative;
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 12px 16px;
  /* Cap copyable address fields at a uniform, tidy width site-wide instead of
     stretching to fill their row; longer addresses wrap inside the box. */
  max-width: 400px;
  font-weight: 600;
  color: #111;
  overflow-wrap: anywhere;
  font-size: 14px;
  cursor: pointer;
  user-select: none;
  overflow: visible;
  transition: border-color 0.15s, background 0.15s;
}
.address-box:hover { border-color: #888; background: #f4f4f4; }
.broadcast-row { display: flex; gap: 8px; align-items: stretch; flex-wrap: wrap; }
.broadcast-row .address-box { flex: 1; min-width: 0; margin-bottom: 0; }
.url-text {
  flex: 1;
  min-width: 0;
  position: relative;
  background: #f9fafb;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 12px 16px;
  font-size: 14px;
  color: #111;
  overflow-wrap: anywhere;
  line-height: 1.4;
  cursor: pointer;
  user-select: none;
  overflow: visible;
  transition: border-color 0.15s, background 0.15s;
}
.url-text:hover { border-color: #888; background: #f4f4f4; }
.address-box::after,
.url-text::after,
.qr-wrapper::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: #111;
  color: white;
  padding: 4px 10px;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s;
}
.address-box:hover::after,
.url-text:hover::after,
.qr-wrapper:hover::after { opacity: 1; }
.address-box.copied::after,
.url-text.copied::after,
.qr-wrapper.copied::after { background: #16a34a; }
.btn-copy { font-size: 15px; padding: 12px 16px; white-space: nowrap; }
.btn-copy.copied { background: #16a34a; }
.qr-wrapper { position: relative; display: inline-block; cursor: pointer; border-radius: 8px; flex-shrink: 0; transition: opacity 0.15s; }
.qr-wrapper:hover { opacity: 0.85; }

@media (max-width: 480px) {
  .broadcast-row .address-box { min-width: 100%; }
}

/* ── Contact / Support form ─────────────────────────────────────────────────── */
.form-contact input, .form-contact textarea { margin-bottom: 20px; }
.form-contact textarea { resize: vertical; min-height: 120px; }
.form-contact button { min-width: 200px; display: block; margin: 8px auto 0; }
.form-contact .result { margin-top: 24px; padding: 20px; }

/* ── FAQ content ────────────────────────────────────────────────────────────── */
.faq-item { margin-bottom: 28px; }
.faq-item:last-child { margin-bottom: 0; }
.faq-q { font-size: 15px; font-weight: 700; color: #111; margin-bottom: 6px; }
.faq-a { font-size: 15px; color: #555; line-height: 1.65; }
p a { color: #111; font-weight: 600; }
p a:visited { color: #111; }
.faq-a a { color: #111; font-weight: 600; }
.faq-a a:visited { color: #111; }
.faq-a a:hover { text-decoration: underline; }

/* ── Pro tier pill ──────────────────────────────────────────────────────────── */
/* Small green badge marking a Pro-tier surface (Pro-only sections, page titles
   on Pro lists). Inline-block so it sits inside an h1/h2 next to the title text. */
.pro-pill {
  display: inline-block;
  background: #16a34a;
  color: white;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 3px 10px;
  border-radius: 12px;
  vertical-align: middle;
  margin-left: 8px;
  position: relative;
  top: -2px;
}

/* ── Coming Soon pill ───────────────────────────────────────────────────────── */
/* Neutral counterpart to .pro-pill for future-availability hints. Pair with
   .coming-soon-note when the accompanying copy may wrap, so the text doesn't
   slide under the pill on the second line. */
.coming-soon-pill {
  display: inline-block;
  background: #eef2ff;
  color: #4338ca;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 3px 10px;
  border-radius: 12px;
  vertical-align: middle;
  margin-right: 6px;
}
.coming-soon-note {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 12px;
  margin-bottom: 16px;
}
.coming-soon-note .coming-soon-pill {
  flex-shrink: 0;
  margin-right: 0;
  margin-top: 1px;
}
.coming-soon-note p {
  margin: 0;
  font-size: 13px;
  color: #6b7280;
}

/* ── Beta pill ──────────────────────────────────────────────────────────────── */
/* ── Cancelled-list banner ──────────────────────────────────────────────────── */
/* Shown above <main> on authenticated pages when the active list has
   is_active=false. Amber tone so it reads as an inert state notice rather
   than a destructive error. Paired with cancelled-banner.js, which also
   disables form inputs and submit buttons inside <main> (opt-out via the
   .allow-when-cancelled class — used by Delete Account, broadcast PDF
   downloads, and any read-only navigation buttons). */
.cancelled-banner {
  background: #fef3c7;
  color: #78350f;
  border-bottom: 1px solid #fde68a;
  padding: 14px 24px;
  text-align: center;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.5;
}
.cancelled-banner strong { font-weight: 700; }
.cancelled-banner a {
  color: #78350f;
  text-decoration: underline;
  font-weight: 700;
  margin-left: 6px;
}
.cancelled-banner a:visited { color: #78350f; }
/* Disabled-state styling for form fields and buttons inside <main> when
   cancelled-banner.js has flipped them. Keeps them visible but obviously
   inactive — most browsers gray out the chrome already; this just nudges
   the cursor/opacity to match. */
.cancelled-banner ~ main input:disabled,
.cancelled-banner ~ main select:disabled,
.cancelled-banner ~ main textarea:disabled,
.cancelled-banner ~ main button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

/* ── Proof-mode lock banner ─────────────────────────────────────────────────── */
/* Shown by proof-lock.js above <main> when the active book (book-config pages)
   or any book on the list (mission-archive pages) is in proof. Sticky so it
   stays visible while the customer scrolls the (now read-only) page. Blue tone
   to read as an informational "paused" state distinct from the amber cancelled
   banner and red errors. Paired with .proof-locked, which inert-s + dims the
   page's [data-proof-lock] content region. */
.proof-lock-banner {
  position: sticky;
  top: 0;
  z-index: 50;
  background: #e0f2fe;
  color: #075985;
  border-bottom: 1px solid #bae6fd;
  padding: 14px 24px;
  text-align: center;
  font-size: 15px;
  line-height: 1.5;
  font-weight: 600;
}
.proof-lock-banner strong { font-weight: 700; display: block; }
.proof-lock-banner em { font-style: normal; opacity: 0.8; }
.proof-lock-banner-link {
  display: inline-block;
  margin-top: 6px;
  color: #075985;
  font-weight: 700;
  text-decoration: underline;
  white-space: nowrap;
}
/* The locked content region: inert already removes interaction + focus; this
   just signals "read-only" visually (dim + not-allowed cursor). Kept off the
   book-picker, which sits outside [data-proof-lock] so book-switching stays
   live. */
.proof-locked {
  opacity: 0.6;
  pointer-events: none;
  cursor: not-allowed;
  user-select: none;
}

/* Twemoji <img class="emoji"> tags inserted by the server-side
 * replaceEmojiWithImg pass. Sized to surrounding text via 1em height,
 * baseline-aligned. Matches the book pipeline rule in
 * lib/book/profiles/book-base.css so web + print render identically.
 *
 * Selector uses `.emoji.emoji` (doubled class) on purpose: it bumps
 * the specificity to (0,2,1) so any page-level inline <style> rule
 * shaped `<scope> img { width/height: ... }` (specificity 0,1,1)
 * cannot clobber it via source-order. Without the bump, a rule like
 * `.email-body-card img { height: auto }` rendered emoji SVGs at
 * their natural 36-72px size. The book pipeline solves this with
 * !important at book-base.css:171; the web side uses the specificity
 * trick instead, which is reachable without sledgehammering everything
 * via !important. */
img.emoji.emoji {
  height: 1em;
  width: auto;
  vertical-align: -0.1em;
  display: inline-block;
  margin: 0 0.05em;
}

/* BETA pill — used on /book/* surfaces that haven't reached GA yet
 * (Book Structure, Cover, Title Pages, Print). Small uppercase pill in
 * a muted neutral palette so it reads as metadata next to an H1 or
 * sub-nav tab, not as competing UI. Removal is mechanical: drop the
 * <span class="beta-tag">BETA</span> markup at each site (per the
 * post-launch BETA-removal operation in docs/book-restructure-plan.md). */
.beta-tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #6b7280;
  background: #f3f4f6;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  padding: 3px 6px;
  margin-left: 8px;
  vertical-align: middle;
}

/* OPTIONAL pill — same visual vocabulary as .beta-tag, used on the 4
 * non-gated cards inside /book/title-pages (Parents' Message, Mission
 * Call, Area Maps, LOA) to signal that those items are not required to
 * order. Title Page does NOT carry the tag (it's the order-gated
 * singleton). Round 16 UX polish — reduces "completion theater". */
.optional-tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #6b7280;
  background: #f3f4f6;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  padding: 3px 6px;
  margin-left: 8px;
  vertical-align: middle;
}

/* /book/* sub-nav (docs/book-subnav-redesign-plan.md; supersedes the
 * original docs/book-restructure-plan.md Phase A 5-tab strip). Rendered
 * on every /book/* page by index.js's static-page middleware between
 * <!-- AUTH-NAV --> and <main> as a full-width row mirroring AUTH_NAV's
 * 0 32px horizontal padding. 5 equal-weight tabs (Add Content / Cover /
 * Title Pages / Mission Archive / Print) center-grouped within the strip.
 * Active tab is signalled by `<body data-book-tab="...">` + CSS sibling
 * selectors; no JS.
 *
 * Visual treatment (Pick B, 2026-06-05): the strip gets a `#f9fafb`
 * background band so the sub-nav reads as a distinct header region, and
 * the active tab gets a white background + 3px black underline — much
 * stronger "you are here" signal than the previous color-only treatment
 * (round-3 user feedback: prior active state was invisible on /book/print
 * specifically, and the whole strip was too quiet against the page below).
 *
 * Layout: the row centers the [picker + tab nav] group with
 * justify-content:center, and the optional book picker is glued to the LEFT of
 * the first tab (Add Content) — mirroring how the main nav's list switcher sits
 * just left of its links. With a single book the picker is hidden, so only the
 * nav remains and its tabs stay centered exactly as before. The nav sizes to
 * content but may shrink (flex:0 1 auto + min-width:0) so on narrow screens the
 * group fills the bar and the tabs scroll inside the nav (overflow-x), instead
 * of the whole centered group sliding off-screen. The picker stays a SIBLING of
 * the nav (not inside it) so the nav's overflow-x scroll never clips the
 * picker's absolutely-positioned dropdown. */
/* Row wrapping the optional book picker (left) + the centered tab nav. The
 * bar's background/border/margin live on the row so they span the full width.
 * See lib/head.js BOOK_SUBNAV. */
.book-subnav-row {
  display: flex;
  justify-content: center;
  align-items: stretch;
  /* Match the inter-tab gap (.book-subnav gap below) so the picker sits the
   * same distance from Add Content as the tabs sit from each other. */
  gap: 4px;
  padding: 0 16px;
  background: #f9fafb;
  border-bottom: 1px solid #e5e7eb;
  margin: 0 0 24px 0;
}
.book-subnav {
  display: flex;
  gap: 4px;
  /* Bar height + vertical-centering of the tabs. Previously inherited from the
     global `nav { height:108px; align-items:center }` in nav.css; that rule is
     now scoped to the classless chrome nav (nav:not([class])), so this <nav>
     sets them explicitly. Heights mirror the chrome nav's responsive steps
     (108 / 80 / 64) so the sub-bar stays the same height as before. */
  height: 108px;
  align-items: center;
  /* Size to content but allow shrink+scroll: keeps the group centered on
   * desktop and lets the tabs scroll within the bar on narrow screens. */
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  /* The bar color lives on .book-subnav-row. This inner element is a <nav>,
   * so it must explicitly opt OUT of the global `nav { background:#111 }`
   * rule (nav.css) — otherwise the inner nav paints itself black and covers
   * the row's #f9fafb, turning the whole strip black. */
  background: transparent;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.book-subnav a {
  flex: 0 0 auto;
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 500;
  color: #6b7280;
  text-decoration: none;
  border-bottom: 3px solid transparent;
  white-space: nowrap;
}
.book-subnav a:hover { color: #111; }
.book-subnav a:visited { color: #6b7280; }
body[data-book-tab="layout"] .book-subnav a[data-tab="layout"],
body[data-book-tab="cover"] .book-subnav a[data-tab="cover"],
body[data-book-tab="title-pages"] .book-subnav a[data-tab="title-pages"],
body[data-book-tab="settings"] .book-subnav a[data-tab="settings"],
body[data-book-tab="print"] .book-subnav a[data-tab="print"],
body[data-book-tab="manage-books"] .book-subnav a[data-tab="manage-books"] {
  color: #111;
  font-weight: 600;
  border-bottom-color: #111;
  background: #fff;
}
/* Dirty dot — same loss-AWARENESS cue as the Cover tertiary tabs
   (.cover-tab-dot): the active page's JS toggles .is-dirty on its own sub-nav
   tab while the page has unsaved edits, clearing it on Save/Cancel. Rendered as
   a pseudo-element so the shared server markup (lib/head.js) stays untouched. */
.book-subnav a.is-dirty::after {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 6px;
  border-radius: 50%;
  background: #d97706;
  vertical-align: middle;
}
/* Responsive bar height — mirrors the chrome nav steps in nav.css (now that the
   sub-nav sets its own height; see .book-subnav above). */
@media (max-width: 900px) { .book-subnav { height: 80px; } }
@media (max-width: 768px) {
  .book-subnav {
    height: 64px;
    /* Scroll cue: all 6 tabs overflow the bar on phones (they scroll via the
       overflow-x:auto above). Fade the right edge so it's visually obvious
       that more tabs (Print / Manage) exist past the viewport, instead of the
       strip looking like it simply ends at whatever tab is last on screen. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
}
@media (max-width: 520px) {
  .book-subnav-row { padding: 0 12px; }
}

/* Operator MARKETING admin sub-nav (lib/head.js ADMIN_MARKETING_SUBNAV).
 * Same structure/behavior as the /book/* sub-nav above: a centered tab strip
 * whose active tab is signalled by `<body data-admin-tab="...">`. Sets the
 * separate marketing tools (Attribution/ROAS, Affiliates, future) into one
 * space. Shorter bar than the book sub-nav — these pages have no picker. */
.admin-subnav-row {
  display: flex;
  justify-content: center;
  align-items: stretch;
  gap: 4px;
  padding: 0 16px;
  background: #f9fafb;
  border-bottom: 1px solid #e5e7eb;
  margin: 0 0 24px 0;
}
.admin-subnav {
  display: flex;
  gap: 4px;
  height: 60px;
  align-items: center;
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  /* Opt out of the global `nav { background:#111 }` (nav.css) like .book-subnav. */
  background: transparent;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.admin-subnav a {
  flex: 0 0 auto;
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 500;
  color: #6b7280;
  text-decoration: none;
  border-bottom: 3px solid transparent;
  white-space: nowrap;
}
.admin-subnav a:hover { color: #111; }
.admin-subnav a:visited { color: #6b7280; }
body[data-admin-tab="attribution"] .admin-subnav a[data-tab="attribution"],
body[data-admin-tab="affiliates"] .admin-subnav a[data-tab="affiliates"],
body[data-admin-tab="book-orders"] .admin-subnav a[data-tab="book-orders"],
body[data-admin-tab="quote"] .admin-subnav a[data-tab="quote"],
body[data-admin-tab="synthesize"] .admin-subnav a[data-tab="synthesize"] {
  color: #111;
  font-weight: 600;
  border-bottom-color: #111;
  background: #fff;
}

/* Book picker (left of the sub-nav tabs). Mirrors the nav list-switcher
 * (public/css/nav.css) but styled for the light sub-nav bar. Hidden until
 * public/js/book-nav.js reveals it (list has >1 book). */
/* align-self:center so the picker sizes to the button (not the stretched row
 * height) — otherwise the absolutely-positioned dropdown anchors to the bottom
 * of the full-height row and floats with a gap instead of sitting snug under
 * the button. The button stays vertically centered in the row either way. */
.book-picker { position: relative; display: flex; align-items: center; flex: 0 0 auto; align-self: center; }
.book-picker[hidden] { display: none; }
.book-picker-btn {
  background: #fff;
  border: 1px solid #e5e7eb;
  color: #111;
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  max-width: 220px;
  font-family: inherit;
}
.book-picker-btn:hover { background: #f3f4f6; }
.book-picker-btn:focus-visible { outline: 2px solid #111; outline-offset: 2px; }
#bookPickerName { overflow: hidden; text-overflow: ellipsis; }
.book-picker-chevron { display: inline-block; width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid currentColor; opacity: 0.7; flex-shrink: 0; margin-top: 1px; }
.book-picker-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
  min-width: 240px;
  z-index: 200;
  overflow: hidden;
}
.book-picker-dropdown.open { display: block; }
.book-picker-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  font-size: 14px;
  color: #111;
  cursor: pointer;
  border-bottom: 1px solid #e5e7eb;
  gap: 12px;
}
.book-picker-item:last-child { border-bottom: none; }
.book-picker-item:hover { background: #f9fafb; }
.book-picker-item:focus-visible { outline: 2px solid #111; outline-offset: -2px; }
.book-picker-item-name { font-weight: 500; flex: 1; }
.book-picker-item.active .book-picker-item-name { font-weight: 700; }
.book-picker-check { color: #111; font-size: 14px; }
.book-picker-dropdown .book-picker-manage {
  display: block;
  padding: 10px 16px;
  font-size: 14px;
  font-weight: 600;
  color: #6b7280;
  text-decoration: none;
  border-top: 1px solid #e5e7eb;
  background: #f9fafb;
}
.book-picker-dropdown .book-picker-manage:hover { background: #f3f4f6; color: #111; }

/* Heading variant — the picker rendered ABOVE the page <h1> (per-book settings
 * pages). A left-aligned block whose button reads like a small heading; the
 * dropdown still anchors under the button. */
.book-picker--heading {
  align-self: flex-start;
  margin: 0 0 20px;
}
/* Extra breathing room below the picker on the per-book editing pages that
 * lead straight into an <h1> (Layout / Cover / Title Pages / Settings / Print).
 * Mission Archive is deliberately excluded — its picker is immediately followed
 * by the photo-quality notice, which supplies its own spacing. */
body[data-book-tab="layout"] .book-picker--heading,
body[data-book-tab="cover"] .book-picker--heading,
body[data-book-tab="title-pages"] .book-picker--heading,
body[data-book-tab="settings"] .book-picker--heading,
body[data-book-tab="print"] .book-picker--heading {
  margin-bottom: 36px;
}
.book-picker--heading .book-picker-btn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 4px 10px 4px 4px;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.01em;
  /* Cap at the container width + allow wrapping so a long book label doesn't
   * overflow off the right edge on narrow screens (it can't ellipsize at this
   * size — a heading reads fine wrapped). */
  max-width: 100%;
  white-space: normal;
  text-align: left;
  gap: 8px;
}
.book-picker--heading .book-picker-btn:hover { background: #f3f4f6; border-color: #e5e7eb; }
.book-picker--heading .book-picker-chevron {
  border-left-width: 5px;
  border-right-width: 5px;
  border-top-width: 7px;
}

/* Static single-book heading — the title is shown but the picker is inert
 * (one book = no choice to make). Kill the hover/cursor affordances so it reads
 * as a heading, not a menu button. The chevron is hidden inline by
 * public/js/book-nav.js#renderStaticTitle. */
.book-picker--static .book-picker-btn { cursor: default; pointer-events: none; }
.book-picker--static .book-picker-btn:hover { background: transparent; border-color: transparent; }

/* /book/title-pages 5-card UI (docs/book-restructure-plan.md Phase B).
 * Vertical stack of cards mirroring the printed-book front-matter order.
 * Same visual vocabulary as .structure-card for cross-page consistency. */
.tp-card {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  padding: 24px 24px 20px;
  margin: 0 0 16px;
}
.tp-card header { margin-bottom: 16px; }
.tp-card h2 {
  margin: 0 0 4px;
  font-size: 18px;
  font-weight: 700;
  color: #111;
}
.tp-card .section-desc {
  margin: 0;
  font-size: 14px;
  color: #555;
}
.tp-card-body { display: block; }
.tp-card .empty {
  margin: 0 0 12px;
  font-size: 14px;
  color: #888;
}
.tp-card--inactive { opacity: 0.5; }

.tp-inclusion { margin: 16px 0 24px; }
.tp-inclusion h2 {
  font-size: 16px;
  font-weight: 700;
  margin: 0 0 8px;
  color: #111;
}
.tp-inclusion-list {
  list-style: none;
  padding: 0 0 0 14px; /* indent the checkbox rows under their heading */
  margin: 0;
}
/* Block <li> per row guarantees one-per-line; default inline layout
 * inside the <label> keeps the checkbox + text immediately adjacent
 * regardless of any inherited flex/grid styles from ancestors. */
.tp-inclusion-list li {
  display: block;
  margin: 0;
  padding: 4px 0;
}
.tp-inclusion-list label {
  /* Flex row so the checkbox is vertically centered against the label text
   * regardless of font metrics — more reliable than inline vertical-align,
   * which left the box sitting slightly high relative to the text. */
  display: flex;
  align-items: center;
  cursor: pointer;
  font-size: 14px;
  color: #111;
  line-height: 1.4;
}
.tp-inclusion-list input[type="checkbox"] {
  /* Override the global `input, textarea { width: 100% }` rule
   * (line 51) — without this the checkbox stretches to fill the row
   * and pushes the label text onto the next line. Also strip the
   * global 14px padding + 16px bottom margin which gave the checkbox
   * an oversized hit-box and a stray gap below each row. */
  width: auto;
  padding: 0;
  flex-shrink: 0; /* keep the native checkbox size inside the flex label row */
  vertical-align: middle;
  margin: 0 10px 0 0;
}
.tp-inclusion-list .tp-inclusion-label-text {
  vertical-align: middle;
}
/* Locked (always-included) inclusion row — e.g. the Title Page: muted + a small
   "Always included" tag, mirroring the title-page include list. */
.tp-inclusion-list .tp-inclusion-locked { color: #6b7280; cursor: default; }
.tp-inclusion-list .tp-inclusion-locked input[type="checkbox"] { cursor: default; }
.tp-inclusion-always {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #9ca3af;
  vertical-align: middle;
}
.tp-inclusion-error {
  margin: 8px 0 0;
  color: #b91c1c;
  font-size: 13px;
}

.tp-form { display: block; }
.tp-field {
  display: block;
  margin: 0 0 12px;
}
/* Default <fieldset> styling adds a 2px groove border + horizontal
 * padding + 2px inline margin + a quirky min-inline-size that breaks
 * the field's flex/grid math. Strip all of it so a fieldset.tp-field
 * (used by array fields like missions/languages) looks identical to a
 * div.tp-field. */
fieldset.tp-field {
  border: 0;
  padding: 0;
  margin-inline: 0;
  min-inline-size: 0;
}
.tp-field label,
.tp-field legend {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: #111;
  margin: 0 0 4px;
  padding: 0;
}
.tp-field input,
.tp-field textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
  box-sizing: border-box;
}
.tp-field textarea { resize: vertical; min-height: 80px; }
/* Checkbox fields (Personal Message options) — override the global/.tp-field
   `input { width:100%; padding }` rules so the box stays its natural size and
   sits inline before its label instead of stretching to a full-width block
   that pushes the text onto the next line. */
.tp-field--checkbox label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
  cursor: pointer;
}
.tp-field--checkbox input[type="checkbox"] {
  width: auto;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  accent-color: #111;
  cursor: pointer;
}

/* The "Include on the title page" list reuses the .tp-inclusion markup/styles
   (h2 + ul of labelled checkboxes), matching the book-level "include these
   pages" list. .tp-include-on-title just trims the top margin since it sits at
   the top of the form, right under the section description. */
.tp-include-on-title { margin-top: 0; }

/* Per-field opt-in extra top margin — used by the Mission Start row on
 * title_page to put visual breathing room between the Mission(s)
 * fieldset above and the Mission Dates pair. */
.tp-field--space-above { margin-top: 24px; }
/* The title_page Mission Dates fieldset reuses .cover-field (whose `margin`
 * shorthand zeroes margin-top); this compound selector out-specifies it to
 * keep the space-above gap below the Mission(s) fieldset. */
.cover-field.tp-field--space-above { margin-top: 24px; }

/* Sub-option of the field above it (e.g. "We will sign our name(s) by hand
 * instead" under Sign Off): indent it and pull it up tight so it reads as a
 * modifier of that field rather than a separate control. --tight trims the
 * 12px bottom-margin gap of the field above. */
.tp-field--indent { padding-left: 24px; }
/* Pull the sub-option checkbox up so it sits directly beneath the field above
 * it — cancels almost all of that field's 12px bottom-margin gap, leaving a
 * hair of separation so it reads as "right below" rather than floating. */
.tp-field--tight { margin-top: -10px; }

/* The "write the whole page by hand" opt-out (Personal Message) gets extra
 * breathing room above + below so it reads as a distinct choice, set apart from
 * the message fields below it. */
.tp-field--blank-page { margin-top: 28px; margin-bottom: 28px; }
/* Pull a field up to tighten the gap above it (Message + Sign Off on the
 * Personal Message form sit closer to the field above). */
.tp-field--tight-top { margin-top: -6px; }

/* Indent "+ Add another mission" / "+ Add another language" and pull it up
 * tight against the input rows so it reads as a sub-action of the fieldset. */
.tp-field [data-tp-array-add] { margin-top: -4px; padding-left: 14px; }
/* Extra whitespace below the optional array fieldset (Language(s) on the title
 * page) so the "+ Add another language" action isn't crowded by the next block
 * (the headshot upload). Only the optional array carries data-tp-array-optional. */
.tp-field[data-tp-array-optional] { margin-bottom: 28px; }

/* Array-field rows (missions / languages on title_page) — flex layout
 * mirroring /book/cover's .cover-array-row so the remove × (and the
 * first-language Clear) sit on the right of the input and align to the
 * existing cover styling. Replaces the prior unstyled stacked layout
 * where the × wrapped below the full-width input. */
.tp-array-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 0 0 8px;
}
.tp-array-row:last-child { margin-bottom: 0; }
.tp-array-row input[type="text"] { flex: 1; }
.tp-array-remove {
  width: 44px;
  height: 44px;
  padding: 0;
  background: #fff;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  color: #555;
  flex-shrink: 0;
  /* See .cover-array-remove — match the input margin so the × centers, not 8px low. */
  margin-bottom: 16px;
}
.tp-array-remove:hover { background: #f3f4f6; color: #111; }
.tp-array-clear {
  /* Match the language input's height exactly: stretch to the flex row's
   * cross-size instead of a fixed 44px that stood taller than the field.
   * inline-flex centers the label vertically. The margin-bottom:16px mirrors the
   * input's global margin so stretch resolves to the input's OWN height (not the
   * margin-inflated line cross-size) and the button aligns instead of sitting
   * 16px taller + low. */
  align-self: stretch;
  display: inline-flex;
  align-items: center;
  padding: 0 14px;
  background: #fff;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  color: #555;
  flex-shrink: 0;
  margin-bottom: 16px;
}
.tp-array-clear:hover { background: #f3f4f6; color: #111; }

/* Each area_map slot is just a renderUploadBlock + a small Remove
 * link below — no boxed row chrome. Slots stack with the same gap
 * the inputs use. */
.tp-area-map-slot { margin: 0 0 12px; }

/* Per-section load failure — surfaces 429 / 5xx / network blips that
 * would otherwise render as an empty section indistinguishable from
 * a fresh untouched list. Hosts an inline Retry that re-runs the
 * loader and re-paints. */
.tp-load-error {
  margin: 0;
  padding: 8px 10px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 6px;
  color: #991b1b;
  font-size: 13px;
}
.tp-load-error .tp-load-retry {
  margin-left: 4px;
  color: #991b1b;
}
.tp-load-error .tp-load-retry:hover { color: #7f1d1d; }

.tp-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  /* Generous whitespace above the Save button on the always-edit forms (Title
     Page + Personal Message), separating it from the last field. */
  margin-top: 40px;
}
.tp-error {
  margin: 8px 0 0;
  padding: 8px 10px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 6px;
  color: #991b1b;
  font-size: 13px;
}
/* Non-blocking heads-up (amber, distinct from the red .tp-error) — e.g. the
 * Title Page tab's "end date before start date" notice. The save still goes
 * through; this only nudges the customer to double-check. */
.tp-date-warning {
  margin: 8px 0 0;
  padding: 8px 10px;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 6px;
  color: #92400e;
  font-size: 13px;
}
.tp-preview-block {
  margin: 0 0 8px;
  font-size: 14px;
  color: #111;
}
.tp-preview-block strong { color: #111; font-weight: 600; }
.tp-preview-body { margin-top: 4px; color: #333; }

/* Personal Message preview — a book-page-style mock of the saved note
 * (serif page card), shown on the parents_message card in place of the
 * generic tp-preview-block. */
/* Preview card — mirrors the /book/cover "Front Cover Preview" framing
   (gray rounded card + bold heading label) so the Personal Message + Title
   Page previews read as the same kind of book preview. The page INSIDE keeps
   the 6×9 interior-page trim. */
/* Shared preview frame width — the cover (front/back/spine) and the title-pages
 * (personal message / title page) previews all use it so every full-page preview
 * is the SAME physical size (same width → same height, since they share the
 * trim --book-aspect). Spine height derives from it too (see .spine-preview-pane).
 * Sized so TWO previews fit side-by-side in the 640px .container (544px content):
 * 2×250 + 28px gap = 528 ≤ 544. (Wider would wrap the spine+back / linen pairs to
 * a stack.) */
:root { --book-preview-w: 250px; }
.tp-preview-card {
  margin-top: 24px;
  padding: 16px;
  background: #fafafa;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  /* Fixed width (was max-width) so the message/title previews don't shrink to
     their content — they must match the fixed-width .cover-preview exactly. */
  width: var(--book-preview-w, 280px);
  max-width: 100%;
}
.tp-preview-card h2 {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: 700;
  color: #111;
  text-align: center;
}
.tp-message-preview-page {
  /* White to match the real printed book page (book-base.css body has no
     background → pages render white); the prior #fdfcf9 cream was preview-only. */
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  /* Interior-page proportions so the preview reads as a real book page.
     --book-aspect is set per page by JS from the selected book type
     (memory_book 6/9, photo_book 7.44/9.68). Defaults to 6/9. */
  width: 100%;
  aspect-ratio: var(--book-aspect, 6 / 9);
  box-sizing: border-box;
  overflow-y: auto;
  padding: 20px 22px;
  margin: 0;
}
.tp-message-preview-body {
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 15px;
  line-height: 1.75;
  color: #2a2a2a;
}
/* Greeting ("Dear Elder Smith,") — same book type as the body, one blank line
   above it. Mirrors .fm-greeting in the rendered book. */
.tp-message-preview-greeting {
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 15px;
  line-height: 1.75;
  color: #2a2a2a;
  margin-bottom: 1.1em;
}
.tp-message-preview-placeholder {
  color: #9ca3af;
  font-size: 14px;
  /* Centered in the 6×9 page so the empty-state copy isn't stranded at
     the top of a tall page. */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
/* Personal Message preview variants (docs/personal-message-signoff-plan.md) —
   mirror the book renderer. The centered (sign-by-hand / no sign-off) layout now
   lives on .tp-preview-fit--centered (shared shrink-to-fit content layer). */
/* Right-aligned sign-off, one blank line below the message. padding-right is a
   scaled stand-in for the print page's 10ch inset (this preview box is narrow). */
.tp-message-preview-signoff {
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 15px;
  line-height: 1.75;
  color: #2a2a2a;
  text-align: right;
  padding-right: 1.5em;
  margin-top: 1.75em;
}
/* Disabled (grayed) Personal Message field when sign-by-hand / blank-page is
   checked. Value is retained; this only dims the field + its label. */
.tp-field--disabled {
  opacity: 0.5;
}

/* Attachment-card view — mirrors /email-group's inline .attachment-card
 * rules, adopted on /book/title-pages for the Mission Call / LOA / Area Map
 * PDF cards and the headshot photo card. NOTE: /email-group still carries its
 * own inline copy; keep them in sync (or consolidate email-group onto these). */
.attachment-card {
  border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px 14px; background: #fff;
  display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap;
}
.attachment-card .attachment-thumb-wrapper { position: relative; flex: 0 0 auto; width: 240px; max-width: 100%; height: 240px; }
.attachment-card .attachment-thumb { width: 240px; max-width: 100%; height: 240px; object-fit: cover; border-radius: 4px; background: #f3f4f6; display: block; }
.attachment-card .attachment-thumb-placeholder {
  width: 240px; max-width: 100%; height: 240px; box-sizing: border-box;
  border: 1px solid #e5e7eb; border-radius: 4px;
  background: #f3f4f6; display: flex; flex-direction: column; gap: 12px;
  align-items: center; justify-content: center;
  color: #9ca3af; font-size: 24px; font-weight: 600;
}
.attachment-card .attachment-thumb-regen {
  font-size: 13px; font-weight: 500; line-height: 1;
  padding: 7px 12px; border-radius: 6px;
  border: 1px solid #d1d5db; background: #fff; color: #374151; cursor: pointer;
}
.attachment-card .attachment-thumb-regen:hover:not(:disabled) { background: #f3f4f6; color: #111; }
.attachment-card .attachment-thumb-regen:disabled { color: #9ca3af; cursor: default; }
.attachment-card-body { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.attachment-card .filename { font-size: 13px; color: #111; font-weight: 500; word-break: break-word; }
/* Titled variant (headshot): title heading on top, image + controls below. */
.attachment-card--titled { flex-direction: column; align-items: stretch; }
.attachment-card--titled .attachment-card-heading { margin-bottom: 2px; }
.attachment-card--titled .attachment-card-row { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.attachment-card .meta { font-size: 12px; color: #6b7280; }
.attachment-card .controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* First-page overlay controls on a filled PDF card (mission_call / loa /
   area_map / other_document). docs/pdf-first-page-title-overlay-plan.md. */
/* Own full-width row below the thumb + body (flex-basis:100% wraps it onto its
   own line within the flex-wrap .attachment-card), so the label never gets
   squeezed into the narrow body column and wrapped word-by-word. */
.tp-overlay-controls {
  flex-basis: 100%; display: flex; flex-direction: column; gap: 6px;
  margin-top: 8px; padding-top: 10px; border-top: 1px solid #f0f1f3;
}
.tp-overlay-label {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; line-height: 1.3; color: #4b5563; cursor: pointer; user-select: none;
}
/* width/padding:auto/0 undo the global `input { width: 100% }` rule (line 239)
   that would otherwise stretch the checkbox across the whole label and shove
   the text off to the right. */
.tp-overlay-label input { width: auto; padding: 0; margin: 0; flex: none; cursor: pointer; }
.tp-overlay-title {
  font-size: 13px; padding: 4px 8px; border: 1px solid #d1d5db;
  border-radius: 4px; max-width: 320px;
}
.tp-overlay-status { font-size: 12px; color: #059669; min-height: 14px; }
.tp-overlay-status.error { color: #b91c1c; }
.attachment-card .btn-open {
  width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid #d1d5db; background: #fff;
  color: #374151; border-radius: 4px; text-decoration: none;
}
.attachment-card .btn-open:hover { background: #f3f4f6; }
.attachment-card .btn-open svg { display: block; }
/* Title-pages card text actions (Replace / Remove) — sized to the 32px icon
 * buttons so the controls row sits on one line. */
.attachment-card .tp-card-action {
  height: 32px; box-sizing: border-box; padding: 0 12px;
  display: inline-flex; align-items: center;
  font-size: 13px; font-weight: 500;
  border: 1px solid #d1d5db; background: #fff; color: #374151;
  border-radius: 4px; cursor: pointer; text-decoration: none;
}
.attachment-card .tp-card-action:hover { background: #f3f4f6; }
.attachment-card .tp-card-action--danger { border-color: #fecaca; color: #b91c1c; }
.attachment-card .tp-card-action--danger:hover { background: #fef2f2; }

/* Shrink-to-fit preview box + content layer — shared by the Title Page and
   Personal Message previews (fitPreview() in singleton.js). The box is a fixed
   trim-proportioned page (it keeps .tp-message-preview-page's aspect-ratio) that
   never scrolls; padding is 0 so the content layer measures cleanly against it.
   The .tp-preview-fit layer is in-flow with min-height = the box, so it FILLS
   the page when content is short and grows taller when content needs it; the JS
   fitter then scales it from the top to fit. A transform doesn't change layout
   height, so the overflow is clipped by the box while the scaled content stays
   fully visible. */
.tp-preview-scaled { overflow: hidden; position: relative; padding: 0; }
.tp-preview-fit {
  min-height: 100%;
  box-sizing: border-box;
  padding: 20px 22px;
  transform-origin: top center;
}
/* Centered message variant (sign-by-hand / no sign-off) — greeting + message
   centered horizontally + vertically with 2 blank lines for a hand signature. */
.tp-preview-fit--centered {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}

/* Title Page preview — book-page mock. Mirrors the rendered book: eyebrow +
   small-caps name + paper-plane divider + mission / language / dates (optional
   headshot below), with the Church attribution pinned to the bottom via
   space-between. Serif (Georgia) approximates the print Source Serif Pro. The
   box/scale behavior comes from .tp-preview-scaled / .tp-preview-fit above. */
.tp-titlepage-preview {
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-family: Georgia, 'Times New Roman', serif;
}
.tp-titlepage-main { padding-top: 22px; }
.tp-titlepage-eyebrow { text-transform: uppercase; letter-spacing: 0.2em; font-size: 11px; color: #555; margin-bottom: 8px; }
.tp-titlepage-name { font-variant: small-caps; font-weight: 700; font-size: 28px; line-height: 1.05; color: #2a2a2a; }
.tp-titlepage-divider { display: flex; align-items: center; justify-content: center; gap: 8px; width: 70%; margin: 10px auto 14px; }
.tp-titlepage-divider span { flex: 1 1 auto; height: 1px; background: #333; max-width: 60px; }
.tp-titlepage-plane { width: 16px; height: 16px; color: #333; flex: 0 0 auto; }
.tp-titlepage-mission { text-transform: uppercase; letter-spacing: 0.12em; font-weight: 700; font-size: 13px; color: #2a2a2a; margin-bottom: 6px; }
.tp-titlepage-langs { font-style: italic; font-size: 13px; color: #666; margin-bottom: 6px; }
.tp-titlepage-dates { text-transform: uppercase; letter-spacing: 0.12em; font-size: 12px; color: #555; }
.tp-titlepage-photo { width: 84px; height: 84px; object-fit: cover; border-radius: 50%; margin: 16px auto 0; display: block; }
.tp-titlepage-church-pre { text-transform: uppercase; letter-spacing: 0.14em; font-size: 10px; color: #444; }
.tp-titlepage-church-name { font-variant: small-caps; font-weight: 700; font-size: 16px; line-height: 1.15; color: #2a2a2a; }
.tp-titlepage-church-post { text-transform: uppercase; letter-spacing: 0.1em; font-size: 9px; color: #444; }

/* Server-rendered front-matter preview (true WYSIWYG) — the faithful JPEG from
   the title_page / parents_message preview endpoints (singleton.js
   swapServerPreviewImage) replaces the client mock above (Title Page + Personal
   Message). The page box keeps its --book-aspect; the image (already the
   finished trim) fills it exactly. */
.tp-message-preview-page.tp-titlepage-rendered { overflow: hidden; }
.tp-titlepage-render-img { display: block; width: 100%; height: 100%; object-fit: contain; cursor: pointer; }

.btn-link {
  background: none;
  border: none;
  padding: 0;
  font-size: 13px;
  font-weight: 600;
  color: #6b7280;
  cursor: pointer;
  text-decoration: underline;
}
/* Reset the global `button:hover { background: #333 }` — without this
 * a <button class="btn-link"> picks up the primary-button hover bg
 * on hover (Cancel + "+ Add another mission" on /book/cover both
 * had this). Matches button.muted-link:hover above. */
.btn-link:hover { color: #111; background: none; }
/* A disabled .btn-link (e.g. "Don't want to Broadcast?" inside a locked
   teaser for a no/standard-group user) must stay a plain text link — without
   this, `button:disabled { background:#888 }` paints a gray highlight box
   behind it. Higher specificity than button:disabled, so it wins. */
.btn-link:disabled { background: none; cursor: default; }

/* /book/cover customization form (docs/book-restructure-plan.md Phase B,
 * Phase 6 UI). 2-column layout on desktop (form left, preview right),
 * single column below 720px. */
.cover-form { display: block; }
/* Tertiary nav (Cover / Front Cover / Spine + Back Cover) — a segmented tablist
 * above the three panes (docs/cover-tertiary-nav-plan.md). Matches the /book/*
 * card vocabulary. */
/* The .tp-* twins (/book/title-pages tertiary tabs, docs/title-pages-tabs-plan.md)
 * share these exact rule blocks via grouped selectors — same segmented-tab chrome,
 * no duplicated styling, cover markup/classes untouched. */
.cover-tabs,
.tp-tabs {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin: 0 0 20px;
  border-bottom: 1px solid #e5e7eb;
}
.cover-tab,
.tp-tab {
  appearance: none;
  background: transparent; /* explicit — never inherit a dark button bg */
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  margin-bottom: -1px; /* overlap the row border so the active tab joins its pane */
  padding: 10px 16px;
  font: inherit;
  font-weight: 600;
  color: #4b5563;
  cursor: pointer;
}
/* Re-declare the :hover background — single-class button :hover otherwise leaks to
 * the global #333 hover (see feedback_button_hover_dark_leak). */
.cover-tab:hover,
.tp-tab:hover { background: #f3f4f6; color: #1f2937; }
.cover-tab.is-active,
.cover-tab.is-active:hover,
.tp-tab.is-active,
.tp-tab.is-active:hover {
  background: #fff;
  color: #1f2937;
  border-color: #e5e7eb;
  border-bottom-color: #fff;
}
.cover-tab:focus-visible,
.tp-tab:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
/* Dirty dot — a loss-AWARENESS cue; hidden until the pane has unsaved edits. */
.cover-tab-dot,
.tp-tab-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 6px;
  border-radius: 50%;
  background: #d97706;
  vertical-align: middle;
  visibility: hidden;
}
.cover-tab--dirty .cover-tab-dot,
.tp-tab--dirty .tp-tab-dot { visibility: visible; }
/* Only the active pane shows (also covered by the global [hidden] rule;
 * kept for parity + intent). */
.cover-pane[hidden],
.tp-pane[hidden] { display: none; }
@media (max-width: 480px) {
  .cover-tabs,
  .tp-tabs { flex-wrap: nowrap; overflow-x: auto; }
  .cover-tab,
  .tp-tab { white-space: nowrap; padding: 10px 12px; }
}

/* Each /book/cover section is its own card, matching the .tp-card visual
 * vocabulary used on /book/title-pages. */
.cover-card {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  padding: 24px 24px 20px;
  margin: 0 0 16px;
}
/* Staged (picked-but-unsaved) cover-photo thumbnail — a local object-URL
 * preview shown until Save commits it. Constrained so a large source image
 * doesn't blow out the card. */
.cover-photo-staged { margin: 10px 0 4px; }
.cover-photo-staged img {
  max-width: 160px;
  max-height: 200px;
  border-radius: 8px;
  border: 1px solid #e5e7eb;
  display: block;
}
/* The Spine card sits below the previews row — add breathing room so the
 * spine section visually separates from the previews rather than abutting
 * them. */
.cover-previews-row + .cover-card { margin-top: 32px; }
/* Two previews sit side-by-side (spine + back, or the linen pair). flex-start
 * so the panes top-align under their identical h2s. wrap so on narrow widths
 * the second pane stacks under the first. The row also breathes away from the
 * Save actions above it, and stacked rows (dust-jacket pair, then the linen
 * pair on Premium 6×9) are spaced from each other. */
.cover-previews-row {
  display: flex;
  align-items: flex-start;
  gap: 28px;
  flex-wrap: wrap;
  margin-top: 24px;
}
.cover-previews-row + .cover-previews-row { margin-top: 32px; }
.cover-previews-row .cover-preview { margin-top: 0; }
.cover-card-title {
  margin: 0 0 16px;
  font-size: 18px;
  font-weight: 700;
  color: #111;
}
/* Sub-heading inside a card (e.g. "Spine foil lettering color" within the
 * Linen spine text card on /book/cover). Smaller than the card title. */
.cover-card-subtitle {
  margin: 0 0 6px;
  font-size: 16px;
  font-weight: 700;
  color: #111;
}
.cover-field { margin: 0 0 32px; }
/* Extra breathing room above the "Lettering vertical placement" slider so it
 * reads as its own control, separated from the lettering-color swatches +
 * contrast warning above it. Compound selector out-specifies the `margin`
 * shorthand's margin-top:0. */
.cover-field.cover-lettering-placement { margin-top: 24px; }
/* Same fieldset reset as .tp-field above — applies to the Mission(s)
 * + Mission Dates fieldsets on /book/cover. */
fieldset.cover-field {
  border: 0;
  padding: 0;
  margin-inline: 0;
  min-inline-size: 0;
}
/* Toggled by handleCoverInclusionToggle on /book/cover when the
 * matching inclusion checkbox is unchecked. Opacity-only — mirrors
 * .tp-card--inactive (line 517). Fields stay editable. */
.cover-field--inactive { opacity: 0.5; }

/* /book/cover inclusion checkbox list — mirrors the /book/title-pages
 * tp-inclusion-list styling, shifted to the cover namespace. */
.cover-inclusion { margin: 0 0 32px; }
.cover-inclusion h3 {
  font-size: 16px;
  font-weight: 700;
  margin: 0 0 8px;
  color: #111;
}
.cover-inclusion-list {
  list-style: none;
  padding: 0 0 0 14px; /* indent the checkbox rows under their heading */
  margin: 0;
}
.cover-inclusion-list li {
  display: block;
  margin: 0;
  padding: 4px 0;
}
.cover-inclusion-list label {
  display: inline-block;
  cursor: pointer;
  font-size: 14px;
  color: #111;
  line-height: 1.4;
}
.cover-inclusion-list input[type="checkbox"] {
  /* Override the global `input, textarea { width: 100% }` rule the same
   * way .tp-inclusion-list does — without this the checkbox stretches
   * across the row and pushes the label text onto a second line. */
  width: auto;
  padding: 0;
  vertical-align: middle;
  margin: 0 10px 0 0;
}
.cover-inclusion-error {
  margin: 8px 0 0;
  color: #b91c1c;
  font-size: 13px;
}
.cover-field > label,
.cover-field > legend,
.cover-field > .cover-field-label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: #111;
  margin: 0 0 6px;
}
.cover-field input[type="text"],
.cover-field input[type="month"] {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
  box-sizing: border-box;
}
.cover-helper {
  margin: 0 0 12px;
  font-size: 12px;
  color: #6b7280;
}
/* Indent helper text + the array "Add another" action so they sit under their
   input/label rather than flush to the field's left edge. */
.cover-helper--indent { padding-left: 14px; }
.cover-array-add { padding-left: 14px; }
/* End Date "Clear" — sits flush-right of the End Date label, opposite it. */
.cover-subfield-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.cover-date-clear { font-size: 12px; flex: 0 0 auto; }

/* /book/cover color picker — 6-swatch grid populated from
 * lib/book/cover-options.js (Lulu hardcover linen wrap palette).
 * Each swatch is a hidden radio input + a visible label wrapping a
 * colored thumbnail and the color name; clicking anywhere on the
 * label selects the radio. Selected state shown by border + ring
 * around the thumbnail. Layout: 3 columns at desktop, 2 at narrow
 * widths so each thumbnail stays a usable target. */
.cover-swatch-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
@media (max-width: 420px) {
  .cover-swatch-grid { grid-template-columns: repeat(2, 1fr); }
}
.cover-swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px;
  border: 2px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  /* Anchor the absolutely-positioned hidden radio to the swatch itself.
   * Without this, the radio's containing block is the viewport and its
   * inherited width: 100% (global input rule) becomes 100vw, leaking a
   * page-wide ghost input that scrolls into blank whitespace. */
  position: relative;
}
.cover-swatch:hover { background: #f9fafb; }
.cover-swatch input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  /* Override the global `input, textarea { width: 100% }` rule the same
   * way .cover-inclusion-list does — otherwise the hidden radio stretches
   * to 100% of its containing block and overflows the page. */
  width: auto;
}
.cover-swatch-visual {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  max-width: 72px;
  border-radius: 6px;
  border: 1px solid rgba(0, 0, 0, 0.1);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* Unavailable swatch — shown but disabled with a soft X (e.g. White cover, or
   Navy/Gray lettering, on a linen Premium: Lulu has no white linen / navy-gray
   foil). The radio also carries `disabled` so it can't be picked. The X is two
   soft diagonal lines drawn over a light veil so it reads on light AND dark
   swatches. */
.cover-swatch--unavailable { cursor: default; }
.cover-swatch--unavailable:hover { background: none; }
.cover-swatch--unavailable .cover-swatch-label { color: #9ca3af; }
.cover-swatch--unavailable .cover-swatch-visual::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 6px;
  background:
    linear-gradient(to top right, transparent 46%, rgba(107, 114, 128, 0.85) 46%, rgba(107, 114, 128, 0.85) 54%, transparent 54%),
    linear-gradient(to bottom right, transparent 46%, rgba(107, 114, 128, 0.85) 46%, rgba(107, 114, 128, 0.85) 54%, transparent 54%),
    rgba(255, 255, 255, 0.5);
}
.cover-swatch-label {
  font-size: 13px;
  font-weight: 500;
  color: #374151;
}
/* "No Box" chip (2026-07-02, photo cover box picker) — a transparent/empty
   well: a light checkerboard with a soft diagonal slash so it reads as "none"
   rather than a color. Mirrors the .cover-swatch-visual box shape. */
.cover-swatch-visual--nobox {
  background:
    linear-gradient(to top right, transparent 46%, rgba(107, 114, 128, 0.7) 46%, rgba(107, 114, 128, 0.7) 54%, transparent 54%),
    conic-gradient(#f3f4f6 0 25%, #e5e7eb 0 50%, #f3f4f6 0 75%, #e5e7eb 0) 0 0 / 16px 16px;
}
/* Front box transparency slider (photo covers). Width + margin now come from
 * the shared .cover-range rule below, so this slider is full-width and visually
 * identical to the Lettering-vertical-placement slider (no 320px cap). */
.cover-box-transparency { margin-top: 24px; }
.cover-box-transparency .cover-card-subtitle { display: block; }

/* Custom range sliders on /book/cover (Lettering vertical placement + Box
 * transparency). Native sliders inset the thumb by half its width at each end,
 * so 0% / 100% never look like they reach the track ends. This restyle draws a
 * full-width track with a fill driven by --range-pct (kept in sync in
 * book-cover.js syncRangeFill) so the fill runs edge-to-edge, plus a thumb that
 * rests flush against the ends. Both sliders carry .cover-range. */
.cover-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 18px;            /* pointer/hit area; the 6px track sits centered */
  margin: 6px 0 2px;
  background: transparent;  /* the track pseudo-element paints the bar */
  cursor: pointer;
}
.cover-range:focus { outline: none; }
/* Track + edge-to-edge fill — WebKit/Blink (Chrome, Edge, Safari) */
.cover-range::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background-color: #d1d5db;
  background-image: linear-gradient(#2563eb, #2563eb);
  background-repeat: no-repeat;
  background-size: var(--range-pct, 0%) 100%;
}
.cover-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  margin-top: -5px;        /* center the 16px thumb on the 6px track */
  border-radius: 50%;
  background: #ffffff;
  border: 2px solid #2563eb;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.cover-range:focus-visible::-webkit-slider-thumb {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}
/* Track + native fill (::-moz-range-progress) — Firefox */
.cover-range::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  background: #d1d5db;
}
.cover-range::-moz-range-progress {
  height: 6px;
  border-radius: 999px;
  background: #2563eb;
}
.cover-range::-moz-range-thumb {
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  border: 2px solid #2563eb;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.cover-range:focus-visible::-moz-range-thumb {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}
.cover-swatch:has(input[type="radio"]:checked) {
  border-color: #111;
  background: #f3f4f6;
}
.cover-swatch:has(input[type="radio"]:checked) .cover-swatch-label {
  color: #111;
  font-weight: 600;
}
.cover-swatch:has(input[type="radio"]:focus-visible) {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

/* Custom color picker (2026-07-03). The visible swatch is a trigger button that
   opens OUR OWN popover picker (saturation/value box + hue slider + hex field) —
   NOT the native OS picker, so the hex field lives inside the picker. Only the
   hidden radio carries the value. */
.cover-swatch--custom { position: relative; }
.cover-custom-trigger {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  /* Square to match the preset .cover-swatch-visual chips. */
  width: 72px;
  height: 72px;
  max-width: 100%;
  margin: 0;
  padding: 0;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
  cursor: pointer;
}
/* The picker popover, anchored under the swatch. Absolutely positioned so it
   floats over the grid; starts hidden until the trigger opens it. */
.cover-custom-pop {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  width: 200px;
  padding: 12px;
  background: #fff;
  border: 1px solid #d1d5db;
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
  cursor: default;
}
/* Little pointer triangle at the top of the popover. */
.cover-custom-pop::before {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 7px solid transparent;
  border-bottom-color: #fff;
  filter: drop-shadow(0 -1px 0 #d1d5db);
}
/* Saturation (x) / value (y) box. Its layered gradient background is set in JS
   per current hue. A round thumb marks the current s/v. */
.cover-custom-pop .cc-sv {
  position: relative;
  width: 100%;
  height: 130px;
  border-radius: 6px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  cursor: crosshair;
  touch-action: none;
}
.cover-custom-pop .cc-sv-thumb {
  position: absolute;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.45);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
/* Hue bar — a rainbow track with a percent-positioned thumb (NOT a native range
   input, whose thumb is inset by half its width and drifts off the gradient). The
   thumb can sit at 0%/100% via translateX(-50%), so it lands exactly on its hue. */
.cover-custom-pop .cc-hue {
  position: relative;
  width: 100%;
  height: 12px;
  margin: 12px 0 10px;
  border-radius: 6px;
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
  cursor: pointer;
  touch-action: none;
}
.cover-custom-pop .cc-hue:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}
.cover-custom-pop .cc-hue-thumb {
  position: absolute;
  top: 50%;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.35);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
/* Hex row — a preview chip + the hex text field, inside the picker. */
.cover-custom-pop .cc-hexrow {
  display: flex;
  align-items: center;
  gap: 8px;
}
.cover-custom-pop .cc-preview {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  border: 1px solid rgba(0, 0, 0, 0.15);
}
.cover-custom-pop .cc-hexrow input[type="text"] {
  flex: 1 1 auto;
  box-sizing: border-box;
  /* min-width:0 lets the input shrink inside the flex row — without it the input's
     intrinsic width keeps it from shrinking and it overflows the popover. */
  min-width: 0;
  width: auto;
  height: 30px;
  margin: 0;
  padding: 0 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  text-transform: lowercase;
  border: 1px solid #d1d5db;
  border-radius: 6px;
}
.cover-custom-pop .cc-hexrow input[type="text"]:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 1px;
}
.cover-custom-pop .cc-hexrow input.cover-custom-hex--invalid {
  border-color: #dc2626;
  outline-color: #dc2626;
}
/* R/G/B number fields — three equal columns under the hex row. */
.cover-custom-pop .cc-rgbrow {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}
.cover-custom-pop .cc-rgb {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1 1 0;
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  color: #6b7280;
}
.cover-custom-pop .cc-rgb input[type="number"] {
  box-sizing: border-box;
  min-width: 0;
  width: 100%;
  height: 28px;
  margin: 0;
  padding: 0 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  font-weight: 400;
  text-align: center;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  /* Drop the spinner arrows — they steal width and clip 3-digit values (255). */
  -webkit-appearance: none;
  -moz-appearance: textfield;
  appearance: textfield;
}
.cover-custom-pop .cc-rgb input[type="number"]::-webkit-outer-spin-button,
.cover-custom-pop .cc-rgb input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.cover-custom-pop .cc-rgb input[type="number"]:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 1px;
}
.cover-custom-note {
  margin-top: 10px;
}

/* Cover Style chooser — two CSS-drawn book thumbnails (Photo / Solid color).
 * The radio is visually hidden; selection shows as a highlighted card. */
.cover-style-thumbs {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}
.cstyle-thumb {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 12px;
  border: 2px solid #e5e7eb;
  border-radius: 10px;
  cursor: pointer;
  background: #fff;
  transition: border-color 0.15s, background 0.15s;
}
.cstyle-thumb:hover { background: #f9fafb; }
.cstyle-thumb input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.cstyle-thumb.is-selected,
.cstyle-thumb:has(input[type="radio"]:checked) {
  border-color: #111;
  background: #f3f4f6;
}
.cstyle-thumb:has(input[type="radio"]:focus-visible) {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}
/* The mini book cover (2:3-ish) — frames the inline SVG mockup. */
.cstyle-thumb-art {
  position: relative;
  display: block;
  width: 86px;
  height: 116px;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.cstyle-thumb-svg {
  display: block;
  width: 100%;
  height: 100%;
}
.cstyle-thumb-label {
  font-size: 13px;
  font-weight: 600;
  color: #374151;
}
.cstyle-thumb.is-selected .cstyle-thumb-label,
.cstyle-thumb:has(input[type="radio"]:checked) .cstyle-thumb-label { color: #111; }

.cover-array-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 0 0 8px;
}
/* Tighten the gap between the last mission input and the helper text
 * below it — keeps between-row spacing at 8px but drops the trailing
 * gap so the helper sits closer to the input. */
.cover-array-row:last-child { margin-bottom: 0; }
.cover-array-row input[type="text"] { flex: 1; }
.cover-array-remove {
  width: 38px;
  height: 38px;
  padding: 0;
  background: #fff;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  color: #555;
  flex-shrink: 0;
  /* Match the input's global margin-bottom:16px so align-items:center aligns the
   * × on equal margin-boxes — otherwise the marginless button parks ~8px low. */
  margin-bottom: 16px;
}
.cover-array-remove:hover { background: #f3f4f6; color: #111; }

.cover-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
@media (max-width: 480px) {
  .cover-grid-2 { grid-template-columns: 1fr; }
}

/* Sub-label inside the Mission Dates fieldset — smaller weight than the
 * fieldset legend so the visual hierarchy reads as one date range, not
 * two unrelated date inputs. */
.cover-subfield > label {
  display: block;
  font-size: 12px;
  font-weight: 500;
  color: #374151;
  margin: 0 0 4px;
}
.cover-subfield input[type="month"] {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
  box-sizing: border-box;
}

/* /book/cover mission-date picker: keeps native <input type="month">
 * (and its picker) but layers a custom-formatted overlay ("June 2024")
 * over it. The input is opacity:0 + absolute over the entire field, so
 * clicks pass through to it and open the native picker; the overlay
 * span renders the visible value via .cover-date-display, kept in sync
 * by syncDateDisplay() in book-cover.js. Placeholder for empty state
 * comes from .cover-date-display:empty::before — data-placeholder
 * attribute on the span sets the copy. */
.cover-subfield .cover-date-field {
  position: relative;
  width: 100%;
  background: #fff;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  padding: 8px 10px;
  box-sizing: border-box;
  cursor: pointer;
  min-height: 38px;
  display: flex;
  align-items: center;
  font-size: 14px;
  font-family: inherit;
}
.cover-subfield .cover-date-field:focus-within { border-color: #111; }
.cover-date-display {
  color: #111;
  pointer-events: none;
}
.cover-date-display:empty::before {
  content: attr(data-placeholder);
  color: #9ca3af;
}
.cover-subfield .cover-date-field input[type="month"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  border: 0;
  padding: 0;
  margin: 0;
  background: transparent;
  font: inherit;
}
.cover-date-error {
  margin: 8px 0 0;
  font-size: 13px;
  color: #b91c1c;
}

.cover-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  margin-top: 32px;
}
.cover-error {
  margin: 12px 0 0;
  padding: 8px 10px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 6px;
  color: #991b1b;
  font-size: 13px;
}

.cover-preview {
  margin-top: 24px;
  padding: 16px;
  background: #fafafa;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  /* Fixed frame width (border-box → 248px content) so every preview is the
   * SAME size regardless of heading length. Without this, each .cover-preview
   * flex item sizes to its content's intrinsic width — and since the panes are
   * width:100% (zero intrinsic contribution), the heading text alone drove the
   * frame width, making "Linen Back Cover Preview" smaller than the longer
   * "Dust Jacket Back Cover Preview" and the spine frames mismatch row-to-row.
   * max-width:100% keeps it responsive (shrinks equally on narrow viewports).
   * --book-preview-w is shared with .tp-preview-card so the two pages' previews
   * are the same physical size. */
  width: var(--book-preview-w, 280px);
  max-width: 100%;
}
.cover-preview h2 {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: 700;
  color: #111;
  text-align: center;
}
.cover-preview-placeholder {
  padding: 24px 12px;
  border: 1px dashed #d1d5db;
  border-radius: 8px;
  text-align: center;
  color: #6b7280;
  font-size: 13px;
}
/* Muted caption beneath a preview pane (e.g. the linen front cover note). */
.cover-preview-note {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: #6b7280;
}
/* The pane shape tracks the CHOSEN layout's trim via --book-aspect (set by
 * syncBookAspect() in book-cover.js from the selected book type + trim), so the
 * front/back panes and the spine preview (which derives its height from the
 * matching --book-hw, see .spine-preview-pane below) all share one trim source
 * and line up — the img, placeholder, and solid-panel states included. */
.cover-preview-pane {
  /* --book-aspect: memory_book us_trade 6/9, crown_quarto 7.44/9.68,
     photo_book 7/10 (Executive). Defaults to 6/9. */
  aspect-ratio: var(--book-aspect, 6 / 9);
  position: relative;       /* anchor the absolutely-filled preview img */
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* Cover preview img is the small thumb; click opens the lightbox (JS wires
 * the click handler when the img is ready; the hand cursor signals it). It's
 * absolutely filled + object-fit:contain so the rendered cover (whose ratio
 * may differ slightly from the trim) CANNOT stretch the pane taller — the pane
 * stays a strict trim-aspect box, exactly matching the solid Linen Front Cover
 * Preview beside it. */
.cover-preview-pane img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  border-radius: 4px;
  cursor: pointer;
}
.cover-disclaimer {
  margin: 12px 0 0;
  font-size: 12px;
  color: #6b7280;
  text-align: center;
}

/* /book/cover Spine card — radio row for the orientation toggle. The
 * global `input { width: 100% }` rule on line 51 stretches checkboxes
 * across the row; same width: auto override as .cover-inclusion-list
 * to keep radios next to their labels. */
.cover-radio-row { display: flex; gap: 16px; flex-wrap: wrap; }
.cover-radio {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  font-size: 14px;
  color: #111;
}
.cover-radio input[type="radio"] {
  width: auto;
  padding: 0;
  margin: 0 6px 0 0;
  vertical-align: middle;
}
/* Mirrors the .cover-swatch:has(input[type="radio"]:focus-visible) rule
 * — keyboard users get a visible focus ring on the orientation radios
 * matching the rest of the cover page (the browser default is easy to
 * miss). */
.cover-radio:has(input[type="radio"]:focus-visible) {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
  border-radius: 4px;
}

/* Spine preview aside — shares .cover-preview's 280px/16px frame so the
 * two previews read as a matched pair. The pane itself stays narrow
 * (a spine is intrinsically thin) but its height matches the rendered
 * front-cover img so the silhouettes line up. */
.spine-preview-pane {
  /* Mini spine strip — width is the "mini strip" visual; height derives
   * from the cover-preview-pane content width (248px after 16px padding
   * each side on a 280px-max-width .cover-preview) at the us_trade 6 × 9 in
   * trim AR — so the spine silhouette lines up with the 6×9 front-cover
   * preview. Single source of truth for trim — change the numbers in one
   * place if the trim ever shifts. */
  background: #363c48;     /* default body slate; per-color rules below override */
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  width: 58px;
  /* Height = the front/back PANE's content height so the spine lines up exactly
     with (and is the same height as) the other previews. The pane content width
     is the frame (--book-preview-w) minus its 1px border ×2 + 16px padding ×2
     (= 34px), and pane height = that × the trim's height/width ratio (--book-hw,
     set by JS: 6×9 → 1.5; 7.44×9.68 → ~1.301). */
  height: calc((var(--book-preview-w, 280px) - 34px) * var(--book-hw, 1.5));
  /* No top offset — top-aligns with the back pane in the same row (both sit
   * directly under their identical h2), so the spine + back silhouettes line
   * up. Horizontally centered as a thin "mini strip" in the 280px frame. */
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;      /* anchor the server-rendered spine image overlay */
  cursor: pointer;         /* whole pane is click-to-expand (image or swatch) */
}
/* Server-rendered spine preview (true WYSIWYG) — the faithful PNG from
   POST /api/book/cover/preview/spine (book-cover.js refreshSpinePreview) is
   OVERLAID on the swatch (the .spine-text stays underneath, covered). The spine
   is intrinsically thin, so it's small inline + click-to-zoom (lightbox). The
   contain letterbox bars show the pane's swatch color (= the spine color) so
   they blend. Used by BOTH the dust-jacket (#spine-preview-pane) and linen
   (#linen-spine-preview-pane) spines — same geometry, matched dimensions. */
.spine-preview-pane img.spine-preview-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  cursor: pointer;
}
.spine-preview-pane .spine-text {
  writing-mode: vertical-rl;
  font-family: 'Source Sans 3', Arial, Helvetica, sans-serif;
  font-size: 16px;
  font-weight: 600;
  color: #f4f1ea;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Orientation is fixed — the base writing-mode: vertical-rl reads top-to-bottom
   (rotated), matching Lulu's linen foil. The old sideways/top_down modifiers are
   removed so the dust-jacket, cover, and linen spine previews all match. */
.spine-preview-pane--hidden .spine-text { visibility: hidden; }

/* Per-color tinting of the spine preview — parallel set to the
 * server-side COVER_COLOR_BG_RULES in lib/book/cover.js. Hand-authored
 * to keep the customer-facing /book/cover page self-contained; if the
 * server palette grows beyond 6 entries, switch to JS-injection.
 * Keep in sync with COVER_COLORS in public/js/book-cover.js +
 * lib/book/cover-options.js. */
/* Per-color tint applied directly to the spine pane. The attribute lives
 * on the pane itself (not a parent) so it only updates when Save spine
 * fires — keeping the spine preview a frozen snapshot of saved values
 * while the cover color radio + spine checkbox/radio can be edited
 * freely without the preview reacting. */
.spine-preview-pane[data-cover-color="navy"]  { background: #242b42; }
.spine-preview-pane[data-cover-color="black"] { background: #252628; }
.spine-preview-pane[data-cover-color="green"] { background: #3e5d4a; }
.spine-preview-pane[data-cover-color="gray"]  { background: #484a4c; }
.spine-preview-pane[data-cover-color="red"]   { background: #7a2c2c; }
.spine-preview-pane[data-cover-color="tan"]   { background: #969183; }
/* White is a rendered-only jacket color (no linen cloth). Needed so a white
   spine's pane bg matches the server-rendered white spine — otherwise the
   overlay's letterbox bars fall back to the slate default and don't blend. */
.spine-preview-pane[data-cover-color="white"] { background: #ffffff; }
/* Tan is the only color light enough to need dark spine text — matches
 * COVER_TAN_TEXT_RULES in lib/book/cover.js for the print render. */
.spine-preview-pane[data-cover-color="tan"] .spine-text { color: #2a2620; }

/* Back Cover Preview pane (2026-06-23) — a full-cover-shaped solid panel that
 * sits next to the spine preview in .cover-previews-row. The aspect-ratio +
 * --book-aspect var match the front-cover preview so the silhouette lines up.
 * Background + text color are painted inline by book-cover.js (slug-or-hex →
 * palette swatch, with a readable text color), so there are no per-color CSS
 * rules here (unlike the spine pane). The back text sits bottom-center, mirroring
 * the renderer's .cover-back-text placement. */
.back-preview-pane {
  aspect-ratio: var(--book-aspect, 6 / 9);
  width: 100%;
  background: #242b42;     /* overridden inline; default navy */
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  cursor: pointer;         /* whole pane is click-to-expand (image or swatch) */
}
.back-preview-pane .back-preview-text {
  position: absolute;
  bottom: 8%;
  left: 10%;
  right: 10%;
  text-align: center;
  font-family: 'Source Sans 3', Arial, Helvetica, sans-serif;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: #f4f1ea;          /* overridden inline */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Server-rendered back-cover preview (true WYSIWYG) — the faithful PNG from
   POST /api/book/cover/preview/back (book-cover.js refreshBackPreview) replaces
   the inline swatch above. Absolutely filled + object-fit:contain so the
   rendered back panel (trim-width × full cover height, like the front preview)
   can't stretch the strict trim-aspect pane — mirrors .cover-preview-pane img. */
#back-preview-pane img.back-preview-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  border-radius: 4px;
  cursor: pointer;
}

/* Linen previews (Premium 6×9, 2026-06-23) — the foil-stamped cloth case under
 * the dust jacket. They REUSE .spine-preview-pane / .back-preview-pane for all
 * layout and paint their cloth bg + foil-color spine text INLINE (book-cover.js
 * repaintLinenPanes / render markup), so no per-color rules are needed here. The
 * .linen-*-preview-pane classes exist only as hooks; the linen spine still hides
 * via .spine-preview-pane--hidden. The linen back is a plain cloth panel (no
 * text), so it carries no .back-preview-text child. */

/* Universal click-to-expand affordance. attachImageLightbox (utils.js) stamps
 * role="button" on every preview <img> it wires, so this single rule gives the
 * hand cursor to ALL lightbox thumbnails — present and future — with no
 * per-selector upkeep. Scoped to `img` on purpose: the app's other
 * role="button" elements are <span>/<div> (inline-edit fields, switcher cards),
 * which this does not touch. */
img[role="button"] { cursor: pointer; }

/* Generic lightbox overlay — mirrors the /welcome lightbox rules in
 * share.css so /book/cover can reuse the same UX without pulling in
 * share.css. If a 3rd consumer arrives, dedupe by moving share.css's
 * rules out of share.css entirely. */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.88);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 20px;
  cursor: zoom-out;
  animation: lightbox-fade-in 0.15s ease-out;
}
@keyframes lightbox-fade-in { from { opacity: 0; } to { opacity: 1; } }
.lightbox-overlay img {
  max-width: min(95vw, 540px);
  max-height: 95vh;
  border-radius: 6px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
  cursor: default;
  display: block;
}
/* Enlarged cloth/color SWATCH clone (book-cover.js openLightboxSwatch) — for the
 * linen front/back + the spine/back swatch fallback, which have no server image.
 * The clone is sized to the pane's natural box + scaled via CSS transform (its
 * spine/back text scales too); flex-centered in the overlay. */
.lightbox-overlay .lightbox-swatch {
  flex: 0 0 auto;
  cursor: default;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}
.lightbox-close {
  position: absolute;
  top: 16px;
  right: 16px;
  background: rgba(255, 255, 255, 0.15);
  color: white;
  border: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.lightbox-close:hover { background: rgba(255, 255, 255, 0.25); }

/* /book/print digital proof flow. The digital book is handled inside the main
 * order flow (#step-digital): order-book.js renders the proof state + actions
 * into .db-state / .db-actions. Proof is free; the clean final costs $50. */
.db-state {
  margin: 0 0 16px;
  font-size: 14px;
  color: #333;
}
.db-state p { margin: 0 0 8px; }
.db-state p:last-child { margin: 0; }
.db-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin: 0 0 16px;
}
.db-error {
  margin: 4px 0 0;
  padding: 8px 10px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 6px;
  color: #991b1b;
  font-size: 13px;
}

/* /book/title-pages upload UI (docs/book-restructure-plan.md Phase 7).
 * Inline-with-card-body — same visual weight as the text-field block
 * above it. Thumbnail sits left, upload button right, status + error
 * below. */
.tp-upload-block {
  margin: 16px 0 0;
  padding: 12px;
  background: #fafafa;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
}
.tp-upload-thumb {
  margin: 0 0 12px;
}
.tp-upload-thumb img {
  display: block;
  max-width: 200px;
  max-height: 200px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  background: #fff;
}
.tp-upload-thumb-placeholder {
  padding: 12px 14px;
  background: #fff;
  border: 1px dashed #d1d5db;
  border-radius: 6px;
  font-size: 13px;
  color: #6b7280;
  max-width: 240px;
}
.tp-upload-meta {
  margin-top: 4px;
  font-size: 12px;
  color: #6b7280;
}
.tp-upload-label {
  display: inline-block;
  cursor: pointer;
}
.tp-upload-label .btn-action {
  cursor: pointer;
}
.tp-upload-status {
  margin: 8px 0 0;
  font-size: 13px;
  color: #6b7280;
}
.tp-upload-error {
  margin: 8px 0 0;
  padding: 8px 10px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: 6px;
  color: #991b1b;
  font-size: 13px;
}

/* "Add content" icon row — reusable card-row vocabulary used on two
 * surfaces today:
 *   * /dashboard — decorative <div> tiles ("here's what you can add"
 *     above the Go to Add Content CTA)
 *   * /add-content — interactive <a> tiles that jump to the
 *     relevant section anchor on the same page
 * 4 tiles in a row at desktop, wrapping to 2-up below 480px. Visual
 * rhythm mirrors .quick-link on /dashboard (neutral border, light
 * hover) so the two card vocabularies feel related. */
.add-content-icons {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.add-content-icon-item {
  flex: 1;
  min-width: 120px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 16px 12px;
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: #374151;
}
.add-content-icon-item svg {
  color: #6b7280;
  flex: 0 0 auto;
}
/* Labels size to their actual line count (no 2-line reservation), so
 * the icon+label group sits visually centered inside each tile rather
 * than top-shifted by an empty second line. Trade-off: the icon on the
 * "Emails + Attachments" tile (which wraps to 2 lines) is slightly
 * higher than the icons on the single-line tiles, since flex-row
 * stretch equalizes tile heights and the wrapping tile's larger group
 * pushes its icon up to keep the group centered. */
.add-content-icon-item span {
  display: block;
  line-height: 1.3;
}
/* Link variant — used on /add-content where each tile jumps to a
 * section on the same page. Hover lifts the tile via border + bg shift
 * to match the .quick-link interaction pattern. */
a.add-content-icon-item {
  text-decoration: none;
  color: #374151;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}
a.add-content-icon-item:visited { color: #374151; }
a.add-content-icon-item:hover {
  border-color: #111;
  background: #f9fafb;
  color: #111;
}
a.add-content-icon-item:hover svg { color: #111; }
a.add-content-icon-item:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}
@media (max-width: 480px) {
  .add-content-icon-item { min-width: calc(50% - 6px); }
}

/* iOS Safari auto-zooms the viewport whenever a focused form control renders
   below 16px, and never fully zooms back out — leaving the page magnified
   after the first tap on a field. Many controls are intentionally denser than
   16px on desktop (account fields, the print-flow ledger/promo/typography
   inputs, the cover hex/date inputs, admin tools, the inline-edit inputs on
   the content-group pages, etc.), scattered across this file, book-print.css,
   and ~14 per-page inline <style> blocks. Rather than lift each one (and miss
   future pages), clamp every form control to the 16px focus-zoom floor at
   <=768px (phones + tablet portrait) only. `!important` is required to win
   over the more-specific class/id rules in the later-loading inline styles;
   it is scoped to the mobile breakpoint so desktop sizing is byte-for-byte
   unchanged. Verified safe: nothing sizes a form control >=17px or inside a
   media query anywhere, so this clamps nothing intentional. */
@media (max-width: 768px) {
  input, select, textarea { font-size: 16px !important; }
}

/* Responsive-table primitive. Wrap a wide <table> in
   <div class="table-scroll"> so it scrolls horizontally within its column
   instead of forcing the whole page wider than a phone viewport. The block
   wrapper is required because a <table> can't scroll its own overflow.
   Desktop is unaffected — overflow-x:auto only engages when the content is
   actually wider than the wrapper. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
