/* (c) 2026 Evan Kastor. All rights reserved. Unauthorized copying, reproduction, or reverse engineering of this site or its code is prohibited. */
@font-face {
  font-family: 'SuisseScreenMonitor';
  src: url('./fonts/SuisseScreen-Monitor-Web.woff2') format('woff2'),
       url('./fonts/SuisseScreen-Monitor-Web.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'SuisseWorks';
  src: url('./fonts/SuisseWorks-Book-Web.woff2') format('woff2'),
       url('./fonts/SuisseWorks-Book-Web.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'SuisseWorks';
  src: url('./fonts/SuisseWorks-Medium-Web.woff2') format('woff2'),
       url('./fonts/SuisseWorks-Medium-Web.woff') format('woff');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'SuisseWorks';
  src: url('./fonts/SuisseWorks-Bold-Web.woff2') format('woff2'),
       url('./fonts/SuisseWorks-Bold-Web.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
/* #38 */
@font-face {
  font-family: 'FrauncesReveal';
  src: url('./fonts/Fraunces-Variable.woff2') format('woff2-variations'),
       url('./fonts/Fraunces-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'FrauncesReveal';
  src: url('./fonts/Fraunces-Italic-Variable.woff2') format('woff2-variations'),
       url('./fonts/Fraunces-Italic-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Suisse';
  src: url('./fonts/SuisseIntl-Regular-Web.woff2') format('woff2'),
       url('./fonts/SuisseIntl-Regular-Web.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Suisse';
  src: url('./fonts/SuisseIntl-Medium-Web.woff2') format('woff2'),
       url('./fonts/SuisseIntl-Medium-Web.woff') format('woff');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Suisse';
  src: url('./fonts/SuisseIntl-SemiBold-Web.woff2') format('woff2'),
       url('./fonts/SuisseIntl-SemiBold-Web.woff') format('woff');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Suisse';
  src: url('./fonts/SuisseIntl-Bold-Web.woff2') format('woff2'),
       url('./fonts/SuisseIntl-Bold-Web.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'SangBleuSunrise';
  src: url('./fonts/SangBleuSunrise-Regular-Web.woff2') format('woff2'),
       url('./fonts/SangBleuSunrise-Regular-Web.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'SangBleuSunrise';
  src: url('./fonts/SangBleuSunrise-Medium-Web.woff2') format('woff2'),
       url('./fonts/SangBleuSunrise-Medium-Web.woff') format('woff');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'SangBleuSunrise';
  src: url('./fonts/SangBleuSunrise-Bold-Web.woff2') format('woff2'),
       url('./fonts/SangBleuSunrise-Bold-Web.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'RivieraNightsThin';
  src: url('./fonts/RivieraNights-Light-Web.woff2') format('woff2'),
       url('./fonts/RivieraNights-Light-Web.woff') format('woff');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'RivieraNightsRegular';
  src: url('./fonts/RivieraNights-Regular-Web.woff2') format('woff2'),
       url('./fonts/RivieraNights-Regular-Web.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #0a0908;
  --bg-panel: #120f0b;
  --ink: #f2e8d5;
  --ink-dim: #a19d94;
  --accent: #b08d57;
  --accent-ink: #16130e;
  --footer-1: #0c0a08;
  --footer-2: #1c1712;
  --footer-3: #241d14;
  --border: rgba(255,255,255,0.12);
  --starfield: radial-gradient(1.5px 1.5px at 20% 30%, rgba(255,255,255,0.55) 40%, transparent 100%),
               radial-gradient(1.5px 1.5px at 70% 20%, rgba(255,255,255,0.55) 40%, transparent 100%),
               radial-gradient(1.5px 1.5px at 40% 70%, rgba(255,255,255,0.55) 40%, transparent 100%),
               radial-gradient(1.5px 1.5px at 85% 65%, rgba(255,255,255,0.55) 40%, transparent 100%),
               radial-gradient(1.5px 1.5px at 55% 85%, rgba(255,255,255,0.55) 40%, transparent 100%),
               radial-gradient(1.5px 1.5px at 10% 60%, rgba(255,255,255,0.55) 40%, transparent 100%);
  --starfield-size: 560px 560px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'SuisseWorks', sans-serif;
  line-height: 1.6;
}

h1, h2 { font-family: 'SangBleuSunrise', serif; font-weight: 700; margin: 0 0 20px; }
p { color: var(--ink-dim); margin: 0 0 18px; max-width: 52ch; }

a { color: inherit; text-decoration: none; }

.eyebrow {
  font-family: 'RivieraNightsThin', sans-serif;
  font-size: 15px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
  display: block;
  margin-bottom: 14px;
}
.eyebrow.accent { color: var(--accent); }

/* Header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px 48px;
  background: linear-gradient(to bottom, rgba(6,6,8,0.92), rgba(6,6,8,0));
}
.site-header .logo img { height: 51.6px; display: block; }
.site-header .logo, .site-header nav, .nav-toggle {
  opacity: 1;
  transition: opacity 2s ease;
}
body.tarot-focus .site-header .logo,
body.tarot-focus .site-header nav,
body.tarot-focus .nav-toggle,
body.tarot-focus #tarot-shuffle,
body.reading-exited #tarot-shuffle {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
/* #82 */
/* Once a shuffle has actually started, this button is done for good —
   !important so a scroll-triggered exit from focus mode (which un-hides
   it via the rule above losing its class) can never bring it back to
   life mid-reading. Replaying is a full page reload now, not a button. */
#tarot-shuffle.spent {
  opacity: 0 !important;
  pointer-events: none !important;
}
.site-header nav { display: flex; align-items: center; gap: 40px; }
.site-header nav a {
  font-family: 'RivieraNightsThin', sans-serif;
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-dim);
  transition: color 0.2s;
}
.site-header nav a:hover { color: var(--accent); }
/* The Tarot link is a decorative wordmark image, not styled text like its
   neighbors — set apart deliberately, at the far end of the menu, rather
   than trying to blend in stylistically. Two stacked images crossfade on
   hover to still get the same color-shift feedback as the other links. */
.nav-tarot-wordmark {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* Re-cropped to the ink itself (see scripts/render_tarot_nav_decorative.py) —
   the old export carried a lot of dead canvas above/below the letters, so
   this height reads noticeably smaller than the 22px box it sat in. */
.nav-tarot-img { display: block; height: 16px; width: auto; }
.nav-tarot-img-accent {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  transition: opacity 0.2s;
}
.nav-tarot-wordmark:hover .nav-tarot-img-dim { opacity: 0; }
.nav-tarot-wordmark:hover .nav-tarot-img-accent { opacity: 1; }
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  z-index: 51;
}
.nav-toggle-bar {
  display: block;
  width: 100%;
  height: 2px;
  background: var(--ink);
  transition: transform 0.25s ease, opacity 0.2s ease;
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Hero */
.hero {
  position: relative;
  min-height: 92vh;
  display: flex;
  align-items: center;
  overflow: hidden;
  background: radial-gradient(circle at 60% 45%, #14131c 0%, #060608 60%);
}
.hero-sparkle {
  position: absolute;
  inset: 0;
  background-image: var(--starfield);
  background-size: var(--starfield-size);
}
.twinkle-star {
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: #fff;
  animation-name: starPulse;
  animation-timing-function: ease-in-out;
  animation-iteration-count: 3;
}
@keyframes starPulse {
  0%, 100% { opacity: 0.15; transform: scale(0.7); }
  50% { opacity: 1; transform: scale(1.4); }
}
.hero-photo {
  position: absolute;
  right: 6%;
  bottom: 0;
  max-height: 92vh;
  z-index: 1;
  opacity: 0;
  animation: heroFadeIn 3s ease-out 3s both;
  filter: blur(0.9px) drop-shadow(0 0 3px var(--bg)) drop-shadow(0 0 3px var(--bg));
}
.hero-text {
  position: absolute;
  left: 30%;
  top: 50%;
  z-index: 2;
  width: min(90%, 640px);
  text-align: center;
  transform: translate(-50%, -50%);
}
.hero .btn {
  opacity: 0;
  animation: heroFadeIn 3s ease-out 6s both;
}

@keyframes heroFadeIn {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}
.hero-name {
  opacity: 0;
  animation: heroFadeIn 1.2s ease-out both;
}
.hero h1 {
  font-size: clamp(43px, 5.4vw, 76px);
  font-weight: 500;
  line-height: 1.02;
  opacity: 0;
  animation: heroFadeIn 1.5s ease-out 1.5s both;
}
.hero-name {
  font-size: 30px;
}
.hero .btn { margin-top: 12px; }
.scroll-chevron {
  display: block;
  margin: 26px auto 0;
  width: 14px;
  height: 14px;
  opacity: 0;
  transition: opacity 1s ease;
}
.scroll-chevron::before {
  content: '';
  display: block;
  width: 12px;
  height: 12px;
  margin: 0 auto;
  border-right: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
  transform: rotate(45deg);
}
.scroll-chevron.visible { opacity: 1; }

/* Crowd banner */
.crowd-banner {
  position: relative;
  overflow: hidden;
  height: 60vh;
  background-image: url('images/wide-shot.jpg');
  background-size: cover;
  background-position: center 50%;
  background-repeat: no-repeat;
  will-change: background-position;
}
.crowd-banner::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.45);
}
.crowd-banner-text {
  position: absolute;
  top: 38.2%;
  left: 16%;
  max-width: 40%;
  transform: translateY(-50%);
  text-align: left;
  font-family: 'SangBleuSunrise', serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: clamp(16px, 2.4vw, 34px);
  white-space: nowrap;
  color: var(--ink);
  text-shadow: 0 4px 30px rgba(0,0,0,0.5);
}

/* Buttons */
.btn {
  display: inline-block;
  padding: 14px 30px;
  font-family: 'RivieraNightsThin', sans-serif;
  text-transform: uppercase;
  font-size: 15px;
  letter-spacing: 0.06em;
  border-radius: 4px;
  background: none;
  border: none;
  appearance: none;
  -webkit-appearance: none;
  transition: transform 0.15s ease, opacity 0.15s ease;
}
.btn:hover { transform: translateY(-2px); }
.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
}
.btn-outline {
  border: 1px solid var(--border);
  color: var(--ink);
}
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }
.btn-ghost {
  background: var(--accent);
  color: var(--accent-ink);
}

/* Panels */
.panel {
  display: flex;
  align-items: center;
  gap: 64px;
  padding: 100px 8%;
  background-color: var(--bg);
  background-image: var(--starfield);
  background-size: var(--starfield-size);
}
.panel-dark {
  background-image: var(--starfield), radial-gradient(circle at 60% 45%, #14131c 0%, #060608 60%);
  background-size: var(--starfield-size), auto;
}
.panel-reverse { flex-direction: row-reverse; }
.panel-media { flex: 1 1 46%; }
.panel-media img {
  width: 100%;
  border-radius: 8px;
  display: block;
}
#philosophy .panel-media {
  display: flex;
  align-items: center;
  justify-content: center;
}
#philosophy .panel-media img {
  width: auto;
  max-width: 100%;
  max-height: 400px;
  margin: 0 auto;
}
.panel-media-portrait img {
  max-width: 380px;
  border-radius: 50%;
  position: relative;
  top: -15px;
}
.panel-copy { flex: 1 1 54%; }
.seal-row {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 25px;
  max-width: 52ch;
  margin: 8px 0 24px;
}
.seal-row img {
  width: 100px;
  height: auto;
}
.seal-row img:nth-child(2) {
  width: 105px;
}
.seal-row img.seal-magic-circle {
  width: 95px;
}
.story-quote {
  font-style: italic;
  border-left: 2px solid var(--accent);
  padding-left: 18px;
  color: #c7c5cf;
}
.story-quote::first-letter {
  font-family: 'SangBleuSunrise', serif;
  font-style: normal;
  font-weight: 700;
  font-size: 3.6em;
  line-height: 0.75;
  float: left;
  padding-right: 10px;
  padding-top: 4px;
  color: var(--accent);
}
.email-link {
  font-family: 'Suisse', monospace;
  color: var(--accent);
  text-decoration: none;
}
.inline-link {
  color: var(--accent);
  text-decoration: none;
}

/* Footer */
.site-footer {
  background-color: #000000;
  border-top: 1px solid rgba(176,141,87,0.25);
}
.footer-inner {
  padding: 100px 8% 0;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  gap: 64px;
}
.footer-side p { max-width: 44ch; }
.footer-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 8px;
}
.contact-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 44ch;
  margin-top: 14px;
}
.contact-form input,
.contact-form textarea {
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 12px 14px;
  font-family: 'SuisseWorks', sans-serif;
  font-size: 15px;
  color: var(--ink);
  resize: vertical;
}
.contact-form input::placeholder,
.contact-form textarea::placeholder {
  color: var(--ink-dim);
}
.contact-form input:focus,
.contact-form textarea:focus {
  outline: none;
  border-color: var(--accent);
}
.contact-form button {
  align-self: flex-start;
  border: none;
  cursor: pointer;
}
.footer-fine {
  margin: 60px auto 0;
  padding-bottom: 40px;
  width: 100%;
  max-width: none;
  text-align: center;
  font-family: 'RivieraNightsRegular', sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 13px;
  color: rgba(242,232,213,0.6);
}
/* #39 */
.footer-fine::after {
  content: " · css v62";
  opacity: 0.35;
  font-size: 11px;
}
.footer-brand {
  color: inherit;
}
.jinx-link {
  display: inline-block;
  vertical-align: middle;
  margin: 0 6px;
}
.jinx-icon {
  height: 25.3px;
  width: auto;
  vertical-align: middle;
  opacity: 0.85;
  position: relative;
  top: -5px;
}

/* Jinx / offerings page */
.jinx-item {
  justify-content: center;
  text-align: center;
}
.jinx-item img {
  max-width: 480px;
  width: 90%;
  border-radius: 8px;
  transition: transform 0.2s ease;
}
.jinx-item a:hover img { transform: scale(1.02); }
.jinx-heading { text-align: center; max-width: none; }
.jinx-pair {
  display: flex;
  gap: 20px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}
.jinx-pair img {
  max-width: 320px;
  width: 100%;
  border-radius: 8px;
}

/* Lightbox */
.lightbox-main { cursor: zoom-in; }
.lightbox-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.92);
  z-index: 200;
  align-items: center;
  justify-content: center;
  padding: 40px;
}
.lightbox-overlay.open { display: flex; }
.lightbox-images {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  max-width: 90vw;
  max-height: 85vh;
  overflow: auto;
  justify-content: center;
}
.lightbox-images img {
  max-height: 80vh;
  max-width: 100%;
  border-radius: 6px;
}
.lightbox-close {
  position: absolute;
  top: 24px;
  right: 32px;
  background: none;
  border: none;
  color: var(--ink);
  font-size: 36px;
  line-height: 1;
  cursor: pointer;
}

@media (max-width: 955px) {
  /* Both anchored a fixed pixel distance from the window's center (matching
     the default 6%/30% position exactly at 955px), so the photo and text
     keep a constant gap and slide together smoothly as the window narrows,
     instead of jumping to a different percentage. */
  .hero-photo { right: calc(50% - 420px); }
  .hero-text { left: calc(50% - 191px); }
}

@media (max-width: 860px) {
  .site-header { padding: 18px 24px; flex-wrap: wrap; row-gap: 10px; }
  .panel, .panel-reverse { flex-direction: column; padding: 70px 24px; }
  .footer-grid { grid-template-columns: 1fr; }
  .seal-row { flex-wrap: wrap; }
}

@media (max-width: 840px) {
  .site-header { flex-wrap: nowrap; }
  .nav-toggle { display: flex; }
  .site-header nav {
    display: none;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: center;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    gap: 20px;
    padding: 24px 24px 32px;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
  }
  .site-header nav.open { display: flex; }
}

@media (max-width: 714px) {
  .hero-photo { display: none; }
  .hero-text { left: 50%; }
  .hero .btn { animation-delay: 2.5s; }
}

/* Tarot spread */
.tarot-panel { text-align: center; flex-direction: column; padding-bottom: 40px; }
.tarot-panel .eyebrow, .tarot-panel h2 { text-align: center; }
/* 404 page */
.notfound-panel {
  text-align: center;
  flex-direction: column;
  justify-content: center;
  min-height: 70vh;
}
.notfound-panel p { max-width: 52ch; margin: 0 auto 28px; }
/* z-index: 0 (not just position: relative) gives this section its own
   stacking context — without it, the starfield's z-index: -1 below escapes
   to the page's root stacking context instead of staying local, and can end
   up rendered behind the page background instead of just behind this
   section's own content. */
#online-reading { position: relative; z-index: 0; background-image: none; }
#online-reading::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--starfield);
  background-size: var(--starfield-size);
  opacity: 1;
  transition: opacity 2s ease;
  /* Delay matches the fade-in, so the stars finish appearing before they
     start to twinkle instead of jumping straight to a pulse. */
  animation: starfieldTwinkle 7s ease-in-out 2s infinite;
}
body.tarot-focus #online-reading::before {
  opacity: 0;
  transition: opacity 0.15s ease;
  animation: none;
}
@keyframes starfieldTwinkle {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.72; }
}
/* .tarot-intro (the old eyebrow + h2 above .tarot-stage) and its .faded
   state removed 2026-07-22 along with the HTML element itself -- see
   .tarot-heading for its replacement, now living inside the sticky box. */
