{"roots":["2:2"],"nodeById":{"2:2":{"type":"WEBPAGE","id":"2:2","name":"/fretboard","absoluteBoundingBox":{"x":1887.0,"y":0.0,"width":1847.0,"height":1017.0},"isolatedAbsoluteRenderBounds":{"x":1887.0,"y":0.0,"width":1847.0,"height":1017.0},"relativeTransform":[[1.0,0.0,1887.0],[0.0,1.0,0.0]],"size":{"x":1847.0,"y":1017.0},"fills":[{"opacity":0.0470588244497776,"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":1.0,"b":1.0,"a":1.0},"visible":true}],"strokeAlign":"INSIDE","strokes":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"children":["2:3","2:5"]},"10:248":{"type":"TEXT","id":"10:248","name":"Refresh the page if the widget doesn’t load","absoluteBoundingBox":{"x":3363.5,"y":213.0,"width":238.0,"height":16.0},"isolatedAbsoluteRenderBounds":{"x":3364.48388671875,"y":217.0,"width":235.671875,"height":11.5440063476562},"relativeTransform":[[1.0,0.0,68.5],[0.0,1.0,113.0]],"size":{"x":238.0,"y":16.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.292217552661896,"g":0.292217552661896,"b":0.292217552661896,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Refresh the page if the widget doesn’t load","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"resolvedParagraphValues":{"0":{"listSpacing":0.0,"paragraphIndent":0.0,"paragraphSpacing":0.0}},"textAutoResize":"WIDTH_AND_HEIGHT","textAlignVertical":"CENTER","style":{"fontFamily":"IBM Plex Sans","fontPostScriptName":"IBMPlexSans-SemiBold","fontStyle":"SemiBold","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":12.0,"textAlignHorizontal":"LEFT","textAlignVertical":"CENTER","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":15.5999994277954,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"7:77":{"type":"FRAME","id":"7:77","name":"Button","absoluteBoundingBox":{"x":3378.0,"y":120.0,"width":272.0,"height":53.0},"isolatedAbsoluteRenderBounds":{"x":3378.0,"y":120.0,"width":272.0,"height":53.0},"relativeTransform":[[1.0,0.0,63.0],[0.0,1.0,0.0]],"size":{"x":272.0,"y":53.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.831372559070587,"b":0.364705890417099,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","layoutGrow":1.0,"strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"rectangleCornerRadii":[200.0,200.0,200.0,200.0],"cornerRadius":200.0,"paddingTop":10.0,"paddingRight":20.0,"paddingBottom":12.0,"paddingLeft":20.0,"clipsContent":true,"layoutMode":"HORIZONTAL","itemSpacing":40.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["7:78"]},"7:98":{"type":"FRAME","id":"7:98","name":"Button","absoluteBoundingBox":{"x":3315.0,"y":120.0,"width":53.0,"height":53.0},"isolatedAbsoluteRenderBounds":{"x":3315.0,"y":120.0,"width":53.0,"height":53.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":53.0,"y":53.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":0.564705908298492,"b":0.800000011920929,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":2,"localID":162},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":0,"localID":3},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/"}],"isDeleted":false,"stateManagementVersion":1}],"rectangleCornerRadii":[200.0,200.0,200.0,200.0],"cornerRadius":200.0,"paddingTop":10.0,"paddingRight":20.0,"paddingBottom":12.0,"paddingLeft":20.0,"clipsContent":true,"layoutMode":"HORIZONTAL","itemSpacing":20.0,"counterAxisAlignItems":"CENTER","primaryAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","counterAxisSizingMode":"FIXED","children":["7:99","7:100"]},"7:99":{"type":"TEXT","id":"7:99","name":"Home","absoluteBoundingBox":{"x":3335.0,"y":130.0,"width":66.0,"height":31.0},"isolatedAbsoluteRenderBounds":{"x":3335.0,"y":130.0,"width":66.0,"height":31.0},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,10.0]],"size":{"x":66.0,"y":31.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.123685397207737,"g":0.123685397207737,"b":0.123685397207737,"a":1.0},"visible":true,"opacity":1.0}],"visible":false,"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Home","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"resolvedParagraphValues":{"0":{"listSpacing":0.0,"paragraphIndent":0.0,"paragraphSpacing":0.0}},"textAutoResize":"WIDTH_AND_HEIGHT","textAlignVertical":"CENTER","style":{"fontFamily":"IBM Plex Sans","fontPostScriptName":"IBMPlexSans-SemiBold","fontStyle":"SemiBold","textCase":"TITLE","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":24.0,"textAlignHorizontal":"LEFT","textAlignVertical":"CENTER","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":31.1999988555908,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"2:3":{"type":"FRAME","id":"2:3","name":"Desktop","absoluteBoundingBox":{"x":1951.0,"y":100.0,"width":1280.0,"height":853.0},"isolatedAbsoluteRenderBounds":{"x":1951.0,"y":100.0,"width":1280.0,"height":853.0},"relativeTransform":[[1.0,0.0,64.0],[0.0,1.0,100.0]],"size":{"x":1280.0,"y":853.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.123685397207737,"g":0.123685397207737,"b":0.123685397207737,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"paddingTop":20.0,"paddingRight":20.0,"paddingBottom":20.0,"paddingLeft":20.0,"clipsContent":true,"overflowDirection":"VERTICAL_SCROLLING","layoutMode":"VERTICAL","itemSpacing":40.0,"counterAxisAlignItems":"CENTER","counterAxisSizingMode":"FIXED","isBreakpointFrame":true,"children":["7:111","10:247","2:6"]},"2:6":{"type":"WIDGET","id":"2:6","name":"Embed 1","absoluteBoundingBox":{"x":1971.0,"y":274.0,"width":1240.0,"height":659.0},"isolatedAbsoluteRenderBounds":{"x":1971.0,"y":274.0,"width":1240.0,"height":659.0},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,174.0]],"size":{"x":1240.0,"y":659.0},"layoutAlign":"STRETCH","accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"syncedState":{"embedAllowFullscreen":"false","embedCodeType":"html","embedIframeHtml":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Fretboard Drill</title>\n<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n<style>\n  :root{\n    --board:#202020; --edge:#151515; --bone:#F8F8F8; --wire:#4A4A4A;\n    --brass:#00D45D; --ink:#151515; --moss:#00D45D; --rust:#FF4C56;\n    --dim:#8A8A8A; --panel:#202020; --line:#2E2E2E;\n    --orange:#FF7A18; --blue:#6A89FF; --pink:#FF90CC; --sky:#8FD0FF;\n  }\n  *{box-sizing:border-box}\n  html,body{max-width:100%;overflow-x:hidden}\n  body{margin:0;background:#151515;color:var(--bone);\n       font-family:'IBM Plex Sans',system-ui,sans-serif;padding:20px 16px 48px}\n  .wrap{max-width:920px;margin:0 auto}\n  h1{font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:26px;letter-spacing:.5px;margin:0}\n  .mono{font-family:'IBM Plex Mono',monospace}\n  .top{display:flex;align-items:baseline;justify-content:flex-start;flex-wrap:wrap;gap:8px;margin-bottom:14px}\n  .stats{font-family:'IBM Plex Mono',monospace;font-size:13px;color:var(--dim);display:flex;gap:18px;flex-wrap:wrap;letter-spacing:.5px}\n  .stats b{color:var(--bone)}\n  .stats b.gold{color:var(--brass)}\n  .modes{display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap}\n  .mode{font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:15px;padding:12px 22px;\n        border-radius:999px;cursor:pointer;border:none;background:#252525;color:var(--bone)}\n  .mode:hover{background:#2E2E2E}\n  .mode[aria-pressed=\"true\"]{background:var(--brass);color:var(--ink)}\n  .bar{background:var(--panel);color:var(--bone);border:1px solid var(--line);border-radius:24px;\n       padding:22px 24px;margin-bottom:20px;display:flex;align-items:center;\n       justify-content:space-between;gap:20px;flex-wrap:wrap;min-height:120px}\n  .bar.ok{border-color:var(--brass)} .bar.no{border-color:var(--rust)}\n  .cue{font-family:'IBM Plex Sans',sans-serif;font-weight:700;font-size:54px;line-height:1;\n       min-width:64px;color:var(--brass)}\n  .bar.no .cue{color:var(--rust)}\n  #staff{color:var(--brass);flex-shrink:0}\n  #staff svg{width:76px;height:auto;max-height:110px;display:block}\n  .bar.no #staff{color:var(--rust)}\n  .prompt{display:flex;align-items:center;gap:18px;flex:1 1 340px;min-width:0}\n  .hint{font-family:'IBM Plex Sans',sans-serif;font-weight:500;font-size:17px;line-height:1.45;\n        color:var(--dim);flex:1 1 auto;min-width:0}\n  .hint b{font-weight:700;color:var(--bone)}\n  .ctrl{display:flex;align-items:center;gap:10px;flex-shrink:0}\n  .ghost{font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:14px;padding:11px 20px;\n         border-radius:999px;border:none;background:#252525;color:var(--bone);cursor:pointer}\n  .ghost:hover{background:var(--brass);color:var(--ink)}\n  .clock{font-family:'IBM Plex Mono',monospace;font-size:30px;font-weight:500;color:var(--brass);\n         min-width:56px;text-align:right}\n  .clock.low{color:var(--rust)}\n  .scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:6px;max-width:100%}\n  .neck{min-width:720px;max-width:100%}\n  .nums,.row{display:grid;grid-template-columns:34px 44px repeat(var(--frets),1fr)}\n  .grid{display:flex;flex-direction:column;width:100%}\n  .row{position:relative}\n  .strline{position:absolute;left:78px;right:0;top:50%;transform:translateY(-50%);pointer-events:none}\n  .nums div{text-align:center;font-family:'IBM Plex Mono',monospace;font-size:10px;color:#8A8A8A;padding-bottom:4px}\n  .nums div.twelve{color:var(--brass)}\n  .grid{background:var(--board);border:none;border-radius:0;overflow:hidden}\n  .row.dim .strline{opacity:.45}\n  .sbtn{background:var(--panel);border:none;border-right:1px solid var(--edge);height:44px;padding:0;\n        font-family:'IBM Plex Mono',monospace;font-size:11px;color:#5A5A5A;cursor:pointer}\n  .sbtn.on{color:var(--bone)}\n  .sbtn span{opacity:.5}\n  .fret{position:relative;height:44px;border:none;padding:0;background:transparent;cursor:pointer;\n        border-right:2px solid var(--wire);transition:opacity .2s}\n  .fret.nut{background:#1A1A1A;border-right:4px solid var(--dim)}\n  .fret.off{opacity:.4;cursor:default}\n  .inlay{position:absolute;inset:0;margin:auto;width:8px;height:8px;border-radius:50%;\n         background:rgba(248,248,248,.14)}\n  .dot{position:absolute;inset:0;margin:auto;width:30px;height:30px;border-radius:50%;\n       display:flex;align-items:center;justify-content:center;font-family:'IBM Plex Mono',monospace;\n       font-size:12px;font-weight:500;box-shadow:0 1px 4px rgba(0,0,0,.5)}\n  .keys{display:grid;grid-template-columns:repeat(auto-fit,minmax(56px,1fr));gap:6px;margin-top:16px}\n  .key{padding:0;height:46px;border-radius:999px;border:none;background:#252525;\n       color:var(--bone);cursor:pointer;font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:15px;\n       display:flex;align-items:center;justify-content:center;text-align:center;line-height:1}\n  .key:hover{background:var(--brass);color:var(--ink)}\n  .set{margin-top:18px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;\n       font-family:'IBM Plex Mono',monospace;font-size:11px;color:#8A8A8A}\n  input[type=range]{accent-color:var(--brass);width:140px}\n  button:focus-visible{outline:2px solid var(--brass);outline-offset:1px}\n  @media (max-width:640px){\n    body{padding:12px 12px 32px}\n    .bar{flex-direction:column;align-items:stretch;gap:10px;padding:14px;border-radius:18px;\n         min-height:0}\n    .prompt{flex:0 1 auto;align-items:center;gap:10px;flex-wrap:nowrap}\n    .cue{font-size:34px;min-width:0;flex-shrink:0}\n    #staff svg{width:60px;max-height:76px}\n    .hint{font-size:14px;line-height:1.35}\n    .ctrl{justify-content:flex-end;gap:8px}\n    .ghost{padding:8px 14px;font-size:13px}\n    .clock{font-size:24px;min-width:44px}\n    .mode{font-size:13px;padding:9px 14px}\n    .modes{gap:6px;margin-bottom:10px}\n    .keys{grid-template-columns:repeat(6,1fr);gap:5px}\n    .key{height:42px;font-size:14px}\n    /* keep frets comfortably wide and let the board scroll sideways */\n    .neck{min-width:0;width:max-content}\n    .nums,.row{grid-template-columns:26px 34px repeat(var(--frets),46px)}\n    .strline{left:60px}\n    .neck,.nums,.grid,.row{width:max-content;min-width:100%}\n    .scroll{overflow-x:auto;margin:0 -12px;padding:0 12px 8px}\n    .sbtn{height:40px;font-size:10px}\n    .sbtn span{display:none}\n    .fret{height:40px}\n    .dot{width:26px;height:26px;font-size:10px}\n    .nums div{font-size:10px}\n    .set{font-size:10px;gap:8px}\n    input[type=range]{width:110px}\n  }\n  @media (prefers-reduced-motion:reduce){*{transition:none!important}}\n</style>\n</head>\n<body>\n<div class=\"wrap\">\n  <div class=\"top\">\n    <div class=\"stats\">\n      <span>ACC <b id=\"acc\">0%</b></span>\n      <span>STREAK <b class=\"gold\" id=\"streak\">0</b></span>\n      <span>BEST <b id=\"best\">0</b></span>\n      <span>AVG <b id=\"avg\">—</b></span>\n    </div>\n  </div>\n\n  <div class=\"modes\" id=\"modes\">\n    <button class=\"mode\" data-m=\"find\">Find the note</button>\n    <button class=\"mode\" data-m=\"name\">Name the fret</button>\n    <button class=\"mode\" data-m=\"beat\">Beat the clock</button>\n    <button class=\"mode\" data-m=\"all\">Select All</button>\n    <button class=\"mode\" data-m=\"explore\">Explore</button>\n  </div>\n\n  <div class=\"bar\" id=\"bar\">\n    <div class=\"prompt\">\n      <div class=\"cue\" id=\"cue\"></div>\n      <div id=\"staff\"></div>\n      <div class=\"hint\" id=\"hint\"></div>\n    </div>\n    <div class=\"ctrl\">\n      <div class=\"clock mono\" id=\"clock\"></div>\n      <button class=\"ghost\" id=\"show\">Show me</button>\n      <button class=\"ghost\" id=\"skip\">Skip</button>\n    </div>\n  </div>\n\n  <div class=\"scroll\">\n    <div class=\"neck\">\n      <div class=\"nums\" id=\"nums\"></div>\n      <div class=\"grid\" id=\"grid\"></div>\n    </div>\n  </div>\n\n  <div class=\"keys\" id=\"keys\" style=\"display:none\"></div>\n\n  <div class=\"set\">\n    <span>FRETS 0–<b id=\"fmax\" style=\"color:var(--bone)\">12</b></span>\n    <input type=\"range\" id=\"range\" min=\"5\" max=\"15\" value=\"12\" />\n    <span style=\"color:#5A5A5A\">|</span>\n    <span>Tap a string number on the left to add or remove it from the drill.</span>\n  </div>\n</div>\n\n<script>\nconst CHROMA=[\"C\",\"C#\",\"D\",\"D#\",\"E\",\"F\",\"F#\",\"G\",\"G#\",\"A\",\"A#\",\"B\"];\nconst STRINGS=[\n  {label:\"1\",name:\"e\",open:4,g:1},{label:\"2\",name:\"B\",open:11,g:1.4},\n  {label:\"3\",name:\"G\",open:7,g:1.9},{label:\"4\",name:\"D\",open:2,g:2.6},\n  {label:\"5\",name:\"A\",open:9,g:3.4},{label:\"6\",name:\"E\",open:4,g:4.4}];\nconst DOTS=[3,5,7,9,12,15];\nconst nameAt=(s,f)=>CHROMA[(STRINGS[s].open+f)%12];\n\nlet mode=\"find\", allowed=[5,4], maxFret=12;\nlet target=null, feedback=null, reveal=false, sel=null, locked=false;\nlet found=[];\nlet hit=0,miss=0,streak=0,best=0,times=[],t0=Date.now();\nlet timerId=null, left=0;\n\nconst $=id=>document.getElementById(id);\n\nfunction newTarget(){\n  const pool=allowed.length?allowed:[5];\n  feedback=null; reveal=false; locked=false; found=[]; t0=Date.now();\n  if(mode===\"all\"){\n    const pcs=[...new Set(pool.flatMap(s=>Array.from({length:maxFret+1},(_,f)=>(STRINGS[s].open+f)%12)))];\n    target={p:pcs[Math.floor(Math.random()*pcs.length)]};\n    render(); return;\n  }\n  const s=pool[Math.floor(Math.random()*pool.length)];\n  const f=Math.floor(Math.random()*(maxFret+1));\n  target={s,f};\n  if(mode===\"beat\") startClock();\n  render();\n}\nfunction startClock(){\n  stopClock(); left=5.0;\n  timerId=setInterval(()=>{\n    left-=0.1;\n    if(left<=0){ stopClock(); locked=true; feedback={ok:false,s:target.s,f:target.f}; score(false);\n      setTimeout(newTarget,900); }\n    render();\n  },100);\n}\nfunction stopClock(){ if(timerId){clearInterval(timerId); timerId=null;} }\n\nfunction score(ok){\n  const secs=(Date.now()-t0)/1000;\n  if(ok){hit++; streak++; best=Math.max(best,streak); times.push(secs); if(times.length>20)times.shift();}\n  else {miss++; streak=0;}\n}\nconst MIDI=[64,59,55,50,45,40];              // sounding pitch of open strings 1..6\nconst LETTER={0:0,1:0,2:1,3:1,4:2,5:3,6:3,7:4,8:4,9:5,10:5,11:6}; // pc -> C D E F G A B\nconst SHARP=[0,1,0,1,0,0,1,0,1,0,1,0];\n// Guitar is notated an octave above sounding pitch: written = sounding + 12.\nfunction staffSVG(midi){\n  const pc=midi%12, oct=Math.floor(midi/12)-1;\n  const step=oct*7+LETTER[pc];               // diatonic step; E4 (bottom line) = 30\n  const H=8, top=14, bottomLine=top+4*H;     // lines at steps 30,32,34,36,38\n  const yOf=s=>bottomLine-(s-30)*(H/2);\n  const y=yOf(step), x=46;\n  let g=\"\";\n  for(let i=0;i<5;i++) g+=`<line x1=\"6\" x2=\"68\" y1=\"${top+i*H}\" y2=\"${top+i*H}\" stroke=\"currentColor\" stroke-width=\"1\"/>`;\n  for(let s=28;s>=step;s-=2) g+=`<line x1=\"${x-10}\" x2=\"${x+10}\" y1=\"${yOf(s)}\" y2=\"${yOf(s)}\" stroke=\"currentColor\" stroke-width=\"1\"/>`;\n  for(let s=40;s<=step;s+=2) g+=`<line x1=\"${x-10}\" x2=\"${x+10}\" y1=\"${yOf(s)}\" y2=\"${yOf(s)}\" stroke=\"currentColor\" stroke-width=\"1\"/>`;\n  g+=`<ellipse cx=\"${x}\" cy=\"${y}\" rx=\"6\" ry=\"4.4\" fill=\"currentColor\" transform=\"rotate(-18 ${x} ${y})\"/>`;\n  const up=step<34;\n  g+= up\n    ? `<line x1=\"${x+5.7}\" x2=\"${x+5.7}\" y1=\"${y-1}\" y2=\"${y-26}\" stroke=\"currentColor\" stroke-width=\"1.6\"/>`\n    : `<line x1=\"${x-5.7}\" x2=\"${x-5.7}\" y1=\"${y+1}\" y2=\"${y+26}\" stroke=\"currentColor\" stroke-width=\"1.6\"/>`;\n  if(SHARP[pc]) g+=`<text x=\"${x-24}\" y=\"${y+5}\" font-size=\"16\" fill=\"currentColor\" font-family=\"serif\">♯</text>`;\n  const yTop=Math.min(y-32, top-10), yBot=Math.max(y+32, bottomLine+18);\n  return `<svg viewBox=\"0 ${yTop} 76 ${yBot-yTop}\" preserveAspectRatio=\"xMidYMid meet\" aria-hidden=\"true\">\n    <text x=\"4\" y=\"49\" font-size=\"48\" fill=\"currentColor\" opacity=\".55\" font-family=\"serif\">𝄞</text>${g}</svg>`;\n}\nfunction positionsOf(p){\n  const pool=allowed.length?allowed:[5];\n  const out=[];\n  for(const s of pool) for(let f=0;f<=maxFret;f++)\n    if((STRINGS[s].open+f)%12===p) out.push(s+\":\"+f);\n  return out;\n}\nfunction tapFret(s,f){\n  if(mode===\"explore\"){ sel={s,f}; render(); return; }\n  if(locked||!target||!allowed.includes(s)) return;\n  if(mode===\"name\") return;\n  if(mode===\"all\"){\n    const key=s+\":\"+f;\n    if((STRINGS[s].open+f)%12===target.p){\n      if(!found.includes(key)){\n        found.push(key); hit++; streak++; best=Math.max(best,streak);\n        if(found.length===positionsOf(target.p).length){\n          locked=true;\n          times.push((Date.now()-t0)/1000); if(times.length>20)times.shift();\n          setTimeout(newTarget,900);\n        }\n      }\n    } else {\n      miss++; streak=0; feedback={ok:false,s,f};\n      setTimeout(()=>{ if(feedback&&!feedback.ok&&feedback.s===s&&feedback.f===f){feedback=null;render();} },600);\n    }\n    render(); return;\n  }\n  const ok=nameAt(s,f)===nameAt(target.s,target.f);\n  feedback={ok,s,f}; score(ok); if(ok){locked=true; stopClock(); setTimeout(newTarget,700);}\n  render();\n}\nfunction answerName(n){\n  if(locked||!target||mode!==\"name\") return;\n  const ok=n===nameAt(target.s,target.f);\n  feedback={ok,s:target.s,f:target.f}; score(ok);\n  if(ok){locked=true; setTimeout(newTarget,700);}\n  render();\n}\nfunction octaves(s,f){\n  const out=[]; const p=(STRINGS[s].open+f)%12;\n  for(let ss=0;ss<6;ss++) for(let ff=0;ff<=maxFret;ff++)\n    if((STRINGS[ss].open+ff)%12===p && !(ss===s&&ff===f)) out.push(ss+\":\"+ff);\n  return out;\n}\n\nfunction render(){\n  const total=hit+miss;\n  $(\"acc\").textContent=(total?Math.round(hit/total*100):0)+\"%\";\n  $(\"streak\").textContent=streak; $(\"best\").textContent=best;\n  $(\"avg\").textContent=times.length?(times.reduce((a,b)=>a+b,0)/times.length).toFixed(1)+\"s\":\"—\";\n  $(\"fmax\").textContent=maxFret;\n  document.querySelectorAll(\".mode\").forEach(b=>b.setAttribute(\"aria-pressed\",b.dataset.m===mode));\n\n  const drill=(mode===\"find\"||mode===\"beat\");\n  $(\"keys\").style.display=mode===\"name\"?\"grid\":\"none\";\n  $(\"show\").style.display=(drill||mode===\"all\")?\"\":\"none\";\n  $(\"skip\").style.display=mode===\"explore\"?\"none\":\"\";\n  const cl=$(\"clock\");\n  cl.textContent=mode===\"beat\"?Math.max(0,left).toFixed(1):\"\";\n  cl.className=\"clock mono\"+(mode===\"beat\"&&left<2?\" low\":\"\");\n\n  const cue=$(\"cue\"), hint=$(\"hint\");\n  $(\"bar\").className=\"bar\"+((feedback&&!feedback.ok)?\" no\":((feedback&&feedback.ok)||locked?\" ok\":\"\"));\n  if(mode===\"explore\"){\n    cue.textContent=sel?nameAt(sel.s,sel.f):\"\"; cue.className=\"cue\";\n    hint.innerHTML=sel\n      ? `String ${STRINGS[sel.s].label}, fret ${sel.f}. Every other <b>${nameAt(sel.s,sel.f)}</b> on the neck is lit.`\n      : \"Tap any fret to see its note and every octave of it across the neck.\";\n  } else if(mode===\"name\"){\n    cue.textContent=\"?\"; cue.className=\"cue\";\n    hint.innerHTML=\"What note is the <b style='color:var(--sky)'>marked fret</b>?\";\n  } else if(mode===\"all\"){\n    cue.textContent=target?CHROMA[target.p]:\"\";\n    cue.className=\"cue\";\n    const totalPos=target?positionsOf(target.p).length:0;\n    hint.innerHTML=target\n      ? `Find <b>every ${CHROMA[target.p]}</b> on the active strings. <b>${found.length}</b> of <b>${totalPos}</b> found.`\n      : \"\";\n  } else {\n    cue.textContent=target?nameAt(target.s,target.f):\"\";\n    cue.className=\"cue\";\n    hint.innerHTML=target\n      ? `Find <b>${nameAt(target.s,target.f)}</b> on string <b>${STRINGS[target.s].label}</b> (${STRINGS[target.s].name}).`\n        +(mode===\"beat\"?\"<br>Five seconds. Any octave on that string counts.\":\"<br>Any octave of it on that string counts.\")\n      : \"\";\n  }\n\n  let midi=null;\n  if(mode===\"explore\"&&sel) midi=MIDI[sel.s]+sel.f+12;\n  else if(mode===\"all\"&&target) midi=60+target.p;\n  else if(drill&&target) midi=MIDI[target.s]+target.f+12;\n  $(\"staff\").innerHTML=midi!==null?staffSVG(midi):\"\";\n\n  const partners = (mode===\"explore\"&&sel)?octaves(sel.s,sel.f):[];\n  $(\"nums\").style.setProperty(\"--frets\",maxFret);\n\n\n  let n='<div></div>';\n  for(let f=0;f<=maxFret;f++) n+=`<div class=\"${f===12?'twelve':''}\">${f}</div>`;\n  $(\"nums\").innerHTML=n;\n\n  let g=\"\";\n  for(let s=0;s<6;s++){\n    const on=allowed.includes(s);\n    g+=`<div class=\"row ${(mode!==\"explore\"&&!on)?'dim':''}\" style=\"--frets:${maxFret}\">\n        <span class=\"strline\" style=\"height:${STRINGS[s].g}px;background:${s>2?'#5E5E5E':'#7E7E7E'}\"></span>\n        <button class=\"sbtn ${on?'on':''}\" data-s=\"${s}\" title=\"Toggle string\">${STRINGS[s].label}<span> ${STRINGS[s].name}</span></button>`;\n    for(let f=0;f<=maxFret;f++){\n      const isT=target&&target.s===s&&target.f===f;\n      const fb=feedback&&feedback.s===s&&feedback.f===f;\n      const rv=reveal&&drill&&target&&s===target.s&&nameAt(s,f)===nameAt(target.s,target.f);\n      const isSel=sel&&sel.s===s&&sel.f===f;\n      const isP=partners.includes(s+\":\"+f);\n      const dim=mode!==\"explore\"&&!on;\n      let dot=null;\n      if(mode===\"all\"&&target){\n        const key=s+\":\"+f;\n        if(found.includes(key)) dot={bg:\"var(--moss)\",fg:\"#fff\",t:nameAt(s,f)};\n        else if(fb) dot={bg:\"var(--rust)\",fg:\"#fff\",t:nameAt(s,f)};\n        else if(reveal&&on&&(STRINGS[s].open+f)%12===target.p) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      }\n      else if(mode===\"name\"&&isT&&!feedback) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:\"?\"};\n      else if(fb) dot={bg:feedback.ok?\"var(--moss)\":\"var(--rust)\",fg:\"#fff\",t:nameAt(s,f)};\n      else if(rv) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      else if(isSel) dot={bg:\"var(--bone)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      else if(isP) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      g+=`<button class=\"fret ${f===0?'nut':''} ${dim?'off':''}\" data-s=\"${s}\" data-f=\"${f}\"\n            aria-label=\"String ${STRINGS[s].label}, fret ${f}\">\n            ${(s===2&&DOTS.includes(f)&&!dot)?'<span class=\"inlay\"></span>':''}\n            ${dot?`<span class=\"dot\" style=\"background:${dot.bg};color:${dot.fg}\">${dot.t}</span>`:''}\n          </button>`;\n    }\n    g+=`</div>`;\n  }\n  $(\"grid\").innerHTML=g;\n  $(\"grid\").querySelectorAll(\".sbtn\").forEach(b=>b.onclick=()=>{\n    const i=+b.dataset.s;\n    allowed=allowed.includes(i)?allowed.filter(x=>x!==i):[...allowed,i];\n    if(mode===\"all\") newTarget(); else render();\n  });\n  $(\"grid\").querySelectorAll(\".fret\").forEach(b=>b.onclick=()=>tapFret(+b.dataset.s,+b.dataset.f));\n\n  if(!$(\"keys\").dataset.built){\n    $(\"keys\").innerHTML=CHROMA.map(x=>`<button class=\"key\" data-n=\"${x}\">${x}</button>`).join(\"\");\n    $(\"keys\").querySelectorAll(\".key\").forEach(b=>b.onclick=()=>answerName(b.dataset.n));\n    $(\"keys\").dataset.built=\"1\";\n  }\n}\n\ndocument.querySelectorAll(\".mode\").forEach(b=>b.onclick=()=>{\n  mode=b.dataset.m; sel=null; stopClock();\n  if(mode===\"explore\"){target=null;feedback=null;render();} else newTarget();\n});\n$(\"skip\").onclick=newTarget;\n$(\"show\").onclick=()=>{reveal=true;render();};\n$(\"range\").oninput=e=>{maxFret=+e.target.value; if(mode!==\"explore\")newTarget(); else render();};\n\nnewTarget();\n</script>\n</body>\n</html>","embedURL":""}},"10:247":{"type":"TEXT","id":"10:247","name":"Refresh the page if the widget doesn’t load","absoluteBoundingBox":{"x":2432.5,"y":213.0,"width":317.0,"height":21.0},"isolatedAbsoluteRenderBounds":{"x":2433.81201171875,"y":218.0,"width":314.22900390625,"height":15.3919982910156},"relativeTransform":[[1.0,0.0,481.5],[0.0,1.0,113.0]],"size":{"x":317.0,"y":21.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.292217552661896,"g":0.292217552661896,"b":0.292217552661896,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Refresh the page if the widget doesn’t load","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"resolvedParagraphValues":{"0":{"listSpacing":0.0,"paragraphIndent":0.0,"paragraphSpacing":0.0}},"textAutoResize":"WIDTH_AND_HEIGHT","textAlignVertical":"CENTER","style":{"fontFamily":"IBM Plex Sans","fontPostScriptName":"IBMPlexSans-SemiBold","fontStyle":"SemiBold","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":16.0,"textAlignHorizontal":"LEFT","textAlignVertical":"CENTER","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":20.7999992370605,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textCase":"ORIGINAL","textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"7:111":{"type":"FRAME","id":"7:111","name":"Header","absoluteBoundingBox":{"x":1971.0,"y":120.0,"width":1240.0,"height":53.0},"isolatedAbsoluteRenderBounds":{"x":1971.0,"y":120.0,"width":1240.0,"height":53.0},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,20.0]],"size":{"x":1240.0,"y":53.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":10.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["7:93","7:72"]},"2:10":{"type":"WIDGET","id":"2:10","name":"Embed 1","absoluteBoundingBox":{"x":3315.0,"y":269.0,"width":335.0,"height":660.0},"isolatedAbsoluteRenderBounds":{"x":3315.0,"y":269.0,"width":335.0,"height":660.0},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,169.0]],"size":{"x":335.0,"y":660.0},"layoutAlign":"STRETCH","accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"syncedState":{"embedAllowFullscreen":"false","embedCodeType":"html","embedIframeHtml":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Fretboard Drill</title>\n<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n<style>\n  :root{\n    --board:#202020; --edge:#151515; --bone:#F8F8F8; --wire:#4A4A4A;\n    --brass:#00D45D; --ink:#151515; --moss:#00D45D; --rust:#FF4C56;\n    --dim:#8A8A8A; --panel:#202020; --line:#2E2E2E;\n    --orange:#FF7A18; --blue:#6A89FF; --pink:#FF90CC; --sky:#8FD0FF;\n  }\n  *{box-sizing:border-box}\n  html,body{max-width:100%;overflow-x:hidden}\n  body{margin:0;background:#151515;color:var(--bone);\n       font-family:'IBM Plex Sans',system-ui,sans-serif;padding:20px 16px 48px}\n  .wrap{max-width:920px;margin:0 auto}\n  h1{font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:26px;letter-spacing:.5px;margin:0}\n  .mono{font-family:'IBM Plex Mono',monospace}\n  .top{display:flex;align-items:baseline;justify-content:flex-start;flex-wrap:wrap;gap:8px;margin-bottom:14px}\n  .stats{font-family:'IBM Plex Mono',monospace;font-size:13px;color:var(--dim);display:flex;gap:18px;flex-wrap:wrap;letter-spacing:.5px}\n  .stats b{color:var(--bone)}\n  .stats b.gold{color:var(--brass)}\n  .modes{display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap}\n  .mode{font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:15px;padding:12px 22px;\n        border-radius:999px;cursor:pointer;border:none;background:#252525;color:var(--bone)}\n  .mode:hover{background:#2E2E2E}\n  .mode[aria-pressed=\"true\"]{background:var(--brass);color:var(--ink)}\n  .bar{background:var(--panel);color:var(--bone);border:1px solid var(--line);border-radius:24px;\n       padding:22px 24px;margin-bottom:20px;display:flex;align-items:center;\n       justify-content:space-between;gap:20px;flex-wrap:wrap;min-height:120px}\n  .bar.ok{border-color:var(--brass)} .bar.no{border-color:var(--rust)}\n  .cue{font-family:'IBM Plex Sans',sans-serif;font-weight:700;font-size:54px;line-height:1;\n       min-width:64px;color:var(--brass)}\n  .bar.no .cue{color:var(--rust)}\n  #staff{color:var(--brass);flex-shrink:0}\n  #staff svg{width:76px;height:auto;max-height:110px;display:block}\n  .bar.no #staff{color:var(--rust)}\n  .prompt{display:flex;align-items:center;gap:18px;flex:1 1 340px;min-width:0}\n  .hint{font-family:'IBM Plex Sans',sans-serif;font-weight:500;font-size:17px;line-height:1.45;\n        color:var(--dim);flex:1 1 auto;min-width:0}\n  .hint b{font-weight:700;color:var(--bone)}\n  .ctrl{display:flex;align-items:center;gap:10px;flex-shrink:0}\n  .ghost{font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:14px;padding:11px 20px;\n         border-radius:999px;border:none;background:#252525;color:var(--bone);cursor:pointer}\n  .ghost:hover{background:var(--brass);color:var(--ink)}\n  .clock{font-family:'IBM Plex Mono',monospace;font-size:30px;font-weight:500;color:var(--brass);\n         min-width:56px;text-align:right}\n  .clock.low{color:var(--rust)}\n  .scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:6px;max-width:100%}\n  .neck{min-width:720px;max-width:100%}\n  .nums,.row{display:grid;grid-template-columns:34px 44px repeat(var(--frets),1fr)}\n  .grid{display:flex;flex-direction:column;width:100%}\n  .row{position:relative}\n  .strline{position:absolute;left:78px;right:0;top:50%;transform:translateY(-50%);pointer-events:none}\n  .nums div{text-align:center;font-family:'IBM Plex Mono',monospace;font-size:10px;color:#8A8A8A;padding-bottom:4px}\n  .nums div.twelve{color:var(--brass)}\n  .grid{background:var(--board);border:none;border-radius:0;overflow:hidden}\n  .row.dim .strline{opacity:.45}\n  .sbtn{background:var(--panel);border:none;border-right:1px solid var(--edge);height:44px;padding:0;\n        font-family:'IBM Plex Mono',monospace;font-size:11px;color:#5A5A5A;cursor:pointer}\n  .sbtn.on{color:var(--bone)}\n  .sbtn span{opacity:.5}\n  .fret{position:relative;height:44px;border:none;padding:0;background:transparent;cursor:pointer;\n        border-right:2px solid var(--wire);transition:opacity .2s}\n  .fret.nut{background:#1A1A1A;border-right:4px solid var(--dim)}\n  .fret.off{opacity:.4;cursor:default}\n  .inlay{position:absolute;inset:0;margin:auto;width:8px;height:8px;border-radius:50%;\n         background:rgba(248,248,248,.14)}\n  .dot{position:absolute;inset:0;margin:auto;width:30px;height:30px;border-radius:50%;\n       display:flex;align-items:center;justify-content:center;font-family:'IBM Plex Mono',monospace;\n       font-size:12px;font-weight:500;box-shadow:0 1px 4px rgba(0,0,0,.5)}\n  .keys{display:grid;grid-template-columns:repeat(auto-fit,minmax(56px,1fr));gap:6px;margin-top:16px}\n  .key{padding:0;height:46px;border-radius:999px;border:none;background:#252525;\n       color:var(--bone);cursor:pointer;font-family:'IBM Plex Sans',sans-serif;font-weight:600;font-size:15px;\n       display:flex;align-items:center;justify-content:center;text-align:center;line-height:1}\n  .key:hover{background:var(--brass);color:var(--ink)}\n  .set{margin-top:18px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;\n       font-family:'IBM Plex Mono',monospace;font-size:11px;color:#8A8A8A}\n  input[type=range]{accent-color:var(--brass);width:140px}\n  button:focus-visible{outline:2px solid var(--brass);outline-offset:1px}\n  @media (max-width:640px){\n    body{padding:12px 12px 32px}\n    .bar{flex-direction:column;align-items:stretch;gap:10px;padding:14px;border-radius:18px;\n         min-height:0}\n    .prompt{flex:0 1 auto;align-items:center;gap:10px;flex-wrap:nowrap}\n    .cue{font-size:34px;min-width:0;flex-shrink:0}\n    #staff svg{width:60px;max-height:76px}\n    .hint{font-size:14px;line-height:1.35}\n    .ctrl{justify-content:flex-end;gap:8px}\n    .ghost{padding:8px 14px;font-size:13px}\n    .clock{font-size:24px;min-width:44px}\n    .mode{font-size:13px;padding:9px 14px}\n    .modes{gap:6px;margin-bottom:10px}\n    .keys{grid-template-columns:repeat(6,1fr);gap:5px}\n    .key{height:42px;font-size:14px}\n    /* keep frets comfortably wide and let the board scroll sideways */\n    .neck{min-width:0;width:max-content}\n    .nums,.row{grid-template-columns:26px 34px repeat(var(--frets),46px)}\n    .strline{left:60px}\n    .neck,.nums,.grid,.row{width:max-content;min-width:100%}\n    .scroll{overflow-x:auto;margin:0 -12px;padding:0 12px 8px}\n    .sbtn{height:40px;font-size:10px}\n    .sbtn span{display:none}\n    .fret{height:40px}\n    .dot{width:26px;height:26px;font-size:10px}\n    .nums div{font-size:10px}\n    .set{font-size:10px;gap:8px}\n    input[type=range]{width:110px}\n  }\n  @media (prefers-reduced-motion:reduce){*{transition:none!important}}\n</style>\n</head>\n<body>\n<div class=\"wrap\">\n  <div class=\"top\">\n    <div class=\"stats\">\n      <span>ACC <b id=\"acc\">0%</b></span>\n      <span>STREAK <b class=\"gold\" id=\"streak\">0</b></span>\n      <span>BEST <b id=\"best\">0</b></span>\n      <span>AVG <b id=\"avg\">—</b></span>\n    </div>\n  </div>\n\n  <div class=\"modes\" id=\"modes\">\n    <button class=\"mode\" data-m=\"find\">Find the note</button>\n    <button class=\"mode\" data-m=\"name\">Name the fret</button>\n    <button class=\"mode\" data-m=\"beat\">Beat the clock</button>\n    <button class=\"mode\" data-m=\"all\">Select All</button>\n    <button class=\"mode\" data-m=\"explore\">Explore</button>\n  </div>\n\n  <div class=\"bar\" id=\"bar\">\n    <div class=\"prompt\">\n      <div class=\"cue\" id=\"cue\"></div>\n      <div id=\"staff\"></div>\n      <div class=\"hint\" id=\"hint\"></div>\n    </div>\n    <div class=\"ctrl\">\n      <div class=\"clock mono\" id=\"clock\"></div>\n      <button class=\"ghost\" id=\"show\">Show me</button>\n      <button class=\"ghost\" id=\"skip\">Skip</button>\n    </div>\n  </div>\n\n  <div class=\"scroll\">\n    <div class=\"neck\">\n      <div class=\"nums\" id=\"nums\"></div>\n      <div class=\"grid\" id=\"grid\"></div>\n    </div>\n  </div>\n\n  <div class=\"keys\" id=\"keys\" style=\"display:none\"></div>\n\n  <div class=\"set\">\n    <span>FRETS 0–<b id=\"fmax\" style=\"color:var(--bone)\">12</b></span>\n    <input type=\"range\" id=\"range\" min=\"5\" max=\"15\" value=\"12\" />\n    <span style=\"color:#5A5A5A\">|</span>\n    <span>Tap a string number on the left to add or remove it from the drill.</span>\n  </div>\n</div>\n\n<script>\nconst CHROMA=[\"C\",\"C#\",\"D\",\"D#\",\"E\",\"F\",\"F#\",\"G\",\"G#\",\"A\",\"A#\",\"B\"];\nconst STRINGS=[\n  {label:\"1\",name:\"e\",open:4,g:1},{label:\"2\",name:\"B\",open:11,g:1.4},\n  {label:\"3\",name:\"G\",open:7,g:1.9},{label:\"4\",name:\"D\",open:2,g:2.6},\n  {label:\"5\",name:\"A\",open:9,g:3.4},{label:\"6\",name:\"E\",open:4,g:4.4}];\nconst DOTS=[3,5,7,9,12,15];\nconst nameAt=(s,f)=>CHROMA[(STRINGS[s].open+f)%12];\n\nlet mode=\"find\", allowed=[5,4], maxFret=12;\nlet target=null, feedback=null, reveal=false, sel=null, locked=false;\nlet found=[];\nlet hit=0,miss=0,streak=0,best=0,times=[],t0=Date.now();\nlet timerId=null, left=0;\n\nconst $=id=>document.getElementById(id);\n\nfunction newTarget(){\n  const pool=allowed.length?allowed:[5];\n  feedback=null; reveal=false; locked=false; found=[]; t0=Date.now();\n  if(mode===\"all\"){\n    const pcs=[...new Set(pool.flatMap(s=>Array.from({length:maxFret+1},(_,f)=>(STRINGS[s].open+f)%12)))];\n    target={p:pcs[Math.floor(Math.random()*pcs.length)]};\n    render(); return;\n  }\n  const s=pool[Math.floor(Math.random()*pool.length)];\n  const f=Math.floor(Math.random()*(maxFret+1));\n  target={s,f};\n  if(mode===\"beat\") startClock();\n  render();\n}\nfunction startClock(){\n  stopClock(); left=5.0;\n  timerId=setInterval(()=>{\n    left-=0.1;\n    if(left<=0){ stopClock(); locked=true; feedback={ok:false,s:target.s,f:target.f}; score(false);\n      setTimeout(newTarget,900); }\n    render();\n  },100);\n}\nfunction stopClock(){ if(timerId){clearInterval(timerId); timerId=null;} }\n\nfunction score(ok){\n  const secs=(Date.now()-t0)/1000;\n  if(ok){hit++; streak++; best=Math.max(best,streak); times.push(secs); if(times.length>20)times.shift();}\n  else {miss++; streak=0;}\n}\nconst MIDI=[64,59,55,50,45,40];              // sounding pitch of open strings 1..6\nconst LETTER={0:0,1:0,2:1,3:1,4:2,5:3,6:3,7:4,8:4,9:5,10:5,11:6}; // pc -> C D E F G A B\nconst SHARP=[0,1,0,1,0,0,1,0,1,0,1,0];\n// Guitar is notated an octave above sounding pitch: written = sounding + 12.\nfunction staffSVG(midi){\n  const pc=midi%12, oct=Math.floor(midi/12)-1;\n  const step=oct*7+LETTER[pc];               // diatonic step; E4 (bottom line) = 30\n  const H=8, top=14, bottomLine=top+4*H;     // lines at steps 30,32,34,36,38\n  const yOf=s=>bottomLine-(s-30)*(H/2);\n  const y=yOf(step), x=46;\n  let g=\"\";\n  for(let i=0;i<5;i++) g+=`<line x1=\"6\" x2=\"68\" y1=\"${top+i*H}\" y2=\"${top+i*H}\" stroke=\"currentColor\" stroke-width=\"1\"/>`;\n  for(let s=28;s>=step;s-=2) g+=`<line x1=\"${x-10}\" x2=\"${x+10}\" y1=\"${yOf(s)}\" y2=\"${yOf(s)}\" stroke=\"currentColor\" stroke-width=\"1\"/>`;\n  for(let s=40;s<=step;s+=2) g+=`<line x1=\"${x-10}\" x2=\"${x+10}\" y1=\"${yOf(s)}\" y2=\"${yOf(s)}\" stroke=\"currentColor\" stroke-width=\"1\"/>`;\n  g+=`<ellipse cx=\"${x}\" cy=\"${y}\" rx=\"6\" ry=\"4.4\" fill=\"currentColor\" transform=\"rotate(-18 ${x} ${y})\"/>`;\n  const up=step<34;\n  g+= up\n    ? `<line x1=\"${x+5.7}\" x2=\"${x+5.7}\" y1=\"${y-1}\" y2=\"${y-26}\" stroke=\"currentColor\" stroke-width=\"1.6\"/>`\n    : `<line x1=\"${x-5.7}\" x2=\"${x-5.7}\" y1=\"${y+1}\" y2=\"${y+26}\" stroke=\"currentColor\" stroke-width=\"1.6\"/>`;\n  if(SHARP[pc]) g+=`<text x=\"${x-24}\" y=\"${y+5}\" font-size=\"16\" fill=\"currentColor\" font-family=\"serif\">♯</text>`;\n  const yTop=Math.min(y-32, top-10), yBot=Math.max(y+32, bottomLine+18);\n  return `<svg viewBox=\"0 ${yTop} 76 ${yBot-yTop}\" preserveAspectRatio=\"xMidYMid meet\" aria-hidden=\"true\">\n    <text x=\"4\" y=\"49\" font-size=\"48\" fill=\"currentColor\" opacity=\".55\" font-family=\"serif\">𝄞</text>${g}</svg>`;\n}\nfunction positionsOf(p){\n  const pool=allowed.length?allowed:[5];\n  const out=[];\n  for(const s of pool) for(let f=0;f<=maxFret;f++)\n    if((STRINGS[s].open+f)%12===p) out.push(s+\":\"+f);\n  return out;\n}\nfunction tapFret(s,f){\n  if(mode===\"explore\"){ sel={s,f}; render(); return; }\n  if(locked||!target||!allowed.includes(s)) return;\n  if(mode===\"name\") return;\n  if(mode===\"all\"){\n    const key=s+\":\"+f;\n    if((STRINGS[s].open+f)%12===target.p){\n      if(!found.includes(key)){\n        found.push(key); hit++; streak++; best=Math.max(best,streak);\n        if(found.length===positionsOf(target.p).length){\n          locked=true;\n          times.push((Date.now()-t0)/1000); if(times.length>20)times.shift();\n          setTimeout(newTarget,900);\n        }\n      }\n    } else {\n      miss++; streak=0; feedback={ok:false,s,f};\n      setTimeout(()=>{ if(feedback&&!feedback.ok&&feedback.s===s&&feedback.f===f){feedback=null;render();} },600);\n    }\n    render(); return;\n  }\n  const ok=nameAt(s,f)===nameAt(target.s,target.f);\n  feedback={ok,s,f}; score(ok); if(ok){locked=true; stopClock(); setTimeout(newTarget,700);}\n  render();\n}\nfunction answerName(n){\n  if(locked||!target||mode!==\"name\") return;\n  const ok=n===nameAt(target.s,target.f);\n  feedback={ok,s:target.s,f:target.f}; score(ok);\n  if(ok){locked=true; setTimeout(newTarget,700);}\n  render();\n}\nfunction octaves(s,f){\n  const out=[]; const p=(STRINGS[s].open+f)%12;\n  for(let ss=0;ss<6;ss++) for(let ff=0;ff<=maxFret;ff++)\n    if((STRINGS[ss].open+ff)%12===p && !(ss===s&&ff===f)) out.push(ss+\":\"+ff);\n  return out;\n}\n\nfunction render(){\n  const total=hit+miss;\n  $(\"acc\").textContent=(total?Math.round(hit/total*100):0)+\"%\";\n  $(\"streak\").textContent=streak; $(\"best\").textContent=best;\n  $(\"avg\").textContent=times.length?(times.reduce((a,b)=>a+b,0)/times.length).toFixed(1)+\"s\":\"—\";\n  $(\"fmax\").textContent=maxFret;\n  document.querySelectorAll(\".mode\").forEach(b=>b.setAttribute(\"aria-pressed\",b.dataset.m===mode));\n\n  const drill=(mode===\"find\"||mode===\"beat\");\n  $(\"keys\").style.display=mode===\"name\"?\"grid\":\"none\";\n  $(\"show\").style.display=(drill||mode===\"all\")?\"\":\"none\";\n  $(\"skip\").style.display=mode===\"explore\"?\"none\":\"\";\n  const cl=$(\"clock\");\n  cl.textContent=mode===\"beat\"?Math.max(0,left).toFixed(1):\"\";\n  cl.className=\"clock mono\"+(mode===\"beat\"&&left<2?\" low\":\"\");\n\n  const cue=$(\"cue\"), hint=$(\"hint\");\n  $(\"bar\").className=\"bar\"+((feedback&&!feedback.ok)?\" no\":((feedback&&feedback.ok)||locked?\" ok\":\"\"));\n  if(mode===\"explore\"){\n    cue.textContent=sel?nameAt(sel.s,sel.f):\"\"; cue.className=\"cue\";\n    hint.innerHTML=sel\n      ? `String ${STRINGS[sel.s].label}, fret ${sel.f}. Every other <b>${nameAt(sel.s,sel.f)}</b> on the neck is lit.`\n      : \"Tap any fret to see its note and every octave of it across the neck.\";\n  } else if(mode===\"name\"){\n    cue.textContent=\"?\"; cue.className=\"cue\";\n    hint.innerHTML=\"What note is the <b style='color:var(--sky)'>marked fret</b>?\";\n  } else if(mode===\"all\"){\n    cue.textContent=target?CHROMA[target.p]:\"\";\n    cue.className=\"cue\";\n    const totalPos=target?positionsOf(target.p).length:0;\n    hint.innerHTML=target\n      ? `Find <b>every ${CHROMA[target.p]}</b> on the active strings. <b>${found.length}</b> of <b>${totalPos}</b> found.`\n      : \"\";\n  } else {\n    cue.textContent=target?nameAt(target.s,target.f):\"\";\n    cue.className=\"cue\";\n    hint.innerHTML=target\n      ? `Find <b>${nameAt(target.s,target.f)}</b> on string <b>${STRINGS[target.s].label}</b> (${STRINGS[target.s].name}).`\n        +(mode===\"beat\"?\"<br>Five seconds. Any octave on that string counts.\":\"<br>Any octave of it on that string counts.\")\n      : \"\";\n  }\n\n  let midi=null;\n  if(mode===\"explore\"&&sel) midi=MIDI[sel.s]+sel.f+12;\n  else if(mode===\"all\"&&target) midi=60+target.p;\n  else if(drill&&target) midi=MIDI[target.s]+target.f+12;\n  $(\"staff\").innerHTML=midi!==null?staffSVG(midi):\"\";\n\n  const partners = (mode===\"explore\"&&sel)?octaves(sel.s,sel.f):[];\n  $(\"nums\").style.setProperty(\"--frets\",maxFret);\n\n\n  let n='<div></div>';\n  for(let f=0;f<=maxFret;f++) n+=`<div class=\"${f===12?'twelve':''}\">${f}</div>`;\n  $(\"nums\").innerHTML=n;\n\n  let g=\"\";\n  for(let s=0;s<6;s++){\n    const on=allowed.includes(s);\n    g+=`<div class=\"row ${(mode!==\"explore\"&&!on)?'dim':''}\" style=\"--frets:${maxFret}\">\n        <span class=\"strline\" style=\"height:${STRINGS[s].g}px;background:${s>2?'#5E5E5E':'#7E7E7E'}\"></span>\n        <button class=\"sbtn ${on?'on':''}\" data-s=\"${s}\" title=\"Toggle string\">${STRINGS[s].label}<span> ${STRINGS[s].name}</span></button>`;\n    for(let f=0;f<=maxFret;f++){\n      const isT=target&&target.s===s&&target.f===f;\n      const fb=feedback&&feedback.s===s&&feedback.f===f;\n      const rv=reveal&&drill&&target&&s===target.s&&nameAt(s,f)===nameAt(target.s,target.f);\n      const isSel=sel&&sel.s===s&&sel.f===f;\n      const isP=partners.includes(s+\":\"+f);\n      const dim=mode!==\"explore\"&&!on;\n      let dot=null;\n      if(mode===\"all\"&&target){\n        const key=s+\":\"+f;\n        if(found.includes(key)) dot={bg:\"var(--moss)\",fg:\"#fff\",t:nameAt(s,f)};\n        else if(fb) dot={bg:\"var(--rust)\",fg:\"#fff\",t:nameAt(s,f)};\n        else if(reveal&&on&&(STRINGS[s].open+f)%12===target.p) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      }\n      else if(mode===\"name\"&&isT&&!feedback) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:\"?\"};\n      else if(fb) dot={bg:feedback.ok?\"var(--moss)\":\"var(--rust)\",fg:\"#fff\",t:nameAt(s,f)};\n      else if(rv) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      else if(isSel) dot={bg:\"var(--bone)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      else if(isP) dot={bg:\"var(--sky)\",fg:\"var(--ink)\",t:nameAt(s,f)};\n      g+=`<button class=\"fret ${f===0?'nut':''} ${dim?'off':''}\" data-s=\"${s}\" data-f=\"${f}\"\n            aria-label=\"String ${STRINGS[s].label}, fret ${f}\">\n            ${(s===2&&DOTS.includes(f)&&!dot)?'<span class=\"inlay\"></span>':''}\n            ${dot?`<span class=\"dot\" style=\"background:${dot.bg};color:${dot.fg}\">${dot.t}</span>`:''}\n          </button>`;\n    }\n    g+=`</div>`;\n  }\n  $(\"grid\").innerHTML=g;\n  $(\"grid\").querySelectorAll(\".sbtn\").forEach(b=>b.onclick=()=>{\n    const i=+b.dataset.s;\n    allowed=allowed.includes(i)?allowed.filter(x=>x!==i):[...allowed,i];\n    if(mode===\"all\") newTarget(); else render();\n  });\n  $(\"grid\").querySelectorAll(\".fret\").forEach(b=>b.onclick=()=>tapFret(+b.dataset.s,+b.dataset.f));\n\n  if(!$(\"keys\").dataset.built){\n    $(\"keys\").innerHTML=CHROMA.map(x=>`<button class=\"key\" data-n=\"${x}\">${x}</button>`).join(\"\");\n    $(\"keys\").querySelectorAll(\".key\").forEach(b=>b.onclick=()=>answerName(b.dataset.n));\n    $(\"keys\").dataset.built=\"1\";\n  }\n}\n\ndocument.querySelectorAll(\".mode\").forEach(b=>b.onclick=()=>{\n  mode=b.dataset.m; sel=null; stopClock();\n  if(mode===\"explore\"){target=null;feedback=null;render();} else newTarget();\n});\n$(\"skip\").onclick=newTarget;\n$(\"show\").onclick=()=>{reveal=true;render();};\n$(\"range\").oninput=e=>{maxFret=+e.target.value; if(mode!==\"explore\")newTarget(); else render();};\n\nnewTarget();\n</script>\n</body>\n</html>","embedURL":""}},"7:73":{"type":"TEXT","id":"7:73","name":"Fretboard","absoluteBoundingBox":{"x":2146.79907226562,"y":130.0,"width":110.0,"height":31.0},"isolatedAbsoluteRenderBounds":{"x":2148.76708984375,"y":137.240005493164,"width":106.140380859375,"height":18.0479888916016},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,10.0]],"size":{"x":110.0,"y":31.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.123685397207737,"g":0.123685397207737,"b":0.123685397207737,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Fretboard","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"resolvedParagraphValues":{"0":{"listSpacing":0.0,"paragraphIndent":0.0,"paragraphSpacing":0.0}},"textAutoResize":"WIDTH_AND_HEIGHT","textAlignVertical":"CENTER","style":{"fontFamily":"IBM Plex Sans","fontPostScriptName":"IBMPlexSans-SemiBold","fontStyle":"SemiBold","textCase":"TITLE","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":24.0,"textAlignHorizontal":"LEFT","textAlignVertical":"CENTER","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":31.1999988555908,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"7:72":{"type":"FRAME","id":"7:72","name":"Button","absoluteBoundingBox":{"x":2126.79907226562,"y":120.0,"width":1084.20092773438,"height":53.0},"isolatedAbsoluteRenderBounds":{"x":2126.79907226562,"y":120.0,"width":1084.20092773438,"height":53.0},"relativeTransform":[[1.0,0.0,155.799011230469],[0.0,1.0,0.0]],"size":{"x":1084.20092773438,"y":53.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.0,"g":0.831372559070587,"b":0.364705890417099,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","layoutGrow":1.0,"strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"rectangleCornerRadii":[200.0,200.0,200.0,200.0],"cornerRadius":200.0,"paddingTop":10.0,"paddingRight":20.0,"paddingBottom":12.0,"paddingLeft":20.0,"clipsContent":true,"layoutMode":"HORIZONTAL","itemSpacing":40.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["7:73"]},"7:94":{"type":"TEXT","id":"7:94","name":"Home","absoluteBoundingBox":{"x":1991.0,"y":130.0,"width":66.0,"height":31.0},"isolatedAbsoluteRenderBounds":{"x":1992.96801757812,"y":138.248001098633,"width":62.566162109375,"height":17.0399932861328},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,10.0]],"size":{"x":66.0,"y":31.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.123685397207737,"g":0.123685397207737,"b":0.123685397207737,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Home","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"resolvedParagraphValues":{"0":{"listSpacing":0.0,"paragraphIndent":0.0,"paragraphSpacing":0.0}},"textAutoResize":"WIDTH_AND_HEIGHT","textAlignVertical":"CENTER","style":{"fontFamily":"IBM Plex Sans","fontPostScriptName":"IBMPlexSans-SemiBold","fontStyle":"SemiBold","textCase":"TITLE","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":24.0,"textAlignHorizontal":"LEFT","textAlignVertical":"CENTER","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":31.1999988555908,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"7:112":{"type":"FRAME","id":"7:112","name":"Header","absoluteBoundingBox":{"x":3315.0,"y":120.0,"width":335.0,"height":53.0},"isolatedAbsoluteRenderBounds":{"x":3315.0,"y":120.0,"width":335.0,"height":53.0},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,20.0]],"size":{"x":335.0,"y":53.0},"fills":[],"strokeAlign":"INSIDE","layoutAlign":"STRETCH","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"layoutMode":"HORIZONTAL","itemSpacing":10.0,"counterAxisAlignItems":"CENTER","primaryAxisSizingMode":"FIXED","children":["7:98","7:77"]},"7:93":{"type":"FRAME","id":"7:93","name":"Button","absoluteBoundingBox":{"x":1971.0,"y":120.0,"width":145.799011230469,"height":53.0},"isolatedAbsoluteRenderBounds":{"x":1971.0,"y":120.0,"width":145.799072265625,"height":53.0},"relativeTransform":[[1.0,0.0,0.0],[0.0,1.0,0.0]],"size":{"x":145.799011230469,"y":53.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":1.0,"g":0.564705908298492,"b":0.800000011920929,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[{"id":{"sessionID":2,"localID":163},"event":{"interactionType":"ON_CLICK"},"actions":[{"transitionNodeID":{"sessionID":0,"localID":3},"connectionType":"INTERNAL_NODE","navigationType":"NAVIGATE","connectionURL":"/"}],"isDeleted":false,"stateManagementVersion":1}],"rectangleCornerRadii":[200.0,200.0,200.0,200.0],"cornerRadius":200.0,"paddingTop":10.0,"paddingRight":20.0,"paddingBottom":12.0,"paddingLeft":20.0,"clipsContent":true,"layoutMode":"HORIZONTAL","itemSpacing":20.0,"counterAxisAlignItems":"CENTER","children":["7:94","7:95"]},"7:78":{"type":"TEXT","id":"7:78","name":"Fretboard","absoluteBoundingBox":{"x":3398.0,"y":130.0,"width":110.0,"height":31.0},"isolatedAbsoluteRenderBounds":{"x":3399.96801757812,"y":137.240005493164,"width":106.140380859375,"height":18.0479888916016},"relativeTransform":[[1.0,0.0,20.0],[0.0,1.0,10.0]],"size":{"x":110.0,"y":31.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.123685397207737,"g":0.123685397207737,"b":0.123685397207737,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"OUTSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"characterStyleOverrides":[],"characters":"Fretboard","lineIndentations":[0],"lineTypes":["NONE"],"listStartOffsets":[],"lineStyleOverrides":[0],"lineTextDirections":null,"resolvedParagraphValues":{"0":{"listSpacing":0.0,"paragraphIndent":0.0,"paragraphSpacing":0.0}},"textAutoResize":"WIDTH_AND_HEIGHT","textAlignVertical":"CENTER","style":{"fontFamily":"IBM Plex Sans","fontPostScriptName":"IBMPlexSans-SemiBold","fontStyle":"SemiBold","textCase":"TITLE","textAutoResize":"WIDTH_AND_HEIGHT","fontVariantPosition":"NORMAL","fontSize":24.0,"textAlignHorizontal":"LEFT","textAlignVertical":"CENTER","letterSpacing":0.0,"letterSpacingValue":0.0,"letterSpacingUnit":"PERCENT","lineHeightPx":31.1999988555908,"lineHeightPercent":100.0,"lineHeightUnit":"INTRINSIC_%","paragraphSpacing":0,"paragraphIndent":0,"listSpacing":0,"italic":false,"textDecoration":"NONE","textDecorationSkipInk":false,"textDecorationStyle":"solid","textTruncation":"DISABLED","lineHeightPercentFontSize":100},"styleOverrideTable":{}},"7:100":{"type":"SVG","id":"7:100","name":"Group 1","absoluteBoundingBox":{"x":3330.18628495115,"y":134.186290605416,"width":22.6274300977093,"height":22.6274300977091},"targetAspectRatio":{"x":86.0000076293945,"y":86.0000076293945},"isolatedAbsoluteRenderBounds":{"x":3328.77197265625,"y":133.479202270508,"width":24.041748046875,"height":24.0416412353516},"relativeTransform":[[-0.70710676908493,0.70710676908493,26.5],[-0.70710676908493,-0.70710676908493,36.8137168884277]],"size":{"x":16.0000095367432,"y":16.0000095367432},"fills":[],"strokeAlign":"INSIDE","strokes":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"hash":"691b3de63fc4d2eaea85228adcc8c006cffacce8"},"2:5":{"type":"FRAME","id":"2:5","name":"Mobile","absoluteBoundingBox":{"x":3295.0,"y":100.0,"width":375.0,"height":849.0},"isolatedAbsoluteRenderBounds":{"x":3295.0,"y":100.0,"width":375.0,"height":849.0},"relativeTransform":[[1.0,0.0,1408.0],[0.0,1.0,100.0]],"size":{"x":375.0,"y":849.0},"fills":[{"blendMode":"NORMAL","type":"SOLID","color":{"r":0.123685397207737,"g":0.123685397207737,"b":0.123685397207737,"a":1.0},"visible":true,"opacity":1.0}],"strokeAlign":"INSIDE","strokes":[],"effects":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"paddingTop":20.0,"paddingRight":20.0,"paddingBottom":20.0,"paddingLeft":20.0,"clipsContent":true,"overflowDirection":"VERTICAL_SCROLLING","layoutMode":"VERTICAL","itemSpacing":40.0,"counterAxisAlignItems":"CENTER","counterAxisSizingMode":"FIXED","isBreakpointFrame":true,"children":["7:112","10:248","2:10"]},"7:95":{"type":"SVG","id":"7:95","name":"Group 1","absoluteBoundingBox":{"x":2076.99991120312,"y":135.600499896467,"width":19.7990057187671,"height":19.7990057187676},"targetAspectRatio":{"x":86.0000076293945,"y":86.0000076293945},"isolatedAbsoluteRenderBounds":{"x":2075.58569335938,"y":134.893417358398,"width":21.2132235625086,"height":21.2132110595703},"relativeTransform":[[-0.70710676908493,0.70710676908493,115.899505615234],[-0.70710676908493,-0.70710676908493,35.3995018005371]],"size":{"x":14.0000114440918,"y":14.0000114440918},"fills":[],"strokeAlign":"INSIDE","strokes":[],"accessibleHTMLTag":"AUTO","isDecorativeImage":false,"ariaAttributes":{},"interactions":[],"hash":"798e3de9ce02b7650d7beb69e773d4cf9057e8be"}},"assetIdToGuid":{},"guidToUrl":{"2:2":"/fretboard","11:324":"/daily-spend","7:126":"/flashcards","0:3":"/"},"fonts":{"IBM Plex Sans:SemiBold":{"id":"IBMPlexSans_wdth_wght__1","url":"/_woff/v2/IBMPlexSans_wdth_wght__1/IBMPlexSans_wdth_wght__1.woff2","source":1,"italic":false,"weight":600,"variationAxes":[{"tag":"wght","value":600.0,"name":"Weight"},{"tag":"wdth","value":100.0,"name":"Width"}],"subsets":{"baseUrl":"/_woff/v2/IBMPlexSans_wdth_wght__1/","subsetMappings":[{"unicodeRange":"U+0000-00A0,U+00A2-00A9,U+00AC-00AE,U+00B0-00B7,U+00B9-00BA,U+00BC-00BE,U+00D7,U+00F7,U+2000-206F,U+2074,U+20AC,U+2122,U+2190-21BB,U+2212,U+2215,U+F8FF,U+FEFF,U+FFFD","file":"IBMPlexSans_wdth_wght__1-english.woff2"},{"unicodeRange":"U+00A1,U+00AA-00AB,U+00AF,U+00B8,U+00BB,U+00BF-00D6,U+00D8-00F6,U+00F8-00FF,U+0131,U+0152-0153,U+02B0-02FF","file":"IBMPlexSans_wdth_wght__1-rest-latin.woff2"},{"unicodeRange":"U+0100-0130,U+0132-0151,U+0154-017F","file":"IBMPlexSans_wdth_wght__1-latin-extended-a.woff2"},{"unicodeRange":"U+0180-024F","file":"IBMPlexSans_wdth_wght__1-latin-extended-b.woff2"},{"unicodeRange":"U+1E00-1EFF","file":"IBMPlexSans_wdth_wght__1-latin-extended-additional.woff2"},{"unicodeRange":"U+0250-02AF,U+0300-1DFF,U+1F00-1FFF,U+2070-2073,U+2075-20AB,U+20AD-2121,U+2123-218F,U+21BC-2211,U+2213-2214,U+2216-F8FE,U+F900-FEFE,U+FF00-FFFC,U+FFFE-FFFF","file":"IBMPlexSans_wdth_wght__1-rest.woff2"}]}}},"assets":{"691b3de63fc4d2eaea85228adcc8c006cffacce8":{"type":"GENERATED_ASSET","url":"691b3de63fc4d2eaea85228adcc8c006cffacce8.svg","size":{"x":17.7071132659912,"y":17.7071151733398},"offsets":{"left":{"value":-4.4194578859461,"unit":"PERCENT"},"right":{"value":-6.24993406240102,"unit":"PERCENT"},"top":{"value":-6.25004246828409,"unit":"PERCENT"},"bottom":{"value":-4.41936140098488,"unit":"PERCENT"}},"format":"SVG"},"798e3de9ce02b7650d7beb69e773d4cf9057e8be":{"type":"GENERATED_ASSET","url":"798e3de9ce02b7650d7beb69e773d4cf9057e8be.svg","size":{"x":15.7071142196655,"y":15.7071151733398},"offsets":{"left":{"value":-5.0508164092611,"unit":"PERCENT"},"right":{"value":-7.14276487737529,"unit":"PERCENT"},"top":{"value":-7.14291516610677,"unit":"PERCENT"},"bottom":{"value":-5.05067293248346,"unit":"PERCENT"}},"format":"SVG"},"80b30a8b8d495f47f96c611b0b160ea7b7cf0546":{"type":"GENERATED_ASSET","url":"80b30a8b8d495f47f96c611b0b160ea7b7cf0546.png","size":{"x":1200.0,"y":630.0},"offsets":{"top":{"value":0,"unit":"PIXELS"},"right":{"value":0,"unit":"PIXELS"},"bottom":{"value":0,"unit":"PIXELS"},"left":{"value":0,"unit":"PIXELS"}},"format":"PNG"},"b46c3e4bd3e95a52c9b79f9891719f37e799a9a7":{"type":"GENERATED_ASSET","url":"b46c3e4bd3e95a52c9b79f9891719f37e799a9a7.png","size":{"x":95.0,"y":95.0},"offsets":{"top":{"value":0,"unit":"PIXELS"},"right":{"value":0,"unit":"PIXELS"},"bottom":{"value":0,"unit":"PIXELS"},"left":{"value":0,"unit":"PIXELS"}},"format":"PNG"}},"stablePathToAssetInfo":{"7:100":{"hash":"691b3de63fc4d2eaea85228adcc8c006cffacce8"},"7:95":{"hash":"798e3de9ce02b7650d7beb69e773d4cf9057e8be"},"11:417":{"hash":"80b30a8b8d495f47f96c611b0b160ea7b7cf0546"},"2:164":{"hash":"b46c3e4bd3e95a52c9b79f9891719f37e799a9a7"}},"animateRootIds":[],"siteSettings":{"title":"Useful Shit | Free Little Tools for Learning & Everyday Life","description":"A small collection of free browser tools made for fun: learn every note on the guitar fretboard, turn any content into flashcards, and track your daily spend.","scalingMode":"REFLOW","lang":"en","faviconFilename":"b46c3e4bd3e95a52c9b79f9891719f37e799a9a7.png","socialImageFilename":"80b30a8b8d495f47f96c611b0b160ea7b7cf0546.png","labs":{"E5FBBA911B2B7A09E649D4BE6CDF8591EAEFC881":false}},"sourceCodeHash":""}