/* ─── Platytutor app chrome — storybook UI ────────────────────────────────
   Top bar, user menu, section heads, game grid, login overlay and avatar
   picker: everything the signed-in pages share. Warm cream paper,
   hand-drawn vibes, deep navy ink. (The public landing page has its own
   sheet: landing/css/landing.css.)                                      */

* { box-sizing: border-box; 
}

html, body {
  margin: 0;
  padding: 0;
}

/* Base body + grain overlay live in css/components.css. */
body {
  position: relative;
  overflow-x: hidden;
}

.page-layer {
  position: relative;
  z-index: 1;
  min-height: 100vh;
}

/* ─── Top bar ───────────────────────────────────────────────────────── */

.top-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.2rem clamp(1rem, 3vw, 2.2rem);
  /* A phone on its side puts the notch over the edge (viewport-fit=cover);
     env() is 0 everywhere else, so this is the clamp above on a desktop. */
  padding-left: max(clamp(1rem, 3vw, 2.2rem), var(--safe-l, 0px));
  padding-right: max(clamp(1rem, 3vw, 2.2rem), var(--safe-r, 0px));
  max-width: 1280px;
  margin: 0 auto;
  /* Keep the header (and the user dropdown in it) above main content, whose
     hover transforms create competing stacking contexts. */
  position: relative;
  z-index: 100;
}

/* .brand / .brand-mark base are in components.css; the app pages use a
   slightly larger, softer mark. */
.brand { font-size: var(--fs-title); font-variation-settings: "SOFT" 50, "WONK" 1; 
}
.brand-mark { --brand-mark-size: 44px; border-radius: var(--r-md); font-size: var(--fs-title); 
}

.top-bar-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}

/* .chip / .chip-dot base are in components.css. */
.user-chip { padding-left: 0.4rem; 
}
.user-chip.user-chip--avatar-only {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  gap: 0;
  border: none;
  background: transparent;
  box-shadow: none;
  cursor: default;
}
.user-chip.user-chip--avatar-only:hover,
.user-chip.user-chip--avatar-only:active {
  transform: none;
  box-shadow: none;
}
/* ─── Account menu ("olvasójegy") ─────────────────────────────────────
   The chip in the top bar says who is playing (photo and name) and opens
   the account menu (auth.js mountUserMenu): a library card under the chip on
   a desktop, a bottom sheet on a phone. Every colour is a token, so the menu
   takes on the theme it switches. */

/* The chip and its card side by side; the card hangs from this box. */
.user-menu-anchor {
  position: relative;
  display: inline-flex;
  min-width: 0;
  max-width: 100%;
}

/* The chip is a stamp: ink line, card fill, the small offset. */
.user-chip.user-menu-trigger {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  /* 44px to the pixel (36 + 2 × 2 + 2 × 2): the top bar keeps its height. */
  height: 44px;
  padding: 2px 0.7rem 2px 2px;
  border: 2px solid var(--ink);
  border-radius: var(--r-full);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-stamp-sm);
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.user-chip.user-menu-trigger .avatar {
  --avatar-size: 36px;
  order: 0;
  border: 2px solid var(--ink);
  background: var(--paper-deep);
}
.user-chip.user-menu-trigger .avatar--placeholder {
  font-size: 0.95rem;
  color: var(--ink);
}
.user-chip.user-menu-trigger .user-chip-name {
  display: inline;
  order: 1;
  min-width: 0;
  max-width: 12ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 800;
  font-size: 0.95rem;
  color: var(--ink);
}
@media (hover: hover) and (pointer: fine) {
  .user-chip.user-menu-trigger:hover { transform: translate(-1px, -1px); box-shadow: var(--shadow-stamp); }
}
/* Pressed, and pressed while its card is open (the stamp grammar's
   "this one is chosen"). */
.user-chip.user-menu-trigger:active,
.user-chip.user-menu-trigger[aria-expanded="true"] { transform: translate(2px, 2px); box-shadow: var(--shadow-stamp-press);
}
.user-chip.user-menu-trigger:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px;
}
.user-menu-caret {
  display: inline-flex;
  order: 2;
  color: var(--ink);
  transition: transform 0.18s ease;
}
.user-chip.user-menu-trigger[aria-expanded="true"] .user-menu-caret { transform: rotate(180deg);
}

/* The card on a desktop: under the chip, its right edge on the chip's. */
.um-panel {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  z-index: 60;
  box-sizing: border-box;
  width: 360px;
  max-width: calc(100vw - 2rem);
  max-height: calc(100vh - 6rem);
  max-height: calc(100dvh - 6rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1.1rem 1.1rem 1rem;
  background: var(--card);
  color: var(--ink);
  border: 2.5px solid var(--ink);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-stamp);
  text-align: left;
  cursor: default;
  animation: um-drop 0.15s ease-out;
}
.um-panel:focus { outline: none;
}
@keyframes um-drop {
  from { opacity: 0; transform: translateY(-4px); }
}
@media (prefers-reduced-motion: reduce) {
  .um-panel { animation: none; }
}

/* 1. Who is playing: the ID photo (a sticker, tilted, a pencil on it) and
   the name. */
.um-id {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 0 0 1.1rem;
}
.um-photo {
  position: relative;
  flex: none;
  width: 76px;
  height: 76px;
  padding: 0;
  border: 2.5px solid var(--ink);
  border-radius: 50%;
  background: var(--paper-deep);
  box-shadow: var(--shadow-stamp-sm);
  transform: rotate(-3deg);
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.um-photo-frame {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 50%;
}
.um-photo-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.um-photo-initial {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 2rem;
  color: var(--ink);
}
.um-photo-edit {
  position: absolute;
  right: -6px;
  bottom: -4px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  box-sizing: border-box;
  border: 2px solid var(--ink);
  border-radius: 50%;
  background: var(--sun);
  color: var(--on-bright);
}
@media (hover: hover) and (pointer: fine) {
  .um-photo:hover { transform: rotate(-3deg) translate(-1px, -2px); box-shadow: var(--shadow-stamp-md); }
}
.um-photo:active { transform: rotate(-3deg) translate(2px, 2px); box-shadow: none;
}
.um-who {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
}
.um-panel .um-name {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "SOFT" 50, "WONK" 1;
  font-weight: 700;
  font-size: 1.75rem;
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-align: left;
  overflow-wrap: anywhere;
}

/* 2. Where to go: two big stamps; the page this is, is pressed down. */
.um-tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--phone-gap-stamped);
  margin: 0 0 1rem;
}
.um-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 0.4rem;
  box-sizing: border-box;
  min-height: 82px;
  padding: 0.7rem 0.85rem;
  border: 2px solid var(--ink);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-stamp);
  color: var(--on-bright);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 1.05rem;
  line-height: 1.15;
  text-decoration: none;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.um-tile svg { flex: none;
}
.um-tile--games { background: var(--mint);
}
.um-tile--stats { background: var(--sun);
}
@media (hover: hover) and (pointer: fine) {
  .um-tile:not([aria-current="page"]):hover { transform: translate(-2px, -3px); box-shadow: var(--shadow-stamp-hover); }
}
.um-tile:active,
.um-tile[aria-current="page"] { transform: translate(var(--stamp-off), var(--stamp-off)); box-shadow: none;
}
/* "itt vagy" sits on the icon's row, at the tile's right edge (the same
   padding as the label's left), so it never lengthens the label line. A
   tile too narrow for it on one line wraps it right-aligned beside the
   icon instead of letting it run past the border. */
