Initial Lime window manager

This commit is contained in:
2026-10-02 14:14:47 -05:00
commit ccd691edd0
121 changed files with 29719 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
<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>