hallway8 / static /style.css
alvations's picture
Deploy Hallway 8 (multi-arc memory game)
03b1c9d verified
Raw
History Blame Contribute Delete
37.7 kB
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2026 alvations (Melon Lab)
*/
:root {
--bg: #0b0c0e;
--fg: #d7d7d2;
/* Two tiers of secondary text, both WCAG AA on the dark bg but kept gloomy:
--dim = reading tier (lead, taglines, asides, intro/win) ~6.7:1
--dim2 = chrome tier (HUD, give-up link, language row) ~5.0:1 */
--dim: #93979f;
--dim2: #7d818a;
--accent: #9fd0c0;
--danger: #c98b8b;
--hue: 210; /* base hue of the ambient background glow */
--panel-w: 40rem;
/* These are nudged by JS a fraction at a time -- a drift you feel
rather than see. */
--bg-shift: 0;
--weight: 340;
--tracking: 0.01em;
}
/* --- skins ---------------------------------------------------------------
Each arc sets data-skin on <body>; these override the palette. */
body[data-skin="hallway"] {
--bg: #0b0c0e;
--fg: #d7d7d2;
--dim: #93979f; /* cool blue-grey, reading tier */
--dim2: #7d818a; /* cool blue-grey, chrome tier */
--accent: #6fb2ee; /* cold fluorescent blue */
--hue: 212;
}
body[data-skin="stairway"] {
--bg: #0a0d0b;
--fg: #d3d8d2;
--dim: #949c96; /* green-grey, reading tier */
--dim2: #7e857f; /* green-grey, chrome tier */
--accent: #8fc79a; /* emergency-exit green */
--hue: 150;
}
body[data-skin="coach"] {
--bg: #0c0a08;
--fg: #e0d7c8;
--dim: #9d978a; /* warm amber-grey, reading tier */
--dim2: #877f72; /* warm amber-grey, chrome tier */
--accent: #d8b06a; /* warm cabin amber */
--hue: 35;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
height: 100%;
background: var(--bg);
color: var(--fg);
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
-webkit-font-smoothing: antialiased;
}
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
/* the 1%-at-a-time background shift lives here */
background:
radial-gradient(
120% 90% at 50% -10%,
hsl(var(--hue) 13% calc(9.5% + var(--bg-shift) * 1%)),
var(--bg) 70%
);
transition: background 2.4s ease;
}
#screen {
width: var(--panel-w);
max-width: 92vw;
padding: 2rem;
}
.panel { animation: fade-in 1.1s ease both; }
.hidden { display: none !important; }
/* --- title --- */
.title {
font-size: 3rem;
font-weight: 300;
letter-spacing: 0.18em;
text-align: center;
margin: 0 0 1.5rem;
color: var(--fg);
}
.select-lead {
text-align: center;
color: var(--dim);
line-height: 1.9;
margin: 0 0 1rem;
}
/* The ways through fade in a beat after the title, so the first paint is just
"8" and the two lead lines: a deliberate, empty-of-chrome splash. */
/* The ways-through occupy their space from first paint (so the title/lead sit at
their final position immediately); they only fade opacity in on reveal. The
min-height reserves the arcs' room even before they have loaded, so nothing
shifts between the splash and the loaded screen. */
.select-reveal { transition: opacity 1.3s ease; min-height: 19rem; }
.select-reveal.pre { opacity: 0; visibility: hidden; }
.brief {
white-space: pre-wrap;
line-height: 1.9;
color: var(--dim);
font-size: 1.02rem;
min-height: 12rem;
margin-bottom: 2rem;
}
.brief .lead { color: var(--fg); letter-spacing: 0.14em; }
/* --- arc cards --- */
.arcs {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 2rem;
}
.arc-card {
text-align: left;
background: transparent;
color: var(--fg);
border: 1px solid #2a2d31;
border-left: 5px solid #2a2d31;
padding: 1.1rem 1.3rem;
font-family: inherit;
cursor: pointer;
transition: border-color 0.3s ease, background 0.3s ease, transform 0.15s ease,
box-shadow 0.3s ease;
}
.arc-card:hover { transform: translateX(4px); }
/* Each arc wears its own colour prominently: tinted card, coloured title and
edge, a glow on hover. */
.arc-card[data-skin="hallway"] {
border-color: rgba(111, 178, 238, 0.35);
border-left-color: #6fb2ee;
background: rgba(111, 178, 238, 0.06);
}
.arc-card[data-skin="hallway"] .arc-title { color: #6fb2ee; }
.arc-card[data-skin="hallway"]:hover {
border-color: #6fb2ee;
background: rgba(111, 178, 238, 0.12);
box-shadow: 0 0 24px -8px rgba(111, 178, 238, 0.6);
}
.arc-card[data-skin="stairway"] {
border-color: rgba(143, 199, 154, 0.35);
border-left-color: #8fc79a;
background: rgba(143, 199, 154, 0.06);
}
.arc-card[data-skin="stairway"] .arc-title { color: #8fc79a; }
.arc-card[data-skin="stairway"]:hover {
border-color: #8fc79a;
background: rgba(143, 199, 154, 0.12);
box-shadow: 0 0 24px -8px rgba(143, 199, 154, 0.5);
}
.arc-card[data-skin="coach"] {
border-color: rgba(216, 176, 106, 0.35);
border-left-color: #d8b06a;
background: rgba(216, 176, 106, 0.06);
}
.arc-card[data-skin="coach"] .arc-title { color: #d8b06a; }
.arc-card[data-skin="coach"]:hover {
border-color: #d8b06a;
background: rgba(216, 176, 106, 0.12);
box-shadow: 0 0 24px -8px rgba(216, 176, 106, 0.5);
}
.arc-title {
font-size: 1.3rem;
font-weight: 300;
letter-spacing: 0.12em;
}
.arc-tag {
color: var(--dim);
font-size: 0.95rem;
margin-top: 0.4rem;
line-height: 1.5;
}
/* --- hud --- */
.hud {
display: flex;
justify-content: space-between;
font-size: 0.72rem;
letter-spacing: 0.24em;
text-transform: uppercase;
color: var(--dim2); /* chrome tier */
margin-bottom: 2.5rem;
gap: 1rem;
}
/* The progress word ("HALLWAY 3 / 8") takes a hair of the arc's accent, so the
HUD carries the arc's colour too, not just the prose. */
.progress { color: color-mix(in srgb, var(--accent) 42%, var(--dim2)); }
.best { opacity: 0.72; white-space: nowrap; }
/* --- the loop --- */
.heading {
text-align: center;
font-weight: 300;
letter-spacing: 0.22em;
font-size: 1.5rem;
margin: 0 0 2.5rem;
color: var(--fg);
}
.prose {
line-height: 2.1;
font-size: 1.12rem;
font-weight: var(--weight);
letter-spacing: var(--tracking);
min-height: 11rem;
}
.prose .line {
opacity: 0;
cursor: default;
transition: opacity 0.9s ease, color 0.4s ease;
}
.prose .line.show { opacity: 1; }
.prose .line:hover { color: #fff; }
/* Hard mode double-change reveal: the two changed lines briefly glow the accent
after a correct turn-back, teaching "two moved here". Silent, no text. */
.prose .line.flare { animation: flare 1.4s ease; }
@keyframes flare {
0% { color: inherit; text-shadow: none; }
25% { color: var(--accent);
text-shadow: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent); }
70% { color: var(--accent);
text-shadow: 0 0 6px color-mix(in srgb, var(--accent) 35%, transparent); }
100% { color: inherit; text-shadow: none; }
}
html.motion-reduce .prose .line.flare {
animation: none;
color: var(--accent); /* still reveal which two, just without the pulse */
}
.aside {
color: var(--dim);
font-style: italic;
min-height: 1.6rem;
opacity: 0;
transition: opacity 1.2s ease;
margin-top: 0.5rem;
}
.aside.show { opacity: 0.85; }
/* --- controls --- */
.controls {
display: flex;
gap: 1rem;
margin-top: 2.5rem;
animation: fade-in 1s ease both;
}
.confidence {
margin-top: 1.6rem;
animation: fade-in 0.6s ease both;
}
.conf-q { color: var(--dim); font-size: 0.92rem; margin: 0 0 0.7rem; }
.conf-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.row {
display: flex;
gap: 1rem;
align-items: center;
justify-content: center;
}
/* --- buttons --- */
.btn {
background: transparent;
color: var(--fg);
border: 1px solid #2a2d31;
padding: 0.8rem 1.4rem;
font-family: inherit;
font-size: 1rem;
letter-spacing: 0.06em;
cursor: pointer;
transition: border-color 0.3s ease, background 0.3s ease, color 0.3s ease;
}
.btn:hover { border-color: var(--accent); color: #fff; }
.btn:active { transform: translateY(1px); }
/* The go-on / turn-back buttons carry a whisper of the arc's accent even at
rest, so the place keeps its colour identity in-loop (blue hallway, amber
coach, green stair) instead of collapsing to the same neutral pair. */
.choice { flex: 1; border-color: color-mix(in srgb, var(--accent) 26%, #2a2d31); }
.choice:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.btn.ghost {
border-color: #23262a;
color: var(--dim);
padding: 0.55rem 1rem;
font-size: 0.86rem;
}
.btn.ghost:hover { color: var(--fg); border-color: var(--dim); }
#begin, #again { min-width: 12rem; }
/* always-visible mute toggle */
.sound-toggle {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 10;
background: transparent;
border: none;
line-height: 0;
padding: 0.4rem;
cursor: pointer;
color: var(--dim2); /* chrome tier, per skin */
opacity: 0.55;
transition: opacity 0.3s ease, color 0.3s ease;
}
.sound-toggle svg { display: block; }
.sound-toggle:hover { opacity: 1; color: var(--fg); }
/* language picker: a quiet fixed corner control, top-left (mirrors the sound
toggle top-right). Only shown on the selection screen. */
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0);
white-space: nowrap; border: 0;
}
.lang-corner {
position: fixed;
top: 1rem;
left: 1rem;
z-index: 10;
display: flex;
align-items: center;
gap: 0.3rem;
color: var(--dim2); /* chrome tier, per skin */
opacity: 0.55;
transition: opacity 0.3s ease, color 0.3s ease;
}
.lang-corner:hover { opacity: 1; color: var(--fg); }
.lang-globe { display: block; flex: none; }
/* Strip the OS chrome: no native border/background/arrow, a drawn caret
instead, so the resting control reads as part of the mood. */
.lang-select {
appearance: none;
-webkit-appearance: none;
background: transparent;
border: none;
color: inherit;
font-family: inherit;
font-size: 0.8rem;
letter-spacing: 0.04em;
cursor: pointer;
padding: 0.1rem 0.9rem 0.1rem 0.1rem;
background-image:
linear-gradient(45deg, transparent 50%, currentColor 50%),
linear-gradient(135deg, currentColor 50%, transparent 50%);
background-size: 4px 4px, 4px 4px;
background-position: right 3px center, right calc(3px - 4px) center;
background-repeat: no-repeat;
}
.lang-select:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
/* The open dropdown is OS-native; keep its rows legible on the dark bg. */
.lang-select option { background: #16181c; color: #d7d7d2; }
/* a quiet text link to abandon the run */
.give-up-row { text-align: center; margin-top: 3.5rem; }
.link-btn {
background: none;
border: none;
color: var(--dim2); /* chrome tier */
font-family: inherit;
font-size: 0.8rem;
letter-spacing: 0.14em;
text-transform: uppercase;
cursor: pointer;
opacity: 0.78;
transition: opacity 0.3s ease, color 0.3s ease;
}
.link-btn:hover { opacity: 1; color: var(--fg); }
/* On the win screen the brief needs no reserved height (that spacer is for the
intro); collapse it so the fleeting "look back" line sits close under the
win text rather than across a dead gap. */
#win .brief { min-height: 0; margin-bottom: 1.6rem; }
/* --- fleeting "look back" (how many runs it took) --- */
.look { text-align: center; margin: 0 0 1.4rem; min-height: 1.5rem; }
.look-toggle {
background: none;
border: none;
color: var(--dim2); /* chrome tier */
font-family: inherit;
font-size: 0.82rem;
letter-spacing: 0.04em;
cursor: pointer;
opacity: 0.7;
padding: 0 0 1px;
border-bottom: 1px dotted rgba(255, 255, 255, 0.18);
transition: opacity 0.3s ease, color 0.3s ease;
}
.look-toggle:hover { opacity: 1; color: var(--fg); }
.look-line {
color: var(--dim);
font-style: italic;
font-size: 0.95rem;
line-height: 1.55;
margin: 0;
opacity: 0;
transition: opacity 0.8s ease;
}
.look-line.show { opacity: 1; }
/* --- win-screen share (the reward: pass the place on) ---
Kept quiet and compact so it reads as part of the win, not a bolted-on box. */
.share {
margin: 1.6rem auto 1.8rem;
max-width: 30rem;
text-align: center;
}
/* Progressive disclosure: the arc prompt is a quiet trigger (matches the
"look back" toggle); the panel with the line and share targets stays folded
until tapped, so the reward moment is calm by default. */
.share-open {
background: none;
border: none;
color: var(--dim2); /* chrome tier */
font-family: inherit;
font-size: 0.82rem;
letter-spacing: 0.04em;
cursor: pointer;
opacity: 0.7;
padding: 0 0 1px;
border-bottom: 1px dotted rgba(255, 255, 255, 0.18);
transition: opacity 0.3s ease, color 0.3s ease;
}
.share-open:hover { opacity: 1; color: var(--fg); }
.share-panel {
opacity: 0;
transition: opacity 0.7s ease;
}
.share-panel.show { opacity: 1; }
.share-line {
color: var(--dim); /* reading tier: a quiet aside, not a headline */
font-style: italic;
font-size: 0.95rem;
line-height: 1.6;
margin: 0 auto 1.1rem;
}
.share-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.4rem;
align-items: baseline;
justify-content: center;
}
.share-platforms {
display: inline-flex;
flex-wrap: wrap;
gap: 0.5rem 1.4rem;
justify-content: center;
}
.share-btn { margin: 0; text-decoration: none; }
@media (max-width: 640px) {
.share { margin: 1.2rem auto 1.4rem; }
.share-row { gap: 0.4rem 1.1rem; }
.share-line { font-size: 0.95rem; }
}
/* --- Act 2: touch the room --- */
.touch-line {
text-align: center;
color: var(--dim);
font-style: italic;
line-height: 1.35;
min-height: 1.2rem;
margin: 0.2rem auto 0.5rem;
max-width: 34rem;
/* Balance the wrap so a single short word never orphans onto its own line. */
text-wrap: balance;
opacity: 0;
transition: opacity 0.7s ease;
}
.touch-line.show { opacity: 1; }
.touch-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.3rem;
justify-content: center;
margin: 0 0 1.4rem;
}
.touch-btn { margin: 0; }
.touch-btn:disabled { opacity: 0.35; cursor: default; }
/* The rare false way out: visually distinct (accent), never a misclick. */
.exit-offer {
text-align: center;
margin: 0 auto 1.6rem;
max-width: 30rem;
padding: 0.9rem 1rem;
border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
border-radius: 2px;
background: color-mix(in srgb, var(--accent) 7%, transparent);
animation: fade-in 0.9s ease both;
}
.exit-tempt {
color: var(--fg);
line-height: 1.6;
margin: 0 0 0.7rem;
}
.exit-take {
color: var(--accent) !important;
letter-spacing: 0.14em;
text-transform: uppercase;
font-size: 0.82rem;
opacity: 0.95;
}
.exit-take:hover { opacity: 1; }
/* Reduced motion: the game leans on slow fades and reveals. For users who ask
for less motion (OS setting or the in-app toggle, JS sets html.motion-reduce),
drop the transitions/animations and make sure nothing that carries meaning
starts invisible or fades away. JS also shows loop text at once and skips the
timed "look back" fade-out. */
html.motion-reduce *,
html.motion-reduce *::before,
html.motion-reduce *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
animation-delay: 0ms !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
html.motion-reduce .line,
html.motion-reduce .aside,
html.motion-reduce .look-line,
html.motion-reduce .share-panel,
html.motion-reduce .select-reveal,
html.motion-reduce .exit-offer { opacity: 1 !important; }
/* .touch-line is deliberately NOT forced visible here: it toggles on/off via
the `show` class (cleared each loop), so it must be free to return to
opacity 0. But it DOES keep a gentle opacity fade even under reduced motion
(opacity is not motion): a flavour line appears, holds long enough to read,
then fades on a timer instead of snapping in and out. */
html.motion-reduce .touch-line { transition-duration: 0.7s !important; }
/* --- text size (opt-in; default is unchanged) --- */
:root[data-text="1"] { font-size: 112.5%; }
:root[data-text="2"] { font-size: 125%; }
/* --- brightness (opt-in; lifts the dim reading + chrome tiers toward the
foreground for legibility; default leaves the skin values untouched) --- */
body[data-bright="1"] { --dim: #b8bbc1 !important; --dim2: #a2a6ad !important; }
body[data-bright="2"] { --dim: #dcdcd7 !important; --dim2: #c6cad0 !important; }
/* --- settings gear + panel --- */
.settings-toggle {
position: fixed;
top: 1rem;
right: 3.4rem;
z-index: 10;
background: transparent;
border: none;
line-height: 0;
padding: 0.4rem;
cursor: pointer;
color: var(--dim2);
opacity: 0.55;
transition: opacity 0.3s ease, color 0.3s ease;
}
.settings-toggle:hover { opacity: 1; color: var(--fg); }
/* Collection corner button: a quiet icon, third in the top-right cluster. */
.ach-corner-toggle {
position: fixed;
top: 1rem;
right: 5.8rem;
z-index: 10;
background: transparent;
border: none;
line-height: 0;
padding: 0.4rem;
cursor: pointer;
color: var(--dim2);
opacity: 0.55;
transition: opacity 0.3s ease, color 0.3s ease;
}
.ach-corner-toggle svg { display: block; }
.ach-corner-toggle:hover { opacity: 1; color: var(--fg); }
.settings-panel { text-align: left; }
.set-group { margin: 0 0 1.3rem; }
.set-label {
display: block;
color: var(--dim2);
font-size: 0.78rem;
letter-spacing: 0.14em;
text-transform: uppercase;
margin: 0 0 0.6rem;
}
.set-opts { display: flex; gap: 0.6rem; }
.set-opts button {
flex: 1;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.14);
color: var(--fg);
border-radius: 2px;
padding: 0.5rem;
cursor: pointer;
font-family: inherit;
display: flex;
align-items: center;
justify-content: center;
min-height: 2.4rem;
}
.set-opts button:hover { border-color: var(--accent); }
.set-opts button.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.set-opts .swatch {
width: 1.4rem; height: 1.4rem; border-radius: 50%;
background: var(--fg); display: block;
}
/* Text-size and brightness as 3-stop sliders, with a small cue at each end. */
.set-slider { display: flex; align-items: center; gap: 0.7rem; }
.set-end {
color: var(--dim2); line-height: 1; flex: none;
font-weight: 300; letter-spacing: 0;
}
.set-end.swatch { width: 0.9rem; height: 0.9rem; border-radius: 50%; background: var(--fg); }
/* The track carries a thin line plus a notch at each of the three stops (left,
centre, right), so it reads as three positions, not a continuous dial. */
.set-range {
flex: 1; -webkit-appearance: none; appearance: none;
height: 0.9rem; background: transparent;
outline: none; cursor: pointer; margin: 0.5rem 0;
}
.set-range::-webkit-slider-runnable-track {
height: 0.9rem;
background:
linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)) 0% 50% / 2px 0.55rem no-repeat,
linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)) 50% 50% / 2px 0.55rem no-repeat,
linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)) 100% 50% / 2px 0.55rem no-repeat,
linear-gradient(rgba(255,255,255,0.18),rgba(255,255,255,0.18)) 50% 50% / 100% 2px no-repeat;
}
.set-range::-moz-range-track {
height: 0.9rem;
background:
linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)) 0% 50% / 2px 0.55rem no-repeat,
linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)) 50% 50% / 2px 0.55rem no-repeat,
linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)) 100% 50% / 2px 0.55rem no-repeat,
linear-gradient(rgba(255,255,255,0.18),rgba(255,255,255,0.18)) 50% 50% / 100% 2px no-repeat;
}
.set-range::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 0.95rem; height: 0.95rem; border-radius: 50%; margin-top: -0.025rem;
background: var(--accent); border: none; cursor: pointer;
}
.set-range::-moz-range-thumb {
width: 0.95rem; height: 0.95rem; border-radius: 50%;
background: var(--accent); border: none; cursor: pointer;
}
.set-range:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 40%, transparent); }
/* --- achievements / collection page --- */
.ach-open-row { text-align: center; margin-top: 2.4rem; }
.ach-overlay {
position: fixed; inset: 0; z-index: 70;
background: rgba(0, 0, 0, 0.72);
display: flex; align-items: flex-start; justify-content: center;
overflow-y: auto; padding: 2rem 1rem;
animation: fade-in 0.5s ease both;
}
.ach-panel {
background: var(--bg);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 3px;
max-width: 40rem; width: 100%;
padding: 1.8rem 1.8rem 2.2rem;
margin: auto;
}
.ach-head { display: flex; align-items: baseline; justify-content: space-between; }
.ach-title { font-size: 1.3rem; font-weight: 300; letter-spacing: 0.14em; color: var(--fg); margin: 0; }
.ach-close {
background: none; border: none; color: var(--dim2);
font-size: 1.6rem; line-height: 1; cursor: pointer; padding: 0 0.2rem;
}
.ach-close:hover { color: var(--fg); }
.ach-progress { color: var(--dim2); font-size: 0.82rem; letter-spacing: 0.12em;
text-transform: uppercase; margin: 0.4rem 0 1.6rem; }
/* Section headers set the two halves apart: a small hairline-underlined label. */
.ach-section-head {
color: var(--dim); font-size: 0.72rem; letter-spacing: 0.22em;
text-transform: uppercase; margin: 0 0 0.9rem;
padding-bottom: 0.4rem; border-bottom: 1px solid rgba(255, 255, 255, 0.09);
}
.ach-section { margin-bottom: 1.9rem; }
.ach-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.7rem;
}
.ach-tile {
display: flex; align-items: flex-start; gap: 0.7rem;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 2px; padding: 0.8rem 0.9rem; opacity: 0.5;
}
.ach-tile.on { opacity: 1; border-color: color-mix(in srgb, var(--accent) 45%, transparent);
background: color-mix(in srgb, var(--accent) 6%, transparent); }
.ach-icon { flex: none; margin-top: 0.05rem; color: var(--dim2); }
.ach-tile.on .ach-icon { color: var(--accent); }
/* The empty-state line lives inside the 2-column grid; without this it is boxed
into one column (half width), so a short English line fits but longer
translations wrap to two. Span the full row so every locale reads as one line. */
.ach-empty { grid-column: 1 / -1; color: var(--dim); font-size: 0.9rem; }
.ach-body { min-width: 0; }
.ach-name { color: var(--fg); font-size: 0.98rem; letter-spacing: 0.03em; margin-bottom: 0.2rem; }
.ach-tile:not(.on) .ach-name { color: var(--dim2); letter-spacing: 0.14em; text-transform: uppercase; font-size: 0.8rem; }
.ach-desc { color: var(--dim); font-size: 0.85rem; line-height: 1.4; }
.ach-tile:not(.on) .ach-desc { visibility: hidden; }
/* Collectibles read as a quieter, inset ledger, distinct from the badge grid. */
.col-section {
margin-bottom: 1.9rem;
padding: 1.1rem 1.1rem 0.5rem;
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 3px;
background: rgba(255, 255, 255, 0.015);
}
.col-section .ach-section-head { border-bottom-color: rgba(255, 255, 255, 0.06); }
.ach-flash { margin-bottom: 0; }
.flash-row { display: flex; align-items: center; gap: 0.7rem; margin: 0 0 0.6rem; }
.flash-arc { color: var(--dim); font-size: 0.85rem; min-width: 6.5rem; }
.flash-dots { display: flex; gap: 0.35rem; flex: 1; flex-wrap: wrap; }
.flash-dot {
width: 0.72rem; height: 0.72rem; border-radius: 50%; padding: 0;
border: 1px solid rgba(255, 255, 255, 0.22); background: transparent;
}
.flash-dot.on { cursor: pointer; transition: transform 0.12s ease, box-shadow 0.12s ease; }
.flash-dot.on:hover, .flash-dot.on:focus-visible { transform: scale(1.25); outline: none; }
.flash-dot.on.recall { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px currentColor; }
.flash-row[data-skin="hallway"] .flash-dot.on { background: #6fb2ee; border-color: #6fb2ee; color: #6fb2ee; }
.flash-row[data-skin="stairway"] .flash-dot.on { background: #8fc79a; border-color: #8fc79a; color: #8fc79a; }
.flash-row[data-skin="coach"] .flash-dot.on { background: #d8b06a; border-color: #d8b06a; color: #d8b06a; }
.flash-num { color: var(--dim2); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
/* The recalled fragment's own words, surfacing quietly under the ledger. */
.col-recap {
color: var(--dim); font-size: 0.9rem; line-height: 1.5; font-style: italic;
margin: 0.9rem 0 0.2rem; min-height: 1.2rem; opacity: 0;
transition: opacity 0.5s ease;
}
.col-recap.show { opacity: 1; }
/* Records: a quiet self-competition ledger, one row per how-you-won mark. */
.rec-section { margin-bottom: 1.9rem; }
.ach-rec { display: flex; flex-direction: column; gap: 0.55rem; }
.rec-row { display: flex; align-items: center; gap: 0.7rem; opacity: 0.5; }
.rec-row.on { opacity: 1; }
.rec-dot {
width: 0.65rem; height: 0.65rem; border-radius: 50%; flex: none;
border: 1px solid rgba(255, 255, 255, 0.22); background: transparent;
}
.rec-dot.on { background: var(--accent); border-color: var(--accent); }
.rec-label { color: var(--dim); font-size: 0.88rem; line-height: 1.4; }
.rec-row.on .rec-label { color: var(--fg); }
/* The fleeting "how you did" record under the win-screen look-back line. */
.look-record { display: inline-block; margin-top: 0.5rem; color: var(--dim2); font-style: italic; }
/* Let the win-screen ending breathe: the look-back and share surfaces stay
hushed for a beat after the ending, then fade up, so the benediction lands
alone first. */
#look, #share { transition: opacity 1s ease; }
.win-hush #look, .win-hush #share, .win-hush #nudge { opacity: 0; pointer-events: none; }
/* Difficulty / reading (register) settings: word buttons + a hint line. */
.set-group-wide { display: block; }
.set-group-wide .set-label { display: block; margin-bottom: 0.5rem; }
.set-words { display: flex; gap: 0.5rem; }
.set-words button {
flex: 1; background: transparent; color: var(--dim);
border: 1px solid rgba(255, 255, 255, 0.14); border-radius: 2px;
font-family: inherit; font-size: 0.9rem; letter-spacing: 0.04em;
padding: 0.5rem 0.4rem; cursor: pointer;
}
.set-words button.on { color: var(--fg); border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 8%, transparent); }
.set-words button.locked { opacity: 0.4; cursor: not-allowed; }
.set-hint { color: var(--dim2); font-size: 0.78rem; margin: 0.4rem 0 0.6rem; min-height: 1rem; }
/* Coach: the passenger's fixed utterance. Quiet by default; louder in easy. */
.npc-utterance {
color: var(--dim); font-style: italic; line-height: 1.5; margin: 0.6rem 0 0;
opacity: 0; transition: opacity 0.6s ease; max-width: 34rem;
}
.npc-utterance.show { opacity: 1; }
.npc-utterance.prominent {
color: var(--fg); font-style: normal; font-size: 1.08rem;
border-left: 2px solid var(--accent); padding-left: 0.9rem;
}
/* Simple reading register: the plain rule, set apart quietly from the prose. */
.intro-plain, .learn-plain {
color: var(--dim); font-size: 0.9rem; line-height: 1.5;
border-left: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
padding-left: 0.9rem; margin-top: 1.1rem;
}
.ach-actions { display: flex; gap: 1rem; justify-content: center; margin: 0 0 1.4rem; }
.ach-actions .btn { min-width: 7rem; }
@media (max-width: 640px) {
.ach-grid { grid-template-columns: 1fr; }
.flash-arc { min-width: 5rem; }
}
/* --- verdict flashes --- */
.flash-good { animation: good 1.4s ease; }
.flash-bad { animation: bad 1.4s ease; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes good {
0% { box-shadow: none; }
30% { box-shadow: inset 0 0 120px -40px var(--accent); }
100% { box-shadow: none; }
}
@keyframes bad {
0% { box-shadow: none; }
20% { box-shadow: inset 0 0 160px -30px var(--danger); }
100% { box-shadow: none; }
}
/* --- onboarding panel (first reset only) --- */
.learn-overlay {
position: fixed;
inset: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
background: rgba(6, 7, 8, 0.72);
backdrop-filter: blur(2px);
animation: fade-in 0.7s ease both;
}
.learn-panel {
width: 34rem;
max-width: 92vw;
/* Never taller than the viewport: scroll inside the panel so the button at the
bottom (Begin / Go on) is always reachable, even on a short phone. */
max-height: calc(100dvh - 2rem);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
background: color-mix(in srgb, var(--bg) 82%, #16181c);
border: 1px solid #2a2d31;
border-top: 2px solid var(--accent);
padding: 2.4rem 2.2rem 2rem;
box-shadow: 0 24px 80px -30px #000;
animation: fade-in 0.9s ease both;
}
/* Settings uses the same overlay; keep its panel scrollable too. */
.learn-overlay > .settings-panel,
#settings .learn-panel { max-height: calc(100dvh - 2rem); overflow-y: auto; }
.learn-title {
color: var(--fg);
font-size: 1.15rem;
letter-spacing: 0.06em;
line-height: 1.6;
margin: 0 0 1.4rem;
}
.learn-lines { margin: 0 0 1.9rem; }
.learn-lines p {
color: var(--dim);
line-height: 1.65;
margin: 0 0 0.85rem;
padding-left: 1rem;
border-left: 1px solid #2a2d31;
}
.learn-lines p:last-child { margin-bottom: 0; }
.learn-panel .btn { display: block; width: 100%; }
/* First-reset rule cutscene: a full-screen quiet line that fades into the
instructions. */
.cutscene-overlay {
position: fixed;
inset: 0;
z-index: 60;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
cursor: pointer;
animation: fade-in 1.3s ease both;
}
.cutscene-text {
max-width: 32rem;
text-align: center;
color: var(--fg);
font-size: 1.15rem;
line-height: 2;
letter-spacing: 0.04em;
}
.cutscene-text p { margin: 0 0 1.2rem; }
.cutscene-text p:last-child { margin-bottom: 0; color: var(--dim); font-size: 1rem; }
/* --- ending credits (landing-quiet style, slow upward roll) --- */
.credits-overlay {
position: fixed;
inset: 0;
z-index: 80;
background: var(--bg);
cursor: pointer;
animation: fade-in 1.3s ease both;
}
.credits-scroller { position: absolute; inset: 0; overflow: hidden; }
.credits-roll {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: min(32rem, 88vw);
text-align: center;
color: var(--dim);
animation: credits-scroll 52s linear forwards;
}
@keyframes credits-scroll {
from { transform: translate(-50%, 0); }
to { transform: translate(-50%, calc(-100% - 100vh)); }
}
/* One deliberate type scale; spacing lives on the blocks, not on manual gaps. */
.credits-block { margin: 0 0 3.6rem; }
/* Small "a Melon Lab game" presenter above the title. */
.credits-presenter {
color: var(--dim2);
font-size: 0.8rem;
letter-spacing: 0.3em;
text-transform: uppercase;
margin: 0 0 1.3rem;
}
/* Closing studio splash: the wordmark alone, a small copyright beneath. */
.credits-splash {
color: var(--fg);
font-size: 1.9rem;
font-weight: 300;
letter-spacing: 0.34em;
text-transform: uppercase;
margin: 0 0 0.9rem;
}
.credits-copy { color: var(--dim2); font-size: 0.82rem; letter-spacing: 0.12em; margin: 0; }
.credits-title {
color: var(--fg);
font-size: 2.6rem;
font-weight: 300;
letter-spacing: 0.32em;
margin: 0;
}
.credits-head {
color: var(--dim2);
font-size: 0.78rem;
letter-spacing: 0.36em;
margin: 0 0 1.4rem;
}
.credits-cast + .credits-cast { margin-top: 1.5rem; }
.credits-role { color: var(--dim2); font-size: 0.86rem; letter-spacing: 0.05em; line-height: 1.5; }
.credits-name { color: var(--fg); font-size: 1.15rem; letter-spacing: 0.06em; line-height: 1.55; }
/* A secondary name (the AI collaborators) sits one notch smaller than the lead. */
.credits-name-sm { font-size: 1rem; }
.credits-note { color: var(--dim); font-style: italic; font-size: 1rem; line-height: 1.6; }
/* Dedication: each "the ..." gets room to breathe. */
.credits-ded + .credits-ded { margin-top: 2rem; }
.credits-ded .credits-role { margin-bottom: 0.35rem; }
.credits-quote { color: var(--dim); font-style: italic; font-size: 1.06rem; line-height: 1.95; }
.credits-by { color: var(--dim2); font-size: 0.9rem; letter-spacing: 0.14em; margin: 1.4rem 0 0; }
/* The closing quote stands alone, well after the rest, before it fades out. */
.credits-solo { margin-top: 46vh; margin-bottom: 42vh; }
.credits-done {
position: fixed;
top: 1rem; right: 1rem;
z-index: 81;
background: none; border: none;
color: var(--dim2);
font-size: 1.8rem; line-height: 1;
cursor: pointer; opacity: 0.6;
transition: opacity 0.3s ease, color 0.3s ease;
}
.credits-done:hover { opacity: 1; color: var(--fg); }
/* Reduced motion: no roll. Read it statically, scrolled by hand. */
html.motion-reduce .credits-roll {
position: static;
transform: translateX(-50%);
animation: none;
margin: 0 auto;
padding: 3rem 0;
}
html.motion-reduce .credits-scroller { overflow-y: auto; }
/* Erase confirmation: a small centered modal over a dim scrim. */
.confirm-overlay {
position: fixed;
inset: 0;
z-index: 90;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: rgba(0, 0, 0, 0.72);
animation: fade-in 0.4s ease both;
}
.confirm-panel {
max-width: 26rem;
background: var(--bg);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 3px;
padding: 1.8rem 1.6rem 1.4rem;
text-align: center;
}
.confirm-title { color: var(--fg); font-size: 1.15rem; letter-spacing: 0.06em; margin: 0 0 0.9rem; }
.confirm-body { color: var(--dim); line-height: 1.65; font-size: 0.96rem; margin: 0 0 1.6rem; }
.confirm-row { display: flex; gap: 0.9rem; justify-content: center; }
.btn-danger { border-color: color-mix(in srgb, var(--danger) 55%, #2a2d31); color: var(--danger); }
.btn-danger:hover { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent); color: #fff; }
.btn-erase { color: color-mix(in srgb, var(--danger) 70%, var(--dim)); }
.btn-erase:hover { border-color: var(--danger); color: var(--danger); }
/* Win-screen adaptive nudge: a quiet line, chrome tier. */
.nudge {
text-align: center;
color: var(--dim2);
font-size: 0.95rem;
font-style: italic;
line-height: 1.6;
margin: 0 0 1.4rem;
}
.credits-row { margin-top: 1.4rem; text-align: center; }
.view-credits { font-size: 0.8rem; letter-spacing: 0.14em; text-transform: uppercase; }
.fold-hide-row {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--dim2);
font-size: 0.82rem;
margin: 0 0 1.4rem;
cursor: pointer;
}
.fold-hide-row input { accent-color: var(--accent); cursor: pointer; }
@media (max-width: 640px) {
/* Top-align instead of centering: centered flex clips the top (the HUD) when
content is taller than the viewport. Top-align keeps the progress bar
visible and lets the page scroll naturally. */
body {
align-items: flex-start;
justify-content: flex-start;
}
#screen {
padding: 1.1rem;
/* Clear the fixed toggle cluster, and add the notch inset so on a notched
iPhone (Safari) the content shifts down with the toggles instead of
sliding under them. */
padding-top: calc(3rem + env(safe-area-inset-top));
}
.title { font-size: 2.2rem; }
.select-lead { font-size: 0.95rem; }
.select-reveal { min-height: 23.5rem; } /* arcs stack taller on narrow screens */
.brief { min-height: 0; margin-bottom: 1.5rem; }
/* progress bar: smaller, always readable, wraps rather than hides */
.hud {
font-size: 0.68rem;
letter-spacing: 0.12em;
margin-bottom: 1.6rem;
flex-wrap: wrap;
gap: 0.2rem 0.8rem;
/* Keep "Furthest" out from under the sound/settings toggles at the top-right.
space-between otherwise pins it to the very edge, directly beneath the
icons; on Safari the vertical clearance is tight and they touched. */
padding-right: 4rem;
}
.progress { font-weight: 500; }
.heading { font-size: 1.25rem; margin-bottom: 1.6rem; }
.prose { font-size: 1.02rem; line-height: 1.95; min-height: 7rem; }
.controls { margin-top: 1.8rem; }
.give-up-row { margin-top: 2.2rem; }
.arcs { margin-top: 1.5rem; }
/* Memory action row: on a 375px phone three 7rem buttons overflow the panel.
Let them share the width evenly and shrink instead of running off-edge. */
.ach-actions { gap: 0.5rem; }
.ach-actions .btn { min-width: 0; flex: 1 1 0; padding-left: 0.6rem; padding-right: 0.6rem; }
/* Follow the notch on the fixed corner controls so they never hide under it. */
.sound-toggle { top: calc(0.5rem + env(safe-area-inset-top)); right: 0.5rem; opacity: 0.6; }
.settings-toggle { top: calc(0.5rem + env(safe-area-inset-top)); right: 2.7rem; opacity: 0.6; }
.ach-corner-toggle { top: calc(0.5rem + env(safe-area-inset-top)); right: 4.9rem; opacity: 0.6; }
.lang-corner { top: calc(0.5rem + env(safe-area-inset-top)); left: 0.5rem; opacity: 0.6; }
}
/* Very small phones (iPhone 12/13 mini, ~375px and under): tighten type and
spacing so prose stays compact and nothing overflows its box. The larger
phones (390-430px: 14/15/16/17 and the Pro / Pro Max) keep the 640px sizing. */
@media (max-width: 380px) {
#screen { padding: 0.9rem; padding-top: calc(2.8rem + env(safe-area-inset-top)); }
.title { font-size: 2rem; }
.heading { font-size: 1.15rem; letter-spacing: 0.16em; }
.prose { font-size: 0.97rem; line-height: 1.85; }
.hud { font-size: 0.64rem; letter-spacing: 0.09em; padding-right: 3.6rem; }
.ach-panel { padding: 1.4rem 1.2rem 1.8rem; }
.ach-title { font-size: 1.15rem; }
.ach-actions .btn { padding-left: 0.4rem; padding-right: 0.4rem; font-size: 0.85rem; }
.learn-panel { padding-left: 1.3rem; padding-right: 1.3rem; }
}