.um-here {
  position: absolute;
  top: 0.7rem;
  right: 0.85rem;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 26px;
  max-width: calc(100% - 2 * 0.85rem - 26px - 0.5rem);
  font-weight: 700;
  font-size: 0.85rem;
  line-height: 1.15;
  text-align: right;
}
.um-tile:focus-visible,
.um-row:focus-visible,
.um-photo:focus-visible,
.um-editor-links a:focus-visible,
.um-dev-chip:focus-visible,
.um-logout:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px;
}

/* 3. Settings: three quiet rows under a dashed rule, plain words beside a
   small icon, a finger high. The tiles above are what the menu is for;
   these are seldom needed. Sötét mód and Diszlexiabarát betű end in a
   switch (theme.js, reading-font.js, components.css .switch-pill),
   Hangpróba in a chevron. */
.um-settings {
  padding-top: 0.35rem;
  border-top: 1.5px dashed var(--line-strong);
}
.um-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  box-sizing: border-box;
  width: 100%;
  min-height: 44px;
  margin: 0;
  padding: 0 0.4rem;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  text-align: left;
}
button.um-row { cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  button.um-row:hover { background-color: var(--ink-wash); }
}
button.um-row:active { background-color: var(--ink-focus);
}
.um-row-text { flex: 1 1 auto; min-width: 0;
}
.um-icon {
  display: inline-flex;
  color: var(--ink-soft);
}
.um-icon svg { width: 18px; height: 18px;
}
.um-chevron {
  display: inline-flex;
  color: var(--ink-soft);
}
/* 4. Editors: their tools in a compact two-column list; on a local dev
   stack the persona switch under it. */
.um-editor { margin-top: 0.9rem;
}
.um-group-label {
  display: block;
  margin: 0 0 0.3rem 0.1rem;
  font-weight: 800;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.um-editor-links {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.1rem 0.5rem;
}
.um-editor-links a {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  min-height: 34px;
  padding: 0 0.35rem;
  border-radius: var(--r-xs);
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.9rem;
  line-height: 1.15;
  text-decoration: none;
}
.um-editor-links svg { flex: none;
}
@media (hover: hover) and (pointer: fine) {
  .um-editor-links a:hover { background: var(--ink-wash); }
}
.um-editor-links a[aria-current="page"] {
  font-weight: 800;
  text-decoration: underline 3px var(--sun);
  text-underline-offset: 4px;
}
.um-dev {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.55rem;
}
.um-dev-chip {
  min-height: 36px;
  padding: 0 0.8rem;
  border: 2px dashed var(--ink);
  border-radius: var(--r-full);
  background: var(--card);
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 0.85rem;
  cursor: pointer;
}
.um-dev-chip[aria-pressed="true"] {
  border-style: solid;
  background: var(--ink);
  color: var(--paper);
  cursor: default;
}

/* 5. Leaving: Profilváltás a ghost stamp, Kijelentkezés quiet words, at the
   right; on a line of its own where the two do not fit side by side (the
   reading font on a phone). */
.um-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 1.1rem;
}
.um-switch-profile { gap: 0.5rem; min-height: 44px; box-sizing: border-box;
}
.um-logout {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  margin-left: auto;
  padding: 0 0.25rem;
  border: 0;
  background: none;
  color: var(--badge-danger-fg);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 0.95rem;
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .um-logout:hover { text-decoration: underline 2px; text-underline-offset: 4px; }
}

/* The dyslexia-friendly font (tokens.css) runs about 1.5× wider: a tile's
   label gives up a little size rather than its last letters, and each
   editor link has a row of its own, so no word is cut. */
:root[data-reading-font="dyslexic"] .um-tile { font-size: 0.95rem;
}
:root[data-reading-font="dyslexic"] .um-editor-links { grid-template-columns: minmax(0, 1fr);
}

/* The old menu list (design-system.html's top-bar example still shows it). */
.user-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  z-index: 60;
  min-width: 170px;
  padding: 0.35rem;
  background: var(--card);
  border: 1.5px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-stamp-sm);
}
.user-menu-item {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  width: 100%;
  padding: 0.55rem 0.75rem;
  border: none;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-body);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .user-menu-item:hover { background: rgba(27, 58, 91, 0.08); }
}
.user-menu-item.is-active { background: var(--ink-focus);
}
a.user-menu-item { text-decoration: none;
}
.user-menu-divider {
  height: 1.5px;
  margin: 0.35rem 0.5rem;
  background: var(--line-strong);
  opacity: 0.35;
  border-radius: 1px;
}

/* ─── Page shell ────────────────────────────────────────────────────── */

.shell {
  max-width: 1280px;
  margin: 0 auto;
  padding: clamp(1.5rem, 4vw, 3rem) clamp(1rem, 3vw, 2.2rem) 5rem;
  padding-left: max(clamp(1rem, 3vw, 2.2rem), var(--safe-l, 0px));
  padding-right: max(clamp(1rem, 3vw, 2.2rem), var(--safe-r, 0px));
}

/* ─── Section heads ─────────────────────────────────────────────────── */

.section-head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.5rem 0 1.2rem;
  flex-wrap: wrap;
}

.section-head h2 {
  font-family: var(--font-display);
  font-variation-settings: "SOFT" 30, "WONK" 1, "opsz" 72;
  font-weight: 600;
  font-size: clamp(1.9rem, 3.2vw, 2.6rem);
  line-height: 1;
  letter-spacing: -0.025em;
  margin: 0;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
}

.section-emoji-chip {
  width: 46px;
  height: 46px;
  border-radius: var(--r-md);
  background: var(--card);
  border: 2px solid var(--ink);
  box-shadow: var(--shadow-stamp-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-title);
  transform: rotate(-5deg);
}

/* The chip's icons carry no width/height of their own. Chromium stretches an
   unsized SVG over the chip's content box; WebKit (every iPhone, Safari)
   collapses it to 0x0 and the chip shows empty. Sized here to what Chromium
   always drew. */
.section-emoji-chip svg { width: 100%; height: 100%; display: block; 
}

