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

421 lines
32 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.

<div id="lime-lisp-palettes" aria-label="Lime dark workstation palette comparison">
<style>
#lime-lisp-palettes { --lime-base:#0f0f0f; --lime-surface:#191919; --lime-selection:#262626; --lime-selection-ink:#ffb054; --lime-title-text:#dbc9a6; --lime-text:#dbc9a6; --lime-muted:#928f86; --lime-accent:#ffb054; --lime-mint:#768565; --lime-sky:#76929f; --lime-alert:#b76c64; --lime-app:#0f0f0f; --lime-app-text:#dbc9a6; --lime-app-muted:#928f86; --lime-edge:#4c4c4c; --lime-bar-text:#dbc9a6; --lime-bar-background:#191919; --lime-logo:#dbc9a6; --lime-attention:#b76c64; --lime-separator:#686868; --lime-key:#ffb054; --lime-menu-label:#dbc9a6; --lime-music:#dbc9a6; --lime-volume:#dbc9a6; --lime-clock:#dbc9a6; --lime-status-icon:#76929f; color:var(--lime-text); font:400 13px/1.65 "Dina", "Iosevka", "DejaVu Sans Mono", monospace; width:100%; }
#lime-lisp-palettes * { box-sizing:border-box; }
#lime-lisp-palettes button { font:inherit; color:inherit; cursor:pointer; }
#lime-lisp-palettes .lime-palette-switcher { display:flex; flex-wrap:wrap; gap:8px; padding-bottom:12px; }
#lime-lisp-palettes .lime-choice-swatch { display:inline-block; width:14px; height:14px; margin-right:8px; vertical-align:-2px; }
#lime-lisp-palettes .lime-palette-switcher button { flex:1 1 180px; min-height:44px; padding:9px 14px; background:var(--lime-surface); color:var(--lime-text); border:1px solid var(--lime-edge); }
#lime-lisp-palettes .lime-palette-switcher button:hover { background:var(--lime-selection); color:var(--lime-selection-ink); }
#lime-lisp-palettes .lime-palette-switcher button[aria-pressed="true"] { background:var(--lime-selection); color:var(--lime-selection-ink); border-color:var(--lime-accent); box-shadow:inset 0 -2px 0 var(--lime-accent); }
#lime-lisp-palettes .lime-desk { background:var(--lime-base); position:relative; isolation:isolate; }
#lime-lisp-palettes .lime-bar { display:flex; align-items:center; gap:0; min-height:32px; background:var(--lime-bar-background); color:var(--lime-bar-text); white-space:nowrap; }
#lime-lisp-palettes .lime-context-group { display:flex; align-items:center; max-width:100%; }
#lime-lisp-palettes .lime-logo { background:none; border:0; padding:4px 16px; color:var(--lime-logo); }
#lime-lisp-palettes .lime-pipe { color:var(--lime-separator); padding:0; }
#lime-lisp-palettes .lime-context { padding:0 16px; }
#lime-lisp-palettes .lime-layout { padding:0 16px; }
#lime-lisp-palettes .lime-title { padding:4px 12px; flex:1; min-width:20px; overflow:hidden; text-overflow:ellipsis; border-inline:1px solid var(--lime-edge); background:var(--lime-selection); color:var(--lime-title-text); }
#lime-lisp-palettes .lime-status { display:flex; flex-wrap:wrap; align-items:center; padding:0 16px; column-gap:12px; row-gap:4px; }
#lime-lisp-palettes .lime-status .lime-pipe { padding:0; }
#lime-lisp-palettes .lime-attention { color:var(--lime-attention); }
#lime-lisp-palettes .lime-music { color:var(--lime-music); }
#lime-lisp-palettes .lime-volume { color:var(--lime-volume); }
#lime-lisp-palettes .lime-clock { color:var(--lime-clock); }
#lime-lisp-palettes .lime-status-icon { color:var(--lime-status-icon); display:inline-flex; align-items:center; vertical-align:middle; }
#lime-lisp-palettes .lime-volume .lime-status-icon, #lime-lisp-palettes .lime-clock .lime-status-icon { margin-right:1ch; }
#lime-lisp-palettes .lime-workspace { position:relative; }
#lime-lisp-palettes .lime-windows { padding:7px; display:grid; grid-template-columns:minmax(0,1.4fr) minmax(0,1fr); gap:7px; min-height:510px; }
#lime-lisp-palettes .lime-window { min-width:0; background:var(--lime-app); color:var(--lime-app-text); border:3px solid var(--lime-edge); display:flex; flex-direction:column; }
#lime-lisp-palettes .lime-window[data-selected="true"] { border-color:var(--lime-accent); }
#lime-lisp-palettes .lime-stack { display:grid; grid-template-rows:1fr 1fr; min-width:0; gap:7px; }
#lime-lisp-palettes .lime-buffer-tabs { padding:8px 13px; border-bottom:1px solid var(--lime-edge); color:var(--lime-app-muted); display:flex; gap:18px; }
#lime-lisp-palettes .lime-buffer-tabs span:first-child { color:var(--lime-accent); }
#lime-lisp-palettes .lime-code { margin:0; padding:26px 17px; font:inherit; white-space:pre-wrap; overflow-wrap:anywhere; flex:1; }
#lime-lisp-palettes .lime-code .lime-comment, #lime-lisp-palettes .lime-faded { color:var(--lime-app-muted); }
#lime-lisp-palettes .lime-keyword { color:var(--lime-accent); }
#lime-lisp-palettes .lime-symbol { color:var(--lime-mint); }
#lime-lisp-palettes .lime-constant { color:var(--lime-sky); }
#lime-lisp-palettes .lime-string { color:var(--lime-alert); }
#lime-lisp-palettes .lime-edit-space { min-height:75px; flex:1; }
#lime-lisp-palettes .lime-mode-line { padding:3px 12px; display:flex; justify-content:space-between; gap:12px; background:var(--lime-surface); color:var(--lime-muted); border-top:1px solid var(--lime-edge); }
#lime-lisp-palettes .lime-minibuffer { padding:5px 12px; color:var(--lime-app-muted); }
#lime-lisp-palettes .lime-terminal { padding:20px 18px; overflow-wrap:anywhere; }
#lime-lisp-palettes .lime-terminal p { margin:0 0 10px; }
#lime-lisp-palettes .lime-caret { display:inline-block; height:15px; width:8px; vertical-align:middle; background:var(--lime-accent); }
#lime-lisp-palettes .lime-notes { padding:22px 20px; }
#lime-lisp-palettes .lime-notes h3 { font:500 16px/1.5 "Dina", "DejaVu Sans Mono", monospace; color:var(--lime-mint); margin:0 0 15px; }
#lime-lisp-palettes .lime-notes p { margin:0 0 12px; }
#lime-lisp-palettes .lime-note-rule { height:1px; background:var(--lime-edge); margin:18px 0; }
#lime-lisp-palettes .lime-menu { position:absolute; z-index:2; top:8px; left:12px; right:12px; background:var(--lime-surface); border:1px solid var(--lime-edge); box-shadow:0 12px 22px #00000033; padding:13px 18px 10px; }
#lime-lisp-palettes .lime-menu[hidden] { display:none; }
#lime-lisp-palettes .lime-menu-top { display:flex; justify-content:space-between; align-items:center; color:var(--lime-muted); padding-bottom:10px; border-bottom:1px solid var(--lime-edge); }
#lime-lisp-palettes .lime-menu-heading { color:var(--lime-accent); }
#lime-lisp-palettes .lime-menu-back { border:0; padding:0; background:none; color:var(--lime-muted); }
#lime-lisp-palettes .lime-map { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:4px 22px; padding:12px 0; }
#lime-lisp-palettes .lime-map button { background:none; border:0; padding:4px 0; display:flex; align-items:baseline; gap:12px; text-align:left; min-width:0; }
#lime-lisp-palettes .lime-map button:hover { background:var(--lime-selection); color:var(--lime-selection-ink); }
#lime-lisp-palettes .lime-map kbd { font:inherit; color:var(--lime-key); min-width:16px; }
#lime-lisp-palettes .lime-map-label { min-width:0; overflow-wrap:anywhere; }
#lime-lisp-palettes .lime-map button[data-branch="true"] .lime-map-label { color:var(--lime-menu-label); }
#lime-lisp-palettes .lime-map-plus { color:var(--lime-muted); margin-left:auto; }
#lime-lisp-palettes .lime-map button:hover kbd, #lime-lisp-palettes .lime-map button:hover .lime-map-plus, #lime-lisp-palettes .lime-map button[data-branch="true"]:hover .lime-map-label { color:inherit; }
#lime-lisp-palettes .lime-menu-footer { color:var(--lime-muted); display:flex; justify-content:space-between; border-top:1px solid var(--lime-edge); padding-top:7px; gap:12px; }
#lime-lisp-palettes .lime-palette-key { padding:15px 16px; background:#191919; color:#dbc9a6; border-top:1px solid #4c4c4c; }
#lime-lisp-palettes .lime-palette-name { display:block; margin-bottom:12px; font-weight:500; }
#lime-lisp-palettes .lime-color-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr)); gap:12px 18px; }
#lime-lisp-palettes .lime-color-field { min-width:0; }
#lime-lisp-palettes .lime-color-field label { display:block; margin-bottom:4px; }
#lime-lisp-palettes .lime-color-inputs { display:flex; gap:8px; align-items:center; }
#lime-lisp-palettes .lime-color-inputs input { font:inherit; border:1px solid #686868; background:#0f0f0f; color:#dbc9a6; border-radius:0; min-height:36px; }
#lime-lisp-palettes .lime-color-inputs input[type="color"] { flex:none; width:44px; padding:3px; cursor:pointer; }
#lime-lisp-palettes .lime-color-inputs input[type="text"] { width:100%; min-width:0; padding:5px 9px; }
#lime-lisp-palettes .lime-color-inputs input[aria-invalid="true"] { border-color:#d98e85; }
#lime-lisp-palettes .lime-color-error { color:#d98e85; display:block; margin-top:4px; }
#lime-lisp-palettes .lime-color-error[hidden] { display:none; }
#lime-lisp-palettes .lime-more-colors { margin-top:14px; }
#lime-lisp-palettes .lime-more-colors summary { cursor:pointer; padding:4px 0; }
#lime-lisp-palettes .lime-more-colors[open] summary { margin-bottom:10px; }
@media(max-width:1000px) { #lime-lisp-palettes .lime-bar { flex-wrap:wrap; } #lime-lisp-palettes .lime-status { flex-basis:100%; justify-content:flex-end; order:3; padding:5px 16px; } }
@media(max-width:880px) { #lime-lisp-palettes .lime-windows { grid-template-columns:minmax(0,1.15fr) minmax(0,1fr); } }
@media(max-width:650px) { #lime-lisp-palettes .lime-title { flex-basis:100%; order:2; border:0; border-top:1px solid var(--lime-edge); } #lime-lisp-palettes .lime-map { grid-template-columns:repeat(2,minmax(0,1fr)); gap:3px 18px; } #lime-lisp-palettes .lime-windows { grid-template-columns:minmax(0,1fr); } #lime-lisp-palettes .lime-window-editor { min-height:470px; } #lime-lisp-palettes .lime-stack { grid-template-rows:auto auto; } #lime-lisp-palettes .lime-palette-name { flex-basis:100%; } #lime-lisp-palettes .lime-code { padding:20px 12px; } }
@media(pointer:coarse) { #lime-lisp-palettes .lime-map button, #lime-lisp-palettes .lime-logo, #lime-lisp-palettes .lime-menu-back { min-height:44px; } #lime-lisp-palettes .lime-window-editor { min-height:540px; } }
@media(pointer:coarse) { #lime-lisp-palettes .lime-color-inputs input { min-height:44px; font-size:16px; } #lime-lisp-palettes .lime-more-colors summary { min-height:44px; } }
</style>
<div class="lime-palette-switcher" role="group" aria-label="Preview palette">
<button type="button" data-palette="Your picks" aria-pressed="true"><span class="lime-choice-swatch" style="background:#ffb054" aria-hidden="true"></span>Your picks</button>
<button type="button" data-palette="dwm cream · neutral backgrounds" aria-pressed="false"><span class="lime-choice-swatch" style="background:#ffdbb0" aria-hidden="true"></span>Previous version</button>
</div>
<section class="lime-desk" aria-label="Lime master-stack desktop, with an editor, Lisp REPL, and notes">
<header class="lime-bar">
<div class="lime-context-group">
<button type="button" class="lime-logo" aria-expanded="true" aria-controls="lime-palette-menu">Lime</button>
<span class="lime-pipe" aria-hidden="true">|</span>
<span class="lime-context">lime:Code*</span>
<span class="lime-pipe" aria-hidden="true">|</span>
<span class="lime-layout">[]=</span>
</div>
<span class="lime-title">attention.lisp — Emacs</span>
<div class="lime-status"><span class="lime-attention">! 1</span><span class="lime-pipe">|</span><span class="lime-music"><span class="lime-status-icon" aria-hidden="true">▶</span> quiet company</span><span class="lime-pipe lime-media-pipe">|</span><span class="lime-volume"><span class="lime-status-icon"><i data-lucide="volume-2" aria-hidden="true"></i></span> 32%</span><span class="lime-pipe">|</span><span class="lime-clock"><span class="lime-status-icon"><i data-lucide="calendar" aria-hidden="true"></i></span> Thu Oct 01, 01:42</span></div>
</header>
<div class="lime-workspace">
<div class="lime-windows">
<section class="lime-window lime-window-editor" data-window="editor" data-selected="true" aria-label="Emacs, focused window">
<div class="lime-buffer-tabs"><span>attention.lisp</span><span>config.lisp</span></div>
<pre class="lime-code"><span class="lime-comment">;;; Attention belongs to the live session.</span>
(<span class="lime-keyword">in-package</span> <span class="lime-constant">#:lime.x11</span>)
(<span class="lime-keyword">defun</span> <span class="lime-symbol">visit-next-request</span> (backend)
(<span class="lime-keyword">let</span> ((requests
(<span class="lime-symbol">backend-attention-client-ids</span> backend)))
(<span class="lime-keyword">when</span> requests
(<span class="lime-symbol">backend-attend-client</span> backend
(first requests)))))
<span class="lime-comment">;; Quiet until something needs you.</span>
<span class="lime-caret" aria-hidden="true"></span></pre>
<div class="lime-edit-space"></div>
<div class="lime-mode-line"><span>** attention.lisp</span><span>Lisp · 15:0</span></div>
<div class="lime-minibuffer">Connected to SBCL. Ready.</div>
</section>
<div class="lime-stack">
<section class="lime-window" data-window="repl" data-selected="false" aria-label="Lisp REPL, unfocused window">
<div class="lime-terminal">
<p class="lime-faded">SBCL · lime development</p>
<p><span class="lime-symbol">LIME&gt;</span> (backend-attention-client-ids<br> *backend*)<br><span class="lime-string">("x11-800005")</span></p>
<p><span class="lime-symbol">LIME&gt;</span> (world-revision *world*)<br><span class="lime-constant">42</span></p>
<p><span class="lime-symbol">LIME&gt;</span> <span class="lime-caret" aria-hidden="true"></span></p>
</div>
</section>
<section class="lime-window" data-window="notes" data-selected="false" aria-label="Project notes, unfocused window">
<div class="lime-notes">
<span class="lime-faded">~/notes/lime.org</span>
<h3>* Making a desk feel like home</h3>
<p><span class="lime-symbol">DONE</span> activities + views<br><span class="lime-symbol">DONE</span> persistence<br><span class="lime-symbol">DONE</span> attention indicators</p>
<div class="lime-note-rule"></div>
<p><span class="lime-string">NEXT</span> actually live here</p>
</div>
</section>
</div>
</div>
<section class="lime-menu" id="lime-palette-menu" aria-label="Lime which-key command menu">
<div class="lime-menu-top"><span class="lime-menu-heading">Lime</span><button class="lime-menu-back" type="button">Esc close</button></div>
<div class="lime-map">
<button type="button" data-prefix="Activities"><kbd>a</kbd><span class="lime-map-label">Activities</span><span class="lime-map-plus">+</span></button>
<button type="button" data-prefix="Views"><kbd>v</kbd><span class="lime-map-label">Views</span><span class="lime-map-plus">+</span></button>
<button type="button" data-prefix="Windows"><kbd>w</kbd><span class="lime-map-label">Windows</span><span class="lime-map-plus">+</span></button>
<button type="button" data-prefix="Layout"><kbd>l</kbd><span class="lime-map-label">Layout</span><span class="lime-map-plus">+</span></button>
<button type="button" data-prefix="Heads"><kbd>h</kbd><span class="lime-map-label">Heads</span><span class="lime-map-plus">+</span></button>
<button type="button" data-prefix="Launch"><kbd>r</kbd><span class="lime-map-label">Launch</span><span class="lime-map-plus">+</span></button>
</div>
<div class="lime-menu-footer"><span>Space · search commands</span><span>Esc · close</span></div>
</section>
</div>
<div class="lime-palette-key" role="group" aria-label="Live palette editor">
<span class="lime-palette-name" aria-live="polite">Your picks</span>
<div class="lime-color-grid" data-color-fields="primary"></div>
<details class="lime-more-colors">
<summary>More colors</summary>
<div class="lime-color-grid" data-color-fields="more"></div>
</details>
</div>
</section>
<script>
(() => {
const root = document.getElementById('lime-lisp-palettes');
const palettes = {
// Bar/window colors: ~/.config/dwm/config.h, active `comfy` scheme.
// Status roles: ~/scripts/bar.sh, the script launched by ~/.xinitrc.
// Lime's extra menu/app roles reuse this palette for the comparison.
'Your dwm · comfy': { base:'#060a0c',surface:'#060a0c',selection:'#121f25','selection-ink':'#ffdbb0',text:'#ffdbb0',muted:'#416e79',accent:'#ffac54',mint:'#416e79',sky:'#416e79',alert:'#d44429',edge:'#121f25',separator:'#ffdbb0',music:'#416e79',volume:'#ffac54',clock:'#d44429','status-icon':'inherit' },
'Warm signal': { base:'#191919',surface:'#222222',selection:'#30302f','selection-ink':'#ce956f',text:'#c9c3b8',muted:'#a29b90',accent:'#ce956f',mint:'#a6b59a',sky:'#89a3ab',alert:'#d77d70',edge:'#494743','bar-text':'#b9b4ab',separator:'#68655f','status-icon':'#89a3ab' },
'Fuji · quieter': { base:'#0f0f0f',surface:'#191919',selection:'#262626','selection-ink':'#c6a679',text:'#cacaca',muted:'#989898',accent:'#c6a679',mint:'#8aac8b',sky:'#8a98ac',alert:'#ac8aac',edge:'#4c4c4c','bar-text':'#b5b5b5',separator:'#686868','status-icon':'#8a98ac' },
'Fuji · full accents': { base:'#0f0f0f',surface:'#191919',selection:'#262626','selection-ink':'#c6a679',text:'#f0f0f0',muted:'#8a98ac',accent:'#c6a679',mint:'#8aac8b',sky:'#8a98ac',alert:'#ac8aac',edge:'#4c4c4c','bar-background':'#0f0f0f',separator:'#8f8aac','menu-label':'#8f8aac',music:'#8aac8b',volume:'#8aacab',clock:'#aca98a','status-icon':'inherit' },
'Fuji · Mountain': { base:'#0f0f0f',surface:'#191919',selection:'#262626','selection-ink':'#f0f0f0',text:'#cacaca',muted:'#989898',accent:'#c6a679',mint:'#8aac8b',sky:'#7797b7',alert:'#ac8a8c',edge:'#4c4c4c' },
'Workbench · softened': { base:'#232323',surface:'#2d2d2d',selection:'#494743','selection-ink':'#e3ddd2',text:'#ddd7cc',muted:'#aba69e',accent:'#d79a75',mint:'#c9be99',sky:'#b7bcc0',alert:'#e7c279',edge:'#53514e' },
'Workbench': { base:'#202321',surface:'#2b2e2b',selection:'#d8d0bd','selection-ink':'#2a2d29',text:'#ddd8c8',muted:'#a7aa98',accent:'#d79a75',mint:'#b9c285',sky:'#adbeb0',alert:'#e7c279',edge:'#51554b' },
'Late-night listener': { base:'#212322',surface:'#2a2d2a',selection:'#424739','selection-ink':'#e3dfc9',text:'#d9d8c3',muted:'#a5ab98',accent:'#c6c6a0',mint:'#b3bf7e',sky:'#d29e85',alert:'#df967c',edge:'#515849' },
'Lambda Lab · steel': { base:'#181d23',surface:'#222a31',selection:'#2c3b46',text:'#e3e2da',muted:'#9eacb5',accent:'#9bb9c9',mint:'#96c6b1',sky:'#a8bfce',alert:'#e4b080',edge:'#43515c' },
'Lambda Lab · graphite': { base:'#1b1e22',surface:'#24282d',selection:'#343a40',text:'#e3e2da',muted:'#a2abae',accent:'#c2ccc8',mint:'#96c6b1',sky:'#a8bfce',alert:'#e4b080',edge:'#474f55' },
'Lambda Lab': { base:'#181c25',surface:'#202631',selection:'#30344a',text:'#e3e2da',muted:'#9ca8b6',accent:'#b7a7de',mint:'#96c6b1',sky:'#9abbd3',alert:'#e4b080',edge:'#414b5c' },
'Phosphor Garden': { base:'#18211f',surface:'#202c27',selection:'#2a4037',text:'#e0e5d9',muted:'#9fb2a5',accent:'#a7cfaa',mint:'#b4bae0',sky:'#8dbfca',alert:'#e3b08b',edge:'#43584b' },
'Amethyst Workstation': { base:'#221e29',surface:'#2b2535',selection:'#3c314a',text:'#e7dfe8',muted:'#b1a1ba',accent:'#d1a4cc',mint:'#9cc9c5',sky:'#a5b9df',alert:'#e4b78b',edge:'#55465f' }
};
// Personal Lime accents extend the dwm reference without recoloring
// its widgets, separators, title, or application syntax samples.
palettes['Comfy · Lime accents'] = {
...palettes['Your dwm · comfy'], logo:'#9fb58b', attention:'#b2a1c7'
};
// Custom accent set, using quieter Fuji's unchanged neutral surfaces,
// text contrast, separators, and menu labels as the foundation.
palettes['Fuji · clear colors'] = {
...palettes['Fuji · quieter'],
accent:'#d99856', 'selection-ink':'#d99856',
mint:'#83b56b', sky:'#6c9fd2', alert:'#d97065',
logo:'#83b56b', attention:'#d97065',
music:'#6c9fd2', volume:'#d1bb62', clock:'#66b6b2',
'status-icon':'inherit'
};
// Creamier lettering at similar brightness; neutral surfaces retained.
// Status labels and logo share the quiet foreground. Only the small
// widget icons use denim blue, with brick red reserved for attention.
palettes['Fuji · cream'] = {
...palettes['Fuji · quieter'],
text:'#cbc5b7', muted:'#928f86', 'bar-text':'#c4bfb2',
accent:'#b6ab70', 'selection-ink':'#b6ab70',
mint:'#91a58a', sky:'#76929f', alert:'#b76c64',
'status-icon':'#76929f'
};
// Keep the accepted colors and arrangement fixed while comparing
// warmer lettering, then a straw-yellow alternative to the olive focus.
palettes['Fuji · warmer cream'] = {
...palettes['Fuji · cream'], text:'#dbc9a6', 'bar-text':'#d3c2a1'
};
palettes['Fuji · cream & yellow'] = {
...palettes['Fuji · warmer cream'],
accent:'#c7b45b', 'selection-ink':'#c7b45b'
};
palettes['Fuji · cream & golden yellow'] = {
...palettes['Fuji · warmer cream'],
accent:'#d8b04c', 'selection-ink':'#d8b04c'
};
palettes['Fuji · cream & muted yellow'] = {
...palettes['Fuji · warmer cream'],
accent:'#b8aa80', 'selection-ink':'#b8aa80'
};
palettes['Fuji · butter yellow'] = {
...palettes['Fuji · warmer cream'],
accent:'#d6cc83', 'selection-ink':'#d6cc83'
};
palettes['Fuji · dim butter'] = {
...palettes['Fuji · warmer cream'],
accent:'#c3ba77', 'selection-ink':'#c3ba77'
};
// Establish the hue with standard RGB yellow, then reduce saturation
// and brightness while retaining equal red/green channels (60° hue).
palettes['Yellow reference'] = {
...palettes['Fuji · warmer cream'],
accent:'#ffff00', 'selection-ink':'#ffff00'
};
palettes['Softer yellow'] = {
...palettes['Fuji · warmer cream'],
accent:'#e0e080', 'selection-ink':'#e0e080'
};
palettes['Cream · dwm focus'] = {
...palettes['Fuji · warmer cream'],
accent:'#ffac54', 'selection-ink':'#ffac54'
};
palettes['dwm cream · neutral backgrounds'] = {
...palettes['Cream · dwm focus'],
text:'#ffdbb0', 'bar-text':'#ffdbb0', 'title-text':'#ffdbb0'
};
palettes['Your picks'] = {
...palettes['dwm cream · neutral backgrounds'],
accent:'#ffb054', 'selection-ink':'#ffb054',
text:'#dbc9a6', 'bar-text':'#dbc9a6', 'title-text':'#dbc9a6',
mint:'#768565'
};
const state = { palette:'Your picks', menu:true, focused:'editor', matchedApps:true };
// Keep edits per comparison palette, so switching does not lose them.
const colorEdits = new Map();
const colorFields = [
{ id:'accent', label:'Focus / accent', roles:['accent','selection-ink'], primary:true },
{ id:'text', label:'Cream text / title', roles:['text','bar-text','title-text','logo','menu-label','music','volume','clock'], primary:true },
{ id:'sky', label:'Icons / blue', roles:['sky','status-icon'], primary:true },
{ id:'base', label:'Window background', roles:['base'] },
{ id:'surface', label:'Bar / menu background', roles:['surface','bar-background'] },
{ id:'selection', label:'Title / hover background', roles:['selection'] },
{ id:'muted', label:'Secondary text', roles:['muted'] },
{ id:'edge', label:'Unfocused borders', roles:['edge'] },
{ id:'separator', label:'Bar separators', roles:['separator'] },
{ id:'mint', label:'Green', roles:['mint'] },
{ id:'alert', label:'Attention / red', roles:['alert','attention'] }
];
function currentPalette() {
const p = {...palettes[state.palette]};
const edits = colorEdits.get(state.palette) || {};
colorFields.forEach(field => {
if (edits[field.id]) field.roles.forEach(role => { p[role] = edits[field.id]; });
});
return p;
}
function normalizeHex(value) {
const hex = value.trim().replace(/^#/, '');
if (/^[0-9a-f]{6}$/i.test(hex)) return '#' + hex.toLowerCase();
if (/^[0-9a-f]{3}$/i.test(hex)) return '#' + [...hex].map(c => c + c).join('').toLowerCase();
return null;
}
function editColor(field, value) {
const edits = colorEdits.get(state.palette) || {};
edits[field.id] = value;
colorEdits.set(state.palette, edits);
render();
}
function buildColorEditor() {
colorFields.forEach(field => {
const group = document.createElement('div'); group.className = 'lime-color-field';
const label = document.createElement('label'); label.textContent = field.label;
const inputs = document.createElement('div'); inputs.className = 'lime-color-inputs';
const picker = document.createElement('input'); picker.type = 'color';
picker.setAttribute('aria-label', field.label + ' color picker');
const hex = document.createElement('input'); hex.type = 'text';
hex.id = 'lime-color-' + field.id; label.htmlFor = hex.id;
hex.spellcheck = false; hex.autocomplete = 'off';
hex.setAttribute('autocapitalize', 'off');
hex.setAttribute('aria-invalid', 'false');
const error = document.createElement('span'); error.className = 'lime-color-error';
error.id = hex.id + '-error'; error.hidden = true; error.setAttribute('role', 'alert');
error.textContent = 'Use 3 or 6 hex digits, e.g. #FFAC54.';
hex.setAttribute('aria-describedby', error.id);
Object.assign(field, {picker, hex, error});
hex.addEventListener('input', () => {
const color = normalizeHex(hex.value);
hex.setAttribute('aria-invalid', String(!color));
error.hidden = true;
if (color) editColor(field, color);
});
hex.addEventListener('blur', () => {
const color = normalizeHex(hex.value);
error.hidden = !!color;
if (color) hex.value = color.toUpperCase();
});
picker.addEventListener('input', () => {
hex.value = picker.value.toUpperCase();
hex.setAttribute('aria-invalid', 'false'); error.hidden = true;
editColor(field, picker.value);
});
inputs.append(picker, hex); group.append(label, inputs, error);
root.querySelector(`[data-color-fields="${field.primary ? 'primary' : 'more'}"]`).append(group);
});
}
const menu = root.querySelector('.lime-menu');
const map = root.querySelector('.lime-map');
const heading = root.querySelector('.lime-menu-heading');
const back = root.querySelector('.lime-menu-back');
let branch = null;
const rootEntries = [['a','Activities'],['v','Views'],['w','Windows'],['l','Layout'],['h','Heads'],['r','Launch']];
const branches = {
Activities:[['n','New activity…'],['o','Open activity / view…']],
Views:[['s','Save checkpoint…'],['u','Update checkpoint'],['o','Open view…']],
Windows:[['a','Attention…'],['i','Show hidden window…'],['h','Hide focused window'],['n','Focus next'],['p','Focus previous'],['f','Toggle float']],
Layout:[['m','Master/stack'],['o','Monocle'],['c','Cycle layout']],
Heads:[['n','Select next Head'],['p','Select previous Head']],
Launch:[['r','Run command…'],['t','Launch terminal'],['b','Launch browser']]
};
function renderMenu() {
const entries = branch ? branches[branch] : rootEntries;
heading.textContent = branch ? `Lime › ${branch}` : 'Lime';
back.textContent = branch ? '← back' : 'Esc close';
map.replaceChildren();
entries.forEach(([key,label]) => {
const button = document.createElement('button'); button.type = 'button';
button.dataset.branch = String(!branch);
const kbd = document.createElement('kbd'); kbd.textContent = key;
const text = document.createElement('span'); text.className='lime-map-label'; text.textContent = label;
button.append(kbd,text);
if (!branch) {
const more = document.createElement('span'); more.className='lime-map-plus'; more.textContent='+'; button.append(more);
}
button.addEventListener('click', () => {
if (!branch) { branch = label; renderMenu(); }
else if (label === 'Focus next' || label === 'Focus previous') {
state.focused = state.focused === 'editor' ? 'repl' : 'editor'; render();
} else { state.menu=false; branch=null; render(); renderMenu(); }
});
map.append(button);
});
}
function render() {
const p = currentPalette();
Object.entries(p).forEach(([role,color])=>root.style.setProperty(`--lime-${role}`,color));
const roleDefaults = {'bar-text':p.text,'bar-background':p.surface,'title-text':p['selection-ink'] || p.accent,logo:p['bar-text'] || p.text,attention:p.alert,separator:p.edge,'menu-label':p.text,key:p.accent,music:p['bar-text'] || p.text,volume:p['bar-text'] || p.text,clock:p['bar-text'] || p.text,'status-icon':p.muted};
Object.entries(roleDefaults).forEach(([role,color])=>root.style.setProperty(`--lime-${role}`,p[role] || color));
root.style.setProperty('--lime-selection-ink', p['selection-ink'] || p.accent);
root.style.setProperty('--lime-app', state.matchedApps ? p.base : '#090f12');
root.style.setProperty('--lime-app-text', state.matchedApps ? p.text : '#dce6e5');
root.style.setProperty('--lime-app-muted', state.matchedApps ? p.muted : '#8ba3ae');
menu.hidden = !state.menu;
root.querySelector('.lime-logo').setAttribute('aria-expanded',String(state.menu));
const edited = Object.keys(colorEdits.get(state.palette) || {}).length > 0;
root.querySelector('.lime-palette-name').textContent = state.palette + (edited ? ' · edited' : '');
root.querySelectorAll('[data-palette]').forEach(button => {
button.setAttribute('aria-pressed', String(button.dataset.palette === state.palette));
});
colorFields.forEach(field => {
const color = p[field.roles[0]];
field.picker.value = color;
if (document.activeElement !== field.hex) {
field.hex.value = color.toUpperCase();
field.hex.setAttribute('aria-invalid', 'false');
field.error.hidden = true;
}
});
root.querySelectorAll('[data-window]').forEach(el => {
const focused=el.dataset.window===state.focused;
el.dataset.selected=String(focused);
el.setAttribute('aria-label',`${el.dataset.window}, ${focused?'focused':'unfocused'} window`);
});
root.querySelector('.lime-title').textContent = state.focused==='editor'?'attention.lisp — Emacs':'*sly-mrepl sbcl* — st';
}
root.querySelectorAll('[data-palette]').forEach(button => {
button.addEventListener('click', () => {
state.palette = button.dataset.palette;
render();
});
});
root.querySelector('.lime-logo').addEventListener('click',()=> { state.menu=!state.menu; branch=null; renderMenu(); render(); });
back.addEventListener('click',()=> { if (branch) { branch=null; renderMenu(); } else { state.menu=false; render(); } });
root.addEventListener('keydown',event=> { if(event.key==='Escape') {state.menu=false;render();} });
buildColorEditor(); renderMenu(); render();
if (globalThis.Tweak) {
const tweak = new Tweak({container:root,onChange:render});
tweak.addToggle(state,'menu',{label:'Which-key menu open'});
tweak.addSelect(state,'focused',{label:'Focused window',options:[{label:'Editor',value:'editor'},{label:'Lisp REPL',value:'repl'}]});
tweak.addToggle(state,'matchedApps',{label:'Coordinate application backgrounds'});
}
})();
</script>
</div>