/* --------------------------------------------------------------------
   Palette — cream chrome around the green play area.

   The page is warm paper. Text is ink. Accents are terracotta.
   Designed to sit happily over the green felt while the header is
   visible on the puzzle page (cream paper band over a felt table).
   -------------------------------------------------------------------- */
:root {
  --bg:           #f3ecd6;  /* page background — warm cream */
  --surface:      #faf4e2;  /* cards, header, inputs — lighter cream */
  --surface-hi:   #fffaeb;  /* hovers, lifted surfaces */

  --ink:          #2a2620;  /* primary text — warm near-black */
  --ink-soft:     #6a6055;  /* body text, secondary labels */
  --ink-faint:    #9a917f;  /* tertiary, meta, placeholder */

  --line:         #e1d6b9;  /* subtle warm divider */
  --line-strong:  #cdbf9b;  /* input borders, card borders */

  --accent:       #b8553a;  /* terracotta — primary action */
  --accent-hover: #9d4530;
  --accent-tint:  #f3e0d4;  /* very soft accent wash */
  --on-accent:    #fbf5e3;  /* text on accent surfaces */

  --danger:       #a83a3a;
  --danger-soft:  rgba(168, 58, 58, 0.10);
  --success:        #4caf50;
  --success-soft:   rgba(76, 175, 80, 0.14);
  --success-pastel: #a5d6a7;  /* lighter green for in-progress own puzzles */
  --accent-pastel:  #d6a08c;  /* lighter terracotta for joined in-progress  */

  --shadow-card:  0 1px 2px rgba(42, 38, 32, 0.04),
                  0 6px 18px rgba(42, 38, 32, 0.06);
  --shadow-pop:   0 8px 24px rgba(42, 38, 32, 0.14);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: system-ui, -apple-system, sans-serif;
  background: var(--bg);
  color: var(--ink);
  min-height: 100vh;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.container {
  max-width: 960px;
  margin: 0 auto;
  padding: 2rem 1rem;
}

/* Wide variant for grid-heavy pages (gallery, puzzles, sandbox) — lets the
   puzzle grid / picker grow up to 5 columns. Intro text inside still wraps
   at its own max-width (.about-intro). */
.container.wide {
  max-width: 1380px;
}

/* --- Content rhythm ---
   The global `* { margin: 0 }` reset above leaves block flow flat; these
   rules restore consistent vertical spacing for content pages (gallery,
   home, my-puzzles, puzzles, contact, etc). Cards and forms keep their
   own internal margins via more-specific selectors. */
.container h1,
.container h2 {
  margin-bottom: 0.75rem;
}

.container h3 {
  margin-bottom: 0.5rem;
}

.container p {
  margin-bottom: 1rem;
}

.container .page-header {
  margin-bottom: 1rem;
}

.container .page-header h1,
.container .page-header h2 {
  margin-bottom: 0;
}

.container section,
.container .puzzle-section {
  margin-bottom: 2.5rem;
}

.container section:last-child,
.container .puzzle-section:last-child {
  margin-bottom: 0;
}

.container .puzzle-grid,
.container .puzzle-picker,
.container .news-list {
  margin-top: 0.5rem;
}

.lang-banner {
  padding: 0.5rem 2rem;
  background: var(--accent-tint);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  font-size: 0.92rem;
}

.lang-banner-msg {
  color: var(--ink);
}

.lang-banner-actions {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

.lang-banner-switch {
  color: var(--accent);
  font-weight: 500;
}

.lang-banner-stay {
  color: var(--ink-soft);
}

/* Guest banner: stripe under the header that nudges anonymous visitors
   to sign in. Same warm wash as the language banner so the chrome stays
   coherent, with a faint accent rule on top to flag it as a separate
   notice (and to read clearly on the puzzle page where it sits between
   cream header and green felt). */
.guest-banner {
  padding: 0.5rem 2rem;
  background: var(--accent-tint);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  flex-wrap: wrap;
  font-size: 0.92rem;
}

.guest-banner-msg {
  color: var(--ink);
}

.guest-banner-actions {
  display: flex;
  gap: 1rem;
  align-items: center;
}

.guest-banner-actions a {
  color: var(--accent);
  font-weight: 500;
}

header {
  padding: 0.85rem 2rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

header h1 {
  font-size: 1.4rem;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

header h1 a {
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
header h1 a:hover { text-decoration: none; }

.brand-icon {
  width: 1.5rem;
  height: 1.5rem;
  display: block;
}

header nav {
  display: flex;
  gap: 1.5rem;
  align-items: center;
}

header nav > a {
  color: var(--ink-soft);
  font-size: 0.95rem;
}

header nav > a:hover {
  color: var(--ink);
  text-decoration: none;
}

.nav-spacer {
  width: 1rem;
}

.user-menu {
  position: relative;
  outline: none;
  cursor: default;
}

.user-menu-label {
  color: var(--ink-soft);
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.user-menu:hover .user-menu-label,
.user-menu:focus-within .user-menu-label {
  color: var(--ink);
}

.user-menu-caret {
  font-size: 1em;
  opacity: 0.7;
  transition: transform 0.15s;
}

.user-menu:hover .user-menu-caret,
.user-menu:focus-within .user-menu-caret {
  transform: rotate(-180deg);
}

.user-menu-items {
  position: absolute;
  top: 100%;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.35rem 0;
  display: none;
  flex-direction: column;
  box-shadow: var(--shadow-pop);
  z-index: 10;
}

.user-menu:hover .user-menu-items,
.user-menu:focus-within .user-menu-items {
  display: flex;
}

.user-menu-items a,
.user-menu-items .lang-current {
  padding: 0.45rem 0.85rem;
  color: var(--ink);
  white-space: nowrap;
}

.user-menu-items a:hover,
.user-menu-items form.menu-action button:hover {
  background: var(--accent-tint);
  color: var(--ink);
  text-decoration: none;
}

/* The log-out menu entry is a tiny POST form (so a cross-site GET can't
   sign the user out). `display: contents` keeps the button laid out as
   if it were a direct child of `.user-menu-items`, so it matches the
   adjacent anchors. */
.user-menu-items form.menu-action {
  display: contents;
}

.user-menu-items form.menu-action button {
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  text-align: left;
  padding: 0.45rem 0.85rem;
  color: var(--ink);
  white-space: nowrap;
}

.user-menu-items .lang-current {
  color: var(--accent);
  font-weight: 600;
  cursor: default;
}

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 2.35rem;
}

.btn-primary {
  display: inline-block;
  padding: 0.5rem 1rem;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 4px;
  font: inherit;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: background 0.15s;
}

.btn-primary:hover {
  background: var(--accent-hover);
  text-decoration: none;
}

.btn-primary:disabled {
  background: var(--surface-hi);
  color: var(--ink-faint);
  cursor: not-allowed;
}

.btn-primary:disabled:hover {
  background: var(--surface-hi);
}

/* Puzzle grid (lobby, my puzzles) */
.puzzle-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1.25rem;
}

.puzzle-card {
  background: var(--surface);
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--line);
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.15s;
  text-decoration: none;
  color: inherit;
  display: block;
}

.puzzle-card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-card);
  text-decoration: none;
}

.puzzle-card.completed {
  cursor: default;
}

.puzzle-card.completed:hover {
  border-color: var(--line);
  box-shadow: none;
}

.puzzle-card img {
  width: 100%;
  aspect-ratio: 3/2;
  object-fit: cover;
  display: block;
}

.card-img-wrap {
  position: relative;
}

/* Placeholder shown in place of the motif preview when a puzzle was
   created with the preview disabled and isn't yet completed. Same
   aspect-ratio as .puzzle-card img so the card layout doesn't jump
   when comparing flagged and unflagged puzzles. */
.card-img-hidden {
  width: 100%;
  aspect-ratio: 3/2;
  background:
    radial-gradient(120% 90% at 50% 35%,
                    var(--surface-hi) 0%,
                    var(--line) 60%,
                    var(--line-strong) 100%);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: help;
}

.card-img-hidden-glyph {
  font:
    700 4.5rem/1 ui-serif, Georgia, "Times New Roman", serif;
  color: var(--ink-faint);
  opacity: 0.7;
  user-select: none;
}

.progress-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  background: rgba(42, 38, 32, 0.78);
  color: #fff;
  font: 600 0.9rem/1 system-ui, sans-serif;
  padding: 4px 8px;
  border-radius: 5px;
}