.section-emoji-chip[data-tone="mint"] { background: var(--mint); color: var(--on-bright); 
}
.section-emoji-chip[data-tone="coral"] { background: var(--coral); color: var(--on-color); 
}
.section-emoji-chip[data-tone="sun"]   { background: var(--sun); color: var(--on-bright); 
}
.section-emoji-chip[data-tone="sky"]   { background: var(--sky); color: var(--on-color); 
}
.section-emoji-chip[data-tone="berry"] { background: var(--berry); color: var(--on-color); 
}
.section-emoji-chip[data-tone="ink"]   { background: var(--ink); color: var(--paper); 
}

.section-hint {
  font-family: var(--font-hand);
  font-size: var(--fs-title);
  line-height: 1;
  color: var(--ink-soft);
  transform: rotate(-1.5deg);
}

/* ─── Journeys panels ───────────────────────────────────────────────── */
/* Visually its own module — a distinct panel, not just another section in
   the plain task grid below — so picking a journey reads as a different
   kind of choice from picking a single game. Journeys still ahead and ones
   already finished each get their own panel, so the two read as separate
   shelves rather than one long list. */

.journey-panel {
  background: var(--paper-deep);
  border: 2.5px solid var(--ink);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-stamp);
  padding: 1.6rem 1.6rem 0.6rem;
  margin: 1.8rem 0 2.8rem;
}

.journey-panel .section-head {
  margin-top: 0;
}

/* The finished shelf is an archive, not the thing to do next: it sits back
   with a flatter, paler treatment so the open journeys keep the emphasis. */
.journey-panel--done {
  background: var(--paper);
  box-shadow: var(--shadow-stamp-sm);
}

.journey-panel--done .game-card {
  opacity: 0.82;
}

.journey-panel--done .game-card:focus-visible {
  opacity: 1;
}

@media (hover: hover) and (pointer: fine) {
  .journey-panel--done .game-card:hover { opacity: 1; }
}

/* The open shelf is always the same three slots wide, so the cards keep equal
   weight instead of the last one stretching into whatever space is left. The
   compound selector is deliberate: .game-grid's auto-fill columns are declared
   further down this file and would otherwise win on source order alone. */
.game-grid.journey-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-bottom: 1.6rem;
}

/* An editor sees every journey (and with ?tasks=1 every task) at once, so the
   list takes a big screen's width (--page-max) and lays as many journeys to a
   row as fit. A player's shelf keeps its three slots and its measure
   (index.js sets the class). */
body.picker-wide .top-bar,
body.picker-wide .shell {
  max-width: var(--page-max);
}

body.picker-wide .game-grid.journey-grid {
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

/* The hand-written rule between the two shelves. It carries the only sentence
   that explains the seal, so the sealed cards themselves don't have to. */
.journey-tier-note {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin: 0 0 1.3rem;
  font-family: var(--font-hand);
  font-size: var(--fs-title);
  line-height: 1;
  color: var(--ink-soft);
}

.journey-tier-note::before,
.journey-tier-note::after {
  content: "";
  flex: 1;
  border-top: 2px dashed var(--line-strong);
}

.journey-locked-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  margin-bottom: 1.8rem;
}

/* Open journeys are raised off the panel (ink border, offset stamp shadow);
   a sealed one is pressed into it — darker than the panel, no shadow to lift
   it. Availability is the whole message of this shelf, so it is carried by the
   surface itself rather than by a disabled-looking copy of a real card. */
.journey-locked {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.85rem 1.1rem;
  background: var(--paper-warm);
  border: 2px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: inset 0 2px 0 var(--ink-inset);
}

.journey-locked-seal {
  flex: 0 0 auto;
  width: 54px;
  height: 54px;
  object-fit: contain;
  /* The seal is drawn in the house palette; on this shelf it reads as one more
     quiet mark, not a third accent colour. */
  filter: saturate(0.25) opacity(0.85);
}

.journey-locked-body { min-width: 0; 
}

.journey-locked-title {
  font-family: var(--font-display);
  font-variation-settings: "SOFT" 30, "WONK" 1, "opsz" 72;
  font-weight: 600;
  font-size: var(--fs-body-lg);
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0;
  /* Muted by the surface it sits on, not by pale text: --ink-muted on this
     warm plate is 2.7:1, which is not a colour a title may be set in. */
  color: var(--ink);
  /* Generated names can run long; the row keeps one title line so the sealed
     cards in the grid stay the same height. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.journey-locked-meta {
  margin: 0.25rem 0 0;
  font-size: var(--fs-label);
  font-weight: 700;
  color: var(--ink-soft);
}

/* ─── Game grid & cards ─────────────────────────────────────────────── */

.game-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.4rem;
  margin-bottom: 2.5rem;
}

.game-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 2.5px solid var(--ink);
  border-radius: var(--r-lg);
  overflow: hidden;
  text-decoration: none;
  color: var(--ink);
  box-shadow: var(--shadow-stamp);
  transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1),
              box-shadow 0.22s ease,
              rotate 0.22s ease;
  rotate: 0deg;
  animation: card-in 600ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
  /* The stagger is for the first row or two; past that the cards come in
     together. Uncapped, the editor's 100+-task grid kept its last cards
     invisible for the better part of ten seconds. */
  animation-delay: min(calc(var(--card-i, 0) * 70ms + 150ms), 640ms);
}

@keyframes card-in {
  from { opacity: 0; transform: translateY(14px) scale(0.96); 
}
  to   { opacity: 1; transform: translateY(0) scale(1); 
}
}

/* Lift on a mouse only: on a touch screen :hover sticks after the tap and
   leaves the card tilted. */
@media (hover: hover) and (pointer: fine) {
  .game-card:hover {
    transform: translate(-3px, -5px);
    box-shadow: var(--shadow-stamp-lift);
    rotate: -0.8deg;
  }
}

.game-card:active {
  transform: translate(2px, 2px);
  box-shadow: var(--shadow-stamp-xs);
}

