/* Pipelinify shared theme: the park guide map. Cream water, ink type and edges, one flat lobe colour per game,
   coral only for "not solved". No gradients, grain or shadows. All games import this. */
/* Fonts are served from this site (shared/fonts, latin subset, variable files from @fontsource-variable 5.3.0;
   each family's OFL licence sits beside it), so no page asks a third party for anything. */
@font-face { font-family: 'Bricolage Grotesque'; font-weight: 800; font-display: swap; src: url('fonts/bricolage-grotesque-latin-opsz-normal.woff2') format('woff2'); }
@font-face { font-family: 'Figtree'; font-weight: 400 700; font-display: swap; src: url('fonts/figtree-latin-wght-normal.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 400 700; font-display: swap; src: url('fonts/jetbrains-mono-latin-wght-normal.woff2') format('woff2'); }

:root {
  --water: #f5f1e4;       /* the ground everywhere, and the fill of cards and lakes */
  --ink: #2c2e2a;         /* type, edges, filled controls: ≥7:1 on water and on every lobe */

  /* the names every game already uses */
  --bg: var(--water);
  --bg-2: var(--water);
  --panel: var(--water);
  --panel-2: #e9e3d0;                /* deeper cream: hover and selected rows */
  --line: rgb(44 46 42 / .25);       /* dividers inside cards and tables only, never a control edge */
  --line-strong: var(--ink);         /* card frames and control edges */
  --text: var(--ink);
  --muted: #494e43;                  /* secondary text: ≥4.5:1 on water and on every lobe */

  /* one lobe per game. A page picks its lobe with data-lobe="<game id>" on <body> (or any element);
     ink text is ≥7:1 on all of them. A screen shows one lobe only: its own --accent. */
  --lobe-refinery: #8ed462;
  --lobe-dag-doctor: #ebc1ff;
  --lobe-quality: #f5e211;
  --lobe-stream-lab: #8fd0f5;
  --lobe-schema-architect: #ffc170;
  --lobe-partition-planner: #ffb3cf;
  /* earlier names, still set inline by some pages */
  --cyan: var(--lobe-refinery);
  --violet: var(--lobe-dag-doctor);
  --pink: var(--lobe-quality);
  --orange: var(--lobe-stream-lab);
  --blue: var(--lobe-schema-architect);
  --lime: var(--lobe-partition-planner);

  /* status. Words and icons carry it; colour only backs them up. */
  --coral: #ff6168;       /* the "not solved" fill, always under ink text (4.7:1) */
  --bad: #8f1f14;         /* "wrong" as text or a thin mark: ≥4.5:1 on water and on every lobe */
  --good: #17502f;        /* "right" as text or a thin mark */
  --amber: #5e4100;       /* "careful" as text or a thin mark */
  --scarlet: var(--coral);
  --on-scarlet: var(--ink);

  --radius: 12px;         /* small controls */
  --radius-card: 18px;
  --radius-lake: 28px;
  --pill: 999px;
  --pad: 40px;            /* page gutter; the lobe uses it to leave the frame */
  --display: 'Bricolage Grotesque', system-ui, sans-serif; /* headings and names */
  --font: 'Figtree', system-ui, sans-serif;                /* reading and controls */
  --mono: 'JetBrains Mono', ui-monospace, monospace;       /* data only */
  --bounce: cubic-bezier(.34, 1.56, .64, 1);               /* a small overshoot, then settle */
  --t: .4s;

  /* drawn icons, used as masks so they take the text colour: <i class="ico ico-tick"></i> */
  --i-tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
  --i-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12h15M13 6l6 6-6 6'/%3E%3C/svg%3E");
  --i-back: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 12H5M11 6l-6 6 6 6'/%3E%3C/svg%3E");
  --i-out: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17L17 7M9 7h8v8'/%3E%3C/svg%3E");
  --i-prev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 5l-7 7 7 7'/%3E%3C/svg%3E");
  --i-next: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 5l7 7-7 7'/%3E%3C/svg%3E");
  --i-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  --i-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/svg%3E");
  --i-star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' stroke='%23000' stroke-width='2' stroke-linejoin='round'%3E%3Cpath d='M12 2.5l2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 17.7l-6.1 3.4 1.4-6.8-5.1-4.7 6.9-.8z'/%3E%3C/svg%3E");
  --i-star-o: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round'%3E%3Cpath d='M12 2.5l2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 17.7l-6.1 3.4 1.4-6.8-5.1-4.7 6.9-.8z'/%3E%3C/svg%3E");
  --i-bang: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v8.5M12 18.5v.01'/%3E%3C/svg%3E");
  /* the same tick, play and skip, drawn in ink, for use as a picture on a lobe-colour chip */
  --pic-tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232c2e2a' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
  --pic-play: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232c2e2a' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 5l11 7-11 7z'/%3E%3C/svg%3E");
  --pic-skip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232c2e2a' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 5l7 7-7 7M13 5l7 7-7 7'/%3E%3C/svg%3E");

  --pic-bang: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f5f1e4' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v8.5M12 18.5v.01'/%3E%3C/svg%3E");
  /* a divider on the water: one ink stroke that wanders like a coast. Use as  background: var(--rule) 0 0 / 420px 8px repeat-x  (see .ruled) */
  --rule: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 420 8' fill='none' stroke='%232c2e2a' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M0 4C22 1.5 46 1.5 74 4S128 6.5 160 4S214 1.8 250 4S318 6.2 350 4S398 6 420 4'/%3E%3C/svg%3E");

  /* the lobe's coast, in four pieces so it keeps its scale at any size (see .lobe) */
  --lobe-corner: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 120' preserveAspectRatio='none'%3E%3Cpath d='M36 120C36 96 8 84 14 56C18 36 48 40 72 40V120Z'/%3E%3C/svg%3E");
  --lobe-crest: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 900 120' preserveAspectRatio='none'%3E%3Cpath d='M0 40C120 40 240 48 400 42C560 36 640 26 760 30C830 32 870 26 900 20V120H0Z'/%3E%3C/svg%3E");
  --lobe-coast: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 600' preserveAspectRatio='none'%3E%3Cpath d='M36 0C36 80 62 130 52 220C42 310 8 340 14 430C18 500 30 540 26 600H72V0Z'/%3E%3C/svg%3E");
  --lobe-wave-top: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 28' preserveAspectRatio='none'%3E%3Cpath d='M0 14C60 2 120 24 200 14C280 4 340 22 400 8V28H0Z'/%3E%3C/svg%3E");
  --lobe-wave-bottom: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 28' preserveAspectRatio='none'%3E%3Cpath d='M0 0H400V16C330 28 270 6 200 16C120 26 60 6 0 18Z'/%3E%3C/svg%3E");
  color-scheme: light;
}
@media (max-width: 640px) { :root { --pad: 16px; } }

* { box-sizing: border-box; }

/* The lobe colour of the page. Refinery's unless the page says otherwise. */
body { --accent: var(--lobe-refinery); }
[data-lobe="refinery"] { --accent: var(--lobe-refinery); }
[data-lobe="dag-doctor"] { --accent: var(--lobe-dag-doctor); }
[data-lobe="quality"] { --accent: var(--lobe-quality); }
[data-lobe="stream-lab"] { --accent: var(--lobe-stream-lab); }
[data-lobe="schema-architect"] { --accent: var(--lobe-schema-architect); }
[data-lobe="partition-planner"] { --accent: var(--lobe-partition-planner); }

body {
  margin: 0;
  min-height: 100vh;
  font: 16px/1.5 var(--font);
  color: var(--text);
  background: var(--bg);
}

a { color: var(--text); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--text); text-decoration-thickness: 2px; }
h1, h2, h3 { margin: 0 0 .4em; font-family: var(--display); font-weight: 800; line-height: .95; letter-spacing: -.02em; text-wrap: balance; }
h1 { font-size: clamp(34px, 4.2vw, 54px); }
h2 { font-size: 23px; }
h3 { font-size: 19px; }
p, li, dd { text-wrap: pretty; }
code, .mono { font-family: var(--mono); }
::selection { background: var(--ink); color: var(--water); }
html { scrollbar-color: var(--ink) var(--water); }
input, select, textarea { accent-color: var(--ink); caret-color: var(--ink); }
:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; } /* ink reads on water and on every lobe */