.completed-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  background: var(--success);
  color: #fff;
  font: 600 0.8rem/1 system-ui, sans-serif;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 5px 9px;
  border-radius: 5px;
  box-shadow: 0 1px 3px rgba(42, 38, 32, 0.3);
}

.time-badge {
  position: absolute;
  bottom: 8px;
  left: 8px;
  background: rgba(42, 38, 32, 0.78);
  color: #fff;
  font: 600 0.85rem/1 system-ui, sans-serif;
  padding: 4px 8px;
  border-radius: 5px;
}

.owner-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font: 600 0.75rem/1 system-ui, sans-serif;
  letter-spacing: 0.03em;
  padding: 4px 8px;
  border-radius: 5px;
}

.puzzle-card .info {
  padding: 0.6rem 0.85rem 0.7rem;
}

.puzzle-card .info h3 {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 0.2rem;
}

.puzzle-card .info .meta {
  font-size: 0.8rem;
  color: var(--ink-faint);
  display: block;
}

.empty {
  color: var(--ink-faint);
  margin-top: 1rem;
}


.puzzle-section h3 {
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.4rem;
}

.section-empty {
  margin-top: 0.6rem;
  font-size: 0.9rem;
}

/* "More by <artist>" / "More from this series" sections on the motif
   detail page. Each heading gets extra top space so the related grid
   reads as a fresh block beneath the puzzle form. */