/* Every card gets a randomized tone via nth-child — the look breathes */
.game-card[data-tone="mint"]  .card-scene { background: linear-gradient(165deg, #c8ece6, #7ecec4); 
}
.game-card[data-tone="coral"] .card-scene { background: linear-gradient(165deg, #ffd0c5, #ff9f8f); 
}
.game-card[data-tone="sun"]   .card-scene { background: linear-gradient(165deg, #ffe9a5, var(--sun)); 
}
.game-card[data-tone="sky"]   .card-scene { background: linear-gradient(165deg, #b8daec, #6fb3e0); 
}
.game-card[data-tone="berry"] .card-scene { background: linear-gradient(165deg, #f2b4d4, var(--berry)); color: var(--on-color); 
}
.game-card[data-tone="ink"]   .card-scene { background: linear-gradient(165deg, #3d5775, var(--ink)); color: var(--on-color); 
}

.card-status-dot {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 2;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  box-shadow: var(--shadow-stamp-sm);
  pointer-events: none;
}

.card-status-dot[data-status="draft"] { background: var(--sun); 
}
.card-status-dot[data-status="reviewed"] { background: var(--mint); 
}
.card-status-dot[data-status="published"] { background: var(--success); 
}
.card-status-dot[data-status="needs_correction"] { background: var(--coral); 
}
.card-status-dot[data-status="review_rejected"] { background: var(--berry-bright); 
}

/* Stamped badge marking a game type that requires a mobile device
   (e.g. orientation/motion sensors) — opposite corner from .card-status-dot
   so the two never collide. */
.card-mobile-badge {
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 2;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
  border: 2px solid var(--ink);
  box-shadow: var(--shadow-stamp-sm);
  pointer-events: none;
}

.card-mobile-badge svg { width: 21px; height: 21px; 
}

.card-scene {
  position: relative;
  min-height: 170px;
  padding: 1.2rem 1.2rem 1rem;
  border-bottom: 2.5px solid var(--ink);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 0.8rem;
  overflow: hidden;
}

.card-scene::before {
  content: "";
  position: absolute;
  right: -30px;
  top: -30px;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.35);
  pointer-events: none;
}

.card-scene::after {
  content: "";
  position: absolute;
  left: -24px;
  bottom: -24px;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: rgba(27, 58, 91, 0.08);
  pointer-events: none;
}

.card-type-sticker {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--card);
  color: var(--ink);
  font-size: var(--fs-label);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  padding: 0.35rem 0.7rem;
  border-radius: var(--r-full);
  border: 2px solid var(--ink);
  box-shadow: var(--shadow-stamp-sm);
  align-self: flex-start;
  transform: rotate(-3deg);
}

.card-type-sticker svg { width: 14px; height: 14px; 
}

/* The journey the player is most likely to want next — the one already under
   way, or the easiest one open. Exactly one card per shelf wears this.
   Inverted rather than tinted: the marked card can be any of the three open
   tones, and a sun-yellow sticker on the sun-toned card marks nothing. */
.card-type-sticker--marked {
  background: var(--ink);
  color: var(--paper);
  transform: rotate(-3deg) scale(1.04);
}

.card-scene-icon {
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  width: 76px;
  height: 76px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card);
  border: 2.5px solid var(--ink);
  border-radius: 50%;
  box-shadow: var(--shadow-stamp-sm);
  z-index: 1;
  transform: rotate(4deg);
  transition: transform 0.3s ease;
}

@media (hover: hover) and (pointer: fine) {
  .game-card:hover .card-scene-icon {
    transform: rotate(-10deg) scale(1.08);
  }
}

.card-scene-icon svg { width: 44px; height: 44px; color: var(--ink); 
}

.card-body {
  padding: 1.1rem 1.2rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  flex: 1;
}

.card-title {
  font-family: var(--font-display);
  font-variation-settings: "SOFT" 30, "WONK" 1, "opsz" 72;
  font-weight: 600;
  font-size: var(--fs-title);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--ink);
}

.card-sub {
  font-size: var(--fs-body);
  line-height: 1.4;
  color: var(--ink-soft);
  margin: 0;
  flex: 1;
}

.journey-progress {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.journey-progress-track {
  flex: 1;
  height: 8px;
  border-radius: var(--r-full);
  background: var(--paper-deep);
  overflow: hidden;
}

.journey-progress-fill {
  height: 100%;
  border-radius: var(--r-full);
  background: var(--mint-deep);
  transition: width 0.3s ease;
}

.journey-progress-label {
  flex: 0 0 auto;
  font-size: var(--fs-label);
  font-weight: 700;
  color: var(--ink-muted);
  white-space: nowrap;
}

.journey-progress-skipped {
  margin: 0.35rem 0 0;
  font-size: var(--fs-label);
  font-weight: 700;
  color: var(--ink-soft);
}

.card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding-top: 0.75rem;
  border-top: 1.5px dashed var(--line-strong);
}

.card-foot-meta {
  font-size: var(--fs-label);
  font-weight: 700;
  color: var(--ink-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.card-play {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--fs-body);
  color: var(--coral-deep);
  transition: gap 0.2s ease;
}

@media (hover: hover) and (pointer: fine) {
  .game-card:hover .card-play { gap: 0.65rem; color: var(--ink); }
}
.card-play-arrow { width: 18px; height: 18px; 
}

/* Empty / error states use the shared .empty-state--card / .empty-state--muted
   modifiers in components.css. */

/* ─── Loading and load failure (the journey picker) ─────────────────────
   Platy over the map while the journeys load, bobbing as if walking; the
   same Platy, still, when they could not be loaded. */

.journeys-loading,
.journeys-failed {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

.journeys-loading {
  padding: 2.5rem 1rem;
  color: var(--ink);
  font-weight: 700;
}

.journeys-loading-platy {
  width: 160px;
  height: 160px;
  object-fit: contain;
}

.journeys-loading .journeys-loading-platy {
  animation: journeys-platy-walk 1.4s ease-in-out infinite;
}

@keyframes journeys-platy-walk {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50%      { transform: translateY(-8px) rotate(2deg); }
}

.journeys-loading-text {
  margin: 0;
  font-size: var(--fs-body-lg);
}

.journeys-loading-dots i {
  font-style: normal;
  animation: journeys-dot 1.2s ease-in-out infinite;
}

.journeys-loading-dots i:nth-child(2) { animation-delay: 0.2s; }
.journeys-loading-dots i:nth-child(3) { animation-delay: 0.4s; }

@keyframes journeys-dot {
  0%, 100% { opacity: 0.2; }
  50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .journeys-loading .journeys-loading-platy,
  .journeys-loading-dots i { animation: none; }
}

.journeys-failed p {
  margin: 0 0 0.6rem;
}

/* The login overlay itself — backdrop, .login-card, .login-close, .login-mascot,
   headings, error/success, note list/items — is all in components.css. What is
   left here is what only this page shows inside that card. */

/* ─── Avatar picker (inside the login-card modal) ───────────────────── */

/* On a phone the sheet itself takes focus (avatar-picker.js), as the other
   sheets do: no ring painted around the whole card when it opens by itself. */
#avatarOverlay .login-card:focus { outline: none;
}

.avatar-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.7rem;
  margin-bottom: 0.9rem;
  max-height: min(52vh, 390px);
  overflow-y: auto;
  /* Room for the scrollbar, the hover lift and the chosen one's ✓, so the
     right column's circles never get clipped. */
  scrollbar-gutter: stable;
  padding: 0.3rem 0.5rem 0.3rem 0.3rem;
  /* WebKit sizes the auto rows of this scrolling grid before the square
     buttons (aspect-ratio) know their height, so the rows collapsed to ~31px
     and the avatars overlapped like a stack of coins. Rows as tall as their
     content is what Chromium already drew. */
  grid-auto-rows: max-content;
}

.avatar-grid-loading {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--ink-muted);
  font-weight: 700;
  margin: 0.6rem 0;
}

.avatar-option {
  position: relative;
  appearance: none;
  padding: 0;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 2.5px solid var(--line-strong);
  background: var(--paper-deep);
  cursor: pointer;
  transition: transform 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease;
}

/* The picture is cut round by itself, so the ✓ can sit on the rim. */
.avatar-option img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 50%;
}

