@import url('https://fonts.googleapis.com/css2?family=Gorditas:wght@400;700&family=Inter:wght@400;500;600;700;800&display=swap');

:root {
    /* Type scale. Every font-size on the site is one of these.
       All reading text is 16px: body copy, hero ledes, card descriptions
       and notes alike. Only chrome (labels, buttons, code) goes smaller,
       and only headings go larger. Whole pixels throughout. */
    --fs-label: 12px;                         /* uppercase micro labels */
    --fs-ui: 14px;                            /* buttons, nav, meta, code */
    --fs-body: 16px;                          /* all reading text */
    --fs-h3: 22px;
    --fs-h2-sm: clamp(30px, 2.6vw, 39px);
    --fs-h2: clamp(34px, 3.3vw, 54px);
    --fs-h1: clamp(52px, 5.4vw, 88px);
    --fs-h1-hero: clamp(60px, 5.5vw, 92px);

    --cloud: #f3f1ed;
    --paper: #fbfaf8;
    --pearl: #eeeaf1;
    --lavender: #d3ccd8;
    --lavender-mid: #b6b1c3;
    --violet: #565167;
    --violet-deep: #312b55;
    --muted: #6f6a7c;
    --blue: #5fa5e1;
    --blue-deep: #267fc8;
    --orange: #a85d2f;
    --orange-deep: #5f240a;
    --gold: #e3b958;
    --green: #758d52;
    --danger: #bb4a34;
    --line: rgba(86, 81, 103, .2);
    --line-soft: rgba(86, 81, 103, .1);
    --shadow: 0 18px 50px rgba(66, 59, 87, .13);
    --shadow-soft: 0 9px 24px rgba(66, 59, 87, .1);
    --display: "Gorditas", Georgia, serif;
    --body: "Inter", system-ui, sans-serif;
    --shell: min(1280px, calc(100% - 56px));
    /* The header panel's height, and the height the logo occupies in its
       layout - the logo's canvas is drawn taller than this and allowed to
       overflow, so this is what sets how far it stands proud of the panel. */
    --header-height: 60px;
    /* How much of the top of the window the sticky header covers: its own
       padding plus the panel's height. Anchor targets clear this much before
       they are considered to be in view, otherwise the browser scrolls a
       heading to y=0 and puts it behind the header. Kept beside the height it
       is derived from, and restated at each width where either part changes. */
    --header-offset: 74px;
    /* How dark the logo's drop shadow reads. The one number to turn: the
       contact shadow beneath it is derived from this rather than set on its
       own, so the two stay in proportion and cannot drift apart. Zero removes
       the shadow without removing the filter. */
    --logo-shadow-opacity: .8;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* Every anchor target inside the page proper, landing two root ems past the
   header so it arrives with a little air above it rather than flush against
   the panel.
   
   rem and not em: em resolves against the target's own font size, so a 2.45rem
   heading was landing 78px below the header while a plain section landed 32px
   below it. Targets stopped at different heights depending on how large their
   text happened to be, and the contents panel - which measures one fixed line -
   marked the heading above the one that had just been jumped to. The header
   itself is outside main and is deliberately not included. */
main [id] { scroll-margin-top: calc(var(--header-offset) + 2rem); }
/* Takes the slack, and never shrinks below its own content. */
#main-content { flex: 1 0 auto; }
.site-footer { flex-shrink: 0; }
/* The page is a column the height of the window at minimum, with main taking
   up whatever slack is left. Without this the footer sits directly under the
   content, which on a short page - a two paragraph news post, an empty
   downloads list - leaves it stranded in the middle of the window with the
   background showing beneath it.

   dvh after vh: the dynamic unit accounts for a phone browser's collapsing
   address bar, and browsers that do not know it keep the vh value above. */
body {
    min-width: 320px;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    margin: 0;
    color: var(--violet-deep);
    background:
        radial-gradient(circle at 85% 3%, rgba(116, 141, 155, .18), transparent 29rem),
        radial-gradient(circle at 14% 18%, rgba(227, 208, 172, .32), transparent 34rem),
        var(--cloud);
    font-family: var(--body);
    font-size: 16px;
    line-height: 1.62;
    overflow-x: hidden;
}
body::before {
    position: fixed;
    inset: 0;
    z-index: -1;
    content: "";
    pointer-events: none;
    background-image:
        linear-gradient(var(--line-soft) 1px, transparent 1px),
        linear-gradient(90deg, var(--line-soft) 1px, transparent 1px);
    background-size: 46px 46px;
    opacity: .35;
    mask-image: linear-gradient(to bottom, black, transparent 62%);
}
a { color: inherit; }
img, video { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; }
h1, h2, h3, p { margin-top: 0; }
h1, h2, h3 { font-family: var(--display); line-height: 1.08; letter-spacing: -.035em; }
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
p { color: var(--muted); }
.section-shell { width: var(--shell); margin-inline: auto; }
.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;
}
.skip-link {
    position: fixed;
    top: 12px;
    left: 12px;
    z-index: 100;
    padding: 10px 16px;
    border-radius: 10px;
    color: white;
    background: var(--violet-deep);
    transform: translateY(-150%);
}
.skip-link:focus { transform: translateY(0); }

