Compare commits
10
Commits
5eda03ea33
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bd9aed4ef | ||
|
|
0ec748f67f | ||
|
|
7071858390 | ||
|
|
ca5b34db96 | ||
|
|
94672dadf5 | ||
|
|
90707cdead | ||
|
|
7a17711f60 | ||
|
|
9402456020 | ||
|
|
dc7fa003f0 | ||
|
|
9a2c502a19 |
@@ -0,0 +1,17 @@
|
||||
## Design Context
|
||||
|
||||
### Users
|
||||
TTRPG players (typically 2-6 people) using this collaboratively around a table or over video call. Mix of casual and enthusiast gamers. Used during active play sessions and between sessions to review the history they've built. The person who set up the site has more admin access than other players.
|
||||
|
||||
### Brand Personality
|
||||
Archival. Serious. Atmospheric. Like a field record from a frontier colony that actually existed — worn documents, brass fittings, classified reports, the weight of real history. The game is called Space Station 33. NOT: space-age glossy sci-fi. YES: grimy industrial, lived-in, 19th-century expedition logs meets deep-space infrastructure.
|
||||
|
||||
### Aesthetic Direction
|
||||
Dark mode — players use this in dimly-lit rooms during evening sessions. The existing palette is strong: near-black warm void, brass gold accents, rust red for dark mood, moss green for ongoing/active, teal for cast/people. Typography uses Georgia for headers (good serif anchor), system sans for body. The aesthetic should feel like a real archival document system — authoritative, dense, trustworthy. Anti-references: anything that looks like a SaaS dashboard, anything glowing/neon, anything that feels like it was made in Figma in 2023.
|
||||
|
||||
### Design Principles
|
||||
1. **Weight = history** — things that matter should feel heavy and real, not lightweight and clickable
|
||||
2. **Typography does the work** — hierarchy through size and weight, not color or decoration
|
||||
3. **Silence is deliberate** — whitespace should feel considered, not just "airy"
|
||||
4. **No chrome without purpose** — UI elements earn their existence by doing something specific
|
||||
5. **The tool disappears into the game** — players should feel like they're looking at a real archive, not a web app
|
||||
@@ -19,9 +19,12 @@
|
||||
--moss: #70794e;
|
||||
--oxygen: #86a5a4;
|
||||
--line: rgba(232, 220, 196, .18);
|
||||
--text-secondary: #d0c6b3;
|
||||
--text-body: #c2b7a4;
|
||||
--text-dim: #b8ae9e;
|
||||
--shadow: 0 22px 70px rgba(0,0,0,.42);
|
||||
--serif: Georgia, "Times New Roman", serif;
|
||||
--sans: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
--serif: Spectral, Georgia, "Times New Roman", serif;
|
||||
--sans: Jost, Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
--mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
@@ -65,7 +68,7 @@
|
||||
padding: 1rem 0;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
font: 650 .72rem/1 var(--mono);
|
||||
font: 700 .72rem/1 var(--mono);
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
@@ -82,7 +85,7 @@
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
transition: background 0.18s cubic-bezier(0.22,1,.36,1), border-color 0.18s cubic-bezier(0.22,1,.36,1), color 0.18s cubic-bezier(0.22,1,.36,1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.edit-toggle.locked { color: var(--muted); }
|
||||
@@ -129,7 +132,7 @@
|
||||
.mood-badge.light { color: var(--brass); border-color: rgba(184,138,67,.4); }
|
||||
.mood-badge.dark { color: var(--rust); border-color: rgba(148,69,47,.4); }
|
||||
.game-dates { font: .72rem/1 var(--mono); color: var(--muted); letter-spacing: .06em; }
|
||||
.game-desc { color: #c2b7a4; max-width: 72ch; margin-top: 1rem; font-size: 1rem; }
|
||||
.game-desc { color: var(--text-body); max-width: 72ch; margin-top: 1rem; font-size: 1rem; }
|
||||
.game-header-actions { display: flex; gap: .7rem; flex-wrap: wrap; }
|
||||
|
||||
.btn {
|
||||
@@ -145,7 +148,7 @@
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background .15s, border-color .15s;
|
||||
transition: background 0.18s cubic-bezier(0.22,1,.36,1), border-color 0.18s cubic-bezier(0.22,1,.36,1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn:hover { background: rgba(184,138,67,.1); border-color: var(--brass); }
|
||||
@@ -217,7 +220,7 @@
|
||||
text-align: center;
|
||||
}
|
||||
.tl-lbl-name {
|
||||
font: 600 .64rem/1.25 var(--mono);
|
||||
font: 600 .72rem/1.25 var(--mono);
|
||||
letter-spacing: .04em;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
@@ -227,7 +230,7 @@
|
||||
}
|
||||
.tl-lbl-name.is-anchor { color: var(--paper); font-weight: 700; }
|
||||
.tl-lbl-date {
|
||||
font: .56rem/1.1 var(--mono);
|
||||
font: .63rem/1.1 var(--mono);
|
||||
color: var(--muted);
|
||||
margin-top: .12rem;
|
||||
white-space: nowrap;
|
||||
@@ -345,7 +348,7 @@
|
||||
margin: 0 0 .75rem;
|
||||
}
|
||||
.entry-desc {
|
||||
color: #c2b7a4;
|
||||
color: var(--text-body);
|
||||
font-size: 1rem;
|
||||
line-height: 1.7;
|
||||
max-width: 72ch;
|
||||
@@ -476,7 +479,7 @@
|
||||
}
|
||||
select option { background: var(--panel); }
|
||||
textarea { resize: vertical; min-height: 80px; }
|
||||
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--brass); }
|
||||
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--brass); box-shadow: 0 0 0 3px rgba(184,138,67,.12); }
|
||||
.date-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||
.mood-row { display: flex; gap: 1rem; }
|
||||
.mood-radio { display: flex; align-items: center; gap: .5rem; cursor: pointer; }
|
||||
@@ -595,7 +598,7 @@
|
||||
text-transform: uppercase;
|
||||
padding: .5rem;
|
||||
cursor: pointer;
|
||||
transition: background .15s, border-color .15s;
|
||||
transition: background 0.18s cubic-bezier(0.22,1,.36,1), border-color 0.18s cubic-bezier(0.22,1,.36,1);
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.card-dismiss:hover { background: rgba(90,50,20,.18); border-color: rgba(90,50,20,.8); }
|
||||
@@ -637,26 +640,45 @@
|
||||
color:var(--brass); flex-shrink:0; opacity:.7;
|
||||
}
|
||||
.truth-item-text { flex:1; }
|
||||
/* Project bar event dots + click zone */
|
||||
/* Project sub-timeline row: dashed rail above, bar below */
|
||||
.tl-row.project-tl {
|
||||
height: 36px;
|
||||
overflow: visible;
|
||||
}
|
||||
/* Dashed sub-rail at pin level */
|
||||
.tl-row.project-tl::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
top: 7px;
|
||||
border-top: 1px dashed rgba(232,220,196,.2);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Project bar sits in lower half */
|
||||
.tl-row.project-tl .tl-bar {
|
||||
top: 16px;
|
||||
height: 18px;
|
||||
}
|
||||
/* Pins sit on the dashed rail */
|
||||
.tl-project-evt-dot {
|
||||
position: absolute;
|
||||
width: 11px; height: 11px;
|
||||
width: 10px; height: 10px;
|
||||
border-radius: 50%;
|
||||
top: -6px; /* sticks above the bar */
|
||||
transform: translateX(-50%);
|
||||
top: 7px;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 3; cursor: pointer;
|
||||
border: 2px solid var(--void);
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,.6);
|
||||
transition: transform .1s;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.5);
|
||||
}
|
||||
.tl-project-evt-dot.light { background: var(--paper); border-color: var(--brass); }
|
||||
.tl-project-evt-dot.dark { background: var(--rust); border-color: var(--void); }
|
||||
.tl-project-evt-dot:hover { transform: translateX(-50%) scale(1.4); }
|
||||
.tl-bar-add-overlay {
|
||||
position: absolute; top: 0; bottom: 0;
|
||||
position: absolute; top: 0; height: 16px; /* rail area only */
|
||||
z-index: 2; cursor: crosshair; background: transparent;
|
||||
}
|
||||
|
||||
|
||||
.project-events {
|
||||
margin-top:.8rem; padding-top:.8rem; border-top:1px solid var(--line);
|
||||
}
|
||||
@@ -716,8 +738,147 @@
|
||||
.entry { grid-template-columns: 1fr; }
|
||||
.entry-actions { justify-content: flex-start; }
|
||||
}
|
||||
|
||||
/* ── Mobile ─────────────────────────────────────────────────── */
|
||||
@media (max-width: 640px) {
|
||||
|
||||
/* Nav: keep two groups, wrap if needed, larger tap areas */
|
||||
nav .shell { flex-wrap: wrap; gap: 0; }
|
||||
nav .nav-left { gap: .9rem; flex: 1 1 auto; min-width: 0; overflow-x: auto; }
|
||||
nav .nav-left a { padding: .8rem 0; font-size: .65rem; }
|
||||
.nav-right { gap: .4rem; flex-shrink: 0; }
|
||||
.edit-toggle { padding: .5rem .8rem; font-size: .6rem; }
|
||||
|
||||
/* Timeline: horizontal scroll instead of hidden overflow */
|
||||
.timeline-section { overflow: hidden; }
|
||||
.tl-wrap {
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-x: contain;
|
||||
}
|
||||
|
||||
/* Chronicle: tighter vertical rhythm */
|
||||
.chronicle-section { margin-bottom: 3rem; }
|
||||
.chronicle-header-row { padding-bottom: 1.5rem; }
|
||||
.chronicle-title { font-size: clamp(1.8rem, 8vw, 3.2rem); }
|
||||
.entry { padding: 1.4rem 0; gap: 0 1rem; }
|
||||
.entry-title { font-size: clamp(1.2rem, 5vw, 1.9rem); }
|
||||
|
||||
/* Entry side images: break earlier (was 700px, now caught here too) */
|
||||
.entry-body.env-left,
|
||||
.entry-body.env-right { display: block; }
|
||||
.entry-body.env-left .entry-env,
|
||||
.entry-body.env-right .entry-env {
|
||||
position: relative; top: auto; margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Modal: less padding, date fields stack */
|
||||
.modal { padding: 1.2rem 1rem 1.6rem; }
|
||||
.date-row { grid-template-columns: 1fr; }
|
||||
|
||||
/* Minimum touch target height on all buttons */
|
||||
.btn { min-height: 42px; }
|
||||
.btn.sm { min-height: 36px; padding: .45rem .8rem; }
|
||||
|
||||
/* Game header actions stack naturally */
|
||||
.game-header-top { align-items: flex-start; }
|
||||
|
||||
/* Section label + button row */
|
||||
.section-header { flex-wrap: wrap; }
|
||||
|
||||
/* Truth items full width */
|
||||
.truth-list { gap: .35rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
/* Modal rises from bottom, full width */
|
||||
.modal-overlay { padding: 0; align-items: flex-end; }
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-height: 88vh;
|
||||
padding: 1rem 1rem 2rem;
|
||||
}
|
||||
|
||||
/* Smaller eyebrow text */
|
||||
.chronicle-eyebrow { font-size: .6rem; }
|
||||
.entry-eyebrow { font-size: .62rem; }
|
||||
|
||||
/* Game header meta tighter */
|
||||
.game-meta-row { gap: .5rem; flex-wrap: wrap; }
|
||||
}
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
/* ── Animation ──────────────────────────────────────────────────── */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
|
||||
/* Alpine x-transition utility classes (used by toast + modals) */
|
||||
.transition { transition-property: opacity, transform; }
|
||||
.ease-out { transition-timing-function: cubic-bezier(0.22,1,.36,1); }
|
||||
.ease-in { transition-timing-function: cubic-bezier(0.4,0,1,1); }
|
||||
.duration-150 { transition-duration: 150ms; }
|
||||
.duration-200 { transition-duration: 200ms; }
|
||||
.duration-250 { transition-duration: 250ms; }
|
||||
.duration-300 { transition-duration: 300ms; }
|
||||
.opacity-0 { opacity: 0; }
|
||||
.opacity-100 { opacity: 1; }
|
||||
.translate-y-2 { transform: translateY(8px); }
|
||||
.translate-y-0 { transform: translateY(0); }
|
||||
|
||||
/* Card draw — comes up from below with a slight tilt */
|
||||
.card-frame {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1) rotate(0deg);
|
||||
transition: opacity 0.45s cubic-bezier(0.22,1,.36,1),
|
||||
transform 0.45s cubic-bezier(0.22,1,.36,1);
|
||||
}
|
||||
@starting-style {
|
||||
.card-frame {
|
||||
opacity: 0;
|
||||
transform: translateY(42px) scale(0.92) rotate(-1.5deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal backdrop fade */
|
||||
.modal-overlay {
|
||||
transition: opacity 0.22s cubic-bezier(0.22,1,.36,1);
|
||||
opacity: 1;
|
||||
}
|
||||
@starting-style { .modal-overlay { opacity: 0; } }
|
||||
|
||||
/* Modal inner panel slides up */
|
||||
.modal {
|
||||
transition: opacity 0.3s cubic-bezier(0.22,1,.36,1),
|
||||
transform 0.3s cubic-bezier(0.22,1,.36,1);
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
@starting-style {
|
||||
.modal {
|
||||
opacity: 0;
|
||||
transform: translateY(18px) scale(0.97);
|
||||
}
|
||||
}
|
||||
|
||||
/* Chronicle entry scroll reveals */
|
||||
.entry[data-reveal] {
|
||||
opacity: 0;
|
||||
transform: translateY(18px);
|
||||
transition: opacity 0.5s cubic-bezier(0.22,1,.36,1),
|
||||
transform 0.5s cubic-bezier(0.22,1,.36,1);
|
||||
}
|
||||
.entry[data-reveal].revealed { opacity: 1; transform: translateY(0); }
|
||||
|
||||
/* Button press */
|
||||
.btn:active {
|
||||
transform: translateY(1px) scale(0.98);
|
||||
transition-duration: 0.06s !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600&family=Jost:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body x-data="gameApp()" x-cloak>
|
||||
|
||||
@@ -728,7 +889,7 @@
|
||||
<a href="games.html">Sessions</a>
|
||||
<a x-show="game" x-text="game && game.title" style="color:var(--paper);pointer-events:none;"></a>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.6rem">
|
||||
<div class="nav-right" style="display:flex;align-items:center;gap:.6rem">
|
||||
<select x-show="game && (cards.length || customDecks.length)"
|
||||
x-model="activeDeckId"
|
||||
style="background:transparent;border:1px solid var(--line);color:var(--muted);
|
||||
@@ -853,7 +1014,7 @@
|
||||
<div class="tl-sublabel">Projects</div>
|
||||
<div class="tl-rows">
|
||||
<template x-for="item in projects()" :key="item.id">
|
||||
<div class="tl-row" style="height:26px;overflow:visible">
|
||||
<div class="tl-row project-tl">
|
||||
<a
|
||||
class="tl-bar project-bar"
|
||||
:class="[item.mood || (game && game.mood), !item.end_date ? 'open-right' : '']"
|
||||
@@ -904,6 +1065,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div><!-- /.tl-wrap -->
|
||||
</div>
|
||||
|
||||
@@ -1121,9 +1283,11 @@
|
||||
|
||||
<!-- Live update toast -->
|
||||
<div x-show="liveUpdate"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 transform translate-y-2"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:enter="transition ease-out duration-250"
|
||||
x-transition:enter-start="opacity-0 translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
style="position:fixed;bottom:1.5rem;right:1.5rem;z-index:400;background:var(--panel);border:1px solid rgba(112,121,78,.5);color:var(--moss);font:700 .62rem/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;padding:.55rem 1rem;pointer-events:none">
|
||||
Chronicle updated
|
||||
@@ -1150,7 +1314,13 @@
|
||||
</footer>
|
||||
|
||||
<!-- Modal — all sections use x-show (not x-if) so Alpine bindings are always live -->
|
||||
<div class="modal-overlay" x-show="showModal" @click="closeModal()" @keydown.escape.window="closeModal()">
|
||||
<div class="modal-overlay" x-show="showModal" @click="closeModal()" @keydown.escape.window="closeModal()"
|
||||
x-transition:enter="transition ease-out duration-250"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0">
|
||||
<div class="modal" @click.stop>
|
||||
<div class="modal-title" x-text="modalTitle()"></div>
|
||||
|
||||
@@ -2400,5 +2570,37 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
// Scroll reveals for chronicle entries.
|
||||
// Uses MutationObserver so it works with Alpine's dynamic rendering.
|
||||
const revealObs = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => {
|
||||
if (e.isIntersecting) {
|
||||
const delay = +(e.target.dataset.revealDelay || 0)
|
||||
setTimeout(() => e.target.classList.add('revealed'), delay)
|
||||
revealObs.unobserve(e.target)
|
||||
}
|
||||
})
|
||||
}, { threshold: 0.04, rootMargin: '0px 0px -24px 0px' })
|
||||
|
||||
const observe = (el) => {
|
||||
if (!el.classList?.contains('entry') || el.hasAttribute('data-reveal')) return
|
||||
const siblings = Array.from(el.parentElement?.querySelectorAll('.entry') || [])
|
||||
el.dataset.revealDelay = Math.min(siblings.indexOf(el) * 45, 200)
|
||||
el.setAttribute('data-reveal', '')
|
||||
revealObs.observe(el)
|
||||
}
|
||||
|
||||
// Watch for entries Alpine adds dynamically
|
||||
new MutationObserver((muts) => {
|
||||
muts.forEach(m => m.addedNodes.forEach(node => {
|
||||
if (node.nodeType !== 1) return
|
||||
if (node.classList?.contains('entry')) observe(node)
|
||||
node.querySelectorAll?.('.entry').forEach(observe)
|
||||
}))
|
||||
}).observe(document.body, { childList: true, subtree: true })
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+14
-11
@@ -19,9 +19,12 @@
|
||||
--moss: #70794e;
|
||||
--oxygen: #86a5a4;
|
||||
--line: rgba(232, 220, 196, .18);
|
||||
--text-secondary: #d0c6b3;
|
||||
--text-body: #c2b7a4;
|
||||
--text-dim: #b8ae9e;
|
||||
--shadow: 0 22px 70px rgba(0,0,0,.42);
|
||||
--serif: Georgia, "Times New Roman", serif;
|
||||
--sans: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
--serif: Spectral, Georgia, "Times New Roman", serif;
|
||||
--sans: Jost, Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
--mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
@@ -63,7 +66,7 @@
|
||||
padding: 1rem 0;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
font: 650 .72rem/1 var(--mono);
|
||||
font: 700 .72rem/1 var(--mono);
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
@@ -100,7 +103,7 @@
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background .15s, border-color .15s, color .15s;
|
||||
transition: background 0.18s cubic-bezier(0.22,1,.36,1), border-color 0.18s cubic-bezier(0.22,1,.36,1), color 0.18s cubic-bezier(0.22,1,.36,1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn:hover { background: rgba(184,138,67,.12); border-color: var(--brass); }
|
||||
@@ -116,7 +119,7 @@
|
||||
border-bottom: 1px solid var(--line);
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
transition: color .15s;
|
||||
transition: color 0.18s cubic-bezier(0.22,1,.36,1);
|
||||
}
|
||||
.game-row:first-child { border-top: 1px solid var(--line); }
|
||||
.game-row-eyebrow {
|
||||
@@ -136,10 +139,10 @@
|
||||
letter-spacing: -.02em;
|
||||
line-height: 1.05;
|
||||
margin: 0 0 .6rem;
|
||||
transition: color .15s;
|
||||
transition: color 0.18s cubic-bezier(0.22,1,.36,1);
|
||||
}
|
||||
.game-row:hover .game-row-title { color: var(--brass); }
|
||||
.game-row-desc { color: #c2b7a4; font-size: 1rem; line-height: 1.6; max-width: 72ch; }
|
||||
.game-row-desc { color: var(--text-body); font-size: 1rem; line-height: 1.6; max-width: 72ch; }
|
||||
.empty-state {
|
||||
padding: 3rem 0;
|
||||
color: var(--muted);
|
||||
@@ -197,10 +200,7 @@
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
textarea { resize: vertical; min-height: 80px; }
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--brass);
|
||||
}
|
||||
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--brass); box-shadow: 0 0 0 3px rgba(184,138,67,.12); }
|
||||
.mood-row { display: flex; gap: 1rem; }
|
||||
.mood-radio { display: flex; align-items: center; gap: .5rem; cursor: pointer; }
|
||||
.mood-radio input { width: auto; }
|
||||
@@ -216,6 +216,9 @@
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600&family=Jost:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body x-data="gamesApp()" x-cloak>
|
||||
|
||||
|
||||
+157
-12
@@ -20,9 +20,12 @@
|
||||
--moss: #70794e;
|
||||
--oxygen: #86a5a4;
|
||||
--line: rgba(232, 220, 196, .18);
|
||||
--text-secondary: #d0c6b3;
|
||||
--text-body: #c2b7a4;
|
||||
--text-dim: #b8ae9e;
|
||||
--shadow: 0 22px 70px rgba(0,0,0,.42);
|
||||
--serif: Georgia, "Times New Roman", serif;
|
||||
--sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--serif: Spectral, Georgia, "Times New Roman", serif;
|
||||
--sans: Jost, Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
--mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
|
||||
@@ -99,7 +102,7 @@
|
||||
h2 { font-size: clamp(2.2rem, 5vw, 4.2rem); line-height: 1.05; color: var(--paper); }
|
||||
h3 { font-size: clamp(1.35rem, 2.4vw, 2rem); line-height: 1.25; color: var(--paper); }
|
||||
|
||||
.hero-copy p { max-width: 62ch; color: #d0c6b3; font-size: 1.08rem; }
|
||||
.hero-copy p { max-width: 62ch; color: var(--text-secondary); font-size: 1.08rem; }
|
||||
|
||||
nav {
|
||||
position: sticky;
|
||||
@@ -115,7 +118,7 @@
|
||||
padding: 1rem 0;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
font: 650 .72rem/1 var(--mono);
|
||||
font: 700 .72rem/1 var(--mono);
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
@@ -134,12 +137,16 @@
|
||||
|
||||
.project {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.25fr) minmax(280px, .75fr);
|
||||
grid-template-columns: minmax(200px, 0.68fr) minmax(0, 1fr);
|
||||
gap: clamp(2rem, 5vw, 5rem);
|
||||
align-items: start;
|
||||
margin: 0 0 7rem;
|
||||
}
|
||||
.project:nth-of-type(even) .project-image { order: 2; }
|
||||
/* Explicit column placement so text always gets the wider side */
|
||||
.project .project-image { grid-column: 1; grid-row: 1; }
|
||||
.project .project-copy { grid-column: 2; grid-row: 1; }
|
||||
.project:nth-of-type(even) .project-image { grid-column: 2; order: initial; }
|
||||
.project:nth-of-type(even) .project-copy { grid-column: 1; }
|
||||
.project-image {
|
||||
position: sticky;
|
||||
top: 5.3rem;
|
||||
@@ -150,7 +157,7 @@
|
||||
}
|
||||
.project-image img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
|
||||
.project-copy { padding-top: 1rem; }
|
||||
.project-copy p { color: #c2b7a4; }
|
||||
.project-copy p { color: var(--text-body); }
|
||||
|
||||
.meta {
|
||||
display: grid;
|
||||
@@ -200,12 +207,14 @@
|
||||
|
||||
blockquote {
|
||||
margin: 2rem 0 0;
|
||||
padding: 1.3rem 1.5rem;
|
||||
border-left: 4px solid var(--brass);
|
||||
background: rgba(184,138,67,.08);
|
||||
color: var(--paper);
|
||||
padding: 1.2rem 0;
|
||||
border-top: 1px solid var(--line);
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--text-dim);
|
||||
font-family: var(--serif);
|
||||
font-size: 1.25rem;
|
||||
font-size: .95rem;
|
||||
font-style: italic;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
footer { border-top: 1px solid var(--line); padding: 2.5rem 0 4rem; color: var(--muted); }
|
||||
@@ -219,7 +228,107 @@
|
||||
.project:nth-of-type(even) .project-image { order: initial; }
|
||||
.project-image { position: relative; top: auto; }
|
||||
}
|
||||
|
||||
/* ── Delight ─────────────────────────────────────────────────────── */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
|
||||
/* Hero: eyebrow, title, and paragraph stagger in on load */
|
||||
@keyframes archiveIn {
|
||||
from { opacity: 0; transform: translateY(22px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.hero-copy .eyebrow { animation: archiveIn 0.7s cubic-bezier(0.22,1,.36,1) 0.08s both; }
|
||||
.hero-copy h1 { animation: archiveIn 1.0s cubic-bezier(0.22,1,.36,1) 0.22s both; }
|
||||
.hero-copy p { animation: archiveIn 0.7s cubic-bezier(0.22,1,.36,1) 0.48s both; }
|
||||
|
||||
/* Masthead badge: letter-spacing collapses as it materialises */
|
||||
@keyframes badgeIn {
|
||||
from { opacity: 0; letter-spacing: .32em; }
|
||||
to { opacity: .72; letter-spacing: .18em; }
|
||||
}
|
||||
.masthead::after { animation: badgeIn 1.5s cubic-bezier(0.22,1,.36,1) 0.65s both; }
|
||||
|
||||
/* Scroll-reveal: sections fade and slide up as they enter the viewport */
|
||||
[data-reveal] {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.7s cubic-bezier(0.22,1,.36,1),
|
||||
transform 0.7s cubic-bezier(0.22,1,.36,1);
|
||||
}
|
||||
[data-reveal].visible { opacity: 1; transform: translateY(0); }
|
||||
|
||||
/* Subtle offset for even-numbered sections to break rhythm */
|
||||
.project:nth-of-type(even)[data-reveal] {
|
||||
transition-delay: 0.06s;
|
||||
}
|
||||
|
||||
/* Project images: whole frame lifts (no clipping of the inner border) */
|
||||
.project-image {
|
||||
transition: transform 0.6s cubic-bezier(0.22,1,.36,1),
|
||||
box-shadow 0.6s cubic-bezier(0.22,1,.36,1);
|
||||
}
|
||||
.project-image:hover {
|
||||
transform: scale(1.015) translateY(-4px);
|
||||
box-shadow: 0 40px 80px rgba(0,0,0,.62);
|
||||
}
|
||||
|
||||
/* Divider: fade in with slight width expansion */
|
||||
img.divider[data-reveal] {
|
||||
transform: none;
|
||||
filter: opacity(0) blur(2px);
|
||||
transition: filter 0.6s cubic-bezier(0.22,1,.36,1),
|
||||
transform 0.6s cubic-bezier(0.22,1,.36,1);
|
||||
}
|
||||
img.divider[data-reveal].visible {
|
||||
filter: opacity(0.9) blur(0px);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mobile ─────────────────────────────────────────────────── */
|
||||
@media (max-width: 640px) {
|
||||
|
||||
/* Hero: less top padding, tighter leading */
|
||||
.hero-copy { padding: 4rem 0 2.5rem; }
|
||||
h1 { line-height: 1; }
|
||||
|
||||
/* Nav links: slightly smaller gap, still scrollable */
|
||||
nav .shell { gap: 1rem; }
|
||||
|
||||
/* Reduce section spacing */
|
||||
main { padding: 3rem 0 5rem; }
|
||||
.project { margin: 0 0 4rem; }
|
||||
|
||||
/* Intro lead text: smaller on very narrow */
|
||||
.intro .lead { font-size: 1.15rem; }
|
||||
|
||||
/* Meta grid: single column below 480px */
|
||||
.meta { grid-template-columns: 1fr; }
|
||||
|
||||
/* Document: less padding */
|
||||
.document { padding: 1.4rem; }
|
||||
.document::before { display: none; }
|
||||
.document::after { display: none; }
|
||||
|
||||
/* Blockquote: tighter */
|
||||
blockquote { margin: 1.2rem 0 0; font-size: .88rem; }
|
||||
|
||||
/* Touch targets for nav */
|
||||
nav a { padding: .9rem 0; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
/* Masthead: less height on very small screens */
|
||||
.masthead { min-height: 55vh; }
|
||||
.masthead::after { display: none; }
|
||||
.hero-copy h1 { font-size: clamp(2.6rem, 12vw, 4rem); }
|
||||
|
||||
/* Hide extra nav items, show only essentials */
|
||||
nav .shell { gap: .8rem; }
|
||||
}
|
||||
</style>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600&family=Jost:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header class="masthead">
|
||||
@@ -332,5 +441,41 @@
|
||||
<p>You are not completing the colony's story.<br />You are creating the place where its next story can begin.</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
|
||||
if (!reducedMotion) {
|
||||
// Scroll reveal ─ sections and dividers materialise as they enter view
|
||||
const reveal = new IntersectionObserver((entries) => {
|
||||
entries.forEach(e => {
|
||||
if (e.isIntersecting) {
|
||||
e.target.classList.add('visible')
|
||||
reveal.unobserve(e.target)
|
||||
}
|
||||
})
|
||||
}, { threshold: 0.06, rootMargin: '0px 0px -40px 0px' })
|
||||
|
||||
document.querySelectorAll('.intro, .project, .document, img.divider').forEach(el => {
|
||||
el.setAttribute('data-reveal', '')
|
||||
reveal.observe(el)
|
||||
})
|
||||
|
||||
// Masthead parallax ─ background drifts very slowly upward on scroll
|
||||
const masthead = document.querySelector('.masthead')
|
||||
if (masthead) {
|
||||
let ticking = false
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(() => {
|
||||
const y = Math.min(window.scrollY * 0.12, 80)
|
||||
masthead.style.backgroundPositionY = `calc(30% + ${y}px)`
|
||||
ticking = false
|
||||
})
|
||||
ticking = true
|
||||
}
|
||||
}, { passive: true })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user