/* keycap: <kbd>R</kbd> */
kbd { font: 700 12px/1.45 var(--mono); padding: 0 5px; border: 1.5px solid currentColor; border-radius: 6px; }

/* drawn icon that takes the text colour: <i class="ico ico-tick" aria-hidden="true"></i> */
.ico { display: inline-block; flex: none; width: 1em; height: 1em; vertical-align: -.14em; background: currentColor; mask: var(--i) center / contain no-repeat; }
.ico-tick { --i: var(--i-tick); } .ico-arrow { --i: var(--i-arrow); } .ico-back { --i: var(--i-back); } .ico-out { --i: var(--i-out); }
.ico-prev { --i: var(--i-prev); } .ico-next { --i: var(--i-next); } .ico-down { --i: var(--i-down); } .ico-x { --i: var(--i-x); }

/* drawn stars over the text stars, which stay in the markup for screen readers and tests:
   <span class="star">★</span>   <span class="star-row" data-stars="2">★★☆</span>   .result-stars reads its aria-label ("2 of 3 stars") */
.star, .star-row, .result-stars[aria-label] {
  display: inline-block; height: 1em; overflow: hidden; white-space: nowrap; vertical-align: -.14em; line-height: 1; letter-spacing: 0;
  background: currentColor; mask: var(--i-star) center / contain no-repeat;
}
.star { width: 1em; }
.star-row, .result-stars[aria-label] {
  width: 3.3em; mask-size: 1em 1em; mask-position: 0 0, 50% 0, 100% 0;
  mask-image: var(--i-star-o), var(--i-star-o), var(--i-star-o);
}
.star-row[data-stars="1"], .result-stars[aria-label^="1 "] { mask-image: var(--i-star), var(--i-star-o), var(--i-star-o); }
.star-row[data-stars="2"], .result-stars[aria-label^="2 "] { mask-image: var(--i-star), var(--i-star), var(--i-star-o); }
.star-row[data-stars="3"], .result-stars[aria-label^="3 "] { mask-image: var(--i-star), var(--i-star), var(--i-star); }