.related-heading {
  margin-top: 2.5rem;
}

/* Auth forms */
.auth-form {
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 1.5rem;
}

.auth-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.auth-form input {
  padding: 0.6rem 0.75rem;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  color: var(--ink);
  font-size: 1rem;
}

.auth-form input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

.auth-form input:disabled {
  color: var(--ink-faint);
  background: var(--bg);
  border-color: var(--line);
  cursor: not-allowed;
}

.auth-form input.field-invalid {
  border-color: var(--danger);
}

.auth-form input.field-invalid:focus {
  box-shadow: 0 0 0 3px var(--danger-soft);
}

.auth-form .inline-error {
  font-size: 0.8rem;
  color: var(--danger);
  margin: 0;
}

/* The Change-password trigger lives inside `.auth-form` (so it sits with
   the email field), but visually it's a secondary action — not the form's
   primary submit. Override the generic `.auth-form button` accent styling
   so it reads as the subtle .btn-secondary it is. */
.auth-form .change-password-trigger {
  align-self: flex-start;
  padding: 0.4rem 0.75rem;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  font-size: 0.9rem;
  font-weight: 500;
  margin-top: -0.25rem;
}

.auth-form .change-password-trigger:hover {
  background: var(--surface-hi);
  border-color: var(--ink-faint);
}

.auth-form .field-help {
  font-size: 0.8rem;
  color: var(--ink-faint);
  margin-top: 0.15rem;
}

.auth-form .field-error {
  font-size: 0.8rem;
  color: var(--danger);
  margin-top: 0.15rem;
}

.auth-form button:disabled {
  background: var(--line-strong);
  color: var(--ink-faint);
  cursor: not-allowed;
}

.auth-form button:disabled:hover {
  background: var(--line-strong);
}

/* Pending email-change notice + inline verify/cancel form, rendered below
   the main profile form when the user has requested a new address and is
   waiting on the code. Subtle inset card rather than an alert. */
.pending-email-change {
  max-width: 360px;
  margin-top: 1.5rem;
  padding: 1rem 1.25rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
}

.pending-email-change > p {
  margin: 0 0 0.75rem 0;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.pending-email-change .auth-form {
  margin-top: 0;
}

.pending-email-change .row {
  display: flex;
  gap: 0.5rem;
}

/* Password-change modal — fixed full-screen overlay with a dim backdrop
   and a centered card. Hidden on first paint via inline display:none
   (Datastar removes the rule when $passwordOpen flips truthy, see
   pages/change-password-section for the flash-prevention comment). */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(36, 28, 22, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 1rem;
}

.modal-content {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1.25rem 1.5rem 1.5rem;
  max-width: 360px;
  width: 100%;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}

.modal-title {
  margin: 0 0 1rem 0;
  font-size: 1.1rem;
  font-weight: 600;
}

.modal-error {
  margin: 0 0 1rem 0;
  padding: 0.6rem 0.75rem;
  background: var(--danger-soft);
  color: var(--danger);
  border-radius: 4px;
  font-size: 0.9rem;
}

.modal-content .auth-form {
  margin-top: 0;
}

.modal-content .row {
  display: flex;
  gap: 0.5rem;
}

.color-field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.color-field-label {
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.color-field-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.color-swatch-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  position: relative;
  /* Don't stretch to the form's full width; otherwise `left: 100%` on the
     popup would land at the form's right edge instead of the button's. */
  align-self: flex-start;
}

.auth-form .color-help {
  /* Sits inline next to the swatch instead of stacked under it; no top gap. */
  margin-top: 0;
  flex: 1 1 0;
  min-width: 0;
}

/* Selectors below use `.auth-form .X` to beat the generic `.auth-form button`
   rule (defined later in this file) on specificity — otherwise its
   border-radius / padding / margin-top leak onto every button inside the
   form, including the swatch and palette cells. */

.auth-form .color-swatch {
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  margin: 0;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 120ms ease-out;
}

.auth-form .color-swatch:hover,
.auth-form .color-swatch:focus {
  outline: none;
  border-color: var(--accent);
}

.color-palette {
  /* Floats next to the swatch button, vertically centered with it. */
  position: absolute;
  top: 50%;
  left: 100%;
  transform: translateY(-50%);
  margin-left: 0.5rem;
  display: grid;
  grid-auto-rows: 1.25rem;
  /* grid-template-columns set inline so it stays in sync with colors/cols */
  background: var(--surface);
  box-shadow: var(--shadow-pop);
  z-index: 20;
  width: max-content;
}

.color-palette.hidden {
  display: none;
}

.auth-form .palette-cell {
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  display: block;
  outline: none;
}

.auth-form .palette-cell:hover {
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.7);
  z-index: 1;
}

.auth-form .palette-cell.selected {
  /* Selection ring sits inside the cell so it doesn't reflow neighbours. */
  box-shadow:
    inset 0 0 0 2px var(--surface),
    inset 0 0 0 4px var(--ink);
  z-index: 2;
}

