|
1 | 1 | import { COPY } from './data'; |
2 | 2 |
|
| 3 | +// "Instrument" chrome: the live sim is a recessed screen with bordered tags cut |
| 4 | +// into its bezel (live / status / readout) and registration ticks at the |
| 5 | +// corners. Square, inset, warm-dark field, green the single cold accent. |
| 6 | +// Controls = a command line with clickable tokens. |
3 | 7 | const CSS = ` |
4 | | - #app{--bg:#0b0d0e;--ink:#c8c0b0;--dim:#8b9398;--warm:#bfa46a;--line:rgba(143,143,85,.30); |
5 | | - min-height:100vh;display:flex;flex-direction:column;align-items:center;gap:1.4rem; |
6 | | - padding:0 1rem 4rem} |
7 | | - #app header{display:flex;justify-content:space-between;align-items:center;width:100%; |
8 | | - max-width:980px;padding:1.3rem .25rem;border-bottom:1px solid var(--line)} |
9 | | - #app .wm{font:600 1.1rem "FiraCodeNFMono",monospace;letter-spacing:.02em} |
10 | | - #app nav a{color:var(--dim);text-decoration:none;margin-left:1.3rem;font-size:.9rem} |
11 | | - #app nav a:hover{color:var(--warm)} |
12 | | - #app h1{margin:1.4rem 0 0;font-size:2.1rem;letter-spacing:-.02em;text-align:center} |
13 | | - #app p{margin:0;max-width:38rem;text-align:center;color:var(--dim);line-height:1.55} |
14 | | - #app .frame{position:relative;width:min(900px,96vw);aspect-ratio:16/10;background:#1c1916; |
15 | | - border:1px solid var(--line);border-radius:10px;padding:20px;overflow:hidden; |
16 | | - box-shadow:0 0 0 1px rgba(143,143,85,.10),0 0 28px rgba(143,143,85,.16),0 18px 50px rgba(0,0,0,.55)} |
17 | | - #app .frame[data-loading]::after{content:"warming…";position:absolute;inset:0;display:grid; |
18 | | - place-items:center;color:var(--dim);font:.9rem "FiraCodeNFMono",monospace} |
| 8 | + #app{--bg:#17120d;--screen:#0b0805;--ink:#c8c0b0;--dim:#8b8378;--acc:#6dbf66; |
| 9 | + --line:rgba(150,140,120,.20); |
| 10 | + font-family:"FiraCodeNFMono",monospace;min-height:100vh;display:flex; |
| 11 | + flex-direction:column;gap:1.6rem;width:min(940px,94vw);margin:0 auto; |
| 12 | + padding:0 0 3rem} |
| 13 | + #app a{color:var(--dim);text-decoration:none} |
| 14 | + #app a:hover{color:var(--acc)} |
| 15 | +
|
| 16 | + #app .top{display:flex;justify-content:space-between;align-items:baseline; |
| 17 | + padding:1.4rem 0 .9rem;border-bottom:1px solid var(--line);gap:1rem;flex-wrap:wrap} |
| 18 | + #app .wm{font-weight:600;font-size:1.05rem;letter-spacing:.02em;color:var(--ink)} |
| 19 | + #app .wm b{color:var(--acc);font-weight:600} |
| 20 | + #app .meta{font-size:.78rem;color:var(--dim);letter-spacing:.04em} |
| 21 | + #app .meta a{margin-left:.9rem} |
| 22 | +
|
| 23 | + #app .lede{display:flex;flex-direction:column;gap:.7rem} |
| 24 | + #app h1{margin:0;font-size:clamp(1.9rem,5vw,2.9rem);font-weight:600; |
| 25 | + letter-spacing:-.01em;line-height:1.05;color:var(--ink)} |
| 26 | + #app h1 i{color:var(--acc);font-style:normal;margin-right:.4rem} |
| 27 | + #app .blurb{margin:0;max-width:58ch;color:var(--dim);font-size:.9rem;line-height:1.6} |
| 28 | +
|
| 29 | + /* Recessed screen: square, inner shadow well, bordered bezel tags. */ |
| 30 | + #app .screen{position:relative;width:100%;aspect-ratio:16/9;background:var(--screen); |
| 31 | + border:1px solid var(--line);padding:18px; |
| 32 | + box-shadow:inset 0 1px 0 rgba(255,244,228,.05),inset 0 18px 44px -14px rgba(0,0,0,.9), |
| 33 | + inset 0 -10px 30px -16px rgba(0,0,0,.7)} |
| 34 | + #app .viewport{position:absolute;inset:18px;overflow:hidden} |
19 | 35 | #app .host{width:100%;height:100%} |
20 | | - #app .ctl{display:flex;flex-wrap:wrap;gap:.4rem;justify-content:center;align-items:center} |
21 | | - #app .ctl .lbl{font:600 .72rem "FiraCodeNFMono",monospace;color:var(--dim); |
22 | | - text-transform:uppercase;letter-spacing:.1em;margin-right:.3rem} |
23 | | - #app .pill{all:unset;cursor:pointer;font-size:.82rem;padding:.32rem .8rem;border-radius:999px; |
24 | | - border:1px solid var(--line);color:var(--dim)} |
25 | | - #app .pill:hover{color:var(--ink);border-color:var(--warm)} |
26 | | - #app .pill[data-active]{background:var(--warm);color:#1c1916;border-color:var(--warm)} |
27 | | - #app .frame[data-error]::after{content:"Couldn't start the simulation. Here's a still."; |
28 | | - position:absolute;inset:0;display:grid;place-items:end center;padding:1rem; |
29 | | - color:var(--dim);font:.85rem "FiraCodeNFMono",monospace;background: |
30 | | - center/cover no-repeat url('/tslime/poster.png')} |
31 | | - #app .toggle{all:unset;cursor:pointer;font:.78rem "FiraCodeNFMono",monospace; |
32 | | - color:var(--dim);border:1px solid var(--line);border-radius:999px;padding:.28rem .8rem} |
33 | | - #app .toggle:hover{color:var(--warm);border-color:var(--warm)}`; |
| 36 | + /* registration ticks at the four corners */ |
| 37 | + #app .tick{position:absolute;width:9px;height:9px;border:1px solid var(--acc);opacity:.55} |
| 38 | + #app .tick.tl{top:6px;left:6px;border-right:0;border-bottom:0} |
| 39 | + #app .tick.tr{top:6px;right:6px;border-left:0;border-bottom:0} |
| 40 | + #app .tick.bl{bottom:6px;left:6px;border-right:0;border-top:0} |
| 41 | + #app .tick.br{bottom:6px;right:6px;border-left:0;border-top:0} |
| 42 | + /* bordered HUD tags floating just inside the screen, over the sim */ |
| 43 | + #app .tag{position:absolute;font-size:.64rem;letter-spacing:.12em;text-transform:uppercase; |
| 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} |
| 48 | + #app .status[data-paused]{color:var(--dim)} |
| 49 | + #app .readout{bottom:1.5rem;right:1.5rem;text-transform:none;letter-spacing:.04em} |
| 50 | + #app .readout .leg{color:var(--dim);opacity:.7} |
| 51 | + #app .screen[data-loading] .viewport::after{content:"warming…";position:absolute; |
| 52 | + inset:0;display:grid;place-items:center;color:var(--dim);font-size:.85rem} |
| 53 | + #app .screen[data-error] .viewport{background:center/cover no-repeat url('/tslime/poster.png')} |
| 54 | + #app .screen[data-error] .viewport::after{content:"can't start the sim — here's a still"; |
| 55 | + position:absolute;left:0;right:0;bottom:0;padding:.7rem;text-align:center; |
| 56 | + color:var(--ink);font-size:.8rem;background:linear-gradient(transparent,rgba(0,0,0,.7))} |
| 57 | +
|
| 58 | + /* command line: clickable tokens cycle presets/palettes */ |
| 59 | + #app .cmd{font-size:clamp(.85rem,2.4vw,1.05rem);color:var(--dim); |
| 60 | + display:flex;flex-wrap:wrap;align-items:center;gap:.5ch} |
| 61 | + #app .cmd .pr{color:var(--acc)} |
| 62 | + #app .cmd .name{color:var(--ink)} |
| 63 | + #app .cmd .tok{color:var(--acc);cursor:pointer;border-bottom:1px dotted var(--acc); |
| 64 | + padding-bottom:1px;transition:color .12s} |
| 65 | + #app .cmd .tok:hover{color:#a8e29c;border-bottom-style:solid} |
| 66 | + #app .cmd .cur{color:var(--acc);animation:blink 1.1s steps(1) infinite;margin-left:.2ch} |
| 67 | + @keyframes blink{50%{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}`; |
| 71 | + |
| 72 | +export interface ChromeHandles { |
| 73 | + host: HTMLElement; |
| 74 | + screen: HTMLElement; |
| 75 | + tokPreset: HTMLElement; |
| 76 | + tokPalette: HTMLElement; |
| 77 | + status: HTMLElement; |
| 78 | + readout: HTMLElement; |
| 79 | + hint: HTMLElement; |
| 80 | + cursor: HTMLElement; |
| 81 | + chevron: HTMLElement; |
| 82 | +} |
34 | 83 |
|
35 | | -export function buildChrome(app: HTMLElement) { |
| 84 | +export function buildChrome(app: HTMLElement): ChromeHandles { |
36 | 85 | const style = document.createElement('style'); |
37 | 86 | style.textContent = CSS; |
38 | 87 | document.head.appendChild(style); |
39 | | - // Replace the static first-paint contents with the enhanced chrome. |
40 | 88 | app.innerHTML = ` |
41 | | - <header> |
42 | | - <div class="wm">tslime</div> |
43 | | - <nav><a href="${COPY.github}">GitHub</a><a href="${COPY.releases}">Releases</a></nav> |
44 | | - </header> |
45 | | - <h1>${COPY.tagline}</h1> |
46 | | - <p>${COPY.blurb}</p> |
47 | | - <div class="frame" data-loading><div class="host" id="host"></div></div> |
48 | | - <div class="ctl" id="presetBar"><span class="lbl">Preset</span></div> |
49 | | - <div class="ctl" id="paletteBar"><span class="lbl">Palette</span></div>`; |
| 89 | + <div class="top"> |
| 90 | + <div class="wm">~ $ <b>tslime</b></div> |
| 91 | + <div class="meta">v${COPY.version}<a href="${COPY.github}">github</a><a href="${COPY.releases}">releases</a></div> |
| 92 | + </div> |
| 93 | + <div class="lede"> |
| 94 | + <h1><i id="chev">❯</i>${COPY.tagline.replace(/\.$/, '')}</h1> |
| 95 | + <p class="blurb">${COPY.blurb}</p> |
| 96 | + </div> |
| 97 | + <div class="screen" data-loading> |
| 98 | + <span class="tick tl"></span><span class="tick tr"></span> |
| 99 | + <span class="tick bl"></span><span class="tick br"></span> |
| 100 | + <span class="tag slabel">live</span> |
| 101 | + <span class="tag status" id="status">● running</span> |
| 102 | + <div class="viewport"><div class="host" id="host"></div></div> |
| 103 | + <span class="tag readout" id="readout">—</span> |
| 104 | + </div> |
| 105 | + <div class="cmd" id="cmd"> |
| 106 | + <span class="pr">$</span> <span class="name">tslime</span> |
| 107 | + <span class="flag">--preset</span> <span class="tok" id="tokPreset" role="button" tabindex="0">network</span> |
| 108 | + <span class="flag">--palette</span> <span class="tok" id="tokPalette" role="button" tabindex="0">slime</span> |
| 109 | + <span class="cur" id="cursor">█</span> |
| 110 | + </div> |
| 111 | + <div class="hint" id="hint">click a value to cycle</div>`; |
| 112 | + const q = <T extends HTMLElement>(s: string) => app.querySelector<T>(s)!; |
50 | 113 | return { |
51 | | - host: app.querySelector<HTMLElement>('#host')!, |
52 | | - presetBar: app.querySelector<HTMLElement>('#presetBar')!, |
53 | | - paletteBar: app.querySelector<HTMLElement>('#paletteBar')!, |
| 114 | + host: q('#host'), |
| 115 | + screen: q('.screen'), |
| 116 | + tokPreset: q('#tokPreset'), |
| 117 | + tokPalette: q('#tokPalette'), |
| 118 | + status: q('#status'), |
| 119 | + readout: q('#readout'), |
| 120 | + hint: q('#hint'), |
| 121 | + cursor: q('#cursor'), |
| 122 | + chevron: q('#chev'), |
54 | 123 | }; |
55 | 124 | } |
0 commit comments