/* ---- surfaces ----
   .panel: a card (cream, ink frame). .lake: a cream opening inside the lobe, no frame. */
.panel { background: var(--panel); border: 2px solid var(--line-strong); border-radius: var(--radius-card); padding: 16px; }
.lake { background: var(--water); border-radius: var(--radius-lake); padding: 18px 24px; }
@media (max-width: 640px) { .lake { padding: 14px 16px; border-radius: 22px; } }

/* ---- the lobe: one flat shape in the page's lobe colour, behind the build area ----
   <section class="build"> <div class="lobe" aria-hidden="true"></div> … </section>
   Put it first inside any box that is position: relative; isolation: isolate (.build is). On desktop its coast runs
   down the left and along the top, and it leaves the frame on the right and at the bottom; on narrow screens it is
   a band behind the box with a coast above and below. The box keeps its first row (the level stops) clear of the
   coast with its own top padding (.build does). Tune with --lobe-top / --lobe-left (how far the coast sits
   from the box) and --lobe-w / --lobe-h (the size of the shoulder). */
.lobe {
  --w: var(--lobe-w, 72px); --h: var(--lobe-h, 120px);
  position: absolute; z-index: -1; pointer-events: none;
  inset: var(--lobe-top, -32px) calc(-1 * var(--pad)) calc(-1 * var(--pad)) var(--lobe-left, -64px);
  background: var(--accent);
  mask-image: var(--lobe-corner), var(--lobe-crest), var(--lobe-coast), linear-gradient(#000 0 0);
  mask-repeat: no-repeat;
  mask-position: 0 0, calc(var(--w) - 1px) 0, 0 calc(var(--h) - 1px), calc(var(--w) - 1px) calc(var(--h) - 1px);
  mask-size: var(--w) var(--h), calc(100% - var(--w) + 1px) var(--h), var(--w) calc(100% - var(--h) + 1px), calc(100% - var(--w) + 1px) calc(100% - var(--h) + 1px);
}
@media (max-width: 960px) {
  .lobe {
    inset: var(--lobe-top, -20px) calc(-1 * var(--pad)) var(--lobe-bottom, -24px);
    mask-image: var(--lobe-wave-top), linear-gradient(#000 0 0), var(--lobe-wave-bottom);
    mask-position: 0 0, 0 27px, 0 100%;
    mask-size: 100% 28px, 100% calc(100% - 54px), 100% 28px;
  }
}

/* ---- game page frame: reading on the water, building on the lobe ----
   <main class="shell">
     <section class="read"> h1.level-title, goal, details.concept-inline </section>
     <section class="build"> div.lobe, nav.levelbar, the game's pieces, the toolbar, div.lake </section>
     <section class="panel result-panel"> … </section>
   </main>
   Wide: the result sits on the water under the concept and stays in view while a tall build column scrolls
   (it sticks to the top of the window, so it can never ride up over the concept). One column: it follows the build. */
.shell { display: grid; grid-template-columns: minmax(0, 392px) minmax(0, 1fr); grid-template-rows: auto 1fr; gap: 28px 68px; padding: 28px var(--pad) var(--pad); min-height: calc(100vh - 60px); }
.shell > * { min-width: 0; } /* lets inner scrollers scroll instead of widening the page */
.shell > .read { grid-area: 1 / 1; display: flex; flex-direction: column; gap: 20px; }
.shell > .build { grid-area: 1 / 2 / span 2; }
.shell > .result-panel { grid-area: 2 / 1; align-self: start; }
@media (min-width: 961px) and (min-height: 700px) { .shell > .result-panel { position: sticky; top: 16px; } } /* only where a tall result still fits the window */
.build { position: relative; isolation: isolate; display: grid; gap: 16px; align-content: start; padding-top: 40px; } /* the coast runs above the level stops, never through them */
.build > :not(.lobe) { max-width: 1040px; min-width: 0; }
@media (max-width: 1340px) { .shell { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); column-gap: 56px; } } /* a laptop gives the build area the room */
@media (max-width: 960px) {
  .shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; gap: 44px; padding-top: 16px; min-height: 0; }
  .shell > :is(.read, .build, .result-panel) { grid-area: auto; }
  .build { padding-block: 28px 20px; }
}

