Skip to content

Commit 33af489

Browse files
committed
feat: palette-reactive chevron, preset notes, physical HUD plates
- chevron (and the --palette token) recolor to a slime-parallel accent of the active palette (Slime/Warm/Ocean/Mono/Heat) as a live visual response - per-preset note replaces the cycle hint after the first preset cycle, and fades out gently after a long dwell; palette-first interactions leave it empty until a preset is cycled - HUD tags float inward over the sim as solid, raised plates (drop shadow + bevel) instead of bezel-cut labels
1 parent 73af524 commit 33af489

2 files changed

Lines changed: 64 additions & 18 deletions

File tree

web/src/chrome.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ const CSS = `
3939
#app .tick.tr{top:6px;right:6px;border-left:0;border-bottom:0}
4040
#app .tick.bl{bottom:6px;left:6px;border-right:0;border-top:0}
4141
#app .tick.br{bottom:6px;right:6px;border-left:0;border-top:0}
42-
/* bordered tags cut into the bezel border */
42+
/* bordered HUD tags floating just inside the screen, over the sim */
4343
#app .tag{position:absolute;font-size:.64rem;letter-spacing:.12em;text-transform:uppercase;
44-
background:var(--bg);border:1px solid var(--line);padding:.12rem .5rem;color:var(--dim)}
45-
#app .slabel{top:-.78rem;left:1.2rem}
46-
#app .status{top:-.78rem;right:1.2rem;color:var(--acc);cursor:pointer}
44+
background:#241c14;border:1px solid rgba(150,140,120,.35);padding:.18rem .6rem;color:var(--ink);
45+
box-shadow:inset 0 1px 0 rgba(255,244,228,.08),0 3px 11px rgba(0,0,0,.75)}
46+
#app .slabel{top:1.5rem;left:1.5rem}
47+
#app .status{top:1.5rem;right:1.5rem;color:var(--acc);cursor:pointer}
4748
#app .status[data-paused]{color:var(--dim)}
48-
#app .readout{bottom:-.78rem;right:1.2rem;text-transform:none;letter-spacing:.04em}
49+
#app .readout{bottom:1.5rem;right:1.5rem;text-transform:none;letter-spacing:.04em}
4950
#app .readout .leg{color:var(--dim);opacity:.7}
5051
#app .screen[data-loading] .viewport::after{content:"warming…";position:absolute;
5152
inset:0;display:grid;place-items:center;color:var(--dim);font-size:.85rem}
@@ -64,8 +65,9 @@ const CSS = `
6465
#app .cmd .tok:hover{color:#a8e29c;border-bottom-style:solid}
6566
#app .cmd .cur{color:var(--acc);animation:blink 1.1s steps(1) infinite;margin-left:.2ch}
6667
@keyframes blink{50%{opacity:0}}
67-
#app .hint{font-size:.74rem;color:var(--dim);opacity:.7;transition:opacity .4s}
68-
#app .hint[data-done]{opacity:0}`;
68+
#app .hint{font-size:.74rem;color:var(--dim);opacity:.7;line-height:1.5;min-height:1.1em;
69+
transition:opacity 1.1s ease}
70+
#app .hint[data-faded]{opacity:0}`;
6971