.tarot-instructions {
  max-width: 56ch;
  margin: 0 auto 32px;
  color: var(--ink-dim);
  opacity: 1;
  transition: opacity 0.7s ease;
}
/* #200 */
body.tarot-hide-intro .tarot-instructions {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}
.tarot-special-note {
  font-size: 13px;
  max-width: 48ch;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
/* max-width matches GEO_MAX_VW in tarot.html's getGeometry() — the ring's
   own size already scales continuously up to that viewport width, but it
   was being capped at 960px here regardless, so wide desktop screens never
   actually got the bigger ring. Keep the two numbers in sync. */
/* overflow-anchor: none here (not just on .tarot-arena below) -- .tarot-intro
   ALSO collapses its height (max-height 200px -> 0, its own 0.6s transition,
   see .tarot-intro.faded) the same instant mixing starts, and that was
   enough on its own to trigger the browser's scroll anchoring, same as the
   arena's own height jump. Setting this on the shared container covers
   every element inside that changes size, instead of chasing each one down
   individually. */
/* 2026-07-22, the massive rework: no more scrolling the PAGE to frame the
   experience (centerArenaScroll, scrollSoBottomSafe -- both removed from
   tarot.html). .tarot-arena below is now position: sticky, which needs a
   TALLER parent to have any real "stick" distance to work with -- a sticky
   child whose parent is exactly its own height never visibly sticks at
   all, since there's no extra scroll room to traverse while pinned. The
   extra height here (60vh beyond the arena's own 100dvh) is that runway:
   enough that a small accidental scroll doesn't release it, small enough
   that one real deliberate scroll gesture does -- replaces the old
   FOCUS_EXIT_SCROLL (160px) threshold that used to do this same job in JS.
   The browser handles the entire pin/release transition natively now --
   zero JS scroll math, so zero risk of it landing short (the whole bug
   class this session kept finding). */
/* Real root cause found 2026-07-22, from a real phone recording: this used
   to be width: 100%, but `100%` here means 100% of #online-reading's
   CONTENT box -- and .panel (the shared section class) has padding: 100px
   8%, which #online-reading never overrode horizontally. So .tarot-stage
   (and everything sticky inside it) was never actually full-bleed -- it
   sat inset 8% on each side the whole time. That's what read as "black
   bars on both sides," and it's also why the candle-position math (which
   correctly computes a position based on the TRUE window width) put
   candles past this box's own clipped edge (overflow: hidden on
   #tarot-viewport), landing them closer to the card than intended. Full-
   bleed breakout instead: explicitly 100vw, pulled back out to the true
   screen edges with negative margins, ignoring the panel's own padding
   for this one full-screen section. */
/* #40 */
.tarot-stage { position: relative; width: 100vw; margin: 16px calc(50% - 50vw) 0; text-align: center; overflow-anchor: none; height: calc(100dvh + 30vh); }
/* Trimmed from the panel's normal top padding specifically here -- this is
   the very first thing on the page now, and the deck + button need to fit
   in the first screen without scrolling, which the standard panel spacing
   (tuned for sections further down, with no such constraint) didn't leave
   room for. */
#online-reading { padding-top: 32px; }
@media (max-width: 860px) {
  #online-reading { padding-top: 20px; }
}
/* #201 */
#tarot-shuffle {
  position: absolute;
  /* #41 */
  bottom: 25%;
  left: 50%;
  transform: translate(-50%, 0);
  z-index: 5;
  /* #42 */
  padding: 3px 36px;
  min-width: 260px;
  text-align: center;
  /* #43 */
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
  /* Belt and suspenders alongside the pointerdown preventDefault in JS: a
     long press on the button itself should never bring up a text-selection
     or callout menu on mobile. */
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
/* Second tap of the touch mix toggle ("They're Mixed") used to give no
   sign the tap registered -- the button just sat there, unchanged, until
   the cards finished landing and it vanished. Historically this flipped the
   button to a hollow accent-outlined look as that confirmation; now that
   the base style already IS that look (see #tarot-shuffle above, v43
   review), there's no color left to flip -- this class is kept only for
   the transition timing tarot.html's fade-to-opacity-0 relies on. */
#tarot-shuffle.confirming {
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
/* Grown from 680 to 800 so the ring can sit at a true top: 50% (dead center)
   and still leave clearance below it for the shuffle button, instead of
   biasing the ring upward the way a shorter box would force. */
/* No perspective here (each .tarot-card already carries its own, which is all
   the 3D flip needs) — an arena-level perspective would otherwise establish
   its own stacking context and trap the featured lightbox card's z-index
   inside it, unable to render above the dim overlay sitting just outside. */
/* overflow-anchor: none -- this box's height jumps abruptly at a few points
   (removing .pre-mix the instant mixing starts, .reveal-complete growing it
   later) and the browser's own scroll anchoring was compensating for those
   jumps by shifting the page underneath the guest, reading as an unwanted
   scroll right when mixing starts. The height changes are already
   deliberate and already handled by the code's own scroll calls where one
   is actually wanted -- this just stops the browser from adding its own
   uninvited one on top. */
/* 2026-07-22 rework: was a fixed 800px, grown to 1150px for .reveal-complete
   below, plus separate hand-tuned pixel heights per breakpoint (see the
   860px and 861-1180px blocks) -- three tuned buckets that each needed
   their own re-scroll once content changed size. One constant,
   viewport-relative height instead: from the moment mixing locks in
   (.pre-mix removed, tarot-focus added), this box IS the screen, for
   every stage, on every device. Since nearly everything inside already
   positions itself with top: % (the fan, the progress text, etc.), a
   constant height means those percentages just resolve correctly on their
   own -- no more per-stage height jump, no more re-scrolling to chase it.
   100vh first as a fallback for browsers without dvh support, 100dvh after
   to account for mobile browser chrome showing/hiding. */
/* The actual sticky "screen" -- see .tarot-stage above for why it's taller
   than this box (that's the scroll runway the sticky mechanism needs).
   Containing block for both #tarot-arena and #tarot-reading inside it, so
   both fill it exactly and neither needs its own scroll-into-view. */
/* #83 */
.tarot-viewport { position: sticky; top: 0; height: 100vh; height: 100dvh; overflow: hidden; }
.tarot-arena { position: relative; width: 100%; height: 100%; transition: opacity 2s ease, filter 2s linear; overflow-anchor: none; }
/* #202 */
/* Used to also collapse height: 0 here (delayed 2s, after the opacity fade
   finished) so .tarot-stage's own height would shrink back down once
   nobody could see this box anymore. Not needed since the rework above --
   .tarot-stage now has its own explicit height independent of whatever's
   showing inside the sticky arena, so there's nothing left to reclaim. */
/* Real bug found 2026-07-22, from a real recording: this inherited the
   base rule's 2s opacity transition, while .tarot-reading fades IN over
   only 1.2s (see below) -- both run at the same time (see exitReading in
   tarot.html), so for the ~0.8s gap between them, the reading panel was
   already fully visible while the arena (candles, deck fan, button) was
   still readably faded in behind/around it, a legible double-exposure
   rather than a clean crossfade. Matched to the same 1.2s so both finish
   together. */
.tarot-arena.exiting { opacity: 0; pointer-events: none; transition: opacity 1.2s ease; }
/* Used to grow the box to 1150px here, since the final row reaches further
   above/below center than the old fixed base height accounted for. Now
   that the base height above is a constant 100dvh (the full screen) for
   the whole locked-in experience, there's nothing left to grow into --
   removed. If the final row still runs close to the edges on a real
   device, that's now a sizing question for the row itself (getGeometry's
   finalScale), not a box-height one. */
/* Before the first tap, the ring hasn't fanned out yet -- just the small
   piled-up deck sitting at dead center -- so it doesn't need the full ring
   clearance above. Shrinking the box for that resting moment pulls the
   deck and the Mix and Begin button up into the first screen instead of
   leaving them stranded under a mostly-empty box sized for a ring that
   only exists once shuffling actually starts. Removed the instant the
   button is first pressed (see tarot.html), the same moment the intro
   text starts collapsing out of the way. */
/* #44 */
/* #84 */
.tarot-lightbox-overlay {
  position: fixed;
  inset: 0;
  background: transparent;
  z-index: 250;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}
.tarot-lightbox-overlay.open { opacity: 1; pointer-events: auto; cursor: pointer; }
/* Holding anywhere (other than the exit prompt) during the reading dims the
   whole arena gradually, over the same span as the hold threshold, like the
   candles are guttering out — release early and it brightens straight back. */
body.extinguishing .tarot-arena { filter: brightness(0.15); }
/* #45 */
.tarot-heading {
  position: absolute;
  /* #85 */
  top: 25%;
  left: 50%;
  transform: translate(-50%, 0);
  margin: 0;
  font-family: 'RivieraNightsThin', sans-serif;
  font-size: clamp(20px, 3vw, 30px);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
  opacity: 1;
  transition: opacity 0.5s ease;
  white-space: nowrap;
}
.tarot-heading.faded { opacity: 0; pointer-events: none; }
/* #46 */
.tarot-advance {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 260;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--ink-dim);
  background: rgba(6, 6, 8, 0.4);
  color: var(--ink-dim);
  font-family: 'RivieraNightsThin', sans-serif;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
}
.tarot-advance.visible { opacity: 0.7; pointer-events: auto; }
.tarot-advance:hover { opacity: 1; }
.tarot-fan {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
}
.tarot-fan.shuffling .tarot-card { pointer-events: none; }
.tarot-card {
  position: absolute;
  top: 0;
  left: 0;
  /* Continuous width fill instead of a jump at each breakpoint: scales
     linearly with the actual viewport width between a 390px phone (60px
     card) and a 1400px+ desktop (84px card), clamped flat outside that
     range. Height follows the same two anchor points to keep the card's
     aspect ratio steady. Matches the geometry math in tarot.html's script,
     which derives the ring radius/spacing from the same two anchors. */
  --card-w: clamp(60px, calc(50.7px + 2.38vw), 84px);
  --card-h: clamp(98px, calc(83.3px + 3.76vw), 136px);
  width: var(--card-w);
  height: var(--card-h);
  margin: calc(var(--card-h) / -2) 0 0 calc(var(--card-w) / -2);
  transition: transform 0.6s cubic-bezier(.2,.8,.2,1), opacity 0.7s ease;
  cursor: pointer;
  perspective: 1000px;
  /* The name caption image is laid out small (76x16) and, without this,
     browsers cache its rasterized bitmap at that small size — so when
     revealScale later blows the whole card up 3x+, that already-small
     bitmap gets stretched instead of the high-res source being resampled
     at the bigger final size, reading as blurry despite the source PNG
     being nowhere near that low-res. Promoting this to its own layer
     makes it re-rasterize at the actual on-screen (scaled-up) size. */
  will-change: transform;
  backface-visibility: hidden;
  /* Clearance for the sticky nav when the featured card in the lightbox
     scrolls itself into view (see featureCard() in tarot.html). */
  scroll-margin-top: 110px;
}
.tarot-card.locked { cursor: default; }
.tarot-card.disabled { cursor: default; opacity: 0.35; filter: grayscale(0.5); }
.tarot-card.chosen { cursor: pointer; }
.tarot-card.chosen.flipped { cursor: default; }
/* Scoped to real hover-capable+fine-pointer devices only -- a touchscreen
   has no way to "un-hover", so whatever card a finger last landed on (not
   necessarily the one actually picked) was staying stuck bright for the
   rest of the reading. Desktop's mouse hover is unaffected. */