.auth-form button {
  padding: 0.7rem;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  margin-top: 0.5rem;
  transition: background 0.15s;
}

.auth-form button:hover {
  background: var(--accent-hover);
}

.auth-link {
  margin-top: 1rem;
  font-size: 0.9rem;
  color: var(--ink-faint);
}

/* New puzzle — motif picker */
.section-label {
  font-size: 0.85rem;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.5rem;
}

.puzzle-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin-top: 0.5rem;
  margin-bottom: 1.5rem;
}

.motif-card.selectable {
  position: relative;
  width: 240px;
  cursor: pointer;
  border: 2px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  transition: border-color 0.2s, box-shadow 0.2s;
}

.motif-card.selectable:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-card);
}

.motif-card.selectable img {
  width: 100%;
  aspect-ratio: 3/2;
  object-fit: cover;
  display: block;
  border-radius: 8px 8px 0 0;
}

.motif-card.selectable .info {
  padding: 0.6rem 0.85rem 0.7rem;
}

.motif-card.selectable .info h3 {
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: 0.15rem;
  color: var(--ink);
}

.motif-card.selectable .info .meta {
  font-size: 0.8rem;
  color: var(--ink-faint);
  display: block;
}

/* Motif detail */
.back-link {
  display: inline-block;
  margin-bottom: 1.25rem;
  font-size: 0.9rem;
  cursor: pointer;
  color: var(--accent);
  transition: opacity 0.15s;
}

.back-link:hover {
  opacity: 0.75;
  text-decoration: none;
}

.motif-detail {
  display: flex;
  gap: 1.5rem;
  margin-bottom: 2rem;
  align-items: flex-start;
}

.motif-preview {
  border-radius: 8px;
  overflow: auto;
  flex-shrink: 0;
  background: var(--surface);
}

.motif-preview img {
  display: block;
}

/* Zoomable preview (gallery detail). The outer wrapper is a fixed-size
   placeholder so surrounding layout never shifts. At rest the inner
   fills the placeholder; when `.expanded` is on, the inner promotes to
   position:fixed, centers in the viewport, and fills the viewport
   height — a backdrop sibling dims the rest of the page. */
.motif-preview.zoomable {
  position: relative;
  overflow: visible;
  background: transparent;
  border-radius: 0;
  /* width/height come from the inline `style` attribute (placeholder size) */
}

.motif-preview.zoomable .motif-preview-inner {
  position: absolute;
  inset: 0;
  background: var(--surface);
  border-radius: 8px;
  overflow: auto;
  cursor: zoom-in;
  view-transition-name: motif-preview;
}

.motif-preview.zoomable .motif-preview-inner img {
  display: block;
}

/* Image sizing by aspect — applies to both small and (with the expanded
   override below) lightbox states. */
.motif-preview.zoomable[data-aspect-case="normal"] .motif-preview-inner img {
  width: 100%;
  height: 100%;
}
.motif-preview.zoomable[data-aspect-case="wide"] .motif-preview-inner img {
  height: 100%;
  width: auto;
}
.motif-preview.zoomable[data-aspect-case="tall"] .motif-preview-inner img {
  width: 100%;
  height: auto;
}

/* Expanded (lightbox) state — promoted to position:fixed and
   viewport-centered. The actual entry/exit morph between the in-card
   state and this one is driven by the View Transitions API (see the
   view-transition-name on .motif-preview-inner above and the click
   handler that wraps the class toggle in document.startViewTransition).
   Browsers without that API get an instant jump. */
.motif-preview.zoomable.expanded .motif-preview-inner {
  position: fixed;
  inset: auto;
  top: 50%;
  left: 50%;
  width: auto;
  height: 90vh;
  max-width: 95vw;
  transform: translate(-50%, -50%);
  cursor: zoom-out;
  z-index: 1001;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.35);
}

/* In the lightbox the image always fills the height; width follows the
   natural aspect, and the wrapper's max-width + overflow:auto handles
   extreme-aspect images. */
.motif-preview.zoomable.expanded .motif-preview-inner img {
  width: auto;
  height: 100%;
  max-width: none;
}

.motif-preview-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
  cursor: zoom-out;
}

/* Tune view-transition durations to match.
   The backdrop is intentionally NOT given its own view-transition-name —
   it rides along with the root snapshot's crossfade, so the morphing
   preview (which is lifted onto its own transition layer) never sits
   underneath a rising backdrop mid-transition. */
::view-transition-old(motif-preview),
::view-transition-new(motif-preview),
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}

.size-option:disabled {
  cursor: not-allowed;
  color: var(--ink-faint);
  opacity: 0.6;
}

.size-option:disabled:hover {
  border-color: var(--line);
  background: var(--surface);
}

.motif-info {
  padding-top: 0.25rem;
}

.motif-info h3 {
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 0.35rem;
}

