Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

ac-electron: bento-box notepat chrome with volume slider + side drag handles

- Drop the × close tab; Cmd+W still closes the window
- Add left/right drag handles (protrude outside the frame on both edges,
-webkit-app-region: drag) so the window can be grabbed from either side
- Remove all border-radius — frame, handles, and volume strip are
rectangular for a bento-box look
- Add a 22px volume strip along the bottom spanning the full width,
wired to window.AC.setMasterVolume on dom-ready (persisted per-session)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+141 -40
+141 -40
ac-electron/renderer/notepat-view.html
··· 35 35 background: transparent; 36 36 } 37 37 38 - /* Rounded chrome frame — same geometry as flip-view's card, without 39 - the 3D flip machinery. We inset on the right so the close tab has 40 - room to protrude outside the border (matching the prompt pane's 41 - flip tabs on left/right). */ 38 + /* Bento-box chrome: rectangular (no border-radius), inset on left/right 39 + for the drag handles and bottom for the volume strip. Each piece is its 40 + own visually distinct compartment. */ 42 41 .frame { 43 42 position: absolute; 44 43 top: 0; 45 - bottom: 0; 46 - left: 0; 47 - right: 26px; 44 + bottom: 22px; 45 + left: 22px; 46 + right: 22px; 48 47 border: 4px solid var(--np-border); 49 - border-radius: 10px; 48 + border-radius: 0; 50 49 box-shadow: inset 0 0 0 1px var(--np-border-solid); 51 50 overflow: hidden; 52 51 background: var(--np-chrome-bg); 53 52 } 54 53 55 - /* Top drag strip — lets you move the frameless window. */ 54 + /* Top drag strip — extra grab area along the top inside the frame. */ 56 55 #drag-strip { 57 56 position: absolute; 58 57 top: 0; ··· 63 62 z-index: 5; 64 63 } 65 64 66 - /* Close tab — small square pocket on the top-right edge, protruding 67 - outside the frame. Shaped like a little tab (not a drag handle) 68 - with the × centered. Clicks close the window. */ 69 - .close-tab { 65 + /* Side drag handles — protrude outside the frame on both edges so the 66 + window can be grabbed from either side, like the prompt pane's flip 67 + tabs. These don't flip — they just drag. */ 68 + .drag-tab { 70 69 position: absolute; 71 - right: 0; 72 - top: 10px; 70 + z-index: 250; 73 71 width: 22px; 74 - height: 22px; 72 + top: 50%; 73 + height: 80px; 74 + transform: translateY(-50%); 75 75 background: var(--np-border-solid); 76 - border-radius: 0 6px 6px 0; 77 76 display: flex; 78 77 align-items: center; 79 78 justify-content: center; 80 - cursor: pointer; 79 + cursor: grab; 80 + -webkit-app-region: drag; 81 81 user-select: none; 82 - -webkit-app-region: no-drag; 83 - z-index: 250; 84 82 transition: background 150ms ease; 85 83 } 86 - .close-tab::after { 87 - content: '×'; 88 - color: var(--np-accent); 89 - font-family: "SF Mono", ui-monospace, monospace; 90 - font-size: 16px; 91 - line-height: 1; 92 - font-weight: 400; 93 - transition: color 150ms ease, text-shadow 150ms ease, transform 150ms ease; 84 + .drag-tab:active { cursor: grabbing; } 85 + .drag-tab::after { 86 + content: ''; 87 + width: 3px; 88 + height: 30px; 89 + background: var(--np-accent); 90 + transition: all 0.2s ease; 94 91 pointer-events: none; 95 92 } 96 - .close-tab:hover { background: var(--np-border-hover); } 97 - .close-tab:hover::after { 98 - color: var(--np-border-active); 99 - text-shadow: 0 0 10px var(--np-accent-glow); 100 - transform: scale(1.15); 93 + .drag-tab:hover { background: var(--np-border-hover); } 94 + .drag-tab:hover::after { 95 + background: var(--np-border-active); 96 + height: 50px; 97 + box-shadow: 0 0 12px var(--np-accent-glow); 101 98 } 99 + .drag-tab.left { left: 0; } 100 + .drag-tab.right { right: 0; } 102 101 103 - #note-webview { 102 + /* Volume strip at the bottom — spans the full window width so it reads 103 + as its own bento compartment. */ 104 + .volume-control { 104 105 position: absolute; 105 - top: 0; 106 106 left: 0; 107 107 right: 0; 108 108 bottom: 0; 109 + height: 22px; 110 + z-index: 240; 111 + display: flex; 112 + align-items: center; 113 + justify-content: center; 114 + padding: 0 10px; 115 + background: var(--np-border-solid); 116 + -webkit-app-region: no-drag; 117 + } 118 + 119 + #volume-slider { 120 + -webkit-appearance: none; 121 + appearance: none; 122 + width: 140px; 123 + height: 4px; 124 + background: var(--np-border); 125 + cursor: pointer; 126 + } 127 + #volume-slider::-webkit-slider-runnable-track { 128 + height: 4px; 129 + background: transparent; 130 + } 131 + #volume-slider::-webkit-slider-thumb { 132 + -webkit-appearance: none; 133 + appearance: none; 134 + width: 11px; 135 + height: 11px; 136 + background: var(--np-accent); 137 + border: 1px solid var(--np-border-active); 138 + box-shadow: 0 0 8px var(--np-accent-glow); 139 + margin-top: -4px; 140 + } 141 + #volume-slider::-moz-range-track { 142 + height: 4px; 143 + background: transparent; 144 + } 145 + #volume-slider::-moz-range-thumb { 146 + width: 11px; 147 + height: 11px; 148 + background: var(--np-accent); 149 + border: 1px solid var(--np-border-active); 150 + box-shadow: 0 0 8px var(--np-accent-glow); 151 + } 152 + 153 + #note-webview { 154 + position: absolute; 155 + inset: 0; 109 156 width: 100%; 110 157 height: 100%; 111 158 border: none; ··· 123 170 preload="../webview-preload.js" 124 171 ></webview> 125 172 </div> 126 - <div class="close-tab" title="Close (Cmd+W)"></div> 173 + <div class="drag-tab left" title="Drag to move"></div> 174 + <div class="drag-tab right" title="Drag to move"></div> 175 + <div class="volume-control" aria-label="Master volume"> 176 + <input id="volume-slider" type="range" min="0" max="1" step="0.01" value="0.25" /> 177 + </div> 127 178 <script> 128 179 const { ipcRenderer } = require('electron'); 129 180 const webviewEl = document.getElementById('note-webview'); 130 - const closeTab = document.querySelector('.close-tab'); 181 + const volumeSlider = document.getElementById('volume-slider'); 131 182 132 183 // Apply ?piece=, ?base= overrides. Default src (set in HTML) keeps us 133 184 // functional even if the webview element ignores a late src= assignment ··· 138 189 const targetUrl = `${base}/${piece}?desktop&nogap=true`; 139 190 if (webviewEl.src !== targetUrl) webviewEl.src = targetUrl; 140 191 141 - closeTab.addEventListener('click', () => window.close()); 142 - 143 - // Cmd+W / Ctrl+W also closes. 192 + // Cmd+W / Ctrl+W closes the window. 144 193 window.addEventListener('keydown', (e) => { 145 194 const mod = navigator.platform.toLowerCase().includes('mac') ? e.metaKey : e.ctrlKey; 146 195 if (mod && e.key.toLowerCase() === 'w') { 147 196 e.preventDefault(); 148 197 window.close(); 149 198 } 199 + }); 200 + 201 + // ========== Master volume ========== 202 + const DEFAULT_MASTER_VOLUME = 0.25; 203 + const VOLUME_STORAGE_KEY = 'notepat-master-volume'; 204 + let webviewReady = false; 205 + let pendingMasterVolume = null; 206 + 207 + function clampVolume(v) { 208 + const n = Number(v); 209 + if (!Number.isFinite(n)) return DEFAULT_MASTER_VOLUME; 210 + return Math.min(1, Math.max(0, n)); 211 + } 212 + 213 + function sendMasterVolume(value) { 214 + const clamped = clampVolume(value); 215 + if (typeof webviewEl?.executeJavaScript !== 'function') { 216 + pendingMasterVolume = clamped; 217 + return; 218 + } 219 + const code = `(() => { 220 + const v = ${clamped}; 221 + if (window.AC && typeof window.AC.setMasterVolume === 'function') { 222 + return window.AC.setMasterVolume(v); 223 + } 224 + if (typeof window.acSetMasterVolume === 'function') { 225 + return window.acSetMasterVolume(v); 226 + } 227 + return null; 228 + })()`; 229 + webviewEl.executeJavaScript(code, true).catch(() => {}); 230 + pendingMasterVolume = null; 231 + } 232 + 233 + function syncMasterVolume() { 234 + const v = clampVolume(volumeSlider.value); 235 + sessionStorage.setItem(VOLUME_STORAGE_KEY, String(v)); 236 + if (webviewReady) sendMasterVolume(v); 237 + else pendingMasterVolume = v; 238 + } 239 + 240 + const stored = sessionStorage.getItem(VOLUME_STORAGE_KEY); 241 + volumeSlider.value = clampVolume(stored ?? DEFAULT_MASTER_VOLUME).toString(); 242 + 243 + volumeSlider.addEventListener('input', syncMasterVolume); 244 + 245 + webviewEl.addEventListener('dom-ready', () => { 246 + webviewReady = true; 247 + syncMasterVolume(); 248 + // Retry after AC boots so late-initialized audio picks up the value. 249 + setTimeout(syncMasterVolume, 2000); 250 + setTimeout(syncMasterVolume, 5000); 150 251 }); 151 252 152 253 // Keep the tray title in sync with whatever piece the webview lands on.