/* ====================================================================
   SHARED INVITATION CORE, styling
   Theme tokens, the music picker, the theme dock and the calendar menu.
   Every card style loads this, then adds only its own design on top.
   ==================================================================== */

:root {
  /* Defaults are the maroon theme; applyTheme() overwrites the triplets.
     Each colour is declared twice -- as an RGB triplet so alpha modifiers
     work, and as a colour for plain CSS. */
  --canvas-deep-rgb:  42 4 21;
  --canvas-dark-rgb:  61 6 32;
  --canvas-mid-rgb:   74 11 40;
  --canvas-rgb:       92 10 45;
  --canvas-light-rgb: 122 17 65;
  --accent-dark-rgb:  161 98 7;
  --accent-rich-rgb:  184 134 11;
  --accent-rgb:       212 168 71;
  --accent-light-rgb: 245 230 184;
  --seal-1-rgb:       192 57 43;
  --seal-2-rgb:       146 43 33;
  --seal-3-rgb:       107 30 22;

  --canvas-deep:  rgb(var(--canvas-deep-rgb));
  --canvas-dark:  rgb(var(--canvas-dark-rgb));
  --canvas-mid:   rgb(var(--canvas-mid-rgb));
  --canvas:       rgb(var(--canvas-rgb));
  --canvas-light: rgb(var(--canvas-light-rgb));
  --accent-dark:  rgb(var(--accent-dark-rgb));
  --accent-rich:  rgb(var(--accent-rich-rgb));
  --accent:       rgb(var(--accent-rgb));
  --accent-light: rgb(var(--accent-light-rgb));
  --seal-1:       rgb(var(--seal-1-rgb));
  --seal-2:       rgb(var(--seal-2-rgb));
  --seal-3:       rgb(var(--seal-3-rgb));

  --foil: linear-gradient(135deg, var(--accent-dark) 0%, var(--accent) 30%,
          var(--accent-light) 50%, var(--accent) 70%, var(--accent-dark) 100%);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; background: var(--canvas-deep); -webkit-font-smoothing: antialiased; }

/* Decorative SVGs paint with currentColor so one rule retints them all.
   currentColor rather than var() in the attribute, because var() in SVG
   presentation attributes is broken on iOS Safari 15 and older. */
svg { color: var(--accent); }

/* Gold-foil text. The padding matters: display faces overflow their line
   box, and background-clip:text paints only inside that box, so a tight
   line-height silently slices the tops off capitals. */
.foil {
  background: var(--foil); background-size: 200% auto;
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
  padding-block: 0.18em; margin-block: -0.18em;
}
.foil-shimmer { animation: coreShimmer 7s ease-in-out infinite; }
@keyframes coreShimmer { 0% { background-position: 200% center; } 100% { background-position: -200% center; } }

/* Reveal-on-scroll, driven by core.js */
[data-r] {
  opacity: 0; transform: translateY(24px); filter: blur(4px);
  transition: opacity .8s ease, transform .8s cubic-bezier(.25,.46,.45,.94), filter 1s ease;
}
[data-r].on { opacity: 1; transform: none; filter: none; }

/* ---------------- theme dock ---------------- */
.theme-dock {
  position: fixed; right: 14px; bottom: 14px; z-index: 250;
  display: grid; grid-template-columns: repeat(2, auto); gap: 8px;
  padding: 10px; border-radius: 16px;
  background: rgb(var(--canvas-deep-rgb) / 0.72);
  border: 1px solid rgb(var(--accent-rgb) / 0.28);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  box-shadow: 0 6px 24px rgba(0,0,0,0.38);
}
.theme-dock[hidden] { display: none; }
[dir="rtl"] .theme-dock { right: auto; left: 14px; }
.theme-dock-label {
  grid-column: 1 / -1; text-align: center; margin-bottom: 1px;
  font-size: 9px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: rgb(var(--accent-rgb) / .55);
}
.theme-swatch {
  width: 24px; height: 24px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 1px solid rgb(var(--accent-rgb) / .3); position: relative; flex: none;
  transition: transform .25s cubic-bezier(.34,1.56,.64,1), border-color .25s ease, box-shadow .25s ease;
}
.theme-swatch::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; }
.theme-swatch:hover { transform: scale(1.15); }
.theme-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.theme-swatch[aria-checked="true"] {
  border-color: rgb(var(--accent-rgb) / .95);
  box-shadow: 0 0 0 2px rgb(var(--accent-rgb) / .28);
  transform: scale(1.08);
}