.motif-info .artist {
  color: var(--ink-soft);
  font-size: 0.95rem;
  margin-bottom: 0.35rem;
}

.motif-info .series {
  color: var(--ink-faint);
  font-size: 0.9rem;
  font-style: italic;
  margin-bottom: 0.35rem;
}

.meta.series {
  font-style: italic;
}

.motif-info .link {
  font-size: 0.85rem;
}

/* Size picker */
.size-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 0.5rem;
  margin-bottom: 1.5rem;
}

.size-option {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.75rem 1.5rem;
  background: var(--surface);
  border: 2px solid var(--line);
  border-radius: 8px;
  color: var(--ink);
  cursor: pointer;
  transition: all 0.15s ease;
}

.size-option:hover {
  border-color: var(--line-strong);
  background: var(--surface-hi);
}

.size-option.selected {
  background: var(--accent-tint);
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-tint);
}

.size-option .piece-count {
  font-size: 1.4rem;
  font-weight: 700;
  line-height: 1.2;
}

.size-option .dimensions {
  font-size: 0.78rem;
  color: var(--ink-faint);
  transition: color 0.15s;
}

.size-option.selected .dimensions {
  color: var(--accent-hover);
}

/* Solved mark — circular badge on motif cards and size buttons showing the
   current user's relationship to a motif/box. Bright = completed, pastel =
   in progress; green = you started the puzzle, terracotta = you joined
   someone else's. The motif-card variant overhangs the card edge. */
.solved-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  border-radius: 50%;
}

.solved-mark.own-done        { background: var(--success); }
.solved-mark.own-progress    { background: var(--success-pastel); }
.solved-mark.joined-done     { background: var(--accent); }
.solved-mark.joined-progress { background: var(--accent-pastel); }

.motif-card .solved-mark {
  position: absolute;
  top: -10px;
  right: -10px;
  width: 32px;
  height: 32px;
  border: 2px solid #fff;
  box-shadow: 0 2px 6px rgba(42, 38, 32, 0.18);
  z-index: 1;
}

.size-option .solved-mark {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 22px;
  height: 22px;
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(42, 38, 32, 0.15);
}

/* Puzzle form */
.puzzle-form {
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.puzzle-form.hidden {
  display: none;
}

.puzzle-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.puzzle-form input[type="text"] {
  padding: 0.6rem 0.75rem;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  color: var(--ink);
  font-size: 1rem;
}

.puzzle-form input[type="text"]:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

.puzzle-form button[type="submit"],
.puzzle-form a.puzzle-form-join {
  display: block;
  padding: 0.7rem;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  margin-top: 0.5rem;
  text-align: center;
  text-decoration: none;
  transition: background 0.15s;
}

.puzzle-form button[type="submit"]:hover,
.puzzle-form a.puzzle-form-join:hover {
  background: var(--accent-hover);
  text-decoration: none;
}

.puzzle-form-notice {
  padding: 0.6rem 0.75rem;
  background: var(--accent-tint);
  border-radius: 4px;
  font-size: 0.9rem;
  color: var(--ink);
  margin: 0;
}

/* Notice + join link travel together: tighter than the form's
   1rem column gap, since they're an explanation and its CTA. The
   wrapper inherits the form's gap-1rem from above and adds 0.5rem
   margin-top so its top edge sits where a button would. */
.puzzle-form-cta-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: 0.5rem;
}

.puzzle-form-cta-group .puzzle-form-join {
  margin-top: 0;
}

.toggle-label {
  flex-direction: row !important;
  align-items: center;
  gap: 0.5rem !important;
  cursor: pointer;
}

.toggle-label .toggle {
  width: 1.1rem;
  height: 1.1rem;
  accent-color: var(--accent);
}

/* Feedback messages */
.error {
  color: var(--danger);
  background: var(--danger-soft);
  padding: 0.6rem 0.75rem;
  border-radius: 4px;
  font-size: 0.9rem;
}

.success {
  color: var(--success);
  background: var(--success-soft);
  padding: 0.6rem 0.75rem;
  border-radius: 4px;
  font-size: 0.9rem;
}

/* --- Avatar (reused by profile page + future puzzle badges) --- */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  user-select: none;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.avatar-initial {
  color: #fff;
  font-weight: 600;
  font-size: 2.5rem;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}

/* --- Profile picture section --- */

.profile-pic-section {
  margin-bottom: 1.5rem;
}

.profile-pic-view {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

.profile-pic-trigger {
  position: relative;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  line-height: 0;
}

.profile-pic-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.profile-pic-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(42, 38, 32, 0.55);
  color: #fff;
  font-size: 0.85rem;
  opacity: 0;
  transition: opacity 120ms ease-out;
  text-align: center;
  padding: 0 0.5rem;
}

.profile-pic-trigger:hover .profile-pic-overlay,
.profile-pic-trigger:focus-visible .profile-pic-overlay {
  opacity: 1;
}

.profile-pic-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: flex-start;
}

