feat(adventure): add CSS theme, layout, and component styles

Extracts v1 visual design from adventure.html into 8 modular CSS files
(theme, layout, hud, room, challenge, sidebar, map, animations) and
imports them in main.ts.
This commit is contained in:
Alexander Brown
2026-03-26 18:13:10 -07:00
parent 87d0865bf2
commit 7ea4a370bd
9 changed files with 1291 additions and 0 deletions

View File

@@ -1,3 +1,12 @@
import './style/theme.css'
import './style/layout.css'
import './style/hud.css'
import './style/room.css'
import './style/challenge.css'
import './style/sidebar.css'
import './style/map.css'
import './style/animations.css'
function main(): void {
const app = document.getElementById('app')
if (!app) throw new Error('Missing #app element')

View File

@@ -0,0 +1,57 @@
@keyframes fadeSlideIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes unlockPulse {
0% {
box-shadow: 0 0 0 0 rgb(88 166 255 / 0.4);
}
70% {
box-shadow: 0 0 0 8px rgb(88 166 255 / 0);
}
100% {
box-shadow: 0 0 0 0 rgb(88 166 255 / 0);
}
}
@keyframes nodeUnlock {
0% {
opacity: 0.3;
transform: scale(0.9);
}
60% {
transform: scale(1.05);
}
100% {
opacity: 1;
transform: scale(1);
}
}
#narrative {
transition: opacity 0.2s ease;
}
#narrative.exit,
#narrative.enter {
opacity: 0;
}
.map-room.newly-unlocked {
animation: unlockPulse 0.6s ease-out;
}
.map-node.newly-unlocked circle {
animation: unlockPulse 0.6s ease-out;
}
.map-node {
transition: opacity 0.3s ease, transform 0.3s ease;
}

View File

@@ -0,0 +1,208 @@
#challenge-panel {
border: 2px solid var(--yellow);
border-radius: 10px;
overflow: hidden;
display: none;
animation: fadeSlideIn 0.3s ease;
}
#challenge-panel.active {
display: block;
}
#challenge-header {
background: rgb(210 153 34 / 0.1);
padding: 12px 16px;
border-bottom: 1px solid var(--yellow);
display: flex;
align-items: center;
gap: 8px;
}
#challenge-header .icon {
font-size: 16px;
}
#challenge-title {
font-size: 16px;
font-weight: 600;
color: var(--yellow);
}
#challenge-desc {
padding: 14px 18px;
font-size: 15px;
line-height: 1.8;
color: var(--muted);
}
#challenge-desc code {
background: var(--surface);
border: 1px solid var(--border);
padding: 1px 5px;
border-radius: 3px;
color: var(--accent);
font-size: 14px;
}
#challenge-desc a {
color: var(--accent);
text-decoration: none;
border-bottom: 1px dotted var(--accent);
}
#challenge-desc a:hover {
border-bottom-style: solid;
}
#challenge-choices {
padding: 8px 16px 16px;
display: flex;
gap: 10px;
}
.challenge-choice-btn {
flex: 1;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 0;
color: var(--text);
font-family: inherit;
font-size: 15px;
cursor: pointer;
text-align: center;
transition: all 0.15s;
display: flex;
flex-direction: column;
overflow: hidden;
}
.challenge-choice-btn:hover {
border-color: var(--yellow);
background: rgb(210 153 34 / 0.08);
transform: translateY(-2px);
}
.challenge-choice-btn .choice-icon-wrap {
position: relative;
background: var(--bg);
padding: 12px;
}
.challenge-choice-btn .choice-key {
position: absolute;
top: 6px;
left: 6px;
background: var(--yellow);
color: var(--bg);
padding: 1px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 700;
line-height: 1.4;
}
.challenge-choice-btn .choice-icon {
width: 56px;
height: 56px;
border-radius: 8px;
border: 1px solid var(--border);
overflow: hidden;
margin: 0 auto;
}
.challenge-choice-btn .choice-icon img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.challenge-choice-btn .choice-text {
padding: 10px 12px 14px;
}
.challenge-choice-btn .choice-label {
display: block;
font-weight: 600;
font-size: 14px;
}
.challenge-choice-btn .choice-hint {
display: block;
font-size: 12px;
color: var(--muted);
margin-top: 4px;
line-height: 1.4;
}
#result-banner {
border-radius: 8px;
padding: 12px 16px;
display: none;
animation: fadeSlideIn 0.3s ease;
font-size: 15px;
line-height: 1.5;
}
#result-banner.active {
display: block;
}
#result-banner.good {
border: 1px solid var(--green);
background: rgb(63 185 80 / 0.08);
color: var(--green);
}
#result-banner.ok {
border: 1px solid var(--yellow);
background: rgb(210 153 34 / 0.08);
color: var(--yellow);
}
#result-banner.bad {
border: 1px solid var(--red);
background: rgb(248 81 73 / 0.08);
color: var(--red);
}
.stat-delta {
font-weight: 600;
font-size: 11px;
}
.stat-delta.positive {
color: var(--green);
}
.stat-delta.negative {
color: var(--red);
}
.result-recommended {
margin-top: 10px;
padding: 8px 12px;
background: rgb(88 166 255 / 0.06);
border: 1px solid var(--accent);
border-radius: 6px;
font-size: 12px;
color: var(--muted);
line-height: 1.5;
}
.result-recommended strong {
color: var(--accent);
}
.result-doc-link {
color: var(--accent);
text-decoration: none;
border-bottom: 1px dotted var(--accent);
font-size: 11px;
}
.result-doc-link:hover {
border-bottom-style: solid;
}

