Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

slides/notepat-keymap: 16:9 keymap reference for casey's social-software meeting

Reusable AC slides format — piano + QWERTY layout with hand-split tint,
fading dashed connectors from C·D·E·F·G to their QWERTY twins (using
notepat.mjs's per-note color scheme), implementations chip row, and QR
codes to notepat.com / prompt.ac/menuband.

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

+587
+1
slides/notepat-keymap/.gitignore
··· 1 + notepat-keymap.png
+38
slides/notepat-keymap/README.md
··· 1 + # notepat keymap slide 2 + 3 + Single 16:9 image (2400×1350, Google Slides retina) explaining the 4 + notepat keyboard layout — piano keyboard up top with the QWERTY letter 5 + that plays each note, then the QWERTY layout itself with hand-split 6 + tint, then a "why this layout" rationale and the implementation list 7 + with QR codes to `notepat.com` and `prompt.ac/menuband`. 8 + 9 + Designed for casey's social-software meeting (April 2026) but reusable 10 + as a template for future AC slides. 11 + 12 + ## Build 13 + 14 + ```fish 15 + ./build.fish # → notepat-keymap.png 16 + ./build.fish ~/Desktop/notepat-keymap.png # → custom path 17 + ``` 18 + 19 + Deps: `qrencode` (`brew install qrencode`), `sips` (built-in), `python3`, 20 + Google Chrome at `/Applications/Google Chrome.app`. 21 + 22 + ## Files 23 + 24 + - `template.html` — the slide layout. Image src attributes use placeholder 25 + strings (`__QR_NOTEPAT__`, `__QR_MENUBAND__`, `__PALS_LOGO__`, 26 + `__JEFFREY_PIC__`); `build.fish` substitutes data URIs at render time. 27 + - `build.fish` — generates QRs, crops portrait, injects data URIs, runs 28 + headless Chrome to capture the PNG. 29 + 30 + ## Editing 31 + 32 + Open `template.html` directly in a browser to iterate on layout — the 33 + placeholders render as broken-image icons but everything else works. 34 + Run `build.fish` to produce the final PNG with all assets embedded. 35 + 36 + The piano + QWERTY positions are hard-coded so any change to key sizing 37 + needs matching updates to the absolute `left:` values in the piano keys 38 + and to `.qrow` padding-left for the QWERTY stagger.
+58
slides/notepat-keymap/build.fish
··· 1 + #!/usr/bin/env fish 2 + # Render the notepat keymap slide to a 16:9 PNG (2400×1350, retina for 3 + # Google Slides widescreen). Generates QR codes + base64 data URIs for 4 + # the pals mark and Jeffrey portrait, substitutes them into template.html, 5 + # then captures with headless Chrome. 6 + # 7 + # Output: slides/notepat-keymap/notepat-keymap.png 8 + # Optional first arg: alternative output path. 9 + # 10 + # Deps: qrencode (brew), sips (macOS built-in), python3, Google Chrome. 11 + 12 + set -l here (dirname (status filename)) 13 + set -l repo (realpath "$here/../..") 14 + set -l out (test -n "$argv[1]"; and echo $argv[1]; or echo "$here/notepat-keymap.png") 15 + set -l tmp (mktemp -d) 16 + 17 + set -l template "$here/template.html" 18 + set -l portrait "$repo/portraits/jeffrey/corpus/shoot/jeffery-av--01.jpg" 19 + set -l pals_svg "$repo/papers/arxiv-kidlisp/figures/pals.svg" 20 + 21 + # 1. QR codes 22 + qrencode -o "$tmp/qr-notepat.png" -s 12 -m 2 -l Q "https://notepat.com" 23 + qrencode -o "$tmp/qr-menuband.png" -s 12 -m 2 -l Q "https://prompt.ac/menuband" 24 + 25 + # 2. Square Jeffrey portrait (top-aligned crop, scaled to 600×600) 26 + sips -c 4815 4815 --cropOffset 0 0 "$portrait" --out "$tmp/jeffrey-raw.jpg" >/dev/null 27 + sips -z 600 600 "$tmp/jeffrey-raw.jpg" --out "$tmp/jeffrey.jpg" >/dev/null 28 + 29 + # 3. Base64 data URIs 30 + echo "data:image/png;base64,"(base64 -i "$tmp/qr-notepat.png") > "$tmp/qr-notepat.b64" 31 + echo "data:image/png;base64,"(base64 -i "$tmp/qr-menuband.png") > "$tmp/qr-menuband.b64" 32 + echo "data:image/svg+xml;base64,"(base64 -i "$pals_svg") > "$tmp/pals.b64" 33 + echo "data:image/jpeg;base64,"(base64 -i "$tmp/jeffrey.jpg") > "$tmp/jeffrey.b64" 34 + 35 + # 4. Template substitution 36 + python3 -c " 37 + import sys 38 + html = open('$template').read() 39 + for placeholder, path in [ 40 + ('__QR_NOTEPAT__', '$tmp/qr-notepat.b64'), 41 + ('__QR_MENUBAND__', '$tmp/qr-menuband.b64'), 42 + ('__PALS_LOGO__', '$tmp/pals.b64'), 43 + ('__JEFFREY_PIC__', '$tmp/jeffrey.b64'), 44 + ]: 45 + html = html.replace(placeholder, open(path).read().strip()) 46 + open('$tmp/keymap.html', 'w').write(html) 47 + " 48 + 49 + # 5. Headless Chrome render → PNG 50 + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \ 51 + --headless=new --no-sandbox --disable-gpu --hide-scrollbars \ 52 + --window-size=2400,1350 \ 53 + --virtual-time-budget=500 \ 54 + --screenshot="$out" \ 55 + "file://$tmp/keymap.html" 2>/dev/null 56 + 57 + rm -rf "$tmp" 58 + echo "wrote $out"
+490
slides/notepat-keymap/template.html
··· 1 + <!doctype html> 2 + <html> 3 + <head> 4 + <meta charset="utf-8"> 5 + <style> 6 + html, body { margin: 0; padding: 0; } 7 + /* 16:9 — exactly Google Slides widescreen at 2× retina */ 8 + body { 9 + width: 2400px; 10 + height: 1350px; 11 + background: #ffffff; 12 + color: #111; 13 + font-family: -apple-system, "SF Pro Display", "Helvetica Neue", Arial, sans-serif; 14 + box-sizing: border-box; 15 + padding: 32px 40px; 16 + overflow: hidden; 17 + display: flex; 18 + flex-direction: column; 19 + position: relative; 20 + } 21 + .connectors { 22 + position: absolute; 23 + inset: 0; 24 + width: 2400px; 25 + height: 1350px; 26 + pointer-events: none; 27 + z-index: 50; 28 + } 29 + 30 + /* ---------------- Header ---------------- */ 31 + .header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 18px; } 32 + .title { font-size: 100px; font-weight: 800; letter-spacing: -0.02em; margin: 0; line-height: 1; } 33 + .subtitle { font-size: 34px; color: #444; margin: 12px 0 0 0; font-weight: 400; max-width: 1900px; line-height: 1.25; } 34 + .brand { font-size: 26px; color: #888; letter-spacing: 0.18em; text-transform: uppercase; padding-bottom: 12px; white-space: nowrap; } 35 + 36 + /* ---------------- Top half: PIANO (full width) ---------------- */ 37 + .piano-wrap { display: flex; justify-content: center; margin-bottom: 14px; } 38 + .piano { 39 + position: relative; 40 + height: 320px; 41 + width: 2076px; /* 17 white keys × 120px + 36 gutter */ 42 + padding-left: 36px; 43 + box-sizing: border-box; 44 + } 45 + .wkey, .bkey { 46 + position: absolute; 47 + box-sizing: border-box; 48 + border-radius: 0 0 10px 10px; 49 + border: 2px solid #111; 50 + display: flex; 51 + flex-direction: column; 52 + align-items: center; 53 + justify-content: flex-end; 54 + padding-bottom: 16px; 55 + } 56 + .wkey { width: 120px; height: 320px; background: #fff; color: #111; } 57 + .wkey .qkey { font-size: 54px; font-weight: 800; line-height: 1; } 58 + .wkey .nname { font-size: 22px; color: #777; letter-spacing: 0.05em; margin-top: 8px; font-weight: 600; } 59 + .bkey { width: 72px; height: 200px; background: #111; color: #fff; border-color: #000; z-index: 2; } 60 + .bkey .qkey { font-size: 36px; font-weight: 800; line-height: 1; } 61 + .bkey .nname { font-size: 16px; color: #bbb; letter-spacing: 0.05em; margin-top: 6px; font-weight: 600; } 62 + /* Extended-range keys: aged-yellow tint + faded — these are optional 63 + "edge" keys, not the core two-octave instrument. Lower: A♯3/B3; 64 + upper: C6/C♯6/D6. */ 65 + .wkey.ext { background: #fff7dc; color: #aa9255; border-color: #d4c075; opacity: 0.78; } 66 + .wkey.ext .qkey { color: #aa9255; } 67 + .wkey.ext .nname { color: #b09a55; } 68 + .bkey.ext { background: #6a5826; color: #f8e7a0; border-color: #4a3814; opacity: 0.78; } 69 + .bkey.ext .nname { color: #d8c878; } 70 + .wkey.ext .ext-tag, .bkey.ext .ext-tag { 71 + font-size: 13px; 72 + letter-spacing: 0.18em; 73 + text-transform: uppercase; 74 + margin-top: 4px; 75 + opacity: 0.85; 76 + font-weight: 700; 77 + } 78 + 79 + /* ---------------- Bottom half: QWERTY (left) + RATIONALE+QRs (right) ---------------- */ 80 + .below { display: flex; gap: 36px; flex: 1; align-items: stretch; min-height: 0; } 81 + .below-left { flex: 0 0 1380px; display: flex; flex-direction: column; } 82 + .below-right { flex: 1; display: flex; flex-direction: column; } 83 + 84 + /* QWERTY layout. ANSI/MacBook stagger 0 / 0.25u / 0.75u 85 + cap = 100px, gap = 12px → slot = 112px 86 + 0.25 × 112 = 28px 87 + 0.75 × 112 = 84px */ 88 + .hand-labels { display: flex; justify-content: space-between; margin-bottom: 10px; font-size: 22px; letter-spacing: 0.22em; text-transform: uppercase; font-weight: 700; padding: 0 24px; } 89 + .hand-labels .l { color: #2a6cb8; } 90 + .hand-labels .r { color: #b8472a; } 91 + .qwerty-bg { 92 + position: relative; 93 + padding: 18px 20px; 94 + border-radius: 16px; 95 + background: linear-gradient(to right, 96 + rgba(74, 130, 200, 0.12) 0%, 97 + rgba(74, 130, 200, 0.12) 48%, 98 + rgba(200, 110, 74, 0.12) 52%, 99 + rgba(200, 110, 74, 0.12) 100%); 100 + box-sizing: border-box; 101 + flex: 1; 102 + display: flex; 103 + align-items: center; 104 + } 105 + .qwerty-wrap { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; width: 100%; } 106 + .qrow { display: flex; gap: 12px; } 107 + .qrow.r0 { padding-left: 0; } 108 + .qrow.r1 { padding-left: 28px; } 109 + .qrow.r2 { padding-left: 84px; } 110 + .qcap { 111 + width: 100px; height: 100px; 112 + border-radius: 14px; 113 + border: 3px solid #111; 114 + display: flex; 115 + align-items: center; 116 + justify-content: center; 117 + font-size: 42px; 118 + font-weight: 800; 119 + text-transform: uppercase; 120 + flex-shrink: 0; 121 + } 122 + .qcap.white { background: #fff; color: #111; } 123 + .qcap.black { background: #111; color: #fff; border-color: #000; } 124 + /* Extended-range caps: yellowed + faded. Not part of the core two-octave 125 + instrument — included as optional reach. */ 126 + .qcap.ext-white { 127 + background: #fff7dc; color: #aa9255; 128 + border-color: #d4c075; 129 + opacity: 0.78; 130 + flex-direction: column; 131 + } 132 + .qcap.ext-black { 133 + background: #6a5826; color: #f8e7a0; 134 + border-color: #4a3814; 135 + opacity: 0.78; 136 + flex-direction: column; 137 + } 138 + .qcap .ext-tag { 139 + font-size: 13px; 140 + letter-spacing: 0.18em; 141 + text-transform: uppercase; 142 + margin-top: 5px; 143 + opacity: 0.9; 144 + font-weight: 700; 145 + } 146 + .qcap.ghost { background: transparent; color: #c8c8c8; border: 2.5px dashed #d4d4d4; font-weight: 600; font-size: 30px; } 147 + .qcap.octshift { 148 + background: #f4ecff; color: #6b3fc0; 149 + border: 2.5px solid #b89df0; 150 + font-weight: 800; font-size: 26px; 151 + flex-direction: column; 152 + } 153 + .qcap.octshift .lbl { font-size: 13px; letter-spacing: 0.1em; opacity: 0.75; margin-top: 3px; } 154 + 155 + /* ---------------- Right side: rationale + impls + QRs ---------------- */ 156 + .rationale { flex: 1; } 157 + .rationale h3, .impls h3 { 158 + font-size: 24px; 159 + letter-spacing: 0.2em; 160 + text-transform: uppercase; 161 + color: #555; 162 + margin: 0 0 14px 0; 163 + font-weight: 700; 164 + } 165 + .rationale h3 .accent { color: #b8472a; } 166 + .rationale ul { list-style: none; padding: 0; margin: 0; font-size: 26px; line-height: 1.35; color: #1a1a1a; } 167 + .rationale li { 168 + padding: 11px 0 11px 32px; 169 + border-bottom: 1px solid #eee; 170 + position: relative; 171 + } 172 + .rationale li:last-child { border-bottom: none; } 173 + .rationale li::before { 174 + content: "▸"; 175 + position: absolute; left: 0; top: 11px; 176 + color: #b8472a; 177 + font-weight: 800; 178 + font-size: 26px; 179 + } 180 + .rationale li b { color: #000; font-weight: 800; } 181 + .rationale li .keys { 182 + font-family: "SF Mono", Menlo, monospace; 183 + background: #f4f4f4; 184 + padding: 2px 8px; 185 + border-radius: 5px; 186 + font-size: 22px; 187 + color: #111; 188 + } 189 + 190 + .below-bottom { 191 + display: flex; 192 + flex-direction: column; 193 + gap: 14px; 194 + margin-top: 18px; 195 + border-top: 2px solid #ddd; 196 + padding-top: 16px; 197 + } 198 + .below-bottom .row { 199 + display: flex; 200 + align-items: center; 201 + gap: 24px; 202 + } 203 + .impls { flex: 1; } 204 + .impls .impl-list { 205 + list-style: none; 206 + padding: 0; margin: 0; 207 + display: flex; 208 + flex-wrap: wrap; 209 + gap: 8px 10px; 210 + align-items: stretch; 211 + } 212 + .impls .impl-list li { 213 + background: #f6f5f1; 214 + border: 1px solid #e3e0d8; 215 + border-radius: 10px; 216 + padding: 7px 12px; 217 + display: flex; 218 + flex-direction: column; 219 + line-height: 1.05; 220 + min-width: 0; 221 + } 222 + .impls .impl-list b { font-weight: 800; font-size: 21px; color: #111; white-space: nowrap; } 223 + .impls .impl-list .where { 224 + color: #777; 225 + font-family: "SF Mono", Menlo, monospace; 226 + font-size: 13px; 227 + margin-top: 4px; 228 + white-space: nowrap; 229 + letter-spacing: 0.02em; 230 + } 231 + 232 + .qrs { display: flex; gap: 22px; } 233 + .qrs .qr { display: flex; flex-direction: column; align-items: center; gap: 7px; } 234 + .qrs .qr img { width: 180px; height: 180px; image-rendering: pixelated; border: 1px solid #ddd; border-radius: 8px; background: #fff; } 235 + .qrs .qr .label { font-family: "SF Mono", Menlo, monospace; font-size: 17px; color: #111; font-weight: 700; } 236 + 237 + /* PALS watermark + Jeffrey portrait + brand. Stacked images in the 238 + top-right corner, text label to their left. */ 239 + .brand-block { 240 + position: absolute; 241 + top: 18px; 242 + right: 32px; 243 + display: flex; align-items: flex-start; gap: 20px; 244 + } 245 + .brand-block .marks { 246 + display: flex; flex-direction: column; gap: 4px; align-items: center; 247 + } 248 + .brand-block .marks #pals { width: 124px; height: 124px; opacity: 0.95; } 249 + .brand-block .marks #jeffrey { 250 + width: 124px; height: 124px; 251 + border-radius: 6px; 252 + object-fit: cover; 253 + object-position: center top; 254 + border: 2px solid #ddd; 255 + /* Pull up into the pals SVG's empty padding so the two marks 256 + visually stack flush. */ 257 + margin-top: -22px; 258 + } 259 + .brand-block .brand-text { 260 + display: flex; flex-direction: column; gap: 6px; 261 + align-items: flex-end; 262 + padding-top: 6px; 263 + } 264 + .brand-block .brand-text .top { 265 + font-size: 36px; color: #333; 266 + letter-spacing: 0.04em; 267 + white-space: nowrap; font-weight: 800; 268 + line-height: 1; 269 + } 270 + .brand-block .brand-text .author { 271 + font-size: 22px; color: #888; 272 + white-space: nowrap; font-weight: 500; 273 + font-family: "SF Mono", Menlo, monospace; 274 + } 275 + .brand-block .brand-text .author b { color: #b8472a; font-weight: 800; } 276 + </style> 277 + </head> 278 + <body> 279 + <!-- HEADER --> 280 + <div class="header"> 281 + <div> 282 + <div class="title">notepat.com Keymap</div> 283 + <div class="subtitle">A chromatic keyboard instrument. <b>C&middot;D&middot;E&middot;F&middot;G&middot;A&middot;B</b> play the notes they name. Sharps <i>mostly</i> sit one row above their natural — like piano black keys.</div> 284 + </div> 285 + <div class="brand-block"> 286 + <div class="brand-text"> 287 + <div class="top">Aesthetic.Computer</div> 288 + <div class="author">by <b>@jeffrey</b></div> 289 + </div> 290 + <div class="marks"> 291 + <img id="pals" alt=""> 292 + <img id="jeffrey" alt="@jeffrey"> 293 + </div> 294 + </div> 295 + </div> 296 + 297 + <!-- TOP: PIANO (hero, full width) --> 298 + <div class="piano-wrap"> 299 + <div class="piano"> 300 + <div class="wkey ext" style="left:36px;"> <div class="qkey">X</div><div class="nname">B3</div><div class="ext-tag">ext</div></div> 301 + <div class="wkey" data-pn="c" style="left:156px;"> <div class="qkey">C</div><div class="nname">C4</div></div> 302 + <div class="wkey" data-pn="d" style="left:276px;"> <div class="qkey">D</div><div class="nname">D4</div></div> 303 + <div class="wkey" data-pn="e" style="left:396px;"> <div class="qkey">E</div><div class="nname">E4</div></div> 304 + <div class="wkey" data-pn="f" style="left:516px;"> <div class="qkey">F</div><div class="nname">F4</div></div> 305 + <div class="wkey" data-pn="g" style="left:636px;"> <div class="qkey">G</div><div class="nname">G4</div></div> 306 + <div class="wkey" data-pn="a" style="left:756px;"> <div class="qkey">A</div><div class="nname">A4</div></div> 307 + <div class="wkey" data-pn="b" style="left:876px;"> <div class="qkey">B</div><div class="nname">B4</div></div> 308 + <div class="wkey" style="left:996px;"> <div class="qkey">H</div><div class="nname">C5</div></div> 309 + <div class="wkey" style="left:1116px;"> <div class="qkey">I</div><div class="nname">D5</div></div> 310 + <div class="wkey" style="left:1236px;"> <div class="qkey">J</div><div class="nname">E5</div></div> 311 + <div class="wkey" style="left:1356px;"> <div class="qkey">K</div><div class="nname">F5</div></div> 312 + <div class="wkey" style="left:1476px;"> <div class="qkey">L</div><div class="nname">G5</div></div> 313 + <div class="wkey" style="left:1596px;"> <div class="qkey">M</div><div class="nname">A5</div></div> 314 + <div class="wkey" style="left:1716px;"> <div class="qkey">N</div><div class="nname">B5</div></div> 315 + <div class="wkey ext" style="left:1836px;"><div class="qkey">;</div><div class="nname">C6</div><div class="ext-tag">ext</div></div> 316 + <div class="wkey ext" style="left:1956px;"><div class="qkey">]</div><div class="nname">D6</div><div class="ext-tag">ext</div></div> 317 + 318 + <!-- Black keys: width 72, left = white_left - 36 --> 319 + <div class="bkey ext" style="left:0px;"> <div class="qkey">Z</div><div class="nname">A♯3</div></div> 320 + <div class="bkey" style="left:240px;"> <div class="qkey">V</div><div class="nname">C♯4</div></div> 321 + <div class="bkey" style="left:360px;"> <div class="qkey">S</div><div class="nname">D♯4</div></div> 322 + <div class="bkey" style="left:600px;"> <div class="qkey">W</div><div class="nname">F♯4</div></div> 323 + <div class="bkey" style="left:720px;"> <div class="qkey">R</div><div class="nname">G♯4</div></div> 324 + <div class="bkey" style="left:840px;"> <div class="qkey">Q</div><div class="nname">A♯4</div></div> 325 + <div class="bkey" style="left:1080px;"> <div class="qkey">T</div><div class="nname">C♯5</div></div> 326 + <div class="bkey" style="left:1200px;"> <div class="qkey">Y</div><div class="nname">D♯5</div></div> 327 + <div class="bkey" style="left:1440px;"> <div class="qkey">U</div><div class="nname">F♯5</div></div> 328 + <div class="bkey" style="left:1560px;"> <div class="qkey">O</div><div class="nname">G♯5</div></div> 329 + <div class="bkey" style="left:1680px;"> <div class="qkey">P</div><div class="nname">A♯5</div></div> 330 + <div class="bkey ext" style="left:1920px;"> <div class="qkey">'</div><div class="nname">C♯6</div></div> 331 + </div> 332 + </div> 333 + 334 + <!-- BELOW: QWERTY (left) + RATIONALE/IMPLS/QRs (right) --> 335 + <div class="below"> 336 + <div class="below-left"> 337 + <div class="hand-labels"> 338 + <div class="l">← Left hand · lower octave</div> 339 + <div class="r">Right hand · upper octave →</div> 340 + </div> 341 + <div class="qwerty-bg"> 342 + <div class="qwerty-wrap"> 343 + <div class="qrow r0"> 344 + <div class="qcap black">Q</div> 345 + <div class="qcap black">W</div> 346 + <div class="qcap white" data-qn="e">E</div> 347 + <div class="qcap black">R</div> 348 + <div class="qcap black">T</div> 349 + <div class="qcap black">Y</div> 350 + <div class="qcap black">U</div> 351 + <div class="qcap white">I</div> 352 + <div class="qcap black">O</div> 353 + <div class="qcap black">P</div> 354 + <div class="qcap ghost">[</div> 355 + <div class="qcap ext-white">]<div class="ext-tag">ext</div></div> 356 + </div> 357 + <div class="qrow r1"> 358 + <div class="qcap white" data-qn="a">A</div> 359 + <div class="qcap black">S</div> 360 + <div class="qcap white" data-qn="d">D</div> 361 + <div class="qcap white" data-qn="f">F</div> 362 + <div class="qcap white" data-qn="g">G</div> 363 + <div class="qcap white">H</div> 364 + <div class="qcap white">J</div> 365 + <div class="qcap white">K</div> 366 + <div class="qcap white">L</div> 367 + <div class="qcap ext-white">;<div class="ext-tag">ext</div></div> 368 + <div class="qcap ext-black">'<div class="ext-tag">ext</div></div> 369 + </div> 370 + <div class="qrow r2"> 371 + <div class="qcap ext-black">Z<div class="ext-tag">ext</div></div> 372 + <div class="qcap ext-white">X<div class="ext-tag">ext</div></div> 373 + <div class="qcap white" data-qn="c">C</div> 374 + <div class="qcap black">V</div> 375 + <div class="qcap white" data-qn="b">B</div> 376 + <div class="qcap white">N</div> 377 + <div class="qcap white">M</div> 378 + <div class="qcap octshift">,<div class="lbl">oct−</div></div> 379 + <div class="qcap octshift">.<div class="lbl">oct+</div></div> 380 + <div class="qcap ghost">/</div> 381 + </div> 382 + </div> 383 + </div> 384 + </div> 385 + 386 + <div class="below-right"> 387 + <div class="rationale"> 388 + <h3>Why this <span class="accent">layout</span></h3> 389 + <ul> 390 + <li><b>Notes that name themselves.</b> <span class="keys">C D E F G A B</span> play the notes they name — anyone who knows the Western scale already knows it.</li> 391 + <li><b>Sharps <i>mostly</i> sit above their naturals.</b> <span class="keys">V S W R Q</span> = C♯ D♯ F♯ G♯ A♯ — close to piano black keys, with a few exceptions.</li> 392 + <li><b>Two-handed by default.</b> Lower octave under the left hand; upper octave (<span class="keys">H J K L ;</span>) mirrors it under the right.</li> 393 + <li><b>Social software.</b> The instrument shares the keyboard everyone already owns — musical participation isn't gated by software literacy.</li> 394 + </ul> 395 + </div> 396 + 397 + <div class="below-bottom"> 398 + <div class="impls"> 399 + <h3>Notepat lives in</h3> 400 + <ul class="impl-list"> 401 + <li><b>notepat.com</b><span class="where">web</span></li> 402 + <li><b>AC Native</b><span class="where">AC OS</span></li> 403 + <li><b>MenuBand</b><span class="where">macOS</span></li> 404 + <li><b>AC-Notepat</b><span class="where">Max for Live</span></li> 405 + <li><b>Remote</b><span class="where">phone</span></li> 406 + </ul> 407 + </div> 408 + <div class="row"> 409 + <div class="qrs"> 410 + <div class="qr"> 411 + <img id="qr1" alt="notepat.com"> 412 + <div class="label">https://notepat.com</div> 413 + </div> 414 + <div class="qr"> 415 + <img id="qr2" alt="prompt.ac/menuband"> 416 + <div class="label">https://prompt.ac/menuband</div> 417 + </div> 418 + </div> 419 + </div> 420 + </div> 421 + </div> 422 + </div> 423 + 424 + <!-- SVG connector overlay: fading dashed lines from each lower-octave 425 + natural on the piano down to the QWERTY key with the same letter. 426 + Helps the viewer SEE that C·D·E·F·G·A·B name themselves. --> 427 + <svg class="connectors" id="connectors"></svg> 428 + 429 + <script> 430 + document.getElementById("qr1").src = "__QR_NOTEPAT__"; 431 + document.getElementById("qr2").src = "__QR_MENUBAND__"; 432 + document.getElementById("pals").src = "__PALS_LOGO__"; 433 + document.getElementById("jeffrey").src = "__JEFFREY_PIC__"; 434 + 435 + // Draw the piano→qwerty connectors. Each line uses notepat.mjs's 436 + // per-note color (C=red, D=orange, E=yellow, F=green, G=blue, 437 + // A=purple, B=brown). Opacity fades as the scale rises so C reads 438 + // strongest. Lines run center-to-center, drawn over the keys. 439 + function drawConnectors() { 440 + const svg = document.getElementById("connectors"); 441 + // Only the first five naturals — past G the visual gets noisy 442 + // and the point ("notes name themselves") is already made. 443 + const naturals = [ 444 + { n: "c", color: "rgb(255, 0, 0)", op: 0.95 }, // red — strongest 445 + { n: "d", color: "rgb(255, 140, 0)", op: 0.55 }, // orange 446 + { n: "e", color: "rgb(212, 180, 0)", op: 0.32 }, // yellow 447 + { n: "f", color: "rgb(0, 128, 0)", op: 0.18 }, // green 448 + { n: "g", color: "rgb(0, 0, 220)", op: 0.10 }, // blue — barely there 449 + ]; 450 + const NS = "http://www.w3.org/2000/svg"; 451 + for (const { n, color, op } of naturals) { 452 + const piano = document.querySelector(`.wkey[data-pn="${n}"]`); 453 + const qwerty = document.querySelector(`.qcap[data-qn="${n}"]`); 454 + if (!piano || !qwerty) continue; 455 + const pr = piano.getBoundingClientRect(); 456 + const qr = qwerty.getBoundingClientRect(); 457 + const x1 = pr.left + pr.width / 2; 458 + const y1 = pr.top + pr.height / 2; 459 + const x2 = qr.left + qr.width / 2; 460 + const y2 = qr.top + qr.height / 2; 461 + // The connecting dashed line. 462 + const line = document.createElementNS(NS, "line"); 463 + line.setAttribute("x1", x1); 464 + line.setAttribute("y1", y1); 465 + line.setAttribute("x2", x2); 466 + line.setAttribute("y2", y2); 467 + line.setAttribute("stroke", color); 468 + line.setAttribute("stroke-width", "5"); 469 + line.setAttribute("stroke-dasharray", "10 8"); 470 + line.setAttribute("stroke-linecap", "round"); 471 + line.setAttribute("opacity", op); 472 + svg.appendChild(line); 473 + // Endpoint dots — small + see-through so the letter underneath 474 + // still reads. 475 + const dotOp = Math.min(0.55, op * 0.55); 476 + for (const [cx, cy] of [[x1, y1], [x2, y2]]) { 477 + const dot = document.createElementNS(NS, "circle"); 478 + dot.setAttribute("cx", cx); 479 + dot.setAttribute("cy", cy); 480 + dot.setAttribute("r", 6); 481 + dot.setAttribute("fill", color); 482 + dot.setAttribute("opacity", dotOp); 483 + svg.appendChild(dot); 484 + } 485 + } 486 + } 487 + drawConnectors(); 488 + </script> 489 + </body> 490 + </html>