:root{ /* ink3 is the smallest type on the page; it has to clear 4.5:1 on white. */ --ink:#1a2430; --ink2:#516678; --ink3:#647385; --ink4:#5a6974; /* One source of truth for the two-column width, so the panels below the stage line up with it instead of running the full window. */ --rail:clamp(270px,25vw,340px); --boardmax:calc((100svh - 205px) * .92); --stage-w:calc(var(--boardmax) + var(--rail) + 16px); --page:#eceff2; --card:#fff; --line:#d6dee5; --line2:#c2ccd6; /* --live fills bars and the dark board; --live-ink is the readable text version. */ --accent:#3f6d95; --live:#2ea36a; --live-ink:#1f7d50; --warn:#c2762a; --bad:#b8412b; --board-bg:#11181f; --board-ink:#e8eef4; --board-dim:#8798a8; font-family:system-ui,-apple-system,"PingFang SC",sans-serif;color:var(--ink);background:var(--page);font-synthesis:none; } *{box-sizing:border-box} body{margin:0;padding-bottom:28px} h1,h2{margin:0} code{font-family:ui-monospace,Menlo,monospace;font-size:.92em;background:#e2e8ee;padding:1px 4px;border-radius:3px} /* ---------- top bar ---------- */ .topbar{display:flex;align-items:center;gap:14px;padding:10px 20px;background:var(--card);border-bottom:1px solid var(--line);flex-wrap:wrap} .back{color:var(--ink2);text-decoration:none;font-size:13px;white-space:nowrap} .badge{font-size:10px;letter-spacing:.14em;color:var(--ink3);border:1px solid var(--line2);border-radius:20px;padding:5px 11px;margin-left:auto;white-space:nowrap} .picker{display:flex;align-items:center;gap:9px;flex:1;min-width:280px;font-size:12px;color:var(--ink2)} .picker select{font:13px system-ui;padding:6px 9px;border:1px solid var(--line2);border-radius:8px;background:#fff;color:var(--ink);flex:1;min-width:0;max-width:560px} .toggle{display:inline-flex;align-items:center;gap:6px;cursor:pointer;white-space:nowrap} input[type=checkbox]{accent-color:var(--accent)} button{font:14px system-ui;color:var(--ink2);background:#f7fafc;border:1px solid var(--line2);border-radius:10px;padding:7px 12px;cursor:pointer} button:hover{background:#fff;color:var(--ink)} button:disabled{opacity:.4;cursor:default} button.small{font-size:12px;padding:5px 10px;border-radius:8px} button:focus-visible,a:focus-visible,select:focus-visible,input:focus-visible,.steps:focus-visible{outline:3px solid var(--accent);outline-offset:2px} /* Reachable by screen readers, invisible on screen. */ .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0} /* ---------- stage: board left, live rail right ---------- */ /* The board is a portrait hexagon, so its card is height-bound: cap the board column near the height it can actually use and centre the pair, instead of stretching a card whose middle 40% would be empty. */ .stage{display:grid;grid-template-columns:minmax(0,var(--boardmax)) var(--rail); gap:16px;padding:16px 20px 0;align-items:stretch;justify-content:center} .board-col{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px;display:flex;flex-direction:column;min-width:0} /* The canvas is taken out of flow on purpose. Left in flow, `height:100%` degrades to auto during intrinsic sizing and the box falls back to the canvas *attribute* ratio — which resize() writes from this very box. That loop let a detour through a short window permanently inflate the card. */ .board-wrap{flex:1;width:100%;position:relative;min-height:clamp(340px,calc(100svh - 235px),760px)} canvas{position:absolute;inset:0;width:100%;height:100%;display:block} /* Only surfaces when a run's JSON can't be read — otherwise it takes no space. */ .notice{margin:10px 0 0;text-align:center;font-size:13px;line-height:1.6;color:var(--bad)} /* 谁在玩:棋盘正上方的身份横幅 */ .who{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap; padding:0 2px 10px;margin-bottom:10px;border-bottom:1px solid var(--line)} .who-id{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;min-width:0} .who-id b{font-size:19px;font-weight:700;letter-spacing:-.01em} /* Agents self-report free text, so assume the worst: a slug or user-agent string with no spaces has to break rather than push the page sideways. */ .who-agent{font-size:13px;color:var(--accent);font-weight:600;min-width:0;overflow-wrap:anywhere} /* Hang the separator off the model name. As ::before on the agent it wrapped to the start of the next line and read like a bullet. */ .who-id:has(.who-agent:not(:empty)) b::after{content:'·';margin-left:10px;color:var(--ink3);font-weight:400} .who-level{font-size:11.5px;color:var(--ink3);font-variant-numeric:tabular-nums} .rail{display:flex;flex-direction:column;gap:16px;min-width:0;min-height:0} /* ---------- scoreboard: a vertical panel beside the board ---------- */ .scoreboard{background:var(--board-bg);color:var(--board-ink);border-radius:14px;padding:16px; display:flex;flex-direction:column;gap:13px;flex:0 0 auto;transition:opacity .2s} /* Rewound: the clock and the totals belong to the whole run, so they no longer describe the board sitting right next to them. Say so instead of lying. */ .scoreboard.rewound{opacity:.55} @media(prefers-reduced-motion:reduce){.scoreboard{transition:none}} .scoreboard .clock{display:flex;flex-direction:column;gap:4px;min-width:0} .scoreboard .k{font-size:10px;letter-spacing:.12em;color:var(--board-dim);text-transform:uppercase;white-space:nowrap} .scoreboard b{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-variant-numeric:tabular-nums;font-weight:600;line-height:1;font-size:19px} .scoreboard small{font-size:11px;color:var(--board-dim);margin-left:4px;font-family:ui-monospace,Menlo,monospace} .clock.primary b{font-size:clamp(32px,3.1vw,42px);letter-spacing:-.02em} .clock.think b{font-size:23px;color:#f0c674} .tick.hot{color:#ffcf6b} /* Each readout is a wrapping row: the label owns a line, the number keeps its suffix beside it. Stacking all three (a flex column blockifies `inline`) cost a third line per cell and made the panel needlessly tall. */ .scoreboard .stats{display:grid;grid-template-columns:1fr 1fr;gap:12px 12px;border-top:1px solid #27323d;padding-top:13px} .scoreboard .cell{display:flex;flex-wrap:wrap;align-items:baseline;min-width:0} .scoreboard .cell .k{flex:0 0 100%;margin-bottom:4px} .scoreboard .bar{height:5px;background:#27323d;border-radius:3px;overflow:hidden} .scoreboard .bar i{display:block;height:100%;background:var(--live);width:0;transition:width .3s} .pill{align-self:flex-start;font-size:12px;font-weight:600;letter-spacing:.04em;padding:7px 14px;border-radius:20px;white-space:nowrap;background:#25303b;color:var(--board-dim)} .pill.rewound{background:#2b3742;color:#c7d4de} .pill.playing{background:rgba(46,163,106,.18);color:#5ed69b} .pill.playing::before{content:'●';margin-right:6px;animation:blink 1.4s infinite} .pill.cleared{background:rgba(46,163,106,.25);color:#7fe6b4} .pill.gaveup{background:rgba(184,65,43,.22);color:#ff9b85} .pill.budget{background:rgba(194,118,42,.22);color:#f3c98a} @keyframes blink{50%{opacity:.25}} /* ---------- live feed ---------- */ .feed-col{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px;display:flex;flex-direction:column;flex:1 1 auto;min-height:200px} /* The button and the live state are siblings of the heading, not inside it — nested, they became part of the heading's accessible name. */ .feed-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px} .feed-col h2{font-size:11px;letter-spacing:.16em;color:var(--ink3);text-transform:uppercase} .feed-state{font-size:10.5px;letter-spacing:.02em;color:var(--live-ink);font-weight:600} .back-live{margin-left:auto;color:var(--accent);border-color:var(--accent)} .steps{margin:0;padding:0;list-style:none;overflow-y:auto;flex:1 1 0;min-height:0;font-size:12.5px;counter-reset:step} .steps li{padding:8px 10px 8px 32px;border-bottom:1px solid #eef2f5;position:relative;cursor:pointer;line-height:1.55;counter-increment:step} .steps li::before{content:counter(step);position:absolute;left:8px;top:8px;font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--ink3)} .steps li:last-child{border-bottom:none} .steps li.lead{border-top:1px solid var(--line);margin-top:2px} .steps li:first-child.lead{border-top:none;margin-top:0} .steps li b{font-weight:600} .steps li i{display:block;color:var(--ink2);font-style:normal;font-size:11.5px;margin-top:2px} .steps li em{float:right;margin-left:8px;font-style:normal;font-size:10.5px;color:var(--accent);background:#eaf1f7;border-radius:9px;padding:1px 7px;font-variant-numeric:tabular-nums;font-weight:600} .steps li.current{background:#eaf1f7;box-shadow:inset 3px 0 0 var(--accent)} .steps li:hover{background:#f2f6f9} /* ---------- details below ---------- */ .details{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;padding:16px 20px 0; width:100%;max-width:calc(var(--stage-w) + 40px);margin-inline:auto} .panel{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px} .panel h2{font-size:11px;letter-spacing:.16em;color:var(--ink3);text-transform:uppercase;margin-bottom:10px} .kv{margin:0;font-size:12.5px} .kv>div{display:flex;justify-content:space-between;gap:10px;padding:5px 0;border-bottom:1px dashed #e4eaef} .kv>div:last-child{border-bottom:none} .kv dt{color:var(--ink3)} .kv dd{margin:0;font-variant-numeric:tabular-nums;text-align:right;color:var(--ink);font-weight:600} .kv .giveup dd{color:var(--bad);text-align:left;font-weight:400} footer{text-align:center;margin:20px auto 0;max-width:760px;padding:0 20px;font-size:11px;color:var(--ink4);line-height:1.7} /* The rail needs ~260px to stay readable; below that it goes under the board, where the clock turns into a wide two-up row instead of a tall column. */ @media(max-width:820px){ .stage{grid-template-columns:1fr;align-items:start} /* Stacked, the card is full width, so the board should be tall enough to use it — 62vw left a 603px canvas holding a 330px board. */ .board-wrap{min-height:clamp(280px,min(96vw,64svh),680px)} .scoreboard{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:12px 18px} .scoreboard .clock.primary{grid-column:1} .scoreboard .clock.think{grid-column:2} .clock.primary b{font-size:38px} .scoreboard .stats{grid-column:1 / -1;grid-template-columns:repeat(4,minmax(0,1fr))} .scoreboard .bar,.pill{grid-column:1 / -1} /* Stacked, the rail's height comes from its content, so `flex:1 1 0` resolved to zero and the list vanished — taking the only way to replay a step with it. Give the list its own height instead of a share of a leftover. */ .feed-col{min-height:0} .steps{flex:0 0 auto;max-height:min(340px,52svh)} } @media(max-width:520px){ .topbar{padding:8px 12px;gap:8px} .picker{min-width:100%;order:3} .picker select{max-width:none} .stage,.details{padding-left:12px;padding-right:12px} .board-col,.feed-col,.panel{padding:12px} .scoreboard{padding:14px;gap:11px 14px} .clock.primary b{font-size:34px} .clock.think b{font-size:20px} .scoreboard .stats{grid-template-columns:1fr 1fr} .who-id b{font-size:17px} } /* Below ~400px the two clocks no longer fit on one row: 「本步思考」 gets about 94px and its mono value needs 96. Stack them rather than clip the digits. */ @media(max-width:400px){ .scoreboard .clock.primary,.scoreboard .clock.think{grid-column:1 / -1} .clock.think b{font-size:22px} } @media(prefers-reduced-motion:reduce){.pill.playing::before{animation:none}.scoreboard .bar i{transition:none}}