Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 221 additions & 12 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,23 @@
--faint: #d9d9d9;
--paper: #fbfbfa;
--panel: rgba(255, 255, 255, 0.72);
--panel-hover: #fff;
--accent: #111;
--steam: #c9c9c7;
--skyline: rgba(21, 21, 21, 0.07);
}

body.night {
color-scheme: dark;
--ink: #f2f5ff;
--muted: #aeb8cf;
--faint: rgba(255, 255, 255, 0.22);
--paper: #07101f;
--panel: rgba(10, 21, 39, 0.74);
--panel-hover: rgba(18, 34, 61, 0.92);
--accent: #dce8ff;
--steam: #6f7e99;
--skyline: rgba(0, 0, 0, 0.28);
}

* {
Expand All @@ -48,6 +64,9 @@
Arial,
sans-serif;
font-feature-settings: "kern";
transition:
background 280ms ease,
color 280ms ease;
}

main {
Expand Down Expand Up @@ -77,6 +96,125 @@
opacity: 0;
}

.night-details {
position: fixed;
inset: 0;
z-index: 0;
overflow: hidden;
opacity: 0;
pointer-events: none;
transition: opacity 280ms ease;
}

body.night .night-details {
opacity: 1;
}

.moon {
position: absolute;
top: clamp(34px, 8vh, 82px);
right: clamp(70px, 14vw, 180px);
width: clamp(42px, 7vw, 72px);
aspect-ratio: 1;
border-radius: 50%;
background: #f3e9b3;
box-shadow: 0 0 28px rgba(243, 233, 179, 0.36);
}

.moon::after {
content: "";
position: absolute;
top: -6%;
left: 24%;
width: 100%;
height: 100%;
border-radius: inherit;
background: var(--paper);
}

.star {
position: absolute;
width: 3px;
height: 3px;
border-radius: 50%;
background: rgba(235, 241, 255, 0.92);
box-shadow: 0 0 8px rgba(235, 241, 255, 0.58);
}

.star:nth-child(2) {
top: 18%;
left: 12%;
}

.star:nth-child(3) {
top: 11%;
left: 34%;
width: 2px;
height: 2px;
}

.star:nth-child(4) {
top: 28%;
left: 74%;
}

.star:nth-child(5) {
top: 42%;
left: 22%;
width: 2px;
height: 2px;
}

.star:nth-child(6) {
top: 36%;
left: 88%;
}

.star:nth-child(7) {
top: 15%;
left: 58%;
width: 2px;
height: 2px;
}

.skyline {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 18vh;
min-height: 116px;
background:
linear-gradient(90deg, transparent 12px, rgba(255, 214, 118, 0.65) 12px 17px, transparent 17px 40px) 0 38% / 72px 36px repeat-x,
linear-gradient(to top, var(--skyline), var(--skyline));
clip-path: polygon(
0 46%,
7% 46%,
7% 24%,
15% 24%,
15% 38%,
24% 38%,
24% 14%,
34% 14%,
34% 48%,
44% 48%,
44% 30%,
54% 30%,
54% 42%,
64% 42%,
64% 20%,
74% 20%,
74% 48%,
84% 48%,
84% 28%,
94% 28%,
94% 42%,
100% 42%,
100% 100%,
0 100%
);
}

