/* Act 1 — the game layer.
   act1.css lays the page out; this file is the part that makes it feel like a
   place you are moving THROUGH rather than a form you are filling in.

   It borrows the Trial's vocabulary on purpose (trial.css: float, rise, flare,
   draw) — /trial and /act1 are the same world, and inventing a second visual
   language for the second surface would make them read as two products.

   Rules kept throughout:
     - transform and opacity only, so nothing here triggers layout
     - every effect degrades to nothing under prefers-reduced-motion
     - decoration is pointer-events:none and aria-hidden, never in the way */

/* ---------- ambient: the ember field ----------
   A slow drift of motes behind the content. It is the cheapest possible way to
   say "this is a world, not a document", and at 5% opacity nobody reads it as
   an effect — they just stop feeling like they are on a settings screen. */

#a1-embers {
    position: fixed; inset: 0; z-index: 0;
    pointer-events: none;
}
.a1-root { position: relative; }
.a1-wrap { position: relative; z-index: 1; }

/* ---------- view transitions ----------
   Each panel enters as a staggered rise. The stagger is what sells it: things
   arriving together read as a page load, things arriving in sequence read as a
   scene assembling. */

.a1-enter { animation: a1-rise .42s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes a1-rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

/* A single light sweep across the top edge when a view changes — the visual
   equivalent of a page turn. */
.a1-sweep {
    position: absolute; left: 0; right: 0; top: 0; height: 1px;
    pointer-events: none; overflow: visible;
}
.a1-sweep::after {
    content: ''; position: absolute; top: 0; left: -30%; width: 30%; height: 1px;
    background: linear-gradient(90deg, transparent, var(--a1-cyan), transparent);
    box-shadow: 0 0 18px 2px rgba(53, 224, 245, .55);
    animation: a1-sweep .85s cubic-bezier(.3, .7, .3, 1) forwards;
}
@keyframes a1-sweep { to { left: 100%; } }

/* ---------- the hub as a quest path ----------
   The chapters were a list of cards. A list says "pick one"; a path says "you
   are here, and it goes that way" — which is the actual truth about an act. */

/* No overflow:hidden here — the road has to cross the 10px gap between cards,
   and clipping the card is what severs it. */
.a1-ch { position: relative; }

/* The road runs the full height of each card and overhangs into the gap below.
   The numbered circle is opaque and sits above it, so the line reads as a path
   THROUGH the nodes rather than a series of stitches between them. */
.a1-ch::before {
    content: ''; position: absolute; left: 32px; top: 0; bottom: -11px;
    width: 2px; background: #17223a; z-index: 0;
}
/* the road starts at the first node and ends at the last — no dangling ends */
.a1-ch:first-child::before { top: 50%; }
.a1-ch:last-child::before { bottom: 50%; }

/* lit as far as you have walked */
.a1-ch.done::before {
    background: linear-gradient(180deg, rgba(52, 211, 153, .85), rgba(52, 211, 153, .3));
    box-shadow: 0 0 10px rgba(52, 211, 153, .3);
}

.a1-ch-n, .a1-ch-body, .a1-ch-go { position: relative; z-index: 1; }

/* a completed node reads as struck, not just coloured */
.a1-ch.done .a1-ch-n {
    box-shadow: 0 0 0 1px rgba(52, 211, 153, .45), 0 0 16px rgba(52, 211, 153, .25);
}

/* "you are here" — the first chapter you have not finished gets the beacon.
   One marker only; a page where everything pulses has told you nothing. */
.a1-ch.current .a1-ch-n {
    background: rgba(53, 224, 245, .14);
    color: var(--a1-cyan);
    animation: a1-beacon 2.1s ease-in-out infinite;
}
@keyframes a1-beacon {
    0%, 100% { box-shadow: 0 0 0 0 rgba(53, 224, 245, .45), 0 0 14px rgba(53, 224, 245, .3); }
    50%      { box-shadow: 0 0 0 7px rgba(53, 224, 245, 0), 0 0 22px rgba(53, 224, 245, .5); }
}

/* hover lights the card edge from the left, in the direction of travel.
   Inset from the corners so it stays inside the 14px radius without needing
   overflow:hidden, which the road above cannot survive. */
.a1-ch::after {
    content: ''; position: absolute; left: 0; top: 12px; bottom: 12px; width: 2px;
    border-radius: 2px;
    background: linear-gradient(180deg, var(--a1-cyan), #7ad9ff);
    opacity: 0; transform: scaleY(.3); transform-origin: 50% 50%;
    transition: opacity .18s ease, transform .18s ease;
}
.a1-ch:hover::after { opacity: .9; transform: scaleY(1); }

/* ---------- the XP bar ----------
   A progress bar that only ever sits there is furniture. This one has a pulse
   travelling along the filled part, so the thing measuring your progress looks
   alive even when you have not moved. */

.a1-bar-track { position: relative; }
.a1-bar-fill { position: relative; overflow: hidden; }
.a1-bar-fill::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, .75) 50%, transparent 100%);
    transform: translateX(-100%);
    animation: a1-shimmer 2.8s ease-in-out infinite;
}
@keyframes a1-shimmer { to { transform: translateX(220%); } }

