Keep design mockups local

This commit is contained in:
2026-10-02 16:20:53 -05:00
parent ccd691edd0
commit 731922f9c7
9 changed files with 3 additions and 1699 deletions

3
.gitignore vendored
View File

@@ -5,3 +5,6 @@
*.x86f *.x86f
*.log *.log
/.cache/ /.cache/
# Local design mockups retained for reference.
/work/*.html

View File

@@ -1,104 +0,0 @@
<div id="lime-activity-workflow-proposal">
<style>
#lime-activity-workflow-proposal .monitor-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
gap: 16px;
margin: 16px 0 8px;
}
#lime-activity-workflow-proposal .monitor { min-width: 0; }
#lime-activity-workflow-proposal svg { display: block; width: 100%; }
#lime-activity-workflow-proposal svg text { fill: var(--foreground); }
#lime-activity-workflow-proposal .monitor-outline {
fill: none;
stroke: var(--border);
}
#lime-activity-workflow-proposal .window {
fill: color-mix(in srgb, var(--viz-series-1) 12%, transparent);
stroke: var(--border);
}
</style>
<div class="viz-controls">
<label class="form-label">Proposed behavior · scenario
<select class="form-select" data-scenario>
<optgroup label="Everyday workflow">
<option value="0">1 · Start: Lime left, Chill right</option>
<option value="1">2 · Open Lime on the right too</option>
<option value="2">3 · Open Docs on the right</option>
<option value="3">4 · Switch right to Chill</option>
<option value="4">5 · Switch left to Work too</option>
<option value="5">6 · Return to Lime on the right</option>
<option value="6">7 · Return to Lime on the left too</option>
<option value="7">8 · Move Docs to the left</option>
<option value="8">9 · Right: away, then back to Lime</option>
</optgroup>
<optgroup label="A separate edge case to decide">
<option value="9">First visit on right; Lime inactive</option>
</optgroup>
</select>
</label>
</div>
<div class="monitor-grid">
<div class="monitor" data-head="left"></div>
<div class="monitor" data-head="right"></div>
</div>
<div class="text-small" data-detail role="status" aria-live="polite"></div>
</div>
<script>
(() => {
const root = document.getElementById('lime-activity-workflow-proposal');
const choose = root.querySelector('[data-scenario]');
const detail = root.querySelector('[data-detail]');
const panels = ['left', 'right'].map(head => root.querySelector(`[data-head="${head}"]`));
const steps = [
{left:['Lime',['Editor']], right:['Chill',['Chat']], detail:'Lime has only been used on the left so far.'},
{left:['Lime',['Editor']], right:['Lime',[]], detail:'Same activity, additional space. Editor stays on the left.'},
{left:['Lime',['Editor']], right:['Lime',['Docs']], detail:'Both windows belong to Lime; each has one current location.'},
{left:['Lime',['Editor']], right:['Chill',['Chat']], detail:'Docs stays open but is not displayed; its right-hand placement is remembered.'},
{left:['Work',['Work browser']], right:['Chill',['Chat']], detail:'Lime is not displayed. Editor and Docs stay open with their positions remembered.'},
{left:['Work',['Work browser']], right:['Lime',['Docs']], detail:'Docs returns on the right. Editor remains out of sight on the left.'},
{left:['Lime',['Editor']], right:['Lime',['Docs']], detail:'Both halves of the live arrangement are back. No saved View was needed.'},
{left:['Lime',['Editor','Docs']], right:['Lime',[]], detail:'The move updates Lime’s live arrangement: Docs no longer belongs in the right-hand layout.'},
{left:['Lime',['Editor','Docs']], right:['Lime',[]], detail:'No reclaim attempt or conflict dialog. The right-hand layout is still empty.'},
{left:['Work',['Work browser']], right:['Lime',[]], detail:'Edge case: Lime’s Editor is remembered on the left, not automatically brought to the right.'}
];
const ns = 'http://www.w3.org/2000/svg';
function element(tag, attributes, text) {
const node = document.createElementNS(ns, tag);
for (const [key,value] of Object.entries(attributes || {})) node.setAttribute(key, value);
if (text !== undefined) node.textContent = text;
return node;
}
function drawMonitor(panel, side, state) {
const width = Math.max(1, panel.getBoundingClientRect().width);
const height = 204;
const svg = element('svg', {viewBox:`0 0 ${width} ${height}`, height,
role:'img', 'aria-label':`${side} monitor showing ${state[0]}: ${state[1].join(' and ') || 'empty'}`});
svg.append(element('rect',{x:1,y:1,width:width-2,height:height-2,rx:4,class:'monitor-outline'}));
svg.append(element('text',{x:12,y:26,'font-weight':500},`${side} · ${state[0]}`));
svg.append(element('line',{x1:1,y1:39,x2:width-1,y2:39,stroke:'var(--border)'}));
const windows = state[1];
if (!windows.length) {
svg.append(element('text',{x:width/2,y:122,'text-anchor':'middle'},'Empty live arrangement'));
} else {
const gap = 8;
const tileWidth = (width-24-gap*(windows.length-1))/windows.length;
windows.forEach((name,index) => {
const x = 12+index*(tileWidth+gap);
svg.append(element('rect',{x,y:51,width:tileWidth,height:140,rx:2,class:'window'}));
svg.append(element('text',{x:x+tileWidth/2,y:126,'text-anchor':'middle'},name));
});
}
panel.replaceChildren(svg);
}
function draw() {
const step = steps[Number(choose.value)];
drawMonitor(panels[0],'Left',step.left);
drawMonitor(panels[1],'Right',step.right);
detail.textContent = step.detail;
}
choose.addEventListener('change', draw);
new ResizeObserver(draw).observe(root);
draw();
})();
</script>

View File

@@ -1,216 +0,0 @@
<div id="lime-arrange-concept" aria-label="Lime Arrange mode concept">
<style>
#lime-arrange-concept { --lm-bg:light-dark(#eeeae0,#191919); --lm-panel:light-dark(#e2ded4,#252525); --lm-window:light-dark(#faf7ef,#151719); --lm-text:light-dark(#302d28,#d8c9ad); --lm-muted:light-dark(#625e54,#a69e8e); --lm-edge:light-dark(#bcb6a8,#444444); --lm-gold:light-dark(#795323,#c49a62); --lm-sage:light-dark(#486347,#9cae89); color:var(--lm-text); font-family:ui-monospace,monospace; }
#lime-arrange-concept * { box-sizing:border-box; }
#lime-arrange-concept .shell { background:var(--lm-bg); border:1px solid var(--lm-edge); }
#lime-arrange-concept .bar { display:flex; flex-wrap:wrap; align-items:center; gap:.7em; padding:.5em .8em; border-bottom:1px solid var(--lm-edge); }
#lime-arrange-concept .bar .activity { color:var(--lm-gold); }
#lime-arrange-concept .mode { margin-left:auto; color:var(--lm-sage); }
#lime-arrange-concept .guide { padding:.7em .8em; background:var(--lm-panel); border-bottom:1px solid var(--lm-edge); }
#lime-arrange-concept .prompt { margin-bottom:.5em; min-height:1.5em; }
#lime-arrange-concept .actions { display:flex; gap:.4em .6em; flex-wrap:wrap; }
#lime-arrange-concept button { font:inherit; color:var(--lm-text); background:var(--lm-bg); border:1px solid var(--lm-edge); border-radius:2px; padding:.3em .6em; cursor:pointer; }
#lime-arrange-concept button:hover { border-color:var(--lm-gold); }
#lime-arrange-concept button[aria-pressed="true"] { color:var(--lm-sage); border-color:var(--lm-sage); }
#lime-arrange-concept button:disabled { cursor:default; opacity:.5; }
#lime-arrange-concept kbd { color:var(--lm-gold); font:inherit; }
#lime-arrange-concept .desk { height:360px; position:relative; margin:7px; }
#lime-arrange-concept .tile { position:absolute; display:flex; flex-direction:column; align-items:stretch; text-align:left; overflow:hidden; background:var(--lm-window); padding:.7em; transition:left .16s,top .16s,width .16s,height .16s; }
#lime-arrange-concept .tile.selected { border:2px solid var(--lm-gold); }
#lime-arrange-concept .tile:disabled { opacity:1; }
#lime-arrange-concept .tile .caption { display:flex; gap:.6em; align-items:center; white-space:nowrap; overflow:hidden; }
#lime-arrange-concept .tile .title { overflow:hidden; text-overflow:ellipsis; }
#lime-arrange-concept .tile .hint { display:inline-flex; justify-content:center; min-width:1.7em; padding:.1em .3em; background:var(--lm-panel); color:var(--lm-gold); }
#lime-arrange-concept .content { margin-top:1.3em; color:var(--lm-muted); overflow:hidden; white-space:pre; }
#lime-arrange-concept .ghost { position:absolute; border:2px dashed var(--lm-sage); background:color-mix(in srgb,var(--lm-sage) 12%,transparent); pointer-events:none; display:flex; justify-content:center; align-items:center; padding:.6em; text-align:center; color:var(--lm-sage); }
#lime-arrange-concept .handle { position:absolute; z-index:3; padding:0; border:0; background:transparent; touch-action:none; display:flex; align-items:center; justify-content:center; }
#lime-arrange-concept .handle::before { content:""; position:absolute; background:var(--lm-edge); }
#lime-arrange-concept .handle.x { width:24px; transform:translateX(-50%); cursor:ew-resize; }
#lime-arrange-concept .handle.y { height:24px; transform:translateY(-50%); cursor:ns-resize; }
#lime-arrange-concept .handle.x::before { width:2px; height:100%; }
#lime-arrange-concept .handle.y::before { height:2px; width:100%; }
#lime-arrange-concept .handle[aria-pressed="true"]::before { background:var(--lm-sage); }
#lime-arrange-concept .handle span { position:relative; background:var(--lm-panel); border:1px solid var(--lm-sage); padding:.2em .5em; color:var(--lm-sage); }
#lime-arrange-concept .footer { padding:.45em .8em; border-top:1px solid var(--lm-edge); color:var(--lm-muted); overflow-wrap:anywhere; }
@media(max-width:480px) { #lime-arrange-concept .desk { height:400px; } #lime-arrange-concept .content { display:none; } #lime-arrange-concept .tile { padding:.5em; } }
@media(pointer:coarse) { #lime-arrange-concept .actions button { min-height:44px; } #lime-arrange-concept .handle.x { width:44px; } #lime-arrange-concept .handle.y { height:44px; } }
@media(prefers-reduced-motion:reduce) { #lime-arrange-concept .tile { transition:none; } }
</style>
<div class="shell">
<div class="bar"><span>Lime</span><span aria-hidden="true">│</span><span class="activity">WM:Code<span class="dirty"></span> · |M|</span><span class="mode">ARRANGE</span></div>
<div class="guide"><div class="prompt" aria-live="polite"></div><div class="actions"></div></div>
<div class="desk" aria-label="Three-window working layout"></div>
<div class="footer" aria-live="polite">Click a window or type its label. Your applications stay visible.</div>
</div>
<script>
(() => {
const root = document.getElementById('lime-arrange-concept');
const desk = root.querySelector('.desk'), actions = root.querySelector('.actions');
const prompt = root.querySelector('.prompt'), footer = root.querySelector('.footer');
const design = { labels:'numbers' };
const names = {1:'Editor',2:'Browser',3:'Terminal',4:'Next window'};
const contents = {1:'src/layout.lisp\n\n(defun arrange (windows)\n ...)',2:'Lime documentation\n\nActivities and Views',3:'~/code/lime\n\nλ sbcl',4:''};
let tree = {id:'main',axis:'x',ratio:.58,a:{client:1},b:{id:'column',axis:'y',ratio:.52,a:{client:2},b:{client:3}}};
let mode='arrange', selected=1, source=1, target=2, side='right', edge='main', armed=null;
let history=[], splitCounter=0, dragging=null, last='Click a window or type its label. Your applications stay visible.';
const clone = value => JSON.parse(JSON.stringify(value));
const hint = index => design.labels==='numbers' ? String(index) : String.fromCharCode(64+index);
const choose = key => design.labels==='numbers' ? Number(key) : key.toUpperCase().charCodeAt(0)-64;
const has = (node,id) => node.client ? node.client===id : has(node.a,id)||has(node.b,id);
function parts(node=tree,x=0,y=0,w=1,h=1,result={tiles:[],splits:[]}) {
if(node.client) result.tiles.push({id:node.client,x,y,w,h});
else {
result.splits.push({node,x,y,w,h});
if(node.axis==='x') { parts(node.a,x,y,w*node.ratio,h,result); parts(node.b,x+w*node.ratio,y,w*(1-node.ratio),h,result); }
else { parts(node.a,x,y,w,h*node.ratio,result); parts(node.b,x,y+h*node.ratio,w,h*(1-node.ratio),result); }
}
return result;
}
function remove(node,id) {
if(node.client) return node.client===id ? null : node;
const a=remove(node.a,id),b=remove(node.b,id);
return a&&b ? {...node,a,b} : a||b;
}
function replace(node,id,replacement) {
if(node.client) return node.client===id ? replacement : node;
return {...node,a:replace(node.a,id,replacement),b:replace(node.b,id,replacement)};
}
function placed() {
let next=clone(tree);
if(side==='swap') {
function swap(node) { if(node.client) node.client=node.client===source?target:node.client===target?source:node.client; else { swap(node.a);swap(node.b); } }
swap(next); return next;
}
if(source!==4) next=remove(next,source);
const first=side==='left'||side==='above';
const split={id:'edit-'+splitCounter,axis:side==='left'||side==='right'?'x':'y',ratio:.5,
a:{client:first?source:target},b:{client:first?target:source}};
return replace(next,target,split);
}
function remember() { history.push({tree:clone(tree),armed:clone(armed)}); }
function button(key,label,fn,pressed=false,disabled=false) {
const b=document.createElement('button'); b.type='button'; b.dataset.focusKey=key;
const k=document.createElement('kbd');k.textContent=key+' '; b.append(k,document.createTextNode(label));
b.disabled=disabled; if(pressed) b.setAttribute('aria-pressed','true'); b.onclick=fn;actions.append(b);return b;
}
function back() { mode=mode==='side'?(source===4?'arrange':'target'):mode==='arrange'?'closed':'arrange';render(); }
function enter() {
if(mode==='side') {
remember();
if(source===4) { armed={target,side};last='Next tiled window: '+side+' of '+names[target]+'. No empty tile is reserved.'; }
else { tree=placed();splitCounter++;selected=source;armed=null;last='Placement applied to this draft. Arrange stays open.'; }
mode='arrange';
} else if(mode==='closed') mode='arrange';
else mode=mode==='arrange'?'closed':'arrange';
render();
}
function place(newWindow=false) { source=newWindow?4:selected;target=selected;side='right';mode=newWindow?'side':'target';render(); }
function resize() { mode='resize';edge=parts().splits.filter(p=>has(p.node,selected)).at(-1)?.node.id;render(); }
function nudge(delta) { const s=parts().splits.find(p=>p.node.id===edge);if(!s)return;remember();s.node.ratio=Math.max(.2,Math.min(.8,s.node.ratio+delta));last='Divider adjusted. Keep nudging, pick another divider, or go back.';render(); }
function undo() { const old=history.pop();if(!old)return;tree=old.tree;armed=old.armed;mode='arrange';last='Last layout edit undone.';render(); }
function select(id) {
if(mode==='target') {if(id===source)return;target=id;mode='side';side='right';}
else if(mode==='resize') {const s=parts().splits[id-1];if(s)edge=s.node.id;}
else if(mode==='arrange'||mode==='closed') selected=id;
render();
}
function rect(el,p,inset=3) {
el.style.left=`calc(${p.x*100}% + ${inset}px)`;el.style.top=`calc(${p.y*100}% + ${inset}px)`;
el.style.width=`calc(${p.w*100}% - ${inset*2}px)`;el.style.height=`calc(${p.h*100}% - ${inset*2}px)`;
}
function render() {
const active=root.contains(document.activeElement), focusKey=document.activeElement?.dataset.focusKey;
actions.replaceChildren();desk.replaceChildren();
root.querySelector('.mode').textContent=mode==='closed'?'WORK':mode==='side'?'ARRANGE · PREVIEW':'ARRANGE';
root.querySelector('.dirty').textContent=history.length?'*':'';
if(mode==='closed') {prompt.textContent='Layout edits remain in the draft; the saved View is unchanged.';button('Enter','Arrange',enter);}
if(mode==='arrange') {
prompt.textContent=names[selected]+' selected · click a window or type its label';
button('P','Place',()=>place());button('R','Resize',resize);button('N','Next window',()=>place(true));
button('U','Undo',undo,false,!history.length);button('Esc','Done',back);
}
if(mode==='target') {prompt.textContent='Place '+names[source]+' relative to which window?';button('Esc','Back',back);}
if(mode==='side') {
prompt.textContent=(source===4?'Next window':names[source])+' '+(side==='swap'?'↔':side+' of')+' '+names[target];
['left','right','above','below',...(source===4?[]:['swap'])].forEach((value,i)=>button(String(i+1),value,()=>{side=value;render();},side===value));
button('Enter',source===4?'Set next-window hint':'Apply',enter);button('Esc','Back',back);
}
if(mode==='resize') {
const s=parts().splits.find(p=>p.node.id===edge);
prompt.textContent='Pick a divider · drag it or use − / + repeatedly';
button('−',s?.node.axis==='x'?'←':'↑',()=>nudge(-.04));button('+',s?.node.axis==='x'?'→':'↓',()=>nudge(.04));
button('Enter','Back to Arrange',enter);button('Esc','Back',back);
}
const displayTree=mode==='side'?placed():tree;
parts(displayTree).tiles.forEach(p=>{
const b=document.createElement('button');b.type='button';b.className='tile'+(p.id===selected?' selected':'');
b.dataset.focusKey='window-'+p.id;rect(b,p);b.setAttribute('aria-label',names[p.id]+(mode==='target'?' destination':' window'));
b.disabled=mode==='side'||mode==='resize'||(mode==='target'&&p.id===source);
const caption=document.createElement('span');caption.className='caption';
if(mode==='arrange'||mode==='target') {const h=document.createElement('span');h.className='hint';h.textContent=hint(p.id);caption.append(h);}
const title=document.createElement('span');title.className='title';title.textContent=names[p.id];caption.append(title);
const content=document.createElement('span');content.className='content';content.textContent=contents[p.id];
b.append(caption,content);b.onclick=()=>select(p.id);desk.append(b);
});
if(mode==='resize') parts().splits.forEach((p,i)=>{
const b=document.createElement('button');b.type='button';b.className='handle '+p.node.axis;b.dataset.focusKey='divider-'+i;
b.setAttribute('aria-label','Resize divider '+hint(i+1));b.setAttribute('aria-pressed',String(edge===p.node.id));
const label=document.createElement('span');label.textContent=hint(i+1);b.append(label);
if(p.node.axis==='x') {b.style.left=(p.x+p.w*p.node.ratio)*100+'%';b.style.top=p.y*100+'%';b.style.height=p.h*100+'%';}
else {b.style.top=(p.y+p.h*p.node.ratio)*100+'%';b.style.left=p.x*100+'%';b.style.width=p.w*100+'%';}
b.onclick=()=>{edge=p.node.id;render();};
b.onpointerdown=e=>{if(e.button!==0)return;e.preventDefault();edge=p.node.id;remember();dragging=p;root.setPointerCapture(e.pointerId);render();};desk.append(b);
});
if(armed&&(mode==='arrange'||mode==='closed')) {
const p={...parts().tiles.find(t=>t.id===armed.target)};
if(armed.side==='left'||armed.side==='right'){p.w/=2;if(armed.side==='right')p.x+=p.w;}
else {p.h/=2;if(armed.side==='below')p.y+=p.h;}
const ghost=document.createElement('div');ghost.className='ghost';ghost.textContent='Next window';rect(ghost,p,6);desk.append(ghost);
}
footer.textContent=mode==='side'?'Preview only · Enter applies · Escape backs out':last;
if(active) {
const match=[...root.querySelectorAll('button')].find(b=>b.dataset.focusKey===focusKey&&!b.disabled);
(match||actions.querySelector('button:not(:disabled)'))?.focus({preventScroll:true});
}
}
root.addEventListener('pointermove',e=>{
if(!dragging)return;const d=desk.getBoundingClientRect(),p=dragging;
const coordinate=p.node.axis==='x'?(e.clientX-d.left)/d.width:(e.clientY-d.top)/d.height;
p.node.ratio=Math.max(.2,Math.min(.8,(coordinate-(p.node.axis==='x'?p.x:p.y))/(p.node.axis==='x'?p.w:p.h)));
render();
});
root.addEventListener('pointerup',e=>{dragging=null;if(root.hasPointerCapture(e.pointerId))root.releasePointerCapture(e.pointerId);});
root.addEventListener('pointercancel',()=>{dragging=null;});
root.addEventListener('keydown',e=>{
// Tab/Enter retain native button navigation and activation. After a
// typed command, focus the contextual Enter action when one exists.
if(e.altKey||e.ctrlKey||e.metaKey||e.key==='Tab'||e.key==='Enter')return;
const key=e.key.toLowerCase();let fn;
if(key==='escape')fn=back;
else if(mode==='arrange'&&key==='p')fn=()=>place();
else if(mode==='arrange'&&key==='n')fn=()=>place(true);
else if(mode==='arrange'&&key==='r')fn=resize;
else if(mode==='arrange'&&key==='u')fn=undo;
else if(mode==='resize'&&(key==='-'||key==='−'))fn=()=>nudge(-.04);
else if(mode==='resize'&&(key==='+'||key==='='))fn=()=>nudge(.04);
else if(mode==='side'&&/^[1-5]$/.test(key)) {const next=['left','right','above','below','swap'][Number(key)-1];if(!(source===4&&next==='swap'))fn=()=>{side=next;render();};}
else if(key.length===1&&(mode==='arrange'||mode==='target'||mode==='resize')) {
const id=choose(key),count=mode==='resize'?parts().splits.length:3;
if(id>=1&&id<=count)fn=()=>select(id);
}
if(fn) {
e.preventDefault();
if(!e.repeat||mode==='resize') {
fn();
actions.querySelector('[data-focus-key="Enter"]')?.focus({preventScroll:true});
}
}
});
render();
if(globalThis.Tweak) {
const tweak=new Tweak({container:root,onChange:render});
tweak.addSelect(design,'labels',{label:'Target hints',options:[{label:'Numbers',value:'numbers'},{label:'Letters',value:'letters'}]});
}
})();
</script>
</div>

View File

@@ -1,163 +0,0 @@
<div id="lime-bar-design-comparison" aria-label="Lime bar design comparisons">
<style>
#lime-bar-design-comparison {
--lime-bg: light-dark(#fff7ed, #060a0c);
--lime-ink: light-dark(#35271d, #ffdbb0);
--lime-muted: light-dark(#685a4d, #a8adb0);
--lime-focus-bg: light-dark(#f0dfc8, #121f25);
--lime-idle-bg: light-dark(#f7eee1, #0b1216);
--lime-accent: light-dark(#8a470c, #ffac54);
--lime-edge: light-dark(#d5c6b5, #25343b);
font-family: ui-monospace, "DejaVu Sans Mono", monospace;
color: var(--lime-ink);
display: grid;
gap: 22px;
padding: 4px 0;
}
#lime-bar-design-comparison * { box-sizing: border-box; }
#lime-bar-design-comparison .variant { min-width: 0; }
#lime-bar-design-comparison .caption {
display: flex; gap: 8px; flex-wrap: wrap; align-items: baseline;
font-size: 12px; line-height: 1.5; margin-bottom: 7px;
color: var(--lime-muted);
}
#lime-bar-design-comparison .caption strong { font-weight: 500; color: var(--lime-ink); }
#lime-bar-design-comparison .recommended { color: var(--lime-accent); }
#lime-bar-design-comparison .bar {
position: relative; display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
height: 34px; width: 100%; min-width: 0;
background: var(--lime-bg); font-size: 12px; line-height: 1;
border: 1px solid var(--lime-edge);
}
#lime-bar-design-comparison .identity {
grid-column: 1; display: flex; align-items: center; gap: 8px;
padding: 0 10px; min-width: 0; max-width: 280px;
border-right: 1px solid var(--lime-edge);
}
#lime-bar-design-comparison .logo { font-weight: 500; flex: none; }
#lime-bar-design-comparison .context {
overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
max-width: 170px;
}
#lime-bar-design-comparison .layout { white-space: nowrap; flex: none; }
#lime-bar-design-comparison .title-region {
grid-column: 2; display: flex; align-items: center; min-width: 0;
padding: 0 10px; background: var(--lime-focus-bg);
border-left: 2px solid var(--lime-accent);
color: var(--lime-accent);
}
#lime-bar-design-comparison .title-text {
display: block; overflow: hidden; white-space: nowrap;
text-overflow: ellipsis; min-width: 0;
}
#lime-bar-design-comparison .status {
grid-column: 3; display: flex; align-items: center; gap: 8px;
padding: 0 10px; white-space: nowrap;
border-left: 1px solid var(--lime-edge);
}
#lime-bar-design-comparison .status[hidden] { display: none; }
#lime-bar-design-comparison .music { color: var(--lime-muted); }
#lime-bar-design-comparison .clock { color: var(--lime-accent); }
#lime-bar-design-comparison .center-region .title-region { justify-content: center; }
#lime-bar-design-comparison .screen-center .identity { border-right: 0; }
#lime-bar-design-comparison .screen-center .status { border-left: 0; }
#lime-bar-design-comparison .screen-center .bar { border-left: 3px solid var(--lime-accent); }
#lime-bar-design-comparison .screen-center .title-region {
position: absolute; top: 0; bottom: 0; padding: 0;
grid-column: auto; grid-row: auto;
display: flex; justify-content: center; border: 0; background: transparent;
}
#lime-bar-design-comparison[data-focused="false"] .title-region {
background: var(--lime-idle-bg); border-left-color: transparent;
color: var(--lime-muted);
}
#lime-bar-design-comparison[data-focused="false"] .screen-center .title-region { background: transparent; }
#lime-bar-design-comparison[data-focused="false"] .screen-center .bar { border-left-color: var(--lime-edge); }
#lime-bar-design-comparison .state-label { margin-left: auto; }
@media (max-width: 620px) {
#lime-bar-design-comparison .music { display: none; }
#lime-bar-design-comparison .identity { max-width: 200px; }
#lime-bar-design-comparison .context { max-width: 110px; }
}
@media (max-width: 420px) {
#lime-bar-design-comparison .volume { display: none; }
#lime-bar-design-comparison .identity { padding: 0 6px; gap: 6px; max-width: 168px; }
#lime-bar-design-comparison .context { max-width: 80px; }
#lime-bar-design-comparison .title-region, #lime-bar-design-comparison .status { padding: 0 6px; }
#lime-bar-design-comparison .state-label { margin-left: 0; }
}
</style>
<section class="variant left-region" aria-label="Sectioned bar with a left-aligned title">
<div class="caption"><strong>A · Sectioned, left-aligned</strong><span class="recommended">Recommended</span><span class="state-label">Focused head</span></div>
<div class="bar" role="img" aria-label="Menu, activity and layout at left; highlighted expanding title region; status at right">
<div class="identity"><span class="logo">Lime</span><span class="context">WM:Code*</span><span class="layout">[]=</span></div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status"><span class="music">♪ スパイダー</span><span class="volume">25%</span><span class="clock">03:01</span></div>
</div>
</section>
<section class="variant center-region" aria-label="Sectioned bar with its title centered inside the middle region">
<div class="caption"><strong>B · Sectioned, region-centered</strong><span class="state-label">Focused head</span></div>
<div class="bar" role="img" aria-label="Same three sections, with the title centered inside its highlighted section">
<div class="identity"><span class="logo">Lime</span><span class="context">WM:Code*</span><span class="layout">[]=</span></div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status"><span class="music">♪ スパイダー</span><span class="volume">25%</span><span class="clock">03:01</span></div>
</div>
</section>
<section class="variant screen-center" aria-label="Minimal bar with true head-centered title and an edge focus marker">
<div class="caption"><strong>C · Minimal, head-centered</strong><span class="state-label">Focused head</span></div>
<div class="bar" role="img" aria-label="Unsectioned bar with title anchored to the head center where space permits, and focus indicated at the left edge">
<div class="identity"><span class="logo">Lime</span><span class="context">WM:Code*</span><span class="layout">[]=</span></div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status"><span class="music">♪ スパイダー</span><span class="volume">25%</span><span class="clock">03:01</span></div>
</div>
</section>
<script>
(() => {
const root = document.getElementById('lime-bar-design-comparison');
const state = {
focused: true,
status: true,
context: 'WM:Code*',
title: 'config.lisp — VS Code'
};
function placeScreenTitle() {
const bar = root.querySelector('.screen-center .bar');
const region = bar.querySelector('.title-region');
const identity = bar.querySelector('.identity');
const status = bar.querySelector('.status');
const left = identity.offsetWidth + 8;
const right = bar.clientWidth - (status.hidden ? 8 : status.offsetWidth + 8);
const available = Math.max(0, right - left);
const text = region.querySelector('.title-text');
region.style.width = available + 'px';
const desired = Math.min(available, text.scrollWidth);
const centered = (bar.clientWidth - desired) / 2;
const clamped = Math.max(left, Math.min(centered, right - desired));
region.style.width = desired + 'px';
region.style.left = clamped + 'px';
}
function render() {
root.dataset.focused = String(state.focused);
root.querySelectorAll('.context').forEach(el => el.textContent = state.context);
root.querySelectorAll('.title-text').forEach(el => el.textContent = state.title);
root.querySelectorAll('.status').forEach(el => el.hidden = !state.status);
root.querySelectorAll('.state-label').forEach(el => el.textContent = state.focused ? 'Focused head' : 'Unfocused head');
placeScreenTitle();
}
render();
new ResizeObserver(placeScreenTitle).observe(root);
if (globalThis.Tweak) {
const tweak = new Tweak({ container: root, onChange: render });
tweak.addToggle(state, 'focused', { label: 'Head focused' });
tweak.addToggle(state, 'status', { label: 'Status visible' });
tweak.addSelect(state, 'context', { label: 'Activity and view', options: ['WM:Code*', 'Lime development:Debug*', 'Chill:Music'] });
tweak.addSelect(state, 'title', { label: 'Window title', options: ['config.lisp — VS Code', 'Terminal', 'Window management notes — Waterfox', 'Untitled window'] });
}
})();
</script>
</div>

View File

@@ -1,170 +0,0 @@
<div id="lime-bar-grouping" aria-label="Lime bar grouping and separators">
<style>
#lime-bar-grouping {
--lime-bg: light-dark(#fff7ed, #060a0c);
--lime-ink: light-dark(#35271d, #ffdbb0);
--lime-muted: light-dark(#685a4d, #a8adb0);
--lime-focus-bg: light-dark(#f0dfc8, #121f25);
--lime-idle-bg: light-dark(#f7eee1, #0b1216);
--lime-accent: light-dark(#8a470c, #ffac54);
--lime-separator: light-dark(#946336, #b88755);
--lime-edge: light-dark(#d5c6b5, #25343b);
--lime-divider: "|";
--lime-space: var(--lime-requested-space, 9px);
font-family: ui-monospace, "DejaVu Sans Mono", monospace;
color: var(--lime-ink);
display: grid;
gap: 20px;
padding: 4px 0;
}
#lime-bar-grouping * { box-sizing: border-box; }
#lime-bar-grouping .variant { min-width: 0; }
#lime-bar-grouping .caption {
font-size: 12px; line-height: 1.5;
color: var(--lime-muted); margin-bottom: 7px;
}
#lime-bar-grouping .bar {
display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
min-width: 0; width: 100%; height: 34px;
background: var(--lime-bg); border: 1px solid var(--lime-edge);
font-size: 12px; line-height: 1;
}
#lime-bar-grouping .identity {
display: flex; align-items: center;
min-width: 0; padding: 0 var(--lime-space);
}
#lime-bar-grouping .logo { font-weight: 500; flex: none; }
#lime-bar-grouping .context {
display: flex; align-items: baseline; min-width: 0;
max-width: 160px; white-space: nowrap;
}
#lime-bar-grouping .activity {
color: var(--lime-muted); overflow: hidden; text-overflow: ellipsis;
min-width: 0; flex: 0 1 auto;
}
#lime-bar-grouping .view { flex: none; }
#lime-bar-grouping .dirty { color: var(--lime-accent); flex: none; }
#lime-bar-grouping .layout { white-space: nowrap; flex: none; }
#lime-bar-grouping .divider {
flex: none; margin: 0 var(--lime-space); color: var(--lime-separator);
}
#lime-bar-grouping .divider::before { content: var(--lime-divider); }
#lime-bar-grouping .title-region {
display: flex; align-items: center; min-width: 0; padding: 0 10px;
border-left: 2px solid var(--lime-accent);
background: var(--lime-focus-bg); color: var(--lime-accent);
}
#lime-bar-grouping .title-text {
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
min-width: 0;
}
#lime-bar-grouping .inactive .title-region {
border-left-color: transparent; background: var(--lime-idle-bg);
color: var(--lime-muted);
}
#lime-bar-grouping .status {
display: flex; align-items: center; min-width: 0;
padding: 0 var(--lime-space);
}
#lime-bar-grouping .status[hidden] { display: none; }
#lime-bar-grouping .status-item {
display: flex; align-items: center; white-space: nowrap; min-width: 0;
}
#lime-bar-grouping .status-icon {
display: inline-flex; align-items: center; margin-right: 5px;
color: var(--lime-muted); flex: none;
}
#lime-bar-grouping .status-icon svg { width: 13px; height: 13px; stroke-width: 1.8; }
#lime-bar-grouping .music-value {
overflow: hidden; text-overflow: ellipsis; max-width: 100px;
}
#lime-bar-grouping .clock-value { font-variant-numeric: tabular-nums; }
#lime-bar-grouping[data-icons="false"] .status-icon { display: none; }
@media (max-width: 640px) {
#lime-bar-grouping .music, #lime-bar-grouping .music-divider { display: none; }
#lime-bar-grouping .context { max-width: 120px; }
}
@media (max-width: 440px) {
#lime-bar-grouping { --lime-space: min(var(--lime-requested-space, 9px), 5px); }
#lime-bar-grouping .volume, #lime-bar-grouping .volume-divider { display: none; }
#lime-bar-grouping .context { max-width: 90px; }
#lime-bar-grouping .title-region { padding: 0 6px; }
}
</style>
<section class="variant" aria-label="Refined bar on the focused head">
<div class="caption">A · Refined grouping · Focused head</div>
<div class="bar" role="img" aria-label="Lime menu, WM activity and Code view, master-stack layout, focused title, then music, volume and time separated by contrasting pipes">
<div class="identity">
<span class="logo">Lime</span><span class="divider" aria-hidden="true"></span>
<span class="context"><span class="activity">WM</span><span class="view">:Code</span><span class="dirty">*</span></span>
<span class="divider" aria-hidden="true"></span><span class="layout">[]=</span>
</div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status">
<span class="status-item music" aria-label="Playing: スパイダー"><span class="status-icon" aria-hidden="true"><i data-lucide="music-2"></i></span><span class="music-value">スパイダー</span></span>
<span class="divider music-divider" aria-hidden="true"></span>
<span class="status-item volume" aria-label="Volume: 25 percent"><span class="status-icon" aria-hidden="true"><i data-lucide="volume-2"></i></span>25%</span>
<span class="divider volume-divider" aria-hidden="true"></span>
<span class="status-item clock" aria-label="Time: 03:01"><span class="status-icon" aria-hidden="true"><i data-lucide="clock-3"></i></span><span class="clock-value">03:01</span></span>
</div>
</div>
</section>
<section class="variant inactive" aria-label="Refined bar on an unfocused head">
<div class="caption">Same design · Unfocused head</div>
<div class="bar" role="img" aria-label="Same separators and content, with a quieter title section on an unfocused head">
<div class="identity">
<span class="logo">Lime</span><span class="divider" aria-hidden="true"></span>
<span class="context"><span class="activity">WM</span><span class="view">:Code</span><span class="dirty">*</span></span>
<span class="divider" aria-hidden="true"></span><span class="layout">[]=</span>
</div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status">
<span class="status-item music" aria-label="Playing: スパイダー"><span class="status-icon" aria-hidden="true"><i data-lucide="music-2"></i></span><span class="music-value">スパイダー</span></span>
<span class="divider music-divider" aria-hidden="true"></span>
<span class="status-item volume" aria-label="Volume: 25 percent"><span class="status-icon" aria-hidden="true"><i data-lucide="volume-2"></i></span>25%</span>
<span class="divider volume-divider" aria-hidden="true"></span>
<span class="status-item clock" aria-label="Time: 03:01"><span class="status-icon" aria-hidden="true"><i data-lucide="clock-3"></i></span><span class="clock-value">03:01</span></span>
</div>
</div>
</section>
<script>
(() => {
const root = document.getElementById('lime-bar-grouping');
const state = {
separator: '|', spacing: 9, icons: true, status: true,
context: 'WM:Code*', title: 'config.lisp — VS Code'
};
function render() {
root.style.setProperty('--lime-divider', JSON.stringify(state.separator));
root.style.setProperty('--lime-requested-space', state.spacing + 'px');
root.dataset.icons = String(state.icons);
const colon = state.context.indexOf(':');
const dirty = state.context.endsWith('*');
root.querySelectorAll('.activity').forEach(el => el.textContent = state.context.slice(0, colon));
root.querySelectorAll('.view').forEach(el => el.textContent = state.context.slice(colon, dirty ? -1 : undefined));
root.querySelectorAll('.dirty').forEach(el => el.textContent = dirty ? '*' : '');
root.querySelectorAll('.title-text').forEach(el => el.textContent = state.title);
root.querySelectorAll('.status').forEach(el => el.hidden = !state.status);
root.querySelectorAll('.bar').forEach(el => {
const focus = el.closest('.inactive') ? 'Unfocused head' : 'Focused head';
el.setAttribute('aria-label', focus + ': Lime menu; ' + state.context + '; master-stack layout; ' + state.title + (state.status ? '; music, volume and time' : '') + '.');
});
}
render();
if (globalThis.Tweak) {
const tweak = new Tweak({ container: root, onChange: render });
tweak.addSelect(state, 'separator', { label: 'Separator', options: [
{ label: 'Pipe |', value: '|' }, { label: 'Dot ·', value: '·' }
] });
tweak.addSlider(state, 'spacing', { label: 'Separator spacing', min: 5, max: 12, unit: 'px' });
tweak.addToggle(state, 'icons', { label: 'Status icons' });
tweak.addToggle(state, 'status', { label: 'Status visible' });
tweak.addSelect(state, 'context', { label: 'Activity and view', options: ['WM:Code*', 'Lime development:Debug*', 'Chill:Music'] });
tweak.addSelect(state, 'title', { label: 'Window title', options: ['config.lisp — VS Code', 'Terminal', 'Window management notes — Waterfox', 'Untitled window'] });
}
})();
</script>
</div>

View File

@@ -1,170 +0,0 @@
<div id="lime-bar-title-edges" aria-label="Lime title section edge comparison">
<style>
#lime-bar-title-edges {
--lime-bg: light-dark(#fff7ed, #060a0c);
--lime-ink: light-dark(#35271d, #ffdbb0);
--lime-muted: light-dark(#685a4d, #a8adb0);
--lime-focus-bg: light-dark(#f0dfc8, #121f25);
--lime-idle-bg: light-dark(#f7eee1, #0b1216);
--lime-accent: light-dark(#8a470c, #ffac54);
--lime-separator: light-dark(#946336, #b88755);
--lime-edge: light-dark(#d5c6b5, #25343b);
--lime-title-edge: light-dark(#bca68d, #40535d);
--lime-space: 9px;
font-family: ui-monospace, "DejaVu Sans Mono", monospace;
color: var(--lime-ink); display: grid; gap: 20px; padding: 4px 0;
}
#lime-bar-title-edges * { box-sizing: border-box; }
#lime-bar-title-edges .focus-toggle {
display: flex; align-items: center; gap: 8px; justify-self: start;
font-size: 14px; line-height: 1.5; cursor: pointer;
}
#lime-bar-title-edges .focus-toggle input { accent-color: var(--lime-accent); }
@media (pointer: coarse) {
#lime-bar-title-edges .focus-toggle { min-height: 44px; }
}
#lime-bar-title-edges .variant { min-width: 0; }
#lime-bar-title-edges .caption {
display: flex; gap: 8px; flex-wrap: wrap; align-items: baseline;
font-size: 12px; line-height: 1.5; color: var(--lime-muted); margin-bottom: 7px;
}
#lime-bar-title-edges .recommended { color: var(--lime-accent); }
#lime-bar-title-edges .bar {
display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
min-width: 0; width: 100%; height: 34px;
background: var(--lime-bg); border: 1px solid var(--lime-edge);
font-size: 12px; line-height: 1;
}
#lime-bar-title-edges .identity, #lime-bar-title-edges .status {
display: flex; align-items: center; min-width: 0; padding: 0 var(--lime-space);
}
#lime-bar-title-edges .logo { font-weight: 500; flex: none; }
#lime-bar-title-edges .context { white-space: nowrap; }
#lime-bar-title-edges .activity { color: var(--lime-muted); }
#lime-bar-title-edges .dirty { color: var(--lime-accent); }
#lime-bar-title-edges .layout { white-space: nowrap; flex: none; }
#lime-bar-title-edges .divider {
flex: none; margin: 0 var(--lime-space); color: var(--lime-separator);
}
#lime-bar-title-edges .divider::before { content: "|"; }
#lime-bar-title-edges .title-region {
display: flex; align-items: center; min-width: 0; padding: 0 12px;
background: var(--lime-focus-bg); color: var(--lime-accent);
}
#lime-bar-title-edges .leading .title-region { box-shadow: inset 2px 0 var(--lime-accent); }
#lime-bar-title-edges .paired .title-region {
box-shadow: inset 1px 0 var(--lime-title-edge), inset -1px 0 var(--lime-title-edge);
}
#lime-bar-title-edges .title-text {
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
#lime-bar-title-edges:has(.focus-toggle input:not(:checked)) .title-region {
background: var(--lime-idle-bg); color: var(--lime-muted);
}
#lime-bar-title-edges:has(.focus-toggle input:not(:checked)) .leading .title-region { box-shadow: none; }
#lime-bar-title-edges .status[hidden] { display: none; }
#lime-bar-title-edges .status-item {
display: flex; align-items: center; white-space: nowrap; min-width: 0;
}
#lime-bar-title-edges .status-icon {
display: inline-flex; align-items: center; margin-right: 5px;
color: var(--lime-muted); flex: none;
}
#lime-bar-title-edges .status-icon svg { width: 13px; height: 13px; stroke-width: 1.8; }
#lime-bar-title-edges .music-value { overflow: hidden; text-overflow: ellipsis; max-width: 100px; }
#lime-bar-title-edges .clock-value { font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
#lime-bar-title-edges .music, #lime-bar-title-edges .music-divider { display: none; }
}
@media (max-width: 440px) {
#lime-bar-title-edges { --lime-space: 5px; }
#lime-bar-title-edges .volume, #lime-bar-title-edges .volume-divider { display: none; }
#lime-bar-title-edges .title-region { padding: 0 7px; }
}
</style>
<label class="focus-toggle">
<input type="checkbox" checked>
<span>Head focused</span>
</label>
<section class="variant plain" aria-label="Background only, no title stripes">
<div class="caption"><span>1 · Background only</span><span class="recommended">My pick</span></div>
<div class="bar" role="img" aria-label="Focused head with a tinted title section and no edge stripes">
<div class="identity">
<span class="logo">Lime</span><span class="divider" aria-hidden="true"></span>
<span class="context"><span class="activity">WM</span>:Code<span class="dirty">*</span></span>
<span class="divider" aria-hidden="true"></span><span class="layout">[]=</span>
</div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status">
<span class="status-item music"><span class="status-icon" aria-hidden="true"><i data-lucide="music-2"></i></span><span class="music-value">スパイダー</span></span>
<span class="divider music-divider" aria-hidden="true"></span>
<span class="status-item volume"><span class="status-icon" aria-hidden="true"><i data-lucide="volume-2"></i></span>25%</span>
<span class="divider volume-divider" aria-hidden="true"></span>
<span class="status-item clock"><span class="status-icon" aria-hidden="true"><i data-lucide="clock-3"></i></span><span class="clock-value">03:01</span></span>
</div>
</div>
</section>
<section class="variant leading" aria-label="Current design with a left focus stripe">
<div class="caption">2 · Left accent · Current treatment</div>
<div class="bar" role="img" aria-label="Focused head with a two-pixel accent stripe at the start of the title section">
<div class="identity">
<span class="logo">Lime</span><span class="divider" aria-hidden="true"></span>
<span class="context"><span class="activity">WM</span>:Code<span class="dirty">*</span></span>
<span class="divider" aria-hidden="true"></span><span class="layout">[]=</span>
</div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status">
<span class="status-item music"><span class="status-icon" aria-hidden="true"><i data-lucide="music-2"></i></span><span class="music-value">スパイダー</span></span>
<span class="divider music-divider" aria-hidden="true"></span>
<span class="status-item volume"><span class="status-icon" aria-hidden="true"><i data-lucide="volume-2"></i></span>25%</span>
<span class="divider volume-divider" aria-hidden="true"></span>
<span class="status-item clock"><span class="status-icon" aria-hidden="true"><i data-lucide="clock-3"></i></span><span class="clock-value">03:01</span></span>
</div>
</div>
</section>
<section class="variant paired" aria-label="Quiet one-pixel edges on both sides">
<div class="caption">3 · Two subtle edges</div>
<div class="bar" role="img" aria-label="Focused head with muted one-pixel separators on both sides of the title section">
<div class="identity">
<span class="logo">Lime</span><span class="divider" aria-hidden="true"></span>
<span class="context"><span class="activity">WM</span>:Code<span class="dirty">*</span></span>
<span class="divider" aria-hidden="true"></span><span class="layout">[]=</span>
</div>
<div class="title-region"><span class="title-text">config.lisp — VS Code</span></div>
<div class="status">
<span class="status-item music"><span class="status-icon" aria-hidden="true"><i data-lucide="music-2"></i></span><span class="music-value">スパイダー</span></span>
<span class="divider music-divider" aria-hidden="true"></span>
<span class="status-item volume"><span class="status-icon" aria-hidden="true"><i data-lucide="volume-2"></i></span>25%</span>
<span class="divider volume-divider" aria-hidden="true"></span>
<span class="status-item clock"><span class="status-icon" aria-hidden="true"><i data-lucide="clock-3"></i></span><span class="clock-value">03:01</span></span>
</div>
</div>
</section>
<script>
(() => {
const root = document.getElementById('lime-bar-title-edges');
const focusToggle = root.querySelector('.focus-toggle input');
const state = { status: true, title: 'config.lisp — VS Code' };
function render() {
root.querySelectorAll('.title-text').forEach(el => el.textContent = state.title);
root.querySelectorAll('.status').forEach(el => el.hidden = !state.status);
root.querySelectorAll('.bar').forEach(el => {
const variant = el.closest('section').getAttribute('aria-label');
el.setAttribute('aria-label', variant + '. ' + (focusToggle.checked ? 'Focused' : 'Unfocused') + ' head; Lime menu; WM:Code, unsaved changes; master-stack layout; ' + state.title + (state.status ? '; music: スパイダー, volume: 25 percent, time: 03:01.' : '.'));
});
}
focusToggle.addEventListener('change', render);
render();
if (globalThis.Tweak) {
const tweak = new Tweak({ container: root, onChange: render });
tweak.addToggle(state, 'status', { label: 'Status visible' });
tweak.addSelect(state, 'title', { label: 'Window title', options: ['config.lisp — VS Code', 'Terminal', 'Window management notes — Waterfox'] });
}
})();
</script>
</div>

View File

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

View File

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

View File

@@ -1,191 +0,0 @@
<div id="lime-ui-sketch" aria-label="Lime command surface sketch">
<style>
#lime-ui-sketch {
color: light-dark(#1d232b, #e8edf3);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
#lime-ui-sketch * { box-sizing: border-box; }
#lime-ui-sketch .lime-ui-window {
position: relative;
min-height: 760px;
overflow: hidden;
background: light-dark(#d9dfe7, #202733);
border: 1px solid light-dark(#aab4c1, #3d4859);
border-radius: 8px;
}
#lime-ui-sketch .lime-ui-status {
display: flex;
justify-content: space-between;
padding: 7px 11px;
color: light-dark(#f8fafc, #edf2f7);
background: light-dark(#39475a, #111827);
font-size: 12px;
}
#lime-ui-sketch .lime-ui-desktop {
display: grid;
grid-template-columns: 1.1fr .9fr;
gap: 10px;
min-height: 729px;
padding: 12px;
background: light-dark(#c9d2de, #1a202b);
}
#lime-ui-sketch .lime-ui-client {
min-height: 705px;
padding: 10px;
color: light-dark(#253040, #cbd5e1);
background: light-dark(#f5f7fa, #2b3443);
border: 1px solid light-dark(#b6c1ce, #435065);
border-radius: 4px;
font-size: 12px;
}
#lime-ui-sketch .lime-ui-client strong { color: light-dark(#111827, #f8fafc); font-weight: 500; }
#lime-ui-sketch .lime-ui-code { margin-top: 20px; color: light-dark(#516172, #9aacbe); line-height: 1.7; }
#lime-ui-sketch .lime-ui-music { display: grid; align-content: end; gap: 6px; }
#lime-ui-sketch .lime-ui-meter { height: 5px; background: light-dark(#c6d0db, #111827); }
#lime-ui-sketch .lime-ui-meter span { display: block; width: 61%; height: 100%; background: light-dark(#4b7ea8, #71b7f0); }
#lime-ui-sketch .lime-ui-palette {
position: absolute;
top: 43px;
right: 24px;
left: 24px;
margin: 0;
padding: 10px;
color: light-dark(#16202c, #e6edf5);
background: light-dark(#f9fbfd, #161d27);
border: 1px solid light-dark(#8c9bab, #526174);
border-radius: 6px;
box-shadow: 0 12px 28px light-dark(rgb(31 41 55 / .24), rgb(0 0 0 / .45));
}
#lime-ui-sketch .lime-ui-tabs { display: flex; gap: 6px; margin-bottom: 9px; }
#lime-ui-sketch button {
color: inherit;
font: inherit;
cursor: pointer;
}
#lime-ui-sketch .lime-ui-tab {
padding: 4px 7px;
background: transparent;
border: 0;
border-bottom: 2px solid transparent;
font-size: 12px;
}
#lime-ui-sketch .lime-ui-tab[aria-selected="true"] { border-bottom-color: light-dark(#2674a8, #7ac4f7); }
#lime-ui-sketch .lime-ui-query {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
background: light-dark(#e8eef4, #242e3d);
border: 1px solid light-dark(#a7b4c1, #4d5d73);
border-radius: 3px;
}
#lime-ui-sketch .lime-ui-query mark { color: light-dark(#21628d, #8ad1ff); background: transparent; }
#lime-ui-sketch input {
width: 100%;
color: inherit;
background: transparent;
border: 0;
font: inherit;
font-size: 13px;
outline: none;
}
#lime-ui-sketch .lime-ui-list { display: grid; gap: 2px; margin-top: 8px; }
#lime-ui-sketch .lime-ui-choice {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
align-items: center;
width: 100%;
padding: 7px 8px;
text-align: left;
background: transparent;
border: 0;
border-radius: 3px;
font-size: 12px;
}
#lime-ui-sketch .lime-ui-choice:hover,
#lime-ui-sketch .lime-ui-choice:focus-visible { background: light-dark(#dceefa, #263d51); }
#lime-ui-sketch .lime-ui-choice.is-selected { background: light-dark(#d3e9fa, #1f4057); }
#lime-ui-sketch .lime-ui-hint { color: light-dark(#607286, #a4b5c8); }
#lime-ui-sketch .lime-ui-subtle { color: light-dark(#607286, #a4b5c8); }
#lime-ui-sketch .lime-ui-prefix { display: none; }
#lime-ui-sketch[data-mode="prefix"] .lime-ui-direct { display: none; }
#lime-ui-sketch[data-mode="prefix"] .lime-ui-prefix { display: block; }
#lime-ui-sketch .lime-ui-footer { display: flex; justify-content: space-between; margin-top: 9px; font-size: 11px; }
@media (max-width: 480px) {
#lime-ui-sketch .lime-ui-desktop { grid-template-columns: 1fr; }
#lime-ui-sketch .lime-ui-palette { margin-left: 10px; margin-right: 10px; }
}
</style>
<div class="lime-ui-window">
<div class="lime-ui-status"><span>Lime · development / Code</span><span>left head · dirty</span></div>
<div class="lime-ui-desktop" aria-hidden="true">
<div class="lime-ui-client"><strong>emacs — lime/core.lisp</strong><div class="lime-ui-code">(defstruct activity ...)<br> (view-id ...)<br> (presentation ...)</div></div>
<div class="lime-ui-client lime-ui-music"><strong>ncmpcpp</strong><span>Moon Safari — Air</span><div class="lime-ui-meter"><span></span></div></div>
</div>
<div class="lime-ui-palette" role="dialog" aria-label="Lime command surface">
<div class="lime-ui-tabs" role="tablist" aria-label="Command interaction">
<button class="lime-ui-tab" id="lime-ui-direct-tab" type="button" role="tab" aria-controls="lime-ui-direct-panel" aria-selected="true">Direct search</button>
<button class="lime-ui-tab" id="lime-ui-prefix-tab" type="button" role="tab" aria-controls="lime-ui-prefix-panel" aria-selected="false">Key path</button>
</div>
<div class="lime-ui-direct" id="lime-ui-direct-panel" role="tabpanel" aria-labelledby="lime-ui-direct-tab">
<label class="sr-only" for="lime-ui-search">Search commands, activities, or views</label>
<div class="lime-ui-query"><mark>⌘</mark><input id="lime-ui-search" value="switch to…" aria-label="Search commands, activities, or views"></div>
<div class="lime-ui-list">
<button class="lime-ui-choice is-selected" type="button"><span>View · development / <strong>Code</strong></span><span class="lime-ui-hint">Enter</span></button>
<button class="lime-ui-choice" type="button"><span>View · development / Debug</span><span class="lime-ui-hint">Enter</span></button>
<button class="lime-ui-choice" type="button"><span>Activity · game / Play</span><span class="lime-ui-hint">Enter</span></button>
<button class="lime-ui-choice" type="button"><span>New named Activity…</span><span class="lime-ui-hint">a</span></button>
</div>
</div>
<div class="lime-ui-prefix" id="lime-ui-prefix-panel" role="tabpanel" aria-labelledby="lime-ui-prefix-tab" hidden>
<div class="lime-ui-query"><mark>Super</mark><span id="lime-ui-path">then press…</span></div>
<div class="lime-ui-list">
<button class="lime-ui-choice" type="button" data-path="Activity"><span><strong>a</strong> · Activity</span><span class="lime-ui-hint">new / switch</span></button>
<button class="lime-ui-choice" type="button" data-path="View"><span><strong>v</strong> · View</span><span class="lime-ui-hint">save / switch</span></button>
<button class="lime-ui-choice" type="button" data-path="Window"><span><strong>w</strong> · Window</span><span class="lime-ui-hint">focus / move</span></button>
<button class="lime-ui-choice" type="button" data-path="Head"><span><strong>h</strong> · Head</span><span class="lime-ui-hint">select / send</span></button>
</div>
</div>
<div class="lime-ui-footer"><span id="lime-ui-state" aria-live="polite">Type to filter · Enter to run</span><span>Esc cancel</span></div>
</div>
</div>
<script>
(() => {
const root = document.getElementById('lime-ui-sketch');
const directTab = document.getElementById('lime-ui-direct-tab');
const prefixTab = document.getElementById('lime-ui-prefix-tab');
const directPanel = document.getElementById('lime-ui-direct-panel');
const prefixPanel = document.getElementById('lime-ui-prefix-panel');
const search = document.getElementById('lime-ui-search');
const state = document.getElementById('lime-ui-state');
const path = document.getElementById('lime-ui-path');
function select(mode) {
const direct = mode === 'direct';
root.dataset.mode = mode;
directTab.setAttribute('aria-selected', String(direct));
prefixTab.setAttribute('aria-selected', String(!direct));
directPanel.hidden = !direct;
prefixPanel.hidden = direct;
if (direct) { search.focus(); state.textContent = 'Type to filter · Enter to run'; }
else { state.textContent = 'Choose a branch · Esc cancel'; }
}
directTab.addEventListener('click', () => select('direct'));
prefixTab.addEventListener('click', () => select('prefix'));
root.querySelectorAll('[data-path]').forEach(button => button.addEventListener('click', () => {
path.textContent = button.dataset.path + ' · choose command…';
state.textContent = button.dataset.path + ' menu opened';
}));
root.querySelectorAll('.lime-ui-direct .lime-ui-choice').forEach(button => button.addEventListener('click', () => {
root.querySelectorAll('.lime-ui-direct .lime-ui-choice').forEach(choice => choice.classList.remove('is-selected'));
button.classList.add('is-selected');
state.textContent = button.textContent.trim() + ' selected';
}));
})();
</script>
</div>