.btn-secondary {
  padding: 0.45rem 0.9rem;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.btn-secondary:hover {
  background: var(--surface-hi);
  border-color: var(--ink-faint);
}

.btn-danger {
  color: var(--danger);
}

.btn-danger:hover {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger);
}

.profile-pic-error {
  margin-top: 0.5rem;
}

/* --- Cropper --- */

.cropper {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 0.5rem;
  max-width: 320px;
}

.cropper-stage {
  position: relative;
  width: 320px;
  height: 320px;
  background: #1a1714;
  border-radius: 4px;
  overflow: hidden;
}

#pp-canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  touch-action: none;
}

#pp-canvas:active {
  cursor: grabbing;
}

/* Diagonal stripes outside the inscribed circle to indicate the cropped-out
   area. `closest-side` makes 100% of the radial mask equal the inscribed-circle
   radius — without it the default `farthest-corner` extent shrinks the
   transparent disc to ~70% of the square. */
.cropper-mask {
  pointer-events: none;
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(135deg,
      rgba(26, 23, 20, 0.75) 0,
      rgba(26, 23, 20, 0.75) 10px,
      rgba(243, 236, 214, 0.35) 10px,
      rgba(243, 236, 214, 0.35) 20px);
  -webkit-mask:
    radial-gradient(circle closest-side at 50% 50%,
      transparent 0,
      transparent calc(100% - 1px),
      #000 100%);
  mask:
    radial-gradient(circle closest-side at 50% 50%,
      transparent 0,
      transparent calc(100% - 1px),
      #000 100%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15);
}

.cropper-help {
  margin: 0;
  color: var(--ink-faint);
  font-size: 0.85rem;
}

.cropper-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

#pp-zoom {
  flex: 1;
  accent-color: var(--accent);
}

.cropper-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
}

.cropper-actions button {
  padding: 0.5rem 1rem;
  font-size: 0.9rem;
  font-weight: 600;
  border-radius: 4px;
  cursor: pointer;
  border: 0;
}

.cropper-actions button[type="submit"] {
  background: var(--accent);
  color: var(--on-accent);
}

.cropper-actions button[type="submit"]:disabled {
  background: var(--line-strong);
  color: var(--ink-faint);
  cursor: not-allowed;
}

.cropper-actions button[type="submit"]:not(:disabled):hover {
  background: var(--accent-hover);
}

.field-error {
  color: var(--danger);
  font-size: 0.85rem;
  margin: 0;
}

/* About + News landing */
.about-intro {
  max-width: 60ch;
  line-height: 1.55;
  color: var(--ink-soft);
}

.news-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.news-item time {
  display: block;
  font-size: 0.8rem;
  color: var(--ink-faint);
  margin-bottom: 0.25rem;
}

/* --- Landing page (/) --- */
.landing-hero {
  text-align: center;
  padding: 2.5rem 1rem 1.5rem;
}

.landing-hero .landing-headline {
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-bottom: 0.9rem;
}

.landing-tagline {
  font-size: 1.1rem;
  color: var(--ink-soft);
  max-width: 56ch;
  margin: 0 auto 1.5rem;
  line-height: 1.55;
}

.landing-puzzle-feature {
  max-width: 560px;
  margin: 0 auto 1.5rem;
}

.landing-puzzle-feature-label {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  margin-bottom: 0.5rem;
}

.landing-puzzle-feature .puzzle-card {
  display: flex;
  align-items: stretch;
  text-align: left;
}

.landing-puzzle-feature .card-img-wrap {
  width: 60%;
  flex-shrink: 0;
}

.landing-puzzle-feature .puzzle-card img {
  height: 100%;
  border-radius: 10px 0 0 10px;
}

.landing-puzzle-feature .card-img-hidden {
  height: 100%;
  border-radius: 10px 0 0 10px;
  aspect-ratio: auto; /* the wrap controls the size in the side-by-side layout */
  border-bottom: none;
  border-right: 1px solid var(--line);
}

.landing-puzzle-feature .puzzle-card .info {
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex-grow: 1;
}

.landing-puzzle-feature .puzzle-card .info h3 {
  font-size: 1.05rem;
  margin-bottom: 0.4rem;
}

.landing-puzzle-feature .puzzle-card .info .meta {
  font-size: 0.85rem;
  margin-bottom: 0.15rem;
}

.landing-cta {
  margin-bottom: 0;
}

.landing-cta .btn-primary {
  font-size: 1rem;
  padding: 0.7rem 1.4rem;
}

.value-props {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.75rem 2rem;
}

.value-prop h3 {
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--ink);
}

.value-prop p {
  color: var(--ink-soft);
  line-height: 1.55;
  margin-bottom: 0;
}

.featured-cta {
  margin-top: 1rem;
  margin-bottom: 0;
}

.featured-cta .btn-primary {
  font-size: 1rem;
  padding: 0.7rem 1.4rem;
}

