Train snakeOne-line
GPT-6 Luna
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 9 min 58 s
- Cost
- $0.032
- Output tokens
- 63,808
- Spent thinking
- 44,090 tokens
- Speed
- 107.4 tok/s
- Console errors
- 0
- First token after
- 3.7 s
- First visible text after
- 7 min 40 s
- Prompt tokens
- 361
- Code size
- 59.4 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna- Reasoning sent
{"effort":"max"}- Max output
- 128,000 tokens
- Generated
- Nov 11, 2026
What it was asked
The prompt
“make me a snake game where the snake is a little train with a locomotive and colorful wagons, picking up passengers waiting on the platform”
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task. Output rules: - Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block. - Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task. - Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw. - The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar. - Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction. - Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session. - Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. - The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. - The code must run with zero errors in the console of the latest Chrome.
Code run-1.html · 2196 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Little Line — Passenger Express</title>
<style>
:root {
--ink: #203a3b;
--deep: #173638;
--deepest: #102b31;
--paper: #f4f1e7;
--cream: #fffdf6;
--sage: #e8eddf;
--muted: #78847a;
--orange: #e98955;
--gold: #f2c66d;
--line: #dddccc;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
min-height: 100%;
overflow-x: hidden;
}
body {
color: var(--ink);
background:
radial-gradient(circle at 91% 7%, rgba(236, 176, 108, .14), transparent 230px),
radial-gradient(circle at 3% 94%, rgba(101, 151, 128, .08), transparent 260px),
var(--paper);
font-family: ui-rounded, "Trebuchet MS", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-webkit-font-smoothing: antialiased;
}
button { font: inherit; }
.app-shell {
width: min(100%, 1440px);
margin: 0 auto;
padding: 23px clamp(18px, 3.3vw, 44px) 18px;
}
.topbar {
min-height: 54px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
margin-bottom: 18px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.brand-mark {
display: grid;
place-items: center;
width: 46px;
height: 46px;
flex: 0 0 auto;
color: #f5d27c;
background: var(--deep);
border: 1px solid rgba(255,255,255,.12);
border-radius: 15px;
box-shadow: 0 5px 12px rgba(25, 55, 52, .14);
}
.brand-mark svg { width: 31px; height: 31px; }
.brand-kicker {
color: #758176;
font-size: 9px;
line-height: 1.2;
font-weight: 800;
letter-spacing: 1.45px;
text-transform: uppercase;
margin-bottom: 3px;
}
.brand h1 {
margin: 0;
color: var(--deep);
font-size: 27px;
line-height: 1;
font-weight: 850;
letter-spacing: -1.35px;
}
.brand h1 span {
color: var(--orange);
font-weight: 750;
}
.header-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 11px;
}
.route-token,
.service-pill {
min-height: 34px;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0 12px;
border-radius: 999px;
white-space: nowrap;
font-size: 9px;
line-height: 1;
font-weight: 850;
letter-spacing: .95px;
text-transform: uppercase;
}
.route-token {
color: #657366;
background: rgba(255,255,255,.52);
border: 1px solid rgba(93, 115, 96, .13);
}
.route-token .route-separator { color: #c9a267; }
.route-token svg {
width: 13px;
height: 13px;
color: #cc8253;
}
.service-pill {
color: #f6f4e8;
background: #294b47;
box-shadow: 0 5px 14px rgba(28, 64, 58, .14);
}
.service-led,
.mini-led {
width: 7px;
height: 7px;
flex: 0 0 auto;
border-radius: 50%;
background: #9ed59a;
box-shadow: 0 0 0 3px rgba(158, 213, 154, .14);
animation: led-pulse 1.8s ease-in-out infinite;
}
@keyframes led-pulse {
0%, 100% { box-shadow: 0 0 0 3px rgba(158, 213, 154, .13); }
50% { box-shadow: 0 0 0 6px rgba(158, 213, 154, .04); }
}
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 316px;
align-items: stretch;
gap: 22px;
}
.game-column { min-width: 0; }
.board-meta {
min-height: 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: 0 4px 8px;
color: #66766c;
font-size: 9px;
line-height: 1;
font-weight: 850;
letter-spacing: 1.08px;
text-transform: uppercase;
}
.board-meta-left,
.board-meta-right {
display: inline-flex;
align-items: center;
gap: 9px;
min-width: 0;
}
.board-meta-right { color: #879185; }
.meta-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #e7975b;
box-shadow: 0 0 0 4px rgba(231, 151, 91, .12);
}
.meta-slash { color: #c4a571; }
.board-frame {
position: relative;
padding: 9px;
overflow: hidden;
border: 1px solid rgba(244, 247, 222, .12);
border-radius: 24px;
background: linear-gradient(145deg, #234c49, #122f34 70%);
box-shadow:
0 20px 42px rgba(29, 55, 45, .20),
0 3px 8px rgba(32, 55, 44, .10),
inset 0 1px 0 rgba(255,255,255,.08);
}
.board-frame::after {
position: absolute;
z-index: 2;
inset: 4px;
border: 1px solid rgba(240, 242, 215, .075);
border-radius: 20px;
content: "";
pointer-events: none;
}
#boardCanvas {
display: block;
width: 100%;
height: auto;
aspect-ratio: 3 / 2;
border-radius: 16px;
background: #15373d;
}
.game-overlay {
position: absolute;
z-index: 4;
inset: 9px;
display: flex;
align-items: flex-end;
justify-content: flex-start;
padding: 17px;
border-radius: 16px;
background: linear-gradient(0deg, rgba(7, 24, 27, .2), transparent 43%);
opacity: 1;
visibility: visible;
transition: opacity .24s ease, visibility .24s ease;
}
.game-overlay.is-hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.overlay-card {
position: relative;
width: min(358px, calc(100% - 12px));
padding: 18px 20px 15px;
overflow: hidden;
border: 1px solid rgba(255,255,255,.76);
border-radius: 19px;
color: var(--ink);
background: rgba(250, 248, 236, .96);
box-shadow: 0 12px 34px rgba(6, 24, 28, .25);
backdrop-filter: blur(8px);
cursor: default;
}
.overlay-card::before {
position: absolute;
top: 18px;
bottom: 18px;
left: 0;
width: 3px;
border-radius: 0 4px 4px 0;
background: var(--orange);
content: "";
}
.overlay-kicker {
display: flex;
align-items: center;
gap: 7px;
margin-bottom: 5px;
color: #657568;
font-size: 9px;
line-height: 1;
font-weight: 900;
letter-spacing: 1.15px;
text-transform: uppercase;
}
.overlay-kicker .mini-led {
width: 6px;
height: 6px;
background: #e28a58;
box-shadow: 0 0 0 3px rgba(226,138,88,.13);
}
.overlay-card h2 {
margin: 0;
color: #1c3b3d;
font-size: 27px;
line-height: 1.05;
font-weight: 850;
letter-spacing: -1px;
}
.overlay-copy {
margin: 7px 0 12px;
color: #6a776d;
font-size: 12px;
line-height: 1.42;
}
.overlay-button {
width: 100%;
min-height: 39px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 13px;
border: 0;
border-radius: 11px;
color: #203a3b;
background: #f0c66e;
box-shadow: inset 0 -2px 0 rgba(142, 96, 37, .15);
font-size: 12px;
font-weight: 850;
letter-spacing: .05px;
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.overlay-button:hover { background: #f5d17f; transform: translateY(-1px); }
.overlay-button:active { transform: translateY(1px); }
.overlay-button svg {
width: 16px;
height: 16px;
flex: 0 0 auto;
}
.overlay-foot {
margin-top: 9px;
color: #909689;
font-size: 8px;
line-height: 1;
font-weight: 850;
letter-spacing: 1px;
text-transform: uppercase;
}
.board-footer {
min-height: 28px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: 9px 4px 0;
color: #68766d;
font-size: 10px;
line-height: 1.2;
font-weight: 700;
}
.board-tip {
display: inline-flex;
align-items: center;
gap: 7px;
}
.board-tip .sparkle {
color: #d88d54;
font-size: 14px;
}
.key-legend {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 5px;
color: #859084;
font-size: 8px;
font-weight: 850;
letter-spacing: .85px;
white-space: nowrap;
}
kbd {
min-width: 20px;
display: inline-grid;
place-items: center;
padding: 3px 5px;
border: 1px solid #d8d9ca;
border-bottom-width: 2px;
border-radius: 5px;
color: #53645a;
background: rgba(255,255,255,.62);
font: inherit;
font-size: 8px;
line-height: 1;
letter-spacing: 0;
}
.side-card {
min-width: 0;
height: 100%;
display: flex;
flex-direction: column;
gap: 13px;
padding: 18px;
border: 1px solid rgba(108, 117, 91, .16);
border-radius: 23px;
background: rgba(255, 253, 246, .87);
box-shadow:
0 13px 32px rgba(58, 65, 43, .07),
inset 0 1px 0 rgba(255,255,255,.8);
}
.side-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.side-kicker {
margin-bottom: 3px;
color: #849082;
font-size: 8px;
line-height: 1;
font-weight: 900;
letter-spacing: 1.25px;
text-transform: uppercase;
}
.side-top h2 {
margin: 0;
color: #233d3c;
font-size: 19px;
line-height: 1;
font-weight: 850;
letter-spacing: -.55px;
}
.shift-badge {
padding: 7px 9px;
border: 1px solid #e2e2d3;
border-radius: 999px;
color: #788274;
background: #f8f7ef;
font-size: 8px;
line-height: 1;
font-weight: 900;
letter-spacing: .75px;
white-space: nowrap;
}
.score-card {
position: relative;
padding: 14px 15px 12px;
overflow: hidden;
border-radius: 17px;
color: #f7f3df;
background: linear-gradient(135deg, #284e49, #173b3c 76%);
box-shadow: 0 7px 14px rgba(31, 69, 60, .11);
}
.score-card::after {
position: absolute;
top: -30px;
right: -12px;
width: 115px;
height: 115px;
border: 1px solid rgba(239, 213, 142, .10);
border-radius: 50%;
box-shadow: 0 0 0 12px rgba(239, 213, 142, .025), 0 0 0 25px rgba(239, 213, 142, .018);
content: "";
pointer-events: none;
}
.score-label {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
color: #c1d0bf;
font-size: 8px;
line-height: 1;
font-weight: 900;
letter-spacing: 1.1px;
text-transform: uppercase;
}
.score-label svg {
width: 15px;
height: 15px;
color: #f1c86f;
}
.score-main {
position: relative;
z-index: 1;
display: flex;
align-items: flex-end;
gap: 8px;
margin-top: 9px;
}
.score-value {
color: #f3c76e;
font-size: 49px;
line-height: .85;
font-weight: 900;
letter-spacing: -2px;
font-variant-numeric: tabular-nums;
}
.score-unit {
padding-bottom: 3px;
color: #d5dfcf;
font-size: 10px;
line-height: 1.1;
font-weight: 650;
}
.wagon-count {
display: flex;
align-items: center;
gap: 7px;
margin-left: auto;
padding-left: 12px;
border-left: 1px solid rgba(236, 239, 211, .22);
}
.wagon-count svg {
width: 25px;
height: 25px;
color: #f2c76e;
}
.wagon-count-copy {
display: flex;
flex-direction: column;
gap: 2px;
align-items: flex-start;
}
.wagon-count-copy strong {
color: #fff5d7;
font-size: 17px;
line-height: .9;
font-weight: 900;
font-variant-numeric: tabular-nums;
}
.wagon-count-copy small {
color: #b8c9b9;
font-size: 7px;
line-height: 1;
font-weight: 900;
letter-spacing: .8px;
}
.score-rule {
height: 1px;
margin: 11px 0 8px;
background: rgba(236,239,211,.14);
}
.score-foot {
display: flex;
align-items: center;
gap: 6px;
color: #bdcdbd;
font-size: 8px;
line-height: 1;
font-weight: 750;
letter-spacing: .55px;
text-transform: uppercase;
}
.score-foot .plus {
display: inline-grid;
width: 13px;
height: 13px;
place-items: center;
border-radius: 50%;
color: #1d403c;
background: #f1c76c;
font-size: 10px;
font-weight: 900;
}
.next-stop {
padding: 13px 14px 12px;
border: 1px solid #dfe4d5;
border-radius: 16px;
background: linear-gradient(145deg, #eef1e5, #e7ecdf);
}
.stop-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.stop-kicker {
margin-bottom: 4px;
color: #828d7e;
font-size: 8px;
line-height: 1;
font-weight: 900;
letter-spacing: 1.1px;
text-transform: uppercase;
}
.stop-number {
display: grid;
width: 34px;
height: 31px;
place-items: center;
border-radius: 10px;
color: #4d5040;
background: #f3cd7b;
box-shadow: inset 0 -2px 0 rgba(149, 109, 47, .12);
font-size: 12px;
line-height: 1;
font-weight: 900;
font-variant-numeric: tabular-nums;
}
.stop-name {
margin: 0;
color: #244342;
font-size: 18px;
line-height: 1.05;
font-weight: 850;
letter-spacing: -.55px;
}
.stop-route {
display: flex;
align-items: center;
gap: 7px;
margin-top: 11px;
}
.route-train-icon {
width: 22px;
height: 22px;
display: grid;
place-items: center;
flex: 0 0 auto;
border-radius: 7px;
color: #f0c36a;
background: #315650;
}
.route-train-icon svg { width: 14px; height: 14px; }
.route-dash {
height: 1px;
min-width: 12px;
flex: 1 1 auto;
border-top: 1px dashed #aab59f;
}
.distance-label {
color: #6f7d6e;
font-size: 9px;
line-height: 1;
font-weight: 850;
white-space: nowrap;
}
.waiting-row {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
padding-top: 9px;
border-top: 1px solid rgba(102, 124, 99, .14);
}
.avatars {
display: flex;
align-items: center;
padding-left: 2px;
}
.avatar {
width: 21px;
height: 21px;
display: grid;
place-items: center;
margin-left: -3px;
border: 2px solid #e9ede1;
border-radius: 50%;
}
.avatar:first-child { margin-left: 0; }
.avatar svg { width: 11px; height: 11px; }
.avatar.peach { color: #9a5d47; background: #f0c4a5; }
.avatar.blue { color: #315d70; background: #b4d5d2; }
.avatar.gold { color: #87613b; background: #f0d58c; }
.waiting-copy {
color: #7b8779;
font-size: 8px;
line-height: 1.15;
font-weight: 850;
letter-spacing: .52px;
text-transform: uppercase;
}
.little-tip {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 11px;
border: 1px solid #eee4cc;
border-radius: 14px;
background: #f8f3e6;
}
.tip-icon {
width: 29px;
height: 29px;
display: grid;
place-items: center;
flex: 0 0 auto;
border-radius: 10px;
color: #a96a3f;
background: #f2ddae;
font-size: 16px;
}
.tip-copy strong {
display: block;
margin-bottom: 2px;
color: #526455;
font-size: 10px;
line-height: 1;
font-weight: 850;
}
.tip-copy span {
display: block;
color: #899081;
font-size: 9px;
line-height: 1.32;
}
.control-block {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: auto;
}
.drive-button {
width: 100%;
min-height: 43px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 14px;
border: 0;
border-radius: 12px;
color: #f7f3e2;
background: #284b47;
box-shadow: inset 0 -2px 0 rgba(6, 28, 30, .20), 0 5px 12px rgba(34, 70, 61, .10);
font-size: 9px;
line-height: 1;
font-weight: 900;
letter-spacing: .9px;
text-align: left;
cursor: pointer;
transition: background .15s ease, transform .15s ease;
}
.drive-button:hover { background: #345b53; transform: translateY(-1px); }
.drive-button:active { transform: translateY(1px); }
.drive-button svg {
width: 16px;
height: 16px;
flex: 0 0 auto;
color: #f1c76d;
}
.keyboard-hint {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
color: #8c9487;
font-size: 8px;
line-height: 1;
font-weight: 850;
letter-spacing: .75px;
text-transform: uppercase;
}
.keyboard-hint kbd {
min-width: 0;
padding: 3px 5px;
color: #748074;
background: #f5f4ec;
}
.mobile-controls {
display: none;
}
@media (max-width: 940px) {
.layout { grid-template-columns: minmax(0, 1fr); gap: 15px; }
.side-card { height: auto; min-height: 0; }
.control-block { margin-top: 1px; }
}
@media (max-width: 640px) {
.app-shell { padding: 14px 14px 22px; }
.topbar { min-height: 45px; gap: 8px; margin-bottom: 13px; }
.brand { gap: 9px; }
.brand-mark { width: 40px; height: 40px; border-radius: 13px; }
.brand-mark svg { width: 28px; height: 28px; }
.brand-kicker { display: none; }
.brand h1 { font-size: 23px; letter-spacing: -1px; }
.header-actions { gap: 0; }
.route-token { display: none; }
.service-pill { min-height: 30px; padding: 0 9px; gap: 7px; font-size: 8px; letter-spacing: .65px; }
.service-led { width: 6px; height: 6px; }
.layout { gap: 13px; }
.board-meta {
min-height: 20px;
margin: 0 3px 6px;
font-size: 8px;
letter-spacing: .78px;
}
.board-meta-left, .board-meta-right { gap: 6px; }
.board-meta-right { font-size: 7px; }
.board-frame { padding: 6px; border-radius: 19px; }
.board-frame::after { inset: 3px; border-radius: 16px; }
#boardCanvas { border-radius: 13px; }
.game-overlay { inset: 6px; padding: 8px; border-radius: 13px; }
.overlay-card {
width: min(310px, calc(100% - 5px));
padding: 10px 12px;
border-radius: 14px;
}
.overlay-card::before { top: 10px; bottom: 10px; }
.overlay-kicker { margin-bottom: 3px; font-size: 7px; letter-spacing: .8px; }
.overlay-kicker .mini-led { width: 5px; height: 5px; }
.overlay-card h2 { font-size: 19px; letter-spacing: -.55px; }
.overlay-copy, .overlay-foot { display: none; }
.overlay-button { min-height: 33px; margin-top: 6px; padding: 0 10px; border-radius: 9px; font-size: 10px; }
.overlay-button svg { width: 14px; height: 14px; }
.board-footer { min-height: 24px; margin: 7px 3px 0; font-size: 9px; }
.key-legend { display: none; }
.side-card { gap: 11px; padding: 14px; border-radius: 19px; }
.side-top h2 { font-size: 18px; }
.score-card { padding: 13px 14px 11px; }
.next-stop { padding: 12px 13px 11px; }
.little-tip { padding: 9px 10px; }
.control-block { gap: 7px; }
.drive-button { min-height: 41px; }
.keyboard-hint { display: none; }
.mobile-controls {
display: grid;
grid-template-columns: repeat(3, 38px);
grid-template-rows: repeat(2, 33px);
justify-content: center;
gap: 4px;
margin-top: 2px;
}
.pad-button {
display: grid;
place-items: center;
border: 1px solid #dce0d1;
border-bottom-width: 2px;
border-radius: 9px;
color: #31534c;
background: #f2f3e9;
font-size: 17px;
line-height: 1;
font-weight: 800;
touch-action: manipulation;
cursor: pointer;
}
.pad-button:active { background: #e9ebdc; transform: translateY(1px); }
.pad-up { grid-column: 2; grid-row: 1; }
.pad-left { grid-column: 1; grid-row: 2; }
.pad-down { grid-column: 2; grid-row: 2; }
.pad-right { grid-column: 3; grid-row: 2; }
.mobile-controls-label {
grid-column: 1 / -1;
grid-row: 3;
margin-top: 3px;
color: #92988a;
font-size: 7px;
line-height: 1;
font-weight: 900;
letter-spacing: 1px;
text-align: center;
text-transform: uppercase;
}
}
@media (max-width: 390px) {
.app-shell { padding-right: 12px; padding-left: 12px; }
.service-pill { font-size: 7px; }
.score-value { font-size: 45px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
</style>
</head>
<body>
<div class="app-shell">
<header class="topbar">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 36 36" fill="none">
<path d="M5 28h26M9 31l-2 2m22-2 2 2" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
<path d="M10 22.5h16.5a2 2 0 0 1 2 2v3H8v-3a2 2 0 0 1 2-2Z" fill="#E88352" stroke="#F8E8BC" stroke-width="1.25"/>
<path d="M12 16.5h10.5a2 2 0 0 1 2 2v4H10v-4a2 2 0 0 1 2-2Z" fill="#F2C66D" stroke="#F8E8BC" stroke-width="1.25"/>
<path d="M14 19h6" stroke="#31524B" stroke-width="2" stroke-linecap="round"/>
<circle cx="13" cy="27.5" r="2" fill="#173638" stroke="#F8E8BC" stroke-width="1.1"/>
<circle cx="24" cy="27.5" r="2" fill="#173638" stroke="#F8E8BC" stroke-width="1.1"/>
<circle cx="25" cy="18.5" r="1" fill="#fff3ce"/>
</svg>
</div>
<div>
<div class="brand-kicker">The tiniest transit co.</div>
<h1>Little <span>Line</span></h1>
</div>
</div>
<div class="header-actions">
<div class="route-token">
<svg viewBox="0 0 18 18" fill="none" aria-hidden="true">
<path d="M9 15s5-4.35 5-8.25a5 5 0 1 0-10 0C4 10.65 9 15 9 15Z" stroke="currentColor" stroke-width="1.5"/>
<circle cx="9" cy="6.5" r="1.7" fill="currentColor"/>
</svg>
<span>Garden Loop</span><span class="route-separator">/</span><span>Day Shift</span>
</div>
<div class="service-pill" id="servicePill">
<span class="service-led"></span>
<span id="serviceText">Auto-pilot</span>
</div>
</div>
</header>
<main class="layout">
<section class="game-column" aria-label="Little Line train game">
<div class="board-meta">
<div class="board-meta-left">
<span class="meta-dot"></span>
<span>The Garden Loop</span>
<span class="meta-slash">/</span>
<span>Passenger Run</span>
</div>
<div class="board-meta-right">
<span>4 platforms in service</span>
</div>
</div>
<div class="board-frame" id="boardFrame">
<canvas id="boardCanvas" width="960" height="640" aria-label="A little train collecting passengers from station platforms">
A train snake game. Use the arrow keys or WASD to steer.
</canvas>
<div class="game-overlay" id="gameOverlay" aria-live="polite">
<div class="overlay-card">
<div class="overlay-kicker"><span class="mini-led"></span><span id="overlayKicker">Auto-pilot is driving</span></div>
<h2 id="overlayTitle">All aboard!</h2>
<p class="overlay-copy" id="overlayCopy">A little train is already on its way to the waiting passengers.</p>
<button class="overlay-button" id="overlayButton" type="button">
<span id="overlayButtonText">Click or press a key to start</span>
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M4 10h11m-4-4 4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="overlay-foot" id="overlayFoot">Arrow keys / WASD to steer · Space to pause</div>
</div>
</div>
</div>
<div class="board-footer">
<div class="board-tip"><span class="sparkle">✦</span><span>Pick up the glowing riders to add a wagon</span></div>
<div class="key-legend">
<kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>→</kbd><span>STEER</span>
<span style="padding:0 2px">·</span><kbd>SPACE</kbd><span>PAUSE</span>
</div>
</div>
</section>
<aside class="side-card" aria-label="Shift information and controls">
<div class="side-top">
<div>
<div class="side-kicker">Conductor's desk</div>
<h2>Your shift</h2>
</div>
<div class="shift-badge" id="shiftBadge">AUTO / 01</div>
</div>
<section class="score-card" aria-label="Passenger score">
<div class="score-label">
<span>Passengers boarded</span>
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
<circle cx="10" cy="6" r="3" stroke="currentColor" stroke-width="1.6"/>
<path d="M4.2 16c.4-2.7 2.6-4.4 5.8-4.4s5.4 1.7 5.8 4.4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
</svg>
</div>
<div class="score-main">
<strong class="score-value" id="scoreValue">00</strong>
<span class="score-unit">riders<br>this shift</span>
<div class="wagon-count">
<svg viewBox="0 0 28 28" fill="none" aria-hidden="true">
<rect x="3.5" y="8" width="21" height="11" rx="3" stroke="currentColor" stroke-width="1.8"/>
<path d="M7 11h5v4H7zm9 0h5v4h-5z" fill="currentColor" opacity=".8"/>
<circle cx="8" cy="21" r="2" fill="#173638" stroke="currentColor" stroke-width="1.4"/>
<circle cx="20" cy="21" r="2" fill="#173638" stroke="currentColor" stroke-width="1.4"/>
</svg>
<span class="wagon-count-copy"><strong id="wagonValue">03</strong><small>WAGONS</small></span>
</div>
</div>
<div class="score-rule"></div>
<div class="score-foot"><span class="plus">+</span><span>Every new rider adds a wagon</span></div>
</section>
<section class="next-stop" aria-label="Next passenger pickup">
<div class="stop-heading">
<div>
<div class="stop-kicker">Next pickup</div>
<h3 class="stop-name" id="stopName">Sunbeam Square</h3>
</div>
<span class="stop-number" id="stopNumber">02</span>
</div>
<div class="stop-route">
<span class="route-train-icon">
<svg viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="3" y="3" width="12" height="10" rx="3" stroke="currentColor" stroke-width="1.6"/>
<path d="M6 6h2v2H6zm4 0h2v2h-2zM6 14l-1.2 1.5M12 14l1.2 1.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
</svg>
</span>
<span class="route-dash"></span>
<span class="distance-label" id="distanceLabel">13 tiles away</span>
</div>
<div class="waiting-row">
<div class="avatars" aria-hidden="true">
<span class="avatar peach"><svg viewBox="0 0 14 14" fill="none"><circle cx="7" cy="4.5" r="2" fill="currentColor"/><path d="M3.5 12c.3-2.1 1.5-3.3 3.5-3.3s3.2 1.2 3.5 3.3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span>
<span class="avatar blue"><svg viewBox="0 0 14 14" fill="none"><circle cx="7" cy="4.5" r="2" fill="currentColor"/><path d="M3.5 12c.3-2.1 1.5-3.3 3.5-3.3s3.2 1.2 3.5 3.3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span>
<span class="avatar gold"><svg viewBox="0 0 14 14" fill="none"><circle cx="7" cy="4.5" r="2" fill="currentColor"/><path d="M3.5 12c.3-2.1 1.5-3.3 3.5-3.3s3.2 1.2 3.5 3.3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></span>
</div>
<span class="waiting-copy">3 riders waiting on the platform</span>
</div>
</section>
<div class="little-tip">
<div class="tip-icon" aria-hidden="true">✳</div>
<div class="tip-copy">
<strong>Mind the buffers</strong>
<span>Stay on the board, and don't run into your own wagons.</span>
</div>
</div>
<div class="control-block">
<button class="drive-button" id="driveButton" type="button">
<span id="driveButtonText">Take the controls</span>
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M4 10h11m-4-4 4 4-4 4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="keyboard-hint"><kbd>↑ ↓ ← →</kbd><span>or</span><kbd>W A S D</kbd><span>to steer</span><span>·</span><kbd>SPACE</kbd><span>pause</span></div>
<div class="mobile-controls" aria-label="Touch controls">
<button class="pad-button pad-up" data-direction="up" type="button" aria-label="Steer up">↑</button>
<button class="pad-button pad-left" data-direction="left" type="button" aria-label="Steer left">←</button>
<button class="pad-button pad-down" data-direction="down" type="button" aria-label="Steer down">↓</button>
<button class="pad-button pad-right" data-direction="right" type="button" aria-label="Steer right">→</button>
<span class="mobile-controls-label">Tap arrows to steer</span>
</div>
</div>
</aside>
</main>
</div>
<script>
(() => {
"use strict";
const COLS = 24;
const ROWS = 16;
const STEP_MS = 160;
const canvas = document.getElementById("boardCanvas");
const ctx = canvas.getContext("2d");
const overlay = document.getElementById("gameOverlay");
const overlayKicker = document.getElementById("overlayKicker");
const overlayTitle = document.getElementById("overlayTitle");
const overlayCopy = document.getElementById("overlayCopy");
const overlayFoot = document.getElementById("overlayFoot");
const overlayButtonText = document.getElementById("overlayButtonText");
const scoreValue = document.getElementById("scoreValue");
const wagonValue = document.getElementById("wagonValue");
const stopName = document.getElementById("stopName");
const stopNumber = document.getElementById("stopNumber");
const distanceLabel = document.getElementById("distanceLabel");
const serviceText = document.getElementById("serviceText");
const shiftBadge = document.getElementById("shiftBadge");
const driveButtonText = document.getElementById("driveButtonText");
const platforms = [
{ id: "01", name: "Willow Pier", label: "WILLOW PIER", x: 1.15, y: 1.15, w: 7.4, h: 3.25, tx: 4, ty: 3 },
{ id: "02", name: "Sunbeam Square", label: "SUNBEAM SQ.", x: 15.45, y: 1.15, w: 7.4, h: 3.25, tx: 19, ty: 3 },
{ id: "03", name: "Moss Market", label: "MOSS MARKET", x: 1.15, y: 11.6, w: 7.4, h: 3.25, tx: 4, ty: 13 },
{ id: "04", name: "Lantern End", label: "LANTERN END", x: 15.45, y: 11.6, w: 7.4, h: 3.25, tx: 19, ty: 13 }
];
const routeOrder = [1, 3, 2, 0];
const DIRS = [
{ name: "right", x: 1, y: 0 },
{ name: "down", x: 0, y: 1 },
{ name: "left", x: -1, y: 0 },
{ name: "up", x: 0, y: -1 }
];
const dirByName = Object.fromEntries(DIRS.map(d => [d.name, d]));
const carColors = ["#58c4a1", "#f0b94f", "#e87668", "#a78bdd", "#67b6da", "#e889ae", "#7bc885"];
const jacketColors = ["#4b9c8a", "#557da3", "#dd9464", "#8078ae"];
let segments = [
{ x: 10, y: 7 },
{ x: 9, y: 7 },
{ x: 8, y: 7 },
{ x: 7, y: 7 }
];
let previousSegments = segments.map(p => ({ ...p }));
let direction = dirByName.right;
let queuedDirection = null;
let routeCursor = 0;
let riderCount = 0;
let demo = true;
let paused = false;
let gameOver = false;
let accumulator = 0;
let lastFrame = performance.now();
let bursts = [];
let boardWidth = 0;
let cellPixels = 30;
function currentPlatform() {
return platforms[routeOrder[routeCursor]];
}
function roundedRect(c, x, y, w, h, r) {
const radius = Math.max(0, Math.min(r, w / 2, h / 2));
c.beginPath();
c.moveTo(x + radius, y);
c.lineTo(x + w - radius, y);
c.quadraticCurveTo(x + w, y, x + w, y + radius);
c.lineTo(x + w, y + h - radius);
c.quadraticCurveTo(x + w, y + h, x + w - radius, y + h);
c.lineTo(x + radius, y + h);
c.quadraticCurveTo(x, y + h, x, y + h - radius);
c.lineTo(x, y + radius);
c.quadraticCurveTo(x, y, x + radius, y);
c.closePath();
}
function makeDecorations() {
let seed = 81723;
const random = () => {
seed = (seed * 1664525 + 1013904223) >>> 0;
return seed / 4294967296;
};
const items = [];
let attempts = 0;
while (items.length < 54 && attempts < 400) {
attempts++;
const x = 0.55 + random() * (COLS - 1.1);
const y = 0.55 + random() * (ROWS - 1.1);
const onPlatform = platforms.some(p => x > p.x - .22 && x < p.x + p.w + .22 && y > p.y - .2 && y < p.y + p.h + .2);
const nearStart = Math.hypot(x - 9.2, y - 7.4) < 1.1;
if (onPlatform || nearStart) continue;
items.push({ x, y, size: .72 + random() * .6, hue: random() });
}
return items;
}
const decorations = makeDecorations();
function resizeCanvas() {
const rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) return;
const dpr = Math.min(2.5, window.devicePixelRatio || 1);
const width = Math.max(1, Math.round(rect.width * dpr));
const height = Math.max(1, Math.round(rect.height * dpr));
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
boardWidth = rect.width;
cellPixels = rect.width / COLS;
}
function drawGrass(item, time) {
const sway = Math.sin(time * .0013 + item.x * 2 + item.y) * .025;
ctx.save();
ctx.translate(item.x, item.y);
ctx.rotate(item.hue * 2.4 - 1.2);
ctx.scale(item.size, item.size);
ctx.lineCap = "round";
ctx.lineWidth = .035;
ctx.strokeStyle = item.hue > .73 ? "rgba(212,187,116,.34)" : "rgba(133,183,139,.34)";
ctx.beginPath();
ctx.moveTo(-.12, .08);
ctx.quadraticCurveTo(-.12 + sway, -.02, -.18, -.13);
ctx.moveTo(-.02, .09);
ctx.quadraticCurveTo(.04 + sway, -.01, .04, -.18);
ctx.moveTo(.07, .09);
ctx.quadraticCurveTo(.14 + sway, .01, .19, -.1);
ctx.stroke();
if (item.hue > .73) {
ctx.fillStyle = "rgba(242,199,111,.62)";
ctx.beginPath();
ctx.arc(-.18, -.13, .045, 0, Math.PI * 2);
ctx.arc(.04, -.18, .045, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
function drawBackground(time) {
const gradient = ctx.createLinearGradient(0, 0, COLS, ROWS);
gradient.addColorStop(0, "#19434a");
gradient.addColorStop(.5, "#15383e");
gradient.addColorStop(1, "#12333a");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, COLS, ROWS);
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
if ((x * 5 + y * 3) % 4 === 0) {
ctx.fillStyle = "rgba(203,225,184,.018)";
ctx.fillRect(x, y, 1, 1);
} else if ((x + y * 2) % 7 === 0) {
ctx.fillStyle = "rgba(4,19,25,.025)";
ctx.fillRect(x, y, 1, 1);
}
}
}
ctx.strokeStyle = "rgba(206,226,197,.055)";
ctx.lineWidth = .018;
ctx.beginPath();
for (let x = 1; x < COLS; x++) {
ctx.moveTo(x, 0);
ctx.lineTo(x, ROWS);
}
for (let y = 1; y < ROWS; y++) {
ctx.moveTo(0, y);
ctx.lineTo(COLS, y);
}
ctx.stroke();
ctx.save();
ctx.globalAlpha = .22;
ctx.strokeStyle = "#80a696";
ctx.lineWidth = .025;
ctx.setLineDash([.08, .18]);
ctx.beginPath();
ctx.arc(12, 8, 3.25, 0, Math.PI * 2);
ctx.stroke();
ctx.setLineDash([]);
ctx.restore();
decorations.forEach(item => drawGrass(item, time));
ctx.save();
ctx.globalAlpha = .17;
ctx.fillStyle = "#83b99a";
ctx.beginPath();
ctx.arc(12, 8, .19, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "#a9cfac";
ctx.lineWidth = .035;
ctx.beginPath();
ctx.arc(12, 8, .42, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
function drawBench(x, y) {
ctx.save();
ctx.fillStyle = "rgba(31,48,42,.32)";
roundedRect(ctx, x + .035, y + .05, .83, .17, .055);
ctx.fill();
ctx.fillStyle = "#775d42";
roundedRect(ctx, x, y, .83, .13, .05);
ctx.fill();
ctx.fillStyle = "#b48b5e";
roundedRect(ctx, x + .035, y + .025, .75, .045, .02);
ctx.fill();
ctx.fillStyle = "#67543e";
ctx.fillRect(x + .11, y + .12, .055, .12);
ctx.fillRect(x + .67, y + .12, .055, .12);
ctx.restore();
}
function drawPlatformBase(p) {
ctx.save();
ctx.fillStyle = "rgba(5,20,23,.26)";
roundedRect(ctx, p.x + .03, p.y + .10, p.w, p.h, .24);
ctx.fill();
roundedRect(ctx, p.x, p.y, p.w, p.h, .23);
ctx.fillStyle = "#718a75";
ctx.fill();
ctx.lineWidth = .035;
ctx.strokeStyle = "rgba(225,230,194,.45)";
ctx.stroke();
roundedRect(ctx, p.x + .075, p.y + .075, p.w - .15, p.h - .15, .19);
ctx.fillStyle = "#c5cbb0";
ctx.fill();
ctx.save();
roundedRect(ctx, p.x + .13, p.y + .13, p.w - .26, .69, .12);
ctx.fillStyle = "#36575a";
ctx.fill();
roundedRect(ctx, p.x + .22, p.y + .23, .57, .48, .095);
ctx.fillStyle = "#f1ca73";
ctx.fill();
const labelFont = Math.max(.24, Math.min(.57, 8.1 / Math.max(1, cellPixels)));
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = "#4e503e";
ctx.font = `900 ${labelFont * .77}px system-ui, sans-serif`;
ctx.fillText(p.id, p.x + .505, p.y + .47);
ctx.textAlign = "left";
ctx.fillStyle = "#f1efdc";
ctx.font = `800 ${labelFont}px system-ui, sans-serif`;
ctx.fillText(p.label, p.x + .94, p.y + .48);
ctx.restore();
ctx.strokeStyle = "rgba(87,105,81,.19)";
ctx.lineWidth = .022;
ctx.beginPath();
ctx.moveTo(p.x + .22, p.y + 1.08);
ctx.lineTo(p.x + p.w - .22, p.y + 1.08);
ctx.moveTo(p.x + .22, p.y + p.h - .68);
ctx.lineTo(p.x + p.w - .22, p.y + p.h - .68);
ctx.stroke();
drawBench(p.x + .43, p.y + 1.52);
const stripY = p.y + p.h - .43;
roundedRect(ctx, p.x + .16, stripY, p.w - .32, .24, .07);
ctx.fillStyle = "#e6bd62";
ctx.fill();
ctx.fillStyle = "rgba(103,87,52,.47)";
const dashCount = 13;
for (let i = 0; i < dashCount; i++) {
const dx = p.x + .28 + i * ((p.w - .62) / (dashCount - 1));
roundedRect(ctx, dx, stripY + .065, .075, .11, .025);
ctx.fill();
}
ctx.fillStyle = "#526f64";
ctx.beginPath();
ctx.arc(p.x + p.w - .34, p.y + 1.04, .07, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#f0cc77";
ctx.beginPath();
ctx.arc(p.x + p.w - .34, p.y + 1.04, .035, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawPerson(x, groundY, jacket, skin, hair, scale = 1) {
ctx.save();
ctx.translate(x, groundY);
ctx.scale(scale, scale);
ctx.fillStyle = "rgba(24,43,40,.19)";
ctx.beginPath();
ctx.ellipse(0, -.018, .19, .065, 0, 0, Math.PI * 2);
ctx.fill();
ctx.lineCap = "round";
ctx.lineWidth = .072;
ctx.strokeStyle = "#344b47";
ctx.beginPath();
ctx.moveTo(-.065, -.13);
ctx.lineTo(-.085, -.035);
ctx.moveTo(.065, -.13);
ctx.lineTo(.085, -.035);
ctx.stroke();
ctx.strokeStyle = "#283b3a";
ctx.lineWidth = .08;
ctx.beginPath();
ctx.moveTo(-.085, -.035);
ctx.lineTo(-.13, -.015);
ctx.moveTo(.085, -.035);
ctx.lineTo(.13, -.015);
ctx.stroke();
roundedRect(ctx, -.16, -.405, .32, .285, .09);
ctx.fillStyle = jacket;
ctx.fill();
ctx.strokeStyle = jacket;
ctx.lineWidth = .085;
ctx.beginPath();
ctx.moveTo(-.135, -.34);
ctx.lineTo(-.19, -.22);
ctx.moveTo(.135, -.34);
ctx.lineTo(.19, -.22);
ctx.stroke();
ctx.fillStyle = skin;
ctx.beginPath();
ctx.arc(0, -.485, .12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = hair;
ctx.beginPath();
ctx.arc(0, -.515, .12, Math.PI, Math.PI * 2);
ctx.quadraticCurveTo(.11, -.49, .085, -.455);
ctx.quadraticCurveTo(0, -.48, -.105, -.45);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "rgba(255,255,255,.46)";
roundedRect(ctx, -.115, -.375, .07, .13, .03);
ctx.fill();
ctx.restore();
}
function drawStar(x, y, outer, inner, points, color) {
ctx.beginPath();
for (let i = 0; i < points * 2; i++) {
const radius = i % 2 === 0 ? outer : inner;
const angle = -Math.PI / 2 + i * Math.PI / points;
const px = x + Math.cos(angle) * radius;
const py = y + Math.sin(angle) * radius;
if (i === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
function drawWaitingPassengers(p, active, time) {
const cx = p.tx + .5;
const cy = p.ty + .5;
if (active) {
const pulse = .5 + .5 * Math.sin(time * .006);
const radius = .47 + pulse * .06;
const glow = ctx.createRadialGradient(cx, cy - .22, .04, cx, cy - .22, .8);
glow.addColorStop(0, `rgba(255,208,110,${.18 + pulse * .08})`);
glow.addColorStop(1, "rgba(255,208,110,0)");
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(cx, cy - .22, .8, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = `rgba(255,216,129,${.72 - pulse * .18})`;
ctx.lineWidth = .045;
ctx.beginPath();
ctx.arc(cx, cy - .22, radius, 0, Math.PI * 2);
ctx.stroke();
ctx.save();
ctx.setLineDash([.07, .11]);
ctx.strokeStyle = `rgba(255,221,152,${.48 - pulse * .12})`;
ctx.lineWidth = .026;
ctx.beginPath();
ctx.arc(cx, cy - .22, .67 + pulse * .035, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
drawStar(cx + .27, cy - .78 - pulse * .035, .12, .055, 4, "#f5cf70");
}
const people = [
{ x: cx - 1.68, jacket: jacketColors[(Number(p.id) + 1) % jacketColors.length], skin: "#e8b692", hair: "#57453b" },
{ x: cx, jacket: active ? "#e97959" : "#648e79", skin: "#efc29a", hair: "#4b3a35" },
{ x: cx + 1.72, jacket: jacketColors[(Number(p.id) + 2) % jacketColors.length], skin: "#e9bc91", hair: "#493d37" }
];
people.forEach((person, i) => {
drawPerson(person.x, cy, person.jacket, person.skin, person.hair, i === 1 ? 1 : .92);
});
}
function drawBoundary() {
ctx.save();
roundedRect(ctx, .31, .31, COLS - .62, ROWS - .62, .45);
ctx.strokeStyle = "rgba(179,211,181,.26)";
ctx.lineWidth = .035;
ctx.setLineDash([.15, .14]);
ctx.stroke();
ctx.setLineDash([]);
const corners = [
[.48, .48], [COLS - .48, .48],
[.48, ROWS - .48], [COLS - .48, ROWS - .48]
];
ctx.fillStyle = "rgba(231,196,120,.55)";
corners.forEach(([x, y]) => {
ctx.beginPath();
ctx.arc(x, y, .055, 0, Math.PI * 2);
ctx.fill();
});
ctx.restore();
}
function drawTrackLink(a, b) {
const ax = a.x + .5;
const ay = a.y + .5;
const bx = b.x + .5;
const by = b.y + .5;
const dx = bx - ax;
const dy = by - ay;
const length = Math.hypot(dx, dy);
if (length < .16) return;
const nx = -dy / length;
const ny = dx / length;
ctx.save();
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(63,77,65,.82)";
ctx.lineWidth = .07;
const tieSteps = Math.max(2, Math.floor(length / .31));
for (let i = 0; i <= tieSteps; i++) {
const t = (i + .5) / (tieSteps + 1);
const x = ax + dx * t;
const y = ay + dy * t;
ctx.beginPath();
ctx.moveTo(x - nx * .25, y - ny * .25);
ctx.lineTo(x + nx * .25, y + ny * .25);
ctx.stroke();
}
ctx.strokeStyle = "rgba(199,211,174,.72)";
ctx.lineWidth = .045;
for (const offset of [-.145, .145]) {
ctx.beginPath();
ctx.moveTo(ax + nx * offset, ay + ny * offset);
ctx.lineTo(bx + nx * offset, by + ny * offset);
ctx.stroke();
}
ctx.restore();
}
function drawWagon(x, y, angle, color, index) {
ctx.save();
ctx.translate(x + .5, y + .5);
ctx.rotate(angle);
ctx.fillStyle = "rgba(4,18,21,.3)";
ctx.beginPath();
ctx.ellipse(0, .18, .43, .31, 0, 0, Math.PI * 2);
ctx.fill();
for (const wheelX of [-.23, .23]) {
for (const wheelY of [-.31, .31]) {
ctx.fillStyle = "#12282d";
ctx.beginPath();
ctx.arc(wheelX, wheelY, .105, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#85978a";
ctx.beginPath();
ctx.arc(wheelX, wheelY, .045, 0, Math.PI * 2);
ctx.fill();
}
}
roundedRect(ctx, -.43, -.3, .86, .6, .16);
ctx.fillStyle = "#142f34";
ctx.fill();
roundedRect(ctx, -.385, -.255, .77, .51, .135);
ctx.fillStyle = color;
ctx.fill();
roundedRect(ctx, -.31, -.192, .62, .384, .105);
ctx.fillStyle = "rgba(255,255,255,.16)";
ctx.fill();
ctx.fillStyle = "rgba(21,47,48,.72)";
roundedRect(ctx, -.245, -.135, .18, .27, .055);
ctx.fill();
roundedRect(ctx, .065, -.135, .18, .27, .055);
ctx.fill();
ctx.fillStyle = "#f4e9c9";
roundedRect(ctx, -.215, -.105, .12, .21, .035);
ctx.fill();
roundedRect(ctx, .095, -.105, .12, .21, .035);
ctx.fill();
ctx.fillStyle = "rgba(255,255,255,.45)";
roundedRect(ctx, -.29, -.225, .15, .035, .016);
ctx.fill();
ctx.fillStyle = "#fff0bd";
ctx.beginPath();
ctx.arc(0, 0, .038, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawLocomotive(x, y, dir, time) {
const angle = Math.atan2(dir.y, dir.x);
ctx.save();
ctx.translate(x + .5, y + .5);
ctx.rotate(angle);
const puff = Math.sin(time * .012);
ctx.fillStyle = "rgba(234,244,223,.45)";
ctx.beginPath();
ctx.arc(-.47 - Math.max(0, puff) * .05, -.13, .075, 0, Math.PI * 2);
ctx.arc(-.58 - Math.max(0, -puff) * .04, -.19, .052, 0, Math.PI * 2);
ctx.fill();
const beam = ctx.createLinearGradient(.34, 0, 1.05, 0);
beam.addColorStop(0, "rgba(255,218,132,.19)");
beam.addColorStop(1, "rgba(255,218,132,0)");
ctx.fillStyle = beam;
ctx.beginPath();
ctx.moveTo(.31, -.16);
ctx.lineTo(1.02, -.44);
ctx.lineTo(1.02, .44);
ctx.lineTo(.31, .16);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "rgba(4,18,21,.32)";
ctx.beginPath();
ctx.ellipse(0, .2, .48, .34, 0, 0, Math.PI * 2);
ctx.fill();
for (const wheelX of [-.22, .2]) {
for (const wheelY of [-.34, .34]) {
ctx.fillStyle = "#10262b";
ctx.beginPath();
ctx.arc(wheelX, wheelY, .115, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#8b9887";
ctx.beginPath();
ctx.arc(wheelX, wheelY, .047, 0, Math.PI * 2);
ctx.fill();
}
}
roundedRect(ctx, -.42, -.3, .76, .6, .16);
ctx.fillStyle = "#142e34";
ctx.fill();
roundedRect(ctx, -.38, -.255, .37, .51, .12);
ctx.fillStyle = "#dc7650";
ctx.fill();
roundedRect(ctx, -.31, -.18, .22, .36, .07);
ctx.fillStyle = "#f1c66e";
ctx.fill();
roundedRect(ctx, -.285, -.13, .17, .26, .055);
ctx.fillStyle = "#34575a";
ctx.fill();
roundedRect(ctx, -.035, -.235, .48, .47, .19);
ctx.fillStyle = "#e68b4d";
ctx.fill();
roundedRect(ctx, .015, -.176, .37, .352, .15);
ctx.fillStyle = "#f1b855";
ctx.fill();
ctx.fillStyle = "#c96d45";
roundedRect(ctx, -.005, -.225, .11, .45, .05);
ctx.fill();
ctx.fillStyle = "#334a45";
ctx.beginPath();
ctx.arc(-.05, 0, .13, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "#f4d07c";
ctx.lineWidth = .045;
ctx.stroke();
roundedRect(ctx, .35, -.23, .14, .46, .07);
ctx.fillStyle = "#d36b4d";
ctx.fill();
ctx.fillStyle = "#fff2c8";
ctx.beginPath();
ctx.arc(.405, 0, .15, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "#ae6946";
ctx.lineWidth = .035;
ctx.stroke();
ctx.fillStyle = "#34464a";
ctx.beginPath();
ctx.arc(.385, -.052, .022, 0, Math.PI * 2);
ctx.arc(.385, .052, .022, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "#b56a4a";
ctx.lineWidth = .023;
ctx.beginPath();
ctx.arc(.423, 0, .047, .25, Math.PI - .25);
ctx.stroke();
ctx.restore();
}
function drawBursts(time) {
bursts = bursts.filter(b => time - b.start < 780);
bursts.forEach(burst => {
const age = Math.max(0, (time - burst.start) / 780);
const alpha = 1 - age;
const colors = ["#f4c96f", "#f28c65", "#91d5b0", "#f6e8ba", "#df95ad"];
for (let i = 0; i < 9; i++) {
const angle = (Math.PI * 2 * i) / 9 + .22;
const travel = .28 + age * .62;
const px = burst.x + Math.cos(angle) * travel;
const py = burst.y + Math.sin(angle) * travel - age * .15;
ctx.globalAlpha = alpha;
ctx.fillStyle = colors[i % colors.length];
ctx.beginPath();
ctx.arc(px, py, .045 + (i % 3) * .012, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
});
}
function interpolatedSegments(progress) {
return segments.map((point, i) => {
const from = previousSegments[i] || point;
return {
x: from.x + (point.x - from.x) * progress,
y: from.y + (point.y - from.y) * progress
};
});
}
function drawBoard(time, progress) {
if (!canvas.width || !canvas.height) return;
ctx.setTransform(canvas.width / COLS, 0, 0, canvas.height / ROWS, 0, 0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
drawBackground(time);
platforms.forEach(drawPlatformBase);
drawBoundary();
const positions = interpolatedSegments(progress);
for (let i = 0; i < positions.length - 1; i++) {
drawTrackLink(positions[i], positions[i + 1]);
}
if (positions.length) {
const head = positions[0];
drawTrackLink(
head,
{ x: head.x + direction.x * .55, y: head.y + direction.y * .55 }
);
}
const active = currentPlatform();
platforms.forEach(p => drawWaitingPassengers(p, p === active, time));
for (let i = positions.length - 1; i >= 1; i--) {
const prior = segments[i - 1] || segments[i];
const here = segments[i];
const angle = Math.atan2(prior.y - here.y, prior.x - here.x);
drawWagon(positions[i].x, positions[i].y, angle, carColors[(i - 1) % carColors.length], i);
}
if (positions.length) {
drawLocomotive(positions[0].x, positions[0].y, direction, time);
}
drawBursts(time);
}
function sameCell(a, b) {
return a.x === b.x && a.y === b.y;
}
function isOpposite(a, b) {
return a.x === -b.x && a.y === -b.y;
}
function inBounds(p) {
return p.x >= 1 && p.x <= COLS - 2 && p.y >= 1 && p.y <= ROWS - 2;
}
function isSafeMove(next, growing) {
if (!inBounds(next)) return false;
const bodyLimit = growing ? segments.length : segments.length - 1;
for (let i = 0; i < bodyLimit; i++) {
if (sameCell(next, segments[i])) return false;
}
return true;
}
function getSafeFallback() {
const target = currentPlatform();
return DIRS
.filter(d => !isOpposite(d, direction))
.map(d => ({
dir: d,
next: { x: segments[0].x + d.x, y: segments[0].y + d.y }
}))
.filter(item => isSafeMove(item.next, sameCell(item.next, target)))
.sort((a, b) => {
const da = Math.abs(a.next.x - target.tx) + Math.abs(a.next.y - target.ty);
const db = Math.abs(b.next.x - target.tx) + Math.abs(b.next.y - target.ty);
return da - db;
})[0]?.dir || null;
}
function findAIDirection() {
const head = segments[0];
const target = currentPlatform();
const blocked = new Set();
for (let i = 1; i < segments.length - 1; i++) {
blocked.add(`${segments[i].x},${segments[i].y}`);
}
const queue = [{ x: head.x, y: head.y, first: null }];
const visited = new Set([`${head.x},${head.y}`]);
for (let cursor = 0; cursor < queue.length; cursor++) {
const node = queue[cursor];
const options = DIRS.slice().sort((a, b) => {
const da = Math.abs(node.x + a.x - target.tx) + Math.abs(node.y + a.y - target.ty);
const db = Math.abs(node.x + b.x - target.tx) + Math.abs(node.y + b.y - target.ty);
return da - db;
});
for (const d of options) {
if (!node.first && isOpposite(d, direction)) continue;
const next = { x: node.x + d.x, y: node.y + d.y };
if (!inBounds(next)) continue;
const key = `${next.x},${next.y}`;
if (blocked.has(key) || visited.has(key)) continue;
const first = node.first || d;
if (next.x === target.tx && next.y === target.ty) return first;
visited.add(key);
queue.push({ x: next.x, y: next.y, first });
}
}
return getSafeFallback() || direction;
}
function addBurst(point) {
bursts.push({
x: point.x + .5,
y: point.y + .5,
start: performance.now()
});
}
function resetRun() {
segments = [
{ x: 10, y: 7 },
{ x: 9, y: 7 },
{ x: 8, y: 7 },
{ x: 7, y: 7 }
];
previousSegments = segments.map(p => ({ ...p }));
direction = dirByName.right;
queuedDirection = null;
routeCursor = 0;
riderCount = 0;
demo = false;
paused = false;
gameOver = false;
accumulator = 0;
bursts = [];
hideOverlay();
updateHUD();
}
function restartDemoSafely() {
segments = [
{ x: 10, y: 7 },
{ x: 9, y: 7 },
{ x: 8, y: 7 },
{ x: 7, y: 7 }
];
previousSegments = segments.map(p => ({ ...p }));
direction = dirByName.right;
queuedDirection = null;
routeCursor = 0;
riderCount = 0;
accumulator = 0;
updateHUD();
}
function showOverlay(mode) {
overlay.classList.remove("is-hidden");
overlay.setAttribute("aria-hidden", "false");
if (mode === "pause") {
overlayKicker.textContent = "Signal stop";
overlayTitle.textContent = "Catch your breath.";
overlayCopy.textContent = "The train is safely waiting. Ready for the next pickup?";
overlayButtonText.textContent = "Resume the route";
overlayFoot.textContent = "Press space or tap to continue";
} else if (mode === "over") {
overlayKicker.textContent = "End of the line";
overlayTitle.textContent = "Oops — off the rails.";
overlayCopy.textContent = `${riderCount} passenger${riderCount === 1 ? "" : "s"} made it aboard. Fancy another loop?`;
overlayButtonText.textContent = "Run it back";
overlayFoot.textContent = "Press any key to start a new shift";
}
}
function hideOverlay() {
overlay.classList.add("is-hidden");
overlay.setAttribute("aria-hidden", "true");
}
function takeControls() {
if (gameOver) {
resetRun();
return;
}
if (demo) {
demo = false;
paused = false;
hideOverlay();
} else if (paused) {
paused = false;
hideOverlay();
}
updateHUD();
}
function queueTurn(nextDirection) {
if (!nextDirection || isOpposite(nextDirection, direction)) return;
queuedDirection = nextDirection;
}
function steer(nextDirection) {
if (gameOver) resetRun();
else takeControls();
queueTurn(nextDirection);
updateHUD();
}
function endShift() {
gameOver = true;
demo = false;
paused = false;
accumulator = 0;
showOverlay("over");
updateHUD();
}
function moveTick() {
if (demo) {
direction = findAIDirection();
queuedDirection = null;
} else if (queuedDirection) {
if (!isOpposite(queuedDirection, direction)) direction = queuedDirection;
queuedDirection = null;
}
const head = segments[0];
let next = { x: head.x + direction.x, y: head.y + direction.y };
let target = currentPlatform();
let growing = sameCell(next, target);
if (!isSafeMove(next, growing) && demo) {
const fallback = getSafeFallback();
if (fallback) {
direction = fallback;
next = { x: head.x + direction.x, y: head.y + direction.y };
target = currentPlatform();
growing = sameCell(next, target);
}
}
if (!isSafeMove(next, growing)) {
if (demo) restartDemoSafely();
else endShift();
return;
}
const old = segments.map(p => ({ ...p }));
const nextSegments = [{ x: next.x, y: next.y }, ...old];
if (!growing) nextSegments.pop();
previousSegments = nextSegments.map((point, i) => ({ ...(old[Math.min(i, old.length - 1)] || point) }));
segments = nextSegments;
if (growing) {
riderCount++;
addBurst(target);
routeCursor = (routeCursor + 1) % routeOrder.length;
}
updateHUD();
}
function updateHUD() {
const target = currentPlatform();
scoreValue.textContent = String(riderCount).padStart(2, "0");
wagonValue.textContent = String(Math.max(0, segments.length - 1)).padStart(2, "0");
stopName.textContent = target.name;
stopNumber.textContent = target.id;
const distance = Math.abs(segments[0].x - target.tx) + Math.abs(segments[0].y - target.ty);
distanceLabel.textContent = distance === 0
? "At the platform"
: `${distance} ${distance === 1 ? "tile" : "tiles"} away`;
if (demo) {
serviceText.textContent = "Auto-pilot";
shiftBadge.textContent = "AUTO / 01";
driveButtonText.textContent = "Take the controls";
overlayCopy.textContent = `First stop: ${target.name}. Your little train is on its way.`;
} else if (gameOver) {
serviceText.textContent = "Shift ended";
shiftBadge.textContent = "SHIFT / 01";
driveButtonText.textContent = "Run it back";
} else if (paused) {
serviceText.textContent = "At a signal";
shiftBadge.textContent = "PAUSED";
driveButtonText.textContent = "Resume the route";
} else {
serviceText.textContent = "You're driving";
shiftBadge.textContent = "MANUAL / 01";
driveButtonText.textContent = "Pause the route";
}
}
function togglePause() {
if (demo || gameOver) return;
paused = !paused;
accumulator = 0;
if (paused) showOverlay("pause");
else hideOverlay();
updateHUD();
}
function handleOverlayAction() {
if (gameOver) resetRun();
else if (paused) {
paused = false;
accumulator = 0;
hideOverlay();
updateHUD();
} else {
takeControls();
}
}
document.getElementById("overlayButton").addEventListener("click", event => {
event.stopPropagation();
handleOverlayAction();
});
overlay.addEventListener("click", event => {
if (event.target.closest("button")) return;
handleOverlayAction();
});
document.getElementById("boardFrame").addEventListener("click", () => {
if (demo) takeControls();
});
document.getElementById("driveButton").addEventListener("click", () => {
if (demo || paused || gameOver) handleOverlayAction();
else togglePause();
});
document.querySelectorAll(".pad-button").forEach(button => {
button.addEventListener("click", () => steer(dirByName[button.dataset.direction]));
});
const keyDirections = {
ArrowUp: dirByName.up,
w: dirByName.up,
W: dirByName.up,
ArrowDown: dirByName.down,
s: dirByName.down,
S: dirByName.down,
ArrowLeft: dirByName.left,
a: dirByName.left,
A: dirByName.left,
ArrowRight: dirByName.right,
d: dirByName.right,
D: dirByName.right
};
window.addEventListener("keydown", event => {
const key = event.key;
const lower = key.toLowerCase();
const chosenDirection = keyDirections[key];
if (key.startsWith("Arrow") || key === " ") event.preventDefault();
if (demo) {
takeControls();
if (chosenDirection) queueTurn(chosenDirection);
updateHUD();
return;
}
if (gameOver) {
resetRun();
if (chosenDirection) queueTurn(chosenDirection);
return;
}
if (key === " " || lower === "p" || key === "Escape") {
togglePause();
return;
}
if (chosenDirection) {
if (paused) {
paused = false;
hideOverlay();
}
queueTurn(chosenDirection);
updateHUD();
}
});
function animationFrame(time) {
const delta = Math.min(60, Math.max(0, time - lastFrame));
lastFrame = time;
if (!paused && !gameOver) {
accumulator += delta;
while (accumulator >= STEP_MS) {
moveTick();
accumulator -= STEP_MS;
if (paused || gameOver) break;
}
} else {
accumulator = 0;
}
drawBoard(time, Math.min(1, accumulator / STEP_MS));
requestAnimationFrame(animationFrame);
}
updateHUD();
resizeCanvas();
if ("ResizeObserver" in window) {
new ResizeObserver(resizeCanvas).observe(canvas);
}
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(animationFrame);
})();
</script>
</body>
</html>Reasoning trace (44,090 tokens)
**Visualizing a game plan** I'm thinking about creating a complete HTML game that looks beautiful. The user wants a snake game featuring a train locomotive with colorful wagons and passengers waiting on the platform. I need a meaningful introduction and make sure the game showcases a self-playing demo while displaying the start message. The game should be polished and responsive at 1280x800 and 375. I’ll include UI elements like the title, score, and a start overlay prompting “Click or press a key to start.” It needs to be playable with arrow keys or WASD, ensuring visibility of the action.**Designing the snake train game** I’m conceptualizing a snake train game where the locomotive has a face and colorful wagons, with passengers waiting on platforms around a grid-based board. I need to focus on optimizing its visual appeal while using a canvas for high-definition responsive design. The layout should be charming and polished, featuring a navy and cream color scheme. The central play area will be around 700x590, supported by a sidebar. I envision decorative elements like a title bar “NIGHT LINE / SNAKE EXPRESS” and subtle tiles to mark rows and columns, creating a distinctive railway environment.**Creating the passenger platform** I need to design a distinctive platform for passengers waiting beside the station along the board. I’m thinking of tiny characters as passengers drawn near the pickups to create a lively scene. The snake moves along a grid, and I could use striped tiles to represent the platform with commuters clustered around. I’ll use small figures, maybe placing one passenger on a 2x2 cell with a glowing target. When the locomotive picks up a passenger, it increases in length and possibly score. The game board dimensions will be either 28x20 or 26x18.**Designing layout specifications** I'm exploring layout details and considering a fixed board ratio of 1.35, with a resolution of 1280x800. I need to calculate components like the header, main row, and sidebar, ensuring the total height adds up accurately. The board will be responsive, and I want to ensure that at smaller widths, the layout adapts without horizontal scrolling. I'll also think about increasing the aspect ratio slightly to improve the rendering of the grid cells. It's crucial to fit everything dynamically!**Refining board and grid design** I’m working on the board’s aspect ratio, aiming for about 1.56 at a width of 340 and a height of 245, with cells sized at 12.14. For desktop, I’m thinking of a canvas of 830x520, with a grid of 28 columns and 18 rows. I’ll ensure the visual design features a dark rich green/blue base, rounded corners, and alternating tile patterns to keep it clean. Including subtle train tracks in the background will tie everything together nicely!**Visualizing train path** I’m considering how the train’s movement can create a snake-like path within the board. It could be interesting to depict an underlying track network using thin rails and sleepers to emphasize the train's journey. This detail would add depth to the visuals, making the board feel more dynamic and realistic, while also enhancing the overall design. I want to make sure it looks appealing and functional at the same time!**Exploring train movement and design** I'm thinking about a grid for snake movement that incorporates some underlying railway tracks. I could create a faint outline of a railway route, perhaps with subtle rails visible at each cell center. The aesthetic could revolve around a navy or deep teal color scheme, using a scenic map for the board. I want to emphasize the movement of the train along these tracks, ensuring the design aligns with the snake game movement conventions.**Designing train segments and layout** I'm considering making each train segment a small wagon tile with wheels and distinct locomotive designs. I want the wagons to be colorful and rounded to enhance their appeal. A top-down board could resemble a scenic map or arcade with a dark forest background and subtle gradient. I'll include station platforms where passengers can wait, possibly integrating a train route that allows the snake to cross. The website design might have a warm background with a logo and a catchy title like "Platform Panic."**Designing the game interface** I'm envisioning a beautiful layout with a dark navy header text and a "LIVE SERVICE" pill in the top right, complete with a green dot and "DEMO RUNNING" message. The game center would feature a canvas framed in dark, while the right panel would have a light card labeled "TODAY'S ROUTE," displaying a giant count, possibly for passengers or score. I want real-time stats, a status card for the next stop with route instructions, and a waiting queue of illustrated characters. There should be a big call-to-action button and an overlay message that prompts users to click or press any key to start the demo service.**Planning train game mechanics** I’m envisioning a game where a snake moves across a grid randomly to target food while avoiding collisions. The board will have coordinates from x=0 to 27 and y=0 to 17, with some boundaries like walls for safety. I want to design the background with a grid and add four platform islands at specific points for passengers. The initial train will start around the center, at (13,9), moving right with wagons trailing behind. I'll need a map for this!**Designing platform features** I'm planning to place a token on the platform at (22,4) that takes about 1.5 seconds for the train to reach it, accounting for 10 + 5 steps. The train will start visible, likely at the center, but not on the platform itself. Each platform will feature a top-down horizontal boarding strip with a pale green rectangle, about 5 cells wide and 2.5 cells tall, including border rails, diagonal lines, signage labeled "P1," and a bench with light.**Designing a game layout** I'm thinking about using the entire board as a terminal layout, with four small fixed platforms at the corners for routing stops. When the player reaches pickups, the active target would change sequentially through the platforms. Upon reaching a target point, the score and passengers increment. I want to draw passenger icons at fixed positions on each platform, emphasizing the active one with glowing effects. The platform signs will label them, optimizing space on this 28x18 board.**Setting platform coordinates** I’m determining the placements for the platforms. Platform P1 coordinates are from (2,2) to (8,5) with the center at (5,4). P2 is from (19,2) to (26,5), centered at (22,4). P3 and P4 follow the same pattern in the lower section of the board. The platforms should act as passable islands, not collisions. I'll make them 7x3 cells with a passenger area around the center. The canvas board dimensions are 784x504, which works well!**Designing mobile controls** I’m considering controls for a mobile game and thinking about using four directional buttons without an on-screen interface. A buddy pad setup might work for both mobile and desktop — using arrow keys or WASD for movement, plus Space to pause. I’ll also add a big “Take the controls” button outside the overlay for easy access. The overlay should encourage interaction without obfuscating the board too much, perhaps using a semi-transparent backdrop. The starting prompt could say, “Click or press a key to start.”**Building the game interface** I’m thinking about a “click to start” button with a translucent dark overlay. If the user clicks on the overlay, the game should switch to player control while keeping the train's state. I might pause the AI, setting demo mode to false for players, but if there's no input, the demo should continue indefinitely. I also want an initial screenshot at 3 seconds post-load, showing the overlay and AI movement. I need to focus on a visually strong layout without excessive scrolling, using only system fonts.**Evaluating layout dimensions** I'm working through layout dimensions here: At 1280 width, there's a specific game width, board canvas, and frame padding to consider. Then there’s a total height calculation that includes various elements like the header and footer. At 375 width, I'm adjusting for padding, and I see the canvas size becomes manageable with the right proportions. All this leads to ensuring no horizontal scrollbar shows up, which is great for user experience on different devices. I think I'm getting it right!**Choosing color and design elements** I'm considering colors for the game's background, maybe a deep teal (#102B34) for a rich feel. I think a gradient fill for the background will make it look more dynamic. I also want to add tiles with low opacity for a textured look, and grid lines to define spaces. To enhance the board's aesthetics, I might incorporate multiple rail tracks, creating a top-down city yard vibe with subtle paths. I think it's coming together nicely!**Visualizing train tracks** I'm thinking about using horizontal dashed grid lines to create a railway effect. Maybe I can draw track lanes crossing a central area? A decorative route line might work well too, like a dashed loop through the grid. I imagine a snake-like train floating in a field, with a central yard where tracks coordinate movement. I'll need rails beneath each train segment, drawing two parallel rails along each line segment and adding small silver rail ties underneath. This visualization is coming together!**Designing a train station board** I'm considering creating a top-down view of station gardens with a delicate rail path grid. Instead of just grid lines, I could use a seamless field with standard track patterns, following the snake game concept, where any path can be taken. The train's dynamic track would follow directly behind it. I'll draw tracks behind the vehicles in segments, with sleepers and twin rails for each. For each tile, I should also account for connecting directions, ensuring smooth transitions and adding rail segments accordingly. This design is shaping up!**Creating dynamic train tracks** I’m thinking of drawing rails with a spacing of 0.5 cells along the vector from center to center, adding sleepers at fractions like .18, .5, and .82 with a dark brown color. I want to use a subtle color (#9ab3aa) for the two rails and place them beneath the entire train path for a dynamic look. The locomotive can sit without track ahead, which is fine. I’ll also add scenic details like bushes and flowers in the corners for a more vibrant background. This will make everything come together nicely!**Designing a yard layout** I’m working on a yard design featuring a green-dark base field with broad paths and individual grass tufts. I want to use a gradient background ranging from #16404b to #102b34. The design includes a cell grid with alternating tile fills. There will be a dark perimeter boundary, scattered star accents, and four platforms with light mint concrete. Each platform will have specific dimensions and coordinates, structured as rectangular islands in a well-defined grid.**Designing platform features** I'm considering adding a top and bottom stripe filled with a muted warm color, #d4c89f at a low opacity. On a dark board, the platforms will be in pale desaturated mint or beige. The main platform should have a rounded fill of #d7d3b7 with a #f5ebcd stroke and shadow effects. I’ll incorporate alternating strip tiles for texture, and place yellow tactile rail strips along the edges. I also need to design a horizontal rectangle concourse platform with wood planks and pale tan color, plus a safety dash at the edge.**Designing train station layout** I’m figuring out the train station design, targeting coordinates (5, 4). If the right side is inward lower, I’ll make a simpler station strip with a walkway at y+0.45, and the target for the train needs to be at y+1.9. For colors, I’m considering a dull base fill (#b9c7b1) and a lighter inner fill (#d2d5ba). The top station nameplate will be “WILLOW PIER” in dark green and white text. I realize font sizes may need adjustments to remain readable on various devices, which is fundamental for user experience.**Scaling grid and text legibility** I'm working on scaling a logical grid to 28 cells, aiming for fonts around 11 px logically or 4 px on mobile to ensure board labels are legible at a width of 375 pixels. I might consider using CSS for rendered text instead of scaling with cells. I might adjust the font size concerning cell size, with a minimum size set to 10 px. Content may overlap on mobile, so I’m thinking of hiding smaller labels below a width of 500 pixels while keeping the board functional. The platform signs can have a smaller, less readable design, but I need to ensure they're visually acceptable.**Adjusting grid dimensions** I think I need to adjust the grid layout to 24 x 16 cells, with each cell around 14 px on mobile. This setup would give me a board ratio of 1.5. When I set the width to 329 pixels, the canvas height should be about 219 pixels, which results in a total of 13.7 cells. Additionally, I plan to use a sprite size of 10 px. This adjustment feels like it could lead to a more balanced design overall!**Designing game elements** I’m working on a desktop layout for a game, with a board size of 840x560 pixels and a grid of 24x16 cells. The map might feature platforms measuring 6x3. The starting position is centered at (12,8), and I've marked targets. I need to consider collision margins and define platform positions carefully. The plan involves rendering distinct features like round rectangles and specific color codes for visual elements on the canvas. Also, I’m thinking about potential AI pathing and movement strategies.**Designing platform visuals** I'm looking to create a track side with a thin mustard dashed line along the lower edge, close to the stop. The target, where a person is positioned, will be centered on the walkway, specifically at the grid cell (19,3). I’m thinking about having 2-3 commuters nearby as well. The platform plaque will sit between certain y-coordinates. For the platform sprites, I’ll randomly place waiting characters and add a bright highlight for the active platform, along with some idle passengers for a lively scene.**Planning passenger sprites** I'm considering how to draw passengers in a cute, tiny top-down style. I'll likely use little human silhouettes with a round head and capsule body, making them look like small commuters facing the train. The colors will be distinct, and for desktop, they’ll be 18-22px high while for mobile, about 9px. I need to create their shadow too, and position their heads and bodies carefully in the drawing. I'll use specific pixel units to maintain consistency.**Designing character details** I’m mapping out how to draw the person: the bottom will sit at y*cell. The shadow will be centered slightly below, and the legs will extend from both sides in dark shoes. The body will be a rounded rectangle with a filled color, and I’ll add a collar or scarf. For the head, I’ll use a circle and can include hair. I want to create a glowing effect for the selected person and maybe add a small speech bubble and an icon. I'll work on breathing animations and ensuring inactive characters are positioned well too.**Designing active target signs** I’m thinking about how to represent the active target with a narrow sign showing "• NEXT" in an accent color. I want to ensure there's a small `NEXT` tag located on the platform. On desktop, I’ll make this label visible, but on mobile, it might just have a glowing effect to stand out. The platforms may overlap a bit, so I have to be careful about positioning to avoid any collisions with the tracks.**Detailing wagon design** I'm considering the space between the centers of the wheels, which should be 1 cell, with a gap of 0.1. The wheels on the external sides will be at coordinates (y ± 0.32) and (x ± 0.2) to form small circles. Each wagon needs to be placed at its respective (x,y) coordinates and oriented in the right direction. And it’s also useful to note that the rails underneath don’t need to overlap. It’ll come together!**Calculating angles** I'm considering how to determine the angle based on the positioning of neighbors. If they are horizontal, the angle is 0 degrees. However, if they're vertical, I should set the angle to 90 degrees. This seems simple enough, but I need to ensure I’m applying this correctly in any related calculations. If I have any additional factors or variables to consider, I’ll make a note of that as I proceed.**Defining game boundaries** I'm considering the walls and edges of the game board's margin. I think about the collision logic for the segments, where if the next x is less than 1 or greater than or equal to 23, it indicates a boundary condition. Essentially, I want to define the perimeter of the board, which establishes a clear boundary where the rail line is located and ensures that movement cannot return past that edge.**Considering smooth movement** I'm thinking about animations in a demo and how to increase the fluidity of movements with the addition of passengers. I want to ensure there's smooth animation for the trains as they move between grid cells, which should happen in about 140 ms. I’ll also make sure that body segments follow positions appropriately by storing segments with integer coordinates and referring to previous segments to ensure everything moves nicely. It's about creating a polished visual experience!**Considering game mechanics** I’m planning the implementation for a game where I need to set up a render call with `progress = Math.min(accum/stepMs, 1)`. The game should start automatically and keep track of `accum` along with `lastTime`. I want the background to have an animated shimmer and pulse effects. The demo will run without user interaction, so I’ll use `requestAnimationFrame`. I'll enable the keys for starting the game, and also handle key events for direction and pausing during the game.**Developing movement controls** I’m designing the movement controls for the game. When starting, the direction will be set to the right, and pressing the up arrow will take effect immediately on the next tick. I’m considering using an input buffer called `nextDirection`, where `direction` is the actual movement and `queued` is for potential changes. I want to avoid multiple turns before a tick. Using `setDirection`, I'll check that the new direction isn’t opposite to the current or queued direction, and then update the queue accordingly.**Designing panel layout** I'm thinking about the panel design. It needs a `label-row` featuring the dot that says "ON THE NORTH LOOP" and a number badge "01." Then, there's the count card, which should have a dark green background—specifically a dark #1d4443—while the text will be cream and the count will be in orange. I wonder if the count card will stand out nicely against the dark background. Okay, let's go ahead and finalize this design!**Designing user interface elements** I'm thinking about adding another tiny two-stat tile to display scores, train length, and possibly distance. For the route card, I want it to be white with a prominent station number and progress line. I also have an idea for an "On the platform" section with simple avatar circles or colored dots. There might be a helper card overlay, but I need to balance the clutter while ensuring visible controls within the panel height.**Creating sidebar markup** I’m developing a detailed sidebar markup for the interface. It will feature a section titled “CONDUCTOR'S DESK” showing the current shift number. I want to include a score card displaying passenger counts and wagon information, plus a progress indicator for next stops. The "UP NEXT" area will name the next destination with the stop number alongside a distance gauge. I'll also add a waiting list section with avatars and a note reminding users to stay on the tracks.**Planning train pickup scenario** I'm visualizing a platform setup where a train is positioned to pick up passengers. The target center is at x=4.5, while other people are positioned at x=3.4 and x=5.7. I’m considering how to represent the active target at center, and I’ll note the positions of other passengers relative to the target. There’s a thought about adding a sign above the active passenger, but maybe that's unnecessary. Just keeping it simple!**Exploring design details** I'm looking into the font sizes for the labels and thinking about using a max of 8 for mobile and 11 for desktop. I want the header height to be about 0.70 of a cell, which should fit nicely. For the map design, I might center a small roundabout as a "route hub" and add a green island ring to indicate "NORTH LOOP." The train starts at this hub, so the design should be subtle but effective. Maybe I'll add a watermark of "CITY GARDENS" as well.**Designing a vibrant park layout** I'm thinking about how to create a lively park design with decorative shrubs at fixed points, while making sure to avoid the platform and train paths. For the shrubs, I'm considering using three-leaf circles, and laying out central paths. I need to render tiny shrubs or daisies in specific coordinates, and perhaps create faint clusters along the platform's outer side. I want to keep the board lively with little stars in the grass and use track rails under the train for additional detail. It’s an exciting challenge!**Refining the rail design** I'm considering the rail color as `#a8bd9e` with an opacity of `.47`, to create a realistic track appearance. I want the route behind all wagons and the engine to be clear, and I'm using specific coordinates and offsets to draw round lines. The sleepers will be at precise fractions for correct placement. I think it’s crucial to layer the platform art correctly, with tracks beneath the platform, maintaining visibility under the train. I’ll finalize the outer board boundary last for a neat finish!**Considering sign placement** I'm thinking about creating a small sign that says "NEXT" and placing it around the active cell, maybe just above the person. This could be helpful in guiding attention or indicating what's coming next. It's a simple adjustment, but I think it could improve clarity and communication. Let's see how it looks once I implement it, and if necessary, I can tweak the design later for better visibility.**Evaluating train platform design** I’m working on a visualization for a train platform scene. I’m thinking about drawing three riders, each with a queue of waiting passengers. For the active rider, I’ll make the target bright and dim the others if they're inactive, so users can see the waiting passengers clearly. The score should update with the number of riders, and I might start with a train length of four segments. I’d display the distance to the next stop dynamically, like “12 tiles to your stop.”**Updating game mechanics** I need to ensure updates occur at every step using Manhattan distances. I’m thinking of displaying three colored bubbles as tiny avatars for active passengers. The route UI will dynamically show the current stop name and updates for distance to the platform. For example: if I’m at the platform, it says “At the platform,” otherwise, it indicates how many tiles away I am. I’ll also want to keep scores padded, showing updates for carriage numbers and passenger milestones. Progress bars might indicate route completion, and the user desires colorful wagons for visibility.**Considering index patterns** I'm thinking about how to set up my indices. If I start with the first index at 1, then the next one would be at 2, which is bottom-left, and then 3 would go to the bottom-right. It creates a zigzag pattern, which seems like it might work well. I wonder if this approach will effectively manage the layout, but I think it could lead to a nice flow. Let's see how it looks in practice!**Evaluating pathfinding strategies** I’m thinking about the direction for the pathfinding process. It might be best to start moving right for about 9 cells and then go up for 4 cells. The target coordinate is exact at a zero index, and I need to ensure that signage and benches are placed properly away from that target. The target is specifically at (19,3). I’ll make sure the bench and groupings are at the right positions too.**Creating a draw platform** I'm working on the draw platform, starting with a shadow fill and a rounded rectangle base filled with a specific color. The header will also be a rounded rectangle with adjusted sizes. I want to add a stripe line and consider how the platform floor should be filled. I need to focus on layers, color contrast, and elements like the edging with yellow and dashed stripes. Redrawing will happen on resize with the proper scaling function. It feels like I'm growing in my approach!**Analyzing movement path** I'm looking at the P2 target coordinates (19, 3) and the head position (10, 7). It seems the path initially goes east, moving right, followed by a turn north. Now, I'm wondering about the `isOccupied` function. Should it be checking the body indices from 1 to segments.length minus 1, excluding the tail? It's important to clarify that to ensure proper movement and collision detection. I want to get this right for accurate pathfinding!**Analyzing key detection logic** I’m considering how the key detection works for the overlay. If I use "escape," there should be a pause before it captures anything. I need to make sure that the prompt "Click or press a key to start" doesn't capture keyboard input until after the iframe receives the click event. That seems to work well. I’ll also include an overlay button listener to trigger `beginManual()` and possibly add some pulsing effects for the panel.**Considering overlay placement** I'm analyzing a screenshot where the overlay might be occluding important elements, like passengers on a train platform. The current placement may obscure the center of the train. I wonder if positioning the overlay in the bottom left could work better, reducing obstruction. The prompt suggests a self-playing demo should show an AI behind a start message. I think a translucent card at the bottom center might be a good solution, ensuring key visual elements remain visible.**Analyzing overlay positioning** I'm figuring out the best placement for an overlay in relation to a starting locomotive around y=7. I need to make sure the train is visible right from the beginning. If I put the overlay at the top center, it might cover the locomotive. I wonder if anchoring it at the lower left could work, but it might obscure platform P3. Maybe a card overlay on the left at center would be a better choice since the train's path isn't there initially.**Considering dimensions and visibility** I'm thinking about how setting the width to 330 will obscure the left-bottom part of the layout. I want to ensure that the “demo card” isn't too large while maintaining functionality. It’s all about striking the right balance between clarity and aesthetics. I wonder if there’s a way to adjust it without compromising the overall design intent. There’s a lot to consider, especially if it affects how users interact with it!**Designing game elements** I’m brainstorming the layout for an arcade train game called “Little Line.” The tagline could be “A passenger-collecting snake game.” I want an eye-catching header with a locomotive logo, and I’m thinking of adding "CITY SERVICE 04" beneath “LITTLE LINE.” For the top-right, a button that says “DEMO IN MOTION” would work well. I can also incorporate a route section with details about “THE GARDEN LOOP” and “PLATFORM HOP.” I want to make this engaging!**Crafting card UI design** I'm considering a side card background in a soft off-white (#fffdf7) with a slight texture. The panel will have a border and shadow for depth. For the score card, I’m envisioning a gradient from dark teal to a lighter shade. The score number will stand out with a larger font and a specific color. Plus, I'm planning a mobile panel overlay using canvas for physical operations and ensuring the font scales correctly in relation to the transformed coordinate system.**Analyzing movement routes** I’m thinking about a movement path that involves four targets. When following the route from P02 to P04 to P03 to P01, I realize that the body’s path could double back along previous tracks. The approach to P03 may involve navigating through areas around P04 while the body snakes through. Also, I should consider how scoring works in the UI, aiming for efficient moves in a demo setting. Speed may need adjustment for optimal performance, too.**Examining overlay issues** I'm analyzing a snapshot at time 3, where the AI is moving toward P4, but an overlay covers part of the view. The train remains visible while the head and body positioning seem correct. However, the overlay affects the bottom left, perhaps hiding important elements on mobile devices. The mobile card width seems too large, which conceals parts of the train and positions like P2. I need to consider adjusting the card width to ensure important features are visible.**Positioning overlay correctly** I'm considering adjusting the overlay so that it sits lower, around y8. If the card height is 90 in CSS, that translates to about 6.5 cells, which means it would cover y values from 9.5 to 16. The initial head at y7.5 remains visible, and the target at P2 is clear at y3.5. This is good because the head is just above the overlay, ensuring it isn’t obscured. I need to make sure the visibility stays intact.**Preparing HTML layout** I'm considering the affordances for the overlay, like “Click or press a key to start.” I think we can position the mobile pad in a compact row below the drive button. The dimensions I'm looking at are 375x800 for the header and a canvas board height of 218. The game area would fit at around 300, with the side beginning at 360 and extending to 800 for the score. I want to avoid a horizontal scrollbar and will use CSS box sizing for better layout control. Time to prepare the HTML code!**Considering progress display** I’m thinking we can have a mini progress bar that fills based on `score % 5`, but every passenger means adding a wagon. Instead, I could display a message like "Each rider adds a wagon" without a progress indicator. For the CSS, I’m planning to use a specific height for the next stop box and set styles for different elements, including rider circles and badges. It all needs to come together visually appealing while maintaining clarity!**Designing interactive route features** I’m thinking about enhancing the interface with a small "LINE MAP" integrated into it. The next card could show the route path capsule, like `current 02 -> 04`, along with a mini route strip for visuals. Below that, I could add a route map with four numbered stations along a curvy line. Maybe this could be interactive, with a little passenger queue to make the panel richer. I wonder how the total height calculation would look for the layout—should I consider adding a route card too?**Working on pathfinding logic** I’m planning the next steps for updating the train direction and drawing engine in my pathfinding project. I need to check how to interpolate the positions of the rails and make sure the train movement reflects that accurately. There’s some detailed math needed for drawing the track links and maintaining the correct angles at turns, especially considering vector normalization. I also need to be mindful of the targets and how they fit into the overall movement logic.**Detailing drawing logic** I’m focusing on how to draw wagons at interpolated positions and their angles based on current segment positions. I’ll ensure that I only draw the active route and consider platform overlays. For the track links, I’ll keep the rails offset and ensure that sleeping ties are drawn along their length. I need to remember to adjust for diagonals at turns and to draw components like sleepers seamlessly. I also want to verify board dimensions while coding, especially for mobile settings.**Evaluating target centers** I'm looking at the target centers for P2 and P1. For P2, the center is 19.5, with left at 17.8 and right at 21.25, which fits well. Then for P1, the center is 4.5, with left at 2.8 and right at 6.25. I’m analyzing how these measurements correlate. It seems like they’re specific targets, and I’ll need to assess them further for accuracy and fitting into whatever context they're meant for.**Considering CSS canvas transformations** I'm thinking about how the CSS canvas transform scale impacts text geometry and font size in coordinate units. It seems like a factor of around 0.24 physical backing pixels is involved, and with a context (ctx) transform of 35, that could affect everything. I'm curious if setting the font size below 1 would render properly. It's all about ensuring that the transformations work as expected while keeping the visual balance intact!**Building final response outline** I’m working on a final response that involves applying CSS and JavaScript to draw on a canvas. First, I need to ensure my HTML structure is correct, including a top bar with a status indication and an app shell with headers and layout sections. I want to check the accessibility of SVG icons and make sure there are no mistakes in my markup. The button ID will be `driveButton`, and I’m ensuring proper positioning for overlays within the board frame.