blockyard 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +929 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +191 -4
- package/SECURITY.md +38 -0
- package/bin/blockyard.js +41 -0
- package/config/pool-map.json +2620 -0
- package/docs/API.md +1577 -0
- package/docs/ARCHITECTURE.md +1394 -0
- package/docs/AUTO-UPDATE.md +269 -0
- package/docs/CONFIGURATION.md +847 -0
- package/docs/DEFECTS.md +813 -0
- package/docs/EFFECTS-AGENTS.md +448 -0
- package/docs/GETTING-STARTED.md +205 -0
- package/docs/INSTALL.md +547 -0
- package/docs/MEASUREMENTS.md +1401 -0
- package/docs/RULES.md +681 -0
- package/docs/SECURITY-AUDIT-2026-09-14.md +177 -0
- package/docs/SECURITY-AUDIT.md +258 -0
- package/docs/SECURITY.md +212 -0
- package/docs/TROUBLESHOOTING.md +332 -0
- package/docs/USER-GUIDE.md +1262 -0
- package/package.json +53 -5
- package/public/404.html +9 -0
- package/public/css/app.css +2009 -0
- package/public/donate-qr.png +0 -0
- package/public/index.html +1085 -0
- package/public/js/about.js +112 -0
- package/public/js/agents.js +1141 -0
- package/public/js/app.js +1386 -0
- package/public/js/arkanoid.js +806 -0
- package/public/js/blockanoid.js +347 -0
- package/public/js/blockout.js +347 -0
- package/public/js/blockpack.js +428 -0
- package/public/js/blockscene3d.js +2830 -0
- package/public/js/breakout.js +224 -0
- package/public/js/charts.js +635 -0
- package/public/js/depthchart.js +315 -0
- package/public/js/details3d.js +4342 -0
- package/public/js/doom.js +31 -0
- package/public/js/dosaudio.js +48 -0
- package/public/js/dosgame.js +389 -0
- package/public/js/dosio.js +186 -0
- package/public/js/dospc.js +1353 -0
- package/public/js/dosworker.js +196 -0
- package/public/js/explorer.js +405 -0
- package/public/js/feepalette.js +149 -0
- package/public/js/fmt.js +162 -0
- package/public/js/goggles.js +886 -0
- package/public/js/kiosk.js +41 -0
- package/public/js/login.js +88 -0
- package/public/js/markets.js +395 -0
- package/public/js/mining.js +1416 -0
- package/public/js/panels.js +970 -0
- package/public/js/pricechart.js +189 -0
- package/public/js/quake.js +20 -0
- package/public/js/settings.js +1096 -0
- package/public/js/soundcard.js +459 -0
- package/public/js/tetris.js +226 -0
- package/public/js/tetrust.js +356 -0
- package/public/js/tetsound.js +175 -0
- package/public/js/theme.js +235 -0
- package/public/js/wolf3d.js +22 -0
- package/public/js/x86.js +1978 -0
- package/public/login.html +33 -0
- package/scripts/blockfile-measure.js +156 -0
- package/scripts/browser-check.mjs +286 -0
- package/scripts/check.js +173 -0
- package/scripts/decode-check.js +81 -0
- package/scripts/doc-counts.js +109 -0
- package/scripts/donate-qr.py +23 -0
- package/scripts/dos-bench.js +56 -0
- package/scripts/fake-node.js +534 -0
- package/scripts/index-bench.js +216 -0
- package/scripts/index-benchmark.js +117 -0
- package/scripts/index-build.js +40 -0
- package/scripts/live-render-check.mjs +89 -0
- package/scripts/manage-users.js +132 -0
- package/scripts/motion-check.mjs +138 -0
- package/scripts/pool-map.js +157 -0
- package/scripts/setup.js +432 -0
- package/scripts/shots.mjs +278 -0
- package/scripts/smoke.sh +327 -0
- package/scripts/tls.js +31 -0
- package/scripts/ui.js +174 -0
- package/server/auth/sessions.js +221 -0
- package/server/auth/users.js +243 -0
- package/server/chain/blockfile.js +234 -0
- package/server/chain/index/build.js +210 -0
- package/server/chain/index/heights.js +36 -0
- package/server/chain/index/live.js +276 -0
- package/server/chain/index/rows.js +145 -0
- package/server/chain/index/store.js +154 -0
- package/server/chain/index/worker.js +109 -0
- package/server/chain/tx.js +310 -0
- package/server/collect/gbt.js +229 -0
- package/server/collect/logparse.js +765 -0
- package/server/collect/logtail.js +189 -0
- package/server/collect/markets.js +333 -0
- package/server/collect/mining.js +333 -0
- package/server/collect/monitor.js +2545 -0
- package/server/collect/network.js +295 -0
- package/server/collect/nextblock.js +275 -0
- package/server/collect/sync.js +386 -0
- package/server/config.js +644 -0
- package/server/http/api.js +1319 -0
- package/server/http/explorer.js +418 -0
- package/server/http/games.js +77 -0
- package/server/http/server.js +420 -0
- package/server/http/sse.js +176 -0
- package/server/http/static.js +212 -0
- package/server/main.js +673 -0
- package/server/netinfo.js +253 -0
- package/server/rpc/allowlist.js +130 -0
- package/server/rpc/client.js +414 -0
- package/server/store/audit.js +148 -0
- package/server/store/history.js +220 -0
- package/server/store/ledger.js +290 -0
- package/server/store/ring.js +173 -0
- package/server/tls/selfsigned.js +160 -0
- package/server/util/fmt.js +29 -0
- package/systemd/blockyard.service +102 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// APPEARANCE (operator, 2026-09-16: "We need to add an appearance section in preferences to change
|
|
2
|
+
// the colors of our layout. Our current scheme should be the default appearance, but add these
|
|
3
|
+
// options in Preferences, as well as options for configure custom schemes by allowing users to
|
|
4
|
+
// pick colors and save to settings so it sticks").
|
|
5
|
+
//
|
|
6
|
+
// The page is drawn from the CSS custom properties in app.css's :root -- background, panels, text,
|
|
7
|
+
// lines, the accent and the three semantic colours -- so a theme is a set of values for those, and
|
|
8
|
+
// applying one is writing them onto <html> through the CSSOM (element.style.setProperty). That is
|
|
9
|
+
// the one way to colour the page that the CSP allows: style-src 'self' refuses a style attribute
|
|
10
|
+
// in markup and a <style> element, and refuses neither a stylesheet nor a script setting a
|
|
11
|
+
// property (test/csp.test.js).
|
|
12
|
+
//
|
|
13
|
+
// The canvases do not read CSS. Every chart draws its axis text, grid lines and tooltips from
|
|
14
|
+
// literals of the shipped dark look, so a light theme would have put grey-on-grey text on a white
|
|
15
|
+
// card. They now read INK, the live object below, which applyTheme() refills: one object, mutated
|
|
16
|
+
// in place, so a chart mid-paint sees one theme and no chart reads getComputedStyle per frame.
|
|
17
|
+
//
|
|
18
|
+
// A theme has a dark and a light face (GitHub Dark Default and Light Default, Catppuccin Mocha and
|
|
19
|
+
// Latte, and so on) and the mode picks which: Dark, Light, or System (the OS's prefers-color-scheme,
|
|
20
|
+
// followed live). BlockYard's own dark face is the shipped look, value for value, so a monitor that
|
|
21
|
+
// never opens this tab draws exactly what it drew before. Custom takes nine colours from the
|
|
22
|
+
// pickers and derives the rest (a panel a shade lighter, a soft line, a faint text) from them.
|
|
23
|
+
import { loadSettings, onSettingsChange } from './settings.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The charts' series and chrome colours (charts.js re-exports it): the shipped dark values until
|
|
27
|
+
* applyTheme() refills them. One live object, so every module that imported it sees the theme.
|
|
28
|
+
*/
|
|
29
|
+
export const COL = {
|
|
30
|
+
grid: '#20252d', axis: '#2c323c', text: '#6a7484', textDim: '#4d5665',
|
|
31
|
+
accent: '#f7931a', ok: '#2ecc8f', warn: '#f0b429', bad: '#ef5a5a',
|
|
32
|
+
info: '#58a6ff', purple: '#a78bfa', cyan: '#4dd0e1', pink: '#f06ba7',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ------------------------------------------------------------------- colour arithmetic, tiny
|
|
36
|
+
const rgbOf = (hex) => {
|
|
37
|
+
const m = /^#([0-9a-f]{6})$/i.exec(String(hex ?? '').trim());
|
|
38
|
+
if (!m) return null;
|
|
39
|
+
const n = parseInt(m[1], 16);
|
|
40
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
41
|
+
};
|
|
42
|
+
const hexOf = ([r, g, b]) => `#${[r, g, b].map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('')}`;
|
|
43
|
+
/** `t` of the way from `a` to `b`, as a hex. */
|
|
44
|
+
export const mix = (a, b, t) => {
|
|
45
|
+
const x = rgbOf(a), y = rgbOf(b);
|
|
46
|
+
if (!x || !y) return a;
|
|
47
|
+
return hexOf(x.map((v, i) => v + (y[i] - v) * t));
|
|
48
|
+
};
|
|
49
|
+
/** Relative luminance, 0 (black) to 1 (white). */
|
|
50
|
+
export const luminance = (hex) => {
|
|
51
|
+
const c = rgbOf(hex);
|
|
52
|
+
if (!c) return 0;
|
|
53
|
+
const lin = (v) => { const s = v / 255; return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; };
|
|
54
|
+
return 0.2126 * lin(c[0]) + 0.7152 * lin(c[1]) + 0.0722 * lin(c[2]);
|
|
55
|
+
};
|
|
56
|
+
/** A colour with an alpha, as rgba(): the canvases want translucent tooltips of the theme's panel. */
|
|
57
|
+
export const alpha = (hex, a) => {
|
|
58
|
+
const c = rgbOf(hex);
|
|
59
|
+
return c ? `rgba(${c[0]},${c[1]},${c[2]},${a})` : hex;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ------------------------------------------------------------------- the faces
|
|
63
|
+
// The nine a custom theme is built from, in the order the pickers show them. Everything else in a
|
|
64
|
+
// face derives from these unless the preset says otherwise.
|
|
65
|
+
export const BASE_KEYS = Object.freeze(['bg', 'panel', 'text', 'muted', 'accent', 'line', 'ok', 'warn', 'bad']);
|
|
66
|
+
|
|
67
|
+
// The fixed colours of the two schemes: chart series, badges, a few labels. Not worth nine more
|
|
68
|
+
// pickers; they follow the scheme, not the theme.
|
|
69
|
+
const SCHEME = Object.freeze({
|
|
70
|
+
dark: Object.freeze({ info: '#58a6ff', purple: '#a78bfa', cyan: '#4dd0e1', pink: '#f06ba7' }),
|
|
71
|
+
light: Object.freeze({ info: '#2f6fd6', purple: '#7c5cd6', cyan: '#1a8a99', pink: '#c9407f' }),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A whole face from nine (or more) colours. Every derived value is a mix toward a colour already
|
|
76
|
+
* in the face, so a custom theme hangs together whatever the nine are: the raised panel is the
|
|
77
|
+
* panel nudged toward the text, the soft line is the line nudged toward the background, the faint
|
|
78
|
+
* text sits between the muted text and the background.
|
|
79
|
+
*/
|
|
80
|
+
export function derive(base) {
|
|
81
|
+
const f = { ...base };
|
|
82
|
+
const dark = luminance(f.bg) < 0.4;
|
|
83
|
+
f.scheme = dark ? 'dark' : 'light';
|
|
84
|
+
f.bg0 ??= mix(f.bg, dark ? '#000000' : '#ffffff', 0.25); // the well: inputs, bars, code
|
|
85
|
+
f.bg2 ??= mix(f.panel, f.text, dark ? 0.04 : 0.03); // a raised panel
|
|
86
|
+
f.bg3 ??= mix(f.panel, f.text, dark ? 0.09 : 0.07); // a hover, a selected tab
|
|
87
|
+
f.lineSoft ??= mix(f.line, f.bg, 0.4);
|
|
88
|
+
f.faint ??= mix(f.muted, f.bg, 0.3);
|
|
89
|
+
f.accentDim ??= mix(f.accent, f.bg, 0.35);
|
|
90
|
+
f.accentInk ??= luminance(f.accent) > 0.45 ? '#17110a' : '#ffffff'; // text on an accent button
|
|
91
|
+
f.hover ??= dark ? 'rgba(255,255,255,0.025)' : 'rgba(0,0,0,0.035)';
|
|
92
|
+
f.scrim ??= dark ? 'rgba(2,5,9,0.62)' : 'rgba(20,24,30,0.42)';
|
|
93
|
+
f.shadow ??= dark ? '0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.28)' : '0 1px 2px rgba(20,24,30,.08), 0 8px 24px rgba(20,24,30,.10)';
|
|
94
|
+
for (const [k, v] of Object.entries(SCHEME[f.scheme])) f[k] ??= v;
|
|
95
|
+
return f;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Each preset: two faces of nine base colours, with the values the palette itself names given
|
|
99
|
+
// outright where it has them (GitHub and Catppuccin publish their scales; a derived shade would be
|
|
100
|
+
// near but not theirs).
|
|
101
|
+
export const PRESETS = Object.freeze({
|
|
102
|
+
blockyard: {
|
|
103
|
+
label: 'BlockYard', blurb: 'The shipped look — charcoal and bitcoin orange',
|
|
104
|
+
// THE SHIPPED LOOK, value for value from app.css (2026-09-16): the default must draw what the
|
|
105
|
+
// monitor drew before this tab existed, so nothing here is derived
|
|
106
|
+
dark: { bg: '#0b0d10', panel: '#12151a', bg2: '#171b21', bg3: '#1e232b', bg0: '#0c0e11', line: '#262c36', lineSoft: '#1d222a', text: '#dfe5ee', muted: '#96a0b0', faint: '#6a7484', accent: '#f7931a', accentDim: '#b06c10', accentInk: '#17110a', ok: '#2ecc8f', warn: '#f0b429', bad: '#ef5a5a' },
|
|
107
|
+
light: { bg: '#f3f1ec', panel: '#ffffff', bg2: '#f8f6f2', bg3: '#eeeae3', bg0: '#ebe7e0', line: '#d8d2c8', lineSoft: '#e6e1d8', text: '#1c1f26', muted: '#5b6470', faint: '#8a929e', accent: '#e8850f', accentDim: '#b8690a', accentInk: '#1a1206', ok: '#1f9d63', warn: '#c48a08', bad: '#d63f3f' },
|
|
108
|
+
},
|
|
109
|
+
mono: {
|
|
110
|
+
label: 'Mono', blurb: 'Clean grayscale — minimal and focused',
|
|
111
|
+
dark: { bg: '#0f0f0f', panel: '#161616', bg2: '#1b1b1b', bg3: '#232323', line: '#2c2c2c', text: '#e8e8e8', muted: '#9a9a9a', faint: '#6b6b6b', accent: '#f2f2f2', accentDim: '#9a9a9a', ok: '#9ccfae', warn: '#d9c48a', bad: '#d99090', info: '#a9b9c9', purple: '#b8aed6', cyan: '#9fcad0', pink: '#d3a4bc' },
|
|
112
|
+
light: { bg: '#f4f4f4', panel: '#ffffff', bg2: '#f8f8f8', bg3: '#ececec', line: '#d5d5d5', text: '#151515', muted: '#5a5a5a', faint: '#8c8c8c', accent: '#111111', accentDim: '#555555', ok: '#2f8f5b', warn: '#a37a12', bad: '#c04747', info: '#3a5f8a', purple: '#5d4f8a', cyan: '#2a6f78', pink: '#8a4468' },
|
|
113
|
+
},
|
|
114
|
+
nous: {
|
|
115
|
+
label: 'Nous', blurb: 'GitHub chrome, Nous blue accent',
|
|
116
|
+
dark: { bg: '#0d1117', panel: '#161b22', bg2: '#1c2128', bg3: '#21262d', bg0: '#010409', line: '#30363d', lineSoft: '#262c34', text: '#e6edf3', muted: '#8b949e', faint: '#6e7681', accent: '#4d9fff', accentDim: '#2b6fd6', ok: '#3fb950', warn: '#d29922', bad: '#f85149', info: '#79c0ff', purple: '#a371f7', cyan: '#39c5cf', pink: '#db61a2' },
|
|
117
|
+
light: { bg: '#f6f8fa', panel: '#ffffff', bg2: '#f6f8fa', bg3: '#eaeef2', bg0: '#eff2f5', line: '#d0d7de', lineSoft: '#e1e6eb', text: '#1f2328', muted: '#656d76', faint: '#8c959f', accent: '#1f6fe0', accentDim: '#0a4fb5', ok: '#1a7f37', warn: '#9a6700', bad: '#cf222e', info: '#0969da', purple: '#8250df', cyan: '#1b7c83', pink: '#bf3989' },
|
|
118
|
+
},
|
|
119
|
+
github: {
|
|
120
|
+
label: 'GitHub', blurb: 'GitHub Light Default and Dark Default',
|
|
121
|
+
dark: { bg: '#0d1117', panel: '#161b22', bg2: '#1c2128', bg3: '#21262d', bg0: '#010409', line: '#30363d', lineSoft: '#262c34', text: '#e6edf3', muted: '#8b949e', faint: '#6e7681', accent: '#3fb950', accentDim: '#238636', accentInk: '#ffffff', ok: '#3fb950', warn: '#d29922', bad: '#f85149', info: '#58a6ff', purple: '#a371f7', cyan: '#39c5cf', pink: '#db61a2' },
|
|
122
|
+
light: { bg: '#f6f8fa', panel: '#ffffff', bg2: '#f6f8fa', bg3: '#eaeef2', bg0: '#eff2f5', line: '#d0d7de', lineSoft: '#e1e6eb', text: '#1f2328', muted: '#656d76', faint: '#8c959f', accent: '#1f883d', accentDim: '#1a7f37', accentInk: '#ffffff', ok: '#1a7f37', warn: '#9a6700', bad: '#cf222e', info: '#0969da', purple: '#8250df', cyan: '#1b7c83', pink: '#bf3989' },
|
|
123
|
+
},
|
|
124
|
+
catppuccin: {
|
|
125
|
+
label: 'Catppuccin', blurb: 'Soothing pastels — Latte and Mocha',
|
|
126
|
+
dark: { bg: '#181825', panel: '#1e1e2e', bg2: '#242438', bg3: '#313244', bg0: '#11111b', line: '#45475a', lineSoft: '#313244', text: '#cdd6f4', muted: '#a6adc8', faint: '#6c7086', accent: '#fab387', accentDim: '#c98a62', accentInk: '#1e1e2e', ok: '#a6e3a1', warn: '#f9e2af', bad: '#f38ba8', info: '#89b4fa', purple: '#cba6f7', cyan: '#94e2d5', pink: '#f5c2e7' },
|
|
127
|
+
light: { bg: '#e6e9ef', panel: '#eff1f5', bg2: '#e9ecf2', bg3: '#dce0e8', bg0: '#dce0e8', line: '#bcc0cc', lineSoft: '#ccd0da', text: '#4c4f69', muted: '#6c6f85', faint: '#9ca0b0', accent: '#fe640b', accentDim: '#d1520a', accentInk: '#ffffff', ok: '#40a02b', warn: '#df8e1d', bad: '#d20f39', info: '#1e66f5', purple: '#8839ef', cyan: '#179299', pink: '#ea76cb' },
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
export const PRESET_IDS = Object.freeze(Object.keys(PRESETS));
|
|
131
|
+
|
|
132
|
+
// ------------------------------------------------------------------- choosing
|
|
133
|
+
/** Whether the OS asks for light. Stubbed in tests; absent on a server. */
|
|
134
|
+
const systemLight = (win = globalThis.window) => {
|
|
135
|
+
try { return !!win?.matchMedia?.('(prefers-color-scheme: light)')?.matches; } catch { return false; }
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** The scheme the settings resolve to: 'dark' or 'light'. */
|
|
139
|
+
export function resolveScheme(appearance, win = globalThis.window) {
|
|
140
|
+
const mode = appearance?.mode ?? 'dark';
|
|
141
|
+
if (mode === 'system') return systemLight(win) ? 'light' : 'dark';
|
|
142
|
+
return mode === 'light' ? 'light' : 'dark';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The nine custom colours from the settings, as a base for derive(). */
|
|
146
|
+
export function customBase(a) {
|
|
147
|
+
return { bg: a.customBg, panel: a.customPanel, text: a.customText, muted: a.customMuted, accent: a.customAccent, line: a.customLine, ok: a.customOk, warn: a.customWarn, bad: a.customBad };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The face a settings object asks for, whole. A preset's face is derived from its own values (so
|
|
152
|
+
* a preset that names bg2 keeps it and one that does not gets the mix); Custom is derived from the
|
|
153
|
+
* nine pickers, and its scheme is whatever its background says it is, whatever the mode.
|
|
154
|
+
*/
|
|
155
|
+
export function resolveTheme(settings, win = globalThis.window) {
|
|
156
|
+
const a = (settings ?? loadSettings()).appearance ?? {};
|
|
157
|
+
const scheme = resolveScheme(a, win);
|
|
158
|
+
if (a.theme === 'custom') return { id: 'custom', scheme: null, ...derive(customBase(a)) };
|
|
159
|
+
const preset = PRESETS[a.theme] ?? PRESETS.blockyard;
|
|
160
|
+
return { id: PRESETS[a.theme] ? a.theme : 'blockyard', ...derive(preset[scheme]) };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The face of a preset in a scheme, for the cards' swatches. */
|
|
164
|
+
export function presetFace(id, scheme) { return derive(PRESETS[id][scheme === 'light' ? 'light' : 'dark']); }
|
|
165
|
+
|
|
166
|
+
// ------------------------------------------------------------------- applying
|
|
167
|
+
/** face key -> CSS custom property */
|
|
168
|
+
const VARS = Object.freeze({
|
|
169
|
+
bg: '--bg', panel: '--bg-1', bg2: '--bg-2', bg3: '--bg-3', bg0: '--bg-0', line: '--line', lineSoft: '--line-soft',
|
|
170
|
+
text: '--fg', muted: '--fg-dim', faint: '--fg-faint', accent: '--accent', accentDim: '--accent-dim', accentInk: '--accent-ink',
|
|
171
|
+
ok: '--ok', warn: '--warn', bad: '--bad', info: '--info', purple: '--purple', cyan: '--cyan', pink: '--pink',
|
|
172
|
+
hover: '--hover', scrim: '--scrim', shadow: '--shadow',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* What the canvases draw their chrome with. Filled by applyTheme(); the shipped dark values until
|
|
177
|
+
* then, so a chart painted before any theme is applied (there is none: app.js applies at import)
|
|
178
|
+
* still looks as it always did.
|
|
179
|
+
*/
|
|
180
|
+
export const INK = {
|
|
181
|
+
grid: '#20252d', axis: '#2c323c', text: '#6a7484', textDim: '#4d5665', axisText: '#7d8898', bright: '#e8edf5', label: '#b9c3d1',
|
|
182
|
+
panel: '#12151a', panelLine: '#3d4552', tip: 'rgba(12,14,17,0.91)', tipLine: '#2c323c', tipText: '#c7cfdb',
|
|
183
|
+
bg: '#0b0d10', faintLine: 'rgba(255,255,255,0.19)', dash: 'rgba(200,215,230,0.45)', cursorFill: '#3a4452', cursorText: '#ffffff',
|
|
184
|
+
// the order book's two sides: neon on a dark ground, the theme's own good and bad on a light one
|
|
185
|
+
bid: '#3dff7a', ask: '#ff4545', bidT: 'rgba(61,255,122,0.32)', askT: 'rgba(255,69,69,0.32)', bidThen: 'rgba(61,255,122,0.55)', askThen: 'rgba(255,69,69,0.55)', bidHalf: 'rgba(61,255,122,0.5)', askHalf: 'rgba(255,69,69,0.5)',
|
|
186
|
+
scheme: 'dark',
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const inkOf = (f) => {
|
|
190
|
+
const dark = f.scheme === 'dark';
|
|
191
|
+
return {
|
|
192
|
+
grid: f.lineSoft, axis: f.line, text: f.faint, textDim: mix(f.faint, f.bg, 0.3), axisText: mix(f.faint, f.muted, 0.5), bright: f.text, label: mix(f.text, f.muted, 0.3),
|
|
193
|
+
panel: f.panel, panelLine: mix(f.line, f.text, 0.15), tip: alpha(f.bg0, 0.92), tipLine: f.line, tipText: mix(f.text, f.muted, 0.15),
|
|
194
|
+
bg: f.bg, faintLine: dark ? 'rgba(255,255,255,0.19)' : 'rgba(0,0,0,0.16)', dash: dark ? 'rgba(200,215,230,0.45)' : 'rgba(40,50,60,0.45)',
|
|
195
|
+
cursorFill: f.bg3, cursorText: f.text,
|
|
196
|
+
bid: dark ? '#3dff7a' : f.ok, ask: dark ? '#ff4545' : f.bad,
|
|
197
|
+
bidT: alpha(dark ? '#3dff7a' : f.ok, 0.32), askT: alpha(dark ? '#ff4545' : f.bad, 0.32),
|
|
198
|
+
bidThen: alpha(dark ? '#3dff7a' : f.ok, 0.55), askThen: alpha(dark ? '#ff4545' : f.bad, 0.55),
|
|
199
|
+
bidHalf: alpha(dark ? '#3dff7a' : f.ok, 0.5), askHalf: alpha(dark ? '#ff4545' : f.bad, 0.5),
|
|
200
|
+
scheme: f.scheme,
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Put a face on the page: the custom properties on <html>, `data-theme` and `color-scheme` for the
|
|
206
|
+
* stylesheet and the browser's own controls, and INK and the charts' COL for the canvases.
|
|
207
|
+
* Returns the face. Safe without a document (tests pass a stub root).
|
|
208
|
+
*/
|
|
209
|
+
export function applyTheme(settings, { root = globalThis.document?.documentElement, win = globalThis.window } = {}) {
|
|
210
|
+
const f = resolveTheme(settings, win);
|
|
211
|
+
if (root?.style?.setProperty) {
|
|
212
|
+
for (const [k, v] of Object.entries(VARS)) if (f[k]) root.style.setProperty(v, f[k]);
|
|
213
|
+
root.style.setProperty('color-scheme', f.scheme);
|
|
214
|
+
root.setAttribute?.('data-theme', f.scheme);
|
|
215
|
+
root.setAttribute?.('data-palette', f.id);
|
|
216
|
+
}
|
|
217
|
+
Object.assign(INK, inkOf(f));
|
|
218
|
+
Object.assign(COL, { grid: f.lineSoft, axis: f.line, text: f.faint, textDim: INK.textDim, accent: f.accent, ok: f.ok, warn: f.warn, bad: f.bad, info: f.info, purple: f.purple, cyan: f.cyan, pink: f.pink });
|
|
219
|
+
try { const meta = globalThis.document?.querySelector?.('meta[name="theme-color"]'); if (meta) meta.setAttribute('content', f.bg); } catch { /* fine */ }
|
|
220
|
+
return f;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Keep the page on the settings: apply now, again on every settings change, and again when the
|
|
225
|
+
* OS switches scheme while the mode is System. Returns a stop function.
|
|
226
|
+
*/
|
|
227
|
+
export function followTheme(opts = {}) {
|
|
228
|
+
applyTheme(loadSettings(), opts);
|
|
229
|
+
const off = onSettingsChange((s) => applyTheme(s, opts));
|
|
230
|
+
const win = opts.win ?? globalThis.window;
|
|
231
|
+
let mq = null;
|
|
232
|
+
const onChange = () => { if ((loadSettings().appearance?.mode ?? 'dark') === 'system') applyTheme(loadSettings(), opts); };
|
|
233
|
+
try { mq = win?.matchMedia?.('(prefers-color-scheme: light)'); mq?.addEventListener?.('change', onChange); } catch { mq = null; }
|
|
234
|
+
return () => { off(); try { mq?.removeEventListener?.('change', onChange); } catch { /* fine */ } };
|
|
235
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// WOLFENSTEIN 3D, the tab (operator, 2026-09-15: "I added wolf3d_dos - Add that one next, but add it
|
|
2
|
+
// before DOOM and Quake in the Diversions list").
|
|
3
|
+
//
|
|
4
|
+
// The shareware WOLF3D.EXE v1.4 and its .WL1 data from games/wolf3d_dos/, unmodified, on the emulated
|
|
5
|
+
// PC (dosgame.js has the tab, dosworker.js the machine). Where DOOM and Quake were 32-bit programs
|
|
6
|
+
// under DOS extenders, Wolfenstein 3D is a 16-bit real-mode Borland program, so this is the one that
|
|
7
|
+
// runs the CPU in real mode: segment:offset addresses, the interrupt vector table at 0:0, DOS's own
|
|
8
|
+
// EXEC and memory allocator (dospc.js bootMZ). It sounds through the same Sound Blaster: AdLib music
|
|
9
|
+
// on the OPL, digitised effects on the DSP.
|
|
10
|
+
import { createDosGame } from './dosgame.js';
|
|
11
|
+
|
|
12
|
+
export const renderWolf3d = createDosGame({
|
|
13
|
+
game: 'wolf3d',
|
|
14
|
+
page: 'wolf3d',
|
|
15
|
+
prefix: 'wolf',
|
|
16
|
+
title: 'Wolfenstein 3D',
|
|
17
|
+
idleText: 'The shareware episode, Escape from Wolfenstein: the real WOLF3D.EXE v1.4 running on a PC this monitor emulates. Click the screen to use the mouse.',
|
|
18
|
+
loadingText: 'loading the shareware episode…',
|
|
19
|
+
saveName: /^SAVEGAM\d\.WL1$/i,
|
|
20
|
+
keysText: () => '↑ ↓ walk · ← → turn · Alt strafe · Ctrl or left click fire · Space open · Shift run · 1–4 weapons · Esc menu · F8 quick save · F9 quick load · rebind them under Control in the game’s own menu',
|
|
21
|
+
switches: [],
|
|
22
|
+
});
|