@media (hover: hover) and (pointer: fine) {
  .tarot-card:not(.chosen):not(.disabled):not(.locked):hover { filter: brightness(1.2); }
}

/* Silver plates */
.tarot-plate {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 54px;
  height: 18px;
  margin-top: -9px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #f6f6f8 0%, #cdd0d6 42%, #94979e 76%, #5f6167 100%);
  box-shadow: 0 3px 7px rgba(0,0,0,0.45), inset 0 1px 2px rgba(255,255,255,0.55);
  z-index: 2;
  pointer-events: none;
  opacity: 1;
  transition: transform 0.9s ease, opacity 0.9s ease;
}
.tarot-plate.retired {
  transform: translateY(40px);
  opacity: 0;
  z-index: -1;
}
.tarot-plate-left { margin-left: -258px; }
.tarot-plate-right { margin-left: 204px; }

/* Candles */
.tarot-candle {
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -76px;
  width: 32px;
  height: 76px;
  transition: transform 0.8s ease;
  z-index: 3;
}
/* While a card is featured in the lightbox, the dim overlay (z-index 250)
   sits on top of everything in the arena including the candles (base
   z-index 3) -- so even though bringCandlesToDesk() already sets them to
   full opacity beside the row, the overlay was reading as darkening them
   out almost completely. Bumped above the overlay (but still below the
   featured card itself, at 300) only while tarot.html has this class
   toggled on, so they read as actually lit beside the card instead of
   dimmed with the rest of the arena. */