/* ---- capsule buttons ----
   .btn: the main action, an ink capsule. .btn.ghost: every other action, an outlined capsule.
   .btn.run: the one Run capsule, with a round lobe-colour chip on its trailing edge (play; data-mode="skip" draws skip). .btn.arrow: adds the drawn → */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 6px 18px;
  font: 700 15px/1.15 var(--font); color: var(--water); background: var(--ink);
  border: 2px solid var(--ink); border-radius: 26px;
  text-decoration: none; cursor: pointer;
  transition: transform var(--t) var(--bounce), background-color .2s, color .2s;
}
.btn:hover { color: var(--water); transform: translateY(-2px); }
.btn:active { transform: scale(.96); }
.btn.ghost { background: transparent; color: var(--ink); }
.btn.ghost:hover { background: var(--ink); color: var(--water); }
.btn:disabled { background: transparent; color: var(--muted); border-style: dashed; transform: none; cursor: not-allowed; }
.btn.arrow::after, .arrow-after::after { content: ''; flex: none; width: 1.1em; height: 1.1em; background: currentColor; mask: var(--i-arrow) center / contain no-repeat; }
.btn.out::after { content: ''; flex: none; width: 1.1em; height: 1.1em; background: currentColor; mask: var(--i-out) center / contain no-repeat; }
.btn.run { min-height: 52px; min-width: 96px; padding: 6px 8px 6px 22px; gap: 12px; font-size: 17px; }
.btn.run::after, .dot { /* the round chip */
  content: ''; flex: none; width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
  color: var(--ink); background: var(--accent) center / 18px no-repeat; font-style: normal;
}
.btn.run::after { background-image: var(--pic-play); background-position: 55% 50%; transition: transform var(--t) var(--bounce); }
.btn.run[data-mode="skip"]::after { background-image: var(--pic-skip); background-position: center; } /* while a run plays, Run is Skip: the game sets data-mode */
.btn.run:hover:enabled::after { transform: scale(1.12); }
.btn.run:disabled::after { background-color: transparent; box-shadow: inset 0 0 0 2px var(--muted); opacity: .6; }
.btn kbd { font-size: 11px; }
@media (max-width: 640px) { .btn.run { min-height: 44px; padding: 4px 5px 4px 20px; font-size: 16px; } .btn.run::after { width: 30px; height: 30px; background-size: 16px; } } /* level with its neighbours on a phone */
@media (pointer: coarse) { .btn kbd { display: none; } } /* no keyboard to press it */