View File

@@ -0,0 +1,226 @@
#hud,
.stat-bar-group,
.choice-key,
.challenge-badge,
.artifact-type,
.sidebar-header,
#room-layer,
.ending-label,
.scorecard-header,
#challenge-header,
.map-room .room-layer,
#challenge-progress,
.stat-delta,
.ending-stat .label,
#play-again-btn,
#toggle-map,
.choice-btn .choice-hint,
.challenge-choice-btn .choice-hint,
.inv-slot,
.artifact-icon,
#dec-label,
#ach-label,
#inv-label {
user-select: none;
}
#hud {
position: sticky;
top: 0;
z-index: 10;
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 12px 32px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
#hud h1 {
font-size: 16px;
font-weight: 600;
color: var(--accent);
letter-spacing: 0.5px;
}
#hud-right {
display: flex;
align-items: center;
gap: 16px;
}
#stat-bars {
display: flex;
gap: 18px;
font-size: 12px;
}
.stat-bar-group {
display: flex;
align-items: center;
gap: 5px;
}
.stat-bar-label {
color: var(--muted);
white-space: nowrap;
}
.stat-bar {
width: 80px;
height: 10px;
background: var(--border);
border-radius: 4px;
overflow: hidden;
position: relative;
}
.stat-bar-fill {
height: 100%;
border-radius: 4px;
transition:
width 0.4s ease,
background-color 0.4s ease;
}
.stat-bar-value {
color: var(--text);
font-weight: 600;
font-size: 13px;
min-width: 20px;
text-align: right;
}
.stat-debt .stat-bar-fill {
background: var(--red);
}
.stat-quality .stat-bar-fill {
background: var(--green);
}
.stat-morale .stat-bar-fill {
background: var(--yellow);
}
.stat-migration .stat-bar-fill {
background: var(--purple);
}
#challenge-progress {
font-size: 12px;
color: var(--muted);
white-space: nowrap;
}
#challenge-progress .progress-value {
color: var(--text);
font-weight: 600;
}
#restart-btn {
background: none;
border: 1px solid var(--border);
color: var(--muted);
padding: 4px 10px;
border-radius: 4px;
font-family: inherit;
font-size: 11px;
cursor: pointer;
}
#restart-btn:hover {
border-color: var(--red);
color: var(--red);
}
#toggle-map {
background: none;
border: 1px solid var(--border);
color: var(--muted);
padding: 4px 10px;
border-radius: 4px;
font-family: inherit;
font-size: 11px;
cursor: pointer;
}
#toggle-map:hover {
border-color: var(--accent);
color: var(--accent);
}
@media (max-width: 900px) {
#stat-bars {
flex-wrap: wrap;
gap: 8px;
}
.stat-bar {
width: 40px;
}
}
@media (max-width: 768px) {
#hud {
padding: 6px 12px;
gap: 6px;
}
#hud h1 {
font-size: 11px;
}
#hud-right {
width: 100%;
flex-wrap: wrap;
gap: 6px;
}
#stat-bars {
width: 100%;
justify-content: space-between;
gap: 4px;
}
.stat-bar {
width: 32px;
}
.stat-bar-label {
display: none;
}
.stat-bar-group::before {
font-size: 10px;
color: var(--muted);
}
.stat-debt::before {
content: 'D';
}
.stat-quality::before {
content: 'Q';
}
.stat-morale::before {
content: 'M';
}
.stat-migration::before {
content: 'E';
}
#challenge-progress {
font-size: 9px;
}
#restart-btn,
#toggle-map {
padding: 3px 8px;
font-size: 10px;
}
}