.news-title {
  margin: 0 0 0.4rem;
  font-size: 1.05rem;
}

.news-body {
  max-width: 60ch;
  line-height: 1.55;
  color: var(--ink-soft);
  white-space: pre-wrap;
}

/* --- How to play (/how-to-play) ---
   Two-column sections that alternate the side the screenshot lives on,
   so the page reads with a gentle zigzag rather than a tall column of
   identical blocks. Text-only sections (intro, shortcuts table) span the
   full width. Below the alternation breakpoint everything stacks. */

.container.how-to .about-intro {
  margin-bottom: 2rem;
  font-size: 1.05rem;
  max-width: 64ch;
}

/* Two-column layout above the breakpoint: content + sticky TOC on the
   right. Below the breakpoint, the TOC hides and content reclaims the
   full container — the standard reading width. The breakpoint sits well
   above the default 960px container so the TOC only appears when the
   viewport has clear room for it; otherwise we'd squeeze either the
   reading column or the sidebar. */
.how-to-toc {
  display: none;
}

@media (min-width: 1200px) {
  .container.how-to {
    max-width: 1280px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 220px;
    gap: 3rem;
    align-items: start;
  }

  .how-to-toc {
    display: block;
    position: sticky;
    top: 1.25rem;
    font-size: 0.92rem;
    line-height: 1.5;
  }

  /* The page-header / intro / sections all live inside .how-to-content
     so they share the content column. The zoom backdrop is fixed and
     doesn't participate in the grid. */
  .how-to-content {
    min-width: 0; /* lets the content column shrink within minmax(0, 1fr) */
  }
}

.how-to-toc-heading {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: 0.65rem;
}

.how-to-toc ol {
  list-style: none;
  padding: 0;
  margin: 0;
}

.how-to-toc li {
  border-left: 2px solid transparent;
  padding-left: 0.65rem;
  margin-bottom: 0.35rem;
  transition: border-color 0.15s ease;
}

.how-to-toc a {
  color: var(--ink-soft);
  text-decoration: none;
}

.how-to-toc a:hover {
  color: var(--accent);
}

.how-to-toc li.current {
  border-left-color: var(--accent);
}

.how-to-toc li.current a {
  color: var(--accent);
  font-weight: 500;
}

/* Smooth scroll for in-page anchor links (TOC clicks). Applied page-wide
   because scroll-behavior only takes effect on the scrolling root. */
html {
  scroll-behavior: smooth;
}

/* Headroom under the sticky site header when an anchor lands on a section,
   so the heading isn't tucked behind the chrome. */
.how-to-section {
  scroll-margin-top: 1rem;
}

.how-to-section {
  display: grid;
  gap: 2rem;
  align-items: start;
  grid-template-columns: 1fr;
}

.how-to-section-with-image {
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
}

.how-to-section-image-left .how-to-section-figure {
  order: -1;
}

.how-to-section h3 {
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  margin-bottom: 0.6rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--line);
}

/* Small muted suffix on advanced section headings + TOC entries. Reads
   as a parenthetical "(advanced)" hint without competing with the main
   heading or TOC item. */
.how-to-section-tag,
.how-to-toc-tag {
  font-size: 0.72em;
  font-weight: 500;
  color: var(--ink-faint);
  margin-left: 0.5em;
  letter-spacing: 0;
}

.how-to-subsection {
  margin-bottom: 1.1rem;
}

.how-to-subsection:last-child {
  margin-bottom: 0;
}

.how-to-subsection h4 {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 0.2rem;
}

.how-to-subsection p {
  color: var(--ink-soft);
  line-height: 1.55;
  margin-bottom: 0;
}

.how-to-section-text p {
  color: var(--ink-soft);
  line-height: 1.6;
  margin-bottom: 0.85rem;
}

.how-to-section-text p:last-child {
  margin-bottom: 0;
}

/* Soft callout for "Note: …" asides. Sits inside a section's text column
   alongside regular paragraphs but reads as parenthetical — smaller text,
   muted colour, a thin terracotta rule on the leading edge so the eye
   parses it as an aside before reading the words. */
.how-to-note {
  font-size: 0.88rem;
  color: var(--ink-faint);
  border-left: 2px solid var(--accent-tint);
  padding-left: 0.75rem;
  margin-top: 0.4rem;
  line-height: 1.55;
}

.how-to-section-figure {
  /* No position: sticky here — it creates a stacking context that traps
     the `.expanded` image (position: fixed) inside the figure's stack,
     letting later sections' images paint over it and the backdrop dim
     it. Keep the figure in the normal flow so the expanded lightbox
     correctly lifts to the root stacking context. */
  margin: 0;
}