/* ---------------- sound button + menus ---------------- */
.sound-btn {
  position: fixed; left: 14px; bottom: 14px; z-index: 250;
  width: 38px; height: 38px; border-radius: 50%; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: rgb(var(--accent-rgb) / .85);
  background: rgb(var(--canvas-deep-rgb) / .72);
  border: 1px solid rgb(var(--accent-rgb) / .28);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  box-shadow: 0 6px 24px rgba(0,0,0,.38);
  transition: transform .25s ease, border-color .25s ease, color .25s ease;
}
.sound-btn[hidden] { display: none; }
.sound-btn:hover { transform: scale(1.08); border-color: rgb(var(--accent-rgb) / .6); }
.sound-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.sound-btn svg { width: 18px; height: 18px; color: inherit; }
.sound-btn .slash { display: none; }
.sound-btn.muted { color: rgb(var(--accent-rgb) / .4); }
.sound-btn.muted .wave { display: none; }
.sound-btn.muted .slash { display: block; }
[dir="rtl"] .sound-btn { left: auto; right: 14px; }

.pop {
  position: fixed; z-index: 260; min-width: 176px; padding: 7px; border-radius: 14px;
  background: rgb(var(--canvas-deep-rgb) / .94);
  border: 1px solid rgb(var(--accent-rgb) / .3);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px rgba(0,0,0,.45);
  display: flex; flex-direction: column; gap: 1px;
  opacity: 0; pointer-events: none; transition: opacity .2s ease;
}
.pop.open { opacity: 1; pointer-events: auto; }
.pop[hidden] { display: none; }
.pop-head {
  font-size: 9px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: rgb(var(--accent-rgb) / .45); padding: 4px 10px 6px;
}
.pop-item {
  display: flex; align-items: center; gap: 9px; width: 100%;
  font-family: inherit; font-size: 14px; font-weight: 500; letter-spacing: .04em;
  text-align: left; white-space: nowrap;
  color: rgb(var(--accent-rgb) / .72);
  background: none; border: none; cursor: pointer;
  padding: 8px 10px; border-radius: 9px;
  transition: background .2s ease, color .2s ease;
}
[dir="rtl"] .pop-item { text-align: right; }
.pop-item:hover { background: rgb(var(--accent-rgb) / .1); color: var(--accent); }
.pop-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pop-item[aria-checked="true"] { color: var(--accent-light); background: rgb(var(--accent-rgb) / .14); }
.pop-item .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; background: currentColor; opacity: 0; }
.pop-item[aria-checked="true"] .dot { opacity: 1; }
.pop-sep { height: 1px; margin: 4px 8px; background: rgb(var(--accent-rgb) / .16); }

/* ---------------- language switch ---------------- */
/* Ten languages will not sit in one row on a phone. The row shows the current
   language and opens the rest on tap; the list sits ABOVE the toggle because
   on the gate the row is bottom-anchored and has to grow upward. */
.lang-row { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; }
.lang-list { display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
             gap: 0 2px; max-width: min(330px, 86vw); }
.lang-list[hidden] { display: none; }
.lang-toggle { white-space: nowrap; }

/* Six cards pin a shade picker bottom-right while the language toggle sits
   centred at the same height. Seven 18px dots make a ~188px pill, which at
   375px lands straight on top of the language name. Collapsed it shows only
   the shade that is on; opened it grows UPWARD as a column, so it never
   crosses the centre of the screen at any width. Both states are driven from
   here and from core.js, so no card needs its own copy. */
.pal-dock .pal-dot { display: none; }
.pal-dock .pal-dot[aria-pressed="true"] { display: block; }
/* Safety net: if nothing is marked active. A stored colourway that no
   longer exists, or a card whose builder forgot to mark one, show them
   all rather than an empty pill. */
.pal-dock:not(:has(.pal-dot[aria-pressed="true"])) .pal-dot { display: block; }
.pal-dock.open { flex-direction: column-reverse; align-items: center; }
.pal-dock.open .pal-dot { display: block; }
.lang-btn {
  font-family: inherit; font-size: 12px; font-weight: 500; letter-spacing: .08em;
  color: rgb(var(--accent-rgb) / .4); background: none; border: none; cursor: pointer;
  padding: 6px 10px; transition: color .3s ease;
}
.lang-btn:hover { color: rgb(var(--accent-rgb) / .8); }
.lang-btn.active { color: var(--accent); font-weight: 600; }
.lang-sep { color: rgb(var(--accent-rgb) / .2); font-size: 11px; }