@media (hover: hover) and (pointer: fine) {
  .avatar-option:hover { transform: translate(-1px, -2px); border-color: var(--ink); box-shadow: var(--shadow-stamp-sm); }
}
/* The picture in the corner now: pressed in, in ink, with the menu photo's
   sun badge holding a ✓. Never coral, which says "wrong". */
.avatar-option.is-selected {
  border: 3px solid var(--ink);
  box-shadow: none;
  transform: none;
}
.avatar-option.is-selected::after {
  content: '✓';
  content: '✓' / '';
  position: absolute;
  right: -4px;
  bottom: -4px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  box-sizing: border-box;
  border: 2px solid var(--ink);
  border-radius: 50%;
  background: var(--sun);
  color: var(--on-bright);
  font: 900 16px/1 var(--font-body);
}
.avatar-option.is-saving { opacity: 0.55; 
}
.avatar-option:disabled { cursor: wait; 
}

/* Wider on a desktop: four to a row, so more of the 32 show at once. */
@media (min-width: 768px) {
  #avatarOverlay .login-card { width: min(92vw, 520px); }
  #avatarOverlay .avatar-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    max-height: min(60vh, 470px);
  }
}
/* A phone's sheet stands as tall as a sheet may, and the grid takes all of
   it under the heading; turned sideways, as many to a row as fit. */
@media (max-width: 767.98px), (max-height: 500px) and (pointer: coarse) {
  #avatarOverlay .login-card {
    width: 100%;
    height: 92dvh;
    display: flex;
    flex-direction: column;
  }
  #avatarOverlay .avatar-grid {
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    flex: 1 1 0;
    min-height: 0;
    max-height: none;
  }
}

/* Consent checkbox (the parent invite form): the form refuses to submit until
   this is ticked. */
.consent-check {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  text-align: left;
  font-size: var(--fs-label);
  font-weight: 600;
  color: var(--ink-soft);
  line-height: 1.45;
  margin: 0 0 1rem;
  cursor: pointer;
}
.consent-check input[type="checkbox"] {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin: 0.1rem 0 0;
  accent-color: var(--coral);
  cursor: pointer;
}
.consent-check a { color: var(--coral-deep); 
}

.btn-google,
.btn-microsoft {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 0.75rem 1rem;
  background: #fff;
  border: 2px solid var(--ink);
  border-radius: var(--r-md);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow-stamp-faint);
  transition: transform 0.12s, box-shadow 0.12s;
}
@media (hover: hover) and (pointer: fine) {
  .btn-google:hover,
  .btn-microsoft:hover {
    transform: translate(-1px, -1px);
    box-shadow: var(--shadow-stamp-faint-hover);
  }
}
.btn-google:active,
.btn-microsoft:active {
  transform: translate(1px, 1px);
  box-shadow: var(--shadow-stamp-faint-press);
}
.btn-microsoft { margin-top: 0.6rem; 
}

/* Editor catalogue (?tasks=1). The table, form grid and clickable row live
   in admin-pages.css; these only cover what that sheet does not. */
.task-catalogue .field-hint { margin-top: 0; }
.task-catalogue .users-table tbody tr.task-row { cursor: pointer; }
.task-catalogue a.journey-title-cell {
  color: var(--ink);
  text-decoration: none;
}
@media (hover: hover) and (pointer: fine) {
  .task-catalogue tr.task-row:hover a.journey-title-cell { text-decoration: underline; }
}
.task-journey-ids {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

/* ─── Responsive (tokens.css: sm 576 / md 768 / lg 992) ──────────────── */

/* Two shelves, one grid: the slots stay aligned as they collapse, so a sealed
   card sits under an open one at every width. */
@media (max-width: 992px) {
  .game-grid.journey-grid,
  .journey-locked-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); 
}
}

@media (max-width: 680px) {
  /* minmax(0, …), not 1fr: 1fr is minmax(auto, 1fr), so a sealed card's
     one-line title widened the track past the screen — sideways scroll on
     iOS, the whole page zoomed out on Android. */
  .game-grid.journey-grid,
  .journey-locked-grid { grid-template-columns: minmax(0, 1fr); 
}
  .journey-tier-note { font-size: var(--fs-body-lg); 
}
}

@media (max-width: 576px) {
  .brand { font-size: var(--fs-title); 
}
  .brand-mark { width: 38px; height: 38px; font-size: var(--fs-body-lg); 
}

  /* 16px on a phone (the spec's button size), not the desktop body size. */
  .btn-stamp { padding: 0.8rem 1.25rem; font-size: 1rem; 
}
  .game-grid { grid-template-columns: minmax(0, 1fr); 
}
}

/* ─── Phone (< 768px) ──────────────────────────────────────────────────
   The child's home on a phone: a 56px bar with the P mark and a chip saying
   who is playing; the journey under way (or the one to start with) leads as
   the one big card, its button on the first screen; every other journey is
   a compact row; the sealed and finished ones follow below. The account menu
   becomes a bottom sheet. Nothing here reaches the desktop shelf (≥768). */

/* The account sheet (auth.js builds it on a phone only): a .login-card on the
   shared scrim, docked as a bottom sheet by components.css (the grabber,
   a pull down, the X), holding the same card contents as the desktop's
   dropdown, without the dropdown's own frame. */
.user-sheet { text-align: left; padding-top: 1.5rem;
}
.user-sheet:focus { outline: none;
}
.user-sheet .user-sheet-close { top: 0.9rem; right: 0.9rem;
}
.user-sheet .um-panel {
  position: static;
  width: auto;
  max-width: none;
  max-height: none;
  overflow: visible;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  animation: none;
}
.user-sheet .um-id { margin-right: 2.8rem;
}
.user-sheet .um-name { font-size: 1.6rem;
}
/* A short phone (a real iPhone SE shows 553px in Safari) or one held
   sideways: the photo and the tiles step back, the text stays. */
@media (max-width: 767.98px) and (max-height: 600px), (max-height: 500px) and (pointer: coarse) {
  .user-sheet .um-photo { width: 62px; height: 62px; }
  .user-sheet .um-id { margin-bottom: 0.9rem; }
  .user-sheet .um-tile { min-height: 64px; }
  .user-sheet .um-tiles { margin-bottom: 0.85rem; }
}
@media (max-height: 500px) and (pointer: coarse) {
  .user-sheet .um-row { min-height: 48px; }
}
/* A finger on any screen: the compact rows get a finger's height. */
@media (pointer: coarse) {
  .um-editor-links a,
  .um-dev-chip { min-height: 44px; }
}