/* small label capsule: <span class="chip">6 rows</span>; .chip.bad is the "not solved" coral */
.chip {
  display: inline-flex; align-items: center; gap: 6px; min-height: 28px; padding: 2px 11px;
  font: 600 13px/1.2 var(--font); color: var(--ink); background: var(--water);
  border: 1.5px solid var(--ink); border-radius: 15px;
}
.chip.bad { background: var(--coral); }

.stars { white-space: nowrap; }

/* ---- shared game chrome: top bar ----
   <header class="topbar"> a.brand (back, mark, Pipelin<span>ify</span>)  strong.game-name  [span.keys]  button.btn.ghost Learn  [a.btn.ghost All games] */
.topbar { display: flex; align-items: center; gap: 8px 18px; min-height: 60px; padding: 8px var(--pad); }
.topbar .brand { display: inline-block; min-height: 44px; font: 800 22px/44px var(--display); letter-spacing: -.02em; color: var(--ink); text-decoration: none; white-space: nowrap; }
.topbar .brand img, .topbar .brand svg { width: 34px; height: 34px; margin: 0 10px 0 6px; vertical-align: middle; position: relative; top: -2px; }
.topbar .brand svg.ico { width: 22px; height: 22px; margin: 0; }
.topbar .brand > span, .ify { background: var(--accent); border-radius: var(--pill); padding: .02em .22em .1em; margin-left: .04em; }
.topbar .game-name { font: 800 22px/1 var(--display); letter-spacing: -.02em; padding: 2px 0 2px 18px; border-left: 2px solid var(--ink); margin-right: auto; white-space: nowrap; }
.topbar .keys { font-size: 14px; color: var(--muted); white-space: nowrap; }
@media (max-width: 960px) { .topbar .keys { display: none; } }
@media (max-width: 640px) { /* phones: brand, game and Learn on one line; the brand link is the way back */
  .topbar { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; }
  .topbar .brand { font-size: 18px; }
  .topbar .brand img, .topbar .brand svg { width: 28px; height: 28px; margin: 0 6px 0 4px; }
  .topbar .game-name { font-size: 17px; padding-left: 8px; min-width: 0; margin-right: 0; overflow: hidden; text-overflow: ellipsis; }
  .topbar > .btn { grid-column: 3; padding-inline: 14px; }
  .topbar > a.btn { display: none; }
  .lv-all > summary, .concept-inline .why > summary { min-height: 44px; } /* disclosure toggles are touch targets too */
}
@media (max-width: 480px) { /* small phones: the mark stands for the wordmark (the link keeps its name), so the game's name shows in full */
  .topbar .brand { font-size: 0; display: inline-flex; align-items: center; }
  .topbar .brand .ico { width: 18px; height: 18px; } /* the back arrow is sized in em */
  .topbar .brand img, .topbar .brand svg { margin-right: 4px; }
  .topbar .game-name { overflow: visible; white-space: normal; text-wrap: balance; }
}

/* Level heading on every game */
.level-title { font-size: clamp(34px, 4.2vw, 54px); margin: 0 0 6px; }