/* ---------------- shared bits ---------------- */
.accent-line { height: 1px; background: linear-gradient(90deg, transparent, var(--accent), transparent); }
.credit-line { text-align: center; font-size: 12px; letter-spacing: .12em; color: rgb(var(--accent-rgb) / .32); }
.credit-line a { color: rgb(var(--accent-rgb) / .55); text-decoration: none; border-bottom: 1px solid currentColor; }
.credit-line a:hover { color: var(--accent); }

@media (max-width: 767px) {
  .theme-dock { right: 10px; bottom: 10px; gap: 8px; padding: 10px 8px; }
  .theme-swatch { width: 26px; height: 26px; }
  .theme-dock-label { display: none; }
  .sound-btn { left: 10px; bottom: 10px; width: 34px; height: 34px; }
  [dir="rtl"] .sound-btn { left: auto; right: 10px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  [data-r] { opacity: 1 !important; transform: none !important; filter: none !important; }
}

/* ---------------- scratch to reveal ---------------- */
/* A design supplies its own colours; this is only the mechanism. */
#scratch { text-align: center; }
.scratch-panel {
  position: relative; margin: 0 auto; overflow: hidden;
  width: min(440px, 88%); aspect-ratio: 16 / 10;
  border-radius: 4px; display: grid; place-items: center;
  touch-action: none;                    /* the drag is ours, not the page's */
  user-select: none; -webkit-user-select: none;
}
.scratch-canvas {
  position: absolute; inset: 0; z-index: 2; cursor: grab;
  touch-action: none;
}
.scratch-canvas:active { cursor: grabbing; }
.scratch-under { position: relative; z-index: 1; padding: 22px 18px; }
.scratch-hint {
  margin-top: 16px; font-size: 12px; letter-spacing: .18em; text-transform: uppercase;
  transition: opacity .5s ease;
}
#scratch.scratching .scratch-hint, #scratch.done .scratch-hint { opacity: 0; }
.scratch-skip {
  display: inline-block; margin-top: 10px; background: none; border: 0;
  font: inherit; font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase;
  cursor: pointer; opacity: .55; transition: opacity .3s ease;
}
.scratch-skip:hover { opacity: 1; }
#scratch.done .scratch-skip { display: none; }
#scratch.done .scratch-under { animation: scratchPop .8s cubic-bezier(.34,1.56,.64,1); }
@keyframes scratchPop { 0% { transform: scale(.94); } 60% { transform: scale(1.03); } 100% { transform: none; } }

/* Everything past the scratch panel stays shut until it is revealed. */
#gated { opacity: 0; transform: translateY(28px); transition: opacity 1.1s ease, transform 1.2s cubic-bezier(.16,1,.3,1); }
#gated.open { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  #gated { opacity: 1; transform: none; }
  #scratch.done .scratch-under { animation: none; }
}

/* ---- non-Latin label typography -------------------------------------
   Every card styles its small labels the Latin way: `text-transform:
   uppercase` plus .2-.4em of tracking. Uppercase is a no-op in Indic and
   Arabic, and the tracking is actively wrong in both:

   - Arabic script JOINS. Letter-spacing pulls the joins apart, so Urdu was
     rendering as disconnected letterforms in 33 places per card.
   - Indic conjuncts get visually separated, and the width cost is severe:
     at .22em on 9.5px type, the 10-character Kannada word for "seconds"
     carries 21px of pure tracking, which pushed it out of a 58px countdown
     cell on a 320px phone. Malayalam overflowed the same cell.

   Scoped by the `lang` attribute core.js already sets, and written with a
   `html:not(...)` prefix so it outranks the card's own bare-class rule
   whatever the source order. */