@media (max-width: 767.98px) {
  /* svh, not dvh: iOS Safari's toolbar comes back at the end of a scroll,
     and a page minimum that follows it makes the end jump (lib/README.md,
     "Scrolling without shaking"). */
  .page-layer { min-height: 100vh; min-height: 100svh; 
}

  .top-bar {
    min-height: 56px;
    gap: 0.6rem;
    padding-top: max(0.5rem, var(--safe-t, 0px));
    padding-bottom: 0.5rem;
  }
  .top-bar-actions { min-width: 0; gap: 0.5rem; 
}
  /* A shared phone changes hands: the wordmark gives way to the player's name.
     From the first paint, on every page with a chip to mount, so the bar
     does not swap the one for the other a moment after it shows. */
  .top-bar:has(#userChip) .brand-name { display: none; 
}
  #guardianChip { min-width: 0; max-width: 38vw; 
}
  .top-bar-actions a.chip { min-height: 44px; 
}
  a.brand { min-height: 44px; min-width: 44px; 
}
  #guardianText { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; 
}

  /* The chip: avatar + name, as wide as the bar lets it be. (The menu it
     opens is a sheet on the body, auth.js, so a transform here moves
     nothing but the chip.) */
  .user-chip.user-menu-trigger { max-width: min(62vw, 16rem); padding-right: 0.75rem;
}
  .user-chip.user-menu-trigger .user-chip-name { max-width: none;
}

  /* Journey picker: the panels drop their frame (37px of width back for the
     cards) and keep their heads. The page ends 24px under its last card, so
     a shelf that fits the screen does not scroll by its own bottom margins
     (393×659 scrolled 28px with nothing to show). */
  .shell {
    padding-top: 0.5rem;
    padding-bottom: calc(1.5rem + var(--safe-b, 0px));
  }
  .journey-panel {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
    margin: 0 0 1.75rem;
  }
  .journey-panel:last-child,
  .journey-panel:last-child > :last-child { margin-bottom: 0;
}
  .journey-panel .section-head { margin: 0.25rem 0 0.85rem; gap: 0.3rem; 
}
  .journey-panel .section-head h2 { font-size: 1.4rem; gap: 0.6rem; line-height: 1.1; 
}
  .journey-panel .section-emoji-chip { width: 38px; height: 38px; flex: none; 
}
  /* The big card says what to do; the open shelf's hint would only repeat it. */
  .journey-panel:not(.journey-panel--done) .section-hint { display: none; 
}
  .journey-panel--done .section-hint { font-size: 1.15rem; line-height: 1.15; 
}

  .game-grid.journey-grid,
  .journey-locked-grid,
  .journey-panel .game-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.8rem;
    margin-bottom: 1.1rem;
  }

  /* The big card: the journey under way, or the one to start with. */
  .journey-grid .game-card.is-marked { order: -1; 
}
  .game-card.is-marked .card-scene {
    min-height: 0;
    height: 60px;
    padding: 0 0.9rem;
    justify-content: center;
  }
  .game-card.is-marked .card-scene::before { width: 92px; height: 92px; top: -44px; right: -18px; 
}
  .game-card.is-marked .card-scene::after { display: none; 
}
  .game-card.is-marked .card-type-sticker { font-size: 0.875rem; 
}
  .game-card.is-marked .card-scene-icon {
    top: 50%;
    right: 0.9rem;
    bottom: auto;
    width: 44px;
    height: 44px;
    transform: translateY(-50%) rotate(4deg);
  }
  .game-card.is-marked .card-scene-icon svg { width: 26px; height: 26px; 
}
  .game-card.is-marked .card-body { padding: 0.95rem 1rem 1.1rem; gap: 0.6rem; 
}
  .game-card.is-marked .card-title { font-size: 1.45rem; line-height: 1.1; 
}
  .game-card.is-marked .card-sub {
    flex: none;
    font-size: 1rem;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
  .game-card.is-marked .card-foot { border-top: 0; padding-top: 0.2rem; 
}
  /* Its label becomes the stamp button the whole card already is. The press
     moves to the button; the card stays put. */
  .game-card.is-marked .card-play {
    flex: 1;
    justify-content: center;
    min-height: 52px;
    padding: 0.7rem 1.2rem;
    border: 2px solid var(--ink);
    border-radius: var(--r-full);
    background: var(--coral);
    color: var(--on-color);
    box-shadow: var(--shadow-stamp);
    font-size: 1.1rem;
    transition: transform 0.1s ease, box-shadow 0.1s ease;
  }
  .game-card.is-marked .card-play-arrow { width: 20px; height: 20px; 
}
  .game-card.is-marked:active { transform: none; box-shadow: var(--shadow-stamp); 
}
  .game-card.is-marked:active .card-play { transform: translate(2px, 2px); box-shadow: var(--shadow-stamp-press); 
}

  /* Every other journey: one compact row — tone chip, title, progress,
     chevron. The whole row is the tap target, as the card was. */
  .journey-panel .game-card:not(.is-marked) {
    display: grid;
    grid-template-columns: 52px minmax(0, 1fr) 1.5rem;
    grid-template-rows: auto auto;
    column-gap: 0.85rem;
    row-gap: 0.4rem;
    align-items: center;
    min-height: 76px;
    padding: 0.7rem 0.8rem 0.7rem 0.7rem;
    border-width: 2px;
    box-shadow: var(--shadow-stamp-sm);
  }
  .journey-panel .game-card:not(.is-marked):active { box-shadow: var(--shadow-stamp-press); 
}
  .journey-panel .game-card:not(.is-marked) .card-scene {
    grid-column: 1;
    grid-row: 1 / span 2;
    width: 52px;
    height: 52px;
    min-height: 0;
    padding: 0;
    border: 2px solid var(--ink);
    border-radius: var(--r-md);
    align-items: center;
    justify-content: center;
    transform: rotate(-4deg);
  }
  .journey-panel .game-card:not(.is-marked) .card-scene::before,
  .journey-panel .game-card:not(.is-marked) .card-scene::after,
  .journey-panel .game-card:not(.is-marked) .card-type-sticker { display: none; 
}
  .journey-panel .game-card:not(.is-marked) .card-scene-icon {
    position: static;
    width: 100%;
    height: 100%;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    transform: none;
  }
  .journey-panel .game-card:not(.is-marked) .card-scene-icon svg { width: 28px; height: 28px; 
}
  /* The icon is drawn straight on the tone tile here (no card circle), so
     it takes the ink of a bright fill (the night ink on the night). */
  .journey-panel .game-card:not(.is-marked):not([data-tone="ink"]) .card-scene-icon svg { color: var(--on-bright);
}
  .journey-panel .game-card:not(.is-marked) .card-body { display: contents; 
}
  .journey-panel .game-card:not(.is-marked) .card-title {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    font-size: 1.12rem;
    line-height: 1.15;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
  .journey-panel .game-card:not(.is-marked):not(:has(.journey-progress)) .card-title {
    grid-row: 1 / span 2;
    align-self: center;
  }
  .journey-panel .game-card:not(.is-marked) .card-sub,
  .journey-panel .game-card:not(.is-marked) .journey-progress-skipped { display: none; 
}
  .journey-panel .game-card:not(.is-marked) .journey-progress { grid-column: 2; grid-row: 2; align-self: start; 
}
  .journey-panel .game-card:not(.is-marked) .id-chip-row { grid-column: 2 / -1; 
}
  .journey-panel .game-card:not(.is-marked) .card-foot {
    grid-column: 3;
    grid-row: 1 / span 2;
    justify-content: center;
    padding: 0;
    border: 0;
  }
  /* The label stays for screen readers; the row shows a chevron. */
  .journey-panel .game-card:not(.is-marked) .card-play { gap: 0; color: var(--ink); 
}
  .journey-panel .game-card:not(.is-marked) .card-play-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .journey-panel .game-card:not(.is-marked) .card-play-arrow { width: 22px; height: 22px; 
}

  .journey-progress-label,
  .journey-progress-skipped { font-size: 0.875rem; 
}

  /* Sealed journeys: 64px rows, the title may take two lines. */
  .journey-tier-note { font-size: 1.15rem; gap: 0.6rem; margin: 0.4rem 0 0.8rem; 
}
  .journey-tier-note span { text-align: center; 
}
  .journey-locked {
    min-height: 64px;
    padding: 0.55rem 0.85rem;
    gap: 0.75rem;
    border-radius: var(--r-md);
  }
  .journey-locked-seal { width: 40px; height: 40px; 
}
  .journey-locked-title {
    font-size: 1.02rem;
    white-space: normal;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
  }
  .journey-locked-meta { font-size: 0.875rem; 
}

  /* Consent tick (child / partner forms): a 24px box in a 48px row. */
  .consent-check { gap: 0.75rem; padding: 0.35rem 0; min-height: 48px; font-size: 0.9rem; 
}
  .consent-check input[type="checkbox"] { width: 24px; height: 24px; margin: 0;
}

  /* A centred sheet title level with the × (top right) stays clear of it
     on a 360px phone: both sides keep the ×'s room, so the title stays
     centred, and one that no longer fits takes two even lines. */
  #avatarOverlay .login-card > h2,
  .profile-modal-card > h2 { padding-inline: 2.6rem; text-wrap: balance;
}
}

/* ─── The home arriving on a phone ──────────────────────────────────────
   Nothing pops in (the phone spec: smooth, never abrupt). The chips fade in
   as they mount; Platy's loading card, the shelf's heads and an empty or
   failed shelf rise in; the cards follow sooner and quicker than on the
   desktop's big grid: the big card at once, the rest 50ms apart. Opacity
   and transform only, once each; the chips' own press stays theirs. */
@media (max-width: 767.98px) {
  .user-chip.user-menu-trigger,
  #guardianChip { animation: home-fade-in 0.2s ease backwards; 
}
  .journeys-loading,
  .journey-panel .section-head,
  :is(#journeysList, #gamesList) > .empty-state--card { animation: home-rise-in 0.22s cubic-bezier(0.2, 0.8, 0.2, 1) backwards; 
}
  .journey-panel .game-card {
    animation-duration: 0.32s;
    animation-delay: calc(min(var(--card-i, 0), 4) * 50ms + 40ms);
  }
  .journey-panel .game-card.is-marked { animation-delay: 0s; 
}
}