View File

@@ -0,0 +1,45 @@
#main {
display: flex;
max-width: 1600px;
width: 100%;
margin: 0 auto;
padding: 32px;
gap: 32px;
}
#narrative {
flex: 2;
display: flex;
flex-direction: column;
gap: 16px;
}
#sidebar {
flex: 1;
min-width: 240px;
display: flex;
flex-direction: column;
gap: 16px;
position: sticky;
top: 60px;
align-self: flex-start;
max-height: calc(100vh - 80px);
overflow-y: auto;
}
@media (max-width: 768px) {
#main {
flex-direction: column;
padding: 16px;
gap: 16px;
max-width: 100%;
}
#sidebar {
min-width: unset;
width: 100%;
align-self: stretch;
position: static;
max-height: none;
}
}

View File

@@ -0,0 +1,168 @@
#map-dialog {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px 24px;
max-width: 700px;
width: 90%;
color: var(--text);
box-shadow: 0 20px 60px rgb(0 0 0 / 0.6);
opacity: 0;
transform: scale(0.95);
transition:
opacity 0.2s ease,
transform 0.2s ease,
overlay 0.2s ease allow-discrete,
display 0.2s ease allow-discrete;
}
#map-dialog[open] {
opacity: 1;
transform: scale(1);
}
@starting-style {
#map-dialog[open] {
opacity: 0;
transform: scale(0.95);
}
}
#map-dialog::backdrop {
background: rgb(0 0 0 / 0.5);
opacity: 0;
transition:
opacity 0.2s ease,
overlay 0.2s ease allow-discrete,
display 0.2s ease allow-discrete;
}
#map-dialog[open]::backdrop {
opacity: 1;
}
@starting-style {
#map-dialog[open]::backdrop {
opacity: 0;
}
}
#map-dialog h3 {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--muted);
margin-bottom: 12px;
}
#map {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 8px;
}
.map-room {
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 11px;
cursor: pointer;
transition: all 0.15s;
display: flex;
flex-direction: column;
}
.map-room:hover {
border-color: var(--accent);
background: var(--accent-dim);
}
.map-room.visited {
border-color: var(--green);
opacity: 0.7;
}
.map-room.current {
border-color: var(--accent);
background: var(--accent-dim);
}
.map-room .room-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 4px;
}
.map-room .room-layer {
color: var(--muted);
font-size: 9px;
text-transform: uppercase;
}
.map-room .challenge-badge {
display: inline-block;
font-size: 8px;
padding: 1px 4px;
border-radius: 3px;
flex-shrink: 0;
}
.map-room .challenge-badge.pending {
background: var(--yellow);
color: var(--bg);
}
.map-room .challenge-badge.done {
background: var(--green);
color: var(--bg);
}
.map-node circle {
transition: fill 0.3s ease, stroke 0.3s ease;
}
.map-node.locked circle {
fill: var(--bg);
stroke: var(--border);
}
.map-node.visited circle {
fill: var(--surface);
stroke: var(--green);
}
.map-node.current circle {
fill: var(--accent-dim);
stroke: var(--accent);
}
.map-edge {
stroke: var(--border);
stroke-width: 1.5;
}
.map-label {
fill: var(--text);
font-size: 11px;
font-family: var(--font-mono);
}
.map-title {
fill: var(--muted);
font-size: 9px;
text-transform: uppercase;
font-family: var(--font-mono);
}
.map-badge {
font-size: 10px;
}
.map-lock {
font-size: 12px;
}
.hidden {
display: none !important;
}

