@font-face { font-family: "PxVGA"; src: url("./fonts/WebPlus_IBM_VGA_8x16.woff") format("woff"); font-display: swap; } @font-face { font-family: "PxSmall"; src: url("./fonts/Web437_IBM_VGA_8x14.woff") format("woff"); font-display: swap; } :root { color-scheme: light; --paper: #f5f1e6; --panel: #efe9d8; --ink: #1b1a15; --soft: #726b58; --line: #d6ccb2; --accent: #5f7a45; --light-sq: #eae1c6; --dark-sq: #bda06d; --sel: #d7cc9d; --check: #b24b43; --mono: "PxVGA", "Consolas", "Courier New", monospace; --small: "PxSmall", var(--mono); --sq: 62px; } * { box-sizing: border-box; } html, body { margin: 0; min-height: 100%; background: var(--paper); } body { color: var(--ink); font-family: var(--mono); font-size: 16px; line-height: 24px; -webkit-font-smoothing: none; -moz-osx-font-smoothing: unset; } #app { max-width: 900px; margin: 0 auto; padding: 40px 22px 80px; } a { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--soft); } button { font-family: inherit; font-size: inherit; color: inherit; cursor: pointer; background: none; border: none; padding: 0; } .soft { color: var(--soft); } @keyframes blink { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0; } } .cursor { color: var(--accent); animation: blink 1.1s step-end infinite; } /* Home — a text-mode title screen */ .home { padding-top: 40px; } .home .logo { margin: 0 0 14px; font-family: var(--mono); font-size: clamp(7px, 2.15vw, 15px); line-height: 1; color: var(--ink); white-space: pre; overflow-x: auto; } .home .kicker { color: var(--soft); margin: 0 0 40px; padding-left: 2px; } .home .kicker::before { content: "// "; color: var(--accent); } .menu { display: block; } .menu button { display: block; width: 100%; text-align: left; padding: 3px 0; letter-spacing: 1px; } .menu button::before { content: " "; white-space: pre; color: var(--accent); } .menu button:hover::before, .menu button:focus-visible::before { content: "> "; } .menu button:hover, .menu button:focus-visible { color: var(--accent); outline: none; } /* Screen chrome */ .topbar { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 26px; } .topbar h2 { margin: 0; font-family: var(--mono); font-size: 22px; font-weight: normal; } .topbar h2::before { content: "# "; color: var(--accent); } .back { color: var(--soft); } .back:hover { color: var(--ink); } /* Setup */ .setup { max-width: 560px; } .field { margin-bottom: 30px; } .field > label { display: block; color: var(--soft); margin-bottom: 10px; } .field > label::before { content: ": "; color: var(--accent); } .toggle { display: flex; gap: 4px; flex-wrap: wrap; } .toggle button { padding: 2px 6px; color: var(--soft); } .toggle button::before { content: "[ "; } .toggle button::after { content: " ]"; } .toggle button:hover { color: var(--ink); } .toggle button[aria-pressed="true"] { color: var(--ink); } .toggle button[aria-pressed="true"]::before { content: "[x "; color: var(--accent); } .toggle button[aria-pressed="true"]::after { content: " ]"; color: var(--accent); } /* Opponent list — a plain menu with dotted leaders, not cards */ .pick { display: block; } .pick .row { display: flex; align-items: baseline; width: 100%; text-align: left; padding: 7px 0; gap: 10px; border-bottom: 1px solid var(--line); } .pick .row:first-child { border-top: 1px solid var(--line); } .pick .cur { width: 1ch; color: var(--accent); white-space: pre; } .pick .face { font-family: system-ui, sans-serif; } .pick .name { color: var(--ink); } .pick .blurb { color: var(--soft); } .pick .leader { flex: 1; border-bottom: 1px dotted var(--line); transform: translateY(-4px); } .pick .row:hover .name { color: var(--accent); } .pick .row[aria-pressed="true"] .cur::before { content: ">"; } .pick .row[aria-pressed="true"] .name { color: var(--accent); } @media (max-width: 560px) { .pick .blurb { display: none; } } .custom { margin-top: 10px; display: flex; gap: 18px; color: var(--soft); } .custom.hidden { display: none; } .custom input { width: 5ch; font-family: var(--mono); font-size: 16px; background: var(--panel); border: 1px solid var(--line); color: var(--ink); padding: 2px 4px; } .load { width: 100%; font-family: var(--small); font-size: 14px; background: var(--panel); border: 1px solid var(--line); color: var(--ink); padding: 8px; resize: vertical; } .start { margin-top: 10px; font-size: 18px; letter-spacing: 2px; background: var(--ink); color: var(--paper); padding: 10px 22px; box-shadow: 3px 3px 0 var(--line); transition: transform .06s ease, box-shadow .06s ease; } .start::before { content: "> "; } .start:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--line); } .start:hover::after { content: " _"; } .start:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--line); } /* Play */ .play { display: grid; grid-template-columns: auto 240px; gap: 30px; align-items: start; } .arena { display: flex; flex-direction: column; gap: 8px; } .board-row { display: flex; gap: 10px; align-items: stretch; } @media (max-width: 720px) { .play { grid-template-columns: 1fr; } :root { --sq: min(10.6vw, 62px); } .board-row { justify-content: center; } } /* HUD — one row per combatant: face, name, their fallen, and their clock */ .hud { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; min-height: 26px; } .hud .face { font-family: system-ui, "Segoe UI Emoji", sans-serif; font-size: 20px; display: inline-block; } .hud .who { color: var(--ink); } .hud .captured { display: flex; flex-wrap: wrap; gap: 1px; align-items: baseline; font-family: system-ui, "Segoe UI Emoji", sans-serif; font-size: 15px; line-height: 1; } .hud .captured .cap.trad.w { color: #fbf7ec; text-shadow: 0 0 1px #000, 0 1px 1px rgba(0,0,0,.45); } .hud .captured .cap.trad.b { color: #141109; } .hud .mat { color: var(--accent); font-size: 14px; } .hud.thinking .face { animation: bob 0.8s ease infinite; } @keyframes bob { 50% { transform: translateY(-3px); } } /* Clock — the digits ride on a draining time bar */ .hud .clock { position: relative; margin-left: auto; overflow: hidden; display: inline-flex; padding: 2px 10px; border: 1px solid var(--line); min-width: 7ch; justify-content: flex-end; } .clock .bar { position: absolute; left: 0; top: 0; bottom: 0; width: 100%; background: var(--accent); opacity: 0.16; transition: width 0.1s linear; } .clock .time { position: relative; font-size: 20px; letter-spacing: 1px; } .clock.running { border-color: var(--ink); } .clock.low .time { color: var(--check); } .clock.low { animation: pulse 0.5s step-end infinite; } @keyframes pulse { 50% { border-color: var(--check); } } .clock.flag { opacity: 0.5; } /* Eval bar — white's share of the position, from the bottom up */ .evalwrap { display: flex; flex-direction: column; align-items: center; gap: 4px; } .evalbar { width: 12px; flex: 1; border: 1px solid var(--line); background: #1b1a15; display: flex; flex-direction: column; justify-content: flex-end; } .evalfill { width: 100%; height: 50%; background: #f5f1e6; transition: height 0.45s ease; } .evalnum { font-family: var(--small); font-size: 12px; color: var(--soft); } /* Last-move arrow */ .arrows { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 4; pointer-events: none; } .arrow { stroke: var(--accent); stroke-width: 0.22; stroke-linecap: round; fill: none; opacity: 0.55; } polygon.arrow { fill: var(--accent); stroke: none; } .board-wrap { width: calc(var(--sq) * 8); } .board { position: relative; width: calc(var(--sq) * 8); height: calc(var(--sq) * 8); border: 1px solid var(--ink); display: grid; grid-template-columns: repeat(8, var(--sq)); grid-template-rows: repeat(8, var(--sq)); user-select: none; touch-action: manipulation; } .sq { position: relative; display: flex; align-items: center; justify-content: center; } .sq.light { background: var(--light-sq); } .sq.dark { background: var(--dark-sq); } .sq .coord { position: absolute; font-family: var(--small); font-size: 14px; line-height: 1; color: rgba(0,0,0,.4); } .sq.dark .coord { color: rgba(255,255,255,.5); } .sq .coord.file { right: 3px; bottom: 2px; } .sq .coord.rank { left: 3px; top: 1px; } .sq.last::before { content: ""; position: absolute; inset: 0; background: var(--sel); opacity: .5; mix-blend-mode: multiply; } .sq.sel::after { content: ""; position: absolute; inset: 0; background: var(--sel); opacity: .7; mix-blend-mode: multiply; } .sq.check::after { content: ""; position: absolute; inset: 0; background: radial-gradient(circle, var(--check) 55%, transparent 72%); opacity: .5; } .dot { position: absolute; width: 26%; height: 26%; border-radius: 50%; background: rgba(0,0,0,.24); pointer-events: none; } .dot.capture { width: 82%; height: 82%; background: none; border: 3px solid rgba(0,0,0,.24); } /* Pieces */ .piece { position: absolute; width: var(--sq); height: var(--sq); display: flex; align-items: center; justify-content: center; font-family: system-ui, "Segoe UI Emoji", sans-serif; font-size: calc(var(--sq) * .64); line-height: 1; cursor: grab; z-index: 2; transition: transform .26s cubic-bezier(.2,.7,.25,1); will-change: transform; } .piece .glyph { position: relative; z-index: 2; } /* Emoji share one glyph per type, so the light and dark armies are told apart by the token each piece rides on, not by the emoji's own colors. */ /* A grounding shadow plus a hairline light rim, so a mid-tone emoji (a grey wolf, say) still reads on the dark army's token without being darkened into it. */ .piece:not(.traditional) .glyph { filter: drop-shadow(0 1px 1px rgba(0,0,0,.4)) drop-shadow(0 0 .5px rgba(255,255,255,.55)); } .piece::before { content: ""; position: absolute; inset: 9%; border-radius: 50%; z-index: 1; } .piece.w::before { background: radial-gradient(circle at 38% 32%, #fffdf6, #e7dcbe 78%); box-shadow: 0 2px 4px rgba(0,0,0,.22), inset 0 0 0 2px rgba(90,70,28,.5); } .piece.b::before { background: radial-gradient(circle at 38% 32%, #3b362a, #14110b 78%); box-shadow: 0 2px 4px rgba(0,0,0,.38), inset 0 0 0 2px rgba(243,238,220,.5); } .piece.traditional { font-family: var(--mono); font-size: calc(var(--sq) * .8); } .piece.traditional.w { color: #fbf7ec; text-shadow: 0 0 1px #000, 0 1px 1px rgba(0,0,0,.45); } .piece.traditional.b { color: #141109; } .piece.traditional::before { display: none; } .piece.dragging { z-index: 30; transition: none; cursor: grabbing; } .piece.captured { transition: transform .2s ease, opacity .2s ease; opacity: 0; transform: scale(.4); } .piece.hop .glyph { animation: hop .34s ease; } @keyframes hop { 0% { transform: translateY(0); } 45% { transform: translateY(-24%); } 100% { transform: translateY(0); } } .piece.threat::after { content: ""; position: absolute; inset: 3%; border: 2px solid var(--check); border-radius: 50%; opacity: .9; z-index: 3; pointer-events: none; } .puff { position: absolute; width: var(--sq); height: var(--sq); display: flex; align-items: center; justify-content: center; font-family: system-ui, "Segoe UI Emoji", sans-serif; font-size: calc(var(--sq) * .5); z-index: 20; pointer-events: none; animation: puff .48s ease forwards; } @keyframes puff { from { opacity: .8; transform: scale(.7); } to { opacity: 0; transform: scale(1.3); } } .reaction { position: absolute; width: var(--sq); height: var(--sq); display: flex; align-items: center; justify-content: center; font-family: system-ui, "Segoe UI Emoji", sans-serif; font-size: calc(var(--sq) * .42); z-index: 25; pointer-events: none; transition: transform .8s ease, opacity .8s ease; } .reaction.rise { transform: translateY(-40%); opacity: 0; } /* Assist + coach */ .assist { display: flex; flex-wrap: wrap; gap: 4px 12px; } .assist button { color: var(--soft); } .assist button::before { content: "[ "; color: var(--line); } .assist button::after { content: " ]"; color: var(--line); } .assist button[aria-pressed="true"] { color: var(--accent); } .assist button[aria-pressed="true"]::before { content: "[x "; color: var(--accent); } .assist button:hover { color: var(--ink); } .coach { min-height: 0; color: var(--ink); border-left: 2px solid var(--accent); padding-left: 12px; } .coach:empty { display: none; } /* Panel */ .panel { display: flex; flex-direction: column; gap: 20px; } .status { min-height: 24px; } .status .turn-dot { display: inline-block; width: .55em; height: .55em; margin-right: .4em; border: 1px solid var(--ink); } .status .turn-dot.w { background: var(--paper); } .status .turn-dot.b { background: var(--ink); } .thinking { color: var(--accent); } .moves { max-height: 260px; overflow-y: auto; font-family: var(--small); font-size: 14px; line-height: 18px; } .moves ol { margin: 0; padding: 0; list-style: none; } .moves li { display: grid; grid-template-columns: 2.4em 1fr 1fr; } .moves .no { color: var(--soft); } .moves .san.here { color: var(--accent); } .controls { display: flex; flex-wrap: wrap; gap: 4px 14px; } .controls button { color: var(--soft); } .controls button::before { content: "["; color: var(--line); } .controls button::after { content: "]"; color: var(--line); } .controls button:hover { color: var(--ink); } .controls button:disabled { color: var(--line); cursor: default; } /* Promotion */ .promo { position: absolute; z-index: 40; background: var(--paper); border: 1px solid var(--ink); display: flex; flex-direction: column; } .promo button { width: var(--sq); height: var(--sq); font-family: system-ui, "Segoe UI Emoji", sans-serif; font-size: calc(var(--sq) * .6); } .promo button:hover { background: var(--sel); } /* About */ .about { max-width: 560px; line-height: 26px; } .about .card { margin-top: 20px; padding-left: 14px; border-left: 2px solid var(--accent); } .about .kill { margin-top: 26px; color: var(--accent); letter-spacing: 1px; } /* Dossier — the selected opponent's character sheet */ .dossier { display: flex; gap: 14px; align-items: flex-start; border: 1px solid var(--line); background: var(--panel); padding: 12px 14px; margin: -14px 0 30px; } .d-face { font-size: 34px; font-family: system-ui, "Segoe UI Emoji", sans-serif; line-height: 1; } .d-name { color: var(--ink); } .d-blurb { color: var(--soft); font-size: 14px; margin-bottom: 8px; } .d-stats { display: flex; flex-direction: column; gap: 2px; font-size: 14px; color: var(--soft); } .d-stats span { white-space: pre; } /* Buttons with some heft */ .btn { font-family: inherit; font-size: 16px; letter-spacing: 1px; background: var(--ink); color: var(--paper); padding: 8px 16px; box-shadow: 3px 3px 0 var(--line); transition: transform .06s ease, box-shadow .06s ease; } .btn:hover { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--line); } .btn:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--line); } .btn.ghost { background: none; color: var(--soft); box-shadow: none; border: 1px solid var(--line); } .btn.ghost:hover { color: var(--ink); border-color: var(--ink); transform: none; } /* The fallen king and the flinching board */ .piece.defeated .glyph { filter: grayscale(1) brightness(.75); transform: rotate(80deg); transition: transform .4s ease, filter .4s ease; } .board.shake { animation: shake .5s ease; } @keyframes shake { 0%, 100% { transform: translate(0, 0); } 20% { transform: translate(-6px, 2px); } 40% { transform: translate(5px, -3px); } 60% { transform: translate(-4px, -2px); } 80% { transform: translate(3px, 2px); } } .reaction.stay { font-size: calc(var(--sq) * .56); } /* Game over — the curtain call */ .gameover { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--paper) 82%, transparent); z-index: 100; } .gameover.hidden { display: none; } .go-box { background: var(--paper); border: 2px solid var(--ink); box-shadow: 6px 6px 0 var(--line); padding: 34px 44px; text-align: center; max-width: min(92vw, 480px); animation: go-in .3s ease; } @keyframes go-in { from { transform: translateY(14px); opacity: 0; } } .go-banner { font-family: var(--mono); font-size: clamp(24px, 6vw, 40px); letter-spacing: 4px; margin: 0 0 10px; color: var(--ink); } .go-banner::after { content: "_"; color: var(--accent); animation: blink 1.1s step-end infinite; } .go-detail { margin: 0 0 6px; font-size: 18px; } .go-meta { color: var(--soft); margin: 0 0 22px; font-size: 14px; } .go-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-bottom: 14px; } .go-pgn { color: var(--soft); } .go-pgn:hover { color: var(--ink); } /* CRT theme — phosphor green on glass, scanlines and all */ [data-theme="crt"] { color-scheme: dark; --paper: #0b0e08; --panel: #0f140b; --ink: #b6f2a3; --soft: #5d8a52; --line: #24381d; --accent: #56d364; --light-sq: #1a2b14; --dark-sq: #0d170a; --sel: #39512a; --check: #e5534b; } [data-theme="crt"] body { text-shadow: 0 0 7px rgba(86, 211, 100, .3); } [data-theme="crt"] body::after { content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 9999; background: repeating-linear-gradient(0deg, rgba(0, 0, 0, .22) 0 1px, transparent 1px 3px); } [data-theme="crt"] .home .logo { text-shadow: 0 0 12px rgba(86, 211, 100, .6); } [data-theme="crt"] .dot { background: rgba(182, 242, 163, .3); } [data-theme="crt"] .dot.capture { background: none; border-color: rgba(182, 242, 163, .3); } [data-theme="crt"] .sq .coord { color: rgba(182, 242, 163, .45); } [data-theme="crt"] .sq.dark .coord { color: rgba(182, 242, 163, .55); } [data-theme="crt"] .evalbar { border-color: var(--line); } [data-theme="crt"] .piece.traditional.w { color: #e8f5e0; }