.tarot-candle.above-overlay { z-index: 255; }
.tarot-candle.retired {
  transform: translateY(40px);
  opacity: 0;
  z-index: -1;
  pointer-events: none;
  transition: transform 0.9s ease, opacity 0.9s ease;
}
/* Shifted 8px further out (half the candle's width growth from the
   fat-candle pass) so the graphic stays centered on the plate it sits
   on, instead of drifting toward center as the box widened. */
/* Real bug found 2026-07-27, from a real screenshot: the candle wasn't
   sitting centered on its plate -- worked out precisely rather than by eye:
   for both to share a center given .tarot-plate's own width/margin-left
   above, the candle's margin-left must equal
   plateMarginLeft + plateWidth/2 - candleWidth/2. Was off by 5px on the
   left (-252 vs the correct -247) and 9px on the right (224 vs 215). */
.tarot-candle-left { margin-left: -247px; }
.tarot-candle-right { margin-left: 215px; }
/* A slow, small drift once the candles have landed beside the desk, so they
   read as flickering there rather than frozen — the animation targets the
   same base position it was left at, via the custom properties set in JS. */
.tarot-candle.floating {
  animation: candleFloat 9s ease-in-out infinite;
}
.tarot-candle-right.floating { animation-duration: 10s; animation-delay: -3.5s; }
@keyframes candleFloat {
  0%   { transform: translate(var(--cx, 0px), var(--cy, 0px)) translateZ(0); }
  25%  { transform: translate(calc(var(--cx, 0px) + 3px), calc(var(--cy, 0px) - 4px)) translateZ(0); }
  50%  { transform: translate(calc(var(--cx, 0px) - 2px), calc(var(--cy, 0px) + 2px)) translateZ(0); }
  75%  { transform: translate(calc(var(--cx, 0px) + 2px), calc(var(--cy, 0px) + 3px)) translateZ(0); }
  100% { transform: translate(var(--cx, 0px), var(--cy, 0px)) translateZ(0); }
}
.tarot-candle-body {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  /* #203 */
  width: 32px;
  height: 50px;
  background: linear-gradient(180deg, #f2e8d5, #cbb98a);
  border-radius: 6px 6px 1px 1px;
  box-shadow: inset -2px 0 4px rgba(0,0,0,0.3);
}
.tarot-candle-wick {
  position: absolute;
  bottom: 50px;
  left: 50%;
  transform: translateX(-50%);
  width: 2px;
  height: 9px;
  background: #2a2118;
}
.tarot-candle-flame {
  position: absolute;
  bottom: 56px;
  left: 50%;
  width: 13px;
  height: 22px;
  margin-left: -6.5px;
  background: radial-gradient(circle at 50% 32%, #fff6d8 0%, #ffb347 42%, #b08d57 78%, transparent 100%);
  border-radius: 50% 50% 50% 50% / 62% 62% 38% 38%;
  transform-origin: 50% 95%;
  animation: candleFlicker 1.7s ease-in-out infinite;
}
.tarot-candle-glow {
  position: absolute;
  bottom: 42px;
  left: 50%;
  width: 64px;
  height: 64px;
  margin-left: -32px;
  background: radial-gradient(circle, rgba(255,179,71,0.32), transparent 70%);
  border-radius: 50%;
  animation: candleGlow 1.7s ease-in-out infinite;
}
@keyframes candleFlicker {
  0%, 100% { transform: scaleY(1) scaleX(1) rotate(0deg); opacity: 1; }
  25% { transform: scaleY(1.08) scaleX(0.94) rotate(-2deg); opacity: 0.92; }
  50% { transform: scaleY(0.95) scaleX(1.05) rotate(2deg); opacity: 1; }
  75% { transform: scaleY(1.05) scaleX(0.97) rotate(-1deg); opacity: 0.95; }
}
@keyframes candleGlow {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 0.85; }
}
/* #47 */
@keyframes tarotMagicFlash {
  0% { filter: brightness(1) saturate(1) drop-shadow(0 0 0 rgba(255,201,107,0)); }
  45% { filter: brightness(2.4) saturate(0.3) drop-shadow(0 0 22px rgba(255,201,107,0.95)); }
  100% { filter: brightness(1) saturate(1) drop-shadow(0 0 0 rgba(255,201,107,0)); }
}
.tarot-magic-flash { animation: tarotMagicFlash 0.7s ease; }
@keyframes tarotFlameFlash {
  0% { transform: scaleY(1) scaleX(1); opacity: 1; filter: brightness(1); }
  35% { transform: scaleY(1.7) scaleX(1.3); opacity: 1; filter: brightness(2); }
  100% { transform: scaleY(1) scaleX(1); opacity: 1; filter: brightness(1); }
}
.tarot-candle-flame.magic-flash { animation: tarotFlameFlash 0.7s ease; }
.tarot-candle-glow.magic-flash { animation: tarotFlameFlash 0.7s ease; }
.tarot-card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  /* Duration kept in sync with FLIP_DURATION in tarot.html's script. */
  transition: transform 1.1s cubic-bezier(.2,.8,.2,1);
}
.tarot-card.flipped .tarot-card-inner { transform: rotateY(180deg); }
.tarot-card-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  border-radius: 8px;
  border: 1px solid var(--border);
  overflow: hidden;
  padding: 0;
}
.tarot-card-back {
  border-color: rgba(176,141,87,0.45);
}
.tarot-card-back-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.tarot-card-front {
  background: var(--bg-panel);
  border-color: var(--accent);
  transform: rotateY(180deg);
}
.tarot-card-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 7px;
}
/* Sized for the card's true rest-position scale (the resting row is no
   longer scaled up — see revealScale's new job as the lightbox zoom amount
   in tarot.html's script) — these render small in the row, and only look
   "big" because the whole featured card (captions included, as its
   children) is scaled way up while it's alone in the lightbox. Width is
   deliberately narrower than before so neighboring cards' captions in the
   row don't run into each other at the tighter resting spacing. */