table.data { border-collapse: collapse; font: 13px var(--mono); font-variant-numeric: tabular-nums; }
table.data th, table.data td { padding: 4px 10px; border-bottom: 1px solid var(--line); text-align: left; }
table.data th { font-weight: 700; border-bottom: 2px solid var(--ink); }
td.ok { color: var(--good); }
td.err { background: var(--coral); }
td.null { color: var(--muted); font-style: italic; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ---- skip link: first focusable element on every game page ---- */
.skip-link { position: absolute; left: 16px; top: -80px; z-index: 20; padding: 10px 18px; border-radius: var(--pill); background: var(--ink); color: var(--water); font-weight: 700; text-decoration: none; }
.skip-link:hover { color: var(--water); }
.skip-link:focus { top: 8px; }

/* ---- storage notice (inserted by shared/progress.js when storage is blocked) ---- */
.notice { margin: 0 0 12px; padding: 10px 16px; border-radius: var(--radius-card); border: 2px dashed var(--ink); background: var(--panel-2); color: var(--ink); font-size: 15px; }

/* ---- shared result panel: same anatomy and placement in every game ----
   <section class="panel result-panel" data-state="idle|win|lose" tabindex="-1">  (no aria-live: it takes focus after a run;
                                                              a direct child of .shell, after .build: see the page frame)
     <h2>Result</h2> <p class="result-stars" role="img" aria-label="2 of 3 stars">★★☆</p> <p class="result-title">…</p>
     <p>explanation</p> [<p class="hint">…</p>] <div class="result-actions"><button class="btn">Next level</button></div>
   </section>
   After a run the title is the panel's header: coral under ink for "not solved", ink with lobe-colour stars for a win.
   The stars sit in that header's right end; the h2 stays for the outline but is not drawn. */
.result-panel { position: relative; padding: 16px 18px; border-radius: 22px; }
.result-panel > h2 { font-size: 19px; margin: 0 0 10px; }
.result-panel:is([data-state="win"], [data-state="lose"]) > h2 { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.result-panel p { margin: 0 0 8px; max-width: 70ch; }
.result-panel .result-title { font: 800 23px/1 var(--display); letter-spacing: -.02em; text-wrap: balance; }
.result-panel:is([data-state="win"], [data-state="lose"]) .result-title {
  max-width: none; min-width: calc(100% + 36px); margin: -16px -18px 14px; padding: 15px 112px 15px 18px; border-radius: 20px 20px 0 0;
  background: var(--ink); color: var(--water);
}
/* the title's colour is the coral; the letters are filled ink so they read on it */
.result-panel[data-state="lose"] .result-title { color: var(--coral); background: currentColor; -webkit-text-fill-color: var(--ink); }
.result-panel .result-stars { font-size: 22px; margin: 0 0 8px; }
.result-panel:is([data-state="win"], [data-state="lose"]) .result-stars { position: absolute; z-index: 1; top: 15px; right: 18px; margin: 0; }
.result-panel[data-state="win"] .result-stars { color: var(--accent); }
.result-panel .hint { color: var(--ink); }
.result-panel .takeaway { color: var(--ink); }
.result-panel .result-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }

/* ---- inline concept: a ruled section on the water ----
   <details class="panel concept-inline" id="concept" open?>
     <summary><h2 class="concept-title">title</h2></summary>
     <div class="concept-body"><p>what</p>
       <details class="why"><summary>Example · why it matters in real pipelines</summary>[<p class="eg">example</p>]<p>why</p></details></div>
   </details>
   The title is an h2 so the outline goes h1 (level) -> h2 (concept, result). Open on a first visit to a level,
   folded otherwise; Learn opens it and scrolls to it. */
.ruled, .concept-inline { background: var(--rule) 0 0 / 420px 8px repeat-x; padding-top: 8px; } /* the wavy divider, drawn above the box */
.concept-inline { padding: 8px 0 0; border: 0; border-radius: 0; }
.concept-inline > summary { cursor: pointer; padding: 6px 0 8px; display: flex; gap: 8px; align-items: flex-start; list-style: none; min-height: 44px; }
.concept-inline summary::-webkit-details-marker { display: none; }
.concept-inline > summary::before, .lv-all > summary::after { /* drawn chevron */
  content: ''; flex: none; width: 16px; height: 16px; background: currentColor; mask: var(--i-down) center / contain no-repeat;
  transform: rotate(-90deg); transition: transform var(--t) var(--bounce);
}
.concept-inline > summary::before { margin-top: 4px; } /* level with the title's first line when it wraps */
.concept-inline[open] > summary::before { transform: none; }
.concept-inline .concept-title { font-size: 23px; line-height: 1; margin: 0; }
.concept-inline .concept-body { padding: 0 0 12px; font-size: 15px; }
.concept-inline .concept-body p { margin: 0 0 8px; max-width: 58ch; }
.concept-inline .why > summary { cursor: pointer; font-weight: 700; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; min-height: 32px; display: flex; align-items: center; }
.concept-inline .eg { font: 13px/1.5 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; margin: 8px 0; max-width: 58ch; }

/* ---- level picker (shared/levelnav.js): capsule stops along the lobe's top coast ----
   A line of text (Levels, n of m solved, All levels) over the row of stops, so eleven stops fit a laptop.
   Not played: a cream capsule with its number. Solved: ticked, stars beneath. Tried: a dashed edge.
   Current: a wider ink capsule, "Level N" behind a lobe-colour chip.
   When the stops do not fit, the track scrolls and fades out at the edge it can scroll towards (data-more). */
.levelbar { display: grid; grid-template-columns: auto minmax(0, max-content) auto; justify-content: start; align-items: start; gap: 6px 8px; margin: 0; }
.levelbar .lv-label { grid-column: 1 / -1; width: 0; min-width: 100%; /* spans the row without widening its columns */ position: relative; display: flex; align-items: center; gap: 0 14px; min-height: 24px; font-size: 14px; line-height: 19px; font-weight: 600; white-space: nowrap; }
.levelbar .lv-label strong { font-weight: 700; }
.levelbar .lv-step { position: relative; flex: none; width: 38px; height: 38px; border-radius: 50%; cursor: pointer; font-size: 0; color: var(--ink); background: var(--water); border: 2px solid var(--ink); transition: transform var(--t) var(--bounce); }
.levelbar .lv-step::before { content: ''; position: absolute; inset: 8px; background: currentColor; mask: var(--i-prev) center / contain no-repeat; }
.levelbar .lv-step[data-testid="level-next"]::before { mask-image: var(--i-next); }
.levelbar .lv-step:hover:enabled { background: var(--ink); color: var(--water); }
.levelbar .lv-step:active:enabled { transform: scale(.92); }
.levelbar .lv-step:disabled { border-style: dashed; color: var(--muted); border-color: var(--muted); cursor: default; }
.lv-track {
  display: flex; gap: 6px; align-items: flex-start; overflow-x: auto; scrollbar-width: none; counter-reset: lv;
  margin: -6px 0 0; padding: 6px 6px 18px; /* room for focus rings above and stars below */
  scroll-padding-inline: 44px; /* a focused stop lands clear of the fade */
  mask-image: linear-gradient(to right, transparent, #000 var(--fade-start, 0px), #000 calc(100% - var(--fade-end, 0px)), transparent);
}
.lv-track[data-more~="start"] { --fade-start: 40px; }
.lv-track[data-more~="end"] { --fade-end: 40px; }
.lv-track::-webkit-scrollbar { display: none; }
.lv-node {
  position: relative; flex: none; display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  height: 38px; min-width: 44px; padding: 0 10px; cursor: pointer; counter-increment: lv;
  font: 700 15px/1 var(--font); color: var(--ink); background: var(--water); border: 2px solid var(--ink); border-radius: var(--pill);
  transition: transform var(--t) var(--bounce), background-color .2s;
}
.lv-node:hover { transform: translateY(-2px); background: var(--panel-2); }
.lv-node[data-tried] { border-style: dashed; }
.lv-node[data-solved]::before { content: ''; width: 14px; height: 14px; background: var(--pic-tick) center / contain no-repeat; }
/* current: the number moves into the chip and "Level N" follows it; the button's own text is not drawn */
.lv-node[aria-current] { font-size: 0; gap: 9px; padding: 0 16px 0 4px; color: var(--water); background: var(--ink); border-style: solid; }
.lv-node[aria-current]:hover { background: var(--ink); }
.lv-node[aria-current]::before { content: counter(lv) / ''; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-size: 15px; color: var(--ink); background: var(--accent); }
.lv-node[aria-current][data-solved]::before { content: ''; background: var(--accent) var(--pic-tick) center / 16px no-repeat; }
.lv-node[aria-current]::after { content: 'Level ' counter(lv) / ''; font-size: 15px; white-space: nowrap; }
.lv-node .lv-stars { position: absolute; top: calc(100% + 4px); left: 50%; translate: -50% 0; font-size: 11px; color: var(--ink); }
@media (max-width: 640px) {
  .levelbar { grid-template-columns: auto minmax(0, 1fr) auto; justify-content: stretch; }
  .levelbar .lv-step, .lv-node { height: 44px; }
  .levelbar .lv-step { width: 44px; }
  .lv-node[aria-current]::before { width: 34px; height: 34px; }
}

/* "All levels" inside the level bar: every level by title, as a route. Works on touch. */
.lv-all > summary { cursor: pointer; list-style: none; font-weight: 700; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; min-height: 19px; display: inline-flex; align-items: center; gap: 4px; }
.lv-all > summary::-webkit-details-marker { display: none; }
.lv-all > summary::after { transform: none; }
.lv-all[open] > summary::after { transform: rotate(180deg); }
.lv-all > div, .lv-all > ol { position: absolute; left: 0; z-index: 15; } /* a dropdown over the page: opening it adds no height */
.lv-all ol {
  list-style: none; margin: 6px 0 0; padding: 6px; display: grid; width: min(340px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
  background: var(--water); border: 2px solid var(--ink); border-radius: var(--radius-card);
}
.lv-all li button {
  position: relative; z-index: 0; width: 100%; min-height: 44px; display: grid; grid-template-columns: 28px 1fr auto; align-items: center; gap: 10px; text-align: left; cursor: pointer;
  font: 500 15px/1.25 var(--font); color: var(--ink); background: none; border: 0; border-radius: var(--radius); padding: 4px 10px 4px 6px;
}
.lv-all li button::before { content: ''; position: absolute; z-index: -1; left: 18.5px; top: 0; bottom: 0; width: 3px; background: var(--ink); } /* the route */
.lv-all li:first-child button::before { top: 50%; }
.lv-all li:nth-last-child(2) button::before { bottom: 50%; }
.lv-all li button:hover, .lv-all li button[data-current] { background: var(--panel-2); }
.lv-all li .n { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%; font: 700 13px/1 var(--font); color: var(--ink); background: var(--water); border: 2px solid var(--ink); }
.lv-all li button[data-solved] .n { background: var(--ink); color: var(--water); }
.lv-all li button[data-current] .n { background: var(--accent); color: var(--ink); }
.lv-all li .st { font-size: 13px; font-weight: 600; white-space: nowrap; }
.lv-all .lv-key { margin: 6px 6px 2px; padding-top: 8px; border-top: 1px solid var(--line); font-size: 13px; font-weight: 500; color: var(--muted); white-space: normal; }

/* Undo toast (shared/undo.js) */
.undo-toast {
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); translate: -50% 0; z-index: 30;
  display: flex; gap: 12px; align-items: center; padding: 6px 6px 6px 20px; border-radius: var(--pill);
  background: var(--water); border: 2px solid var(--ink); color: var(--ink); font-weight: 600;
  animation: toast-in var(--t) var(--bounce);
}
@keyframes toast-in { from { translate: -50% 24px; opacity: 0; } }
.undo-toast[hidden] { display: none; } /* display:flex would otherwise beat the hidden attribute */
.undo-toast span { white-space: nowrap; }

/* The level's 3-star target, beside Run in every game: <span class="target-chip">★★★ in 2 blocks or fewer</span> */
.target-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; font: 600 15px/1.3 var(--font); color: var(--ink); }
@media (max-width: 640px) { .target-chip { flex-basis: 100%; justify-content: center; text-align: center; flex-wrap: wrap; min-height: 0; } } /* own row on phones */

/* Text for screen readers only */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