/* Shared header */
.site-header {
    position: sticky;
    top: 0;
    z-index: 30;
    padding: 14px 0 0;
}
.header-inner {
    width: var(--shell);
    min-height: var(--header-height);
    margin: auto;
    padding: 0 24px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    /* Without this the brand refuses to give up any of its width and pushes
       the navigation out of the panel instead. */
    min-width: 0;
    border: 1px solid rgba(255,255,255,.8);
    border-radius: 25px;
    /* Two layers: the banner pinned to the top left corner and drawn once, over
       the tint that still has to cover the rest of the panel. The colour sits on
       the last layer, which is the only one a multi-layer background may carry
       one on. */
    background: url("/webgl/banner.png") no-repeat left top, rgba(251,250,248,.88);
    box-shadow: 0 11px 30px rgba(73,67,92,.12);
    backdrop-filter: blur(18px);
}
.brand {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--violet-deep);
    text-decoration: none;
    white-space: nowrap;
}
.brand-logo {
    position: relative;
    display: none;
    flex: 0 0 auto;
}
.brand-logo.ready {
    display: block;
    /* A hundred narrower than the canvas it holds.
       The canvas is offset left: -100px and drawn at this width plus that
       hundred, so the right-hand hundred pixels of this box never had anything
       in them - it was reserved space with a picture nowhere near it. Because
       this element is flex: 0 0 auto, the header could not reclaim it either,
       and the navigation was pushed onto a second row while the gap sat there
       empty. The logo is drawn at exactly the size it was; only the box it
       claims has changed. */
    width: clamp(216px, calc(38vw - 100px), 346px);
    /* Only as tall as the header, though the canvas inside is drawn at the
       logo's full 3.761 aspect. Giving the element the aspect ratio instead
       would make it the tallest thing in the header and the panel would grow
       to contain it - which is exactly what used to happen. */
    height: var(--header-height);
}
.brand-logo canvas {
    position: absolute;
    top: calc(50% + 6px);
    left: -100px;
    display: block;
    /* The hundred the element gave up, taken back here so the drawing keeps
       its former width and the offset still lands it in the same place. */
    width: calc(100% + 100px);
    /* Taken out of the flow and centred on the short element, so it stands
       equally proud above and below the panel rather than hanging off one
       edge. Nothing in the header chain clips, so the overflow shows. */
    height: auto;
    aspect-ratio: 3.761;
    transform: translateY(-50%);
    /* The canvas clears to transparent, so this blurs the wordmark's own
       silhouette rather than its bounding box - the marble included. Two
       shadows rather than one: a wide soft cast that lifts the whole logo off
       the panel, and a tight dark one just beneath it that reads as contact.
       Applied here rather than drawn in the scene because the alpha the
       browser already has is exactly what a shadow pass would have to render
       for itself. */
    filter:
        drop-shadow(0 7px 12px rgba(49,43,85, var(--logo-shadow-opacity)))
        drop-shadow(0 2px 3px rgba(49,43,85, calc(var(--logo-shadow-opacity) * .65)));
    /* The canvas is drawn about 49px taller than the element holding it and
       hangs below the header on purpose. That overflow lands on top of
       whatever follows - the breadcrumb and the editor bar - and was eating
       their clicks. The logo is decoration, so it takes itself out of hit
       testing; .brand-logo still fills the header band inside the brand
       anchor, so clicking the wordmark in the header still goes home. */
    pointer-events: none;
}
.brand-logo.ready ~ .brand-word {
    display: none;
}
.brand-word {
    font: 700 clamp(1.45rem, 2.2vw, 2.15rem)/1 var(--display);
    letter-spacing: -.055em;
    transform: rotate(-2deg);
    text-shadow: 0 2px 0 #d8d1e1, 0 4px 0 rgba(49,43,85,.14);
}
.brand-orb {
    width: 28px;
    height: 28px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: radial-gradient(circle at 32% 25%, #f7bb82 0 7%, #d3783e 12%, var(--orange) 42%, var(--orange-deep) 100%);
    box-shadow: inset -6px -7px 10px rgba(64,20,4,.3), 0 4px 8px rgba(95,36,10,.22);
}
.site-nav { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: clamp(12px, 1.6vw, 28px); }
.site-nav a {
    position: relative;
    padding: 13px 0;
    white-space: nowrap;
    color: var(--violet);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .085em;
    text-decoration: none;
    text-transform: uppercase;
}
.site-nav a:not(.account-link)::after {
    position: absolute;
    right: 0;
    bottom: 5px;
    left: 0;
    height: 3px;
    content: "";
    border-radius: 2px;
    background: var(--blue);
    transform: scaleX(0);
    transition: transform .2s ease;
}
.site-nav a:hover::after, .site-nav a.active::after { transform: scaleX(1); }
.site-nav a.active { color: var(--violet-deep); }
.site-nav .account-link {
    padding: 12px 17px;
    border: 1px solid var(--line);
    border-radius: 13px;
    background: rgba(255,255,255,.62);
    box-shadow: 0 4px 10px rgba(66,59,87,.08);
}
.site-nav .account-link:hover { border-color: var(--blue); color: var(--blue-deep); }
/* The first tab. An icon rather than a word because "Home" beside "Game" reads
   as two destinations of equal weight, which it is not - one is the way back. */
.site-nav .home-link { padding-right: 2px; display: inline-flex; align-items: center; }
.site-nav .home-link svg {
    width: 19px;
    height: 19px;
    display: block;
    fill: none;
    /* currentColor, so it takes the nav's own colour and its hover and active
       states without repeating any of them. */
    stroke: currentColor;
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.admin-link { display: none; }
.is-admin .admin-link { display: inline-block; }
/* Shown only to someone who has somewhere to go. The body carries the signed
   in state, so this needs nothing from the page it appears on - and without
   it the link would send an anonymous visitor to a page that only bounces
   them to sign in. */
.account-only { display: none; }
.authenticated .account-only { display: inline-block; }
.nav-toggle { display: none; }

/* Shared controls and headings */
.eyebrow {
    margin-bottom: 14px;
    color: var(--orange);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .17em;
    text-transform: uppercase;
}
.status-pill, .media-badge {
    width: max-content;
    padding: 7px 15px;
    border-radius: 999px;
    color: white;
    background: #7467ad;
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
}
.button-row, .button-stack { display: flex; flex-wrap: wrap; gap: 12px; }
.button-stack { flex-direction: column; max-width: 360px; }
.button {
    min-height: 52px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 15px;
    padding: 13px 22px;
    border: 1px solid transparent;
    border-radius: 13px;
    font-size: var(--fs-ui);
    font-weight: 800;
    letter-spacing: .075em;
    text-decoration: none;
    text-transform: uppercase;
    cursor: pointer;
    transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}
.button:hover { transform: translateY(-2px); }
.button-primary {
    color: white;
    border-color: #3089cf;
    background: linear-gradient(#78baf0 0%, var(--blue) 45%, var(--blue-deep) 100%);
    box-shadow: inset 0 2px 0 rgba(255,255,255,.46), 0 7px 0 #1c6fae, 0 13px 22px rgba(38,127,200,.22);
}
.button-primary:hover { box-shadow: inset 0 2px 0 rgba(255,255,255,.52), 0 5px 0 #1c6fae, 0 13px 27px rgba(38,127,200,.32); }
.button-secondary {
    color: var(--violet-deep);
    border-color: #a99bd5;
    background: linear-gradient(#fff, #f2eef5);
    box-shadow: inset 0 2px 0 #fff, 0 5px 0 #b9afd2, 0 11px 18px rgba(66,59,87,.1);
}
.button-secondary:hover { border-color: var(--blue); }
.button-disabled { opacity: .48; cursor: not-allowed; }
.button-disabled:hover { transform: none; }
.play-symbol {
    width: 0;
    height: 0;
    border-top: 7px solid transparent;
    border-bottom: 7px solid transparent;
    border-left: 11px solid white;
    filter: drop-shadow(0 2px 1px rgba(24,74,113,.3));
}
.text-link {
    display: inline-flex;
    align-items: center;
    gap: 13px;
    color: var(--blue-deep);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .075em;
    text-decoration: none;
    text-transform: uppercase;
}
.text-link.muted { color: var(--muted); }
.section-heading { max-width: 760px; margin-bottom: 46px; }
.section-heading h2 { margin-bottom: 20px; }
.compact-heading { margin-bottom: 34px; }

/* Home */
.home-hero {
    min-height: min(640px, calc(100vh - 96px));
    padding: 32px 0 48px;
    display: grid;
    grid-template-columns: 1.65fr .85fr;
    align-items: center;
    gap: clamp(28px, 4vw, 60px);
}
.hero-media {
    position: relative;
    overflow: hidden;
    min-height: 460px;
    border: 7px solid rgba(255,255,255,.88);
    border-radius: 29px;
    background: var(--lavender);
    box-shadow: var(--shadow);
}
.hero-media::after {
    position: absolute;
    inset: 0;
    content: "";
    pointer-events: none;
    background: linear-gradient(to top, rgba(49,43,85,.16), transparent 36%);
}
.hero-media img { width: 100%; height: 100%; min-height: 460px; object-fit: cover; object-position: center; }
.media-badge { position: absolute; top: 18px; left: 18px; z-index: 2; background: rgba(49,43,85,.78); backdrop-filter: blur(8px); }
.media-dots { position: absolute; bottom: 20px; left: 50%; z-index: 2; display: flex; gap: 7px; transform: translateX(-50%); }
.media-dots i { width: 9px; height: 9px; border: 2px solid white; border-radius: 50%; box-shadow: 0 2px 4px rgba(49,43,85,.25); }
.media-dots i:first-child { background: white; }
.hero-copy h1 { margin: 17px 0 20px; color: var(--violet-deep); font-size: var(--fs-h1-hero); text-transform: uppercase; }
.hero-copy h1 span { color: var(--orange); text-shadow: 0 5px 0 rgba(95,36,10,.12); }
.hero-lede { max-width: 510px; margin-bottom: 30px; font-size: var(--fs-body); font-weight: 600; }

.builder-section {
    display: grid;
    /* Two columns rather than three. The copy takes the wider one and runs the
       full height beside the pair of cards, which stack in the narrow column -
       three across left the text in a channel too narrow to read comfortably
       once it grew past a couple of sentences. */
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    /* Two rows, declared. Without them the grid has no explicit rows at all,
       so "grid-row: 1 / -1" on the copy resolves to a span of nothing and the
       second card wraps under the paragraphs instead of stacking beside them.
       Equal fractions rather than auto, so the two cards come out the same
       height instead of the first taking whatever the copy's height left over
       and the second getting the remainder. */
    grid-template-rows: 1fr 1fr;
    gap: 16px;
    margin-bottom: 70px;
    padding: 17px;
    border: 1px solid rgba(255,255,255,.75);
    border-radius: 27px;
    background: rgba(238,234,241,.78);
    box-shadow: var(--shadow);
}
.builder-intro {
    /* Held in the first column across both rows, so the pair of cards is
       placed beside the copy rather than flowing around it. */
    grid-column: 1;
    grid-row: 1 / -1;
    padding: clamp(28px, 4vw, 54px);
}
/* A floor rather than a height. The rows share the copy's height between them,
   so a card is as tall as half the column; this only stops the pair collapsing
   when the copy is short. */
.builder-section .learning-card { min-height: 178px; }
.builder-intro h2 { margin-bottom: 17px; font-size: var(--fs-h2); }
.learning-card {
    position: relative;
    min-height: 325px;
    overflow: hidden;
    padding: 29px;
    display: flex;
    flex-direction: column;
    /* Content follows the image rather than being pushed to the bottom of the
       card. Bottom alignment made the gap under the image depend on how long
       each description happened to be, so the two kickers never lined up. */
    justify-content: flex-start;
    border: 1px solid rgba(86,81,103,.16);
    border-radius: 19px;
    color: var(--violet-deep);
    background: rgba(255,255,255,.75);
    box-shadow: var(--shadow-soft);
    text-decoration: none;
    transition: transform .2s ease, box-shadow .2s ease;
}
.learning-card::before {
    position: absolute;
    inset: 0 0 44%;
    content: none;
    opacity: .55;
    background:
        linear-gradient(rgba(86,81,103,.18) 1px, transparent 1px),
        linear-gradient(90deg, rgba(86,81,103,.18) 1px, transparent 1px),
        linear-gradient(145deg, #fdfcfa, #d9d3e1);
    background-size: 28px 28px, 28px 28px, auto;
}
/* In the flow rather than absolutely placed, with negative margins reaching
   back through the card's own padding to the edges. That makes the space under
   it an ordinary margin - one em, the same on both cards, whatever the card is
   stretched to and however long the text below it runs. */
.learning-card-image {
    width: calc(100% + 58px);
    height: 140px;
    margin: -29px -29px 1em;
    flex: 0 0 auto;
    object-fit: cover;
}
.learning-card:hover { transform: translateY(-5px); box-shadow: 0 18px 35px rgba(66,59,87,.17); }
.learning-card .learning-icon { position: absolute; top: 43px; left: 50%; z-index: 1; transform: translateX(-50%) rotate(-5deg); }
.learning-card .card-kicker { position: relative; color: var(--orange); font-size: var(--fs-label); font-weight: 800; letter-spacing: .13em; text-transform: uppercase; }
.learning-card strong { position: relative; margin: 6px 0 9px; font: 700 1.6rem/1.1 var(--display); }
.learning-card > span:last-of-type { position: relative; color: var(--muted); font-size: var(--fs-body); }
.learning-card b { position: absolute; right: 24px; bottom: 20px; color: var(--blue-deep); }
.learning-icon { width: 62px; height: 62px; display: grid; place-items: center; }
.cube-icon { position: relative; border: 3px solid var(--violet-deep); transform: rotate(30deg); }
.cube-icon::before, .cube-icon::after { position: absolute; content: ""; background: var(--violet-deep); }
.cube-icon::before { top: 50%; left: 0; width: 100%; height: 3px; }
.cube-icon::after { top: 0; left: 50%; width: 3px; height: 100%; }
.ts-icon { border-radius: 14px; color: white; background: linear-gradient(145deg, #73b8ee, var(--blue-deep)); box-shadow: inset 0 2px 0 rgba(255,255,255,.4), 0 7px 0 #1c6fae; font: 800 1.25rem/1 var(--body); }

.discover-section {
    display: grid;
    grid-template-columns: .8fr 1.2fr;
    gap: 38px;
    align-items: center;
    margin-bottom: 70px;
    padding: clamp(32px, 5vw, 67px);
    border: 1px solid var(--line);
    border-radius: 28px;
    background: rgba(251,250,248,.9);
    box-shadow: var(--shadow-soft);
}
.discover-copy h2 { margin-bottom: 18px; }
.discover-copy .button { margin-top: 15px; }
.discover-preview { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.discover-preview article {
    min-height: 185px;
    padding: 23px 14px;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* A declared gap under the image rather than whatever height the tile has
       left over, so every label sits the same distance below its picture. */
    justify-content: flex-start;
    gap: 1em;
    border: 1px solid var(--line);
    border-radius: 17px;
    background:
        linear-gradient(rgba(86,81,103,.1) 1px, transparent 1px),
        linear-gradient(90deg, rgba(86,81,103,.1) 1px, transparent 1px), #f8f6f8;
    background-size: 24px 24px;
    text-align: center;
}
.discover-preview strong { font: 700 .9rem/1.2 var(--display); text-transform: uppercase; }
.discover-preview img { width: 100%; height: 118px; border-radius: 11px; object-fit: cover; }
.object-token { position: relative; width: 74px; height: 74px; display: block; margin: auto; }
.enemy-token { width: 0; height: 0; margin-top: 18px; border-right: 38px solid transparent; border-bottom: 68px solid #522619; border-left: 38px solid transparent; filter: drop-shadow(0 8px 4px rgba(64,30,20,.25)); }
.enemy-token::after { position: absolute; top: 33px; left: -9px; width: 18px; height: 18px; content: ""; border-radius: 50%; background: #f14f26; box-shadow: 0 0 16px #f14f26; }
.doodad-token { border: 4px solid #7c6c4c; border-radius: 9px; background: #d9ad51; box-shadow: inset 0 0 0 7px #ae7133, 0 8px 8px rgba(75,56,30,.18); transform: rotate(-5deg); }
.doodad-token::after { position: absolute; inset: 13px; content: "?"; display: grid; place-items: center; color: white; font: 700 2rem/1 var(--display); text-shadow: 0 2px 0 #7c4c22; }
.hidden-token { overflow: hidden; border: 4px solid var(--violet); border-radius: 10px; background: repeating-linear-gradient(90deg, #ebe7ed 0 13px, #c9c1d0 13px 17px); box-shadow: 0 8px 8px rgba(66,59,87,.16); }
.hidden-token::after { position: absolute; right: 12px; bottom: -2px; width: 29px; height: 50px; content: ""; border-radius: 50% 50% 0 0; background: var(--violet-deep); }
.terrain-token { background: linear-gradient(145deg, #dad5df, #a9a2b6); box-shadow: inset -6px -7px 0 rgba(86,81,103,.18), 0 8px 8px rgba(66,59,87,.15); }
.terrain-token::before, .terrain-token::after { position: absolute; content: ""; background: var(--cloud); transform: rotate(35deg); }
.terrain-token::before { top: 4px; left: 35px; width: 5px; height: 68px; }
.terrain-token::after { top: 30px; left: 9px; width: 55px; height: 5px; }
.power-token { border-radius: 50%; background: radial-gradient(circle at 30% 25%, #fff2a4, var(--gold) 35%, #b27722 100%); box-shadow: 0 0 20px rgba(227,185,88,.55), 0 8px 8px rgba(97,69,20,.16); }
.power-token::after { position: absolute; inset: 20px 29px; content: ""; background: white; transform: skew(-15deg); box-shadow: -9px 21px 0 -2px white; }
.switch-token { border: 5px solid #8f859d; border-radius: 14px; background: #d9d4df; box-shadow: inset 0 -8px 0 #aca5b6, 0 8px 8px rgba(66,59,87,.16); }
.switch-token::after { position: absolute; top: 13px; left: 27px; width: 16px; height: 37px; content: ""; border-radius: 8px; background: var(--blue); transform: rotate(-22deg); box-shadow: 0 3px 0 var(--blue-deep); }
.platform-token { height: 27px; margin-top: 25px; border-radius: 7px; background: #ded9e2; box-shadow: inset 0 -8px 0 #8e879b, 0 14px 9px rgba(66,59,87,.2); }
.wall-token { border-radius: 6px; background: repeating-linear-gradient(90deg, #e7e3e9 0 18px, #aaa3b5 18px 21px); box-shadow: inset 0 -8px 0 #8f879e, 0 8px 8px rgba(66,59,87,.17); }

.gameplay-section { margin-bottom: 70px; }
.video-frame { overflow: hidden; padding: 8px; border: 1px solid rgba(255,255,255,.85); border-radius: 28px; background: white; box-shadow: var(--shadow); }
.video-frame video { width: 100%; aspect-ratio: 16 / 9; border-radius: 21px; background: var(--violet-deep); }
.home-next, .path-cta, .cta-panel, .download-panel, .newsletter {
    margin-bottom: 70px;
    padding: clamp(34px, 5vw, 66px);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 40px;
    border: 1px solid rgba(255,255,255,.8);
    border-radius: 27px;
    background: linear-gradient(120deg, rgba(211,204,216,.92), rgba(251,250,248,.92));
    box-shadow: var(--shadow-soft);
}
.home-next h2, .path-cta h2, .cta-panel h2, .download-panel h2, .newsletter h2 { margin-bottom: 10px; }
.home-next p:last-child, .path-cta p:last-child { margin-bottom: 0; }

/* Discover and general page mast */
.page-mast {
    /*padding: 58px 0 66px;*/
    display: grid;
    grid-template-columns: .85fr 1.15fr;
    align-items: center;
    gap: clamp(40px, 7vw, 100px);
}
.page-mast-copy h1 { margin-bottom: 10px; font-size: var(--fs-h1); }
.page-mast-copy > p:not(.eyebrow) { font-size: var(--fs-body); }
.page-mast-media { overflow: hidden; margin: 0; padding: 7px; border-radius: 27px; background: rgba(255,255,255,.86); box-shadow: var(--shadow); transform: rotate(1.2deg); }
.page-mast-media img { width: 100%; aspect-ratio: 16 / 10; border-radius: 20px; object-fit: cover; }
.catalog-section { padding-bottom: 72px; }
.catalog-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
.catalog-card { min-height: 270px; padding: 25px; border: 1px solid var(--line); border-radius: 19px; background: rgba(251,250,248,.88); box-shadow: var(--shadow-soft); }
.catalog-card .object-token { margin: 5px 0 33px; transform: scale(.75); transform-origin: left center; }
.catalog-card .enemy-token { margin-left: 0; }
.catalog-card h3 { margin-bottom: 10px; }
.catalog-card p { margin-bottom: 0; font-size: var(--fs-body); }

/* Learn */
.learn-hero {
    display: grid;
    grid-template-columns: .9fr 1.1fr;
    align-items: center;
    gap: 7vw;
}
.learn-hero h1 { margin-bottom: 10px; }
.learn-hero-copy > p { max-width: 630px; font-size: var(--fs-body); }
.learn-diagram {
    min-height: 270px;
    padding: 36px;
    display: grid;
    grid-template-columns: 1fr auto 1fr auto 1fr;
    align-items: center;
    gap: 17px;
    border: 1px solid var(--line);
    border-radius: 28px;
    background: rgba(251,250,248,.86);
    box-shadow: var(--shadow);
}
.learn-diagram > div { min-height: 190px; padding: 22px 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: 18px; background: var(--pearl); text-align: center; }
.learn-diagram .learning-icon { margin-bottom: 24px; transform: scale(.72); }
.learn-diagram strong { font: 700 1.2rem/1 var(--display); }
.learn-diagram small { margin-top: 7px; color: var(--muted); }
.learn-diagram > i { color: var(--orange); font: 700 2rem/1 var(--display); }
.world-icon { border-radius: 50%; background: radial-gradient(circle at 32% 25%, #f4b777, var(--orange) 40%, var(--orange-deep)); box-shadow: inset -7px -8px 10px rgba(64,20,4,.28), 0 7px 10px rgba(95,36,10,.2); }
.learning-paths { padding: 15px 0 72px; display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.learning-path {
    /* Named so the image can reach back through exactly this much padding to
       the card's edges, at whatever width the clamp resolves to. */
    --path-pad: clamp(32px, 4vw, 54px);
    position: relative;
    min-height: 445px;
    padding: var(--path-pad);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    /* Content follows the image. Bottom alignment left the space under the
       image as whatever the copy did not use, so the two cards' headings sat
       at different heights and the shorter one ran up against its picture. */
    justify-content: flex-start;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: 27px;
    color: var(--violet-deep);
    background: rgba(251,250,248,.9);
    box-shadow: var(--shadow);
    text-decoration: none;
    transition: transform .2s ease;
}
/* The grid behind the image, now sized to it rather than to a share of the card. */
.learning-path::before { position: absolute; inset: 0 0 auto; height: 190px; content: ""; background: linear-gradient(var(--line-soft) 1px, transparent 1px), linear-gradient(90deg, var(--line-soft) 1px, transparent 1px), var(--pearl); background-size: 34px 34px; }
.learning-path-image {
    z-index: 1;
    width: calc(100% + var(--path-pad) * 2);
    height: 190px;
    margin: calc(var(--path-pad) * -1);
    margin-bottom: 1em;
    flex: 0 0 auto;
    object-fit: cover;
}
.learning-path:hover { transform: translateY(-5px); }
.learning-path .learning-icon { position: absolute; top: 90px; left: 50%; z-index: 1; transform: translateX(-50%) scale(1.35) rotate(5deg); }
.learning-path .ts-icon { transform: translateX(-50%) scale(1.35); }
.path-number { position: absolute; top: 28px; left: 31px; z-index: 1; color: var(--lavender-mid); font: 700 1rem/1 var(--display); }
.learning-path h2 { margin: 5px 0 15px; font-size: var(--fs-h2); }
.learning-path > p { max-width: 600px; }
.workflow-section { margin-bottom: 72px; padding: clamp(30px, 4vw, 50px); border: 1px solid var(--line); border-radius: 27px; background: rgba(238,234,241,.85); box-shadow: var(--shadow-soft); }
.workflow-list { margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, 1fr); list-style: none; }
.workflow-list li { padding: 25px; display: flex; gap: 16px; border-left: 1px solid var(--line); }
.workflow-list li:first-child { border-left: 0; }
.workflow-list li > span { width: 30px; height: 30px; flex: 0 0 auto; display: grid; place-items: center; border-radius: 50%; color: white; background: var(--blue); font-weight: 800; }
.workflow-list strong { font: 700 1.2rem/1.2 var(--display); }
.workflow-list p { margin: 7px 0 0; font-size: var(--fs-body); }

/* Detailed learning topics padding: 62px 0 72px */
.topic-hero { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: 7vw; }
.topic-heading h1 { margin: 0 0 10px; font-size: var(--fs-h1); }
.topic-heading > p:not(.eyebrow) { max-width: 660px; font-size: var(--fs-body); }
.topic-visual { position: relative; overflow: hidden; border: 1px solid rgba(255,255,255,.9); border-radius: 28px; background: var(--pearl); box-shadow: var(--shadow); }
.cad-grid { position: absolute; inset: 0; background: linear-gradient(rgba(86,81,103,.15) 1px, transparent 1px), linear-gradient(90deg, rgba(86,81,103,.15) 1px, transparent 1px); background-size: 36px 36px; transform: perspective(500px) rotateX(55deg) scale(1.4); }
.cad-arch { position: absolute; top: 77px; left: 50%; width: 220px; height: 250px; border: 26px solid #aaa3b9; border-bottom: 0; border-radius: 115px 115px 0 0; transform: translateX(-50%); box-shadow: inset 10px 8px 0 rgba(255,255,255,.3), 14px 18px 0 rgba(86,81,103,.13); }
.cad-visual > i { position: absolute; width: 14px; height: 14px; border: 3px solid white; border-radius: 50%; background: var(--blue); box-shadow: 0 2px 5px rgba(38,127,200,.3); }
.cad-visual > i:nth-of-type(1) { top: 97px; left: 37%; }.cad-visual > i:nth-of-type(2) { top: 97px; right: 37%; }.cad-visual > i:nth-of-type(3) { bottom: 100px; left: 31%; }.cad-visual > i:nth-of-type(4) { right: 31%; bottom: 100px; }
.code-visual { color: #eeeaf7; background: #27233e; }
.code-bar { height: 55px; padding: 0 18px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid rgba(255,255,255,.11); background: #312c4b; }
.code-bar i { width: 10px; height: 10px; border-radius: 50%; background: #dd725e; }.code-bar i:nth-child(2) { background: var(--gold); }.code-bar i:nth-child(3) { background: #7ca56d; }
.code-bar span { margin-left: 13px; color: #bdb5cd; font-size: var(--fs-ui); }
.code-visual pre { margin: 0; padding: 72px 42px; overflow: auto; font: 500 clamp(.84rem, 1.3vw, 1.08rem)/1.8 ui-monospace, SFMono-Regular, Consolas, monospace; }
.code-purple { color: #c4a7e7; }.code-blue { color: #73bdf2; }
.lesson-layout { padding-bottom: 56px; display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 7vw; align-items: start; }
/* A grid or flex item defaults to min-width: auto, which refuses to shrink
   below its widest unbreakable content. One long line in a code sample would
   therefore push this column - and the page with it - past the right edge of
   the window instead of scrolling inside the sample. Every level between the
   grid and the pre has to allow shrinking for the pre's own overflow-x to be
   the thing that gives. */
.lesson-content { min-width: 0; }
.lesson-block { min-width: 0; }
.lesson-index { position: sticky; top: 125px; padding: 25px; border: 1px solid var(--line); border-radius: 17px; background: rgba(251,250,248,.78); }
.lesson-index a { position: relative; display: block; padding: 11px 0 11px 13px; border-bottom: 1px solid var(--line-soft); color: var(--muted); font-size: var(--fs-ui); font-weight: 700; text-decoration: none; transition: color .16s ease; }
.lesson-index a:hover { color: var(--blue-deep); }
/* The marker is drawn on every entry and revealed on the current one, so the
   bar fades rather than appearing from nowhere, and no entry shifts sideways
   as the reading position moves down the page. */
.lesson-index a::before {
    position: absolute;
    top: 8px;
    bottom: 8px;
    left: 0;
    width: 3px;
    content: "";
    border-radius: 2px;
    background: var(--blue);
    opacity: 0;
    transition: opacity .16s ease;
}
.lesson-index a.current { color: var(--violet-deep); }
.lesson-index a.current::before { opacity: 1; }
.lesson-index .index-group { padding-left: 13px; }
.lesson-index a:last-child { border-bottom: 0; }
/* Sections are separated by space alone. The rule that used to sit between
   them, with 52px of padding above it and 52px of margin below, put 104px
   and a line between one section and the next and made the page read as a
   stack of unrelated pages. */
.lesson-block { min-width: 0; margin-bottom: 38px; display: flow-root; }
.lesson-block:last-child { margin-bottom: 0; }
.lesson-block h2 { max-width: 830px; margin-bottom: 14px; }
.lesson-block > p { max-width: 830px; font-size: var(--fs-body); }
.lesson-block .button { margin-top: 20px; }

/* A short silent clip used as an illustration beside body text, with a
   caption under it. Fixed at 300x200 so a page can drop one in without
   measuring anything, and floated so the paragraphs run up the left of it
   rather than starting below it. The section it sits in is display:
   flow-root, which contains the float so it cannot bleed into the next one.

   The clip carries its address in data-src rather than src: nothing is
   fetched until the reader scrolls it into view. loading="lazy" is no help
   here, being honoured for img and iframe but not for video. */
.video-thumb {
    float: right;
    width: 300px;
    margin: 0 0 2em 2em;
}
.video-thumb video {
    display: block;
    width: 300px;
    height: 200px;
    border-radius: 14px;
    background: var(--violet-deep);
    box-shadow: var(--shadow);
    object-fit: cover;
}
/* Matches the caption under a .demo-media figure, which is the other place
   the site captions a picture. */
.video-thumb figcaption {
    margin-top: 10px;
    color: var(--muted);
    font-size: var(--fs-ui);
    font-style: italic;
    line-height: 1.45;
    text-align: center;
}
.lesson-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-top: 38px; }
.lesson-cards article { min-height: 235px; padding: 26px; border: 1px solid var(--line); border-radius: 17px; background: rgba(251,250,248,.76); }
.lesson-cards span { color: var(--orange); font-size: var(--fs-label); font-weight: 800; letter-spacing: .1em; }
.lesson-cards h3 { margin: 30px 0 10px; }
.lesson-cards p { margin: 0; font-size: var(--fs-body); }

/* Existing supporting pages */
.page-hero { padding: 10px 0 28px; }
.page-hero h1 { max-width: 970px; margin-bottom: 10px; }
.page-hero > p:not(.eyebrow):not(.lead-note) { max-width: 720px; font-size: var(--fs-body); }
.about-layout { padding: 65px 0; display: grid; grid-template-columns: .9fr 1.1fr; gap: 7vw; align-items: center; }
.about-statement { min-height: 440px; padding: 50px; display: flex; flex-direction: column; justify-content: space-between; border: 1px solid var(--line); border-radius: 24px; background: linear-gradient(145deg, #fdfcfa, var(--lavender)); box-shadow: var(--shadow); }
.about-statement img { width: 100%; margin-bottom: 28px; aspect-ratio: 16 / 9; border-radius: 16px; object-fit: cover; }
.statement-mark { color: var(--orange); font: 700 4rem/1 var(--display); }
blockquote { margin: 0; color: var(--violet-deep); font: 600 clamp(1.7rem, 2.6vw, 2.65rem)/1.16 var(--display); }
.prose h2 { margin-bottom: 25px; }
.prose p { font-size: var(--fs-body); }
.technology-band { padding: 65px 0; border-block: 1px solid var(--line); background: rgba(211,204,216,.55); }
.technology-grid { display: grid; grid-template-columns: repeat(4, 1fr); }
.technology-grid article { padding: 28px; border-left: 1px solid var(--line); }
.technology-grid article:last-child { border-right: 1px solid var(--line); }
.technology-grid article > span { color: var(--orange); font-size: var(--fs-label); font-weight: 800; }
.technology-grid h3 { margin: 28px 0 12px; }
.technology-grid p { font-size: var(--fs-body); }
.technology-band + .cta-panel { margin-top: 68px; }
.article-grid { padding: 58px 0 72px; display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.article-card { overflow: hidden; border: 1px solid var(--line); border-radius: 20px; background: rgba(251,250,248,.9); box-shadow: var(--shadow-soft); }
.article-card.featured { grid-column: 1 / -1; display: grid; grid-template-columns: 1.2fr .8fr; }
.article-image { min-height: 260px; display: grid; place-items: center; color: var(--violet); background: var(--lavender); font-size: var(--fs-label); font-weight: 800; letter-spacing: .1em; }
.featured .article-image { min-height: 430px; }
.gradient-one, .gradient-two, .gradient-three { background: linear-gradient(145deg, var(--paper), var(--lavender)); }
.article-copy { padding: 34px; }
.article-copy h2 { font-size: var(--fs-h2); }
.article-meta { display: flex; justify-content: space-between; color: var(--orange); font-size: var(--fs-label); font-weight: 800; letter-spacing: .1em; text-transform: uppercase; }
.article-meta span { color: var(--muted); }
.community-grid, .admin-grid { padding: 58px 0 72px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.community-card, .admin-card { position: relative; min-height: 315px; padding: 35px; border: 1px solid var(--line); border-radius: 20px; background: rgba(251,250,248,.88); box-shadow: var(--shadow-soft); }
.community-icon { width: 56px; height: 56px; margin-bottom: 38px; display: grid; place-items: center; border: 2px solid var(--blue); border-radius: 17px; color: var(--blue-deep); font: 700 1.3rem/1 var(--display); }
.community-card h2, .admin-card h2 { font-size: var(--fs-h2-sm); }
.admin-card h2 { margin-top: 34px; }
.status-light { position: absolute; top: 35px; right: 35px; width: 11px; height: 11px; border-radius: 50%; background: var(--gold); box-shadow: 0 0 13px var(--gold); }
.status-light.online { background: #63ae69; box-shadow: 0 0 13px #63ae69; }
.newsletter-form { min-width: min(470px, 100%); }
.newsletter-form label, .auth-panel label, .contact-panel label { display: block; margin-bottom: 8px; color: var(--violet); font-size: var(--fs-label); font-weight: 800; letter-spacing: .09em; text-transform: uppercase; }
.newsletter-form > div { display: flex; }
input, textarea { width: 100%; min-height: 52px; padding: 13px 16px; border: 1px solid var(--line); border-radius: 12px; outline: 0; color: var(--violet-deep); background: rgba(255,255,255,.83); }
textarea { line-height: 1.5; resize: vertical; }
input:focus, textarea:focus { border-color: var(--blue); box-shadow: 0 0 0 4px rgba(95,165,225,.16); }

/* A select is drawn by the operating system until its appearance is reset, so
   an unstyled one arrives as a Windows or GTK combo box sitting inside the
   page and matching nothing around it. Reset it, give it the same shell the
   inputs wear, and paint a chevron of our own - the native arrow goes with
   the native widget, and without a replacement there is nothing left to say
   the control opens. The right padding keeps the longest option clear of it. */
select {
    width: 100%;
    min-height: 52px;
    padding: 13px 44px 13px 16px;
    border: 1px solid var(--line);
    border-radius: 12px;
    outline: 0;
    color: var(--violet-deep);
    background-color: rgba(255,255,255,.83);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' fill='none' stroke='%23726a90' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 16px center;
    background-size: 12px 8px;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    cursor: pointer;
}
select:focus { border-color: var(--blue); box-shadow: 0 0 0 4px rgba(95,165,225,.16); }
select:hover { border-color: var(--lavender-mid); }
/* The drop-down list itself is the one part still drawn by the system, so its
   colours are set rather than left to inherit a transparent background. */
select option { color: var(--violet-deep); background: white; }
.newsletter-form input { border-radius: 12px 0 0 12px; }
.newsletter-form .button { flex: 0 0 auto; border-radius: 0 12px 12px 0; box-shadow: inset 0 2px 0 rgba(255,255,255,.46), 0 5px 0 #1c6fae; }
.form-message { min-height: 24px; margin: 12px 0 0; color: var(--blue-deep); font-size: var(--fs-body); }
.form-message.error { color: var(--danger); }
.auth-shell { min-height: 610px; margin: 48px auto 72px; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden; border: 1px solid var(--line); border-radius: 27px; background: rgba(251,250,248,.9); box-shadow: var(--shadow); }
.auth-art { position: relative; overflow: hidden; display: flex; align-items: flex-end; padding: 42px; background: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px), var(--lavender); background-size: 36px 36px; }
.auth-art::before { position: absolute; top: 50%; left: 50%; width: 190px; height: 190px; content: ""; border-radius: 50%; background: radial-gradient(circle at 30% 25%, #f6b477, var(--orange) 38%, var(--orange-deep)); transform: translate(-50%,-50%); box-shadow: inset -22px -25px 32px rgba(64,20,4,.25), 0 25px 24px rgba(66,59,87,.19); }
.auth-art > span { position: relative; z-index: 1; color: var(--violet); font-size: var(--fs-label); font-weight: 800; letter-spacing: .17em; }
.auth-orbit { display: none; }
.auth-panel { padding: clamp(38px, 5vw, 66px); display: flex; flex-direction: column; justify-content: center; }
.auth-panel h1 { margin-bottom: 15px; font-size: var(--fs-h1); }
.auth-panel form { margin-top: 24px; }
.auth-panel input { margin-bottom: 19px; }
.auth-panel .button { width: 100%; margin-top: 7px; }
.contact-shell { margin-block: 48px 72px; display: grid; grid-template-columns: .85fr 1.15fr; overflow: hidden; border: 1px solid var(--line); border-radius: 27px; background: rgba(251,250,248,.9); box-shadow: var(--shadow); }
.contact-intro { position: relative; min-height: 650px; padding: clamp(38px, 5vw, 66px); overflow: hidden; background: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px), var(--lavender); background-size: 36px 36px; }
.contact-intro h1 { margin-bottom: 24px; font-size: var(--fs-h1); }
.contact-intro > p { position: relative; z-index: 1; }
.contact-orb { position: absolute; right: -55px; bottom: -60px; width: 270px; height: 270px; border-radius: 50%; background: radial-gradient(circle at 30% 25%, #f7bb82, var(--orange) 38%, var(--orange-deep)); box-shadow: inset -28px -32px 38px rgba(64,20,4,.28), 0 28px 35px rgba(66,59,87,.2); }
.contact-panel { padding: clamp(38px, 5vw, 66px); }
.contact-panel label:not(:first-child) { margin-top: 19px; }
.contact-panel .button { width: 100%; margin-top: 18px; }
.contact-limit { margin: 7px 0 0; font-size: var(--fs-ui); text-align: right; }
.contact-trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.contact-sent { min-height: 620px; padding-block: 90px; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; }
.contact-sent-mark { width: 78px; height: 78px; margin-bottom: 31px; display: grid; place-items: center; border-radius: 50%; color: white; background: var(--blue); box-shadow: inset 0 3px 0 rgba(255,255,255,.4), 0 8px 0 #1c6fae, var(--shadow); font: 700 2.2rem/1 var(--display); }
.contact-sent h1 { max-width: 800px; margin-bottom: 18px; }
.contact-sent p:not(.eyebrow) { max-width: 620px; font-size: var(--fs-body); }
.contact-sent .button { margin-top: 18px; }
.error-page { position: relative; min-height: 670px; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; }
.error-code { position: absolute; right: 2vw; z-index: -1; color: rgba(86,81,103,.07); font: 700 clamp(12rem, 30vw, 29rem)/1 var(--display); }
.error-page h1 { margin-bottom: 17px; }

/* Footer and animation */
.site-footer {margin-top: 2em;padding: 50px 0;border-top: 1px solid var(--line);background: rgba(238,234,241,.76);}
.footer-inner { width: var(--shell); margin: auto; display: grid; grid-template-columns: 1.4fr .8fr .8fr; gap: 50px; align-items: end; }
.footer-brand { margin-left: 150px; }
.footer-inner p { margin-bottom: 0; font-size: var(--fs-ui); }
.footer-inner nav { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; }
.footer-inner nav a { color: var(--muted); font-size: var(--fs-ui); font-weight: 700; text-decoration: none; }
.footer-inner nav a:hover { color: var(--blue-deep); }
.copyright { text-align: right; }
.reveal { opacity: 1; transform: none; }
.reveal.visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .lesson-index a, .lesson-index a::before { transition: none; }
    .reveal { opacity: 1; transform: none; transition: none; }
    .button, .learning-card, .learning-path { transition: none; }
}

/* The navigation collapses on its own schedule, earlier than the page grids
   below, because the row runs out of room before the layout does. Measured
   against the longest row - nine items, with an administrator signed in -
   which stops fitting at about 1080px; 1140 leaves a margin over that. */

/* Breadcrumb */
.breadcrumb-bar { position: relative; z-index: 40; padding: 18px 0 0; }
.page-home .breadcrumb-bar { display: none; }
/* inline-flex, not inline-block: the gap below is what puts space on the
   left of each "/" separator. Under inline-block both gap and align-items
   are inert, so the separator had 6px after it and none before, and read as
   "Home/ Learn" instead of "Home / Learn". */
#breadcrumb {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    width: auto;
    padding: 8px;
    border-radius: 8px;
    background: #FFFFFF90;
    font-size: var(--fs-ui);
    font-weight: 700;
}
#breadcrumb a { color: var(--muted); text-decoration: none; }
#breadcrumb a:hover { color: var(--blue-deep); }
/* The gap supplies the space before the separator, this margin the space
   after it, so the two are equal. */
#breadcrumb a + a::before { content: "/"; margin-right: 6px; color: var(--lavender-mid); font-weight: 400; }
#breadcrumb a#crumb { color: var(--violet-deep); }

/* Page editor. Rendered only for administrators - see SitePage.EditorTools -
   and every action it calls is separately gated on the server. */
.hidden { display: none !important; }
/* Above the header, whose z-index is 30. The logo canvas hangs out of the
   header and over this bar, and the header is a single stacking context, so
   clearing the logo means clearing the whole panel - there is no rank between
   the two. The trade is that the bar passes over the sticky header on its way
   off the top of the page, which is a moment of overlap while scrolling. */
.editor-bar { position: relative; z-index: 40; margin-top: 16px; }
ul#editor-list {
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0;
    list-style: none;
}
ul#editor-list > li { display: inline-flex; padding: 0 14px; border-left: 1px solid var(--line); }
ul#editor-list > li:first-child, ul#editor-list > li#editor-edit { padding-left: 0; border-left: 0; }
ul#editor-list > li a {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: var(--orange);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .085em;
    text-decoration: none;
    text-transform: uppercase;
}
ul#editor-list > li a:hover { color: var(--orange-deep); }
.edit-icon {
    width: 17px;
    height: 17px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.confirm {
    max-width: 720px;
    margin-top: 14px;
    padding: 16px 20px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    border: 1px solid var(--line);
    border-left: 4px solid var(--orange);
    border-radius: 14px;
    background: rgba(251,250,248,.94);
    box-shadow: var(--shadow-soft);
}
.confirm p { margin: 0; color: var(--violet-deep); font-size: var(--fs-body); font-weight: 600; }
.confirm-buttons { display: flex; gap: 10px; }
.confirm .button { padding: 10px 20px; font-size: var(--fs-ui); }

/* Editing form */
.edit-form { padding: 26px 0 60px; }
.edit-form h1 { font-size: var(--fs-h2); }
.edit-form h3 { margin: 26px 0 8px; font-size: var(--fs-body); }
.edit-form label { cursor: pointer; }
.edit-content-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
#wrapToggle { color: var(--blue-deep); font-size: var(--fs-ui); font-weight: 700; text-decoration: none; }
#wikititle, #wikieditor {
    width: 100%;
    padding: 12px 14px;
    color: var(--violet-deep);
    border: 1px solid var(--line);
    border-radius: 12px;
    background: white;
    font: 400 .92rem/1.6 ui-monospace, SFMono-Regular, Consolas, monospace;
}
#wikititle:focus, #wikieditor:focus { border-color: var(--blue); outline: none; }
#wikieditor { min-height: 60vh; border: 0; border-radius: 0; resize: vertical; }
textarea.nowrap { white-space: pre; overflow-x: auto; }

/* Line number gutter, drawn by LinedTextArea */
.linedwrap {
    display: flex;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: white;
}
.linedwrap .lines {
    flex: 0 0 auto;
    width: 52px;
    padding: 12px 0;
    overflow: hidden;
    border-right: 1px solid var(--line-soft);
    background: var(--pearl);
    user-select: none;
}
.linedwrap .lineno {
    padding-right: 11px;
    color: var(--lavender-mid);
    font: 400 .92rem/1.6 ui-monospace, SFMono-Regular, Consolas, monospace;
    text-align: right;
}

/* ---------------------------------------------------------------------------
   Code samples
   ---------------------------------------------------------------------------
   Written in a page as:

       <pre class="source ts">...</pre>            highlighted
       <pre class="source ts numbered">...</pre>   highlighted with a gutter

   source.ts builds the panel around the pre and paints every token with a
   tok- class. Nothing about the colour scheme lives in the script, so a scheme
   is defined here and only here.

   The palette is a set of custom properties on .source-block. To give one
   language its own scheme, redefine the properties for it - the data-language
   attribute carries the class the author wrote:

       .source-block[data-language="css"] {
           --src-keyword: #f0a6c0;
           --src-type: #ffd8a8;
       }
   --------------------------------------------------------------------------- */
.source-block {
    max-width: 100%;
    /* Panel. Dark is used locally here for contrast, matching the code visual
       already on the Learn pages rather than introducing a second treatment. */
    --src-back: #27233e;
    --src-head: #312c4b;
    --src-edge: rgba(255,255,255,.11);
    --src-text: #eeeaf7;
    --src-gutter: #6d6590;
    /* Tokens. */
    --src-comment: #8b84ac;
    --src-string: #9ed3a6;
    --src-number: #e8b979;
    --src-keyword: #c4a7e7;
    --src-type: #73bdf2;
    --src-function: #f0c674;
    --src-literal: #e59a7c;
    --src-property: #7fd4c1;
    --src-variable: #e8b979;
    --src-tag: #73bdf2;
    --src-attr: #f0c674;
    --src-meta: #8b84ac;
    --src-operator: #cfc8e4;
    --src-punct: #a49dbd;

    margin: 34px 0;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: var(--src-back);
    box-shadow: var(--shadow-soft);
}
.source-head {
    padding: 0 14px 0 18px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    border-bottom: 1px solid var(--src-edge);
    background: var(--src-head);
}
.source-lang {
    color: #bdb5cd;
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
}
.source-lang.failed { color: #f0a48c; }
.source-lang.saved { color: #9ed3a6; }
.source-tools { display: flex; align-items: center; gap: 8px; }
.source-tools button {
    padding: 7px 14px;
    color: #d8d2e8;
    border: 1px solid var(--src-edge);
    border-radius: 9px;
    background: rgba(255,255,255,.05);
    cursor: pointer;
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    transition: background .18s ease, color .18s ease;
}
.source-tools button:hover { color: white; background: rgba(255,255,255,.13); }
.source-copy.copied { color: #27233e; border-color: transparent; background: #9ed3a6; }
/* The administrator's controls read as secondary to Copy until they are in
   use: editing is the rarer act, and the panel is mostly there to be read. */
.source-tools .source-numbers, .source-tools .source-edit { color: #a49dbd; }
.source-tools .source-numbers.on { color: var(--src-type); border-color: rgba(115,189,242,.4); }
.source-tools .source-save { color: #27233e; border-color: transparent; background: #9ed3a6; }
.source-tools .source-save:hover { color: #27233e; background: #b4e2ba; }

/* Editing a sample in place. The gutter comes from the same LinedTextArea the
   page editor uses, so it is restyled here for the dark panel rather than
   duplicated. */
.source-editor {
    width: 100%;
    min-height: 40vh;
    padding: 18px 20px;
    color: var(--src-text);
    border: 0;
    background: var(--src-back);
    font: 400 .88rem/1.7 ui-monospace, SFMono-Regular, Consolas, monospace;
    resize: vertical;
    tab-size: 4;
}
.source-editor:focus { outline: none; }
.source-body .linedwrap { flex: 1 1 auto; min-width: 0; border: 0; border-radius: 0; background: var(--src-back); }
.source-body .linedwrap .lines {
    width: 52px;
    padding: 18px 0;
    border-right: 1px solid var(--src-edge);
    background: rgba(0,0,0,.16);
}
.source-body .linedwrap .lineno {
    padding-right: 14px;
    color: var(--src-gutter);
    font: 400 .88rem/1.7 ui-monospace, SFMono-Regular, Consolas, monospace;
}
.source-body { display: flex; align-items: stretch; }
.source-lines {
    flex: 0 0 auto;
    padding: 18px 0;
    border-right: 1px solid var(--src-edge);
    background: rgba(0,0,0,.16);
    user-select: none;
}
.source-lines .lineno {
    padding: 0 14px;
    color: var(--src-gutter);
    font: 400 .88rem/1.7 ui-monospace, SFMono-Regular, Consolas, monospace;
    text-align: right;
}
pre.source {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    padding: 18px 20px;
    overflow-x: auto;
    color: var(--src-text);
    background: var(--src-back);
    font: 400 .88rem/1.7 ui-monospace, SFMono-Regular, Consolas, monospace;
    tab-size: 4;
}
pre.source code { font: inherit; }

/* A sample styled before the script has built it, so there is no flash of
   unstyled text on a slow load and it still reads correctly with scripting
   off. */
pre.source:not(.highlighted) {
    margin: 34px 0;
    border: 1px solid var(--line);
    border-radius: 18px;
}

.tok-comment { color: var(--src-comment); font-style: italic; }
.tok-string { color: var(--src-string); }
.tok-number { color: var(--src-number); }
.tok-keyword { color: var(--src-keyword); font-weight: 600; }
.tok-type { color: var(--src-type); }
.tok-function { color: var(--src-function); }
.tok-literal { color: var(--src-literal); }
.tok-property { color: var(--src-property); }
.tok-variable { color: var(--src-variable); }
.tok-tag { color: var(--src-tag); }
.tok-attr { color: var(--src-attr); }
.tok-meta { color: var(--src-meta); font-style: italic; }
.tok-operator { color: var(--src-operator); }
.tok-punct { color: var(--src-punct); }

/* Scrollbars on the dark code panels.
   ---------------------------------------------------------------------------
   A long declaration or a wrapped-off line puts a horizontal bar on a panel
   the browser paints in its own light furniture, which is the one thing on
   these panels that does not belong to the game's palette. Same shape as the
   contents panel's bar - 8px, rounded ends, no track - in the panel's own
   colours: the gutter violet at rest, easing to the type blue on hover.

   The tokens are read with fallbacks because two of these three panels sit
   outside .source-block and so never see its custom properties. */
@property --code-thumb {
    syntax: "<color>";
    inherits: true;
    initial-value: #6d6590;
}
pre.source,
.code-visual pre,
.source-body .source-editor {
    --code-thumb: var(--src-gutter, #6d6590);
    transition: --code-thumb .25s ease;
}
pre.source:hover,
.code-visual pre:hover,
.source-body .source-editor:hover { --code-thumb: var(--src-type, #73bdf2); }

pre.source::-webkit-scrollbar,
.code-visual pre::-webkit-scrollbar,
.source-body .source-editor::-webkit-scrollbar { width: 8px; height: 8px; }

pre.source::-webkit-scrollbar-track,
.code-visual pre::-webkit-scrollbar-track,
.source-body .source-editor::-webkit-scrollbar-track { background: transparent; }

pre.source::-webkit-scrollbar-thumb,
.code-visual pre::-webkit-scrollbar-thumb,
.source-body .source-editor::-webkit-scrollbar-thumb {
    border-radius: 999px;
    background: var(--code-thumb);
    transition: background .25s ease;
}

/* A panel with both a horizontal and a vertical bar meets in a corner the
   browser fills with its own colour by default. */
pre.source::-webkit-scrollbar-corner,
.code-visual pre::-webkit-scrollbar-corner,
.source-body .source-editor::-webkit-scrollbar-corner { background: transparent; }

@supports not selector(::-webkit-scrollbar) {
    pre.source,
    .code-visual pre,
    .source-body .source-editor {
        scrollbar-width: thin;
        scrollbar-color: var(--code-thumb) transparent;
    }
}

@media (prefers-reduced-motion: reduce) {
    pre.source, .code-visual pre, .source-body .source-editor,
    pre.source::-webkit-scrollbar-thumb,
    .code-visual pre::-webkit-scrollbar-thumb,
    .source-body .source-editor::-webkit-scrollbar-thumb { transition: none; }
}

/* Registered so it can be interpolated. An unregistered custom property is
   just a string to the engine and would jump rather than ease, which is what
   makes this declaration the thing that actually animates the thumb. */
@property --toc-thumb {
    syntax: "<color>";
    inherits: true;
    initial-value: #b6b1c3;
}

/* ---------------------------------------------------------------------------
   Worked examples
   ---------------------------------------------------------------------------
   A .demo pairs a code sample with a line saying what the sample produces in
   the game. It lays out in one column until a .demo-media child is added -
   a video thumbnail for the example - at which point it becomes two, with no
   change to the markup around it. Dropping the media in is the whole edit.
   --------------------------------------------------------------------------- */
.demo { margin: 22px 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; align-items: start; }
.demo > .source-block, .demo > pre.source { margin: 0; min-width: 0; }
.demo-note {
    margin: 0;
    padding: 13px 17px;
    border-left: 3px solid var(--gold);
    border-radius: 0 11px 11px 0;
    background: rgba(227,185,88,.11);
    color: var(--violet);
    font-size: var(--fs-body);
}
.demo-note strong {
    display: block;
    margin-bottom: 3px;
    color: var(--violet-deep);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .13em;
    text-transform: uppercase;
}
.demo-media { margin: 0; }
.demo-media img, .demo-media video {
    width: 100%;
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: var(--shadow-soft);
    object-fit: cover;
}
.demo-media figcaption { margin-top: 9px; color: var(--muted); font-size: var(--fs-ui); }
@supports selector(:has(*)) {
    .demo:has(.demo-media) { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); }
    .demo:has(.demo-media) .demo-note { grid-column: 1 / -1; }
}

/* ---------------------------------------------------------------------------
   API reference pages
   --------------------------------------------------------------------------- */

/* A reference page carries many more headings than an ordinary one, so they
   are set smaller than a section heading elsewhere - but in the same display
   face, because they are still headings and not code. */
.api-block h2 {
    margin-bottom: 12px;
    font-size: var(--fs-h2-sm);
}
.api-extends {
    display: inline-block;
    margin: 0 0 22px;
    padding: 5px 13px;
    border-radius: 999px;
    color: var(--muted);
    background: var(--pearl);
    font-size: var(--fs-ui);
    font-weight: 700;
    letter-spacing: .02em;
}

/* Inline code inside prose. Deliberately scoped to prose: a bare
   `.lesson-block code` selector also matches the <code> element that every
   highlighted sample is wrapped in, and painting that dark violet on the dark
   code panel makes any token without a colour of its own - a parameter name,
   an ordinary identifier - invisible. */
.lesson-block p code,
.lesson-block li code,
.lesson-block td code {
    padding: 2px 5px;
    border-radius: 5px;
    color: var(--violet-deep);
    background: rgba(86,81,103,.09);
    font: 500 .88em/1.4 ui-monospace, SFMono-Regular, Consolas, monospace;
}
.lesson-block p a code { color: var(--blue-deep); }

/* The index is long on a reference page, so it scrolls inside the sticky
   panel rather than running off the bottom of a tall viewport. */
.lesson-index {
    max-height: calc(100vh - 150px);
    overflow-y: auto;
    overscroll-behavior: contain;
    --toc-thumb: var(--lavender-mid);
    transition: --toc-thumb .25s ease;
}
.lesson-index:hover { --toc-thumb: var(--blue); }
.lesson-index::-webkit-scrollbar { width: 8px; }
/* No track. The panel's own surface reads as the channel, which keeps the
   scrollbar from drawing a second border inside a bordered card. */
.lesson-index::-webkit-scrollbar-track { background: transparent; }
.lesson-index::-webkit-scrollbar-thumb {
    border-radius: 999px;
    background: var(--toc-thumb);
    transition: background .25s ease;
}

/* Firefox takes colours but not a pixel width, and Chrome 121+ ignores the
   -webkit- pseudo-elements entirely once either standard property is set - so
   asking for both would cost the 8px above. The standard properties are given
   only to engines that have no -webkit- scrollbar to style. */
@supports not selector(::-webkit-scrollbar) {
    .lesson-index {
        scrollbar-width: thin;
        scrollbar-color: var(--toc-thumb) transparent;
    }
}

@media (prefers-reduced-motion: reduce) {
    .lesson-index, .lesson-index::-webkit-scrollbar-thumb { transition: none; }
}
.lesson-index .index-group {
    margin: 18px 0 2px;
    font-size: var(--fs-label);
}
.lesson-index .index-group:first-of-type { margin-top: 0; }

/* ---------------------------------------------------------------------------
   Administration console
   --------------------------------------------------------------------------- */
.admin-accounts {
    padding: 20px 0 0;
    display: grid;
    grid-template-columns: 1.25fr .95fr;
    gap: 20px;
    align-items: start;
}
.admin-create { padding: 16px 0 0; }
.admin-panel {
    padding: 24px 26px;
    border: 1px solid var(--line);
    border-radius: 20px;
    background: rgba(251,250,248,.9);
    box-shadow: var(--shadow-soft);
}
.admin-panel-head {
    margin-bottom: 16px;
    padding-bottom: 14px;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    border-bottom: 1px solid var(--line);
}
.admin-panel-head h2 { margin: 0; font-size: var(--fs-h3); }
.admin-count { color: var(--muted); font-size: var(--fs-ui); font-weight: 700; }
.admin-empty { color: var(--muted); font-size: var(--fs-body); }

.admin-table-scroll { overflow-x: auto; }
.admin-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
.admin-table th {
    padding: 0 10px 9px;
    color: var(--violet);
    border-bottom: 1px solid var(--line);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .1em;
    text-align: left;
    text-transform: uppercase;
    white-space: nowrap;
}
.admin-table td { padding: 11px 10px; border-bottom: 1px solid var(--line-soft); color: var(--muted); }
.admin-table td strong { color: var(--violet-deep); }
.admin-table .numeric { text-align: right; font-variant-numeric: tabular-nums; }
.account-row { cursor: pointer; transition: background .15s ease; }
.account-row:hover { background: rgba(95,165,225,.09); }
.account-row.selected { background: rgba(95,165,225,.16); }
.account-row.inactive td { opacity: .55; }

.account-tag {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    color: white;
    background: var(--blue-deep);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
    vertical-align: 1px;
}
.account-tag.off { background: var(--lavender-mid); }

.admin-detail h3 { margin: 22px 0 8px; font-size: var(--fs-body); }
.account-facts { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 7px 18px; font-size: var(--fs-body); }
.account-facts dt { color: var(--violet); font-weight: 700; }
.account-facts dd { margin: 0; color: var(--muted); }
.account-recent { margin: 0; padding-left: 20px; color: var(--muted); font-size: var(--fs-body); }
.account-recent li { padding: 2px 0; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
#account-notes {
    width: 100%;
    min-height: 120px;
    padding: 11px 13px;
    color: var(--violet-deep);
    border: 1px solid var(--line);
    border-radius: 12px;
    background: white;
    font: 400 .87rem/1.55 var(--body);
    resize: vertical;
}
#account-notes:focus { border-color: var(--blue); outline: none; }
.account-actions { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 10px; }
.account-actions .button { padding: 11px 18px; font-size: var(--fs-ui); }
.button-danger {
    color: white;
    border: 1px solid transparent;
    background: var(--danger);
    box-shadow: inset 0 2px 0 rgba(255,255,255,.22), 0 4px 0 #8d3625;
}
.button-danger:hover { background: #cc5540; }
.button-danger.confirming { background: #8d3625; }

.admin-fields { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 18px; }
.admin-fields input {
    width: 100%;
    padding: 11px 13px;
    color: var(--violet-deep);
    border: 1px solid var(--line);
    border-radius: 12px;
    background: white;
    font: 400 .9rem/1.4 var(--body);
}
.admin-fields input:focus { border-color: var(--blue); outline: none; }
/* Matched to .admin-fields input above: the console's controls are smaller
   than the ones on a public form. */
.admin-fields select {
    min-height: 0;
    padding: 11px 38px 11px 13px;
    background-color: white;
    background-position: right 13px center;
    font: 400 .9rem/1.4 var(--body);
}
.admin-fields select:focus { border-color: var(--blue); outline: none; }
/* Date and time read as one fact, so they share a cell rather than wrapping
   apart onto separate rows of the three column grid. */
.admin-fields .field-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
.admin-fields label {
    display: block;
    margin-bottom: 7px;
    color: var(--violet);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .09em;
    text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
   Messages and the account panel
   --------------------------------------------------------------------------- */

/* The unread badge sits at the end of the navigation, after the account link,
   so it is the last thing on the row at every width. */
.unread-badge {
    display: inline-grid;
    place-items: center;
    min-width: 24px;
    height: 24px;
    padding: 0 7px;
    border-radius: 999px;
    color: white;
    background: var(--danger);
    box-shadow: 0 3px 8px rgba(187,74,52,.35);
    font: 800 .72rem/1 var(--body);
    text-decoration: none;
    font-variant-numeric: tabular-nums;
}
.unread-badge:hover { background: #cc5540; }

/* Written against .site-nav on purpose. ".site-nav a" is one class plus one
   element and so outranks a bare ".unread-badge"; without matching that reach
   the badge inherits the nav's 13px vertical padding and its violet text, and
   the number drops to the bottom of a stretched pill instead of sitting white
   in the middle of a circle. */
.site-nav .unread-badge {
    height: 24px;
    min-width: 24px;
    padding: 0 7px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-size: var(--fs-label);
    letter-spacing: 0;
    text-transform: none;
    line-height: 1;
}
/* The nav draws a hover underline on every link that is not the account
   control. On a round badge it lands as a blue bar across the circle. */
.site-nav .unread-badge::after { display: none; }

.account-panels {
    padding: 20px 0 0;
    display: grid;
    grid-template-columns: 1.3fr .9fr;
    gap: 20px;
    align-items: start;
}
.account-sent { padding: 16px 0 0; }

.message {
    padding: 15px 17px;
    margin-bottom: 12px;
    border: 1px solid var(--line);
    border-left: 3px solid var(--lavender-mid);
    border-radius: 0 14px 14px 0;
    background: rgba(255,255,255,.6);
}
.message:last-child { margin-bottom: 0; }
/* Unread is marked by the orb colour, which is the site's own way of saying
   "this one matters" without adding another accent. */
.message.unread { border-left-color: var(--orange); background: rgba(168,93,47,.05); }
.message-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.message-head strong { color: var(--violet-deep); font-size: var(--fs-body); }
.message-when { margin-left: auto; color: var(--muted); font-size: var(--fs-ui); font-variant-numeric: tabular-nums; }
.message-body { margin: 0; color: var(--violet); font-size: var(--fs-body); white-space: pre-wrap; }
.message-note { margin: 0 0 7px; color: var(--muted); font-size: var(--fs-ui); }
.message-actions { margin-top: 11px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.message-actions .button { padding: 8px 14px; font-size: var(--fs-ui); }

#compose-panel label {
    display: block;
    margin: 14px 0 7px;
    color: var(--violet);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .09em;
    text-transform: uppercase;
}
#compose-panel label:first-of-type { margin-top: 0; }
#compose-to, #compose-body {
    width: 100%;
    padding: 11px 13px;
    color: var(--violet-deep);
    border: 1px solid var(--line);
    border-radius: 12px;
    background: white;
    font: 400 .9rem/1.5 var(--body);
}
#compose-body { min-height: 150px; resize: vertical; }
#compose-to:focus, #compose-body:focus { border-color: var(--blue); outline: none; }
.compose-remaining { margin: 8px 0 14px; color: var(--muted); font-size: var(--fs-ui); }
.compose-remaining.error { color: var(--danger); font-weight: 700; }

/* Lead cards, at the head of a control panel.
   ---------------------------------------------------------------------------
   Deliberately the largest thing on the panel: it is what a person signs in to
   check, and it was previously a word in a crowded navigation row. */
.panel-lead { padding: 20px 0 0; }
.lead-card {
    padding: 22px 26px;
    margin-bottom: 2em;
    display: flex;
    align-items: center;
    gap: 20px;
    border: 1px solid var(--line);
    border-radius: 22px;
    background: linear-gradient(135deg, rgba(95,165,225,.10), rgba(251,250,248,.95));
    box-shadow: var(--shadow-soft);
    color: var(--violet-deep);
    text-decoration: none;
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.lead-card:hover {
    transform: translateY(-2px);
    border-color: var(--blue);
    box-shadow: var(--shadow);
}
.lead-card-icon {
    width: 56px;
    height: 56px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    border-radius: 17px;
    background: linear-gradient(145deg, #73b8ee, var(--blue-deep));
    box-shadow: inset 0 2px 0 rgba(255,255,255,.4), 0 6px 0 #1c6fae;
}
.lead-card-icon svg {
    width: 26px;
    height: 26px;
    fill: none;
    stroke: white;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.lead-card-text { display: grid; gap: 3px; }
.lead-card-text strong { font: 700 clamp(1.5rem, 2.4vw, 2rem)/1.1 var(--display); }
.lead-card-text span { color: var(--muted); font-size: var(--fs-body); font-weight: 600; }

/* Something waiting turns the card's own colour, so it reads as unread from
   across the page rather than only in the small badge. */
.lead-card.has-unread {
    border-color: rgba(187,74,52,.4);
    background: linear-gradient(135deg, rgba(187,74,52,.12), rgba(251,250,248,.95));
}
.lead-card.has-unread .lead-card-icon {
    background: linear-gradient(145deg, #d9705c, var(--danger));
    box-shadow: inset 0 2px 0 rgba(255,255,255,.3), 0 6px 0 #8d3625;
}
.lead-card.has-unread .lead-card-text span { color: var(--danger); font-weight: 800; }

/* The account panel is the message centre, so it states the count rather than
   linking to where the reader already is. */
.lead-note { margin-top: 14px; color: var(--muted); font-size: var(--fs-body); }
.lead-note.has-unread strong { color: var(--danger); }

/* A card that leads back rather than onward. Quieter, and its arrow points
   the way it goes. */
.lead-card.back {
    background: linear-gradient(135deg, rgba(211,204,216,.35), rgba(251,250,248,.95));
}
.lead-card.back .lead-card-icon {
    background: linear-gradient(145deg, #b6b1c3, var(--violet));
    box-shadow: inset 0 2px 0 rgba(255,255,255,.3), 0 6px 0 #3f3a52;
}
.lead-card.back .lead-card-text strong { font-size: var(--fs-h3); }

/* The animation guide, set apart from the reference beside it so the pair can
   be told apart at a glance rather than read. */
.lead-card.motion {
    background: linear-gradient(135deg, rgba(116,103,173,.14), rgba(251,250,248,.95));
}
.lead-card.motion .lead-card-icon {
    background: linear-gradient(145deg, #9a8cc9, #6558a0);
    box-shadow: inset 0 2px 0 rgba(255,255,255,.32), 0 6px 0 #4b3f7d;
}

/* Downloads take the warm gold the game uses for things worth finding. */
.lead-card.downloads {
    background: linear-gradient(135deg, rgba(227,185,88,.16), rgba(251,250,248,.95));
}
.lead-card.downloads .lead-card-icon {
    background: linear-gradient(145deg, #efc978, #b98b28);
    box-shadow: inset 0 2px 0 rgba(255,255,255,.4), 0 6px 0 #8c6a1e;
}

/* Two cards side by side on a control panel, stacking when there is no room. */
.panel-cards {
    padding: 20px 0 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
}

/* Features that are coming but are not here yet.
   ---------------------------------------------------------------------------
   Held visibly apart from the catalog of what a course can do today, so the
   two are never read as one list. Quieter surface, dashed edge, and a label on
   each card - the distinction should survive someone skimming. */
.planned-section { padding-top: 8px; }
.catalog-card.planned {
    border-style: dashed;
    border-color: var(--lavender-mid);
    background: rgba(238,234,241,.5);
    box-shadow: none;
}
.catalog-card.planned .object-token { opacity: .55; }
.catalog-card.planned h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: 9px; }
.planned-tag {
    padding: 3px 9px;
    border-radius: 999px;
    color: var(--violet);
    background: var(--pearl);
    font: 800 .58rem/1 var(--body);
    letter-spacing: .12em;
    text-transform: uppercase;
}

/* Console search, paging, and the role set. */
.admin-search { margin-bottom: 14px; }
.admin-search input {
    width: 100%;
    padding: 10px 14px;
    color: var(--violet-deep);
    border: 1px solid var(--line);
    border-radius: 999px;
    background: white;
    font: 400 .88rem/1.4 var(--body);
}
.admin-search input:focus { border-color: var(--blue); outline: none; }
.admin-pager {
    margin-top: 16px;
    padding-top: 14px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    border-top: 1px solid var(--line-soft);
}
.admin-pager .button { padding: 9px 16px; font-size: var(--fs-ui); }
.admin-pager .button:disabled { opacity: .4; cursor: not-allowed; transform: none; }
.admin-page-of { color: var(--muted); font-size: var(--fs-ui); font-weight: 700; }

.role-set { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
.role-box {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 11px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: white;
    cursor: pointer;
    font-size: var(--fs-ui);
    font-weight: 700;
    text-transform: capitalize;
}
.role-box:has(input:checked) { border-color: var(--blue); background: rgba(95,165,225,.12); color: var(--violet-deep); }

/* Settings take the lavender of the account chrome rather than a colour of
   their own; they are maintenance, not a destination. */
/* The news card takes the site's orange, the colour the news page already
   uses for a post's category and rating. */
.lead-card.posts { background: linear-gradient(135deg, rgba(232,124,58,.14), rgba(251,250,248,.95)); }
.lead-card.posts .lead-card-icon { background: linear-gradient(145deg, #f0a072, var(--orange-deep)); }
.lead-card.settings { background: linear-gradient(135deg, rgba(211,204,216,.4), rgba(251,250,248,.95)); }
.lead-card.settings .lead-card-icon {
    background: linear-gradient(145deg, #b6b1c3, var(--violet));
    box-shadow: inset 0 2px 0 rgba(255,255,255,.3), 0 6px 0 #3f3a52;
}

#email-form input, #password-form input {
    width: 100%;
    margin-bottom: 14px;
    padding: 11px 13px;
    color: var(--violet-deep);
    border: 1px solid var(--line);
    border-radius: 12px;
    background: white;
    font: 400 .9rem/1.4 var(--body);
}
#email-form input:focus, #password-form input:focus { border-color: var(--blue); outline: none; }
#email-form label, #password-form label {
    display: block;
    margin: 12px 0 7px;
    color: var(--violet);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .09em;
    text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
   News posts
   ---------------------------------------------------------------------------
   A rating is stored in half stars, 0 to 10. Each star is an empty element
   with a class, carrying no glyph of its own, so the shape is decided here
   and can be redrawn without touching a single stored value. The half star
   is one shape clipped to its left half over a dimmed copy of the same one.
   --------------------------------------------------------------------------- */

.stars { display: inline-flex; gap: 3px; margin: 0 0 12px; vertical-align: middle; }
.stars-none { color: var(--muted); font-size: var(--fs-ui); font-style: italic; }
.star {
    width: 15px;
    height: 15px;
    display: inline-block;
    background: var(--line);
    /* One five-pointed star, drawn once and reused for every state. */
    clip-path: polygon(50% 0, 61.8% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 38.2% 35%);
}
.star.full { background: var(--orange); }
.star.half { background: linear-gradient(to right, var(--orange) 0 50%, var(--line) 50% 100%); }

.news-listing { padding-bottom: 60px; }
.news-search { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 22px 0 6px; }
.news-search label { color: var(--violet); font-size: var(--fs-label); font-weight: 800; letter-spacing: .09em; text-transform: uppercase; }
.news-search input { min-width: 240px; flex: 0 1 320px; }

.post-category { color: var(--orange); text-decoration: none; }
.post-category:hover { color: var(--orange-deep); }
.post-brief { color: var(--muted); }
.post-brief p { margin: 0 0 8px; }
.post-unpublished {
    display: inline-block;
    margin: 0 0 10px;
    padding: 4px 11px;
    border-radius: 999px;
    color: white;
    background: var(--danger);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
}
.article-image { background-position: center; background-size: cover; }

.post { padding: 8px 0 70px; }
/* Sits above the category line, styled like the page editor's own link so the
   two read as the same control in two places. */
.post-edit { margin: 0 0 12px; }
.post-edit a { display: inline-flex; align-items: center; gap: 7px; color: var(--orange); font-size: var(--fs-label); font-weight: 800; letter-spacing: .085em; text-decoration: none; text-transform: uppercase; }
.post-edit a:hover { color: var(--orange-deep); }
.post-head { max-width: 800px; margin-bottom: 30px; }
.post-head h1 { margin: 6px 0 14px; }
.post-image { margin: 0 0 34px; }
.post-image img { width: 100%; border-radius: 22px; box-shadow: var(--shadow); }
.post-body { max-width: 830px; }
.post-figure { margin: 26px 0; }
.post-figure img { width: 100%; border-radius: 16px; }
.post-figure figcaption { margin-top: 10px; color: var(--muted); font-size: var(--fs-ui); font-style: italic; text-align: center; }
.post-back { margin-top: 40px; }

/* The console's posts panel */
.admin-posts { padding-bottom: 20px; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: start; }
.admin-fields .wide { grid-column: 1 / -1; }
.post-mine { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); font-size: var(--fs-ui); font-weight: 700; white-space: nowrap; }
.admin-search { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.admin-search input[type="search"] { flex: 1 1 220px; min-width: 0; }
.admin-search select { flex: 0 1 210px; min-height: 0; padding: 10px 36px 10px 13px; background-color: white; background-position: right 12px center; font-size: var(--fs-ui); }
.field-note { display: block; margin-top: 6px; color: var(--muted); font-size: var(--fs-ui); }
.post-tools { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }
.post-tools .button { padding: 7px 12px; font-size: var(--fs-label); }
.post-switches { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; margin: 14px 0; font-size: var(--fs-ui); }
.post-switches label { display: inline-flex; align-items: center; gap: 7px; font-weight: 700; }
.post-author { color: var(--muted); margin-left: auto; }
.admin-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.admin-table tr.current { background: rgba(211,204,216,.42); }
.admin-table tr[data-id] { cursor: pointer; }
.admin-table td.row-visit { width: 1%; text-align: right; white-space: nowrap; }
.row-visit a { color: var(--blue-deep); font-size: var(--fs-label); font-weight: 800; letter-spacing: .08em; text-decoration: none; text-transform: uppercase; }
.row-visit a:hover { color: var(--orange); }
.state-pill { padding: 3px 10px; border-radius: 999px; font-size: var(--fs-label); font-weight: 800; }
.state-pill.on { color: #1d5b2a; background: rgba(64,160,86,.18); }
.state-pill.off { color: var(--muted); background: var(--pearl); }

/* ---------------------------------------------------------------------------
   Comments on a news post
   --------------------------------------------------------------------------- */

.comments { padding: 0 0 70px; max-width: 830px; }
.comments h2 { margin-bottom: 18px; }
.comment-note { color: var(--muted); font-size: var(--fs-body); }

.comment {
    padding: 18px 20px;
    margin-bottom: 12px;
    border: 1px solid var(--line);
    border-radius: 16px;
    background: rgba(251,250,248,.9);
}
/* A hidden comment is shown to administrators only, and is drawn so it cannot
   be mistaken for one the public can see. */
.comment.hidden-comment { border-style: dashed; background: rgba(211,204,216,.3); opacity: .8; }
.comment-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; margin: 0 0 8px; }
.comment-head strong { color: var(--violet-deep); }
.comment-when { color: var(--muted); font-size: var(--fs-ui); }
.comment-edited, .comment-hidden-tag {
    padding: 2px 9px;
    border-radius: 999px;
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.comment-edited { color: var(--violet); background: var(--pearl); }
.comment-hidden-tag { color: white; background: var(--danger); }
/* The one place on the site carrying words from somebody who is not an
   administrator, so a pasted wall of characters must not widen the page. */
.comment-body { margin: 0; overflow-wrap: anywhere; }

.comment-tools { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }
.comment-tools button {
    padding: 6px 13px;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--violet);
    background: white;
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
}
.comment-tools button:hover { border-color: var(--blue); color: var(--blue-deep); }
.comment-tools .comment-delete:hover { border-color: var(--danger); color: var(--danger); }
.comment-editor { margin-top: 12px; }

.comment-form { margin-top: 26px; }
.comment-form label {
    display: block;
    margin-bottom: 8px;
    color: var(--violet);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .09em;
    text-transform: uppercase;
}
.comment-form textarea { margin-bottom: 6px; }
.comment-form .button { margin-top: 6px; }
.compose-remaining.error { color: var(--danger); font-weight: 700; }

/* ---------------------------------------------------------------------------
   The community board
   ---------------------------------------------------------------------------
   A board's accent is stored as a palette token rather than as a colour, so
   these classes are the one place a token becomes something you can see, and
   retuning the palette above retunes every board banner with it.
   --------------------------------------------------------------------------- */

.accent-orange { background: var(--orange); }
.accent-orange-deep { background: var(--orange-deep); }
.accent-blue { background: var(--blue); }
.accent-blue-deep { background: var(--blue-deep); }
.accent-violet { background: var(--violet); }
.accent-violet-deep { background: var(--violet-deep); }
.accent-lavender { background: var(--lavender); }
.accent-danger { background: var(--danger); }

.accent-text-orange { color: var(--orange); }
.accent-text-orange-deep { color: var(--orange-deep); }
.accent-text-blue { color: var(--blue-deep); }
.accent-text-blue-deep { color: var(--blue-deep); }
.accent-text-violet { color: var(--violet); }
.accent-text-violet-deep { color: var(--violet-deep); }
.accent-text-lavender { color: var(--violet); }
.accent-text-danger { color: var(--danger); }

.board-list { padding: 10px 0 70px; display: grid; gap: 14px; }
.board-card {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    gap: 22px;
    padding: 24px 26px 24px 34px;
    border: 1px solid var(--line);
    border-radius: 20px;
    background: rgba(251,250,248,.92);
    box-shadow: var(--shadow);
    color: inherit;
    text-decoration: none;
    transition: transform .16s ease, border-color .16s ease;
}
.board-card:hover { transform: translateY(-2px); border-color: var(--lavender-mid); }
.board-card.board-off { opacity: .62; border-style: dashed; }
/* The banner: a bar down the leading edge rather than a block of colour, so a
   board is recognisable at a glance without the colour having to carry text. */
.board-accent { position: absolute; top: 0; bottom: 0; left: 0; width: 10px; }
.board-text { display: grid; gap: 4px; flex: 1 1 auto; min-width: 0; }
.board-text strong { font: 700 var(--fs-h3)/1.2 var(--display); color: var(--violet-deep); }
.board-text span { color: var(--muted); }
.board-count {
    display: grid;
    gap: 6px;
    justify-items: end;
    flex: 0 0 auto;
    color: var(--muted);
    font-size: var(--fs-ui);
    font-weight: 700;
    white-space: nowrap;
}
.board-dot { display: inline-block; width: 10px; height: 10px; margin-right: 8px; border-radius: 3px; vertical-align: middle; }

.topic-list { padding-bottom: 70px; }
.topic-row {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 16px 20px;
    margin-bottom: 8px;
    border: 1px solid var(--line);
    border-radius: 14px;
    background: rgba(251,250,248,.9);
    color: inherit;
    text-decoration: none;
}
.topic-row:hover { border-color: var(--blue); }
.topic-row.topic-hidden { border-style: dashed; opacity: .75; }
.topic-main { display: grid; gap: 3px; flex: 1 1 auto; min-width: 0; }
.topic-main strong { color: var(--violet-deep); overflow-wrap: anywhere; }
.topic-by { color: var(--muted); font-size: var(--fs-ui); }
.topic-meta { display: grid; gap: 3px; justify-items: end; flex: 0 0 auto; color: var(--muted); font-size: var(--fs-ui); white-space: nowrap; }
.topic-tag {
    display: inline-block;
    margin-right: 8px;
    padding: 2px 9px;
    border-radius: 999px;
    color: white;
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
    vertical-align: middle;
}
.topic-tag.pinned { background: var(--orange); }
.topic-tag.locked { background: var(--violet); }
.topic-tag.hidden-tag { background: var(--danger); }

.topic-head { padding: 8px 0 22px; max-width: 900px; }
.topic-head h1 { margin: 6px 0 10px; }
.topic-tools { margin-top: 16px; }

.forum-thread { padding-bottom: 70px; max-width: 900px; }
.forum-post {
    padding: 20px 22px;
    margin-bottom: 12px;
    border: 1px solid var(--line);
    border-radius: 16px;
    background: rgba(251,250,248,.92);
}
.forum-post.first-post { border-color: var(--lavender-mid); background: white; }
.forum-post.hidden-comment { border-style: dashed; background: rgba(211,204,216,.3); opacity: .8; }
/* The one place on the site carrying words from somebody who is not an
   administrator, so a pasted wall of characters must not widen the page. */
.forum-body { overflow-wrap: anywhere; }
.forum-body p { margin: 0 0 10px; }
.forum-body p:last-child { margin-bottom: 0; }
.forum-body pre.source { margin: 14px 0; }
.post-quote {
    margin: 10px 0;
    padding: 8px 0 8px 16px;
    border-left: 3px solid var(--lavender-mid);
    color: var(--muted);
    font: inherit;
}
.post-image-inline { max-width: 100%; margin: 12px 0; border-radius: 12px; }
.mention { color: var(--blue-deep); font-weight: 700; text-decoration: none; }
.mention:hover { color: var(--orange); }

.forum-compose { margin-top: 34px; }
.forum-compose h2 { margin-bottom: 14px; }
.forum-form label {
    display: block;
    margin-bottom: 8px;
    color: var(--violet);
    font-size: var(--fs-label);
    font-weight: 800;
    letter-spacing: .09em;
    text-transform: uppercase;
}
.forum-form input { margin-bottom: 16px; }
.forum-form textarea { margin-bottom: 6px; }
.forum-form .button { margin-top: 6px; }
.forum-actions { display: flex; flex-wrap: wrap; gap: 10px; }
/* A button held while a picture is still going up. Somebody who cannot press
   it knows to wait; a press that silently does nothing looks like a fault. */
.button.waiting, .comment-tools button.waiting { opacity: .55; cursor: progress; }
.comment-editor .post-tools { margin: 0 0 8px; }
.comment-editor textarea { margin-bottom: 6px; }
.edit-message { min-height: 0; margin: 0 0 8px; }

/* The limits page */
.admin-hidden-pages { padding-bottom: 10px; }
.admin-hidden-pages .comment-tools { margin-top: 14px; }
.admin-limits { padding-bottom: 60px; }
.limit-group { margin-bottom: 28px; }
.limit-group h2 { margin-bottom: 14px; font-size: var(--fs-h3); }
.limit-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 130px;
    gap: 6px 18px;
    align-items: center;
    padding: 12px 0;
    border-bottom: 1px solid var(--line-soft);
}
.limit-row label { color: var(--violet-deep); font-weight: 700; }
.limit-row input { grid-row: 1 / 3; grid-column: 2; text-align: right; }
.limit-note { grid-column: 1; color: var(--muted); font-size: var(--fs-ui); }