/* when the bar actually advances, it flares once */
.a1-bar-fill.gain { animation: a1-flare .7s ease-out; }
@keyframes a1-flare {
    0%   { box-shadow: 0 0 0 rgba(53, 224, 245, 0); }
    35%  { box-shadow: 0 0 22px 3px rgba(53, 224, 245, .85); }
    100% { box-shadow: 0 0 0 rgba(53, 224, 245, 0); }
}

/* the floating gain number, straight out of the Trial's damage numbers */
.a1-float {
    position: absolute; right: 0; top: -6px;
    font-weight: 900; font-size: 19px; font-style: italic; color: #eaffff;
    text-shadow: 0 0 14px var(--a1-cyan), 0 0 30px rgba(53, 224, 245, .8);
    pointer-events: none; white-space: nowrap;
    animation: a1-riseout .9s cubic-bezier(.2, .8, .2, 1) forwards;
}
@keyframes a1-riseout { to { transform: translate(6px, -30px); opacity: 0; } }

/* ---------- the Stoker's dialogue box ----------
   Typed out, because that is what a character speaking looks like in a game.
   Types once per chapter per session — re-reading something you have already
   read should never make you wait. */

.a1-stoker { position: relative; }
.a1-stoker.typing { cursor: pointer; }
.a1-stoker-said .a1-cursor {
    display: inline-block; width: 9px; height: 17px;
    background: var(--a1-amber); vertical-align: -3px; margin-left: 2px;
    box-shadow: 0 0 12px rgba(255, 207, 107, .8);
    animation: a1-blink .62s steps(1) infinite;
}
@keyframes a1-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

/* the amber edge breathes while they are mid-sentence. The extra padding
   reserves the corner the skip hint sits in, so a long last line cannot run
   underneath it. */
.a1-stoker.typing { animation: a1-speak 1.5s ease-in-out infinite; padding-bottom: 32px; }
@keyframes a1-speak {
    0%, 100% { border-left-color: var(--a1-amber); }
    50%      { border-left-color: #ffe6ab; }
}

.a1-skip {
    position: absolute; right: 13px; bottom: 9px;
    font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
    color: var(--a1-ink-3); pointer-events: none;
}

/* ---------- lanes and buttons ----------
   Small, physical feedback. A button that does not move under the finger feels
   like a picture of a button. */

.a1-lane { position: relative; }
.a1-lane::after {
    content: ''; position: absolute; left: 0; top: 12px; bottom: 12px; width: 2px;
    border-radius: 2px;
    background: var(--a1-cyan); opacity: 0; transform: scaleY(.3);
    transition: opacity .18s ease, transform .18s ease;
}
.a1-lane:hover::after { opacity: .9; transform: scaleY(1); }
.a1-lane:active, .a1-ch:active { transform: translateY(0) scale(.985); }

.a1-btn { transition: transform .12s ease, filter .15s ease, box-shadow .2s ease; }
.a1-btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 8px 26px rgba(53, 224, 245, .3); }
.a1-btn:active:not(:disabled) { transform: translateY(0) scale(.97); }