@keyframes home-fade-in {
  from { opacity: 0; 
}
}

@keyframes home-rise-in {
  from { opacity: 0; transform: translateY(8px); 
}
}

@media (prefers-reduced-motion: reduce) {
  .user-chip.user-menu-trigger,
  #guardianChip,
  .journeys-loading,
  .journey-panel .section-head,
  :is(#journeysList, #gamesList) > .empty-state--card { animation: none; 
}
}

/* ─── First-visit tour on a phone (or one held sideways) ──────────────
   The card is a bottom sheet (components.css). Its foot — the button and the
   skip link — stays pinned to the sheet's bottom edge, so "Tovább" waits in
   the same spot on every card; tour.js holds the deck at one height, so the
   sheet's top edge stays put too. The foot takes over the card's bottom
   padding, or the pinned row would stop that far above the edge. Its top
   rule while the card runs on under it is components.css's (the sheets'
   foot edge). The 44px step rings and skip link are components.css, for
   every touch screen. */
@media (max-width: 767.98px), (max-height: 500px) and (pointer: coarse) {
  #tourOverlay > .tour { padding-bottom: 0; 
}
  .tour-foot {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin: 0.4rem calc(-1 * max(1.25rem, var(--safe-r, 0px))) 0 calc(-1 * max(1.25rem, var(--safe-l, 0px)));
    padding: 0.75rem max(1.25rem, var(--safe-r, 0px)) calc(0.5rem + var(--safe-b, 0px)) max(1.25rem, var(--safe-l, 0px));
    background: var(--card);
  }
  .tour-foot .tour-actions { margin-bottom: 0.15rem; 
}
  .tour-foot .tour-actions .btn-stamp { flex: 1; min-height: 52px; 
}
  /* The last card has no skip link; its slot stays, so the button does not move. */
  .tour-foot .tour-skip.is-spent { visibility: hidden; 
}

  /* The phone game bar on the "Te döntöd el" card (tour.js skipPhone): the
     two stamps as the bar draws them, each with its name under it, then
     the ⋯ sheet's Ugrás row they lead to. */
  .tour-stamp {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    font-size: var(--fs-phone-label-min);
    font-weight: 800;
    line-height: 1.1;
    color: var(--ink-soft);
  }
  .tour-stamp .tour-pill--icon {
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    font-style: normal;
  }
  .tour-stamp-arrow {
    display: inline-flex;
    align-self: center;
    margin-top: -1.1rem;   /* level with the stamps, not with their names */
    color: var(--ink-muted);
  }
  .tour-stamp-arrow svg { width: 20px; height: 20px; 
}
  .tour-pill--row {
    align-self: center;
    margin-top: -1.1rem;
    min-width: 7.5rem;
    border-radius: var(--r-md);
  }
}

/* A short phone: a real iPhone SE shows 553px in Safari, an iPhone 15 about
   659. Platy and the head step back, the exhibit tightens and the foot is
   one row — the skip link beside the button — so the cards keep the screen
   for their text. Text sizes stay. */