/* Zoom mechanics (mirrors the gallery preview pattern):
   - .how-to-section-figure-placeholder is a fixed-aspect outer box that
     stays in the page flow. Its inline aspect-ratio (set from the PNG's
     natural dimensions) reserves the slot so when the inner zoomable
     lifts out of flow, the column below doesn't shift.
   - .how-to-zoomable carries the visible frame (background, border,
     padding, the img). At rest it absolutely fills the placeholder.
     When .expanded is added it lifts to position: fixed at viewport
     center; the placeholder keeps holding the layout.
   - The morph uses the View Transitions API with a single shared name
     `how-to-active`, applied imperatively to only the clicked image so
     other images stay in the root snapshot and don't form competing
     stacking groups. See how-to-zoom-click-handler in pages.clj. */
.how-to-section-figure-placeholder {
  position: relative;
  width: 100%;
}

.how-to-zoomable {
  position: absolute;
  inset: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.5rem;
  box-shadow: var(--shadow-card);
  cursor: zoom-in;
  transition: box-shadow 0.15s ease;
  overflow: hidden;
}

.how-to-zoomable:hover {
  box-shadow: var(--shadow-pop);
}

.how-to-zoomable img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 6px;
  display: block;
}

/* Paired images stack inside a single column with a small gap so the
   sequence reads as step 1 → step 2 without an explicit arrow. */
.how-to-section-figure.paired {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.how-to-zoomable.expanded {
  position: fixed;
  inset: auto;
  top: 50%;
  left: 50%;
  width: auto;
  height: 90vh;
  max-width: 95vw;
  transform: translate(-50%, -50%);
  cursor: zoom-out;
  z-index: 1001;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.35);
}

.how-to-zoomable.expanded img {
  width: auto;
  height: 100%;
  max-width: none;
  max-height: calc(90vh - 1rem);
}

.how-to-zoom-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1000;
  cursor: zoom-out;
}


.how-to-section-figure figcaption {
  margin-top: 0.55rem;
  font-size: 0.85rem;
  color: var(--ink-faint);
  line-height: 1.4;
  text-align: center;
  font-style: italic;
}

/* Keyboard-shortcut table sits inside a text-only section. Width is
   left to auto so the Action column can stretch to fit its longest
   row without wrapping. With `white-space: nowrap` on the cells, the
   table grows horizontally as needed; on the desktop-only target the
   section column has plenty of room. */
.how-to-shortcuts {
  border-collapse: collapse;
  margin-top: 0.75rem;
  font-size: 0.95rem;
}

.how-to-shortcuts th,
.how-to-shortcuts td {
  text-align: left;
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
  white-space: nowrap;
}

.how-to-shortcuts thead th {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  font-weight: 600;
  border-bottom: 1px solid var(--line-strong);
  padding-top: 0;
}

.how-to-shortcuts tbody th {
  font-weight: 500;
  white-space: nowrap;
  width: 1%;
  color: var(--ink);
}

.how-to-shortcuts td {
  color: var(--ink-soft);
}

.how-to-shortcuts tbody tr:last-child th,
.how-to-shortcuts tbody tr:last-child td {
  border-bottom: none;
}

/* Keycap styling: shared between the shortcuts table at the bottom of the
   page and the inline [key:X] markers sprinkled through body prose. Same
   look in both contexts so the reader recognises an inline reference as
   the same key listed in the table. */
.container.how-to kbd {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
  padding: 0.1rem 0.45rem;
  background: var(--surface-hi);
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 5px;
  color: var(--ink);
  white-space: nowrap;
  line-height: 1.3;
  vertical-align: baseline;
}

/* Table cells get a touch more padding so each kbd sits comfortably in
   the row instead of feeling cramped against the cell borders. */
.how-to-shortcuts kbd,
.how-to-shortcuts .mouse-action {
  padding: 0.15rem 0.6rem;
}

/* Mouse-action pill — used for compound interactions like
   "Right-click and drag" or "Left-click". Visually distinct from a key:
   flat pill shape with a soft slate-blue wash, no raised bottom edge.
   Reads as a different *kind* of primitive than a keyboard cap, while
   keeping the same monospace font so they line up cleanly when mixed
   in one sentence ("[key:Ctrl] + [mouse:Left-click and drag]").
   Slate blue (rather than terracotta) keeps the mouse pill from reading
   as a warning, and sits cool against the cream chrome while still
   contrasting clearly with the warm keyboard caps. */
.container.how-to .mouse-action {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
  padding: 0.1rem 0.55rem;
  background: #e8ecf3;
  border: 1px solid #abb8cc;
  border-radius: 999px;
  color: #4a5d78;
  white-space: nowrap;
  line-height: 1.3;
  vertical-align: baseline;
}

@media (max-width: 720px) {
  .how-to-section-with-image {
    grid-template-columns: 1fr;
  }
  .how-to-section-image-left .how-to-section-figure {
    order: 0;
  }
}

/* Error pages (404, 500) */
.error-hero {
  padding-top: 1rem;
  text-align: center;
}

.error-hero h2 {
  margin-bottom: 0.75rem;
}

.error-hero p {
  color: var(--ink-soft);
  margin: 0.5rem auto;
  max-width: 50ch;
}