View File

@@ -0,0 +1,156 @@
#room-header {
border-bottom: 1px solid var(--border);
padding-bottom: 12px;
}
#room-header h2 {
font-size: 26px;
color: var(--text);
margin-bottom: 4px;
}
#room-layer {
font-size: 13px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 1px;
}
.room-image {
aspect-ratio: 21 / 9;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border);
}
.room-image img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.room-image.placeholder {
background: linear-gradient(135deg, #1a1e2e 0%, #0d1117 50%, #161b22 100%);
border-style: dashed;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
text-align: center;
color: var(--muted);
font-size: 12px;
line-height: 1.6;
font-style: italic;
}
#room-description {
font-size: 16px;
line-height: 1.8;
color: var(--muted);
}
#room-description code {
background: var(--surface);
border: 1px solid var(--border);
padding: 1px 5px;
border-radius: 3px;
color: var(--accent);
font-size: 15px;
}
#room-description a {
color: var(--accent);
text-decoration: none;
border-bottom: 1px dotted var(--accent);
}
#room-description a:hover {
border-bottom-style: solid;
}
#choices {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 8px;
}
.choice-btn {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
color: var(--text);
font-family: inherit;
font-size: 15px;
cursor: pointer;
text-align: left;
transition: all 0.15s;
display: flex;
align-items: center;
gap: 10px;
}
.choice-btn:hover {
border-color: var(--accent);
background: var(--accent-dim);
}
.choice-btn .choice-key {
background: var(--border);
color: var(--text);
padding: 2px 8px;
border-radius: 4px;
font-size: 13px;
font-weight: 700;
min-width: 24px;
text-align: center;
}
.choice-btn .choice-label {
flex: 1;
}
.choice-btn .choice-hint {
font-size: 13px;
color: var(--muted);
}
#welcome-panel {
border: 1px solid var(--accent);
border-radius: 10px;
padding: 16px 20px;
background: rgb(88 166 255 / 0.04);
display: none;
}
#welcome-panel.active {
display: block;
}
#welcome-panel h3 {
font-size: 16px;
color: var(--accent);
margin-bottom: 10px;
}
#welcome-panel p {
font-size: 15px;
line-height: 1.7;
color: var(--muted);
margin-bottom: 8px;
}
#welcome-panel p:last-child {
margin-bottom: 0;
}
#welcome-panel kbd {
background: var(--surface);
border: 1px solid var(--border);
padding: 1px 6px;
border-radius: 4px;
font-size: 13px;
color: var(--text);
}

View File

