fuzzyworld

tower / files / src / style.css

style.css

Blame: the commit that last changed each line · file · history

09a64747 2026-09-07 Pavel Safronov1:root {
2 --bg: #0b0d12; --panel: #131722; --line: #232a3a;
9d5a061c 2026-09-27 Pavel Safronov3 --fg: #dde3ee; --dim: #8892a6; --accent: #7fd4ff;
09a64747 2026-09-07 Pavel Safronov4 color-scheme: dark;
5}
6* { box-sizing: border-box; }
7html, body { height: 100%; margin: 0; }
8body {
9 background: var(--bg); color: var(--fg);
10 font: 13px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
11}
12#app { display: grid; grid-template-columns: 1fr 340px; height: 100%; }
13main { position: relative; overflow: hidden; }
5f38efd0 2026-09-07 Pavel Safronov14canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block;
15 cursor: crosshair; touch-action: none; }
7cc177f6 2026-09-07 Pavel Safronov16#exterior, #interior { cursor: grab; }
17#exterior:active, #interior:active { cursor: grabbing; }
5f38efd0 2026-09-07 Pavel Safronov18.views {
19 position: absolute; top: 12px; left: 12px; display: flex; gap: 0;
20 border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
21 background: rgba(10,13,20,0.86);
22}
23.views button {
24 width: auto; margin: 0; border: none; border-radius: 0; padding: 6px 14px;
25 background: transparent; font-size: 12px;
26}
27.views button.active { background: #1d2942; color: var(--accent); }
28#spin { left: auto; right: 12px; bottom: auto; top: 12px; }
8b6287ba 2026-09-07 Pavel Safronov29.labels3d { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
1c0284d0 2026-09-27 Pavel Safronov30/* Show labels, off: the 3D views' captions go; the map stops drawing its names (map.ts). */
31#app.no-labels .labels3d { display: none; }
8b6287ba 2026-09-07 Pavel Safronov32.labels3d span {
33 position: absolute; transform: translate(-50%, -50%); white-space: nowrap;
34 font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
35 color: rgba(226,236,250,0.9); text-shadow: 0 0 4px #05070d, 0 0 8px #05070d;
36 letter-spacing: 0.04em;
37}
38.labels3d span small { display: block; color: var(--dim); letter-spacing: 0; }
7cc177f6 2026-09-07 Pavel Safronov39#int-labels span:hover { color: var(--accent); }
09a64747 2026-09-07 Pavel Safronov40
41.overlay {
42 position: absolute; bottom: 12px; left: 12px;
43 background: rgba(10,13,20,0.86); border: 1px solid var(--line); border-radius: 6px;
44 padding: 8px 10px; font: 11px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
45 pointer-events: none; white-space: pre;
46}
47#sightline { left: auto; right: 12px; }
3e898b51 2026-09-28 Pavel Safronov48
49/* A readout that can be folded down to its title (D-082). The box itself stays
50 click-through, as every overlay is; only the title takes the click. */
51.overlay.collapsible { padding: 0; white-space: normal; }
52.overlay.collapsible > div { white-space: pre; }
53.overlay-head {
54 display: flex; align-items: center; gap: 8px; width: 100%; margin: 0;
55 padding: 6px 10px; border: none; border-radius: 6px; background: none;
56 font: inherit; color: var(--dim); text-align: left; pointer-events: auto;
57 text-transform: uppercase; letter-spacing: 0.08em; font-size: 10px;
58}
59.overlay-head:hover { background: none; color: var(--fg); }
60.overlay-head::after {
61 content: ''; margin-left: auto; width: 5px; height: 5px;
62 border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
63 transform: rotate(-135deg) translate(-1px, -1px);
64}
65.overlay-head[aria-expanded=false]::after { transform: rotate(45deg) translate(-2px, -2px); }
66.overlay.collapsible > div { padding: 0 10px 8px; }
67.overlay.collapsed > div { display: none; }
68
69/* The eye-height gauge (D-082): an altimeter up the right of the interior view, log
70 scaled, with things of known height marked on it. */
71.gauge {
72 position: absolute; right: 18px; top: 64px; bottom: 40px; width: 10px;
73 font: 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
74}
75.gauge-track {
76 position: absolute; inset: 0; border-radius: 5px; cursor: ns-resize; touch-action: none;
77 background: rgba(10,13,20,0.7); border: 1px solid var(--line); outline: none;
78}
79.gauge-track:focus-visible { border-color: var(--accent); }
80.gauge-mark {
81 position: absolute; right: 12px; transform: translateY(50%);
82 display: flex; align-items: center; gap: 6px; white-space: nowrap; pointer-events: none;
83 color: rgba(190,200,216,0.8);
84 text-shadow: 0 0 2px #05070d, 0 0 4px #05070d, 0 0 8px #05070d;
85}
86.gauge-mark i { font-style: normal; opacity: 0.75; }
87/* Too close to the mark below it at this height of screen: tick only. */
88.gauge-mark.crowded span, .gauge-mark.crowded i { display: none; }
89.gauge-mark::after { content: ''; width: 8px; border-top: 1px solid currentColor; }
90.gauge-mark.passed { color: #eef2f8; }
91.gauge-mark.level { color: var(--accent); }
92.gauge-thumb {
93 position: absolute; left: 50%; width: 16px; height: 16px; margin-left: -8px;
94 transform: translateY(50%); border-radius: 50%; pointer-events: none;
95 background: var(--accent); box-shadow: 0 0 0 3px rgba(10,13,20,0.9);
96}
97/* The height itself, beside the thumb and clear of the marks, on the view's side. */
98.gauge-readout {
99 position: absolute; right: 22px; top: 50%; transform: translateY(-50%);
100 white-space: nowrap; text-align: right; padding: 4px 8px; border-radius: 5px;
101 background: rgba(10,13,20,0.92); border: 1px solid var(--accent); color: var(--fg);
102 font-size: 11px;
103}
104.gauge-readout span { color: var(--accent); }
09a64747 2026-09-07 Pavel Safronov105.hidden { display: none !important; }
106
376f2ce6 2026-09-16 Pavel Safronov107/* The status dialog (world-generation.md §10.1). Over the world, never over the panel:
108 the author can keep reading notes while the world is made. */
109.status[hidden] { display: none; }
110.status {
111 position: absolute; left: 50%; top: 42%; transform: translate(-50%, -50%);
112 width: min(420px, calc(100% - 32px)); padding: 18px 20px 20px; box-sizing: border-box;
113 background: rgba(11, 13, 18, 0.92); border: 1px solid var(--line); border-radius: 8px;
114 text-align: center; z-index: 5; pointer-events: none;
115}
116.status p { margin: 0; }
117.status-title { font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--dim); }
118.status-line { font-size: 15px; margin: 10px 0 14px !important; min-height: 1.3em; }
119.status-bar { height: 3px; background: #1b2130; border-radius: 2px; overflow: hidden; }
120#status-fill { height: 100%; width: 0; background: var(--accent); transition: width 0.25s ease-out; }
121
09a64747 2026-09-07 Pavel Safronov122aside {
123 background: var(--panel); border-left: 1px solid var(--line);
124 overflow-y: auto; padding: 0 14px 24px;
125}
0e0c2dba 2026-09-07 Pavel Safronov126header { display: flex; align-items: center; gap: 8px; padding: 14px 0 10px; }
09a64747 2026-09-07 Pavel Safronov127h1 { font-size: 15px; margin: 0; letter-spacing: 0.08em; text-transform: uppercase; }
b4b8e46c 2026-09-28 Pavel Safronov128/* The branch picker: only there when the site was built with more than one branch. */
129#branch { width: auto; margin: 0 0 0 auto; padding: 3px 6px; font-size: 11px; }
0e0c2dba 2026-09-07 Pavel Safronov130
131/* One section open at a time; the summary is the whole affordance. */
132details.sec { border-top: 1px solid var(--line); }
133details.sec[hidden] { display: none; }
134details.sec > summary {
135 display: flex; align-items: center; gap: 8px; cursor: pointer;
136 padding: 12px 0; list-style: none;
137 font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--dim);
138}
139details.sec > summary::-webkit-details-marker { display: none; }
140details.sec > summary::after {
141 content: ''; margin-left: auto; width: 6px; height: 6px;
142 border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
143 transform: rotate(45deg) translate(-2px, -2px); transition: transform 0.12s ease;
144}
145details.sec[open] > summary { color: var(--fg); }
146details.sec[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
147details.sec > summary:hover { color: var(--fg); }
148details.sec > :not(summary):last-child { margin-bottom: 14px; }
09a64747 2026-09-07 Pavel Safronov149.dim { color: var(--dim); font-weight: 400; }
150.hint { color: var(--dim); font-size: 11px; margin: 8px 0 14px; }
151code { background: #0b0d12; padding: 1px 4px; border-radius: 3px; }
152
153button {
154 width: 100%; margin-bottom: 6px; padding: 7px 10px; border-radius: 6px;
155 border: 1px solid var(--line); background: #1b2130; color: var(--fg);
156 font: inherit; font-size: 12px; cursor: pointer;
157}
158button:hover { background: #222a3d; }
7cc177f6 2026-09-07 Pavel Safronov159button:disabled { opacity: 0.45; cursor: default; }
2bb3e0de 2026-09-08 Pavel Safronov160/* The Light menu is disabled while the clock is driving the light; say so. */
161select:disabled, input:disabled { opacity: 0.45; cursor: default; }
7cc177f6 2026-09-07 Pavel Safronov162button:disabled:hover { background: #1b2130; }
09a64747 2026-09-07 Pavel Safronov163button.active { border-color: var(--accent); color: var(--accent); background: #16233a; }
164
165.list { list-style: none; margin: 0; padding: 0; max-height: 260px; overflow-y: auto; }
166.list li { display: flex; align-items: center; gap: 8px; padding: 5px 6px;
167 border-radius: 5px; cursor: pointer; }
168.list li:hover { background: #1b2130; }
169.list li.sel { background: #1d2942; }
170.list .swatch { width: 9px; height: 9px; border-radius: 50%; flex: none; }
171.list .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
172.list .kd { color: var(--dim); font-size: 10px; }
173
174#detail p { margin: 0 0 8px; }
175#d-name { font-size: 14px; font-weight: 600; }
176label { display: block; margin-bottom: 9px; color: var(--dim); font-size: 11px; }
177label.row { display: flex; align-items: center; gap: 7px; color: var(--fg); font-size: 12px; }
9d5a061c 2026-09-27 Pavel Safronov178input[type=number], select {
09a64747 2026-09-07 Pavel Safronov179 width: 100%; margin-top: 3px; padding: 6px 8px; border-radius: 5px;
180 border: 1px solid var(--line); background: #0e1219; color: var(--fg);
9d5a061c 2026-09-27 Pavel Safronov181 font: inherit; font-size: 12px;
09a64747 2026-09-07 Pavel Safronov182}
183input[type=range] { width: 100%; margin-top: 6px; accent-color: var(--accent); }
184.warn { color: #e8c46a; }
185.links { display: flex; flex-wrap: wrap; gap: 4px 12px; margin: 0 0 14px; font-size: 11px; }
186.links a { color: var(--accent); text-decoration: none; }
187.links a:hover { text-decoration: underline; }
a088e5d5 2026-09-17 Pavel Safronov188.board { list-style: none; margin: 0 0 10px; padding: 0; }
189.board li { padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12px; }
190.board li.now { color: var(--accent); }
191.board .rev { color: var(--dim); font-size: 11px; margin-top: 2px; }
192.board s { color: var(--dim); }
fc11b064 2026-09-08 Pavel Safronov193
194/**
195 * Narrow viewports: the sidebar becomes a drawer.
196 *
197 * The desktop layout is a fixed 340 px panel beside the world. On a 390 px phone
198 * that leaves the world about fifty pixels — the 3D views rendered into a black
199 * slot and there was no way to get the panel out of the way. Below 720 px the
200 * panel comes out of the grid entirely and slides in over the world instead, so
201 * the world always has the whole screen and the panel is one tap away.
202 */
203#panel-toggle, #panel-close, .scrim { display: none; }
204
205@media (max-width: 720px) {
206 #app { grid-template-columns: 1fr; }
207
208 aside {
209 position: fixed; top: 0; right: 0; bottom: 0; z-index: 20;
210 /* Never the full width: the strip of world left showing is what tells you
211 the panel is a drawer over the map and not a page you navigated to. */
212 width: min(340px, 86vw);
213 transform: translateX(100%);
214 transition: transform 0.18s ease;
215 box-shadow: -12px 0 28px rgba(0, 0, 0, 0.5);
216 }
217 #app.panel-open aside { transform: translateX(0); }
218
219 /* Opens the drawer. Sits opposite the view switcher, and goes away once the
220 drawer is open — the Close button in the panel header takes over there, in
221 normal flow, so it cannot end up floating over scrolled content. */
222 #panel-toggle {
223 display: block; position: fixed; top: 12px; right: 12px; z-index: 21;
224 width: auto; margin: 0; padding: 6px 14px; font-size: 12px;
225 background: rgba(10, 13, 20, 0.86);
226 }
227 #app.panel-open #panel-toggle { display: none; }
228 #panel-close { display: block; width: auto; margin: 0 0 0 auto; padding: 4px 12px; }
b4b8e46c 2026-09-28 Pavel Safronov229 /* With the picker there, it takes the push to the right and Close sits beside it. */
230 #branch:not([hidden]) + #panel-close { margin-left: 0; }
fc11b064 2026-09-08 Pavel Safronov231
232 /* That strip of world beside the drawer is still live canvas, and in writer
233 mode a stray tap on it drops a marker. The scrim eats those and closes. */
234 #app.panel-open .scrim {
235 display: block; position: fixed; inset: 0; z-index: 19;
236 background: rgba(5, 7, 13, 0.55);
237 }
238
239 /* The spin readout shares the top-right corner with the toggle. */
240 #spin { top: 54px; }
241
242 /* The standing readout is ~65 monospace characters wide, which overruns a
243 390 px screen at 11 px. Shrink the type rather than wrap it: these are
244 aligned columns, and wrapping them destroys the thing they are for. */
245 .overlay { font-size: 9px; padding: 6px 8px; }
3e898b51 2026-09-28 Pavel Safronov246 /* Clear of the gauge; its longest lines were already past the screen's edge. */
247 .overlay.collapsible { padding: 0; max-width: calc(100% - 60px); overflow: hidden; }
248
249 /* No room for the landmark names beside the gauge on a phone: ticks only, and the
250 readout by the thumb still says which one you are near. */
251 .gauge { top: 96px; right: 14px; }
252 .gauge-mark span, .gauge-mark i { display: none; }
7a5edd5f 2026-10-02 Pavel Safronov253 /* The readout box is a big share of a phone's view, so it fades once the height has been
254 still for two seconds, and comes back the moment it changes or the gauge is touched
255 (eye-gauge.ts wake). The thumb stays where it is: the height is still on the scale. */
256 .gauge-readout { opacity: 0; transition: opacity 0.6s ease; }
257 .gauge-readout.awake { opacity: 1; transition: opacity 0.15s ease; }
fc11b064 2026-09-08 Pavel Safronov258}
a088e5d5 2026-09-17 Pavel Safronov259
260/* The scenic tour (interior.ts): a black fade between scenes, and the scene's title. */
261.tour-fade { position: absolute; inset: 0; background: #000; pointer-events: none; opacity: 0; }
262.tour-caption {
263 position: absolute; top: 12px; left: 50%; transform: translateX(-50%);
264 max-width: min(640px, calc(100% - 32px)); text-align: center; white-space: pre-line;
265 background: rgba(10,13,20,0.72); border: 1px solid var(--line); border-radius: 6px;
266 padding: 8px 14px; font-size: 13px; line-height: 1.5; pointer-events: none;
267}
ec1a6075 2026-09-30 Pavel Safronov268/* The leg's progress: the caption's border filling in clockwise until the next leg (D-094). */
269.tour-ring {
270 position: absolute; inset: -1px; width: calc(100% + 2px); height: calc(100% + 2px);
271 overflow: visible; pointer-events: none;
272}
273.tour-ring rect {
274 x: 1px; y: 1px; width: calc(100% - 2px); height: calc(100% - 2px);
275 fill: none; stroke: var(--accent); stroke-width: 2px; stroke-opacity: 0.85;
276 stroke-dasharray: 0 10000; stroke-linecap: round;
277}
a088e5d5 2026-09-17 Pavel Safronov278@media (max-width: 720px) { .tour-caption { top: 56px; font-size: 11px; } }