@media (max-width: 767.98px) and (max-height: 700px), (max-height: 500px) and (pointer: coarse) {
  #tourOverlay .tour-mascot { width: 52px; height: 52px; margin: -0.35rem auto 0.3rem; 
}
  #tourOverlay .tour-steps { margin-bottom: 0.35rem; 
}
  #tourOverlay .tour-card h2 { font-size: 1.45rem; margin-bottom: 0.5rem; 
}
  #tourOverlay .tour-exhibit { margin: 0.15rem 0 0.75rem; padding: 0.6rem; gap: 0.55rem 0.75rem; 
}
  .tour-foot {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding-top: 0.6rem;
    padding-bottom: calc(0.6rem + var(--safe-b, 0px));
  }
  .tour-foot .tour-actions { order: 2; flex: 1; min-width: 0; margin: 0; 
}
  .tour-foot .tour-skip { order: 1; flex: none; padding-inline: 0.5rem; 
}
}

/* Held sideways (about 340px tall): Platy leaves the sheet altogether. */
@media (max-height: 500px) and (pointer: coarse) {
  #tourOverlay .tour-mascot { display: none; 
}
  #tourOverlay > .tour { padding-top: 0.5rem; 
}
}

/* A sealed journey answers a finger (a child will tap it): it presses in
   at once, and the tap wobbles its seal and lights up the note above that
   says how it opens (index.js). A touch screen only; a mouse sees nothing new. */
@media (pointer: coarse) {
  .journey-locked { -webkit-tap-highlight-color: transparent; 
}
  .journey-locked:active {
    border-color: var(--ink-muted);
    box-shadow: inset 0 3px 0 var(--ink-focus);
  }
  .journey-locked.is-nudged .journey-locked-seal { animation: locked-seal-wobble 0.5s ease-in-out; 
}
  .journey-tier-note span { border-radius: var(--r-sm); transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; 
}
  .journey-tier-note.is-flagged span {
    color: var(--ink);
    background: var(--sun-light);
    box-shadow: 0 0 0 0.3rem var(--sun-light);
  }
}

@keyframes locked-seal-wobble {
  0%, 100% { rotate: 0deg; }
  20% { rotate: -12deg; }
  45% { rotate: 9deg; }
  70% { rotate: -5deg; }
}

@media (prefers-reduced-motion: reduce) {
  .journey-locked.is-nudged .journey-locked-seal { animation: none; 
}
  .journey-tier-note span { transition: none; 
}
}

/* ─── Audio check on a short phone (or one held sideways) ─────────────
   Each step fits a real iPhone SE in Safari (553px) with the reason and
   the buttons on one screen: Platy and the lamp step back, and once a step
   has failed (audio-check.js .is-failed: the speaker not heard or not
   loaded, the microphone refused or missing) the lead — the intro, or what
   to say and the privacy sentence — gives its place to the reason and how
   to fix it; the next step, or "Újra", brings the lead back. */
@media (max-width: 767.98px) and (max-height: 700px), (max-height: 500px) and (pointer: coarse) {
  #audioCheckOverlay .audio-check-mascot { width: 52px; height: 52px; margin-bottom: 0.4rem; 
}
  #audioCheckOverlay .audio-check-steps { margin-bottom: 0.8rem; 
}
  #audioCheckOverlay .audio-check-lamp { width: 104px; height: 104px; margin-bottom: 0.6rem; 
}
  #audioCheckOverlay .audio-check-lamp-icon svg { width: 40px; height: 40px; 
}
  #audioCheckOverlay .audio-check.is-failed .audio-check-lead { display: none;
}
  /* "Hallottam" and "Tovább a mikrofonhoz" side by side on a 375px phone. */
  #audioCheckOverlay .audio-check-actions .btn-stamp { padding-inline: 1rem;
}
}

/* Held sideways: no Platy, and the lamp beside the text instead of under
   it, so the pinned button row never lies across it. (The summary has no
   lamp and stays one column.) */
@media (max-height: 500px) and (pointer: coarse) {
  #audioCheckOverlay .audio-check-mascot { display: none; 
}
  #audioCheckOverlay > .audio-check:has(> .audio-check-lamp:not([hidden])) {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 1.25rem;
    align-content: start;
  }
  #audioCheckOverlay > .audio-check:has(> .audio-check-lamp:not([hidden])) > * { grid-column: 2; 
}
  #audioCheckOverlay > .audio-check:has(> .audio-check-lamp:not([hidden])) > :is(.audio-check-actions, .audio-check-hint, .audio-check-skip) { grid-column: 1 / -1; 
}
  #audioCheckOverlay > .audio-check:has(> .audio-check-lamp:not([hidden])) > .audio-check-lamp {
    grid-column: 1;
    grid-row: 1 / span 4;
    align-self: center;
    width: 96px;
    height: 96px;
    margin: 0;
  }
}

/* Narrow phones and phones on their side: two compact rows to a line, the big
   card across both. */
@media (min-width: 577px) and (max-width: 767.98px) {
  .game-grid.journey-grid,
  .journey-locked-grid,
  .journey-panel .game-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); 
}
  .journey-grid .game-card.is-marked { grid-column: 1 / -1; 
}
}

/* Phone on its side: a 44px bar, and the big card loses its tone strip so
   its button is still on the first screen. */
@media (max-height: 500px) and (pointer: coarse) {
  .top-bar { min-height: 44px; padding-top: max(0.25rem, var(--safe-t, 0px)); padding-bottom: 0.25rem; 
}
  .game-card.is-marked .card-scene { display: none; 
}
}

/* Tablets keep the desktop layout, with a finger's 44px on the chip and the
   dropdown's rows. */
@media (pointer: coarse) and (min-width: 768px) {
  .user-chip.user-menu-trigger { min-height: 44px; 
}
  .user-menu-item { min-height: 44px; 
}
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .game-card { animation: none !important; opacity: 1 !important; transform: none !important; 
}
}

/* ─── Themes ─────────────────────────────────────────────────────────────
   The palettes are tokens (tokens.css "Themes"); these are the places on
   the signed-in pages where a colour does not come from one. Every rule
   matches only under <html data-theme>, so Világos is
   untouched. */

/* Sötét. The progress groove is a line on the slate card, not a hole. */
:root[data-theme="dark"] .journey-progress-track { background: var(--line);
}
/* The ink-toned card: its scene is a deeper slate, not a chalk smear. */
:root[data-theme="dark"] .game-card[data-tone="ink"] .card-scene {
  background: linear-gradient(165deg, var(--slate-edge), var(--slate));
  color: var(--ink);
}
/* The sign-in buttons: white provider buttons would glare on the night;
   they become slate stamps with a quiet chalk offset. */
:root[data-theme="dark"] :is(.btn-google, .btn-microsoft) {
  background: var(--card);
  box-shadow: var(--shadow-stamp-line);
}
@media (hover: hover) and (pointer: fine) {
  :root[data-theme="dark"] :is(.btn-google, .btn-microsoft):hover { box-shadow: var(--shadow-stamp); }
}
:root[data-theme="dark"] :is(.btn-google, .btn-microsoft):active { box-shadow: var(--shadow-stamp-line-press);
}
