From d6ed1fef6573f2f470b6cd487d31f382d5504eb7 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sat, 19 Sep 2026 20:29:10 -0700 Subject: [PATCH] =?UTF-8?q?aesel:=20release=200.7.16=20=E2=80=94=20a=20res?= =?UTF-8?q?ize=20that=20keeps=20up,=20a=20picker=20you=20can=20click,=20an?= =?UTF-8?q?d=20pals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The window dragged at ~185 ms a step. Two causes, both in the shell: the waveform strip read the guest's audio buffer sixty times a second through a synchronous webview call that stalled on the busy main process, and macOS waits on the piece guest's compositor frame for every step of a resize while the AC runtime keeps it painting at 60 fps. The strip now reads the host's own mute state, backs off to eight reads a second in silence, and pauses during a drag; the guest is swapped for a snapshot of its last frame for the drag and thawed on settle; the notebook ruling, layout, prompt and donkey wait for the settle too. A step is now ~30 ms, under a plain Chrome window. The provider dropdown could not be switched by mouse: opening it focused the current option, a press on another blurred it, and the focus-out handler closed the list before the click landed. Provider and Model now share one dropdown of the same weight, the model rows name the family and version with the alias beside them, the status line says which model is running, and the AC row wears a looping pal from pals.aesthetic.computer. Menu bar reads Aesel from the bundle name; window titles stay aesel. The aesel page shows the real Studio window and lists Mac now, iPhone next. Co-Authored-By: Claude Fable 5.1 --- easel/desktop/donkey.css | 9 +++- easel/desktop/index.html | 4 +- easel/desktop/notebook-donkey.js | 3 +- easel/desktop/notebook-layout.js | 2 + easel/desktop/package-lock.json | 4 +- easel/desktop/package.json | 6 ++- easel/desktop/preview-waveform.js | 55 +++++++++++++--------- easel/desktop/provider-picker.js | 43 ++++++++++++----- easel/desktop/renderer.js | 55 ++++++++++++++++------ easel/desktop/scripts/linux-download.html | 4 +- easel/desktop/style.css | 2 + easel/desktop/transcript.js | 2 + easel/package.json | 2 +- easel/src/provider-picker.mjs | 5 +- system/public/aesel/index.html | 36 +++++++++----- system/public/aesel/screenshot-studio.png | Bin 0 -> 28792 bytes system/public/aesel/site.js | 21 ++------- system/public/aesel/style.css | 4 +- 18 files changed, 164 insertions(+), 93 deletions(-) create mode 100644 system/public/aesel/screenshot-studio.png diff --git a/easel/desktop/donkey.css b/easel/desktop/donkey.css index cbd2ab312d..9d4f4b0b78 100644 --- a/easel/desktop/donkey.css +++ b/easel/desktop/donkey.css @@ -236,8 +236,10 @@ body::before{left:10px} @media(prefers-reduced-motion:reduce){#version[data-unstable="true"]{animation:none}} #artifact-shell[data-resizing]{transition:none} +.window-resizing #artifact-shell,.window-resizing #artifact{box-shadow:none!important} .provider-mark{background:#fff;border-radius:5px;padding:2px;box-sizing:border-box} +.provider-mark.pal{background:none;padding:0;object-fit:cover} .version-departing .qr-letter-ink{position:absolute;inset:0}.version-departing img{position:absolute;left:-9px;top:-9px;max-width:none} #provider-menu .settings-versions li{display:grid;grid-template-columns:auto 1fr;gap:3px 12px} @@ -340,7 +342,12 @@ body.preview-fullscreen #preview-waveform{display:none} #provider-menu .provider-model-row{display:flex;gap:10px;align-items:start} #provider-menu .provider-model-row>.settings-field{flex:1;min-width:0} #provider-menu .provider-picker{position:relative} -#provider-menu .provider-toggle{display:flex;align-items:center;gap:6px;margin-top:5px;padding:8px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;font:15px Helvetica,sans-serif;min-height:36px} +#provider-menu .provider-toggle{display:flex;align-items:center;gap:6px;margin-top:5px;padding:8px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:6px;font:15px Helvetica,sans-serif;min-height:43px;box-sizing:border-box;width:100%;text-align:left} +#provider-menu .provider-toggle:disabled{opacity:.55} +#provider-menu .choice-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +#provider-menu .choice-detail{font-size:11px;opacity:.6;white-space:nowrap} +#provider-menu .provider-options button{min-height:43px;box-sizing:border-box;width:100%;text-align:left} +#provider-menu .provider-options .choice-detail{margin-left:auto} #provider-menu .provider-arrow{margin-left:auto} #provider-menu .provider-options{position:absolute;top:100%;left:0;min-width:100%;z-index:10;padding:4px;box-sizing:border-box;border:1px solid color-mix(in srgb,currentColor 30%,transparent);border-radius:6px;background:var(--aesel-background,#24152a);box-shadow:0 4px 14px #0006} #provider-menu .provider-options[hidden]{display:none} diff --git a/easel/desktop/index.html b/easel/desktop/index.html index 053ae97328..8aee20a68a 100644 --- a/easel/desktop/index.html +++ b/easel/desktop/index.html @@ -1,7 +1,7 @@ - -Aesel + +aesel
diff --git a/easel/desktop/notebook-donkey.js b/easel/desktop/notebook-donkey.js index b72fdad001..dc7402ca78 100644 --- a/easel/desktop/notebook-donkey.js +++ b/easel/desktop/notebook-donkey.js @@ -6,6 +6,7 @@ window.installNotebookDonkey=anchor=>{ const active=()=>loaded&&!anchor.hidden&&anchor.isConnected&&!document.hidden; function paint(){ clearTimeout(timer);timer=null;if(!active()){canvas.hidden=true;return;} + if(document.body.classList.contains('window-resizing')){timer=setTimeout(paint,160);return;} const rect=anchor.getBoundingClientRect(),sheet=document.getElementById('conversation').getBoundingClientRect(); if(rect.bottomsheet.bottom){canvas.hidden=true;return;} const size=Math.max(1,rect.height),width=Math.min(rect.width-16,innerWidth-24); @@ -30,5 +31,5 @@ window.installNotebookDonkey=anchor=>{ source.src='assets/donkey-pencil-run-v2.png'; new MutationObserver(paint).observe(anchor,{attributes:true,attributeFilter:['hidden','aria-label']}); new MutationObserver(paint).observe(document.getElementById('notebook-page'),{childList:true,subtree:true}); - document.addEventListener('scroll',paint,true);document.addEventListener('visibilitychange',paint);window.addEventListener('resize',paint);reduced.addEventListener('change',paint); + document.addEventListener('scroll',paint,true);document.addEventListener('visibilitychange',paint);window.addEventListener('aesel-resize-settled',paint);reduced.addEventListener('change',paint); }; diff --git a/easel/desktop/notebook-layout.js b/easel/desktop/notebook-layout.js index 380c41d5a7..49ee27fd7f 100644 --- a/easel/desktop/notebook-layout.js +++ b/easel/desktop/notebook-layout.js @@ -19,6 +19,7 @@ }; const layout = () => { frame = 0; + if (document.body.classList.contains("window-resizing")) return; const height = view.clientHeight; const pin = view.scrollHeight - view.scrollTop - height <= 2 || @@ -67,6 +68,7 @@ window.layoutNotebookPreview = () => { if (!frame) frame = requestAnimationFrame(layout); }; + window.addEventListener("aesel-resize-settled", window.layoutNotebookPreview); const observer = new ResizeObserver(window.layoutNotebookPreview); observer.observe(preview); observer.observe(view); diff --git a/easel/desktop/package-lock.json b/easel/desktop/package-lock.json index 31a655ae21..8e03771cc7 100644 --- a/easel/desktop/package-lock.json +++ b/easel/desktop/package-lock.json @@ -1,12 +1,12 @@ { "name": "aesel-desktop", - "version": "0.7.15", + "version": "0.7.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aesel-desktop", - "version": "0.7.15", + "version": "0.7.16", "dependencies": { "@xterm/addon-fit": "0.10.0", "@xterm/addon-webgl": "0.18.0", diff --git a/easel/desktop/package.json b/easel/desktop/package.json index 2e8f35abde..db99541b06 100644 --- a/easel/desktop/package.json +++ b/easel/desktop/package.json @@ -1,7 +1,7 @@ { "name": "aesel-desktop", "productName": "aesel", - "version": "0.7.15", + "version": "0.7.16", "private": true, "main": "main.cjs", "scripts": { @@ -84,6 +84,10 @@ "dmg" ], "category": "public.app-category.developer-tools", + "extendInfo": { + "CFBundleName": "Aesel", + "CFBundleDisplayName": "Aesel" + }, "hardenedRuntime": true, "icon": "build/icon.icns", "notarize": true, diff --git a/easel/desktop/preview-waveform.js b/easel/desktop/preview-waveform.js index 231e1209ee..c73960d3fc 100644 --- a/easel/desktop/preview-waveform.js +++ b/easel/desktop/preview-waveform.js @@ -12,6 +12,7 @@ window.installPreviewWaveform = (preview) => { document.body.append(line); let version; const align = () => { + if (document.body.classList.contains("window-resizing")) return; const inset = version?.offsetWidth ? version.offsetWidth + (parseFloat(getComputedStyle(version).right) || 0) + 12 : 80; document.documentElement.style.setProperty("--preview-audio-inset", `${inset}px`); }; @@ -21,32 +22,41 @@ window.installPreviewWaveform = (preview) => { if (version) placement.observe(version); align(); }); - window.addEventListener("resize", align); + window.addEventListener("aesel-resize-settled", align); align(); const motion = matchMedia("(prefers-reduced-motion: reduce)"); const historyMs = 4000; // Two 4/4 bars at 120 BPM, oldest at the top. + // The host owns muting (preview-audio.js), so its state answers without a + // synchronous guest call. webview.isAudioMuted() blocks this thread on the + // main process, which stalls for hundreds of milliseconds mid-resize. + const silenced = () => { const audio = window.previewAudio; return !!audio && (audio.muted || audio.volume === 0); }; + const paused = () => !ready || document.hidden || preview.hidden || silenced() || document.body.classList.contains("preview-fullscreen"); let history = []; let ready = false, timer = 0, generation = 0, previousRead = 0, peakEnvelope = 0, - frame = 0; + frame = 0, + cleared = true, + lastActiveAt = -Infinity; const clear = () => { - line.classList.remove("sounding"); - path.setAttribute("d", "M0 16H511"); - previousRead = 0; - peakEnvelope = 0; - history = []; cancelAnimationFrame(frame); frame = 0; + history = []; + previousRead = 0; + peakEnvelope = 0; + if (cleared) return; // Silence is the steady state; leave the DOM alone. + cleared = true; + line.classList.remove("sounding"); + path.setAttribute("d", "M0 16H511"); }; function draw(now) { frame = 0; - if (!ready || document.hidden || preview.hidden || preview.isAudioMuted() || document.body.classList.contains("preview-fullscreen")) { clear(); return; } + if (paused()) { clear(); return; } history = history.filter(point => now - point.at < historyMs); if (!history.some(point => point.active)) { clear(); return; } - line.classList.add("sounding"); + if (cleared) { cleared = false; line.classList.add("sounding"); } // Envelope slices retain transients instead of aliasing an entire audio // cycle down to a pixel. Their timestamps set the upward scroll speed. const position = point => (511 * (1 - (now - point.at) / historyMs)).toFixed(2); @@ -59,19 +69,18 @@ window.installPreviewWaveform = (preview) => { const started = performance.now(); let interval = 200; try { - if ( - !ready || - document.hidden || - document.body.classList.contains("preview-fullscreen") || - preview.hidden || - preview.isAudioMuted() - ) { + if (paused()) { clear(); return; } - // Keep listening during silence: the bird's whole first note is 75 ms. - // Only one guest read is in flight, and hidden/muted previews back off. - interval = motion.matches ? 33 : 16; + // A live window resize already saturates the main process, which every + // guest read crosses. Let the drag finish first. + if (document.body.classList.contains("window-resizing")) { interval = 100; return; } + // Read at frame rate while sound plays and for a beat after it stops, so + // the strip keeps its tail. Silence backs off to eight reads a second: + // the main-process round trip is the app's whole idle cost otherwise. + const listening = performance.now() - lastActiveAt < 1500; + interval = listening ? (motion.matches ? 33 : 16) : 125; const samples = await preview.executeJavaScript( "window.AC?.readOutputWaveform?.(512) || []", ); @@ -86,6 +95,7 @@ window.installPreviewWaveform = (preview) => { const peak = centered.reduce((max, v) => Math.max(max, Math.abs(v)), 0); const active = peak > 0.0005; const now = performance.now(); + if (active) { if (!listening) interval = motion.matches ? 33 : 16; lastActiveAt = now; } // Normalize the display only. Follow peaks immediately, release gain // gently, and cap amplification so near-silence doesn't fill the strip. peakEnvelope = Math.max(peak, peakEnvelope * Math.exp(-(now - previousRead) / 180)); @@ -93,9 +103,8 @@ window.installPreviewWaveform = (preview) => { const gain = Math.min(32, 0.85 / (peakEnvelope || 1)); if (motion.matches) { cancelAnimationFrame(frame); frame = 0; history = []; - line.classList.toggle("sounding", active); - path.setAttribute("d", "M0 16H511"); - } else { + if (active === cleared) { cleared = !active; line.classList.toggle("sounding", active); path.setAttribute("d", "M0 16H511"); } + } else if (active || !cleared) { // Normalization changes only each new slice, never the recorded past. history.push({at: now, active, low: active ? Math.min(...centered) * gain : 0, @@ -131,5 +140,5 @@ window.installPreviewWaveform = (preview) => { document.addEventListener("visibilitychange", () => { if (document.hidden) clear(); }); - window.addEventListener("beforeunload", () => { stop(); placement.disconnect(); window.removeEventListener("resize", align); }, { once: true }); + window.addEventListener("beforeunload", () => { stop(); placement.disconnect(); window.removeEventListener("aesel-resize-settled", align); }, { once: true }); }; diff --git a/easel/desktop/provider-picker.js b/easel/desktop/provider-picker.js index 5d675839d1..6cbc848369 100644 --- a/easel/desktop/provider-picker.js +++ b/easel/desktop/provider-picker.js @@ -1,17 +1,38 @@ -window.createProviderPicker=({backend,busy,onSelect})=>{ - const root=document.createElement('div');root.className='settings-field provider-picker';root.append('Provider'); - const toggle=document.createElement('button');toggle.type='button';toggle.className='provider-toggle';toggle.setAttribute('aria-label','Provider');toggle.setAttribute('aria-haspopup','listbox');toggle.setAttribute('aria-expanded','false');toggle.setAttribute('aria-controls','provider-options');toggle.disabled=busy; - const list=document.createElement('div');list.id='provider-options';list.className='provider-options';list.setAttribute('role','listbox');list.setAttribute('aria-label','Provider');list.hidden=true; - const providers=['AC','Claude','Codex'],ids=['ac','claude','codex']; - const icon=id=>{const img=new Image();img.src=`assets/provider-${id}.svg`;img.alt='';img.className='provider-mark';return img;}; - const current=Math.max(0,ids.indexOf(backend));toggle.append(icon(ids[current]),providers[current]); +// One dropdown for both the provider and the model rows, so they carry the +// same weight. Options may bring a mark: a local asset, or a remote image with +// a local fallback. +window.createChoicePicker=({label,options,current=0,busy=false,onSelect})=>{ + const root=document.createElement('div');root.className='settings-field choice-picker';root.append(label); + const listId=`choice-${label.toLowerCase().replace(/[^a-z0-9]+/g,'-')}-options`; + const toggle=document.createElement('button');toggle.type='button';toggle.className='provider-toggle';toggle.setAttribute('aria-label',label);toggle.setAttribute('aria-haspopup','listbox');toggle.setAttribute('aria-expanded','false');toggle.setAttribute('aria-controls',listId);toggle.disabled=busy||options.length<2; + const list=document.createElement('div');list.id=listId;list.className='provider-options';list.setAttribute('role','listbox');list.setAttribute('aria-label',label);list.hidden=true; + const mark=option=>{if(!option.icon)return null;const img=new Image();img.alt='';img.className='provider-mark'+(option.markClass?' '+option.markClass:'');if(option.fallback)img.addEventListener('error',()=>{if(img.src!==option.fallback)img.src=option.fallback;},{once:true});img.src=option.icon;return img;}; + const fill=(node,option)=>{const icon=mark(option);if(icon)node.append(icon);const text=document.createElement('span');text.className='choice-label';text.textContent=option.label;node.append(text);if(option.detail){const detail=document.createElement('small');detail.className='choice-detail';detail.textContent=option.detail;node.append(detail);}}; + current=Math.min(Math.max(0,current),Math.max(0,options.length-1)); + if(options[current])fill(toggle,options[current]); const arrow=document.createElement('span');arrow.className='provider-arrow';arrow.textContent='⌄';arrow.setAttribute('aria-hidden','true');toggle.append(arrow); const close=()=>{list.hidden=true;toggle.setAttribute('aria-expanded','false');};root.closePicker=close; - const open=()=>{list.hidden=false;toggle.setAttribute('aria-expanded','true');list.children[current].focus();}; - providers.forEach((name,index)=>{const option=document.createElement('button');option.type='button';option.setAttribute('role','option');option.setAttribute('aria-selected',String(index===current));option.tabIndex=-1;option.append(icon(ids[index]),name);option.addEventListener('click',()=>{close();toggle.focus();onSelect(index);});list.append(option);}); + const open=()=>{list.hidden=false;toggle.setAttribute('aria-expanded','true');list.children[current]?.focus();}; + options.forEach((option,index)=>{const item=document.createElement('button');item.type='button';item.setAttribute('role','option');item.setAttribute('aria-selected',String(index===current));item.tabIndex=-1;fill(item,option);item.addEventListener('click',()=>{close();toggle.focus();onSelect(index,option);});list.append(item);}); + // Opening focuses the current option, so a mouse press on a different one + // would blur it first; without this the blur closed the list before the + // click could land, and the choice silently never changed. + list.addEventListener('mousedown',event=>event.preventDefault()); toggle.addEventListener('click',()=>list.hidden?open():close()); toggle.addEventListener('keydown',event=>{if(['ArrowDown','ArrowUp'].includes(event.key)){event.preventDefault();open();}}); - list.addEventListener('keydown',event=>{let index=[...list.children].indexOf(document.activeElement);if(event.key==='ArrowDown')index=(index+1)%3;else if(event.key==='ArrowUp')index=(index+2)%3;else if(event.key==='Home')index=0;else if(event.key==='End')index=2;else return;event.preventDefault();list.children[index].focus();}); - root.addEventListener('focusout',()=>queueMicrotask(()=>{if(!root.contains(document.activeElement))close();})); + list.addEventListener('keydown',event=>{const count=options.length;let index=[...list.children].indexOf(document.activeElement);if(event.key==='ArrowDown')index=(index+1)%count;else if(event.key==='ArrowUp')index=(index+count-1)%count;else if(event.key==='Home')index=0;else if(event.key==='End')index=count-1;else if(event.key==='Escape'){close();toggle.focus();return;}else return;event.preventDefault();list.children[index].focus();}); + root.addEventListener('focusout',event=>{if(!root.contains(event.relatedTarget))close();}); root.append(toggle,list);return root; }; +// A fresh pal each time the settings open: the hosted provider is drawn by +// the same turnaround art as aesthetic.computer's own mark. +window.createProviderPicker=({backend,busy,onSelect})=>{ + const pal=`https://pals.aesthetic.computer/random.webp?menu=${Date.now()}`; + const options=[ + {id:'ac',label:'AC',icon:pal,fallback:'assets/provider-ac.svg',markClass:'pal'}, + {id:'claude',label:'Claude',icon:'assets/provider-claude.svg'}, + {id:'codex',label:'Codex',icon:'assets/provider-codex.svg'}, + ]; + const root=window.createChoicePicker({label:'Provider',options,current:Math.max(0,options.findIndex(o=>o.id===backend)),busy,onSelect}); + root.classList.add('provider-picker');return root; +}; diff --git a/easel/desktop/renderer.js b/easel/desktop/renderer.js index f7173c67ca..14a2250109 100644 --- a/easel/desktop/renderer.js +++ b/easel/desktop/renderer.js @@ -123,14 +123,22 @@ function reportTitleGeometry(){ window.aesel.titleGeometry?.({x:visible?r.right+6:0,y:visible?Math.max(0,r.top+(r.height-size)/2):0,size,visible,title:label.dataset.title||label.textContent,titleX:r.x,titleY:r.y,titleWidth:r.width,titleHeight:r.height,titleFontSize:16,titleColors:label.titleColors||[]}); } new ResizeObserver(()=>reportTitleGeometry()).observe(document.getElementById('qr-label')); +// A live drag delivers a resize every frame. Refitting the terminal each time +// rebuilds xterm's WebGL surface and waits on the GPU, so during the drag the +// grid refits at most every 80 ms and settles once the drag ends; the preview +// scale is a transform and still tracks every frame. +// During the drag only the preview transform tracks the window; the terminal +// grid, shelf, notebook ruling and prompt settle once, 150 ms after the last +// resize event, on the 'aesel-resize-settled' event the modules listen for. function resize() { if (resizeFrame) return; resizeFrame = requestAnimationFrame(() => { resizeFrame = 0; - if (!fullscreenState.preview) { fitTerminal(); sizeShelf(); } + const dragging = document.body.classList.contains('window-resizing'); + if (!fullscreenState.preview && !dragging) { fitTerminal(); sizeShelf(); } sizePreviewBox(); scalePreview(); - reportTitleGeometry(); + if (!dragging) reportTitleGeometry(); }); } let noticeTimer = null; @@ -188,6 +196,7 @@ window.placeNotebookActivity=()=>{ }; let promptState=null; function positionPrompt(){ + if(document.body.classList.contains('window-resizing'))return; const rowHeight=terminal.options.fontSize*1.5; promptLine.style.fontSize=`${terminal.options.fontSize}px`; promptLine.style.lineHeight=`${rowHeight}px`; @@ -220,7 +229,7 @@ function updatePrompt(value){ } promptLine.addEventListener('pointerdown',event=>{event.preventDefault();terminal.focus();}); new ResizeObserver(positionPrompt).observe(terminalElement); -window.addEventListener('resize',positionPrompt); +window.addEventListener('aesel-resize-settled',positionPrompt); let draggingSelection = false, pendingOutput = '', pastedInput = false; let clickOrigin = null; @@ -478,10 +487,29 @@ function scalePreview() { previewViewport.style.transform = `translate(${left}px, ${top}px) scale(${scale})`; } new ResizeObserver(scalePreview).observe(artifact); +// macOS resizes a window synchronously and waits for every embedded frame, +// and the piece guest is busy painting at 60 fps, so each drag step waited on +// it (~75 ms of a ~100 ms step). For the drag the guest is swapped for a +// snapshot of its last frame and hidden; hidden, a step takes ~25 ms. +const previewFreeze=new Image();previewFreeze.id='preview-freeze';previewFreeze.alt='';previewFreeze.hidden=true;previewViewport.append(previewFreeze); +let freezing=false; +function freezePreview(){ + if(freezing||fullscreenState.preview||preview.hidden||window.currentPreviewMedium&&window.currentPreviewMedium!=='piece')return; + freezing=true; + preview.capturePage().then(image=>{ + if(!document.body.classList.contains('window-resizing')||image.isEmpty())return; + previewFreeze.src=image.toDataURL();previewFreeze.hidden=false;preview.style.visibility='hidden'; + }).catch(()=>{}); +} +function thawPreview(){ + freezing=false;preview.style.visibility=''; + requestAnimationFrame(()=>{previewFreeze.hidden=true;previewFreeze.removeAttribute('src');}); +} window.addEventListener('resize', () => { document.body.classList.add('window-resizing'); + freezePreview(); clearTimeout(resizeEndTimer); - resizeEndTimer=setTimeout(()=>document.body.classList.remove('window-resizing'),150); + resizeEndTimer=setTimeout(()=>{document.body.classList.remove('window-resizing');thawPreview();resize();window.dispatchEvent(new Event('aesel-resize-settled'));},150); resize(); }); @@ -529,7 +557,7 @@ function reportZoom() { } function updateWindowTitle() { const mark = /^[A-Z]$/.test(instanceLabel) ? String.fromCodePoint(0x1D56C + instanceLabel.charCodeAt(0) - 65) : `[${instanceLabel}]`; - document.title = titlePiece || 'Aesel'; + document.title = titlePiece || 'aesel'; } window.aesel.onInstanceLabel(label => { instanceLabel = label; @@ -652,16 +680,13 @@ boot().catch(error => { document.body.dataset.notebookReady='true';document.getE close();window.aesel.input(`\x1b[99;${index}~`); }})); if(provider?.backend!=='ac'){ - const modelLabel=document.createElement('label');modelLabel.textContent='Model';modelLabel.className='settings-field'; - const modelSelect=document.createElement('select');modelSelect.setAttribute('aria-label','Model');modelSelect.disabled=!!provider?.busy||!provider?.models?.length; - for(const [index,model] of (provider?.models||[]).entries()){ - const option=document.createElement('option');option.value=String(index);option.textContent=model.label; - option.selected=model.id===(provider.selectedModel??provider.model);modelSelect.append(option); - } - if(![...(provider?.models||[])].some(model=>model.id===(provider.selectedModel??provider.model)))modelSelect.selectedIndex=0; + const models=provider?.models||[]; + const chosen=provider?.selectedModel??provider?.model; const backendIndex=['ac','claude','codex'].indexOf(provider?.backend); - modelSelect.addEventListener('change',()=>{if(provider?.busy)return;window.aesel.input(`\x1b[99;4;${backendIndex};${modelSelect.value}~`);}); - modelLabel.append(modelSelect);choices.append(modelLabel); + choices.append(window.createChoicePicker({label:'Model',options:models.map(model=>({id:model.id,label:model.label,detail:model.detail})),current:Math.max(0,models.findIndex(model=>model.id===chosen)),busy:!!provider?.busy||!models.length,onSelect:index=>{ + if(provider?.busy||models[index]?.id===chosen)return; + window.aesel.input(`\x1b[99;4;${backendIndex};${index}~`); + }})); } const audioLabel=document.createElement('label');audioLabel.className='settings-field';audioLabel.textContent='Preview volume'; @@ -673,7 +698,7 @@ boot().catch(error => { document.body.dataset.notebookReady='true';document.getE volume.addEventListener('input',()=>{level.textContent=volume.value+'%';window.previewAudio?.set({volume:Number(volume.value)/100});}); mute.addEventListener('click',()=>{window.previewAudio?.set({muted:!window.previewAudio.muted});updateMute();}); audioRow.append(volume,level,mute);audioLabel.append(audioRow);menu.append(audioLabel); - const stateLine=document.createElement('p');stateLine.className='inference-status';stateLine.textContent=[provider?.mode==='local'?'Local inference':'Remote inference',provider?.status,provider?.activity].filter(Boolean).join(' · ');menu.append(stateLine); + const stateLine=document.createElement('p');stateLine.className='inference-status';stateLine.textContent=[provider?.mode==='local'?'Local inference':'Remote inference',provider?.status,provider?.activity,provider?.backend!=='ac'&&provider?.model?`running ${provider.model}`:''].filter(Boolean).join(' · ');menu.append(stateLine); if(provider?.notice){const notice=document.createElement('p');notice.className='session-notice';notice.textContent=provider.notice;menu.append(notice);} if(provider?.backend==='ac'){const count=document.createElement('p');count.className='braincell-balance';const icon=new Image();icon.src='assets/braincell.svg';icon.alt='';icon.className='provider-mark';const dollars=credits?.dollars;const usd=value=>new Intl.NumberFormat('en-US',{style:'currency',currency:'USD'}).format(value); const valid=dollars?.currency==='USD'&&[dollars.total,dollars.free,dollars.purchased].every(value=>Number.isFinite(value)&&value>=0); diff --git a/easel/desktop/scripts/linux-download.html b/easel/desktop/scripts/linux-download.html index 8e5c8b49d7..a45f1bbaac 100644 --- a/easel/desktop/scripts/linux-download.html +++ b/easel/desktop/scripts/linux-download.html @@ -12,8 +12,8 @@ sudo apt install ./Easel-0.7.0-linux-amd64.deb
chmod +x Easel-0.7.0-linux-x86_64.AppImage
 ./Easel-0.7.0-linux-x86_64.AppImage

macOS

-

Download for Apple silicon

-

ZIP archive · signed and notarized · macOS 12+

+

Download for Apple silicon

+

ZIP archive · signed and notarized · macOS 12+

If FUSE is unavailable, try --appimage-extract-and-run. On Ubuntu, use the .deb above to install the required sandbox. Do not disable the sandbox.

aesel requires sharing conversations and artifact revision references with authorized Aesthetic Computer staff for product improvement, including when using your own provider. Read and accept the disclosure before use. Hosted generation requires account access; bringing your own Claude or Codex requires its CLI and account.

Tested on Ubuntu 24.04 x86-64. ARM builds are not included. SHA-256 checksums · Aesthetic Computer

diff --git a/easel/desktop/style.css b/easel/desktop/style.css index 77d2050396..1c6fc296ab 100644 --- a/easel/desktop/style.css +++ b/easel/desktop/style.css @@ -5,6 +5,8 @@ html,body{margin:0;width:100%;height:100%;overflow:hidden;background:var(--aesel #artifact{position:relative;width:100%;height:100%;box-sizing:border-box;overflow:hidden;background:#1c1728;border:1px solid #ffffff55;border-radius:0;box-shadow:2px 2px #0006;transition:none;z-index:2} #preview-viewport{position:absolute;left:0;top:0;width:var(--preview-logical-width,192px);height:var(--preview-logical-height,128px);transform-origin:0 0;image-rendering:pixelated;overflow:hidden} #piece{display:flex;width:var(--preview-logical-width,192px);height:var(--preview-logical-height,128px);image-rendering:pixelated} +#preview-freeze{position:absolute;left:0;top:0;width:var(--preview-logical-width,192px);height:var(--preview-logical-height,128px);image-rendering:pixelated;pointer-events:none} +#preview-freeze[hidden]{display:none} #version{position:absolute;right:0;top:calc(100% + 4px);color:#fff;text-shadow:1px 1px #000;font:calc(var(--aesel-qr-label-size,16px) * .75) "AC aesel Unifont",monospace;pointer-events:none;white-space:nowrap} #version:empty{display:none} #artifact.refresh{animation:refresh .35s ease-out} diff --git a/easel/desktop/transcript.js b/easel/desktop/transcript.js index 6c72030442..d53a29ebb2 100644 --- a/easel/desktop/transcript.js +++ b/easel/desktop/transcript.js @@ -7,6 +7,7 @@ const paper=document.createElement('div');paper.id='notebook-page';view.append(paper); const prose=document.createElement('div');prose.id='notebook-content';paper.append(prose); window.alignNotebookRuling=()=>{ + if(document.body.classList.contains('window-resizing'))return; const spacing=parseFloat(getComputedStyle(view).lineHeight)||24; const origin=paper.getBoundingClientRect().top; const pageTop=origin-view.getBoundingClientRect().top+view.scrollTop; @@ -16,6 +17,7 @@ window.layoutNotebookPreview?.(); }; view.addEventListener('scroll',window.alignNotebookRuling,{passive:true}); + window.addEventListener('aesel-resize-settled',window.alignNotebookRuling); new ResizeObserver(window.alignNotebookRuling).observe(view); if(title)new ResizeObserver(window.alignNotebookRuling).observe(title); const nodes=new Map(); diff --git a/easel/package.json b/easel/package.json index a5f3244025..51dc048eb1 100644 --- a/easel/package.json +++ b/easel/package.json @@ -1,6 +1,6 @@ { "name": "aesel", - "version": "0.7.15", + "version": "0.7.16", "private": true, "type": "module", "scripts": { diff --git a/easel/src/provider-picker.mjs b/easel/src/provider-picker.mjs index f4b974abc3..787e4d70b0 100644 --- a/easel/src/provider-picker.mjs +++ b/easel/src/provider-picker.mjs @@ -23,9 +23,10 @@ export function pickerModels(p) { const backend=BACKENDS[p.backend]; if(p.backend==='ac')return [{id:backend.defaultModel,label:'Automatic'}]; const choices=p.backend==='ac'?Object.entries(backend.models).map(([label,id])=>({id,label})) - :p.backend==='claude'?['fable','opus','sonnet','haiku'].map(id=>({id,label:id})) + // The CLI resolves a family alias to a dated id; a resolved id selects its family row instead of growing a "custom" row. + :p.backend==='claude'?[['fable','Claude Fable 5.1'],['opus','Claude Opus 5'],['sonnet','Claude Sonnet 5'],['haiku','Claude Haiku 4.5']].map(([family,label])=>{const resolved=String(p.model||'').includes(family)?p.model:family;return {id:resolved,label,detail:resolved};}) :[{id:'',label:'CLI default'},...(p.catalog||[]).filter(x=>!x.hidden).map(x=>({id:x.model,label:x.displayName||x.model}))]; - if(!choices.some(x=>x.id===p.model))choices.unshift({id:p.model,label:p.model||'CLI default'}); + if(!choices.some(x=>x.id===p.model))choices.unshift({id:p.model,label:p.model||'CLI default',detail:p.model?'custom':''}); return choices; } export function pickerEfforts(p) { diff --git a/system/public/aesel/index.html b/system/public/aesel/index.html index 82b901d19f..80899ee221 100644 --- a/system/public/aesel/index.html +++ b/system/public/aesel/index.html @@ -3,28 +3,38 @@ Aesel — Aesthetic Computer - + - + -
acby Aesthetic Computer
+
acby Aesthetic Computer

-

Make art and software with AI.

- Download for Mac -
Apple silicon · macOS 12+ · Linux & more
-
-
-
Interactive demo
-
Click to change the colors
-
make a garden that dances
-
+

Make pieces, pictures, sounds, papers and Game Boy games with Aesthetic Computer.

+ Download for Mac +
0.7.16 · Apple silicon · macOS 12+ · iPhone is next
+
+
aesel @jeffrey/raro v0
+ The Aesel window: a dark ruled notebook titled @jeffrey/raro, a live preview of the piece in the top-right corner with its QR code behind it, a cursor on the last line and a v0 version label. +
Type what to make. Aesel writes the piece, the corner shows it live, and the QR opens it on your phone.
+
PiecePictureSoundPaperGame Boy
+
+
+
+
    +
  • Every save publishes the piece to aesthetic.computer under your handle, and the preview reloads.
  • +
  • Sound plays from the preview with a waveform in the margin.
  • +
  • Replies are a notebook: ruled pages, code, math, diagrams and color swatches. Drag to select, right-click to copy.
  • +
  • Tap a version label to preview an earlier revision.
  • +
  • Provider is your choice: AC with daily credits, or your own Claude or Codex.
  • +
  • ⌘N opens another workspace with its own agent, preview and letter.
  • +
-

Downloads

Sign in with Aesthetic Computer.
Use AC, Claude, or Codex.

Mac Apple silicon · DMG↓Linux Ubuntu / Debian · x86-64↓AppImage Other Linux desktops · x86-64↓
Terminalcurl -fsSL https://aesthetic.computer/aesel.sh | sh

Requires Node.js 22+. Run ac after installing.

Mac 0.7.15 · Linux 0.7.0 (Easel)

+

Downloads

Sign in with Aesthetic Computer.
Use AC, Claude, or Codex.

Mac 0.7.16 · Apple silicon · macOS 12+ · DMG↓
iPhone Same account, notebook, donkey and live piece preview. Not on the App Store yet.…
diff --git a/system/public/aesel/screenshot-studio.png b/system/public/aesel/screenshot-studio.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc957e16e17458e14af13eb1054d8b1c8184cee GIT binary patch literal 28792 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 z%0D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 P8x+`{u6{1-oD!M{const o=audio.createOscillator(),g=audio.createGain(),start=audio.currentTime+i*.15;o.type='sine';o.frequency.value=hz;o.connect(g);g.connect(audio.destination);g.gain.setValueAtTime(0,start);g.gain.linearRampToValueAtTime(.08,start+.015);g.gain.exponentialRampToValueAtTime(.001,start+.4);o.start(start);o.stop(start+.42)})} -function remix(e){seed++;if(mode==='sound')play();if(mode==='game'&&e?.clientX){const r=canvas.getBoundingClientRect();player.x=Math.max(16,Math.min(464,(e.clientX-r.left)*480/r.width));player.y=Math.max(48,Math.min(330,(e.clientY-r.top)*360/r.height))}draw()} -document.querySelectorAll('[data-mode]').forEach(b=>b.addEventListener('click',()=>{mode=b.dataset.mode;document.querySelectorAll('[data-mode]').forEach(x=>x.setAttribute('aria-pressed',String(x===b)));paper.hidden=mode!=='paper';document.querySelector('#prompt').textContent=prompts[mode];document.querySelector('#demo-hint').textContent=hints[mode];canvas.setAttribute('aria-label',hints[mode]);document.querySelector('#remix').hidden=mode==='paper';draw()})); -canvas.addEventListener('click',remix);document.querySelector('#remix').addEventListener('click',()=>remix());canvas.addEventListener('keydown',e=>{if(mode==='game'&&e.key.startsWith('Arrow')){e.preventDefault();player.x=Math.max(16,Math.min(464,player.x+(e.key==='ArrowRight'?10:e.key==='ArrowLeft'?-10:0)));player.y=Math.max(48,Math.min(330,player.y+(e.key==='ArrowDown'?10:e.key==='ArrowUp'?-10:0)));draw()}else if(e.key==='Enter'||e.key===' '){e.preventDefault();remix()}}); -let last=0;function tick(now){if(now-last>80&&!document.hidden){last=now;if(!reduced.matches){t+=.08;if(mode==='piece'||mode==='sound')draw();const f=8+(frame++%4);document.querySelector('.donkey').style.backgroundPosition=`${-(f%4)*128}px ${-Math.floor(f/4)*128}px`}}requestAnimationFrame(tick)}draw();requestAnimationFrame(tick); +const donkey=document.querySelector('.donkey'),reduced=matchMedia('(prefers-reduced-motion: reduce)'); +let last=0,frame=0; +function tick(now){if(now-last>80&&!document.hidden&&!reduced.matches){last=now;const f=8+(frame++%4);donkey.style.backgroundPosition=`${-(f%4)*128}px ${-Math.floor(f/4)*128}px`}requestAnimationFrame(tick)} +requestAnimationFrame(tick); diff --git a/system/public/aesel/style.css b/system/public/aesel/style.css index bde8622955..41051ad297 100644 --- a/system/public/aesel/style.css +++ b/system/public/aesel/style.css @@ -1,6 +1,6 @@ @font-face{font-family:Pixel;src:url(pixel.woff) format('woff');font-display:swap} -:root{--ink:#292030;--paper:#f4eddb;--pink:#f588b3;--violet:#8768d7;--wood:#805337}*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding:30px}body{margin:0;background:var(--paper);color:var(--ink);font-family:Arial,Helvetica,sans-serif}a{color:inherit;text-underline-offset:4px}button,a{-webkit-tap-highlight-color:transparent}button{font:inherit;color:inherit;cursor:pointer}button:focus-visible,a:focus-visible,canvas:focus-visible,summary:focus-visible{outline:3px solid #6041ad;outline-offset:5px}header,main,footer{max-width:1240px;margin:auto}header{padding:28px 42px;display:flex;align-items:center;justify-content:space-between}.ac{display:flex;align-items:center;gap:12px;text-decoration:none;font-size:13px}.ac-mark{font-family:Pixel,monospace;font-size:29px;background:var(--ink);color:#ff94c2;padding:0 7px;box-shadow:3px 3px #c6bba9}.ac-mark span{color:#86d7af}nav{display:flex;gap:30px;font-size:14px}nav a{text-decoration:none}nav a:hover{text-decoration:underline}.hero{display:grid;grid-template-columns:1fr 1.16fr;gap:65px;align-items:center;padding:65px 42px 90px}.intro h1{margin:0 0 24px;font-size:clamp(80px,9vw,128px);letter-spacing:-9px;line-height:1.15;font-family:'Comic Sans MS','Chalkboard SE',cursive;font-weight:900}.intro h1 i{font-style:normal;display:inline-block;-webkit-text-stroke:2px var(--ink);paint-order:stroke fill;text-shadow:4px 6px var(--ink);color:var(--pink);transform:rotate(-7deg)}.intro h1 i:nth-child(2){color:#8dbea1;transform:rotate(7deg)}.intro h1 i:nth-child(3){color:#ebbd50;transform:rotate(-4deg)}.intro h1 i:nth-child(4){color:#8c8cdd;transform:rotate(5deg)}.intro h1 i:nth-child(5){color:#ea916b;transform:rotate(-5deg)}.intro h1 b{display:none}h2{font-size:clamp(29px,3.4vw,44px);font-weight:500;line-height:1.12;letter-spacing:-1.7px;margin:0 0 22px}p{font-size:17px;line-height:1.65;margin:0 0 26px}.button{display:inline-flex;align-items:center;justify-content:space-between;gap:34px;text-decoration:none;padding:17px 24px;border:2px solid var(--ink);box-shadow:4px 5px var(--ink);font-size:17px;font-weight:600;background:var(--pink);transition:transform .12s,box-shadow .12s}.button:hover{transform:translate(2px,2px);box-shadow:2px 3px var(--ink)}.button span{font-size:23px}.download-note{font-size:12px;margin-top:19px;color:#655b62}.studio{min-width:0;transform:rotate(2deg);border:2px solid var(--ink);box-shadow:9px 11px 0 #c7bfae;background:#d7c9f1;border-radius:12px 12px 3px 3px;overflow:hidden}.studio-top{display:flex;justify-content:space-between;align-items:center;padding:12px 15px;font:14px Pixel,monospace;border-bottom:2px solid var(--ink)}.dots{color:#7552ac;font-size:11px;letter-spacing:3px}.canvas-wrap{position:relative;aspect-ratio:4/3;background:#252038;margin:13px;border:3px solid #4b344c;box-shadow:inset 0 0 0 3px #987bae;overflow:hidden}canvas{width:100%;height:100%;display:block;image-rendering:pixelated;cursor:crosshair}#remix{position:absolute;right:12px;top:12px;border:1px solid #fff8;border-radius:50%;width:34px;height:34px;background:#292030ad;color:white;font-size:23px}#remix:hover{background:#a74d8c}#demo-hint{position:absolute;bottom:12px;left:50%;transform:translateX(-50%);white-space:nowrap;background:#201628c9;color:#fff5ed;font:13px Pixel,monospace;padding:5px 10px;pointer-events:none}.workbench{position:relative;min-height:95px;padding:24px 110px 26px 20px;border-top:3px solid #492c29;border-bottom:3px solid #492c29;color:#ffefd8;background-color:#805337;background-image:repeating-linear-gradient(0deg,transparent 0 12px,#452e222f 12px 15px,transparent 15px 23px),repeating-linear-gradient(4deg,transparent 0 28px,#edb77d20 28px 31px,transparent 31px 49px);box-shadow:inset 0 5px #c6905a,inset 0 -5px #503522}.prompt{font:17px/1.4 Pixel,monospace;min-height:44px}.prompt>span:first-child{color:#ff9ece;margin-right:8px}.cursor{color:#ff9ece}.donkey{position:absolute;right:12px;bottom:5px;width:90px;height:128px;background-image:url(donkey.png);background-size:512px 512px;image-rendering:pixelated;filter:drop-shadow(4px 4px 0 #321f3670);background-position:0 -256px}.media{display:flex;gap:0;background:#30253d;overflow:auto}.media button{background:none;border:0;color:#c8b6d8;font:15px Pixel,monospace;padding:15px 0;flex:1;white-space:nowrap;min-width:65px}.media button:hover{color:white;background:#ffffff10}.media button[aria-pressed=true]{color:#292030;background:#e9bc63;box-shadow:inset 0 3px #ffdaa3}.possibilities{padding:55px 42px 70px;border-top:1px solid #b4a999}.possibilities h2{font-size:30px;margin-bottom:38px}.ideas{display:grid;grid-template-columns:repeat(3,1fr);gap:45px}.ideas p{font-size:22px;line-height:1.5;margin:0}.ideas span{display:block;font:13px Pixel,monospace;color:#766779;letter-spacing:1px;margin-bottom:16px}.downloads{padding:65px 42px;background:#ddd0ed;border:2px solid var(--ink);box-shadow:5px 5px var(--ink);margin:0 42px 48px;display:grid;grid-template-columns:1fr 1fr;gap:70px}.downloads h2{font-size:38px}.small{font-size:13px;line-height:1.6;color:#625368}.download-list>a{display:flex;justify-content:space-between;align-items:center;padding:15px 0;border-bottom:1px solid #ac9ab7;text-decoration:none;font-size:20px}.download-list>a:first-child{padding-top:0}.download-list>a:hover{color:#843d75}.download-list small{display:block;font-size:12px;margin-top:5px;color:#625368}.download-list b{font-size:27px}details{margin:22px 0;font-size:14px}summary{cursor:pointer;margin-bottom:12px}details>code{display:block;overflow-wrap:anywhere;padding:12px;background:#f4eddb;font:14px Pixel,monospace}.release{margin:18px 0 0}.privacy{font-size:13px;line-height:1.65;max-width:850px;margin:0 auto;padding:0 42px 45px;color:#695e62}.privacy strong{color:#382d3e}footer{border-top:1px solid #b4a999;padding:28px 42px;display:flex;gap:25px;font:13px Pixel,monospace}footer>span{margin-right:auto}footer>span span{color:#a05793;margin:0 5px}#paper{position:absolute;inset:0;background:#fbf3dc;padding:32px 40px;font-family:Georgia,serif;color:#302236}#paper small{font:10px Arial,sans-serif;letter-spacing:2px}#paper h3{font-size:34px;font-weight:400;margin:22px 0 16px;line-height:1.1}#paper p{font-size:13px;line-height:1.55;margin-bottom:15px}.paper-rule{border-top:1px solid #b7a496;margin:15px 0}#paper>span{font-size:11px} -@media(min-width:1400px){.hero{padding-top:90px;padding-bottom:110px}}@media(max-width:850px){.hero{gap:35px;padding-top:35px}.intro h1{font-size:86px}p{font-size:15px}.button{font-size:14px;padding:15px;gap:15px}.downloads{gap:25px;padding:35px}.ideas p{font-size:18px}#paper{padding:20px}#paper h3{font-size:25px;margin:12px 0}#paper p{font-size:11px}}@media(max-width:650px){header{padding:22px}.ac>span:last-child{font-size:11px}nav{gap:17px;font-size:12px}.hero{grid-template-columns:1fr;padding:30px 25px 65px;gap:46px}.intro{text-align:center}.intro h1{font-size:112px;margin-bottom:20px}.intro h2{font-size:37px}.intro p{font-size:16px}.studio{transform:rotate(1deg);max-width:500px;width:100%;margin:auto}.possibilities{padding:40px 25px}.ideas{grid-template-columns:1fr;gap:28px}.ideas p{font-size:21px}.ideas span{margin-bottom:8px}.downloads{grid-template-columns:1fr;margin:0 20px 35px;padding:30px;gap:10px}.downloads h2{font-size:34px}.privacy{padding:0 25px 30px}footer{padding:24px;flex-wrap:wrap;gap:16px}footer>span{width:100%}#paper{padding:25px 32px}#paper h3{font-size:30px}#paper p{font-size:12px}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{transition:none!important}} +:root{--ink:#292030;--paper:#f4eddb;--pink:#f588b3;--violet:#8768d7;--wood:#805337}*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding:30px}body{margin:0;background:var(--paper);color:var(--ink);font-family:Arial,Helvetica,sans-serif}a{color:inherit;text-underline-offset:4px}button,a{-webkit-tap-highlight-color:transparent}button{font:inherit;color:inherit;cursor:pointer}button:focus-visible,a:focus-visible,canvas:focus-visible,summary:focus-visible{outline:3px solid #6041ad;outline-offset:5px}header,main,footer{max-width:1240px;margin:auto}header{padding:28px 42px;display:flex;align-items:center;justify-content:space-between}.ac{display:flex;align-items:center;gap:12px;text-decoration:none;font-size:13px}.ac-mark{font-family:Pixel,monospace;font-size:29px;background:var(--ink);color:#ff94c2;padding:0 7px;box-shadow:3px 3px #c6bba9}.ac-mark span{color:#86d7af}nav{display:flex;gap:30px;font-size:14px}nav a{text-decoration:none}nav a:hover{text-decoration:underline}.hero{display:grid;grid-template-columns:1fr 1.16fr;gap:65px;align-items:center;padding:65px 42px 90px}.intro h1{margin:0 0 24px;font-size:clamp(80px,9vw,128px);letter-spacing:-9px;line-height:1.15;font-family:'Comic Sans MS','Chalkboard SE',cursive;font-weight:900}.intro h1 i{font-style:normal;display:inline-block;-webkit-text-stroke:2px var(--ink);paint-order:stroke fill;text-shadow:4px 6px var(--ink);color:var(--pink);transform:rotate(-7deg)}.intro h1 i:nth-child(2){color:#8dbea1;transform:rotate(7deg)}.intro h1 i:nth-child(3){color:#ebbd50;transform:rotate(-4deg)}.intro h1 i:nth-child(4){color:#8c8cdd;transform:rotate(5deg)}.intro h1 i:nth-child(5){color:#ea916b;transform:rotate(-5deg)}.intro h1 b{display:none}h2{font-size:clamp(29px,3.4vw,44px);font-weight:500;line-height:1.12;letter-spacing:-1.7px;margin:0 0 22px}p{font-size:17px;line-height:1.65;margin:0 0 26px}.button{display:inline-flex;align-items:center;justify-content:space-between;gap:34px;text-decoration:none;padding:17px 24px;border:2px solid var(--ink);box-shadow:4px 5px var(--ink);font-size:17px;font-weight:600;background:var(--pink);transition:transform .12s,box-shadow .12s}.button:hover{transform:translate(2px,2px);box-shadow:2px 3px var(--ink)}.button span{font-size:23px}.download-note{font-size:12px;margin-top:19px;color:#655b62}.studio{min-width:0;margin:0;border:2px solid var(--ink);box-shadow:9px 11px 0 #c7bfae;background:#d7c9f1;border-radius:12px 12px 3px 3px;overflow:hidden}.studio-top{display:flex;justify-content:space-between;align-items:center;padding:12px 15px;font:14px Pixel,monospace;border-bottom:2px solid var(--ink)}.dots{color:#7552ac;font-size:11px;letter-spacing:3px}.workbench{position:relative;min-height:95px;margin:0;padding:22px 110px 24px 20px;border-top:3px solid #492c29;border-bottom:3px solid #492c29;color:#ffefd8;background-color:#805337;background-image:repeating-linear-gradient(0deg,transparent 0 12px,#452e222f 12px 15px,transparent 15px 23px),repeating-linear-gradient(4deg,transparent 0 28px,#edb77d20 28px 31px,transparent 31px 49px);box-shadow:inset 0 5px #c6905a,inset 0 -5px #503522}.prompt{display:block;font:15px/1.5 Pixel,monospace;min-height:44px}.prompt>span:first-child{color:#ff9ece;margin-right:8px}.cursor{color:#ff9ece}.donkey{position:absolute;right:12px;bottom:5px;width:90px;height:128px;background-image:url(donkey.png);background-size:512px 512px;image-rendering:pixelated;filter:drop-shadow(4px 4px 0 #321f3670);background-position:0 -256px}.shot{display:block;width:100%;height:auto;margin:0;border-top:2px solid var(--ink)}.facts{padding:10px 42px 70px}.facts ul{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);gap:30px 45px}.facts li{font-size:19px;line-height:1.5;padding-top:14px;border-top:2px solid var(--ink)}.soon{display:flex;justify-content:space-between;align-items:center;padding:15px 0;font-size:20px;color:#4d3f55}.soon b{font-size:27px}.media{display:flex;gap:0;background:#30253d;overflow:auto}.media button,.media span{background:none;border:0;color:#c8b6d8;font:15px Pixel,monospace;padding:15px 0;flex:1;white-space:nowrap;min-width:65px}.media span:first-child{color:#292030;background:#e9bc63;box-shadow:inset 0 3px #ffdaa3}.downloads{padding:65px 42px;background:#ddd0ed;border:2px solid var(--ink);box-shadow:5px 5px var(--ink);margin:0 42px 48px;display:grid;grid-template-columns:1fr 1fr;gap:70px}.downloads h2{font-size:38px}.small{font-size:13px;line-height:1.6;color:#625368}.download-list>a{display:flex;justify-content:space-between;align-items:center;padding:15px 0;border-bottom:1px solid #ac9ab7;text-decoration:none;font-size:20px}.download-list>a:first-child{padding-top:0}.download-list>a:hover{color:#843d75}.download-list small{display:block;font-size:12px;margin-top:5px;color:#625368}.download-list b{font-size:27px}.release{margin:18px 0 0}.privacy{font-size:13px;line-height:1.65;max-width:850px;margin:0 auto;padding:0 42px 45px;color:#695e62}.privacy strong{color:#382d3e}footer{border-top:1px solid #b4a999;padding:28px 42px;display:flex;gap:25px;font:13px Pixel,monospace}footer>span{margin-right:auto}footer>span span{color:#a05793;margin:0 5px} +@media(min-width:1400px){.hero{padding-top:90px;padding-bottom:110px}}@media(max-width:850px){.hero{gap:35px;padding-top:35px}.intro h1{font-size:86px}p{font-size:15px}.button{font-size:14px;padding:15px;gap:15px}.downloads{gap:25px;padding:35px}.facts ul{grid-template-columns:1fr 1fr;gap:22px 30px}.facts li{font-size:17px}}@media(max-width:650px){header{padding:22px}.ac>span:last-child{font-size:11px}nav{gap:17px;font-size:12px}.hero{grid-template-columns:1fr;padding:30px 25px 65px;gap:46px}.intro{text-align:center}.intro h1{font-size:112px;margin-bottom:20px}.intro h2{font-size:37px}.intro p{font-size:16px}.studio{max-width:594px;width:100%;margin:auto}.facts{padding:0 25px 45px}.facts ul{grid-template-columns:1fr;gap:18px}.facts li{font-size:17px}.downloads{grid-template-columns:1fr;margin:0 20px 35px;padding:30px;gap:10px}.downloads h2{font-size:34px}.privacy{padding:0 25px 30px}footer{padding:24px;flex-wrap:wrap;gap:16px}footer>span{width:100%}.workbench{padding:18px 100px 20px 16px}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{transition:none!important}} .donkey{transform:scaleX(-1)} -- 2.51.2