.hero-inner {
display: grid;
gap: 18px;
Expand Down Expand Up @@ -127,18 +265,24 @@
.steam {
position: fixed;
z-index: 0;
color: #c9c9c7;
color: var(--steam);
font-size: 18px;
pointer-events: none;
animation: puff 1400ms ease-out forwards;
will-change: transform, opacity;
}

.mute-toggle {
.controls {
position: fixed;
top: 16px;
right: 16px;
z-index: 10;
display: flex;
gap: 8px;
}

.theme-toggle,
.mute-toggle {
display: inline-flex;
width: 38px;
height: 38px;
Expand All @@ -157,12 +301,14 @@
transform 150ms ease;
}

.theme-toggle:hover,
.mute-toggle:hover {
background: #fff;
background: var(--panel-hover);
border-color: #b8b8b8;
transform: translateY(-1px);
}

.theme-toggle:focus-visible,
.mute-toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
Expand Down Expand Up @@ -266,22 +412,45 @@
</head>
<body>
<main id="dispatcher">
<div class="night-details" aria-hidden="true">
<span class="moon"></span>
<span class="star"></span>
<span class="star"></span>
<span class="star"></span>
<span class="star"></span>
<span class="star"></span>
<span class="star"></span>
<span class="skyline"></span>
</div>

<div class="hero" id="hero">
<div class="hero-inner">
<span class="train-emoji" role="img" aria-label="train">🚂</span>
<span class="tagline">Click anywhere to dispatch a train.</span>
</div>
</div>

<button
class="mute-toggle"
id="mute"
type="button"
aria-label="Mute"
aria-pressed="false"
>
🔊
</button>
<div class="controls">
<button
class="theme-toggle"
id="theme"
type="button"
aria-label="Turn on night mode"
aria-pressed="false"
>
🌙
</button>

<button
class="mute-toggle"
id="mute"
type="button"
aria-label="Mute"
aria-pressed="false"
>
🔊
</button>
</div>

<div class="counter" id="counter" aria-live="polite">0 trains dispatched</div>
</main>
Expand All @@ -297,8 +466,11 @@

const dispatcher = document.querySelector("#dispatcher");
const hero = document.querySelector("#hero");
const themeButton = document.querySelector("#theme");
const muteButton = document.querySelector("#mute");
const counter = document.querySelector("#counter");
const themeMeta = document.querySelector('meta[name="theme-color"]');
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
const choo = new Audio("./public/choo-choo.mp3");
choo.preload = "auto";

Expand All @@ -308,6 +480,10 @@
let lastLane = -1;
let lastDirection = "rtl";
let muted = localStorage.getItem("wtc:muted") === "1";
let themePreference = localStorage.getItem("wtc:theme");
let nightMode =
themePreference === "night" ||
(themePreference === null && systemTheme.matches);

function pick(items) {
return items[Math.floor(Math.random() * items.length)];
Expand All @@ -320,6 +496,30 @@
localStorage.setItem("wtc:muted", muted ? "1" : "0");
}

function syncThemeButton() {
document.body.classList.toggle("night", nightMode);
themeButton.textContent = nightMode ? "☀️" : "🌙";
themeButton.setAttribute(
"aria-label",
nightMode ? "Turn off night mode" : "Turn on night mode",
);
themeButton.setAttribute("aria-pressed", String(nightMode));
themeMeta.setAttribute("content", nightMode ? "#07101f" : "#ffffff");
}

function setThemePreference(value) {
themePreference = value;
nightMode = value === "night";
localStorage.setItem("wtc:theme", value);
syncThemeButton();
}

function syncSystemTheme() {
if (themePreference !== null) return;
nightMode = systemTheme.matches;
syncThemeButton();
}

function updateCounter() {
const noun = count === 1 ? "train" : "trains";
counter.textContent = `${count} ${noun} dispatched`;
Expand Down Expand Up @@ -398,6 +598,11 @@
}

dispatcher.addEventListener("click", dispatchTrain);
themeButton.addEventListener("click", (event) => {
event.stopPropagation();
setThemePreference(nightMode ? "day" : "night");
});

muteButton.addEventListener("click", (event) => {
event.stopPropagation();
muted = !muted;
Expand All @@ -408,12 +613,16 @@
if (event.code === "Space" || event.code === "Enter") {
event.preventDefault();
dispatchTrain();
} else if (event.key === "n" || event.key === "N") {
setThemePreference(nightMode ? "day" : "night");
} else if (event.key === "m" || event.key === "M") {
muted = !muted;
syncMuteButton();
}
});

systemTheme.addEventListener("change", syncSystemTheme);
syncThemeButton();
syncMuteButton();
updateCounter();
</script>
Expand Down