/* vihuplanet-home.css — VihuPlanet Home's own shell.
 *
 * Almost nothing, on purpose. Everything a child sees here is the
 * runtime (vihuplanet/runtime/css/runtime.css); this file positions the
 * universe and styles one line of text for the case where a creator has
 * not shared anything yet.
 */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  /* Painted, not inherited — this is the colour behind the universe in
     the moment before its first frame lands. */
  background: var(--vp-ink, #1E2842);
  font-family: 'Kalam', system-ui, sans-serif;
  color: var(--vp-paper, #F1EAD0);
  /* The Ether fills the window and never scrolls. Whatever is off the
     edge is elsewhere in the universe, not further down the page. */
  overflow: hidden;
  overscroll-behavior: none;
}

.vp-home {
  position: relative;
  width: 100%;
  height: 100%;
  /* Respects a phone's notch without letting the universe stop short of
     the edges — the universe is full-bleed; only the text inset. */
  height: 100dvh;
}

.vp-home-universe {
  position: absolute;
  inset: 0;
}

/* ---------- the quiet state ---------- */

/* An Ether with nothing in it is not an error state and gets no error
   styling: no card, no icon, no border. One line of handwriting low in
   the frame, the same way the Hero speaks (sky-caption, never a modal —
   Art Direction v1.0 forbids the alternatives by name). */
.ether-quiet {
  position: absolute;
  left: 50%;
  bottom: calc(12vh + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 6000;
  width: min(30rem, calc(100% - 3rem));
  text-align: center;
  /* The universe underneath stays touchable — a child can still reach
     into the Ether while this is on screen. */
  pointer-events: none;
}

.ether-quiet-line {
  margin: 0 0 0.85rem;
  font-family: 'Caveat', 'Kalam', cursive;
  font-size: clamp(1.15rem, 1rem + 0.8vw, 1.5rem);
  line-height: 1.35;
  text-wrap: balance;
  color: rgba(241, 234, 208, 0.82);
  text-shadow: 0 1px 6px rgba(9, 13, 26, 0.9);
}

.ether-quiet-link {
  pointer-events: auto;
  display: inline-block;
  padding: 0.4rem 1rem;
  border-radius: 999px;
  border: 1px solid rgba(232, 184, 113, 0.45);
  background: rgba(18, 24, 43, 0.5);
  color: var(--vp-candle, #E8B871);
  font-size: 0.95rem;
  text-decoration: none;
  transition: background 220ms ease, border-color 220ms ease;
}

.ether-quiet-link:hover,
.ether-quiet-link:focus-visible {
  background: rgba(232, 184, 113, 0.16);
  border-color: rgba(232, 184, 113, 0.75);
}

.ether-quiet-link:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .ether-quiet-link { transition: none; }
}

/* ---------- Stage 4 · Preview ----------
 *
 * Low and to one side, never a modal over the middle: the Spirit the
 * child just met is at the centre of the screen, and covering it with
 * a panel would hide the thing they came for. Art Direction v1.0
 * forbids modals by name; this is a caption beside a story, not a
 * dialog about one. */

.ether-preview {
  position: absolute;
  left: 50%;
  bottom: calc(4vh + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  /* Above the permanent actions, which occupy the same corner of the
     screen. Below them the nav overlapped the preview's own buttons and
     swallowed the clicks — Cheer and Back were unreachable while a
     Spirit was met. */
  z-index: 6800;
  width: min(34rem, calc(100% - 2rem));
  text-align: center;
  padding: 1rem 1.1rem 1.1rem;
  border-radius: 16px;
  background: rgba(12, 17, 32, 0.62);
  border: 1px solid rgba(241, 234, 208, 0.14);
  backdrop-filter: blur(10px);
  animation: vp-preview-in 420ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

@keyframes vp-preview-in {
  from { opacity: 0; transform: translateX(-50%) translateY(14px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.ether-preview-title {
  margin: 0;
  font-family: 'Caveat', 'Kalam', cursive;
  font-weight: 700;
  font-size: clamp(1.35rem, 1.1rem + 1vw, 1.8rem);
  line-height: 1.2;
  text-wrap: balance;
  color: var(--vp-paper, #F1EAD0);
}

.ether-preview-creator {
  margin: 0.15rem 0 0;
  font-size: 0.9rem;
  color: var(--vp-candle, #E8B871);
}

.ether-preview-meta {
  margin: 0.3rem 0 0.9rem;
  font-size: 0.8rem;
  color: rgba(241, 234, 208, 0.5);
  font-variant-numeric: tabular-nums;
}

.ether-preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  justify-content: center;
}

.ether-preview-actions button {
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: 0.9rem;
  padding: 0.42rem 0.95rem;
  border-radius: 999px;
  border: 1px solid rgba(241, 234, 208, 0.26);
  background: rgba(241, 234, 208, 0.07);
  color: var(--vp-paper, #F1EAD0);
  cursor: pointer;
  transition: background 200ms ease, border-color 200ms ease;
}

.ether-preview-actions button:hover:not(:disabled),
.ether-preview-actions button:focus-visible {
  background: rgba(232, 184, 113, 0.18);
  border-color: rgba(232, 184, 113, 0.6);
}

.ether-preview-actions button.is-primary {
  background: rgba(232, 184, 113, 0.2);
  border-color: rgba(232, 184, 113, 0.55);
  color: var(--vp-candle, #E8B871);
}

.ether-preview-actions button:disabled {
  opacity: 0.42;
  cursor: default;
}

.ether-preview-actions button:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: 3px;
}

/* ---------- Stage 5 · the Portal ----------
 *
 * The Spirit opens and the Traveller steps in. It is an OVERLAY: the
 * universe is still mounted underneath with every Spirit exactly where
 * it was, which is what makes coming back exact rather than restored.
 *
 * The veil grows from the Spirit's own position on screen (--vp-portal-x
 * / y, written by ether.js) so the portal opens out of the light rather
 * than appearing over it. */

.ether-portal {
  position: absolute;
  inset: 0;
  z-index: 7000;
}

.ether-portal-veil {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at var(--vp-portal-x, 50%) var(--vp-portal-y, 50%),
      rgba(232, 184, 113, 0.30) 0%,
      rgba(30, 40, 66, 0.94) 34%,
      rgba(9, 13, 26, 0.985) 100%);
  opacity: 0;
  transform: scale(0.35);
  transform-origin: var(--vp-portal-x, 50%) var(--vp-portal-y, 50%);
  transition: opacity 560ms ease, transform 620ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.ether-portal.is-open .ether-portal-veil {
  opacity: 1;
  transform: scale(1);
}

.ether-portal-inner {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  padding: clamp(0.7rem, 2vh, 1.4rem);
  gap: 0.6rem;
  opacity: 0;
  transition: opacity 380ms ease 240ms;
}

.ether-portal.is-open .ether-portal-inner { opacity: 1; }

.ether-portal-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex: 0 0 auto;
}

.ether-portal-title {
  font-family: 'Caveat', 'Kalam', cursive;
  font-size: clamp(1.1rem, 0.9rem + 0.8vw, 1.5rem);
  color: var(--vp-paper, #F1EAD0);
}

.ether-portal-close {
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: 0.88rem;
  padding: 0.38rem 0.9rem;
  border-radius: 999px;
  border: 1px solid rgba(241, 234, 208, 0.3);
  background: rgba(12, 17, 32, 0.5);
  color: var(--vp-paper, #F1EAD0);
  cursor: pointer;
  white-space: nowrap;
}

.ether-portal-close:hover,
.ether-portal-close:focus-visible {
  border-color: rgba(232, 184, 113, 0.7);
  color: var(--vp-candle, #E8B871);
}

.ether-portal-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.3rem, 1.5vw, 1rem);
}

/* An <img> renders at its NATURAL size unless something tells it not
   to, and max-width/max-height only ever shrink. A page image is small
   by nature — the strip thumbnail is 110px — so the portal was showing
   a whole story at postage-stamp size in the middle of an empty
   screen, and nothing about the CSS looked wrong because none of it
   was: the rules simply never applied.

   `height: 100%` with `width: auto` fills the stage vertically and
   lets the aspect ratio decide the width; the max-width then pulls it
   back in on a narrow screen. A page now fills what it is given. */
.ether-portal-page {
  height: 100%;
  width: auto;
  max-width: 100%;
  max-height: 100%;
  min-height: 0;
  object-fit: contain;
  border-radius: 10px;
  box-shadow: 0 18px 50px rgba(6, 9, 20, 0.66);
}

.ether-portal-arrow {
  flex: 0 0 auto;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  border: 1px solid rgba(241, 234, 208, 0.24);
  background: rgba(12, 17, 32, 0.5);
  color: var(--vp-paper, #F1EAD0);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.ether-portal-arrow:hover:not(:disabled) {
  border-color: rgba(232, 184, 113, 0.7);
  color: var(--vp-candle, #E8B871);
}

.ether-portal-arrow:disabled { opacity: 0.25; cursor: default; }

.ether-portal-count {
  flex: 0 0 auto;
  margin: 0;
  text-align: center;
  font-size: 0.8rem;
  color: rgba(241, 234, 208, 0.45);
  font-variant-numeric: tabular-nums;
}

.ether-portal-close:focus-visible,
.ether-portal-arrow:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: 3px;
}

@media (max-width: 640px) {
  .ether-preview { padding: 0.8rem 0.8rem 0.9rem; }
  .ether-preview-actions button { font-size: 0.84rem; padding: 0.36rem 0.75rem; }
  .ether-portal-arrow { width: 2.2rem; height: 2.2rem; font-size: 1.25rem; }
}

/* The portal still opens and closes under reduced motion — it is a
   response to something the child pressed, and removing it would make
   the control look broken rather than calm. It simply does not grow. */
@media (prefers-reduced-motion: reduce) {
  .ether-portal-veil { transition: opacity 200ms ease; transform: none; }
  .ether-portal.is-open .ether-portal-veil { transform: none; }
  .ether-preview { animation: none; }
}


/* ===============================================================
   VihuPlanet Home
   =============================================================== */

/* ---------- the threshold ---------- */

.vp-threshold {
  position: absolute;
  inset: 0;
  z-index: 8000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.6rem;
  cursor: pointer;
  /* Deliberately not opaque: the Ether is already alive underneath and
     a child should half-see it before they are let in. */
  background: radial-gradient(ellipse at 50% 50%,
    rgba(12, 17, 32, 0.42) 0%,
    rgba(9, 13, 26, 0.78) 70%);
  transition: opacity 780ms ease;
}

.vp-threshold.is-gone {
  opacity: 0;
  pointer-events: none;
}

.vp-threshold-title {
  margin: 0;
  font-family: 'Caveat', 'Kalam', cursive;
  font-weight: 700;
  font-size: clamp(2.6rem, 1.8rem + 4vw, 4.6rem);
  line-height: 1;
  letter-spacing: 0.4px;
  color: var(--vp-paper, #F1EAD0);
  text-shadow: 0 2px 24px rgba(9, 13, 26, 0.8);
}

.vp-threshold-tap {
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: clamp(0.95rem, 0.85rem + 0.4vw, 1.1rem);
  padding: 0.6rem 1.6rem;
  border-radius: 999px;
  border: 1px solid rgba(232, 184, 113, 0.5);
  background: rgba(18, 24, 43, 0.5);
  color: var(--vp-candle, #E8B871);
  cursor: pointer;
  /* The one breathing thing on the page, because it is the one thing
     being asked for. Slow enough to read as an invitation.

     It breathes with LIGHT and never with position. A tap target that
     drifts, even by two pixels, is a target a small hand has to chase
     — and an element that never stops moving is also one no automated
     check can ever click. Both problems, one cause. */
  animation: vp-threshold-breathe 3.6s ease-in-out infinite;
}

@keyframes vp-threshold-breathe {
  0%, 100% { opacity: 0.74; box-shadow: 0 0 0 0 rgba(232, 184, 113, 0); }
  50%      { opacity: 1;    box-shadow: 0 0 22px 0 rgba(232, 184, 113, 0.22); }
}

.vp-threshold-tap:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: 3px;
}

/* ---------- the two permanent actions ---------- */

.vp-actions {
  position: absolute;
  left: 50%;
  bottom: calc(5vh + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 6500;
  display: flex;
  gap: 0.75rem;
  opacity: 0;
  transition: opacity 620ms ease, transform 620ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.vp-actions.is-in { opacity: 1; }

.vp-action {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: clamp(0.92rem, 0.85rem + 0.3vw, 1.05rem);
  padding: 0.6rem 1.25rem;
  border-radius: 999px;
  border: 1px solid rgba(241, 234, 208, 0.24);
  background: rgba(12, 17, 32, 0.6);
  color: var(--vp-paper, #F1EAD0);
  cursor: pointer;
  backdrop-filter: blur(8px);
  transition: background 220ms ease, border-color 220ms ease, transform 220ms ease;
}

.vp-action:hover { transform: translateY(-2px); }

.vp-action:hover,
.vp-action:focus-visible {
  background: rgba(232, 184, 113, 0.16);
  border-color: rgba(232, 184, 113, 0.6);
}

.vp-action.is-primary {
  background: rgba(232, 184, 113, 0.18);
  border-color: rgba(232, 184, 113, 0.5);
  color: var(--vp-candle, #E8B871);
}

.vp-action:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: 3px;
}

.vp-action-glyph { font-size: 1.05em; line-height: 1; }

/* The preview replaces the actions while a Spirit is being met — the
   child is looking at one story, not at the whole universe.
   Driven by a class rather than :has(), so it does not depend on
   selector support for something that decides whether controls are
   clickable. */
.vp-home.is-met .vp-actions,
/* And while a child is drawing their stars. Left up, the two actions
   sit under the veil as ghosts of themselves — present enough to
   notice, too dim to use, and inviting a tap that lands on the veil.
   Either they work or they are not there. */
.vp-home.is-marking .vp-actions { opacity: 0; pointer-events: none; }

@media (max-width: 640px) {
  .vp-actions { gap: 0.5rem; width: min(100%, 26rem); padding: 0 0.75rem; }
  .vp-action { flex: 1 1 0; justify-content: center; padding: 0.6rem 0.75rem; }
  /* The longer label needs the room; the glyph is decoration and the
     words are the instruction. */
  .vp-action-glyph { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vp-threshold-tap { animation: none; opacity: 1; }
  .vp-actions, .vp-threshold { transition: opacity 200ms ease; }
  .vp-action:hover { transform: none; }
}


/* ===============================================================
   Mark Your Stars
   ===============================================================

   Recognition, not authentication — so it must not look like a login
   box. No form, no field, no lock icon, no "sign in" chrome of any
   kind: a title, a line, a sky, and two buttons.

   It is an OVERLAY over the living Ether. The universe keeps drifting
   behind it and is never torn down, which is what makes this feel like
   being recognised by somewhere rather than logged into something. */

.vp-stars {
  position: absolute;
  inset: 0;
  z-index: 7200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(0.6rem, 2vh, 1.2rem);
}

/* Any class rule that sets `display` outranks the browser's own
   [hidden] { display: none }, because a class beats a UA-sheet
   attribute selector. Without this the closed overlay stays laid out
   across the whole screen, invisible and still swallowing every tap —
   the two permanent actions underneath simply stop working. Every
   `display` rule in this section that is paired with a `hidden`
   attribute needs its own guard, so they are all here together. */
.vp-stars[hidden],
.vp-stars-actions[hidden] { display: none; }

.vp-stars-veil {
  position: absolute;
  inset: 0;
  /* Deliberately not opaque. The Ether stays half-visible through it,
     because the thing doing the recognising is the universe. */
  background: radial-gradient(ellipse at 50% 46%,
    rgba(12, 17, 32, 0.62) 0%,
    rgba(9, 13, 26, 0.92) 72%);
  opacity: 0;
  transition: opacity 420ms ease;
}

.vp-stars.is-open .vp-stars-veil { opacity: 1; }

.vp-stars-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  max-width: min(30rem, 100%);
  max-height: 100%;
  text-align: center;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 380ms ease 120ms, transform 420ms cubic-bezier(0.22, 0.61, 0.36, 1) 120ms;
}

.vp-stars.is-open .vp-stars-panel { opacity: 1; transform: translateY(0); }

.vp-stars-title {
  margin: 0;
  font-family: 'Caveat', 'Kalam', cursive;
  font-weight: 700;
  font-size: clamp(1.9rem, 1.4rem + 2vw, 2.9rem);
  line-height: 1.05;
  color: var(--vp-paper, #F1EAD0);
  text-shadow: 0 2px 20px rgba(9, 13, 26, 0.8);
}

.vp-stars-subtitle {
  margin: 0;
  font-family: 'Caveat', 'Kalam', cursive;
  font-size: clamp(1.05rem, 0.95rem + 0.6vw, 1.35rem);
  line-height: 1.3;
  text-wrap: balance;
  color: rgba(241, 234, 208, 0.78);
}

/* ---------- the board ---------- */

.vp-stars-sky {
  width: min(22rem, 78vmin);
  max-width: 100%;
}

.vp-stars-board {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--vp-stars-size, 10), 1fr);
  grid-template-rows: repeat(var(--vp-stars-size, 10), 1fr);
  aspect-ratio: 1 / 1;
  width: 100%;
  gap: 0;
  /* A patch of sky, not a table. No gridlines and no cell borders —
     the numbers below are the only structure ever drawn, and they are
     there so a parent reading "row 3, column 5" off a recovery email
     has something to read it against. */
  border-radius: 14px;
  background: rgba(9, 13, 26, 0.42);
  border: 1px solid rgba(241, 234, 208, 0.10);
}

/* One extra track on each side, sized to its own content so the sky
   keeps almost all of the square. */
.vp-stars-board.has-labels {
  grid-template-columns: auto repeat(var(--vp-stars-size, 10), 1fr);
  grid-template-rows: auto repeat(var(--vp-stars-size, 10), 1fr);
  padding: 2px 4px 4px 2px;
}

.vp-stars-label {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Faint on purpose. They are a reference for the rare moment a card
     is being read back from an email, not part of the sky — a child
     drawing their own constellation should not notice them at all. */
  color: rgba(241, 234, 208, 0.34);
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: clamp(0.45rem, 1.15vmin, 0.72rem);
  line-height: 1;
  user-select: none;
  pointer-events: none;
}

.vp-stars-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /* EVERY cell carries the same dim star at rest. A sparse subset was
     a real reported problem on touch, where there is no hover to
     discover the rest with — and uniform is better camouflage too,
     since there is then no visible subset to tell a real tap from. */
  color: rgba(241, 234, 208, 0.30);
  font-size: clamp(0.55rem, 1.6vmin, 0.9rem);
  line-height: 1;
  -webkit-tap-highlight-color: transparent;
  animation: vp-star-twinkle 2.6s ease-in-out infinite;
  transition: color 160ms ease, transform 160ms ease;
}

@keyframes vp-star-twinkle {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

.vp-stars-cell:hover { color: rgba(241, 234, 208, 0.62); }

.vp-stars-cell.is-lit {
  color: var(--vp-star-color, #FFCB45);
  font-size: clamp(0.9rem, 2.6vmin, 1.5rem);
  animation: none;
  opacity: 1;
  transform: scale(1.05);
  filter: drop-shadow(0 0 6px var(--vp-star-color, #FFCB45));
}

.vp-stars-cell:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: -2px;
  border-radius: 4px;
}

.vp-stars-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Under the stars and out of the way of every tap. */
  pointer-events: none;
  z-index: 0;
}

.vp-stars-line {
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0.85;
}

/* ---------- what it says ---------- */

/* One line, and it never blames. There is no error colour here on
   purpose: a red line under a child's own constellation would say
   "wrong" more loudly than any wording could take back. */
.vp-stars-status {
  margin: 0;
  min-height: 1.5em;
  font-family: 'Caveat', 'Kalam', cursive;
  font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.3rem);
  line-height: 1.3;
  text-wrap: balance;
  color: rgba(241, 234, 208, 0.72);
}

.vp-stars-status.is-quiet { color: rgba(241, 234, 208, 0.72); }
.vp-stars-status.is-known { color: var(--vp-candle, #E8B871); }

/* ---------- the two buttons ---------- */

.vp-stars-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
}

.vp-stars-actions button {
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: clamp(0.9rem, 0.85rem + 0.25vw, 1rem);
  padding: 0.52rem 1.15rem;
  border-radius: 999px;
  border: 1px solid rgba(241, 234, 208, 0.26);
  background: rgba(12, 17, 32, 0.6);
  color: var(--vp-paper, #F1EAD0);
  cursor: pointer;
  transition: background 200ms ease, border-color 200ms ease;
}

.vp-stars-actions button:hover,
.vp-stars-actions button:focus-visible {
  background: rgba(232, 184, 113, 0.18);
  border-color: rgba(232, 184, 113, 0.6);
}

.vp-stars-actions button.is-primary {
  background: rgba(232, 184, 113, 0.2);
  border-color: rgba(232, 184, 113, 0.55);
  color: var(--vp-candle, #E8B871);
}

.vp-stars-actions button:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: 3px;
}

/* Short screens: the board gives up width before anything else does,
   because the title and the two buttons are the parts that stop the
   screen making sense if they go. */
@media (max-height: 640px) {
  .vp-stars-sky { width: min(15rem, 46vh); }
  .vp-stars-panel { gap: 0.35rem; }
}

@media (prefers-reduced-motion: reduce) {
  .vp-stars-cell { animation: none; opacity: 1; }
  .vp-stars-cell.is-lit { transform: none; }
  .vp-stars-veil { transition: opacity 180ms ease; }
  .vp-stars-panel { transition: opacity 180ms ease; transform: none; }
  .vp-stars.is-open .vp-stars-panel { transform: none; }
}


/* ---------- "I don't have my Magic Card" ----------
 *
 * The way out for a child who cannot answer the question at all. It
 * sits UNDER the two buttons and is quieter than both, because it is
 * not a third choice — the screen still offers exactly two — it is the
 * door for somebody who cannot use either. */

.vp-stars-lost {
  margin-top: 0.15rem;
  padding: 0.3rem 0.6rem;
  border: 0;
  background: none;
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: 0.85rem;
  color: rgba(241, 234, 208, 0.55);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.vp-stars-lost:hover,
.vp-stars-lost:focus-visible { color: var(--vp-candle, #E8B871); }

.vp-stars-lost:focus-visible { outline: 2px solid var(--vp-candle, #E8B871); outline-offset: 3px; }

/* A class that sets `display` outranks the browser's own [hidden]
   rule. This one does not set display — but the guard is here anyway,
   next to its neighbours, because every other surface in this file
   that forgot it cost a bug. */
.vp-stars-lost[hidden] { display: none; }

/* "It didn't arrive." Deliberately the quiet shape of the lost-card
   link and not a button: the two real buttons on this screen are
   Continue and I Don't Have One Yet, and this must never read as a
   third one. It appears only in the moments after a send. */
.vp-stars-again {
  margin: 0.1rem 0 0.15rem;
  padding: 0.3rem 0.6rem;
  border: 0;
  background: none;
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: 0.85rem;
  color: rgba(241, 234, 208, 0.55);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  /* Escaping css/style.css's own `button { width:100%; height:
     var(--button-height) }` — the same escape every overlay in this
     codebase needs. */
  width: auto !important;
  height: auto !important;
}

.vp-stars-again:hover:not(:disabled),
.vp-stars-again:focus-visible { color: var(--vp-candle, #E8B871); }

.vp-stars-again:focus-visible { outline: 2px solid var(--vp-candle, #E8B871); outline-offset: 3px; }

.vp-stars-again:disabled { opacity: 0.5; cursor: default; }

.vp-stars-again[hidden] { display: none; }

.vp-stars-lost-form {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  width: min(22rem, 100%);
  margin-top: 0.2rem;
}

.vp-stars-input {
  width: 100%;
  font-family: 'Kalam', system-ui, sans-serif;
  font-size: 0.98rem;
  text-align: center;
  padding: 0.55rem 1rem;
  border-radius: 999px;
  border: 1px solid rgba(241, 234, 208, 0.3);
  background: rgba(12, 17, 32, 0.6);
  color: var(--vp-paper, #F1EAD0);
}

.vp-stars-input::placeholder { color: rgba(241, 234, 208, 0.42); }

.vp-stars-input:focus-visible {
  outline: 2px solid var(--vp-candle, #E8B871);
  outline-offset: 3px;
  border-color: rgba(232, 184, 113, 0.6);
}