.tarot-card-caption {
  position: absolute;
  top: 100%;
  left: 50%;
  /* Laid out at 2x the old box (152x32) and scaled back down by half via
     transform, instead of a small 76x16 box relying entirely on the
     ancestor's later revealScale to enlarge it. Mobile Safari in
     particular decodes/rasterizes an <img> at roughly its CSS-laid-out
     size — a 76px-wide box gets cached that small, and blowing it up 3x+
     via the parent's transform later just stretches that small bitmap,
     which is what was reading as blurry despite the source PNG being far
     higher-res. Starting from a bigger laid-out box gives it more native
     pixels to decode before any scaling happens. transform-origin keeps
     this positioned identically to before. */
  margin-left: -76px;
  transform: scale(0.5);
  transform-origin: top center;
  /* #48 */
  margin-top: 4px;
  width: 152px;
  height: 32px;
  object-fit: contain;
  opacity: 0;
  transition: opacity 1.1s ease;
}
.tarot-card.show-name .tarot-card-caption { opacity: 0.9; }
/* In the final reveal-complete row the whole card (captions included, as
   its children) scales up by finalScale (1.6-2.4x, see getGeometry in
   tarot.html). At finalSlotGap's narrow end (105px between mobile card
   centers) the caption's normal 0.5 scale multiplied by finalScale grows
   past 120px wide -- wider than the gap between neighboring cards -- so
   adjacent names run into each other with no space between them. Scaling
   the caption down further specifically in this state keeps it clear of
   its neighbor across the whole viewport range without touching
   finalSlotGap/finalScale, which were already tuned to avoid a different
   overflow bug (see the mobile-scale note in tarot.html). */
