Files
lime/work/lisp-workstation-study.html
2026-10-02 14:14:47 -05:00

266 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style>
#lime-workstation-study {
--ls-base: #1c2022; --ls-panel: #22282a; --ls-raised: #2b3435;
--ls-fg: #d3d9d2; --ls-muted: #9ca9a4; --ls-line: #4c5957;
--ls-accent: #bfd2ab; --ls-secondary: #a6b8cd; --ls-string: #c9b99b;
--ls-chrome-font: "Dina", "Lucida Console", ui-monospace, monospace;
color-scheme: dark; color: var(--ls-fg); background: var(--ls-base);
font: 13px/1.6 ui-monospace, "DejaVu Sans Mono", monospace;
width: 100%; box-sizing: border-box;
}
#lime-workstation-study *, #lime-workstation-study *::before { box-sizing: border-box; }
#lime-workstation-study button, #lime-workstation-study input { font: inherit; }
#lime-workstation-study button { color: inherit; cursor: pointer; }
#lime-workstation-study button:focus-visible, #lime-workstation-study input:focus-visible {
outline: 2px solid var(--ls-accent); outline-offset: 2px;
}
#lime-workstation-study .ls-bar {
display: flex; align-items: stretch; flex-wrap: wrap; min-height: 36px;
border-bottom: 1px solid var(--ls-line); font-family: var(--ls-chrome-font);
}
#lime-workstation-study .ls-bar button { border: 0; background: transparent; padding: 7px 12px; }
#lime-workstation-study .ls-bar button:hover, #lime-workstation-study .ls-bar button[aria-expanded="true"] {
background: var(--ls-raised); color: var(--ls-accent);
}
#lime-workstation-study .ls-brand { color: var(--ls-accent); font-weight: 700; }
#lime-workstation-study .ls-divider { align-self: center; color: var(--ls-line); }
#lime-workstation-study .ls-layout { padding: 7px 12px; }
#lime-workstation-study .ls-bar-title {
flex: 1 1 150px; padding: 7px 14px; background: var(--ls-panel);
border-inline: 1px solid var(--ls-line); overflow-wrap: anywhere;
}
#lime-workstation-study .ls-time { padding: 7px 14px; color: var(--ls-muted); }
#lime-workstation-study .ls-main { position: relative; }
#lime-workstation-study .ls-desk {
display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
gap: 8px; padding: 12px; min-height: 480px;
}
#lime-workstation-study .ls-window {
min-width: 0; display: flex; flex-direction: column;
background: var(--ls-base); border: 1px solid var(--ls-line);
}
#lime-workstation-study .ls-editor { border-top: 2px solid var(--ls-accent); }
#lime-workstation-study .ls-side { display: grid; grid-template-rows: 0.9fr 1.1fr; gap: 8px; min-width: 0; }
#lime-workstation-study .ls-content { padding: 16px 18px; flex: 1; overflow-wrap: anywhere; }
#lime-workstation-study .ls-file-context { margin-bottom: 24px; color: var(--ls-muted); }
#lime-workstation-study .ls-code { white-space: pre-wrap; margin: 0; font: inherit; overflow-wrap: anywhere; }
#lime-workstation-study .ls-keyword { color: var(--ls-accent); }
#lime-workstation-study .ls-name { color: var(--ls-secondary); }
#lime-workstation-study .ls-string { color: var(--ls-string); }
#lime-workstation-study .ls-comment, #lime-workstation-study .ls-muted { color: var(--ls-muted); }
#lime-workstation-study .ls-selection { background: var(--ls-raised); }
#lime-workstation-study .ls-cursor {
display: inline-block; height: 1.15em; width: 0.58em; vertical-align: -0.15em;
border: 1px solid var(--ls-accent);
}
#lime-workstation-study .ls-modeline {
display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 12px;
padding: 4px 10px; background: var(--ls-panel); color: var(--ls-muted);
border-top: 1px solid var(--ls-line); font-family: var(--ls-chrome-font); font-size: 12px;
}
#lime-workstation-study .ls-editor .ls-modeline { color: var(--ls-fg); }
#lime-workstation-study .ls-repl-line { margin: 0 0 14px; }
#lime-workstation-study .ls-object {
background: none; border: 0; border-bottom: 1px dotted var(--ls-accent);
padding: 0; color: var(--ls-accent); overflow-wrap: anywhere; text-align: left;
}
#lime-workstation-study .ls-object-detail {
margin: 10px 0; border-left: 2px solid var(--ls-line); padding-left: 12px;
color: var(--ls-muted); white-space: pre-wrap;
}
#lime-workstation-study .ls-shell { background: var(--ls-terminal-bg, var(--ls-base)); color: var(--ls-terminal-fg, var(--ls-fg)); }
#lime-workstation-study .ls-shell .ls-prompt { color: var(--ls-terminal-prompt, var(--ls-accent)); }
#lime-workstation-study .ls-shell .ls-comment { color: var(--ls-terminal-muted, var(--ls-muted)); }
#lime-workstation-study .ls-menu {
position: absolute; top: 8px; left: 12px; right: 12px; z-index: 3;
border: 1px solid var(--ls-accent); background: var(--ls-panel);
box-shadow: 0 12px 24px #00000050;
}
#lime-workstation-study [hidden] { display: none !important; }
#lime-workstation-study .ls-menu-header {
display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
padding: 8px 12px; background: var(--ls-raised); border-bottom: 1px solid var(--ls-line);
}
#lime-workstation-study .ls-menu-path { color: var(--ls-accent); flex: 1; }
#lime-workstation-study .ls-menu-header button { padding: 2px 8px; background: transparent; border: 1px solid var(--ls-line); }
#lime-workstation-study .ls-command-list { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); padding: 10px; gap: 6px; }
#lime-workstation-study .ls-command {
display: flex; align-items: center; gap: 10px; text-align: left;
padding: 10px; border: 1px solid transparent; background: transparent; overflow-wrap: anywhere;
}
#lime-workstation-study .ls-command:hover { border-color: var(--ls-line); background: var(--ls-raised); }
#lime-workstation-study .ls-command kbd { color: var(--ls-accent); min-width: 22px; font: inherit; }
#lime-workstation-study .ls-command .ls-branch { color: var(--ls-muted); margin-left: auto; }
#lime-workstation-study .ls-menu-search { width: calc(100% - 24px); margin: 12px 12px 0; padding: 8px; border: 1px solid var(--ls-line); background: var(--ls-base); color: var(--ls-fg); }
#lime-workstation-study .ls-footer { padding: 4px 14px 9px; color: var(--ls-muted); font-size: 12px; }
#lime-workstation-study .ls-menu-status { margin: 0; padding: 6px 14px; border-top: 1px solid var(--ls-line); color: var(--ls-muted); font-size: 12px; }
@media (max-width: 680px) {
#lime-workstation-study .ls-desk { grid-template-columns: minmax(0,1fr); }
#lime-workstation-study .ls-bar-title { flex-basis: 65%; }
#lime-workstation-study .ls-content { padding: 14px; }
#lime-workstation-study .ls-command-list { grid-template-columns: repeat(2, minmax(0,1fr)); }
#lime-workstation-study .ls-file-context { margin-bottom: 14px; }
}
@media (pointer: coarse) {
#lime-workstation-study button { min-height: 44px; }
#lime-workstation-study input { min-height: 44px; font-size: 16px; }
}
</style>
<div id="lime-workstation-study" aria-label="Lime alternate-present workstation design study">
<header class="ls-bar" aria-label="Lime head bar">
<button class="ls-brand" type="button" data-menu="root" aria-expanded="false" aria-controls="ls-command-menu">Lime</button>
<span class="ls-divider" aria-hidden="true">|</span>
<button type="button" data-menu="views" aria-label="Choose a view">WM project:<span data-view>Code</span>*</button>
<span class="ls-divider" aria-hidden="true">|</span>
<span class="ls-layout" aria-label="Master stack layout">[]=</span>
<span class="ls-bar-title">layout.lisp — Emacs</span>
<span class="ls-time">15:42</span>
</header>
<main class="ls-main">
<div class="ls-desk">
<section class="ls-window ls-editor" aria-label="Emacs editor with matching theme">
<div class="ls-content">
<div class="ls-file-context">lime / src / layout.lisp</div>
<pre class="ls-code"><span class="ls-comment">;;; Arrangements are values.</span>
<span class="ls-comment">;;; The activity remains yours.</span>
(<span class="ls-keyword">defun</span> <span class="ls-name">remember-arrangement</span> (world head name)
(<span class="ls-keyword">let</span> ((draft (current-presentation world head)))
(<span class="ls-selection">save-view world draft name</span>)))
(<span class="ls-keyword">defun</span> <span class="ls-name">return-to-code</span> (world head)
(<span class="ls-keyword">let</span> ((view (find-view world <span class="ls-string">"wm-project/code"</span>)))
(activate-view-strictly world head view)))
<span class="ls-comment">;; Keep the work; change the perspective.</span>
<span class="ls-cursor" aria-label="Editor cursor"></span></pre>
</div>
<footer class="ls-modeline"><span>** layout.lisp</span><span>Lisp · 8:5</span></footer>
</section>
<div class="ls-side">
<section class="ls-window ls-shell" aria-label="Terminal">
<div class="ls-content">
<pre class="ls-code"><span class="ls-prompt">~/code/lime ›</span> git status --short
M src/layout.lisp
M test/model.lisp
<span class="ls-comment">master · 2 modified files</span>
<span class="ls-prompt">~/code/lime ›</span> <span class="ls-cursor" aria-label="Terminal cursor"></span></pre>
</div>
<footer class="ls-modeline"><span>st · ~/code/lime</span><span>shell</span></footer>
</section>
<section class="ls-window" aria-label="SLY listener concept">
<div class="ls-content">
<p class="ls-repl-line"><span class="ls-keyword">LIME.CORE&gt;</span> (current-presentation world head)</p>
<button type="button" class="ls-object" aria-expanded="false" aria-controls="ls-inspected-object">#&lt;PRESENTATION WM project:Code*&gt;</button>
<div id="ls-inspected-object" class="ls-object-detail" hidden>activity WM project
source-view Code
dirty-p T
clients (editor terminal browser)</div>
<p class="ls-repl-line ls-comment" style="margin-top:18px">; 248 checks passed.</p>
<div><span class="ls-keyword">LIME.CORE&gt;</span> <span class="ls-cursor" aria-label="Listener cursor"></span></div>
</div>
<footer class="ls-modeline"><span>SLY · listener</span><span>SBCL · local</span></footer>
</section>
</div>
</div>
<section id="ls-command-menu" class="ls-menu" aria-label="Command menu concept" hidden>
<header class="ls-menu-header">
<span class="ls-menu-path">Lime</span>
<button type="button" data-back hidden>Back</button>
<button type="button" data-close>Esc · Close</button>
</header>
<input class="ls-menu-search" type="search" aria-label="Search commands" placeholder="Search commands…" hidden>
<div class="ls-command-list"></div>
<p class="ls-menu-status" aria-live="polite">Choose a branch.</p>
</section>
</main>
<footer class="ls-footer">Concept study · Slate / celadon · <span data-footer-hint>Lime opens commands; the listener value is inspectable.</span></footer>
</div>
<script>
(() => {
const root = document.getElementById('lime-workstation-study');
const state = { palette: 'celadon', matchedTerminal: true, type: 'bitmap' };
const palettes = {
celadon: { label: 'Slate / celadon', base: '#1c2022', panel: '#22282a', raised: '#2b3435', fg: '#d3d9d2', muted: '#9ca9a4', line: '#4c5957', accent: '#bfd2ab', secondary: '#a6b8cd', string: '#c9b99b' },
violet: { label: 'Graphite / violet', base: '#1d1d22', panel: '#25252c', raised: '#30303a', fg: '#d6d5df', muted: '#a4a1b3', line: '#555360', accent: '#bcb0d5', secondary: '#9ebdc1', string: '#c6bba4' },
ice: { label: 'Ink / ice', base: '#192127', panel: '#222d34', raised: '#2c3c46', fg: '#d1dce0', muted: '#9cafb9', line: '#49616e', accent: '#a8cddd', secondary: '#b5c9b5', string: '#ccbd9e' }
};
function renderDesign() {
const p = palettes[state.palette];
for (const [key, value] of Object.entries(p)) if (key !== 'label') root.style.setProperty('--ls-' + key, value);
root.style.setProperty('--ls-chrome-font', state.type === 'bitmap' ? '"Dina", "Lucida Console", ui-monospace, monospace' : 'ui-monospace, "DejaVu Sans Mono", monospace');
root.querySelector('.ls-footer').firstChild.textContent = 'Concept study · ' + p.label + ' · ';
const terminal = root.querySelector('.ls-shell');
for (const [key, value] of Object.entries({ bg: '#060a0c', fg: '#aaca66', prompt: '#e7c77c', muted: '#78a7bd' })) {
if (state.matchedTerminal) terminal.style.removeProperty('--ls-terminal-' + key);
else terminal.style.setProperty('--ls-terminal-' + key, value);
}
}
const menu = root.querySelector('.ls-menu');
const list = root.querySelector('.ls-command-list');
const search = root.querySelector('.ls-menu-search');
const back = root.querySelector('[data-back]');
const status = root.querySelector('.ls-menu-status');
const branches = {
root: [['a', 'Activities', 'activities'], ['v', 'Views', 'views'], ['w', 'Windows', 'windows'], ['l', 'Layout', 'layout'], ['u', 'User', 'user'], ['Space', 'Search commands', 'search']],
activities: [['s', 'Switch activity'], ['n', 'New activity']],
views: [['c', 'Code', null, 'Code'], ['d', 'Debug', null, 'Debug'], ['o', 'Docs', null, 'Docs'], ['s', 'Save checkpoint']],
windows: [['h', 'Hide window'], ['i', 'Show hidden window'], ['f', 'Float / tile'], ['c', 'Close window']],
layout: [['t', 'Master / stack'], ['m', 'Monocle'], ['f', 'Floating'], ['a', 'Manual']],
user: [['r', 'Reload configuration'], ['t', 'Open terminal']]
};
let branch = 'root';
function showBranch(name) {
branch = name;
menu.hidden = false;
root.querySelector('.ls-brand').setAttribute('aria-expanded', 'true');
root.querySelector('.ls-menu-path').textContent = name === 'root' ? 'Lime' : 'Lime › ' + name[0].toUpperCase() + name.slice(1);
back.hidden = name === 'root';
search.hidden = name !== 'search';
status.textContent = name === 'root' ? 'Choose a branch.' : 'Concept interactions only; your running session is unchanged.';
renderCommands();
if (name === 'search') search.focus();
}
function renderCommands() {
list.replaceChildren();
const options = branch === 'search' ? Object.entries(branches).filter(([key]) => key !== 'root').flatMap(([, items]) => items).filter(item => item[1].toLowerCase().includes(search.value.toLowerCase())) : branches[branch];
for (const item of options) {
const button = document.createElement('button');
button.type = 'button'; button.className = 'ls-command';
const key = document.createElement('kbd'); key.textContent = item[0];
const name = document.createElement('span'); name.textContent = item[1];
button.append(key, name);
if (item[2]) { const marker = document.createElement('span'); marker.className = 'ls-branch'; marker.textContent = '+'; button.append(marker); }
button.addEventListener('click', () => {
if (item[2]) showBranch(item[2]);
else if (item[3]) { root.querySelector('[data-view]').textContent = item[3]; status.textContent = item[3] + ' selected in this study.'; }
else status.textContent = item[1] + ' — command location preview only.';
});
list.append(button);
}
}
function closeMenu() { menu.hidden = true; root.querySelector('.ls-brand').setAttribute('aria-expanded', 'false'); root.querySelector('.ls-brand').focus(); }
root.querySelectorAll('[data-menu]').forEach(button => button.addEventListener('click', () => {
if (!menu.hidden && branch === button.dataset.menu) closeMenu(); else showBranch(button.dataset.menu);
}));
root.querySelector('[data-close]').addEventListener('click', closeMenu);
back.addEventListener('click', () => showBranch('root'));
search.addEventListener('input', renderCommands);
root.addEventListener('keydown', event => { if (event.key === 'Escape' && !menu.hidden) { event.preventDefault(); closeMenu(); } });
root.querySelector('.ls-object').addEventListener('click', event => {
const detail = root.querySelector('#ls-inspected-object'); detail.hidden = !detail.hidden;
event.currentTarget.setAttribute('aria-expanded', String(!detail.hidden));
});
renderDesign();
if (globalThis.Tweak) {
const tweak = new Tweak({ container: root, onChange: renderDesign });
tweak.addSelect(state, 'palette', { label: 'Color direction', options: [{ label: 'Slate / celadon', value: 'celadon' }, { label: 'Graphite / violet', value: 'violet' }, { label: 'Ink / ice', value: 'ice' }] });
tweak.addToggle(state, 'matchedTerminal', { label: 'Terminal follows the environment' });
tweak.addSelect(state, 'type', { label: 'Chrome typography', options: [{ label: 'Dina when locally available', value: 'bitmap' }, { label: 'System monospace', value: 'mono' }] });
}
})();
</script>