html:not([lang="en"]) .caps,
html:not([lang="en"]) .cd-lbl,
html:not([lang="en"]) .cover-hint,
html:not([lang="en"]) .ev-lbl,
html:not([lang="en"]) .fam-lbl,
html:not([lang="en"]) .fn-chip,
html:not([lang="en"]) .fn-lbl,
html:not([lang="en"]) .gate-kicker,
html:not([lang="en"]) .kicker,
html:not([lang="en"]) .lang-btn,
html:not([lang="en"]) .lbl,
html:not([lang="en"]) .made,
html:not([lang="en"]) .note-lbl,
html:not([lang="en"]) .prog-time,
html:not([lang="en"]) .ref,
html:not([lang="en"]) .scratch-hint,
html:not([lang="en"]) .scratch-skip,
html:not([lang="en"]) .short-names,
html:not([lang="en"]) .sec-head,
html:not([lang="en"]) .fn-btn,
html:not([lang="en"]) .ev-btn,
html:not([lang="en"]) .fn-name,
html:not([lang="en"]) .pop-head,
html:not([lang="en"]) .pop-item,
/* #knock and #gate copy are IDs, so they need the ID in the selector to be
   outranked, a class-only rule loses to `#knock` however it is written. */
html:not([lang="en"]) #knock,
html:not([lang="en"]) #gate .gate-kicker,
html:not([lang="en"]) #gate .writ-kicker {
  letter-spacing: .03em;
  text-transform: none;
}

/* Arabic must not be tracked ANYWHERE. The script joins, and any positive
   letter-spacing severs the joins, so this is a rendering correction rather
   than a style preference. It is universal and !important because several
   cards set tracking from ID-scoped rules (`#gate .caps`), which outrank any
   class selector no matter how it is written. Urdu is the only right-to-left
   language here, so nothing else is affected. */
html[lang="ur"] * { letter-spacing: normal !important; }

/* ---- the name on the envelope ----------------------------------------
   core.js inserts this just before #knock when a link carries `&g=`. One
   shared block for all cards; a card only moves it (`#gate .addressee`) to
   where a hand would write on that particular gate. Colour is inherited so
   it follows every colourway. The face is the card's own script face when
   it loads one (`--f-hand` set on #gate), otherwise its display face. */
.addressee {
  position: absolute;
  left: 50%;
  bottom: var(--addr-bottom, 18%);
  transform: translateX(-50%);
  z-index: 5;
  margin: 0;
  width: max-content;
  max-width: min(78vw, 30ch);
  text-align: center;
  font-family: var(--f-hand, var(--f-display, Georgia, serif));
  font-size: clamp(1.15rem, 4.6vw, 1.9rem);
  font-weight: 400;
  line-height: 1.2;
  color: var(--addr-ink, inherit);
  text-wrap: balance;
  pointer-events: none;
}
.addressee .addr-name,
.addressee .addr-msg { display: block; }
.addressee.long .addr-name { font-size: .8em; }
/* A name in any script. Tracking severs Arabic joins and Indic conjuncts,
   and letter-spacing inherits, so this must be stated on the element and
   must beat the ID-scoped tracking several cards put on #gate copy. */
.addressee .addr-name { letter-spacing: normal !important; text-transform: none; }
.addressee .addr-msg {
  margin-top: .45rem;
  font-family: var(--f-body, var(--addr-body, serif));
  font-size: clamp(.78rem, 2.6vw, .95rem);
  font-style: normal;
  line-height: 1.4;
  letter-spacing: normal;
  text-transform: none;
}
/* Written, not faded. The name is revealed left to right as a pen would
   leave it; the personal line follows once the name is complete. */
.addressee .addr-name { clip-path: inset(0 100% 0 0); opacity: 0; }
.addressee .addr-msg  { opacity: 0; }
.addressee.on .addr-name {
  clip-path: inset(0 0 0 0); opacity: 1;
  transition: clip-path 1.15s cubic-bezier(.2,.7,.2,1), opacity .5s ease;
}
.addressee.on .addr-msg { opacity: .78; transition: opacity .6s ease .95s; }
/* Urdu is written from the right, so the pen starts there. dir="auto"
   decides per name; the lang rule covers browsers without :dir(). Both
   resets outrank their start rules, whatever the source order. */
.addressee:dir(rtl) .addr-name,
html[lang="ur"] .addressee .addr-name { clip-path: inset(0 0 0 100%); }
.addressee.on:dir(rtl) .addr-name,
html[lang="ur"] .addressee.on .addr-name { clip-path: inset(0 0 0 0); }
@media (prefers-reduced-motion: reduce) {
  .addressee .addr-name, .addressee .addr-msg { clip-path: none; transition: none; }
  .addressee .addr-name { opacity: 1; }
  .addressee .addr-msg  { opacity: .78; }
}