.tarot-arena.reveal-complete .tarot-card-caption { transform: scale(0.36); }
.tarot-card-label {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 32px;
  text-align: center;
  font-family: 'RivieraNightsThin', sans-serif;
  /* Scales up along with the card like every child here — sized down from
     10px so that even multiplied by the now-bigger revealScale, it reads
     smaller than before rather than bigger. The name (tarot-card-caption)
     is left alone since growing along with the card is exactly what was
     asked for there. */
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
  opacity: 0;
  transition: opacity 0.9s ease;
}
.tarot-card.show-label .tarot-card-label { opacity: 1; }
/* Smaller and closer to the card name specifically while a card is the
   one actively zoomed in the lightbox (see the .featured class added in
   featureCard()/dismissFeaturedCard() in tarot.html) -- at the lightbox's
   much bigger revealScale, the base 8px/32px sizing tuned for the small
   resting row reads oversized and too far detached from the name above
   it. Doesn't touch the label's size in the resting row or the later
   reveal-complete row, only this one zoomed state. */
/* #49 */
/* #50 */
.tarot-card.featured .tarot-card-label { font-size: 7px; margin-top: 12px; }
/* #51 */
.tarot-arena.reveal-complete .tarot-card-label { font-family: 'RivieraNightsRegular', sans-serif; }
.tarot-card-meaning {
  position: absolute;
  top: 100%;
  left: 50%;
  /* Same fix as tarot-card-caption above, same reason: this text still
     read a little soft at 8px even though tarot-card-label is crisp at
     that same size — SuisseWorks (this element's font, inherited from
     body) carries much finer detail than the label's simple uppercase
     RivieraNightsThin, so it's more sensitive to the same small-base-size
     rendering issue. Laid out at 2x (152px box, 16px font) and scaled
     back down by half via transform, so the browser hints/rasterizes the
     glyphs from a healthier base size before any scaling compounds. */
  margin-left: -76px;
  transform: scale(0.5);
  transform-origin: top center;
  margin-top: 52px;
  width: 152px;
  text-align: center;
  /* #52 */
  font-family: var(--reveal-bodytext-font, 'FrauncesReveal'), 'SuisseWorks', serif;
  font-style: italic;
  font-weight: 480;
  font-variation-settings: 'SOFT' 0, 'WONK' 1;
  /* #204 */
  font-size: 13px;
  line-height: 1.35;
  color: var(--ink-dim);
  opacity: 0;
  transition: opacity 1.1s ease 0.15s;
  pointer-events: none;
}
.tarot-card.show-meaning .tarot-card-meaning { opacity: 1; }
.tarot-progress {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 220px;
  text-align: center;
  font-family: 'RivieraNightsThin', sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 14px;
  color: var(--ink-dim);
  z-index: 5;
  transition: opacity 0.8s ease, top 0.4s ease;
}
/* Before the shuffle, the deck is just a small stack at dead center — sit
   below it instead of overlapping it. Once the ring fans out, the same text
   moves to the true center where the picked cards will land. */
.tarot-progress.pre-shuffle { top: 77%; }
/* "three cards" reads slightly bigger than the surrounding lines, per the
   4-line break (Which / three cards / call out / to you?) that echoes the
   circle's own line widths. */
.tarot-progress-emphasis { font-size: 1.45em; }
/* History: was a fixed 32px-from-bottom spot (too fragile -- nothing
   guaranteed it stayed clear of the reading text above it), then switched
   to a JS-measured top set off the reading text's real bottom edge, which
   turned out to have its own real bug (2026-07-22): that top is
   CSS-transitioned, and the JS was measuring it before the transition had
   actually moved anything, landing the scroll target on stale geometry and
   leaving this prompt below the fold on a real phone. Now that .tarot-arena
   is a constant 100dvh for the whole locked-in experience (see above),
   bottom-anchoring this directly is simpler AND correct by construction --
   no JS measurement, no scroll math, nothing to get stale. `top: auto`
   is required here since the base .tarot-progress rule sets top: 50% --
   without overriding it to auto, that top would win over bottom per the
   CSS spec and this rule would do nothing.
   Known open risk: a long combined-reading sentence on a short viewport
   could still run down far enough to overlap this fixed line -- that's a
   text-sizing question (.tarot-full-reading's own max-height/font-size),
   tracked separately, not solved by this change alone. */
/* #53 */
/* #54 */
.tarot-progress.reading-cta {
  position: absolute;
  left: 50%;
  top: auto;
  bottom: 2px;
  width: auto;
  max-width: none;
  white-space: nowrap;
  transform: translate(-50%, 0);
  cursor: pointer;
  z-index: 6;
  padding: 3px 36px;
  border: 1px solid var(--accent);
  border-radius: 4px;
  color: var(--accent);
  background: transparent;
}
/* The full flowing reading — all three positions combined into one
   sentence — replaces the old short per-card meanings entirely once a
   reading finishes. JS measures where the (now much bigger) row of cards
   actually landed and sets `top` inline in px, same approach the CTA
   prompt used to use before it moved to a fixed bottom position. */
.tarot-full-reading {
  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
  width: min(86vw, 620px);
  text-align: center;
  font-style: italic;
  font-family: var(--reveal-bodytext-font, 'FrauncesReveal'), 'SuisseWorks', serif;
  font-weight: 480;
  font-variation-settings: 'SOFT' 0, 'WONK' 1;
  /* Sized off viewport WIDTH (2.6vw), which doesn't track available
     VERTICAL room at all -- on a landscape phone (wide, short) this
     clamps to its max, making the text run tall enough to overlap the
     "Blow out the candles" button below it (real screenshot, 2026-07-22).
     The @media (max-height) override further down shrinks this
     specifically for short viewports, independent of width. */
  font-size: clamp(17px, 2.6vw, 26px);
  line-height: 1.55;
  color: var(--ink-dim);
  opacity: 0;
  transition: opacity 1s ease;
  pointer-events: none;
  z-index: 4;
}
.tarot-full-reading.visible { opacity: 1; }
/* 2026-07-22 rework: sibling of #tarot-arena inside the sticky
   #tarot-viewport now (was a sibling of .tarot-arena under .tarot-stage,
   before .tarot-stage grew tall for the sticky scroll-runway -- appearing
   "after" it in flow would now land 60vh below the screen). Both fill the
   same sticky box exactly, so this fades in over the exact same screen
   region .tarot-arena already occupies, in place, with no scroll of any
   kind either way. height (not min-height) + overflow hidden on the parent
   -- this must actually fit within one screen, not scroll past it. */
