Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
8.4 kB · 267 lines
HTML
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" /> <title>Notepat</title> <style> :root { /* B&W analogue of flip-view's purple border system. Dark mode: white-on-black. Light mode: black-on-white. */ --np-border: rgba(240, 240, 240, 0.25); --np-border-solid: rgba(240, 240, 240, 0.4); --np-border-hover: rgba(240, 240, 240, 0.55); --np-border-active: rgba(240, 240, 240, 0.75); --np-accent: rgba(240, 240, 240, 0.9); --np-accent-glow: rgba(240, 240, 240, 0.5); --np-chrome-bg: #000; } @media (prefers-color-scheme: light) { :root { --np-border: rgba(30, 30, 30, 0.25); --np-border-solid: rgba(30, 30, 30, 0.4); --np-border-hover: rgba(30, 30, 30, 0.55); --np-border-active: rgba(30, 30, 30, 0.8); --np-accent: rgba(30, 30, 30, 0.95); --np-accent-glow: rgba(30, 30, 30, 0.5); --np-chrome-bg: #fff; } }
* { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; background: transparent; }
/* Bento-box chrome: rectangular (no border-radius), inset on left/right for the drag handles and bottom for the volume strip. Each piece is its own visually distinct compartment. */ .frame { position: absolute; top: 0; bottom: 22px; left: 22px; right: 22px; border: 4px solid var(--np-border); border-radius: 0; box-shadow: inset 0 0 0 1px var(--np-border-solid); overflow: hidden; background: var(--np-chrome-bg); }
/* Top drag strip — extra grab area along the top inside the frame. */ #drag-strip { position: absolute; top: 0; left: 0; right: 0; height: 18px; -webkit-app-region: drag; z-index: 5; }
/* Side drag handles — protrude outside the frame on both edges so the window can be grabbed from either side, like the prompt pane's flip tabs. These don't flip — they just drag. */ .drag-tab { position: absolute; z-index: 250; width: 22px; top: 50%; height: 80px; transform: translateY(-50%); background: var(--np-border-solid); display: flex; align-items: center; justify-content: center; cursor: grab; -webkit-app-region: drag; user-select: none; transition: background 150ms ease; } .drag-tab:active { cursor: grabbing; } .drag-tab::after { content: ''; width: 3px; height: 30px; background: var(--np-accent); transition: all 0.2s ease; pointer-events: none; } .drag-tab:hover { background: var(--np-border-hover); } .drag-tab:hover::after { background: var(--np-border-active); height: 50px; box-shadow: 0 0 12px var(--np-accent-glow); } .drag-tab.left { left: 0; } .drag-tab.right { right: 0; }
/* Volume strip at the bottom — spans the full window width so it reads as its own bento compartment. */ .volume-control { position: absolute; left: 0; right: 0; bottom: 0; height: 22px; z-index: 240; display: flex; align-items: center; justify-content: center; padding: 0 10px; background: var(--np-border-solid); -webkit-app-region: no-drag; }
#volume-slider { -webkit-appearance: none; appearance: none; width: 140px; height: 4px; background: var(--np-border); cursor: pointer; } #volume-slider::-webkit-slider-runnable-track { height: 4px; background: transparent; } #volume-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 11px; height: 11px; background: var(--np-accent); border: 1px solid var(--np-border-active); box-shadow: 0 0 8px var(--np-accent-glow); margin-top: -4px; } #volume-slider::-moz-range-track { height: 4px; background: transparent; } #volume-slider::-moz-range-thumb { width: 11px; height: 11px; background: var(--np-accent); border: 1px solid var(--np-border-active); box-shadow: 0 0 8px var(--np-accent-glow); }
#note-webview { position: absolute; inset: 0; width: 100%; height: 100%; border: none; background: #111; } </style></head><body> <div class="frame"> <div id="drag-strip"></div> <webview id="note-webview" src="https://aesthetic.computer/notepat?desktop&nogap=true" allowpopups preload="../webview-preload.js" ></webview> </div> <div class="drag-tab left" title="Drag to move"></div> <div class="drag-tab right" title="Drag to move"></div> <div class="volume-control" aria-label="Master volume"> <input id="volume-slider" type="range" min="0" max="1" step="0.01" value="0.25" /> </div> <script> const { ipcRenderer } = require('electron'); const webviewEl = document.getElementById('note-webview'); const volumeSlider = document.getElementById('volume-slider');
// Apply ?piece=, ?base= overrides. Default src (set in HTML) keeps us // functional even if the webview element ignores a late src= assignment // on some Electron builds. const params = new URLSearchParams(location.search); const base = params.get('base') || 'https://aesthetic.computer'; const piece = (params.get('piece') || 'notepat').replace(/^\/+/, ''); const targetUrl = `${base}/${piece}?desktop&nogap=true`; if (webviewEl.src !== targetUrl) webviewEl.src = targetUrl;
// Cmd+W / Ctrl+W closes the window. window.addEventListener('keydown', (e) => { const mod = navigator.platform.toLowerCase().includes('mac') ? e.metaKey : e.ctrlKey; if (mod && e.key.toLowerCase() === 'w') { e.preventDefault(); window.close(); } });
// ========== Master volume ========== const DEFAULT_MASTER_VOLUME = 0.25; const VOLUME_STORAGE_KEY = 'notepat-master-volume'; let webviewReady = false; let pendingMasterVolume = null;
function clampVolume(v) { const n = Number(v); if (!Number.isFinite(n)) return DEFAULT_MASTER_VOLUME; return Math.min(1, Math.max(0, n)); }
function sendMasterVolume(value) { const clamped = clampVolume(value); if (typeof webviewEl?.executeJavaScript !== 'function') { pendingMasterVolume = clamped; return; } const code = `(() => { const v = ${clamped}; if (window.AC && typeof window.AC.setMasterVolume === 'function') { return window.AC.setMasterVolume(v); } if (typeof window.acSetMasterVolume === 'function') { return window.acSetMasterVolume(v); } return null; })()`; webviewEl.executeJavaScript(code, true).catch(() => {}); pendingMasterVolume = null; }
function syncMasterVolume() { const v = clampVolume(volumeSlider.value); sessionStorage.setItem(VOLUME_STORAGE_KEY, String(v)); if (webviewReady) sendMasterVolume(v); else pendingMasterVolume = v; }
const stored = sessionStorage.getItem(VOLUME_STORAGE_KEY); volumeSlider.value = clampVolume(stored ?? DEFAULT_MASTER_VOLUME).toString();
volumeSlider.addEventListener('input', syncMasterVolume);
webviewEl.addEventListener('dom-ready', () => { webviewReady = true; syncMasterVolume(); // Retry after AC boots so late-initialized audio picks up the value. setTimeout(syncMasterVolume, 2000); setTimeout(syncMasterVolume, 5000); });
// Keep the tray title in sync with whatever piece the webview lands on. webviewEl.addEventListener('did-navigate', (e) => { try { const pathname = new URL(e.url).pathname; const current = pathname.replace(/^\//, '').split('/')[0] || piece; ipcRenderer.invoke('set-current-piece', current); } catch (_) {} });
webviewEl.addEventListener('did-fail-load', (e) => { console.warn('[notepat] webview failed to load:', e.errorCode, e.errorDescription, e.validatedURL); }); </script></body></html>