/* ---------- "Run it here" — the casting state ----------
   The wait is the moment a beginner is most likely to think it broke. A moving
   border says the machine is working on it in a way three dots alone do not. */

/* A rotating conic border was the first attempt and it was wrong: painting the
   conic on border-box means the near-transparent padding-box layer above it
   lets the gradient through the whole panel, so the wait looked like a render
   bug. A scanning bar on the top edge says the same thing with no way to
   misdraw — background-position and border-color animate everywhere. */
.a1-run { position: relative; }
.a1-run.casting { animation: a1-castpulse 1.5s ease-in-out infinite; }
@keyframes a1-castpulse {
    0%, 100% { border-color: rgba(53, 224, 245, .22); }
    50%      { border-color: rgba(53, 224, 245, .8); }
}
.a1-run.casting::before {
    content: ''; position: absolute; left: 8px; right: 8px; top: 0; height: 2px;
    pointer-events: none; border-radius: 2px;
    background: linear-gradient(90deg, transparent, var(--a1-cyan), transparent);
    background-size: 45% 100%; background-repeat: no-repeat;
    box-shadow: 0 0 12px rgba(53, 224, 245, .5);
    animation: a1-scan 1.4s ease-in-out infinite;
}
@keyframes a1-scan {
    0%   { background-position: -45% 0; }
    100% { background-position: 145% 0; }
}