@@ -0,0 +1,345 @@
.sidebar-section {
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
.sidebar-header {
background: var(--surface);
padding: 8px 12px;
font-size: 13px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 1px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
}
details.sidebar-section {
interpolate-size: allow-keywords;
}
details.sidebar-section > summary {
cursor: pointer;
list-style: none;
}
details.sidebar-section > summary::-webkit-details-marker {
display: none;
}
details.sidebar-section > summary::after {
content: '▸';
font-size: 10px;
transition: transform 0.2s ease;
flex-shrink: 0;
margin-left: 6px;
}
details[open].sidebar-section > summary::after {
transform: rotate(90deg);
}
details.sidebar-section > .sidebar-body {
overflow: clip;
height: 0;
transition:
height 0.25s ease,
padding 0.25s ease;
padding: 0 8px;
}
details[open].sidebar-section > .sidebar-body {
height: auto;
padding: 8px;
}
.sidebar-body {
padding: 8px;
font-size: 14px;
}
.log-entry {
padding: 4px 6px;
border-radius: 4px;
color: var(--muted);
line-height: 1.5;
}
.log-entry.discovery {
color: var(--green);
}
.log-entry.warning {
color: var(--yellow);
}
.log-entry.error {
color: var(--red);
}
.log-entry.ending {
color: var(--purple);
}
.empty-hint {
padding: 8px;
font-size: 12px;
color: var(--muted);
font-style: italic;
}
#inv-label {
padding: 4px 12px;
font-size: 11px;
color: var(--yellow);
min-height: 22px;
border-bottom: 1px solid var(--border);
user-select: none;
transition: color 0.15s;
}
#inv-label .inv-label-type {
color: var(--muted);
font-size: 9px;
text-transform: uppercase;
margin-left: 6px;
}
.inv-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
gap: 6px;
padding: 8px;
}
.inv-slot {
aspect-ratio: 1;
border-radius: 6px;
background: var(--bg);
border: 1px solid var(--border);
overflow: hidden;
cursor: default;
position: relative;
transition: border-color 0.15s;
}
.inv-slot:hover {
border-color: var(--yellow);
}
.inv-slot img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.inv-slot.placeholder {
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: var(--yellow);
}
#artifacts {
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
display: none;
}
#artifacts.has-items {
display: block;
}
#artifacts-header {
background: var(--surface);
padding: 8px 12px;
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 1px;
border-bottom: 1px solid var(--border);
}
.artifact {
padding: 10px 12px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
font-size: 15px;
}
.artifact:last-child {
border-bottom: none;
}
.artifact-icon {
width: 36px;
height: 36px;
border-radius: 6px;
background: var(--bg);
border: 1px solid var(--border);
overflow: hidden;
flex-shrink: 0;
}
.artifact-icon img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.artifact-icon.placeholder {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: var(--yellow);
}
.artifact-info {
flex: 1;
}
.artifact-name {
color: var(--yellow);
display: block;
}
.artifact-type {
font-size: 10px;
color: var(--muted);
text-transform: uppercase;
display: block;
}
#dec-label {
padding: 4px 12px;
font-size: 11px;
color: var(--accent);
min-height: 22px;
border-bottom: 1px solid var(--border);
user-select: none;
}
#dec-label .dec-label-type {
color: var(--muted);
font-size: 9px;
text-transform: uppercase;
margin-left: 6px;
}
.dec-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
gap: 6px;
padding: 8px;
}
.dec-slot {
aspect-ratio: 1;
border-radius: 6px;
background: var(--bg);
border: 2px solid var(--border);
overflow: hidden;
cursor: default;
position: relative;
transition: border-color 0.15s;
}
.dec-slot:hover {
border-color: var(--accent);
}
.dec-slot.good {
border-color: var(--green);
}
.dec-slot.ok {
border-color: var(--yellow);
}
.dec-slot.bad {
border-color: var(--red);
}
.dec-slot img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.dec-slot.empty {
border-style: dashed;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: var(--muted);
}
#ach-label {
padding: 4px 12px;
font-size: 11px;
color: var(--purple);
min-height: 22px;
border-bottom: 1px solid var(--border);
user-select: none;
}
.ach-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
gap: 6px;
padding: 8px;
}
.ach-slot {
aspect-ratio: 1;
border-radius: 6px;
background: var(--bg);
border: 2px solid var(--border);
overflow: hidden;
cursor: default;
position: relative;
transition: border-color 0.15s;
}
.ach-slot:hover {
border-color: var(--accent);
}
.ach-slot img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.ach-slot.locked {
border-style: dashed;
display: flex;
align-items: center;
justify-content: center;
}
.ach-slot.locked img {
filter: brightness(0) saturate(0);
opacity: 0.2;
}
.ach-slot.unlocked {
border-color: var(--purple);
cursor: pointer;
}
.ach-slot.unlocked:hover {
border-color: var(--accent);
}

View File

@@ -0,0 +1,77 @@
:root {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--text: #e6edf3;
--muted: #9ea7b0;
--accent: #58a6ff;
--accent-dim: #1f6feb33;
--green: #3fb950;
--yellow: #d29922;
--red: #f85149;
--purple: #bc8cff;
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
body {
font-family: var(--font-mono);
background: var(--bg);
color: var(--text);
line-height: 1.5;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
img,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
code {
background: var(--surface);
border: 1px solid var(--border);
padding: 1px 5px;
border-radius: 4px;
font-size: 0.9em;
}
dialog {
margin: auto;
}