/* ============================================================================
   dreamy.css — COMPONENTS (layer 2 of 3)
   ----------------------------------------------------------------------------
   Dreamy Stories' shared component library. "A book, not an app." Warm cream
   parchment, ink-blue text, sunset coral as the ONE loud action per screen,
   dusty plum + gold for magic, sage reserved for the teaching layer.

   THREE-LAYER DESIGN SYSTEM (each a separate, swappable unit):
     1. THEME      → tokens.css       — all colour/type/space/shadow variables
     2. COMPONENTS → THIS FILE        — the reusable .ds-* classes
     3. PAGE/AREA  → page {% block head %} (or a feature CSS file)

   This file owns NO design values of its own — every colour, shadow, radius
   and font here is var(--token), defined in tokens.css. That is the rule:
   never hard-code a colour in a component or a page; add/adjust the token
   instead, so one tokens.css edit reskins the whole app.

   Loaded by templates/base.html AFTER tokens.css. New screens — in any future
   chat — should:
     1. {% extends "base.html" %}
     2. use these classes / tokens
     3. add ZERO hard-coded colours (always var(--token))
   See /styleguide for a live gallery of every component.
   ============================================================================ */

/* ----------------------------------------------------------------------------
   RESPONSIVE BREAKPOINTS  (canonical — decision 126, 2026-07-17)
   ----------------------------------------------------------------------------
   The whole app uses ONE mobile breakpoint. Do not invent per-page widths —
   copy these two selectors verbatim so the phone/desktop switch happens at the
   same place everywhere. (No rules live here yet; layout changes go in each
   page's {% block head %} or a feature CSS file, keyed on these queries.)

     PHONE LAYOUT ......  @media (max-width: 700px)
       Tall single-column arrangement. 700 sits UNDER the smallest tablet
       PORTRAIT width (iPad = 768) so tablets keep the roomy layout, and well
       above every phone-portrait width (~430 max) so all phones qualify. The
       ~68px of headroom under 768 keeps borderline devices (foldables, small
       tablets) on the correct side. 7" tablets (~600) do fall here — fine,
       they're genuinely small. The number tracks the TABLET floor, not a phone.

     SHORT VIEWPORT ...  @media (max-height: 480px)
       Orthogonal, height-based — mostly a phone in LANDSCAPE (short + wide).
       HEIGHT, not width, is what separates a phone in landscape (~390 tall)
       from a desktop in landscape (~900 tall); `orientation:landscape` alone
       can't, because a desktop is landscape too. Use it to collapse vertical
       chrome when vertical room is scarce; place it AFTER the width rules so it
       can override for short viewports.
   ---------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------------
   2. BASE ELEMENTS  (scoped under .dreamy so it never hijacks admin/3rd-party)
   ---------------------------------------------------------------------------- */
.dreamy * { box-sizing:border-box; }
.dreamy {
  background:var(--bg); color:var(--fg1);
  font-family:var(--font-ui); font-size:var(--text-body); line-height:1.6;
  -webkit-font-smoothing:antialiased;
  transition:background .4s ease, color .4s ease;
  min-height:100vh; margin:0;
}
/* subtle paper grain + ambient coral glow at the top */
.dreamy::before {
  content:''; position:fixed; inset:0; pointer-events:none; z-index:0; opacity:.5;
  background:
    radial-gradient(60% 50% at 50% -8%, var(--accent-soft), transparent 60%),
    radial-gradient(1px 1px at 14% 22%, var(--rule), transparent 50%),
    radial-gradient(1px 1px at 67% 41%, var(--rule-soft), transparent 50%),
    radial-gradient(1px 1px at 33% 78%, var(--rule), transparent 50%),
    radial-gradient(1px 1px at 88% 88%, var(--rule-soft), transparent 50%);
}
.dreamy h1,.dreamy h2,.dreamy h3,.dreamy h4 {
  font-family:var(--font-display); color:var(--fg1); font-weight:500;
  letter-spacing:-.015em; line-height:1.12; margin:0 0 .4em;
}
.dreamy h1 { font-size:var(--text-h1); }
.dreamy h2 { font-size:var(--text-h2); }
.dreamy h3 { font-size:var(--text-h3); font-weight:600; }
.dreamy h1 em,.dreamy h2 em,.dreamy h3 em { font-style:italic; font-weight:400; color:var(--accent-ink); }
.dreamy p { margin:0 0 1em; color:var(--fg2); }
.dreamy a { color:var(--accent-ink); text-decoration:none; }
.dreamy a:hover { color:var(--accent-deep); }

.dreamy .eyebrow { font-family:var(--font-ui); font-size:var(--text-xs); font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--fg3); }
.dreamy .lede { font-family:var(--font-prose); font-style:italic; color:var(--fg3); font-size:1.08rem; }

/* ----------------------------------------------------------------------------
   3. LAYOUT
   ---------------------------------------------------------------------------- */
.ds-app { position:relative; z-index:1; min-height:100vh; display:flex; flex-direction:column; }
.ds-page { flex:1; width:100%; max-width:var(--maxw-app); margin:0 auto; padding:40px 28px 72px; }
.ds-page--read { max-width:var(--maxw-read); }
.ds-section-rule {
  max-width:var(--maxw-app); margin:44px auto 18px; padding:0 28px;
  display:flex; align-items:center; gap:14px;
  font-size:var(--text-xs); font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--fg3);
}
.ds-section-rule::after { content:''; flex:1; height:1px; background:var(--rule); }

/* ----------------------------------------------------------------------------
   4. TOP NAV (the shell chrome)
   ---------------------------------------------------------------------------- */
.ds-nav {
  position:sticky; top:0; z-index:50;
  display:flex; align-items:center; justify-content:space-between; gap:20px;
  padding:14px 28px;
  background:color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  border-bottom:1px solid var(--rule);
}
.ds-brand { display:flex; align-items:center; gap:11px; font-family:var(--font-display); font-style:italic; font-weight:500; font-size:1.45rem; color:var(--fg1); text-decoration:none; }
.ds-brand:hover { color:var(--fg1); }
.ds-brand .mark { width:34px; height:34px; border-radius:50%; object-fit:cover; box-shadow:0 0 0 4px var(--accent-soft); background:radial-gradient(circle at 32% 30%, var(--magic), var(--accent) 82%); }
/* contextual create<->library jump — a phone-only affordance (the full tab row
   takes its place on desktop); the phone block below unhides it */