7072
export interface ChromeHandles {
7173
host: HTMLElement;
@@ -76,6 +78,7 @@ export interface ChromeHandles {
7678
readout: HTMLElement;
7779
hint: HTMLElement;
7880
cursor: HTMLElement;
81+
chevron: HTMLElement;
7982
}
8083

8184
export function buildChrome(app: HTMLElement): ChromeHandles {
@@ -88,7 +91,7 @@ export function buildChrome(app: HTMLElement): ChromeHandles {
8891
<div class="meta">v${COPY.version}<a href="${COPY.github}">github</a><a href="${COPY.releases}">releases</a></div>
8992
</div>
9093
<div class="lede">
91-
<h1><i>❯</i>${COPY.tagline.replace(/\.$/, '')}</h1>
94+
<h1><i id="chev">❯</i>${COPY.tagline.replace(/\.$/, '')}</h1>
9295
<p class="blurb">${COPY.blurb}</p>
9396
</div>
9497
<div class="screen" data-loading>
@@ -116,5 +119,6 @@ export function buildChrome(app: HTMLElement): ChromeHandles {
116119
readout: q('#readout'),
117120
hint: q('#hint'),
118121
cursor: q('#cursor'),
122+
chevron: q('#chev'),
119123
};
120124
}

web/src/controls.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@ import { CURATED_PRESETS, CURATED_PALETTES } from './data';
22
import type { TermController } from './terminal';
33
import type { ChromeHandles } from './chrome';
44

5+
// Chevron accent per palette: a lifted, slime-parallel sample of each palette's
6+
// hue (same lightness/chroma profile the Slime green carries as the UI accent).
7+
const PALETTE_ACCENT: Record<string, string> = {
8+
Slime: '#6dbf66',
9+
Warm: '#d2ab5f',
10+
Ocean: '#79aff7',
11+
Mono: '#b0adaa',
12+
Heat: '#f18c64',
13+
};
14+
15+
// One-line character note per preset, shown after the first preset cycle.
16+
const PRESET_INFO: Record<string, string> = {
17+
Network: 'dense, interconnected veins with rapid branching',
18+
Organic: 'balanced, natural-looking growth',
19+
Vortex: 'rotational currents that coil into spirals',
20+
Lightning: 'fast dendritic branching, like lightning',
21+
Tendrils: 'long arms reaching across the field',
22+
};
23+
524
// A clickable command-line token that cycles a curated list and live-swaps.
625
function cycler(el: HTMLElement, names: string[], onPick: (n: string) => void, onChange: () => void) {
726
let i = 0;
@@ -26,18 +45,41 @@ export function wireControls(h: ChromeHandles, c: TermController, agents: number
2645
` <span class="leg">· space pauses</span>`;
2746
};
2847

29-
// The cursor + hint guide first use; they retire once the user cycles a value.
48+
// The cursor + "click a value to cycle" guide retire on the first cycle of
49+
// either token. The preset info line only appears once a preset is cycled — a
50+
// palette-first interaction leaves the line empty until then.
3051
let guided = true;
31-
const onCycle = () => {
32-
readout();
33-
if (guided) {
34-
guided = false;
35-
h.hint.setAttribute('data-done', '');
36-
h.cursor.style.display = 'none';
37-
}
52+
const retireGuide = () => {
53+
if (!guided) return;
54+
guided = false;
55+
h.cursor.style.display = 'none';
56+
h.hint.textContent = '';
57+
};
58+
59+
// Show the preset note, then let it fade out gently after a long-ish dwell.
60+
// A fresh cycle cancels the pending fade and brings the line back.
61+
let fadeTimer: number | undefined;
62+
const showPresetInfo = (text: string) => {
63+
if (fadeTimer) clearTimeout(fadeTimer);
64+
h.hint.removeAttribute('data-faded');
65+
h.hint.textContent = text;
66+
fadeTimer = window.setTimeout(() => h.hint.setAttribute('data-faded', ''), 7000);
3867
};
39-
cycler(h.tokPreset, CURATED_PRESETS, (n) => c.setPresetByName(n), onCycle);
40-
cycler(h.tokPalette, CURATED_PALETTES, (n) => c.setPaletteByName(n), onCycle);
68+
cycler(h.tokPreset, CURATED_PRESETS, (n) => c.setPresetByName(n), () => {
69+
retireGuide();
70+
showPresetInfo(PRESET_INFO[h.tokPreset.textContent ?? ''] ?? '');
71+
readout();
72+
});
73+
cycler(h.tokPalette, CURATED_PALETTES, (n) => {
74+
c.setPaletteByName(n);
75+
const col = PALETTE_ACCENT[n] ?? 'var(--acc)';
76+
h.chevron.style.color = col;
77+
h.tokPalette.style.color = col;
78+
h.tokPalette.style.borderBottomColor = col;
79+
}, () => {
80+
retireGuide();
81+
readout();
82+
});
4183
readout();
4284

4385
// Pause/resume: space (global) or clicking the status tag.

0 commit comments

Comments
 (0)