/* the answer lands rather than appears */
.a1-run-text.landed { animation: a1-land .5s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes a1-land {
    from { opacity: 0; transform: translateY(8px) scale(.995); }
    to   { opacity: 1; transform: none; }
}

/* ---------- chapter clear ----------
   The payoff. Posting is the thing this whole act is built to get someone to
   do once, so the moment it happens should not look like a form submitting. */

.a1-clear {
    position: relative; overflow: hidden; text-align: center;
    border-radius: 16px; padding: 26px 18px 22px; margin-bottom: 20px;
    background: linear-gradient(180deg, rgba(53, 224, 245, .10), rgba(12, 18, 32, .6));
    border: 1px solid rgba(53, 224, 245, .35);
    animation: a1-clear-in .55s cubic-bezier(.2, 1.25, .35, 1) both;
}
@keyframes a1-clear-in {
    from { opacity: 0; transform: scale(.9) translateY(10px); }
    to   { opacity: 1; transform: none; }
}
/* Light rays behind the plate. The mask is not optional polish: without it the
   conic wedges hit the panel's edges as hard diagonal shapes and read as a
   rendering glitch. Faded outward, they read as a burst behind the words. */
.a1-clear-rays {
    position: absolute; left: 50%; top: 50%; width: 520px; height: 520px;
    margin: -260px 0 0 -260px; pointer-events: none; opacity: .55;
    background: conic-gradient(rgba(53, 224, 245, .30) 0 4deg, transparent 4deg 22deg,
                               rgba(53, 224, 245, .16) 22deg 25deg, transparent 25deg 45deg);
    -webkit-mask-image: radial-gradient(closest-side, #000 12%, rgba(0, 0, 0, .55) 42%, transparent 76%);
    mask-image: radial-gradient(closest-side, #000 12%, rgba(0, 0, 0, .55) 42%, transparent 76%);
    animation: a1-rays 15s linear infinite;
}
@keyframes a1-rays { to { transform: rotate(360deg); } }

.a1-clear-txt {
    position: relative; font-size: 24px; font-weight: 900; letter-spacing: .16em;
    text-transform: uppercase; color: #fff;
    text-shadow: 0 0 22px rgba(53, 224, 245, .9), 0 0 46px rgba(53, 224, 245, .45);
}
.a1-clear-sub {
    position: relative; margin-top: 8px; font-size: 13px; letter-spacing: .1em;
    color: var(--a1-ink-2);
}
/* The door that just opened. Amber against the plate's cyan, because it is a
   different kind of news: the cyan says what you finished, the amber says where
   you can go — the same amber the map lights the next place with. */
.a1-clear-open {
    position: relative; display: grid; gap: 3px; justify-items: center;
    margin: 14px auto 0; padding: 10px 16px; max-width: 300px;
    border: 1px solid rgba(255, 207, 107, 0.34); border-radius: 12px;
    background: rgba(255, 207, 107, 0.07);
    animation: a1-open-in .5s .5s cubic-bezier(.2, .8, .2, 1) both;
}
.a1-clear-open b {
    font-size: 11px; font-weight: 800; letter-spacing: .16em;
    color: var(--a1-amber);
}
.a1-clear-open span { font-size: 13.5px; color: var(--a1-ink); }
@keyframes a1-open-in {
    from { opacity: 0; transform: translateY(8px) scale(.97); }
    to   { opacity: 1; transform: none; }
}

/* the rule that stamps under the words */
.a1-clear-rule {
    position: relative; height: 2px; margin: 14px auto 0; max-width: 240px;
    background: linear-gradient(90deg, transparent, var(--a1-cyan), transparent);
    transform-origin: 50% 50%;
    animation: a1-draw .6s .18s cubic-bezier(.2, .8, .2, 1) both;
}
@keyframes a1-draw { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: 1; } }

/* ---------- art ----------
   Three placements, one rule behind all of them: the picture ends by dissolving
   into the page, never at a hard edge. A rectangle that stops is a stock photo
   in a box; a rectangle that fades is a scene the text is standing in.
   Everything here is injected by act1-fx.js and degrades to nothing. */

/* The cover ships at 3:1 and runs at its natural height, so it is never
   re-cropped by a viewport we did not choose — see scripts/act1_art_build.py. */
.a1-key {
    position: relative; margin: 0 0 22px; border-radius: 16px; overflow: hidden;
    border: 1px solid var(--a1-line); background: #070c16; line-height: 0;
}
.a1-key-img { display: block; width: 100%; height: auto; }
.a1-key::after {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background:
        linear-gradient(180deg, rgba(8, 11, 20, .10) 0%, rgba(8, 11, 20, 0) 34%,
                                rgba(8, 11, 20, .55) 76%, rgba(8, 11, 20, .92) 100%);
}
/* The gate's 70px of top air was sized for a page that opened on a heading. */
#a1-gate:has(.a1-key) { padding-top: 18px; }

/* the quest-node tile on a hub card */
.a1-ch-art {
    flex: 0 0 46px; width: 46px; height: 46px; border-radius: 11px;
    object-fit: cover; background: #070c16;
    border: 1px solid var(--a1-line);
    /* undone chapters sit slightly back; the one you are on comes forward */
    filter: saturate(.75) brightness(.85);
    transition: transform .18s ease, border-color .18s ease, filter .18s ease;
}
.a1-ch:hover .a1-ch-art { transform: scale(1.06); border-color: #2e3f63; filter: none; }
.a1-ch.current .a1-ch-art { border-color: rgba(53, 224, 245, .45); filter: none; }
.a1-ch.done .a1-ch-art { border-color: rgba(52, 211, 153, .4); filter: saturate(1) brightness(.95); }

/* The chapter's plate, beside its title. Grid rather than a wrapper element:
   act1.js owns the kicker and the h1 and writes to them by id every open, so
   the art is laid out around them instead of moving them. :has() keeps the
   plain stacked header for anyone whose art did not arrive. */
.a1-c-head:has(.a1-c-art) {
    display: grid; grid-template-columns: 120px 1fr;
    column-gap: 18px; align-items: center;
}
.a1-c-art {
    grid-row: 1 / 3; grid-column: 1;
    width: 120px; height: 120px; border-radius: 16px; object-fit: cover;
    background: #070c16; border: 1px solid #223250;
    box-shadow: 0 10px 30px rgba(0, 0, 0, .5), 0 0 30px rgba(53, 224, 245, .10);
}
.a1-c-head:has(.a1-c-art) .a1-c-kicker { grid-column: 2; align-self: end; }
.a1-c-head:has(.a1-c-art) h1 { grid-column: 2; align-self: start; margin-top: 7px; }

/* the Stoker's face, in the line that names him */
.a1-stoker-who { display: flex; align-items: center; gap: 10px; }
.a1-face {
    flex: 0 0 38px; width: 38px; height: 38px; border-radius: 50%;
    object-fit: cover; background: #070c16;
    border: 1px solid rgba(255, 207, 107, .45);
    box-shadow: 0 0 14px rgba(255, 207, 107, .16);
}
/* he is lit while he is talking */
.a1-stoker.typing .a1-face { animation: a1-face-lit 1.5s ease-in-out infinite; }
@keyframes a1-face-lit {
    0%, 100% { box-shadow: 0 0 12px rgba(255, 207, 107, .14); }
    50%      { box-shadow: 0 0 22px rgba(255, 207, 107, .45); }
}

@media (max-width: 560px) {
    .a1-c-head:has(.a1-c-art) { grid-template-columns: 86px 1fr; column-gap: 14px; }
    .a1-c-art { width: 86px; height: 86px; border-radius: 13px; }
    /* A full-width tappable card does not also need the word "Open" — and the
       art earns that space back for the chapter's name, which is the thing a
       person is actually reading down the list to find. */
    .a1-ch-art { flex-basis: 42px; width: 42px; height: 42px; }
    .a1-ch:has(.a1-ch-art) .a1-ch-go { display: none; }

    /* Same reasoning one level down: a phone taps the whole lane, so the
       chevron is only spending width — and it was spending exactly enough to
       wrap every hint onto a second line. */
    .a1-lane { padding-right: 16px; }
    .a1-lane::before { display: none; }
    .a1-lane-i { flex-basis: 36px; width: 36px; height: 36px; font-size: 18px; }
}

/* ---------- the task screens ----------
   The hub is a road and the chapter is a scene, and then the member pressed
   Start and landed on a form. Everything below exists to keep the world running
   through the three screens where the actual work happens. */

/* The head was a flex row built for a title and a step count. With a track
   under it, it becomes a stacked header — :has() so a browser without it (or a
   page where act1-fx.js never arrived) keeps the original row. */
.a1-t-head:has(.a1-track) { display: block; }

/* Read out loud: dot — line — dot — line — dot, lit as far as you have walked.
   The same grammar as the hub's quest path, one level down. */
.a1-track { display: flex; align-items: center; margin: 11px 0 2px; }
.a1-track-s {
    display: flex; align-items: center; gap: 7px;
    font-size: 11.5px; letter-spacing: .02em; color: #4d5f80;
    white-space: nowrap;
}
.a1-track-s + .a1-track-s { margin-left: 9px; }
.a1-track-s + .a1-track-s::before {
    content: ''; flex: 0 0 18px; height: 1px; background: #17223a;
}
.a1-track-s i {
    width: 9px; height: 9px; flex: 0 0 auto;
    transform: rotate(45deg); border-radius: 2px;
    background: #131c30; box-shadow: inset 0 0 0 1px #24314e;
}

.a1-track-s.done { color: #6f8aa8; }
.a1-track-s.done i {
    background: rgba(52, 211, 153, .9);
    box-shadow: 0 0 10px rgba(52, 211, 153, .35);
}
.a1-track-s.done + .a1-track-s::before,
.a1-track-s.now + .a1-track-s::before { background: rgba(52, 211, 153, .45); }

.a1-track-s.now { color: #cfe6ff; font-weight: 600; }
.a1-track-s.now i {
    background: var(--a1-cyan);
    animation: a1-beacon 2.1s ease-in-out infinite;
}

/* The plain "Step 1 of 3" once the track is up: gone from the page, still in
   the accessibility tree. */
.a1-sr {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- the four lanes as choices ----------
   They were four rows with an emoji floating loose at the left edge. An emoji
   on a plate is a sigil; an emoji in a list is a bullet point. */
.a1-lane { position: relative; padding-right: 32px; }
.a1-lane-i {
    display: grid; place-items: center;
    flex: 0 0 38px; width: 38px; height: 38px;
    border-radius: 11px; font-size: 19px;
    background:
        radial-gradient(circle at 50% 28%, rgba(255, 207, 107, .16), transparent 68%),
        #0a1020;
    box-shadow: inset 0 0 0 1px rgba(255, 207, 107, .2);
    transition: box-shadow .18s ease, transform .18s ease;
}
.a1-lane:hover .a1-lane-i {
    box-shadow: inset 0 0 0 1px rgba(255, 207, 107, .45), 0 0 18px rgba(255, 207, 107, .16);
    transform: translateY(-1px);
}

/* A choice that leads somewhere should point there. */
.a1-lane::before {
    content: '›'; position: absolute; right: 15px; top: 50%;
    transform: translateY(-50%); color: #3c4d70; font-size: 19px; line-height: 1;
    transition: color .18s ease, transform .18s ease;
}
.a1-lane:hover::before { color: var(--a1-cyan); transform: translate(3px, -50%); }

/* the hub's edge-light, in the direction of travel */
.a1-lane::after {
    content: ''; position: absolute; left: 0; top: 12px; bottom: 12px; width: 2px;
    border-radius: 2px;
    background: linear-gradient(180deg, var(--a1-cyan), #7ad9ff);
    opacity: 0; transform: scaleY(.3);
    transition: opacity .18s ease, transform .18s ease;
}
.a1-lane:hover::after { opacity: .9; transform: scaleY(1); }

/* What the next three minutes buy. The screen asked for work and named no
   reward; this is the smallest honest version of one. */
.a1-stakes {
    margin: 16px 0 2px; text-align: center;
    font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase;
    color: #55688c;
}

/* ---------- reduced motion ----------
   Not a token gesture: everything above is decorative, so under this query the
   page should be exactly as usable and completely still. */

@media (prefers-reduced-motion: reduce) {
    #a1-embers { display: none; }
    .a1-enter, .a1-clear, .a1-clear-rule, .a1-clear-open, .a1-run-text.landed { animation: none; }
    .a1-sweep::after, .a1-bar-fill::after, .a1-clear-rays { animation: none; display: none; }
    .a1-ch.current .a1-ch-n, .a1-stoker.typing, .a1-run.casting { animation: none; }
    /* the step you are on still reads as the step you are on — lit, not pulsing */
    .a1-track-s.now i { animation: none; box-shadow: 0 0 12px rgba(53, 224, 245, .5); }
    .a1-lane:hover .a1-lane-i { transform: none; }
    .a1-lane:hover::before { transform: translateY(-50%); }
    .a1-run.casting::before { display: none; }
    /* act1-fx.js never adds these two under reduced motion, so this is belt and
       braces — but a blinking cursor is exactly what this query exists to stop,
       and it should not depend on the JS getting it right. */
    .a1-bar-fill.gain { animation: none; }
    .a1-stoker-said .a1-cursor { animation: none; display: none; }
    .a1-float { display: none; }
    .a1-btn:hover:not(:disabled) { transform: none; }
    .a1-lane:active, .a1-ch:active { transform: none; }
    /* The art stays — a still picture is not motion. Only its movement goes. */
    .a1-stoker.typing .a1-face { animation: none; }
    .a1-ch:hover .a1-ch-art { transform: none; }
}