.ds-navlinks { display:flex; gap:6px; }
.ds-navlinks a {
  display:flex; align-items:center; gap:8px;
  padding:8px 14px; border-radius:var(--radius-chip);
  color:var(--fg2); text-decoration:none; font-weight:600; font-size:.92rem;
  transition:background .15s, color .15s;
}
.ds-navlinks a svg { width:18px; height:18px; }
.ds-navlinks a.active { color:var(--accent-ink); background:var(--accent-soft); }
/* hover:hover excludes touchscreens — without it, tapping a link fires :hover
   and it stays "stuck" orange (looking selected) until the next tap elsewhere. */
@media (hover:hover) {
  .ds-navlinks a:hover { color:var(--accent-ink); background:var(--accent-soft); }
}
.ds-navright { display:flex; align-items:center; gap:12px; }
/* phone-only overflow menu (holds Blog on small screens); hidden on desktop */
.ds-nav-more { display:none; }

/* The personal-workspace pair (My stories + My family). A shared tinted capsule
   binds them so they read as two faces of ONE place; the active face lifts onto
   a raised surface chip (a segmented control that previews the inner sub-tabs).
   See _myspace_tabs.html / .ds-subtabs — same metaphor, one size up, inside. */
.ds-navgroup { display:flex; align-items:center; gap:3px; padding:3px; border-radius:var(--radius-chip); background:var(--magic-soft); }
.ds-navgroup a { padding:6px 12px; }
.ds-navgroup a:hover { background:transparent; }
.ds-navgroup a.active { color:var(--accent-ink); background:var(--surface); box-shadow:var(--shadow-sm); }

/* Workspace sub-tabs — the same segmented capsule, sized up, at the top of each
   inner page (dashboard / children) so the pair always travels together and the
   page you came in on is pre-selected. */
.ds-subtabs { display:inline-flex; gap:3px; padding:4px; border-radius:var(--radius-chip); background:var(--magic-soft); margin-bottom:26px; }
/* .ds-subtab alone (one class) ties on specificity with the global ".dreamy a"
   link-color reset and loses on source order — scope resting color to
   ".ds-subtabs .ds-subtab" (two classes) so it reliably wins instead. */
.ds-subtab { display:inline-flex; align-items:center; gap:8px; padding:9px 18px; border-radius:calc(var(--radius-chip) - 3px); text-decoration:none; font-weight:700; font-size:.92rem; white-space:nowrap; transition:background .15s, color .15s; }
.ds-subtabs .ds-subtab { color:var(--fg2); }
.ds-subtab svg { width:17px; height:17px; }
.ds-subtabs .ds-subtab.active { color:var(--accent-ink); background:var(--surface); box-shadow:var(--shadow-sm); }
@media (hover:hover) {
  .ds-subtabs .ds-subtab:hover { color:var(--accent-ink); }
}

/* ----------------------------------------------------------------------------
   5. BUTTONS  — ONE coral primary per screen
   ---------------------------------------------------------------------------- */
.ds-btn {
  font-family:var(--font-ui); font-weight:700; font-size:.95rem;
  border:none; border-radius:var(--radius-input); padding:11px 20px; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  transition:transform .1s, background .15s, color .15s, border-color .15s;
  text-decoration:none;
}
.ds-btn:active { transform:translateY(1px); }
.ds-btn svg { width:18px; height:18px; }
/* Colour selectors are doubled-up (.ds-btn.ds-btn--x) so they outrank the
   base `.dreamy a` rule when a button is an <a> — otherwise an anchor-button
   renders as coral-text-on-coral (invisible). */
/* primary fills with --accent-deep, not --accent — accent-deep is the tone
   tuned to clear 4.5:1 with white button text in all 3 modes (2026-07-05);
   plain --accent stays the vivid brand tone for icons/borders/decoration. */