.tarot-reading {
  display: none;
  opacity: 0;
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  z-index: 4;
  /* Was 2s -- but the three drawn cards now glide/morph into their spot
     above the deck photo here (see morphCardsToDeck in tarot.html) over
     about 1.1s, as a fixed-position clone that hands off to these real
     elements the moment it's removed. Since opacity on this panel caps
     how opaque everything inside it (including those real cards) can
     render regardless of their own opacity, a much slower fade here left
     the real cards visibly dimmer than the clone right at the handoff.
     Shortened so the fade and the glide finish close enough together that
     the swap doesn't read as a dimming pop. */
  transition: opacity 1.2s ease;
}
/* Was justify-content: center -- .tarot-reading's height still isn't
   guaranteed to exactly match its own content (min-height: 100% of
   .tarot-stage, whose own height depends on .tarot-arena's now-collapsing
   size, see .tarot-arena.exiting), so any leftover slack was getting
   split as equal empty space above AND below the content. Top-aligning
   removes that centering step entirely -- whatever slack remains (if any)
   only ever shows up below the content, not as a gap pushing the actions
   row away from the text above it. */
/* #55 */
/* #56 */
.tarot-reading.visible { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 18px; padding: 10px 0; box-sizing: border-box; }
.tarot-reading.visible.faded-in { opacity: 1; }
/* #86 */
.tarot-reading-deck-wrap {
  position: relative;
  width: fit-content;
  max-width: 100%;
  /* #57 */
  margin: 0 auto 14px;
  /* Real bug found 2026-07-22, from a real recording: .tarot-deck-cards
     below is positioned bottom: 100% -- it extends UPWARD from this
     element's own top edge by its own height (104px) + margin-bottom
     (22px), i.e. ~126px of space it needs above this box. With
     .tarot-reading's own top padding at just 10px and this box sitting
     flush against it (flex-start, see .tarot-reading.visible), those
     floating cards had nowhere to go but off the top of the screen,
     clipped by #tarot-viewport's overflow: hidden. This margin gives them
     the room they actually need; the mobile breakpoint below reduces it
     to match the smaller floating-card size there. */
  /* #58 */
  margin-top: 165px;
  /* The sticky nav (~100px tall) sits on top of the page at scroll position
     0 regardless of scroll — without this, scrollIntoView({block:'start'})
     lines this element's top edge up with the very top of the viewport,
     which is right where the nav covers it. */
  scroll-margin-top: 120px;
}
.tarot-reading-deck-photo {
  display: block;
  max-width: 460px;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.55));
}
/* #59 */
.tarot-deck-cards {
  position: absolute;
  bottom: 100%;
  left: 0;
  right: 0;
  height: 104px;
  margin-bottom: 22px;
  pointer-events: none;
  z-index: 2;
}
.tarot-deck-card {
  position: absolute;
  top: 0;
  width: 64px;
  height: 104px;
  margin-left: -32px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid rgba(176,141,87,0.45);
  box-shadow: 0 10px 22px rgba(0,0,0,0.45);
  /* Real bug found 2026-07-27, from a real recording: these real elements
     had no opacity of their own, only the whole .tarot-reading panel's
     opacity capped how visible they could get -- so for the ~1.1s the
     morph ghost is flying in from the reveal row, this real card (already
     sitting here, already partway through the panel's own fade) was
     visible underneath/behind it at the same time, reading as two copies
     of the same card. Hidden until morphCardsToDeck() explicitly reveals
     it (adds .landed) the instant the ghost actually arrives. */
  opacity: 0;
  transition: opacity 0.2s ease;
}
.tarot-deck-card.landed { opacity: 1; }
@keyframes deckCardFloat0 {
  0%, 100% { transform: rotate(-8deg) translateY(0); }
  50% { transform: rotate(-8deg) translateY(-7px); }
}
@keyframes deckCardFloat1 {
  0%, 100% { transform: translateY(-12px); }
  50% { transform: translateY(-19px); }
}
@keyframes deckCardFloat2 {
  0%, 100% { transform: rotate(8deg) translateY(0); }
  50% { transform: rotate(8deg) translateY(-7px); }
}
.tarot-deck-card-0 { left: 25%; z-index: 1; animation: deckCardFloat0 8s ease-in-out infinite; }
.tarot-deck-card-1 { left: 50%; z-index: 2; animation: deckCardFloat1 9s ease-in-out infinite; animation-delay: -2s; }
.tarot-deck-card-2 { left: 75%; z-index: 1; animation: deckCardFloat2 10s ease-in-out infinite; animation-delay: -4s; }
/* #60 */
.tarot-reading-deck-wrap .tarot-candle {
  left: auto;
  margin-left: 0;
  top: -60px;
}
/* Shifted from -58px to keep the candle visually centered in the same spot
   beside the deck photo now that the box itself is wider (fat-candle pass,
   2026-07-20) -- the candle graphic centers inside this box via its own
   left:50%/transform, so widening the box without compensating here would
   have nudged it 8px further in toward the deck. */
/* Real bug found 2026-07-27: every round of "-95px, still overlapping,
   -140px" tuning here (see git history / current-work.md) was chasing a
   symptom that had nothing to do with this value -- moveCandlesToReading()
   in tarot.html never cleared the inline style.left the two earlier stages
   set, so this rule was never actually being applied at all until that got
   fixed. Once it started applying for real, -140px (tuned against a no-op)
   turned out to be wildly oversized -- the left candle went fully off
   phone screens. Switched to a percentage of the deck-wrap's own width
   instead of a fixed px per breakpoint: scales continuously with however
   wide the deck actually renders, rather than needing a separately-guessed
   number at every width. One rule now covers every breakpoint -- the
   mobile and landscape-short overrides that used to duplicate this are
   removed below. First real pass at the number itself now that the
   mechanism genuinely works -- confirm against a fresh screenshot. */
.tarot-reading-deck-wrap .tarot-candle-left { left: -4%; }
.tarot-reading-deck-wrap .tarot-candle-right { left: auto; right: -4%; }
/* #61 */
.tarot-upsell-copy {
  max-width: 760px;
  margin: 0 auto;
  border-top: 1px solid var(--border);
  padding-top: 20px;
  text-align: center;
  /* #62 */
  margin-bottom: auto;
}
.tarot-upsell-copy p { margin: 0; }
/* #63 */
.tarot-upsell-teaser {
  margin-top: 10px !important;
  color: var(--accent);
  font-style: italic;
}
.tarot-upsell-teaser:empty { display: none; margin-top: 0 !important; }
/* #64 */
.tarot-upsell-cards {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.tarot-upsell-card { text-align: center; }
.tarot-upsell-position {
  display: block;
  font-family: 'RivieraNightsThin', sans-serif;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
}
.tarot-upsell-name { display: inline-block; width: auto; max-width: 180px; height: 24px; object-fit: contain; vertical-align: middle; }
/* #65 */
.tarot-upsell-tag {
  display: inline-block;
  margin-left: 8px;
  font-family: 'RivieraNightsThin', sans-serif;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
  vertical-align: middle;
}
.tarot-actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; align-items: center; margin-top: 8px; }
.tarot-email-form { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.tarot-email-input {
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 12px 14px;
  font-family: 'SuisseWorks', sans-serif;
  font-size: 15px;
  color: var(--ink);
  min-width: 240px;
}
.tarot-email-input::placeholder { color: var(--ink-dim); }
.tarot-email-input:focus { outline: none; border-color: var(--accent); }

@media (max-width: 860px) {
  /* .tarot-upsell (the old row/column-flipping wrapper) removed 2026-07-22
     -- .tarot-upsell-copy and .tarot-upsell-cards are direct
     #tarot-reading children now, always stacked, on every screen size
     (see styles.css above). Reserved space lives on margin-bottom: auto
     there, not a breakpoint-specific gap. */
  .tarot-upsell-cards { gap: 8px; }
  /* Position label and card name were stacked on separate lines each —
     3 cards of that ran long on a phone. Side by side on one line each
     cuts it to a third of the height. */
  .tarot-upsell-card { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; text-align: center; }
  .tarot-upsell-position { flex: 0 0 auto; }
  .tarot-upsell-name { height: 18px; }
  /* Golden/Special tag added 2026-07-27 -- wrap to its own line under the
     name on a narrow phone rather than forcing the row wider than the
     screen (see flex-wrap above, added for the same reason). */
  .tarot-upsell-tag { flex: 0 0 auto; margin-left: 0; }
  /* #66 */
  .tarot-reading-deck-wrap { max-width: 84%; scroll-margin-top: 100px; margin-top: 135px; }
  .tarot-deck-card { width: 46px; height: 75px; margin-left: -23px; }
  .tarot-deck-cards { height: 75px; margin-bottom: 14px; }
  /* Left/right clearance now handled by the single percentage-based rule
     above (2026-07-27) -- no separate mobile number needed. */
  /* Smaller offset to match the smaller mobile floating-cards row/candle
     size (same reasoning as the desktop -60px above). */
  .tarot-reading-deck-wrap .tarot-candle { top: -40px; }
  /* The candles are one shared pair of elements: they live in the shuffle
     arena first, then script.js moves them into .tarot-reading-deck-wrap
     once a reading starts (see tarot.html's deckWrap.appendChild calls).
     Only hidden here for the cramped pre-shuffle/shuffling screen — once
     bringCandlesToDesk() runs (in flipAndPile's step 2) it drives the
     candles' own opacity inline as part of its fly-in animation, which
     overrides this default and fades them in lit beside the deck. Plates
     (the little holder disks) stay hidden throughout; only the candles
     themselves were asked for. */
  #tarot-arena .tarot-candle { opacity: 0; pointer-events: none; }
  #tarot-arena .tarot-plate { display: none; }
  /* Arena height is the constant 100dvh set on the base rule for every
     breakpoint now, including .pre-mix -- see .tarot-arena.pre-mix .tarot-fan
     above for the 38.2%-from-bottom resting deck position, same at every
     width. */
  .tarot-fan { top: 50%; }
  /* Base (non-pre-shuffle) progress text — this is what "Which three cards
     call out to you?" uses once shuffling finishes — matches the fan's own
     center so the text lands in the middle of the ring, not off to one side. */
  .tarot-progress { top: 50%; }
  .tarot-progress.pre-shuffle { top: 74%; }
  /* Card width/height, and the caption/label/meaning sizing below it, are
     handled by the continuous clamp() and the single base caption size now
     — no breakpoint override needed here. */
  .tarot-plate { width: 38px; height: 13px; margin-top: -6.5px; }
  .tarot-plate.retired { transform: translateY(28px); }
  .tarot-plate-left { margin-left: -119px; }
  .tarot-plate-right { margin-left: 81px; }
  .tarot-candle { width: 22px; height: 53px; margin-top: -53px; }
  .tarot-candle.retired { transform: translateY(28px); }
  /* Same centered-on-the-plate fix as the desktop rule above. Left was
     already almost exact (-111.5 vs -111); right was off by 10.5px. */
  .tarot-candle-left { margin-left: -111px; }
  .tarot-candle-right { margin-left: 89px; }
  .tarot-candle-body { width: 22px; height: 35px; border-radius: 4px 4px 1px 1px; }
  .tarot-candle-wick { bottom: 35px; }
  .tarot-candle-flame { bottom: 39px; width: 9px; height: 16px; margin-left: -4.5px; }
  .tarot-candle-glow { bottom: 29px; width: 44px; height: 44px; margin-left: -22px; }
}

/* Touch-primary devices hide the candles regardless of screen width — a
   landscape tablet is desktop-width but still a fingertip trying to grab a
   16px-wide candle, so it gets the "touch and hold the deck" gesture
   instead, same as a phone. */
@media (pointer: coarse) {
  #tarot-arena .tarot-candle { opacity: 0; pointer-events: none; }
  #tarot-arena .tarot-plate { display: none; }
}

/* Tablet band: wider than a phone but not full desktop (e.g. a landscape
   iPad). Numbers below are interpolated halfway between the phone and
   desktop values above/below this block — not yet checked against a real
   tablet screenshot, so treat as a first pass to refine once seen. */
@media (min-width: 861px) and (max-width: 1180px) {
  /* Arena height is the constant 100dvh set on the base rule for every
     breakpoint now, including .pre-mix. */
  .tarot-fan { top: 50%; }
  /* Card width/height, and the caption/label/meaning sizing below it, are
     handled by the continuous clamp() and the single base caption size now
     — no breakpoint override needed here. */
  .tarot-plate { width: 46px; height: 15.5px; margin-top: -8px; }
  .tarot-plate.retired { transform: translateY(34px); }
  .tarot-plate-left { margin-left: -189px; }
  .tarot-plate-right { margin-left: 143px; }
  .tarot-candle { width: 27px; height: 65px; margin-top: -65px; }
  .tarot-candle.retired { transform: translateY(34px); }
  /* Same centered-on-the-plate fix as the desktop rule above. Left was off
     by 2.25px; right was off by 9.25px. */
  .tarot-candle-left { margin-left: -179.5px; }
  .tarot-candle-right { margin-left: 152.5px; }
  .tarot-candle-body { width: 27px; height: 43px; border-radius: 5px 5px 1px 1px; }
  .tarot-candle-wick { bottom: 43px; }
  .tarot-candle-flame { bottom: 48px; width: 11px; height: 19px; margin-left: -5.5px; }
  .tarot-candle-glow { bottom: 36px; width: 54px; height: 54px; margin-left: -27px; }
}

/* #67 */
@media (max-height: 820px) and (orientation: landscape) {
  .tarot-full-reading { font-size: clamp(13px, 2vw, 17px); line-height: 1.4; }
  .tarot-progress.reading-cta { font-size: 11px; padding: 2px 22px; }
  /* #68 */
  .tarot-card.featured .tarot-card-label { margin-top: 8px; font-size: 6px; }
  .tarot-card-meaning { font-size: 11px; }
  /* #69 */
  .tarot-reading.visible { gap: 10px; padding: 6px 0; }
  .tarot-reading-deck-wrap { margin-top: 90px; margin-bottom: 8px; }
  .tarot-deck-cards { height: 75px; margin-bottom: 14px; }
  .tarot-deck-card { width: 46px; height: 75px; margin-left: -23px; }
  .tarot-reading-deck-photo { max-width: 260px; }
  .tarot-reading-deck-wrap .tarot-candle { top: -45px; }
  /* Left/right clearance now handled by the single percentage-based rule
     above (2026-07-27) -- no separate landscape number needed. */
  .tarot-upsell-copy { padding-top: 10px; font-size: 12px; }
  .tarot-upsell-cards { gap: 8px; }
  .tarot-upsell-name { height: 18px; }
  .tarot-actions { margin-top: 4px; }
}
/* #70 */
.tarot-candle.candle-reveal-landscape { width: 43px; height: 103px; margin-top: -103px; }
.tarot-candle.candle-reveal-landscape .tarot-candle-body { width: 43px; height: 68px; }
.tarot-candle.candle-reveal-landscape .tarot-candle-wick { bottom: 68px; width: 3px; height: 12px; }
.tarot-candle.candle-reveal-landscape .tarot-candle-flame { bottom: 76px; width: 18px; height: 30px; margin-left: -9px; }
.tarot-candle.candle-reveal-landscape .tarot-candle-glow { bottom: 57px; width: 86px; height: 86px; margin-left: -43px; }