.ds-btn.ds-btn--primary { background:var(--accent-deep); color:#fff; box-shadow:var(--shadow-warm); }
.ds-btn.ds-btn--primary:hover { filter:brightness(.92); color:#fff; }
.ds-btn.ds-btn--soft { background:var(--surface); color:var(--fg1); border:1px solid var(--rule); box-shadow:var(--shadow-sm); }
.ds-btn.ds-btn--soft:hover { color:var(--accent-ink); border-color:var(--accent-soft); }
.ds-btn.ds-btn--ghost { background:transparent; color:var(--fg2); }
.ds-btn.ds-btn--ghost:hover { color:var(--accent-ink); }
/* danger = quiet outline for destructive admin actions (remove-from-library,
   clear-images); reads as a warning, never competes with the coral CTA. */
.ds-btn.ds-btn--danger { background:var(--surface); color:var(--danger); border:1px solid var(--danger); box-shadow:var(--shadow-sm); }
.ds-btn.ds-btn--danger:hover { background:var(--danger); color:#fff; }
/* glow = the gentle CTA: a soft coral wash (same family as .ds-card--accent),
   for prominent actions where solid coral feels too intense. Still coral, so
   "one loud action per screen" holds. */
.ds-btn.ds-btn--glow { background:var(--accent-grad); color:#fff; box-shadow:var(--shadow-warm); }
.ds-btn.ds-btn--glow:hover { filter:brightness(1.05) saturate(1.05); color:#fff; }
/* disabled = "this is a button, but not right now" (e.g. a plan cap reached) —
   flat, never the coral CTA look, so it reads as unavailable rather than
   broken. Left clickable on purpose (not pointer-events:none): pairs with
   .ds-tooltip-wrap below so tapping/hovering it explains WHY. */
.ds-btn.ds-btn--disabled { background:var(--surface-2); color:var(--fg3); box-shadow:none; cursor:not-allowed; }

/* Explain-on-hover/tap for a disabled control (see .ds-btn--disabled). Click
   toggles (works on touch, where :hover gets "stuck" — see the nav hover fix);
   real pointers additionally get a live hover preview via (hover:hover). */
.ds-tooltip-wrap { position:relative; display:inline-block; }
/* Anchored from the RIGHT edge (not centred): the one control this ships on
   today sits at the right end of its row, where a centred bubble would run
   off the viewport on a phone. Grows leftward instead — safe near an edge
   regardless of which side. */
.ds-tooltip {
  position:absolute; bottom:calc(100% + 10px); right:0;
  transform:translateY(4px);
  background:var(--fg1); color:var(--surface); font-size:.8rem; font-weight:600; line-height:1.35;
  padding:8px 12px; border-radius:10px; box-shadow:var(--shadow-md);
  width:max-content; max-width:min(230px, calc(100vw - 24px)); text-align:center;
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .15s, transform .15s; z-index:60;
}
.ds-tooltip::after {
  content:""; position:absolute; top:100%; right:14px;
  border:6px solid transparent; border-top-color:var(--fg1);
}
.ds-tooltip-wrap.is-open .ds-tooltip { opacity:1; visibility:visible; transform:translateY(0); }
@media (hover:hover) {
  .ds-tooltip-wrap:hover .ds-tooltip { opacity:1; visibility:visible; transform:translateY(0); }
}
.ds-btn--sm { font-size:.82rem; padding:8px 14px; gap:6px; }
.ds-btn--sm svg { width:15px; height:15px; }
.ds-btn .cost { font-size:.76rem; padding:2px 8px; border-radius:7px; background:rgba(255,255,255,.22); font-weight:700; }

/* small round icon button */
.ds-iconbtn {
  width:38px; height:38px; border-radius:50%; border:1px solid var(--rule);
  background:var(--surface); color:var(--fg2);
  display:grid; place-items:center; cursor:pointer; transition:.15s; padding:0;
}
.ds-iconbtn svg { width:18px; height:18px; }
.ds-iconbtn:hover { color:var(--accent-ink); border-color:var(--accent-soft); transform:translateY(-1px); }
/* smaller, for sitting inline in a compact action row (e.g. a card's Delete) */
.ds-iconbtn--sm { width:32px; height:32px; }
.ds-iconbtn--sm svg { width:15px; height:15px; }

/* ----------------------------------------------------------------------------
   6. PILLS / CHIPS / TAGS / BADGES
   ---------------------------------------------------------------------------- */
.ds-pill { display:inline-flex; align-items:center; gap:7px; padding:7px 13px; border-radius:var(--radius-chip); font-size:.84rem; font-weight:700; background:var(--magic-soft); color:var(--fg1); }
.ds-pill .spark { color:var(--accent); }
.ds-avatar { width:38px; height:38px; border-radius:50%; cursor:pointer; background:linear-gradient(135deg, var(--secondary-soft), var(--accent-soft)); display:grid; place-items:center; font-weight:800; color:var(--fg1); font-size:.85rem; text-decoration:none; }

/* Account dropdown — native <details>/<summary>, no JS framework. The summary
   IS the avatar; the panel hangs below-right. Click-away close is handled by a
   tiny base.html script (details don't auto-close on outside click). */
.ds-usermenu { position:relative; }
.ds-usermenu > summary { list-style:none; }              /* hide default marker */
.ds-usermenu > summary::-webkit-details-marker { display:none; }
.ds-usermenu[open] > summary { box-shadow:0 0 0 3px var(--accent-soft); }
.ds-usermenu__panel {
  position:absolute; right:0; top:46px; z-index:60; width:250px;
  background:var(--surface); border:1px solid var(--rule); border-radius:16px;
  box-shadow:var(--shadow-warm); padding:8px; display:flex; flex-direction:column; gap:2px;
}
.ds-usermenu__head { padding:10px 10px 8px; border-bottom:1px solid var(--rule); margin-bottom:4px; }
.ds-usermenu__email { font-size:.82rem; color:var(--fg2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ds-usermenu__tier { display:inline-flex; align-items:center; gap:6px; margin-top:6px; padding:3px 10px; border-radius:var(--radius-chip); background:var(--accent-soft); color:var(--accent-deep); font-weight:800; font-size:.78rem; }
.ds-usermenu__quota { padding:2px 10px 8px; font-size:.78rem; color:var(--fg3); }
.ds-usermenu__panel a, .ds-usermenu__panel button {
  display:flex; align-items:center; gap:10px; padding:9px 10px; border-radius:10px;
  font-size:.9rem; color:var(--fg1); text-decoration:none; background:none; border:none;
  width:100%; text-align:left; cursor:pointer; font:inherit;
}
.ds-usermenu__panel a:hover, .ds-usermenu__panel button:hover { background:var(--surface-2); color:var(--accent-ink); }
.ds-usermenu__panel a i, .ds-usermenu__panel button i { width:16px; height:16px; }
.ds-usermenu__upgrade { color:var(--accent-deep) !important; font-weight:700; }
/* The menu item for the page you're already on: gently highlighted and inert, so
   it reads as "you are here" and can't pointlessly reload the same page. */
.ds-usermenu__panel a.ds-usermenu__current {
  background:var(--accent-soft); color:var(--accent-deep);
  font-weight:700; pointer-events:none; cursor:default;
}
/* items that only exist on phones (e.g. the Admin door once the nav button
   hides) — the 700px block flips these on */
.ds-usermenu__panel .ds-phone-only { display:none; }
.ds-usermenu__sep { height:1px; background:var(--rule); margin:4px 0; }
/* Sign out is gently highlighted (tinted ground + weight) so leaving is always
   easy to find — never buried. It hovers to the accent like every other item. */
.ds-usermenu__signout { background:var(--surface-2) !important; font-weight:700; color:var(--fg1); }
.ds-usermenu__signout:hover { background:var(--accent-soft) !important; color:var(--accent-ink) !important; }

/* Quota meter — used on the Plan & usage page, reusable elsewhere. */
.ds-meter { height:8px; border-radius:6px; background:var(--surface-2); overflow:hidden; border:1px solid var(--rule); }
.ds-meter > span { display:block; height:100%; background:var(--accent-grad, var(--accent)); }
.ds-meter--full > span { background:#d9534f; }

.ds-chip {
  padding:9px 15px; border-radius:var(--radius-chip); background:var(--surface-2);
  border:1.5px solid var(--rule); font-size:.88rem; font-weight:600; color:var(--fg2);
  display:inline-flex; gap:7px; align-items:center; cursor:pointer; transition:.15s; user-select:none;
}
.ds-chip:hover { border-color:var(--accent-soft); }
.ds-chip.is-selected { background:var(--fg1); color:var(--bg); border-color:var(--fg1); }

.ds-tag { font-size:.68rem; font-weight:700; letter-spacing:.05em; text-transform:uppercase; padding:3px 9px; border-radius:7px; }
.ds-tag--teach { background:var(--teach-soft); color:var(--teach-ink); }
.ds-tag--audio { background:var(--magic-soft); color:var(--fg1); }

/* ----------------------------------------------------------------------------
   7. CARDS
   ---------------------------------------------------------------------------- */
.ds-card { background:var(--surface); border-radius:var(--radius-card); padding:24px 26px; box-shadow:var(--shadow-sm); transition:transform .15s, box-shadow .15s; }
.ds-card--link { cursor:pointer; }
.ds-card--link:hover { transform:translateY(-3px); box-shadow:var(--shadow-md); }
.ds-card h3 { font-size:1.25rem; margin:0 0 6px; font-weight:600; }
.ds-card .sub { font-size:.88rem; color:var(--fg2); }
.ds-card--accent { background:linear-gradient(135deg, var(--accent-soft) 0%, var(--accent-deep) 115%); color:#fff; position:relative; overflow:hidden; box-shadow:var(--shadow-warm); }
.ds-card--accent h3, .ds-card--accent .sub { color:#fff; }
.ds-card--accent .sub { opacity:.9; }
.ds-card--plum { background:var(--secondary-soft); }
.ds-card--compact { padding:16px 18px; }

/* CARD ANATOMY — reusable pieces for any content card (child, character,
   series, community author…). Compose these inside a .ds-card; never restyle
   per page. A "card type" = .ds-card + these parts (+ a Jinja macro for markup). */
.ds-cardgrid { display:grid; grid-template-columns:repeat(auto-fill, minmax(240px, 1fr)); gap:20px; }
/* scoped under .dreamy so they outrank the base `.dreamy h3` / `.dreamy p` rules
   (which would otherwise force their own size/weight/colour on a card title/text) */
.dreamy .ds-cardtitle { margin:0; color:var(--fg1); font-size:var(--text-title); font-weight:500; }
/* the card's supporting line (story stub / excerpt) — deliberately quiet so the
   title and the picture lead: prose, muted, italic, a step smaller */
.dreamy .ds-cardtext { margin:0; color:var(--fg3); font-family:var(--font-prose); font-size:var(--text-sm); font-style:italic; line-height:1.55; }
.ds-metaline { color:var(--fg2); font-size:.9rem; }
.ds-metaline strong { color:var(--fg1); font-weight:700; }
.ds-chiprow { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
.ds-minichip { background:var(--bg-deep); color:var(--fg1); border-radius:var(--radius-chip); padding:2px 10px; font-size:.8rem; font-weight:500; }
.ds-minichip--ghost { background:transparent; color:var(--fg3); padding-left:0; font-weight:600; }
.ds-minichip--teach { background:var(--teach-soft); color:var(--teach-ink); border:1px solid var(--teach); font-weight:700; font-size:.74rem; }
.ds-cardactions { display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; }
.ds-cardactions form { margin:0; display:inline; }

/* CHILD CARD composition (_child_card.html): the parent's at-a-glance status
   for one child. Deliberately NOT one flat colour — each section carries its
   own tone so the eye separates them without ALL-CAPS labels: story language =
   coral hero, word-learning languages = plum secondary, learned counts = sage
   green, concepts = cute gold idea-bubbles. Section labels stay whisper-quiet
   (fg3, sentence case) since the real content — a language name, a subject —
   is self-explanatory. Specific to this card; generic anatomy above still does
   the framing. */
/* Child cards carry more inside than a plain tile (a whole story-language row,
   language rows, a concept grid), so give them a wider minimum than the generic
   .ds-cardgrid — enough for the story row to stay on one line. */
.cc-cardgrid { grid-template-columns:repeat(auto-fill, minmax(300px, 1fr)); }

/* children.html's in-page "My children / My toys" jump tabs (Tony
   2026-08-28): same "colored tabs" visual recipe as .ds-subtabs
   (_myspace_tabs.html), but NOT that component itself — .ds-subtabs
   collapses above 700px (desktop uses the top-nav .ds-navgroup pair
   instead), which would have hidden this exact fix on a laptop screen, the
   one Tony specifically called out. This stays visible at every width. */
.cc-subtabs { display:inline-flex; gap:3px; padding:4px; border-radius:var(--radius-chip); background:var(--magic-soft); margin-bottom:22px; }
.cc-subtab { display:inline-flex; align-items:center; gap:8px; padding:9px 18px; border-radius:calc(var(--radius-chip) - 3px); text-decoration:none; font-weight:700; font-size:.92rem; white-space:nowrap; color:var(--fg2); transition:background .15s, color .15s; }
.cc-subtab svg { width:17px; height:17px; }
@media (hover:hover){ .cc-subtab:hover { color:var(--accent-ink); } }
/* Divider between the two tabs, same color as their own text (Tony
   2026-08-29). A real element, not a border on the tab itself — .cc-subtab
   is a rounded chip (border-radius), so a border-right there curves along
   the chip's own corner instead of drawing a straight line. NOTE: the tab
   labels render in var(--accent-ink), not the var(--fg2) set two rules up —
   the global `.dreamy a { color:var(--accent-ink) }` (dreamy.css:81) beats
   a single-class `.cc-subtab` selector on specificity (0,1,1 vs 0,1,0), the
   exact pitfall already called out for .ds-subtab elsewhere in this file.
   Matching the divider to accent-ink (what's actually visible) rather than
   "fixing" fg2 to actually apply — the orange reads fine here and lines up
   with the app's existing active-tab/active-nav-label color anyway. */
.cc-subtab-divider { align-self:stretch; width:1px; margin:6px 0; background:var(--accent-ink); }
/* "My toys" carries a tiny caption under its label explaining what the
   feature actually is ("Toys as story characters") — wider than the plain
   single-line tab so the caption never wraps, and its icon+label sits
   beside a small vertical text stack instead of one inline row. */
.cc-subtab--wide { min-width:200px; }
.cc-subtab__stack { display:flex; flex-direction:column; align-items:flex-start; gap:1px; line-height:1.25; }
.cc-subtab__caption { font-size:.66rem; font-weight:500; opacity:.72; }

/* "Add a toy" photo drop zone (Tony 2026-08-29): a plain <input type="file">
   already accepts a dragged-and-dropped file natively in every modern
   browser — nothing needed there. This is purely the missing visual
   indication that dropping is an option, layering the real input
   (invisible, absolutely positioned, still the actual drop target) over a
   dashed decorative box so native drag/click behavior is unchanged. */
.cc-dropzone { position:relative; border:2px dashed var(--rule-strong); border-radius:var(--radius-input); padding:22px 14px; text-align:center; transition:border-color .15s, background .15s; cursor:pointer; }
.cc-dropzone:hover, .cc-dropzone.is-dragover { border-color:var(--accent); background:var(--accent-soft); }
.cc-dropzone input[type="file"] { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; }
.cc-dropzone__visual { display:flex; flex-direction:column; align-items:center; gap:6px; color:var(--fg2); pointer-events:none; }
.cc-dropzone__visual svg { width:26px; height:26px; color:var(--fg3); }
.cc-dropzone__visual small { color:var(--fg3); font-size:.72rem; }

/* The whole card lifts a touch on hover — it feels alive and invites a tap.
   NOTE: no overflow:hidden here — it would clip the "?" explainer bubbles that
   pop above a divider. The top hairline rounds its own corners instead. */
.cc-card { position:relative; }
.cc-card:hover { transform:translateY(-3px); box-shadow:var(--shadow-md); }
/* A hairline of the child's own hue along the top edge ties the card to its
   avatar colour without shouting; its top corners are rounded to sit inside the
   card's rounded corners (since the card no longer clips). */
.cc-card::before { content:""; position:absolute; inset:0 0 auto 0; height:3px; background:var(--cc-hue, var(--accent)); opacity:.9; border-radius:var(--radius-card) var(--radius-card) 0 0; }

/* Header: a gradient initial-avatar + the name in the display serif, with the
   age band as a soft pill pushed flush right. */
.cc-headrow { display:flex; align-items:center; gap:10px; }
.cc-avatar { flex:0 0 auto; width:36px; height:36px; border-radius:50%; display:grid; place-items:center;
  color:#fff; font-family:var(--font-display); font-weight:600; font-size:1.05rem; line-height:1;
  box-shadow:0 2px 8px rgba(42,37,64,.18); }
.cc-name { margin:0; font-family:var(--font-display); font-weight:600; font-size:1.12rem; color:var(--fg1); letter-spacing:.01em; }
.cc-agepill { margin-left:auto; flex:0 0 auto; background:var(--surface-2); color:var(--fg3);
  border:1px solid var(--rule-soft); border-radius:var(--radius-chip); padding:2px 10px; font-size:.72rem; font-weight:600; white-space:nowrap; }

/* Per-child avatar hue (also drives the top hairline via --cc-hue on the card):
   four warm brand gradients, chosen by id so a child keeps the same colour. */
.cc-hue-0 { background:linear-gradient(135deg,#F2A98C,#E87A5D); }
.cc-hue-1 { background:linear-gradient(135deg,#B79AC9,#7A5A8C); }
.cc-hue-2 { background:linear-gradient(135deg,#EAC271,#C99A3A); }
.cc-hue-3 { background:linear-gradient(135deg,#93C3A2,#63916F); }

/* "+N more" — a much smaller trailing pill after the recent words. */
.cc-morepill { display:inline-flex; align-items:center; border:1px solid var(--rule-strong); color:var(--fg3); border-radius:var(--radius-chip); padding:0 5px; font-size:.62rem; font-weight:700; margin-left:5px; transition:color .12s, border-color .12s; }

/* UNIVERSAL language row — story language and every word-learning language share
   this exact shape (see lang_row macro). Three aligned columns: a fixed-width
   name column (so the words always start from the same column, a little clear of
   the name), the recent words + "+N more" on one line, then a little arrow and
   the "N/total" count flush right. `--primary` (story) is tinted coral, and
   `--secondary` (learning) plum — a subtle wash + name colour tells them apart. */
.cc-langlist { display:flex; flex-direction:column; gap:6px; }
.cc-langrow { display:flex; align-items:center; gap:8px; text-decoration:none; padding:3px 8px; border-radius:10px; }
.cc-langrow-name { flex:0 0 auto; min-width:86px; font-size:.9rem; font-weight:700; }
.cc-langrow-words { flex:0 1 auto; min-width:0; font-size:.78rem; color:var(--fg2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cc-langrow-tail { display:inline-flex; align-items:center; gap:3px; flex:0 0 auto; margin-left:auto; }
.cc-arrow { width:13px; height:13px; color:var(--fg3); flex:0 0 auto; }
.cc-langrow--primary { background:color-mix(in srgb, var(--accent) 7%, transparent); }
.cc-langrow--primary .cc-langrow-name { color:var(--accent-ink); }
.cc-langrow--secondary { background:color-mix(in srgb, var(--secondary) 6%, transparent); }
.cc-langrow--secondary .cc-langrow-name { color:var(--secondary); }
.cc-langrow:hover { background:color-mix(in srgb, var(--accent) 13%, transparent); }
.cc-langrow:hover .cc-langrow-name { color:var(--accent-ink); }
.cc-langrow:hover .cc-morepill { color:var(--accent-ink); border-color:var(--accent-soft); }
.cc-langrow:hover .cc-arrow { color:var(--accent-ink); }
.cc-langrow:hover .cc-wl-count { background:var(--accent-soft); color:var(--accent-ink); border-color:var(--accent-soft); }
.cc-langrow:hover .cc-wl-count .n { color:var(--accent-ink); }

/* Centred dotted-divider section label, with a small leading mini-icon. The
   rule is a clear light-grey 2px dotted line (reads unmistakably as dotted), the
   caption is italic, and there's clear breathing room above each section so the
   card reads as distinct bands. */
.cc-divlabel { display:flex; align-items:center; gap:7px; margin-top:9px; color:var(--fg3); font-size:.72rem; font-weight:600; font-style:italic; }
.cc-divlabel svg { width:13px; height:13px; color:var(--fg3); flex:0 0 auto; }
.cc-divlabel::before, .cc-divlabel::after { content:""; flex:1; border-top:2px dotted var(--fg3); opacity:.4; }
/* The "?" helper that sits inside a divider label (reuses .ds-tooltip-wrap).
   Just the help-circle icon — it already draws its own ring, so the button adds
   no border/background of its own (that was the redundant second circle). */
.cc-help { border:none; background:transparent; color:var(--fg3); display:grid; place-items:center; cursor:pointer; padding:0; flex:0 0 auto; line-height:0; }
.cc-help svg { width:15px; height:15px; }
.cc-help:hover { color:var(--accent-ink); }
/* The explainer bubble shouldn't inherit the divider caption's italic. */
.cc-divlabel .ds-tooltip { font-style:normal; }

/* The "N/total" count pill (shared by every language row). */
.cc-wl-count { background:var(--surface-2); color:var(--fg3); border:1px solid var(--rule-soft); border-radius:var(--radius-chip); padding:1px 10px; font-size:.78rem; font-weight:700; font-variant-numeric:tabular-nums; flex:0 0 auto; transition:background .12s, color .12s, border-color .12s; }
.cc-wl-count .n { color:var(--teach-ink); }
.cc-wl-count.is-zero .n { color:var(--fg3); }

/* Concepts — colourful idea-bubbles, TWO per row (science, math, then the rest).
   Each category owns a hue via a reusable .cat-c* class that sets --cat; the
   bubble mixes that hue with the theme's own surface/ink tokens, so the colours
   stay legible in every theme without per-theme overrides. The icon shows the
   pure hue. This .cat-c* palette is meant to be shared by any surface that needs
   to colour-code the same concept categories. */
/* Two per row, but each COLUMN sizes to its own content so a longer name (e.g.
   "Geography") simply makes its pill wider — every label keeps one font size,
   none truncate. Science+math land on row one, the rest wrap below. */
.cc-bubs { display:grid; grid-template-columns:repeat(2, auto); justify-content:start; gap:7px; }
.cc-bub { display:flex; align-items:center; gap:4px; border-radius:var(--radius-chip); padding:4px 9px 4px 7px; font-size:.75rem; font-weight:600; text-decoration:none;
  background:color-mix(in srgb, var(--cat, var(--magic)) 15%, var(--surface));
  color:color-mix(in srgb, var(--cat, var(--magic)) 78%, var(--fg1));
  border:1px solid color-mix(in srgb, var(--cat, var(--magic)) 30%, transparent);
  box-shadow:0 1px 2px rgba(42,37,64,.05); transition:transform .12s, box-shadow .12s; }
.cc-bub svg { width:12px; height:12px; color:var(--cat, var(--magic)); flex:0 0 auto; }
.cc-bub-label { flex:0 0 auto; min-width:0; white-space:nowrap; }
.cc-bub-n { margin-left:auto; padding-left:8px; font-size:.68rem; opacity:.8; font-weight:800; font-variant-numeric:tabular-nums; flex:0 0 auto; }
.cc-bub:hover { transform:translateY(-1px); box-shadow:var(--shadow-sm); }
/* Reusable concept-category palette (kid-friendly, deliberately far-apart hues
   so adjacent pills never read as the same colour). */
.cat-c0 { --cat:#2E9E52; } /* green — science */
.cat-c1 { --cat:#3B72E0; } /* blue — math */
.cat-c2 { --cat:#C98A2B; } /* amber — geography */
.cat-c3 { --cat:#C24E86; } /* rose */
.cat-c4 { --cat:#8A56C9; } /* violet */
.cat-c5 { --cat:#0E9AA0; } /* teal */

.ds-rowhead { display:flex; align-items:baseline; justify-content:space-between; gap:16px; margin-bottom:20px; }
.ds-rowhead h2 { font-size:1.3rem; margin:0; }
.ds-rowhead a { color:var(--accent-ink); font-weight:700; font-size:.86rem; text-decoration:none; }

/* ----------------------------------------------------------------------------
   8. FORM CONTROLS
   ---------------------------------------------------------------------------- */
/* the standard form/section heading (e.g. a <legend>): the section-heading role */
.ds-legend { font-family:var(--font-display); font-size:var(--text-h3); font-weight:600; color:var(--fg1); }
.ds-field { margin-bottom:24px; }
.ds-field label { display:block; margin-bottom:10px; font-weight:700; color:var(--fg1); font-size:.95rem; }
.ds-field .hint { font-weight:500; color:var(--fg3); font-size:.85rem; margin-left:6px; }
.ds-input {
  width:100%; padding:13px 16px; border:1.5px solid var(--rule); border-radius:var(--radius-input);
  background:var(--surface-2); font-family:inherit; font-size:1rem; color:var(--fg1);
  transition:border-color .15s, background .15s;
}
.ds-input::placeholder { color:var(--fg3); }
.ds-input:focus { outline:none; border-color:var(--accent-ink); background:var(--surface); }

/* A stronger-bordered input for rare/unfamiliar one-off forms where a field
   needs to be unmistakable at a glance (Tony 2026-08-29) — the default
   .ds-input border (var(--rule), ~10% opacity) reads fine on a page full of
   other surrounding UI, but on a sparse form like "Add a toy" it can nearly
   vanish against the modal background. A modifier, not a change to
   .ds-input itself (that would ripple across every form in the app) —
   reuse it anywhere else a rare form needs the same clarity. */
.ds-input--clear { border-width:2px; border-color:var(--rule-strong); }
.ds-input--clear:focus { border-color:var(--accent-ink); }

/* Selectable choice pills — a hidden radio with its label styled as the tile.
   The reusable single-select form pattern (age band, story format, …): the
   checked label fills with the coral wash. Pages may restyle within their own
   scope (e.g. a plum sub-variant) on top of this base. */
.ds-choices { display:flex; flex-wrap:wrap; gap:10px; }
.ds-choice { position:relative; }
.ds-choice input { position:absolute; opacity:0; inset:0; cursor:pointer; }
.ds-choice label { display:block; padding:11px 18px; border-radius:var(--radius-chip); background:var(--surface-2);
  border:1.5px solid var(--rule-strong); font-weight:600; font-size:.92rem; color:var(--fg2); cursor:pointer; transition:.15s; }
.ds-choice:hover label { border-color:var(--accent-soft); }
.ds-choice input:checked + label { background:var(--accent-soft); border-color:var(--accent-ink); color:var(--fg1); }
.ds-choice input:focus-visible + label { outline:2px solid var(--accent-ink); outline-offset:2px; }

/* ----------------------------------------------------------------------------
   9. STORY COVER / TILE  (always the first scene image, never a gradient tile)
   ---------------------------------------------------------------------------- */
.ds-shelf { display:grid; grid-template-columns:repeat(4,1fr); gap:24px; }
.ds-book { cursor:pointer; transition:transform .18s; text-decoration:none; color:inherit; display:block; }
.ds-book:hover { transform:translateY(-5px); }
.ds-cover { aspect-ratio:3/4; border-radius:16px; overflow:hidden; position:relative; box-shadow:var(--shadow-md); display:flex; flex-direction:column; background:var(--bg-deep); }
.ds-cover img, .ds-cover svg { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.ds-cover .placeholder { position:absolute; inset:0; display:grid; place-items:center; color:var(--fg3); font-size:.85rem; font-style:italic; }
.ds-cover::after { content:''; position:absolute; inset:0; background:linear-gradient(180deg,transparent 40%,rgba(20,16,40,.55) 100%); }
.ds-cover .toprule { position:absolute; left:16px; right:16px; top:14px; z-index:2; display:flex; justify-content:space-between; color:rgba(255,255,255,.9); font-size:.66rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase; }
.ds-cover .jacket { position:absolute; left:16px; right:16px; bottom:16px; z-index:2; color:#fff; }
.ds-cover .title { font-family:var(--font-display); font-style:italic; font-weight:600; font-size:1.12rem; line-height:1.15; text-shadow:0 2px 12px rgba(0,0,0,.35); }
.ds-cover .for-who { font-size:.7rem; font-weight:700; letter-spacing:.08em; text-transform:uppercase; opacity:.9; margin-top:4px; }
.ds-book .meta { margin-top:13px; display:flex; justify-content:space-between; align-items:center; gap:8px; font-size:.82rem; color:var(--fg2); }
.ds-book .meta .for { font-weight:700; color:var(--fg1); }

/* horizontal scroller (Fresh rails) */
.ds-scrolly { display:flex; gap:20px; overflow-x:auto; padding:4px 4px 14px; scroll-snap-type:x mandatory; }
.ds-scrolly .ds-book { flex:0 0 220px; scroll-snap-align:start; }

/* ----------------------------------------------------------------------------
   10. READING VIEW
   ---------------------------------------------------------------------------- */
.ds-read { max-width:var(--maxw-read); margin:0 auto; }
.ds-read-hero { text-align:center; margin-bottom:36px; }
.ds-read-hero h1 { font-style:italic; font-size:var(--text-hero); margin:8px 0 6px; }
.ds-read-hero .for-who { color:var(--accent-ink); font-weight:700; font-size:.92rem; }
.ds-read-illus { aspect-ratio:16/9; border-radius:20px; overflow:hidden; box-shadow:var(--shadow-md); margin-bottom:40px; position:relative; background:var(--bg-deep); }
.ds-read-illus img, .ds-read-illus svg { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.ds-prose { font-family:var(--font-prose); font-size:var(--text-prose); line-height:1.85; color:var(--fg2); max-width:62ch; margin:0 auto; }
.ds-prose .first::first-letter { font-family:var(--font-display); float:left; font-size:4rem; line-height:.86; font-weight:600; color:var(--accent-ink); padding:6px 12px 0 0; }
.ds-prose p { margin:0 0 1.3em; }
.ds-ornament { text-align:center; margin:28px 0; color:var(--magic); letter-spacing:1em; }

/* teaching layer — sage only, never decorative */
.ds-teach-word { background:linear-gradient(180deg,transparent 60%,var(--magic-soft) 60%); border-bottom:1px dotted var(--magic); cursor:help; }
.ds-teach-card { max-width:560px; margin:32px auto; display:flex; gap:16px; background:var(--surface); border-left:4px solid var(--teach); border-radius:0 16px 16px 0; padding:20px 24px; box-shadow:var(--shadow-sm); }
.ds-teach-card .ico { width:38px; height:38px; border-radius:50%; background:var(--teach-soft); color:var(--teach-ink); display:grid; place-items:center; flex-shrink:0; font-family:var(--font-display); font-weight:700; }
.ds-teach-card .label { font-size:.7rem; letter-spacing:.14em; text-transform:uppercase; color:var(--teach-ink); font-weight:700; }
.ds-teach-card h4 { margin:2px 0 6px; font-size:1.1rem; font-weight:600; font-family:var(--font-display); }
.ds-teach-card p { margin:0; font-size:.92rem; color:var(--fg2); font-family:var(--font-ui); line-height:1.55; }

/* floating audio bar */
.ds-audiobar { position:sticky; bottom:24px; max-width:620px; margin:48px auto 0; background:var(--fg1); color:var(--bg); border-radius:var(--radius-chip); padding:11px 16px 11px 11px; display:flex; align-items:center; gap:14px; box-shadow:var(--shadow-lg); }
[data-mode="dark"] .ds-audiobar { background:var(--surface-2); color:var(--fg1); }
.ds-audiobar .play { width:44px; height:44px; border-radius:50%; background:var(--accent-deep); color:#fff; display:grid; place-items:center; flex-shrink:0; cursor:pointer; }
.ds-audiobar .play svg { width:20px; height:20px; }
.ds-audiobar .track { flex:1; }
.ds-audiobar .track .t { font-size:.72rem; opacity:.65; margin-bottom:5px; }
.ds-audiobar .track .bar { height:4px; background:rgba(255,255,255,.2); border-radius:2px; position:relative; }
[data-mode="dark"] .ds-audiobar .track .bar { background:var(--rule); }
.ds-audiobar .track .bar > span { position:absolute; inset:0 68% 0 0; background:var(--magic); border-radius:2px; }
.ds-audiobar .time { font-size:.78rem; opacity:.7; font-variant-numeric:tabular-nums; }

/* ----------------------------------------------------------------------------
   11. THEME SWITCH (3-position: light / sepia / dark)
   ---------------------------------------------------------------------------- */
.ds-theme-switch { display:inline-flex; align-items:center; gap:0; background:var(--surface); border:1px solid var(--rule); border-radius:var(--radius-chip); padding:2px; line-height:1; user-select:none; }
.ds-theme-switch button { appearance:none; background:transparent; border:none; cursor:pointer; padding:5px 11px; border-radius:var(--radius-chip); color:var(--fg3); font-size:1rem; display:grid; place-items:center; transition:background .12s, color .12s; }
.ds-theme-switch button svg { width:17px; height:17px; }
.ds-theme-switch button:hover { color:var(--fg1); }
.ds-theme-switch button[aria-pressed="true"] { background:var(--accent-deep); color:#fff; }
/* A second copy of the reading-light switch lives inside the overflow/avatar
   menu, hidden until a very small screen folds the bar copy away (see the
   max-width:360px safety block below). The shared [data-mode-set] JS binds and
   syncs every copy, so no script changes are needed. */
.ds-modes-inmenu { display:none; margin:4px auto 2px; }

/* ----------------------------------------------------------------------------
   12. UTILITIES (sparing — Bootstrap covers most layout/spacing)
   ---------------------------------------------------------------------------- */
.ds-stack { display:flex; flex-direction:column; gap:12px; }
.ds-row { display:flex; gap:12px; align-items:center; }
.ds-muted { color:var(--fg3); }
.ds-center { text-align:center; }
.ds-empty { text-align:center; padding:56px 16px; color:var(--fg2); }
.ds-empty h3 { color:var(--fg1); margin-bottom:8px; }

/* ----------------------------------------------------------------------------
   13. RESPONSIVE
   ---------------------------------------------------------------------------- */
/* Wide screens already show My stories / My family in the nav (.ds-navgroup);
   the global .ds-subtabs row only appears once that collapses — same 700px
   cutover as the navgroup-hide rule below, so exactly one of the two shows. */
@media (min-width:701px){ .ds-subtabs { display:none; } }
@media (max-width:900px){ .ds-shelf { grid-template-columns:repeat(3,1fr); } }
@media (max-width:760px){
  .ds-shelf { grid-template-columns:repeat(2,1fr); }
  .ds-navlinks { display:none; }
  .ds-page { padding:28px 18px 56px; }
}
/* Phone (canonical 700px breakpoint, decision #126): tighten the nav so the
   Sign in CTA stays compact and never crowds the right edge. Scoped to
   .ds-navright so page-level CTAs (e.g. the Create button) keep full size. */
@media (max-width:700px){
  .ds-nav { padding:8px 10px; gap:5px; }
  .ds-navright { gap:5px; }
  /* nowrap so "Sign in" can never break to two rows; tighter padding + size to
     buy the width back (reported wrapping on a real phone). */
  .ds-navright .ds-btn { padding:8px 12px; font-size:.82rem; white-space:nowrap; }
  /* brand becomes a vertical lockup: round mark with a tiny wordmark under it */
  .ds-brand { flex-direction:column; gap:2px; font-size:.66rem; line-height:1; }
  .ds-brand .mark { width:30px; height:30px; box-shadow:0 0 0 3px var(--accent-soft); }
  /* reading-light switch at ~70% of its desktop size */
  .ds-theme-switch button { padding:4px 6px; }
  .ds-theme-switch button svg { width:14px; height:14px; }
  /* signed-in credit counter: a mild trim (not a shrink) — there's room on the
     signed-in bar since the avatar is small and Sign out lives in its menu, so
     this just keeps a little margin on the narrowest phones. */
  .ds-navright .ds-pill { padding:6px 11px; font-size:.78rem; gap:6px; }
  /* the three main destinations stay ON the bar in small type, icons off —
     they're too important to bury in a menu. Only the My stories/My family
     pair drops out (the in-page capsule covers it). Reuses the same grouped-
     capsule language as .ds-navgroup / the theme switch / the subtabs pill
     below it, instead of inventing a new border style: one tinted pill holds
     all three words, the active one rides a raised white chip inside it. */
  .ds-navlinks { display:flex; gap:1px; padding:2px; border-radius:var(--radius-chip); background:var(--magic-soft); }
  .ds-navlinks a { padding:6px 7px; font-size:.76rem; gap:4px; border-radius:calc(var(--radius-chip) - 3px); }
  .ds-navlinks a:hover { background:transparent; }
  .ds-navlinks a.active { background:var(--surface); box-shadow:var(--shadow-sm); }
  .ds-navlinks a svg { display:none; }
  .ds-navlinks .ds-navgroup { display:none; }
  /* Blog moves off the crowded pill into a menu — the avatar menu for signed-in
     users, the small "more" icon menu for signed-out ones — so the Sign in CTA
     keeps its full width and never wraps to two rows. */
  .ds-navlinks .ds-nav-blog,
  .ds-navlinks .ds-nav-features { display:none; }
  .ds-nav-more { display:block; }
  /* the Admin door leaves the bar and reappears inside the avatar menu */
  .ds-nav-admin { display:none; }
  .ds-usermenu__panel .ds-phone-only { display:flex; }
  /* now sitting directly under the sticky header (global, not page content),
     so it needs its own top clearance; shrinks to match the compacted nav
     and stays centred — on a narrow screen a left-hugging pair looks adrift */
  .ds-subtabs { display:flex; width:max-content; padding:3px; margin:10px auto 8px; }
  .ds-subtab { padding:5px 10px; gap:5px; font-size:.8rem; }
  .ds-subtab svg { width:13px; height:13px; }
}

/* Very small phones (≤360px, the standard small-screen width — 320/360 devices;
   nothing common sits in 361–374, and 375+ keeps the full bar). Safety net: the
   3-button reading-light switch folds OFF the bar and into the overflow menu
   (signed-out) / avatar menu (signed-in) so the fixed items never overflow. The
   in-menu copy shares the same [data-mode-set] JS, so it stays in sync. */
@media (max-width:360px){
  .ds-navright > .ds-theme-switch { display:none; }
  .ds-usermenu__panel .ds-modes-inmenu { display:inline-flex; }
}

/* Landscape phones (max-height 480px, decision #126): the nav is sticky, so on
   a ~390px-tall screen every pixel of its height is paid on every scroll —
   flatten it. */
@media (max-height:480px){
  .ds-nav { padding:5px 18px; }
}
