---
description: Focus, one subject's thoughts and topics, as a base d2 Page served at /thoughts/focus/<handle>-<n> (P-1146; built by designer-82 from the locked mockup App:Mockup-focus-v1 v19, razie 2026-10-10)
tags: page, thoughts, focus
---
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><title>Focus</title>
<style>
:root{--paper:#fbfaf7;--card:#fff;--ink:#1d1d1b;--soft:#6b6a66;--line:#e4e1da;--indigo:#9a4b12;--indigo-bg:#f5e8dc;--slate:#6b6a66;--slate-bg:#f3f1ec;--sun:#d99a2b;--sun-bg:#fbf1dc;--leaf:#3f7d4e;--rose:#b23a48;--rose-bg:#f8e6e6;--god:#7b3fb8;--attn:#7a7fc9;--display:'Manrope',system-ui,-apple-system,'Segoe UI',sans-serif;--body:system-ui,-apple-system,'Segoe UI',sans-serif;--mono:ui-monospace,Menlo,Consolas,monospace;color-scheme:light dark;
--th-focus:#1f5bb5;--th-chat:#2f6fdc;--th-done:#2e8b57;--th-ask:var(--rose);--th-monitor:var(--god);--th-sent:var(--soft);--th-amber:#d99a00;--accent:var(--th-done)}
@media (prefers-color-scheme:dark){:root{--paper:#141413;--card:#1d1d1b;--ink:#ecebe6;--soft:#9c9a93;--line:#33322f;--indigo:#f0a35e;--indigo-bg:#2e2419;--slate:#9c9a93;--slate-bg:#22211f;--sun:#e5b454;--sun-bg:#2c2515;--leaf:#6cc08a;--rose:#f08a92;--rose-bg:#3a2022;--god:#c5a0f5;--attn:#9296f5;--th-focus:#7aa7f0;--th-chat:#7aa7f0;--th-done:#6cc08a}}
:root.dk{--th-focus:#7aa7f0;--th-chat:#7aa7f0;--th-done:#6cc08a}:root.lt{--th-focus:#1f5bb5;--th-chat:#2f6fdc;--th-done:#2e8b57}
*{box-sizing:border-box}
html,body{margin:0;background:var(--paper);color:var(--ink);font:calc(16px*var(--fz,1))/1.5 var(--body)}
a{color:inherit}[hidden]{display:none!important}
.wrap{max-width:1280px;margin:0 auto;padding:6px clamp(12px,3vw,28px) 48px}
h1{font:800 calc(clamp(26px,4vw,38px)*var(--fz,1))/1.1 var(--display);letter-spacing:-.02em;margin:10px 0 12px}
h1 small{font:600 calc(15px*var(--fz,1)) var(--mono);color:var(--soft);letter-spacing:0;vertical-align:middle;margin-left:8px}
.small{font-size:calc(13px*var(--fz,1));color:var(--soft)}.bad{color:var(--rose)}
code{font:calc(14px*var(--fz,1)) var(--mono)}
button,.btn{font:600 calc(14px*var(--fz,1)) var(--body);color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:4px 11px;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:4px}
button:hover,.btn:hover{border-color:var(--indigo)}
.btn.primary{background:var(--ink);color:var(--paper);border-color:var(--ink)}.btn.primary:hover{filter:brightness(1.15)}
.btn.danger{color:var(--rose);border-color:color-mix(in srgb,var(--rose) 45%,transparent)}button[disabled]{opacity:.5;cursor:default}
.btn.sm{font-size:calc(12px*var(--fz,1));padding:1px 8px;min-height:24px;border-radius:6px}
.ibtn{background:none;border:0;color:var(--soft);padding:2px 6px;border-radius:6px;font:inherit;min-height:0;cursor:pointer}.ibtn:hover{color:var(--ink);background:var(--line)}
.lnk{background:none;border:0;padding:0;font:inherit;color:inherit;text-decoration:underline;cursor:pointer}
select{font:600 calc(13px*var(--fz,1)) var(--body);color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:8px;padding:2px 8px;max-width:100%}
.tabs{display:inline-flex;flex-wrap:wrap;border:1px solid var(--line);border-radius:999px;overflow:hidden;font:600 calc(13px*var(--fz,1)) var(--body)}
.tabs>button{padding:3px 14px;border:0;border-left:1px solid var(--line);border-radius:0;background:none;font:inherit}.tabs>:first-child{border-left:0}
.tabs>[aria-selected=true]{background:var(--indigo-bg);color:var(--indigo)}
.th-filters{display:none;margin:0 0 10px;background:var(--th-chat);color:#fff;border-color:var(--th-chat)}
.th-grid{display:grid;grid-template-columns:210px minmax(0,1fr);gap:22px;align-items:start}
.th-side{position:sticky;top:12px;max-height:calc(100vh - 24px);overflow-y:auto;padding-right:4px;font-size:calc(15px*var(--fz,1))}
.th-side h3{font-size:calc(12px*var(--fz,1));text-transform:uppercase;letter-spacing:.04em;color:var(--soft);margin:16px 0 6px}
.th-side>div:first-child h3{margin-top:0}
.th-side ul{list-style:none;padding:0;margin:0}.th-side li{margin:0 0 2px}
.th-side li a{display:flex;justify-content:space-between;gap:8px;padding:3px 8px;border-radius:8px;color:var(--ink);text-decoration:none}
.th-side li a:hover,.th-side li a.on{background:var(--card)}.th-side li a.on{font-weight:600}
.th-side .n{color:var(--soft);font-variant-numeric:tabular-nums}
.th-find{width:100%;font:400 calc(14px*var(--fz,1)) var(--body);padding:4px 8px;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--ink);margin:0 0 6px}
.th-tags a span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.th-tags a.th-tag.on{background:color-mix(in srgb,var(--indigo) 12%,transparent);color:var(--indigo);font-weight:600}
.th-tags a.th-tag.on span:first-child::before{content:"✓ "}
.th-tags a.th-tag.red{color:var(--rose)}.th-tags a.th-tag.red span:first-child{text-decoration:line-through}
.th-tags a.th-tag.red span:first-child::before{content:"⊘ ";display:inline-block;text-decoration:none;margin-right:2px}
.th-alltags{margin-top:6px}
.th-hiding,.th-qline{display:flex;justify-content:space-between;gap:10px;margin:0 0 10px}
.th-cap{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:10px 12px;margin:0 0 18px}
.th-cap .th-tx,.th-pop .th-edit .th-tx{width:100%;font:calc(15px*var(--fz,1))/1.45 var(--body);padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink);resize:vertical}
.th-cap .th-tx{min-height:3.2em;max-height:60vh;overflow-y:auto}
.th-cap .th-tx:focus,.th-pop .th-tx:focus{outline:2px solid var(--accent);outline-offset:-1px}
.th-acts{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:6px}
.th-cap .th-keys,.th-pop .th-keys{margin-left:auto;font-size:calc(11px*var(--fz,1));color:var(--soft)}
.th-chip{display:inline-flex;gap:5px;align-items:center;border:1px solid var(--line);border-radius:999px;padding:0 8px;font-size:calc(12px*var(--fz,1));color:var(--soft);background:var(--paper);text-decoration:none;white-space:nowrap}
.th-cap .th-to{cursor:pointer;font-weight:400}
.th-pick{font:400 calc(13px*var(--fz,1)) var(--body);padding:1px 6px;border:1px solid var(--line);border-radius:999px;background:var(--paper);color:var(--ink)}
.th-cap .th-mode{margin:0 0 6px;color:var(--soft);font-weight:600}
.th-cap:not(.th-editing) .th-edbtn{display:none}.th-cap:not(.th-asking) #th-eans{display:none}
.th-cap.th-editing #th-save,.th-cap.th-editing #th-to,.th-cap.th-editing #th-send{display:none}
.th-cap.th-editing .th-tx{border-color:var(--th-focus)}
.th-draft{display:inline-flex;flex-wrap:wrap;gap:6px;align-items:center;margin:0 0 6px;padding:3px 10px;border-radius:999px;background:var(--sun-bg);border:1px solid var(--sun);font-size:calc(13px*var(--fz,1));max-width:100%}
.th-draft span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:min(46ch,60vw);color:var(--soft)}
.th-net{margin:6px 0 0;color:var(--rose);font-weight:600}
#th-msg{margin:6px 0 0}#th-msg:empty{display:none}
.th-voice{display:inline-flex;align-self:stretch}
.th-mic{min-width:40px;min-height:30px;padding:0;justify-content:center;font-size:calc(20px*var(--fz,1));line-height:1;border-radius:8px}
.th-mic.on{background:#c62828;border-color:#c62828;color:#fff;animation:th-mic 1.2s ease-in-out infinite;padding:0 8px}
@keyframes th-mic{50%{box-shadow:0 0 0 5px rgba(198,40,40,.25)}}
.th-interim{margin:4px 2px 0;color:var(--soft);font-style:italic;white-space:pre-wrap}
.th-heard{margin:6px 0 0;display:inline-block;padding:2px 10px;border-radius:999px;background:var(--paper);border:1px solid var(--line);color:var(--ink)}
.th-cap.th-hasbrain{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:12px;align-items:stretch}
.th-capcol{display:flex;flex-direction:column;min-width:0}.th-hasbrain .th-capcol>.th-tx{flex:1 1 auto}
.th-brain{position:relative;min-width:0;min-height:180px;border:1px solid var(--line);border-radius:10px;background:var(--paper);overflow:hidden;display:flex;flex-direction:column}
.th-brain canvas{display:block;width:100%;flex:1;min-height:147px;cursor:pointer}
.th-bcap{margin:0;padding:7px 12px;border-top:1px solid var(--line);font-size:calc(13px*var(--fz,1));color:var(--soft);min-height:2.9em}
.th-bcap b{color:var(--ink)}.th-bcap .th-bn{color:inherit;text-decoration:none;border-bottom:1px dotted currentColor}.th-bcap .th-brel,.th-bcap .th-bpart{opacity:.55}
.th-bfold{position:absolute;top:4px;right:4px;z-index:1}
.th-bmeter{position:absolute;left:8px;bottom:3.6em;width:5px;height:30px;border-radius:3px;background:var(--accent);transform-origin:bottom;transform:scaleY(0)}
.th-brain.th-listening{box-shadow:0 0 0 2px var(--accent),0 0 16px var(--accent)}
.th-cap.th-bc{grid-template-columns:minmax(0,1fr)}.th-cap.th-bc .th-brain{min-height:0}
.th-brain.th-bfolded canvas,.th-brain.th-bfolded .th-bmeter{display:none}.th-brain.th-bfolded .th-bcap{border-top:0;padding-right:36px}
.th-cap.th-bc .th-tx{min-height:10em}
.th-list{columns:230px;column-gap:12px}
.th-card{border:1px solid var(--line);border-radius:12px;padding:10px 12px;background:var(--card);cursor:pointer;display:flex;flex-direction:column;gap:4px;min-width:0;break-inside:avoid;margin:0 0 12px;position:relative}
.th-card:hover{border-color:color-mix(in srgb,var(--indigo) 40%,var(--line))}
.th-card.imp{border-color:var(--rose);box-shadow:inset 3px 0 0 var(--rose)}
.th-card .th-title{font-size:calc(15px*var(--fz,1));font-weight:600;margin:0;overflow-wrap:anywhere;line-height:1.35;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:10;overflow:hidden}
.th-card .th-ansp{margin:6px 0 0;padding-left:8px;border-left:2px solid var(--line);color:var(--ink);font-size:calc(13.5px*var(--fz,1));line-height:1.45;white-space:pre-line;overflow-wrap:anywhere;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:10;overflow:hidden}
.th-card .th-prev{margin:0;color:var(--soft);font-size:calc(14px*var(--fz,1));line-height:1.45;white-space:pre-line;overflow-wrap:anywhere;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:9;overflow:hidden}
.th-card .th-meta{display:flex;flex-wrap:wrap;gap:4px 8px;align-items:center;margin-top:4px;font-size:calc(13px*var(--fz,1))}
.th-card .th-tl{color:var(--indigo);text-decoration:none}.th-card .th-tl:hover{text-decoration:underline}
.th-card .th-date{margin-left:auto;color:var(--soft);white-space:nowrap}
.th-card .th-st{border:1px solid currentColor;border-radius:999px;padding:0 7px;font-size:calc(12px*var(--fz,1))}
.th-card .t-thread{color:var(--th-chat)}.th-card .t-done{color:var(--th-done)}.th-card .t-ai-question{color:var(--th-ask)}
.th-card .t-monitor{color:var(--th-monitor)}.th-card .t-sent{color:var(--th-sent);border-style:dashed}.th-card .t-waiting{color:var(--th-amber)}.th-card .t-working{color:var(--th-chat)}
.th-card.ask{border-color:var(--th-amber);box-shadow:inset 3px 0 0 var(--th-amber);background:color-mix(in srgb,var(--th-amber) 7%,var(--card))}
.th-card .th-go{color:var(--th-done);border:1px solid currentColor;border-radius:999px;padding:0 7px;font-size:calc(12px*var(--fz,1));font-weight:600}
.th-card .th-wait{margin:2px 0 0;color:var(--th-sent)}
.th-card.sent{box-shadow:inset 4px 0 0 var(--th-amber)}.th-card.working{box-shadow:inset 4px 0 0 var(--th-chat)}
.th-card.sent .th-wait,.th-card.working .th-wait{color:var(--ink);font-weight:600}
.th-rdot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px}
.th-card.foc{border-color:var(--th-focus);box-shadow:inset 3px 0 0 var(--th-focus);background:color-mix(in srgb,var(--th-focus) 9%,var(--card))}
.th-card.foc.pin{outline:2px solid var(--th-focus)}
.th-card.foc .th-flink{color:inherit;text-decoration:none}.th-card.foc .th-title{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;overflow:hidden}
.th-fmark{font:600 calc(11px*var(--fz,1)) var(--mono);letter-spacing:.08em;color:var(--th-focus)}
.th-infocus{display:inline-flex;align-items:center;border-radius:999px;padding:0 8px;font-size:calc(11px*var(--fz,1));color:var(--th-focus);background:color-mix(in srgb,var(--th-focus) 9%,var(--card));text-decoration:none;white-space:nowrap}
.th-tick{margin-right:6px}.th-tick input{vertical-align:middle;width:16px;height:16px}
@keyframes thflash{0%,100%{background-color:var(--card)}50%{background-color:var(--sun-bg)}}.th-card.th-flash{animation:thflash 1.2s ease-in-out 2}
.th-empty{color:var(--soft)}
.th-flowc{margin:0 0 8px}.th-flowc b{color:var(--ink)}
.th-sec{margin:0 0 18px}
.th-sec h2{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:calc(13px*var(--fz,1));text-transform:uppercase;letter-spacing:.04em;color:var(--soft);margin:0 0 8px}
.th-sec h2 .n{font-variant-numeric:tabular-nums}.th-sec h2 .btn{text-transform:none;letter-spacing:0}
.th-sec h2 .th-sendin{margin-left:auto}
.th-ask{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 10px;padding:8px 10px;border:1px solid var(--line);border-radius:10px;background:var(--card)}
.th-ask input{flex:1 1 180px;font:calc(14px*var(--fz,1)) var(--body);padding:4px 8px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink)}
.th-sent-ok{color:var(--th-done);font-weight:600}
#th-more{margin:6px 0 0}
#th-main{position:relative;min-width:0}
.th-pop{position:absolute;z-index:20;max-width:100%;border:1px solid var(--line);border-radius:12px;padding:10px 12px;background:var(--card);box-shadow:0 10px 30px rgba(0,0,0,.25)}
.th-pop .th-edit .th-tx{min-height:8em}
.th-pop .th-edhead{display:flex;align-items:center;gap:6px;margin:-2px 0 6px}
.th-pop .th-vsw{display:inline-flex;margin-right:auto;border:1px solid var(--line);border-radius:8px;overflow:hidden}
.th-pop .th-vb{border:0;background:none;padding:2px 10px;font:calc(12px*var(--fz,1)) var(--body);color:var(--soft);border-radius:0}
.th-pop .th-vb.on{background:var(--paper);color:var(--ink);font-weight:600}
.th-pop .th-view{min-height:8em;max-height:60vh;overflow-y:auto;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);font-size:calc(15px*var(--fz,1));overflow-wrap:anywhere}
.th-pop .th-vtail{min-height:2.4em;cursor:text}
.th-pop .th-acts>*{white-space:nowrap}.th-pop .th-kebab{margin-left:auto;font-size:calc(18px*var(--fz,1));line-height:1;padding:0 8px}
.th-menu{position:absolute;right:8px;z-index:30;min-width:220px;max-width:calc(100% - 16px);display:flex;flex-direction:column;padding:6px;border:1px solid var(--line);border-radius:10px;background:var(--card);box-shadow:0 8px 24px rgba(0,0,0,.2)}
.th-menu>button{justify-content:flex-start;border:0;background:none;padding:7px 10px;font-weight:500;border-radius:6px;text-align:left}
.th-menu>button:hover{background:var(--paper)}.th-menu>button.danger{color:var(--rose)}
.th-menu .th-mrow{display:flex;align-items:center;gap:6px;padding:5px 10px;font-size:calc(13px*var(--fz,1));color:var(--soft)}
.th-menu hr{border:0;border-top:1px solid var(--line);margin:4px 0}
.th-foc{display:flex;flex-direction:column;gap:4px;margin-top:8px;padding:8px;border:1px solid var(--line);border-left:3px solid var(--th-focus);border-radius:10px;background:var(--paper)}
.th-foc .th-in{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:calc(13px*var(--fz,1))}
.th-foc .th-in b{color:var(--th-focus)}
.th-foc .th-fl{display:flex;flex-direction:column;max-height:220px;overflow-y:auto}
.th-foc .th-fl button{justify-content:flex-start;border:0;background:none;font-weight:500;padding:5px 8px;text-align:left;border-radius:6px}
.th-foc .th-fl button:hover{background:var(--card)}.th-foc .th-fl .new{color:var(--th-focus);font-weight:700}
.th-foc input{font:calc(14px*var(--fz,1)) var(--body);padding:4px 8px;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--ink)}
.th-pop .th-emsg{margin:6px 0 0}
div[data-part="role-pick"]{display:flex;flex-direction:column;gap:8px;min-width:min(240px,100%);padding:8px;border:1px solid var(--line);border-radius:12px;background:var(--card);box-shadow:0 6px 20px rgba(0,0,0,.12)}
div[data-part="role-pick"] .rpick-row{display:flex;align-items:center;gap:10px;min-height:44px;padding:0 14px;border:1px solid var(--line);border-radius:10px;background:var(--paper);font-weight:500}
div[data-part="role-pick"] .rpick-row.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent)}
div[data-part="role-pick"] .rpick-dot{width:12px;height:12px;border-radius:50%;flex:none}
div[data-part="role-pick"] .rpick-x{align-self:flex-end;min-width:44px;min-height:44px;justify-content:center}
.th-concept h2{font:800 calc(22px*var(--fz,1)) var(--display);margin:8px 0 8px;display:flex;flex-wrap:wrap;align-items:baseline;gap:10px}
.th-cbody{margin-top:12px;padding:12px 14px;border:1px solid var(--line);border-radius:12px;background:var(--card);overflow-wrap:anywhere}
.th-cbody h4{margin:14px 0 4px;font-size:calc(14px*var(--fz,1))}.th-cbody h4:first-child{margin-top:0}
.th-cbody ul{margin:4px 0;padding-left:20px}.th-cbody li{margin:2px 0}
.th-cbody .code{font:calc(11px*var(--fz,1)) var(--mono);color:var(--soft);margin-left:4px}
mark{background:var(--sun-bg);color:inherit;border-radius:3px;padding:0 2px}
.md>:first-child{margin-top:0}.md h3,.md h4{font-size:1.05em;margin:.8em 0 .3em}.md p{margin:.4em 0}.md ul,.md ol{margin:.3em 0;padding-left:1.4em}
.md pre{background:var(--slate-bg);padding:8px;border-radius:8px;overflow-x:auto;white-space:pre-wrap}.md blockquote{margin:.4em 0;padding-left:10px;border-left:3px solid var(--line);color:var(--soft)}
.md a{color:var(--indigo)}
.th-modal{position:fixed;inset:0;z-index:50;display:grid;place-items:center;background:rgba(0,0,0,.35);padding:16px}
.th-mbox{max-width:420px;width:100%;background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px;box-shadow:0 10px 30px rgba(0,0,0,.3)}
.th-mbox h3{margin:0 0 6px;font-size:calc(17px*var(--fz,1))}.th-mbox p{margin:0 0 12px;color:var(--soft)}.th-mbox .th-acts{justify-content:flex-end}
@media (max-width:720px){.th-filters{display:inline-flex}.th-grid{grid-template-columns:minmax(0,1fr)}.th-side{display:none;position:static;max-height:none;overflow:visible;border:1px solid var(--line);border-radius:12px;background:var(--card);padding:10px 12px}body.th-open .th-side{display:block}.th-list{columns:1}.th-cap.th-hasbrain{grid-template-columns:minmax(0,1fr)}}
@media (max-width:600px){.th-cap .th-tx{min-height:calc(4 * 1.45em + 18px)}.th-cap .th-keys{display:none}.th-pop{left:0!important;width:100%!important}}
@media (prefers-reduced-motion:reduce){.th-mic.on,.th-card.th-flash{animation:none}}
.th-thread{display:flex;flex-direction:column;max-height:85vh}.th-thead{display:flex;gap:8px;align-items:center;margin:-2px 0 8px}.th-thead b{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.th-thread .th-tbody{max-height:58vh;min-height:4em}.th-tfoot{margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}.th-tfoot .th-tx{width:100%;font:calc(15px*var(--fz,1))/1.45 var(--body);padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink);resize:vertical;min-height:4.5em}.th-tfoot .th-tx:focus{outline:2px solid var(--accent);outline-offset:-1px}.th-tfoot .th-acts{display:flex;gap:8px;align-items:center;margin-top:6px}.th-thread.th-tbig{box-shadow:0 10px 40px rgba(0,0,0,.3)}.th-thread.th-tbig .th-tbody{max-height:65vh}.th-capcol.th-threading>*:not(.th-thread){display:none!important}.th-thread.th-incap{flex:1 1 auto}.th-thread.th-incap .th-tbody{max-height:65vh}.th-thread.th-incap .th-tbody{max-height:16em;min-height:6em}.th-thread.th-incap .th-tfoot .th-tx{min-height:3.2em}.th-thread .th-tbody{overflow-y:auto;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);font-size:calc(15px*var(--fz,1));overflow-wrap:anywhere}.th-brain canvas{height:0;flex:1 1 auto}#th-body.th-stale{opacity:.55;pointer-events:none;transition:opacity .2s}.d2flow{display:inline-flex;align-items:center;margin-left:8px;padding:5px 9px;border:1px solid var(--line);border-radius:999px;background:var(--card);color:var(--soft);font:600 12px/1 var(--body);letter-spacing:0;vertical-align:middle;cursor:pointer}.d2flow[data-flow="on"]{border-color:transparent;background:color-mix(in srgb,var(--flow,var(--th-focus,#1f5bb5)) 12%,transparent);color:var(--flow,var(--th-focus,#1f5bb5))}.th-info{position:relative;display:inline-block;margin-left:6px;vertical-align:middle}.th-ib{width:22px;height:22px;padding:0;border-radius:50%;border:1px solid var(--line);background:var(--card);color:var(--soft);font:600 12px/1 var(--body);cursor:pointer}.th-ip{position:absolute;z-index:30;left:0;top:28px;width:min(340px,80vw);padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--card);color:var(--ink);font:400 14px/1.45 var(--body);letter-spacing:0;box-shadow:0 8px 24px rgba(0,0,0,.18)}.th-ip[hidden]{display:none}.th-ih{display:block;margin-top:6px;color:var(--soft)}.th-ih a{color:var(--indigo)}.sk{display:inline-block;height:.8em;background:var(--line);border-radius:4px;opacity:.7;vertical-align:middle}.th-card.th-skel{display:flex;flex-direction:column;gap:9px;min-height:7.5em;break-inside:avoid}.th-card.th-skel .sk{display:block}.wrap>h1{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px}.wrap>h1 small{margin-left:0}.wrap>h1 .d2flow,.wrap>h1 .th-info{margin-left:0}.th-bload{position:absolute;left:0;right:0;top:0;bottom:4.6em;display:grid;place-items:center;margin:0;color:var(--soft);font-size:calc(14px*var(--fz,1));pointer-events:none}.th-bcap{min-height:calc(3 * 1.45em + 14px)}.th-noq{margin:0 0 6px;color:var(--soft)}.th-card.rep{border-left:4px solid var(--leaf,#3f7d4e);background:color-mix(in srgb,var(--leaf,#3f7d4e) 7%,var(--card))} .th-tx{display:block;box-sizing:border-box;white-space:pre-wrap;overflow-wrap:anywhere;overflow-y:auto;text-align:left;cursor:text;-webkit-user-select:text;user-select:text} .th-tx:empty::before{content:attr(data-ph);color:var(--soft);pointer-events:none} .th-tfoot .th-tx{min-height:4.5em}
/* The Focus view (SpecUi ^thoughts-ui-24..29, -31): the head, the two-lobed map, the phone strip, the editor under them.
On d2's tokens; the ring, the core and the current node are the Focus blue. Nothing here moves by itself: motion is
the page script's, and a jump under prefers-reduced-motion. */
.fo{max-width:900px;margin:0 auto}
.fo-back{margin:0 0 4px}.fo-back a{color:var(--soft);text-decoration:none}
.fo-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin:0 0 8px}
.fo-head h1{margin:0;font-size:calc(22px*var(--fz,1))}
.fo-mark{font:600 calc(11px*var(--fz,1)) var(--mono,ui-monospace,monospace);letter-spacing:.08em;color:var(--th-focus);border:1px solid var(--th-focus);border-radius:999px;padding:1px 8px;background:color-mix(in srgb,var(--th-focus) 9%,var(--card))}
.fo-sent{color:var(--th-sent,var(--soft));font-size:calc(13px*var(--fz,1));border:1px dashed currentColor;border-radius:999px;padding:0 8px}
.fo-sp{flex:1}
.fo-addw{position:relative}
#fo-addmenu{position:absolute;right:0;top:calc(100% + 4px);z-index:5;display:flex;flex-direction:column;min-width:170px;background:var(--card);border:1px solid var(--line);border-radius:10px;padding:4px;box-shadow:0 6px 20px rgb(0 0 0 / .12)}
#fo-addmenu[hidden]{display:none}
#fo-addmenu a{padding:6px 10px;border-radius:7px;color:var(--ink);text-decoration:none;cursor:pointer}
#fo-addmenu a:hover{background:color-mix(in srgb,var(--th-focus) 9%,var(--card))}
#fo-sendask{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 10px;padding:8px 12px;border:1px solid var(--th-chat);border-radius:10px;background:color-mix(in srgb,var(--th-chat) 7%,var(--card))}
#fo-sendask[hidden]{display:none}
#fo-map{display:block;width:100%;height:auto;max-height:440px;user-select:none}
#fo-map{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:6px;box-sizing:border-box}
#fo-map .fo-lobe{fill:color-mix(in srgb,var(--th-focus) 6%,var(--card));stroke:var(--line);stroke-width:1.5}
#fo-map .fo-cap{font:600 calc(10px*var(--fz,1)) var(--mono,ui-monospace,monospace);letter-spacing:.08em;fill:var(--soft);text-anchor:middle}
#fo-map .fo-spoke,#fo-map .fo-sspoke{stroke:color-mix(in srgb,var(--soft) 45%,var(--line));stroke-width:2;fill:none}
#fo-map .fo-spoke.on{stroke:var(--th-focus);stroke-width:3}
#fo-map .fo-link{stroke:color-mix(in srgb,var(--soft) 45%,var(--line));stroke-width:1.75;stroke-dasharray:3 5;stroke-linecap:round;fill:none}
#fo-map .fo-node,#fo-map .fo-core,#fo-map .fo-stack{cursor:pointer;outline:none}
#fo-map .fo-node a,#fo-map .fo-core a{text-decoration:none}
#fo-map .fo-shape{fill:var(--card);stroke:var(--th-focus);stroke-width:2}
#fo-map .fo-node[data-kind="topic"] .fo-shape{stroke:var(--indigo,#3b3fb6)}
#fo-map .fo-node.sel .fo-shape{fill:color-mix(in srgb,var(--th-focus) 14%,var(--card));stroke-width:3}
#fo-map .fo-node:focus-visible .fo-shape,#fo-map .fo-core:focus-visible circle{stroke-width:3}
#fo-map .fo-num{font:600 calc(12px*var(--fz,1)) var(--mono,ui-monospace,monospace);fill:var(--soft);text-anchor:middle;pointer-events:none}
#fo-map .fo-name{font:500 calc(12px*var(--fz,1)) var(--body);fill:var(--soft);text-anchor:middle;pointer-events:none}
#fo-map .fo-name.sm{font-size:calc(11px*var(--fz,1))}
#fo-map .fo-node.sel .fo-name{font-weight:700;fill:var(--ink)}
#fo-map .fo-node.gone .fo-name{text-decoration:line-through;fill:var(--soft)}
#fo-map .fo-node.gone .fo-shape{stroke-dasharray:3 3;stroke:var(--soft)}
#fo-map .fo-core circle{fill:var(--th-focus)}
#fo-map .fo-core text{font:600 calc(13px*var(--fz,1)) var(--body);fill:#fff;text-anchor:middle;pointer-events:none}
#fo-map .fo-stack rect{fill:var(--card);stroke:var(--indigo,#3b3fb6);stroke-width:1.3}
#fo-ring{fill:none;stroke:var(--th-focus);stroke-width:2.5;opacity:.55;pointer-events:none}
#fo-stacklist{list-style:none;margin:0 0 8px;padding:0;display:flex;flex-wrap:wrap;gap:6px}
#fo-stacklist[hidden]{display:none}
#fo-stacklist a{display:inline-block;border:1px solid var(--line);border-radius:7px;padding:2px 8px;text-decoration:none;color:var(--ink);font-size:calc(13px*var(--fz,1))}
.fo-nav,.fo-pnav{display:flex;align-items:center;justify-content:center;gap:12px;margin:6px 0 10px}
.fo-pnav{display:none;margin:0 0 0 auto}
#fo-count{color:var(--soft);font-variant-numeric:tabular-nums;min-width:7em;text-align:center}
#fo-strip{display:flex;gap:6px;overflow-x:auto;padding:4px 0 8px;margin:0 0 8px;scrollbar-width:thin}
.fo-chip{flex:none;display:inline-flex;align-items:center;gap:6px;border:1.5px solid var(--th-focus);border-radius:999px;padding:7px 10px;font:600 calc(12px*var(--fz,1))/1 var(--body);color:var(--ink);background:var(--card);text-decoration:none;white-space:nowrap}
.fo-chip[data-kind="topic"]{border-color:var(--indigo,#3b3fb6);border-radius:7px}
.fo-chip .fo-sh{display:inline-block;width:9px;height:9px;border-radius:50%;border:1.5px solid var(--th-focus);flex:none}
.fo-chip[data-kind="topic"] .fo-sh{border-radius:2px;border-color:var(--indigo,#3b3fb6)}
.fo-chip.f{background:var(--th-focus);color:#fff}
.fo-chip.on{background:color-mix(in srgb,var(--th-focus) 14%,var(--card));box-shadow:0 0 0 2px var(--th-focus)}
.fo-chip.f.on{background:var(--th-focus);box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--th-focus)}
.fo-chip.gone{text-decoration:line-through;color:var(--soft)}
#fo-empty{border:1px dashed var(--th-focus);border-radius:12px;padding:12px 16px;margin:0 0 12px;background:color-mix(in srgb,var(--th-focus) 5%,var(--card))}
#fo-empty p{margin:4px 0}
#fo-ed{border:1px solid var(--th-focus);border-radius:10px;padding:10px 12px;background:var(--card)}
.fo-edhead{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin:0 0 6px}
.fo-kind{font:600 calc(11px*var(--fz,1))/1.3 var(--mono,ui-monospace,monospace);letter-spacing:.06em;color:var(--th-focus)}
.fo-also{font-size:calc(12px*var(--fz,1));color:var(--soft)}
.fo-saved{margin-left:auto;font-size:calc(12px*var(--fz,1));color:var(--th-done,#2e8b57)}
#fo-text{width:100%;box-sizing:border-box;min-height:12em;font:calc(15px*var(--fz,1)) var(--body);padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink);resize:vertical}
#fo-text[readonly]{background:color-mix(in srgb,var(--soft) 6%,var(--card))}
.fo-acts{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:8px}
#fo-mergelist{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px;flex-basis:100%}
#fo-mergelist[hidden]{display:none}
.fo-gonemsg{color:var(--soft);margin:4px 0}
@media (max-width:599px){#fo-map,.fo-nav{display:none}.fo-pnav{display:flex}}
@media (min-width:600px){#fo-strip{display:none}}
/* Add and Send focus on the editor's bar, at its right; on a phone the bar takes its own line under the name */
.fo-edhead .fo-bar{margin-left:auto;display:inline-flex;gap:6px;align-items:center}
@media (max-width:599px){.fo-edhead .fo-bar{margin-left:0;flex-basis:100%}}
/* the markdown toolbar over the Focus's boxes */
.fo-tools{display:flex;flex-wrap:wrap;gap:2px;margin:0 0 4px}
.fo-tool{min-width:28px;font:600 calc(13px*var(--fz,1)) var(--body);border:1px solid var(--line);border-radius:6px;background:var(--paper);color:var(--ink);padding:2px 6px;cursor:pointer}
#fo-newbox{border:1px dashed var(--th-focus);border-radius:8px;padding:8px;margin:0 0 8px}
#fo-newbox[hidden]{display:none}
#fo-newtext{width:100%;box-sizing:border-box;min-height:5em;font:calc(15px*var(--fz,1)) var(--body);padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink)}
/* the Focus's thoughts as cards under the editor, then the wrap-up */
#fo-cards{margin-top:14px}#fo-cards h2{font-size:calc(15px*var(--fz,1));margin:0 0 8px}#fo-cards .n{color:var(--soft);font-weight:400}
.fo-cacts{display:flex;gap:6px;align-items:center;justify-content:flex-end;margin-top:4px}
.fo-cst{font-size:calc(12px*var(--fz,1));color:var(--th-chat,var(--soft))}.fo-cst.ok{color:var(--th-done,var(--leaf))}
.fo-wrap{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:flex-end;margin:10px 0}
/* sent: a green card saying what it became */
.fo-head + .fo-sent{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline;border:1px solid var(--th-done,var(--leaf));border-style:solid;border-radius:10px;padding:8px 12px;margin:0 0 10px;color:var(--ink);background:color-mix(in srgb,var(--th-done,var(--leaf)) 10%,var(--card))}
.fo-head + .fo-sent b{color:var(--th-done,var(--leaf))}.fo-head + .fo-sent .small{color:var(--soft)}
/* the model lobe (^thoughts-ui-24): concepts violet, features teal, behaviours amber, each its own shade in dark mode;
a legend under the map, and what a tapped node opens; the cards it names light up with an outline */
.fo{--fo-concept:#7a5cc9;--fo-feature:#3f8f8a;--fo-behaviour:#c98a2b}
@media (prefers-color-scheme:dark){.fo{--fo-concept:#a58bf0;--fo-feature:#5cc0b8;--fo-behaviour:#e5b454}}
[data-mk="concept"]{--mk:var(--fo-concept)}[data-mk="feature"]{--mk:var(--fo-feature)}[data-mk="behaviour"]{--mk:var(--fo-behaviour)}
#fo-map .fo-mnode{cursor:pointer;outline:none}#fo-map .fo-name.end{text-anchor:end}
#fo-map .fo-mshape{fill:color-mix(in srgb,var(--mk) 22%,var(--card));stroke:var(--mk);stroke-width:2}
#fo-map .fo-mnode.on .fo-mshape,#fo-map .fo-mnode:focus-visible .fo-mshape{stroke-width:3.5;fill:color-mix(in srgb,var(--mk) 40%,var(--card))}
#fo-map .fo-mspoke{stroke:color-mix(in srgb,var(--soft) 30%,var(--line));stroke-width:1.5;fill:none}
#fo-map .fo-mlink{stroke:color-mix(in srgb,var(--soft) 45%,var(--line));stroke-width:1.25;stroke-dasharray:1 4;stroke-linecap:round;fill:none}
#fo-map .fo-stack[data-kind="model"] rect{stroke:var(--fo-concept)}
.fo-legend{display:flex;flex-wrap:wrap;gap:4px 14px;margin:4px 2px 8px;font-size:calc(12px*var(--fz,1));color:var(--soft)}
.fo-lg{display:inline-flex;align-items:center;gap:5px}.fo-lg i{width:10px;height:10px;border-radius:50%;background:color-mix(in srgb,var(--mk) 22%,var(--card));border:2px solid var(--mk)}
.fo-mchip{flex:none;display:inline-flex;align-items:center;gap:5px;border:1.5px solid var(--mk);border-radius:999px;padding:4px 9px;font:500 calc(12px*var(--fz,1))/1 var(--body);color:var(--ink);background:color-mix(in srgb,var(--mk) 10%,var(--card));white-space:nowrap;cursor:pointer}
.fo-mchip .fo-sh{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--mk);flex:none}.fo-mchip.on{box-shadow:0 0 0 2px var(--mk)}
#fo-mstack{list-style:none;margin:0 0 8px;padding:0;display:flex;flex-wrap:wrap;gap:6px}#fo-mstack[hidden]{display:none}
.fo-mopen{border:1px solid var(--mk);border-radius:10px;padding:10px 12px;margin:0 0 10px;background:color-mix(in srgb,var(--mk) 5%,var(--card))}.fo-mopen[hidden]{display:none}
.fo-mopen .fo-kind{color:var(--mk)}.fo-mtext{white-space:pre-wrap;font-size:calc(14px*var(--fz,1));max-height:16em;overflow:auto}
@keyframes fomlit{0%,100%{outline-color:transparent}50%{outline-color:var(--lit,var(--fo-concept))}}.th-card.fo-mlit{outline:3px solid transparent;outline-offset:2px;animation:fomlit .9s ease-in-out 2}
@media (prefers-reduced-motion:reduce){.th-card.fo-mlit{animation:none;outline-color:var(--lit,var(--fo-concept))}}div[data-part="role-pick"]{display:flex;flex-direction:column;gap:8px;min-width:min(240px,100%);max-width:100%;padding:8px;border:1px solid var(--line);border-radius:12px;background:var(--card,var(--paper));box-shadow:0 6px 20px rgba(0,0,0,.12)}
div[data-part="role-pick"] .rpick-row{display:flex;align-items:center;gap:10px;min-height:44px;margin:0;padding:0 14px;border:1px solid var(--line);border-radius:10px;background:var(--paper);color:var(--ink);font:500 calc(15px*var(--fz,1)) var(--body);text-align:left;cursor:pointer}
div[data-part="role-pick"] .rpick-row:hover{background:var(--bg2,var(--paper))}div[data-part="role-pick"] .rpick-row.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent)}
div[data-part="role-pick"] .rpick-dot{width:12px;height:12px;border-radius:50%;flex:none}
div[data-part="role-pick"] .rpick-x{align-self:flex-end;min-width:44px;min-height:44px}.d2c-btn{position:fixed;right:18px;bottom:12vh;z-index:60;width:48px;height:48px;border-radius:50%;border:1px solid var(--line);background:var(--card);color:var(--ink);font-size:22px;box-shadow:0 4px 14px rgba(0,0,0,.18);cursor:pointer}
.d2c-btn.d2c-blue{background:var(--th-chat,#2f6fdc);border-color:var(--th-chat,#2f6fdc);color:#fff}
.d2c-btn.live::after{content:"";position:absolute;left:-2px;bottom:-2px;width:12px;height:12px;border-radius:50%;background:var(--leaf,#2f9a6f);border:2px solid var(--card)}
.d2c-btn.draft::before{content:"";position:absolute;left:-2px;top:-2px;width:12px;height:12px;border-radius:50%;background:var(--sun,#d99a1e);border:2px solid var(--card)}
.d2c-btn .d2c-n{position:absolute;top:-4px;right:-4px;min-width:18px;padding:1px 5px;border-radius:999px;background:var(--rose,#c0392b);color:#fff;font:700 11px/16px var(--body)}
.d2c-box{position:fixed;right:18px;bottom:calc(12vh + 58px);z-index:61;width:min(380px,calc(100vw - 36px));max-width:100vw;max-height:min(560px,calc(88vh - 76px));overflow-x:hidden;box-sizing:border-box;display:flex;flex-direction:column;border:1px solid var(--line);border-radius:12px;background:var(--card);color:var(--ink);box-shadow:0 8px 28px rgba(0,0,0,.22);font-size:calc(14px*var(--fz,1))}
.d2c-head{display:flex;flex-wrap:wrap;flex:none;align-items:center;gap:6px;padding:4px 10px;min-height:0;border-bottom:1px solid var(--line);border-radius:12px 12px 0 0}.d2c-head b{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.d2c-head .d2c-sp{flex:1}
.d2c-dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--soft);flex:none}.d2c-dot.live{background:var(--leaf,#2f9a6f)}.d2c-dot.asked{background:var(--sun,#d99a1e)}
.d2c-sub{flex:none;padding:6px 10px 4px;color:var(--soft);font-size:calc(12px*var(--fz,1))}.d2c-subj{display:block;color:var(--ink);font-weight:600;font-size:calc(16px*var(--fz,1));line-height:1.3;overflow-wrap:anywhere}
.d2c-msgs{flex:1;overflow:auto;padding:8px 10px;display:flex;flex-direction:column;gap:6px;min-height:120px}
.d2c-m{position:relative;max-width:85%;padding:6px 26px 6px 10px;border-radius:12px;background:var(--slate-bg,#eef1f5);white-space:pre-wrap;word-wrap:break-word;overflow-wrap:anywhere;align-self:flex-start}.d2c-t a{color:var(--accent,#2f6fdc);text-decoration:underline}.d2c-m.me{align-self:flex-end;background:var(--indigo-bg,#eceafd)}
.d2c-cp{position:absolute;top:2px;right:4px;border:0;background:none;color:var(--soft);font-size:12px;cursor:pointer;padding:0}
.d2c-st{color:var(--soft);font-style:italic;font-size:calc(12px*var(--fz,1))}
.d2c-in{display:flex;flex:none;gap:6px;align-items:flex-end;padding:8px 10px;border-top:1px solid var(--line)}.d2c-in textarea{flex:1;min-width:0;min-height:66px;max-height:140px;resize:vertical;font:inherit}.d2c-side{display:flex;flex-direction:column;gap:6px;align-items:stretch}
.d2c-wave{display:inline-flex;gap:2px;align-items:center;height:16px}.d2c-wave i{display:block;width:3px;height:6px;border-radius:2px;background:currentColor}.d2c-wave i:nth-child(2){height:12px}.d2c-wave i:nth-child(3){height:9px}
.d2c-mic[aria-pressed=true]{color:var(--rose,#c0392b)}.d2c-mic[aria-pressed=true] .d2c-wave i{animation:d2cwave .9s ease-in-out infinite}.d2c-mic[aria-pressed=true] .d2c-wave i:nth-child(2){animation-delay:.15s}.d2c-mic[aria-pressed=true] .d2c-wave i:nth-child(3){animation-delay:.3s}.d2c-mic[aria-pressed=true] .d2c-wave i:nth-child(4){animation-delay:.45s}
@keyframes d2cwave{0%,100%{transform:scaleY(.5)}50%{transform:scaleY(1.6)}}
.d2c-after,.d2c-warn,.d2c-first{padding:10px;border-top:1px solid var(--line)}.d2c-after p,.d2c-warn p,.d2c-first p{margin:0 0 8px}.d2c-acts{display:flex;gap:6px;flex-wrap:wrap}
.d2c-row{display:flex;gap:8px;align-items:flex-start;padding:8px 10px;border-bottom:1px solid var(--line);cursor:pointer}.d2c-row:hover{background:var(--slate-bg,#eef1f5)}.d2c-row .d2c-l{flex:1;min-width:0}.d2c-row .d2c-l div{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.d2c-row .d2c-u{min-width:18px;padding:1px 5px;border-radius:999px;background:var(--rose,#c0392b);color:#fff;font:700 11px/16px var(--body);text-align:center}
.d2c-past{display:block;width:100%;padding:8px 10px;border:0;border-bottom:1px solid var(--line);background:none;color:var(--soft);text-align:left;cursor:pointer;font:inherit}
.d2c-foot{display:flex;gap:8px;align-items:center;padding:8px 10px;color:var(--soft);font-size:calc(12px*var(--fz,1))}.d2c-foot span{flex:1}
.d2c-chip{border-style:dashed!important}.d2c-chip[aria-disabled=true]{opacity:.5;cursor:not-allowed}
.d2c-pop{margin:6px 0;padding:10px;border:1px solid var(--line);border-radius:10px;background:var(--card)}.d2c-pop p{margin:0 0 6px}.d2c-pop input{width:100%;margin:2px 0 8px}
.d2c-tint{background:color-mix(in srgb,var(--th-chat,#2f6fdc) 15%,var(--card))}
.d2c-box[hidden]{display:none!important}
@media (max-width:600px){.d2c-box{left:0;right:0;bottom:0;width:100%;max-height:45vh;border-radius:12px 12px 0 0}.d2c-msgs{min-height:48px}}div[data-part="role-pick"]{display:flex;flex-direction:column;gap:8px;min-width:min(240px,100%);max-width:100%;padding:8px;border:1px solid var(--line);border-radius:12px;background:var(--card,var(--paper));box-shadow:0 6px 20px rgba(0,0,0,.12)}
div[data-part="role-pick"] .rpick-row{display:flex;align-items:center;gap:10px;min-height:44px;margin:0;padding:0 14px;border:1px solid var(--line);border-radius:10px;background:var(--paper);color:var(--ink);font:500 calc(15px*var(--fz,1)) var(--body);text-align:left;cursor:pointer}
div[data-part="role-pick"] .rpick-row:hover{background:var(--bg2,var(--paper))}div[data-part="role-pick"] .rpick-row.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent)}
div[data-part="role-pick"] .rpick-dot{width:12px;height:12px;border-radius:50%;flex:none}
div[data-part="role-pick"] .rpick-x{align-self:flex-end;min-width:44px;min-height:44px}
.fo-tp{--mk:var(--indigo,#3b3fb6)}.fo-rp{--mk:var(--soft)}.fo-rp.added{--mk:var(--indigo,#3b3fb6)}#fo-map .fo-rel{cursor:pointer}#fo-map .fo-rel.on circle{stroke-width:3}#fo-map .fo-rel.added circle{stroke:var(--indigo,#3b3fb6);fill:color-mix(in srgb,var(--indigo,#3b3fb6) 20%,var(--card))}#fo-map .fo-rel.added text{fill:var(--indigo,#3b3fb6)}.fo-tp .fo-kind{color:var(--mk)}
.fo-sw{display:inline-flex;border:1px solid var(--line);border-radius:999px;overflow:hidden;font-size:11px;margin-right:6px;vertical-align:middle}.fo-sw span{padding:2px 8px}.fo-sw .on{background:var(--indigo-bg,#f5e8dc);color:var(--indigo,#9a4b12);font-weight:700}
.fo-relg circle{fill:none;stroke:#9c9a93;stroke-dasharray:3 3}.fo-relg text,.fo-relgcap{fill:#9c9a93;font-size:12px}.fo-head .btn{margin-left:6px}.fo-brain{position:relative}.fo-bfold{position:absolute;top:2px;right:2px;z-index:1}.fo-bcap{border:1px solid var(--line);border-radius:10px;margin:2px 0 6px;min-height:0;padding:6px 40px 6px 12px;background:var(--paper)}.fo-brain.fo-bfolded{position:sticky;top:0;z-index:5;background:var(--paper)}.fo-brain.fo-bfolded #fo-map,.fo-brain.fo-bfolded+p.small{display:none}.fo-brain.fo-bfolded .fo-bcap{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.th-card.fo-gone{opacity:0;transform:scale(.96);transition:opacity .25s,transform .25s}#fo-outn{margin-left:8px;vertical-align:middle}.th-card.fo-picked{outline:2px solid var(--rose,#b23a48);outline-offset:-2px}#fo-pickbar{position:sticky;bottom:10px;z-index:6;display:flex;gap:10px;align-items:center;margin:10px auto;width:max-content;max-width:100%;padding:8px 12px;border:1px solid var(--line);border-radius:999px;background:var(--card);box-shadow:0 4px 18px rgba(0,0,0,.18)}#fo-ask{border:1px solid var(--line);border-radius:14px;padding:16px 18px;max-width:420px;background:var(--card);color:var(--ink)}#fo-ask::backdrop{background:rgba(0,0,0,.35)}#fo-ask p{margin:0 0 12px;line-height:1.45}#fo-asksw{margin:0 0 12px}#fo-asksw span{cursor:pointer}.fo-askb{display:flex;gap:8px;justify-content:flex-end}#fo-askgo.fo-danger{background:var(--rose,#b23a48);border-color:var(--rose,#b23a48)}.fo-head #fo-send{margin-right:6px}main.fo .btn.sm:not(.primary),#fo-ask .btn.sm:not(.primary){background:color-mix(in srgb,var(--th-focus,#1f5bb5) 22%,var(--card));border-color:color-mix(in srgb,var(--th-focus,#1f5bb5) 55%,var(--line))}main.fo .btn.sm:not(.primary):hover,#fo-ask .btn.sm:not(.primary):hover{background:color-mix(in srgb,var(--th-focus,#1f5bb5) 32%,var(--card))}#fo-outn[hidden]{display:none!important}/* Page:Focus (P-1146): what the static mockup didn't need */
.fo-head{position:relative}.fo-head .th-info{position:relative;display:inline-block}
.fo-cbody{white-space:pre-wrap;font-size:calc(14px*var(--fz,1));color:var(--ink);max-height:9.5em;overflow:hidden}
.th-card.fo-open{cursor:default;border-color:var(--th-focus)}.th-card.fo-open .fo-cbody{display:none}
.fo-cedit{width:100%;min-height:6em;font:calc(15px*var(--fz,1)) var(--body);padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);color:var(--ink)}
.fo-cacts{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:6px}
.fo-sentb{display:inline-block;padding:2px 8px;border-radius:999px;background:color-mix(in srgb,var(--th-done) 18%,var(--card));color:var(--th-done);font-weight:700;font-size:calc(12px*var(--fz,1))}
.fo-relg{cursor:pointer}.fo-relg:focus,.fo-relg.on circle{stroke:var(--th-focus)}
#fo-ask .fo-sw span{cursor:pointer}
.fo-first{border:1px solid var(--th-focus);border-radius:10px;padding:8px 12px;margin:0 0 10px;background:color-mix(in srgb,var(--th-focus) 8%,var(--card));font-size:calc(14px*var(--fz,1))}
</style></head>
<body>
<div class="wrap"><main class="fo" id="fo-main"><p class="small fo-back"><a href="#" data-go="/thoughts">‹ Thoughts</a></p><p class="small" id="fo-load">Loading the Focus…</p></main></div>
<dialog id="fo-ask"><p id="fo-askt"></p><div id="fo-asksw" class="fo-sw" hidden><span class="on" data-v="one">one task</span><span data-v="split">split by feature</span></div><div class="fo-askb"><button type="button" class="btn sm" id="fo-askno">Cancel</button><button type="button" class="btn sm primary" id="fo-askgo">OK</button></div></dialog>
<script>/* P-1194/P-1195 (razie, chat 6aca1fb6, 2026-10-10): on his iPad every textarea types slowly and a contenteditable block is fast, so the boxes are plain-text editable blocks that answer like a textarea: value, selectionStart/End, setSelectionRange, select, placeholder, rows. Talk (the page's speech input) and iOS dictation, autocorrect, paste, undo all stay. */
window.thTX=el=>{el.classList.add("th-tx");el.setAttribute("role","textbox");el.setAttribute("aria-multiline","true");el.contentEditable="plaintext-only";
if(el.contentEditable!=="plaintext-only"){el.contentEditable="true";el.addEventListener("paste",e=>{e.preventDefault();const t=(e.clipboardData||window.clipboardData).getData("text/plain");document.execCommand("insertText",false,t)})}
const text=()=>{let o="";const walk=(n,top)=>{for(const c of n.childNodes){if(c.nodeType===3)o+=c.data;else if(c.nodeName==="BR"){if(c.nextSibling||!top)o+="\n"}else if(c.nodeType===1){const blk=/^(DIV|P)$/.test(c.nodeName);if(blk&&o&&!o.endsWith("\n"))o+="\n";walk(c,false)}}};walk(el,true);return o};
const offs=()=>{const sel=getSelection();if(!sel.rangeCount||!el.contains(sel.anchorNode))return [text().length,text().length];const at=(node,off)=>{const r=document.createRange();r.selectNodeContents(el);r.setEnd(node,off);const f=r.cloneContents(),d=document.createElement("div");d.appendChild(f);return window.thTX.len(d)};const a=at(sel.anchorNode,sel.anchorOffset),b=at(sel.focusNode,sel.focusOffset);return [Math.min(a,b),Math.max(a,b)]};
const place=(x,y)=>{const pos=k=>{let left=k;const w=document.createTreeWalker(el,NodeFilter.SHOW_TEXT);let t,last=null;while((t=w.nextNode())){last=t;if(left<=t.data.length)return [t,left];left-=t.data.length}return last?[last,last.data.length]:[el,el.childNodes.length]};const r=document.createRange(),p=pos(x),q=pos(y);r.setStart(p[0],p[1]);r.setEnd(q[0],q[1]);const sel=getSelection();sel.removeAllRanges();sel.addRange(r)};
Object.defineProperties(el,{value:{get:text,set(v){el.textContent=String(v==null?"":v)}},
selectionStart:{get:()=>offs()[0],set(v){place(v,v)}},selectionEnd:{get:()=>offs()[1],set(v){place(v,v)}},
placeholder:{get:()=>el.dataset.ph||"",set(v){el.dataset.ph=v}},rows:{get:()=>+(el.dataset.rows||2),set(v){el.dataset.rows=v}}});
el.setSelectionRange=(x,y)=>place(x,y);el.select=()=>place(0,text().length);return el};
window.thTX.len=d=>{let o=0;const walk=n=>{for(const c of n.childNodes){if(c.nodeType===3)o+=c.data.length;else if(c.nodeName==="BR")o+=1;else if(c.nodeType===1)walk(c)}};walk(d);return o};
function inl(s,home){return esc(s).replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*([^*]+)\*\*/g,"<b>$1</b>").replace(/(^|[^*\w])\*([^*\s][^*]*)\*/g,"$1<i>$2</i>")
.replace(/\[\[([^\]|]+?)(?:\|([^\]]+))?\]\]/g,(m,a,l)=>{const [n,s]=a.split("#");const h=(n?home+"/topics/"+encodeURIComponent(n.trim()):"")+(s?"#"+encodeURIComponent(s.replace(/^\^/,"")):"");return `<a href="#" data-go="${h}">${l||a}</a>`})
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g,(m,l,u)=>`<a href="#" data-go="${/^\//.test(u.replace(/&/g,"&"))?home+u:u}">${l}</a>`)
.replace(/(^|\s)(https?:\/\/[^\s<]+)/g,(m,a,u)=>`${a}<a href="#" data-go="${u}">${u}</a>`).replace(/\s\^([a-z][\w-]*)\s*$/,' <span class="code">^$1</span>')}
function md(src,home=""){const L=String(src).replace(/\r\n/g,"\n").split("\n"),o=[];let list=null,code=null,para=[];
const fp=()=>{if(para.length){o.push("<p>"+para.map(x=>inl(x,home)).join("<br>")+"</p>");para=[]}},fl=()=>{if(list){o.push(`</${list}>`);list=null}};
for(const l of L){if(code!==null){if(/^\s*```/.test(l)){o.push("<pre><code>"+esc(code)+"</code></pre>");code=null}else code+=(code?"\n":"")+l;continue}
if(/^\s*```/.test(l)){fp();fl();code="";continue}let m;
if((m=/^(#{1,6})\s+(.*?)\s*(\{#[\w-]+\})?$/.exec(l))){fp();fl();o.push(`<h${m[1].length<3?3:4}>${inl(m[2],home)}</h${m[1].length<3?3:4}>`);continue}
if((m=/^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(l))){fp();const k=/\d/.test(m[1])?"ol":"ul";if(list!==k){fl();o.push(`<${k}>`);list=k}o.push("<li>"+inl(m[2],home)+"</li>");continue}
if((m=/^\s*>\s?(.*)$/.exec(l))){fp();fl();o.push("<blockquote>"+inl(m[1],home)+"</blockquote>");continue}
if(!l.trim()){fp();fl();continue}fl();para.push(l)}
if(code!==null)o.push("<pre><code>"+esc(code)+"</code></pre>");fp();fl();return o.join("")}
// Page:Focus (P-1146; razie 2026-10-10 "app-focus publish", "go from the last working mockup"): the locked mockup
// App:Mockup-focus-v1 v19, drawn live from GET /api/v2/thoughts/<name>/focus. The map's layout is the built-in Focus
// view's (focuspage.ts, removed in P-1149); the boxes are thTX editable blocks (P-1194: textareas type slowly on the iPad).
const D2=window.d2||{},PARAMS=Object.assign({},window.d2params||{},D2.params||{});
const $=id=>document.getElementById(id),esc=s=>String(s??"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
const enc=n=>n.split(":").map(encodeURIComponent).join(":"),pq=p=>"?project="+encodeURIComponent(p);
const cut=(s,n)=>(s=String(s??"")).length>n?s.slice(0,n-1)+"…":s;
const api=(m,u,b)=>D2.api?Promise.resolve(D2.api(m,u,b)):Promise.reject(new Error("E_BRIDGE: this page runs inside d2 (no d2.api here)"));
const why=e=>String(e&&e.message||e).replace(/^E_[A-Z_]+:\s*/,"");
const go=u=>{if(typeof D2.go==="function")return Promise.resolve(D2.go(u)).catch(()=>{});location.href=u};
const PROJ=String(PARAMS.project||""),NAME="Thought:"+String(PARAMS.name||"").replace(/^Thought:/,""),KEY=PROJ+"/"+NAME;
const fpath=op=>"/api/v2/thoughts/"+enc(NAME)+"/focus"+(op?"/"+op:"")+pq(PROJ);
const tpath=(m,op)=>"/api/v2/thoughts/"+enc(m.name)+(op?"/"+op:"")+pq(m.project||PROJ);
const typing=e=>{const x=e.target;if(x&&(x.tagName==="TEXTAREA"||x.tagName==="INPUT"||x.isContentEditable))window.d2typingAt=performance.now()};
document.addEventListener("keydown",typing,true);document.addEventListener("input",typing,true);
let V=null,view={k:"notes"},stamp="",dirty=false,timer=0,openCard=null,busy=false;
const say=(t,ok)=>{const m=$("fo-msg");if(m){m.textContent=t;m.className="small"+(ok?"":" bad")}};
const thoughts=()=>V.members.filter(m=>m.kind==="thought"&&!m.missing),topics=()=>V.members.filter(m=>m.kind==="topic");
const label=m=>m.title||(m.section?m.name+" › "+m.section:m.name.replace(/^Thought:/,""));
const firstLine=t=>(String(t||"").split("\n").find(l=>l.trim())||"").replace(/^#+\s*/,"");
// ---- the map's layout (focuspage.ts focusLayout, unchanged) ----
const CORE={x:360,y:200},LOBES={model:{x:195,y:200},topic:{x:525,y:200}},LOBE_R={rx:165,ry:150},OX=116,OY=140,MAX_DRAWN=12,MAX_MODEL=24;
const OVAL=(()=>{const n=400,ang=[],len=[0];for(let j=0;j<=n;j++)ang.push(-18+j*228/n);
for(let j=1;j<=n;j++){const p=ang[j-1]*Math.PI/180,q=ang[j]*Math.PI/180;len.push(len[j-1]+Math.hypot(OX*(Math.sin(q)-Math.sin(p)),OY*(Math.cos(q)-Math.cos(p))))}return {ang,len}})();
function evenOnOval(t){const want=t*OVAL.len[OVAL.len.length-1];let j=1;while(j<OVAL.len.length-1&&OVAL.len[j]<want)j++;
const f=(want-OVAL.len[j-1])/((OVAL.len[j]-OVAL.len[j-1])||1);return OVAL.ang[j-1]+f*(OVAL.ang[j]-OVAL.ang[j-1])}
function layout(model,tops){const one=(k,side)=>{const c=side<0?LOBES.model:LOBES.topic,out=[];
for(let i=0;i<k;i++){const many=k>6,deg=k===1?90:many?evenOnOval(i/(k-1)):-12+i*215/(k-1),a=deg*Math.PI/180;
const x=Math.round(c.x+side*(many?OX:98)*Math.sin(a)),y=Math.round(c.y-(many?OY:98)*Math.cos(a));
if(!many)out.push({x,y,above:y<400/3});else if(Math.abs(Math.sin(a))>.72)out.push({x,y,above:false,side:true});else out.push({x,y,above:Math.cos(a)>0?i%2===0:i%2===1})}return out};
return {model:one(model,-1),topic:one(tops,1)}}
function coreLines(t){const w=t.split(/\s+/).filter(Boolean);if(t.length<=10||w.length<2)return [cut(t,10)];
let a=w[0],i=1;while(i<w.length&&(a+" "+w[i]).length<=10)a+=" "+w[i++];return [cut(a,10),cut(w.slice(i).join(" "),10)]}
function mapSvg(){
const tp=topics().slice(0,MAX_DRAWN),model=(V.model||[]).slice(0,MAX_MODEL),rel=(V.related||[]).slice(0,Math.max(0,MAX_DRAWN-tp.length)).slice(0,4);
const L=layout(model.length,tp.length),pos=new Map();tp.forEach((m,i)=>pos.set(m.n,L.topic[i]));
const selN=view.k==="topic"?view.n:view.k==="notes"?0:-1,many=tp.length>6;
const spokes=tp.map(m=>{const p=pos.get(m.n);return `<path class="fo-spoke${m.n===selN?" on":""}" data-n="${m.n}" d="M${CORE.x} ${CORE.y} L${p.x} ${p.y}"/>`}).join("");
const links=(V.links||[]).filter(([a,b])=>pos.has(a)&&pos.has(b)).map(([a,b])=>{const p=pos.get(a),q=pos.get(b),mx=Math.round((p.x+q.x)/2+(p.y-q.y)*.18),my=Math.round((p.y+q.y)/2+(q.x-p.x)*.18);
return `<path class="fo-link" data-a="${a}" data-b="${b}" d="M${p.x} ${p.y} Q${mx} ${my} ${q.x} ${q.y}"/>`}).join("");
const node=m=>{const p=pos.get(m.n),sel=m.n===selN,name=cut(label(m),many?13:22)+(m.project!==V.project?" · "+m.project:""),s=sel?34:30;
const ty=p.side?p.y+4:p.above?p.y-26:p.y+38,tx=p.side?p.x+24:p.x;
return `<g class="fo-node${sel?" sel":""}${m.missing?" gone":""}" data-n="${m.n}" data-kind="topic" role="button" tabindex="0" aria-label="Topic ${topics().indexOf(m)+1}: ${esc(label(m))}"><rect class="fo-shape" x="${p.x-s/2}" y="${p.y-s/2}" width="${s}" height="${s}" rx="7"/><text class="fo-num" x="${p.x}" y="${p.y+4}">${topics().indexOf(m)+1}</text><text class="fo-name${many?" sm":""}" x="${tx}" y="${ty}"${p.side?' style="text-anchor:start"':""}>${esc(name)}</text></g>`};
const RS=[[402,128],[600,352],[420,300],[655,64]],relNode=(r,i)=>{const p={x:RS[i][0],y:RS[i][1]};return `<g class="fo-relg${view.k==="rel"&&view.i===i?" on":""}" data-rel="${i}" role="button" tabindex="0" aria-label="Related topic: ${esc(r.name)}"><circle cx="${p.x}" cy="${p.y}" r="9"/><text x="${p.x+14}" y="${p.y+4}">${esc(cut(r.name,18))} · + Add</text></g>`};
const mi=model.length>6;
const mSpokes=model.map((x,i)=>`<path class="fo-mspoke" data-m="${i}" d="M${CORE.x} ${CORE.y} L${L.model[i].x} ${L.model[i].y}"/>`).join("");
const mLinks=model.flatMap((x,i)=>x.kind==="concept"?[]:(x.topics||[]).filter(n=>pos.has(n)).map(n=>{const p=L.model[i],q=pos.get(n);return `<path class="fo-mlink" data-m="${i}" data-b="${n}" d="M${p.x} ${p.y} Q${CORE.x} ${Math.round((p.y+q.y)/2+(p.y<CORE.y?-40:40))} ${q.x} ${q.y}"/>`})).join("");
const mnode=(x,i)=>{const p=L.model[i],ty=p.side?p.y+4:p.above?p.y-18:p.y+26,tx=p.side?p.x-16:p.x;
return `<g class="fo-mnode${view.k==="model"&&view.i===i?" sel":""}" data-m="${i}" data-mk="${esc(x.kind)}" role="button" tabindex="0" aria-label="${esc(x.kind)}: ${esc(x.label)}"><circle class="fo-mshape" cx="${p.x}" cy="${p.y}" r="10"/><text class="fo-name${mi?" sm":""}" x="${tx}" y="${ty}"${p.side?' style="text-anchor:end"':""}>${esc(cut(x.label,mi?13:22))}</text></g>`};
const capY=model.length>6||tp.length>6?16:34,cur=selN===0?CORE:pos.get(selN);
const ring=cur?`<circle id="fo-ring" cx="${cur.x}" cy="${cur.y}" r="${selN===0?46:27}"/>`:"";
const lines=coreLines(V.title);
return `<svg id="fo-map" viewBox="0 0 720 400" role="group" aria-label="The Focus and its members">
<ellipse class="fo-lobe" cx="${LOBES.model.x}" cy="${LOBES.model.y}" rx="${LOBE_R.rx}" ry="${LOBE_R.ry}"/><ellipse class="fo-lobe" cx="${LOBES.topic.x}" cy="${LOBES.topic.y}" rx="${LOBE_R.rx}" ry="${LOBE_R.ry}"/>
<text class="fo-cap" x="${LOBES.model.x}" y="${capY}">MODEL</text><text class="fo-cap" x="${LOBES.topic.x}" y="${capY}">TOPICS</text>
${mSpokes}${spokes}${links}${mLinks}
<g class="fo-core${selN===0?" sel":""}" data-n="0" role="button" tabindex="0" aria-label="Focus notes"><circle cx="${CORE.x}" cy="${CORE.y}" r="38"/>${lines.map((l,i)=>`<text x="${CORE.x}" y="${CORE.y+4+(i-(lines.length-1)/2)*16}">${esc(l)}</text>`).join("")}</g>
${model.map(mnode).join("")}${tp.map(node).join("")}<g class="fo-relgs">${rel.map(relNode).join("")}</g>
${ring}</svg>`}
// ---- the caption line under the map: what you clicked or typed (folded, the map is only this line, sticky) ----
const capHome=()=>`<b>${esc(V.title)}</b>`+(V.model||[]).slice(0,6).map((x,i)=>` · <a href="#" class="th-bn th-brel" data-m="${i}">${esc(cut(x.label,30))}</a>`).join("");
const capModel=i=>{const x=V.model[i],ts=(x.topics||[]).map(n=>V.members.find(m=>m.n===n)).filter(Boolean);return `<a href="#" class="th-bn" data-m="${i}"><b>${esc(x.label)}</b></a> · ${x.kind==="concept"?"class":esc(x.kind)}`+(ts.length?" · in "+ts.map(m=>`<a href="#" class="th-bn th-brel" data-n="${m.n}">${esc(label(m))}</a>`).join(" · "):"")};
const capTopic=n=>{const m=V.members.find(x=>x.n===n),ks=(V.model||[]).map((x,i)=>[x,i]).filter(([x])=>(x.topics||[]).includes(n));return `<a href="#" class="th-bn" data-n="${n}"><b>${esc(label(m))}</b></a>`+(ks.length?" · names "+ks.map(([x,i])=>`<a href="#" class="th-bn th-brel" data-m="${i}">${esc(cut(x.label,30))}</a>`).join(" · "):"")};
const cap=h=>{const c=$("fo-bcap");if(c)c.innerHTML=h};
function follow(t){if(!V)return;t=String(t).slice(-160);let best=null,at=-1;
(V.model||[]).forEach((x,i)=>{const p=t.toLowerCase().lastIndexOf(x.label.toLowerCase());if(p>at){at=p;best=()=>cap(capModel(i))}});
topics().forEach(m=>{const p=t.lastIndexOf(label(m));if(p>at){at=p;best=()=>cap(capTopic(m.n))}});if(best)best()}
// ---- the panel under the map: the notes, a topic, a model node, a related topic ----
const TOOLS=[["b","B","Bold"],["i","I","Italic"],["h","H","Heading"],["ul","•","Bulleted list"],["ol","1.","Numbered list"],["link","[[ ]]","Link a topic"],["tag","#","Tag"]];
const tools=f=>`<div class="fo-tools" role="toolbar" aria-label="Format" data-for="${f}">${TOOLS.map(([k,t,l])=>`<button type="button" class="ibtn fo-tool" data-md="${k}" title="${l}" aria-label="${l}">${esc(t)}</button>`).join("")}</div>`;
const walkN=()=>[0,...topics().map(m=>m.n)];
function pnav(){const W=walkN();if(W.length<2)return "";const cur=view.k==="topic"?view.n:0,i=W.indexOf(cur),prev=W[(i-1+W.length)%W.length],next=W[(i+1)%W.length];
return `<div class="fo-pnav"><a class="btn sm" href="#" data-n="${prev}" role="button" aria-label="Previous">‹</a><span>${cur?`${topics().findIndex(m=>m.n===cur)+1} of ${W.length-1}`:"Focus notes"}</span><a class="btn sm" href="#" data-n="${next}" role="button" aria-label="Next">›</a></div>`}
function nav(){const W=walkN();if(W.length<2)return "";const cur=view.k==="topic"?view.n:0,i=Math.max(0,W.indexOf(cur)),prev=W[(i-1+W.length)%W.length],next=W[(i+1)%W.length];
return `<div class="fo-nav"><a class="btn sm" id="fo-prev" href="#" data-n="${prev}" role="button" aria-label="Previous">‹ Previous</a><span id="fo-count">${cur?`${topics().findIndex(m=>m.n===cur)+1} of ${W.length-1}`:"Focus notes"}</span><a class="btn sm" id="fo-next" href="#" data-n="${next}" role="button" aria-label="Next">Next ›</a></div>`}
function panel(){
if(view.k==="topic"){const m=V.members.find(x=>x.n===view.n);if(m)return `<section class="fo-mopen fo-tp" data-mk="topic"><div class="fo-edhead"><span class="fo-kind">TOPIC · ${esc(label(m))}</span>${m.project!==V.project?`<span class="small">on ${esc(m.project)}</span>`:""}</div>${m.missing?`<p class="fo-gonemsg">This topic isn't there any more, or isn't yours to read.</p>`:`<div class="fo-mtext">${esc(cut(m.text||"",2400))}</div>`}<div class="fo-acts"><a class="btn sm" href="#" data-go="/topics/${encodeURIComponent(m.name)}${m.section?"#"+encodeURIComponent(m.section):""}">Open full</a> <button type="button" class="btn sm fo-tpback">Back to the notes</button>${pnav()}</div></section>`}
if(view.k==="model"){const x=(V.model||[])[view.i];if(x)return `<section class="fo-mopen" data-mk="${esc(x.kind)}"><div class="fo-edhead"><span class="fo-kind">${esc(String(x.title||x.label).toUpperCase())}</span><span class="small">named ${x.count===1?"once":(x.count||0)+" times"}</span></div><div class="fo-mtext">${esc(cut(x.text||x.label,2400))}</div><div class="fo-acts">${x.href?`<a class="btn sm" href="#" data-go="${esc(x.href)}">Open full</a> `:""}<button type="button" class="btn sm fo-tpback">Back to the notes</button></div></section>`}
if(view.k==="rel"){const r=(V.related||[])[view.i];if(r)return `<section class="fo-mopen fo-rp"><div class="fo-edhead"><span class="fo-kind">RELATED TOPIC · ${esc(r.name)}</span><span class="small">not in this focus yet</span></div><div class="fo-mtext" id="fo-rptext">…</div><div class="fo-acts">${V.sent?"":`<button type="button" class="btn sm primary fo-rpadd" data-name="${esc(r.name)}">+ Add to focus</button> `}<a class="btn sm" href="#" data-go="/topics/${encodeURIComponent(r.name)}">Open full</a> <button type="button" class="btn sm fo-tpback">Back to the notes</button></div></section>`}
view={k:"notes"};const ro=V.sent;
return `<section id="fo-ed" data-kind="focus"><div class="fo-edhead"><span class="fo-kind">FOCUS · your notes</span><span class="fo-saved" id="fo-saved" aria-live="polite"></span>${ro?"":`<span class="fo-bar"><span class="fo-addw"><button type="button" class="btn sm" id="fo-add" aria-haspopup="true" aria-expanded="false">Add</button><div id="fo-addmenu" role="menu" hidden><a href="#" role="menuitem" data-act="new">New thought here</a><a href="#" role="menuitem" data-go="/search?focus=${encodeURIComponent(KEY)}">Search to add</a></div></span></span>`}</div>
${ro?"":`<div id="fo-newbox" hidden><div class="fo-kind">NEW THOUGHT · in this Focus</div>${tools("fo-newtext")}<div id="fo-newtext" data-ph="A thought on this subject…" aria-label="A new thought in this Focus"></div><div class="fo-acts"><button type="button" class="btn sm primary" id="fo-newsave">Save</button><button type="button" class="btn sm" id="fo-newno">Cancel</button></div></div>`}
${ro?"":tools("fo-text")}<div id="fo-text" data-ph="What this Focus is about" aria-label="Focus notes"></div><div class="fo-acts">${pnav()}</div></section>`}
// ---- the cards: its thoughts; tick to take out; open one to edit it, Save or Thread now ----
function cards(){const ts=thoughts();if(!ts.length)return "";
return `<section id="fo-cards"><h2>Thoughts <span class="n">${ts.length}</span> <button type="button" class="btn sm" id="fo-outn" hidden>Take 0 out</button></h2><div class="th-list fo-list">${ts.map(m=>{const body=String(m.text||"").split("\n").slice(1).join("\n").trim();
return `<article class="th-card${openCard===m.n?" fo-open":""}" data-n="${m.n}"><h3 class="th-title">${V.sent?"":`<label class="th-tick" title="Pick to take out of the focus"><input type="checkbox" class="fo-pick" data-n="${m.n}" aria-label="Pick to take out of the focus"></label>`}${esc(cut(firstLine(m.text)||label(m),140))}</h3>${body?`<div class="fo-cbody">${esc(cut(body,600))}</div>`:""}
<div class="th-meta">${m.project!==V.project?`<span class="th-chip th-proj">${esc(m.project)}</span>`:""}${(m.also||[]).map(a=>`<span class="small">also in ${esc(a.title)}</span>`).join(" ")}</div>
${openCard===m.n?`<div class="fo-cedit" data-n="${m.n}" aria-label="Thought"></div><div class="fo-cacts"><button type="button" class="btn sm primary fo-csave">Save</button><button type="button" class="btn sm fo-csend">Thread now</button><button type="button" class="btn sm fo-cclose">Close</button></div>`:""}</article>`}).join("")}</div></section>`}
function head(){const n=V.members.length,one=thoughts().length===1&&!topics().length;
return `<div class="fo-head"><h1>${esc(V.title)}</h1><span class="fo-mark">FOCUS · ${n}</span><span class="th-info"><button type="button" class="th-ib" id="fo-ib" aria-label="More info" aria-expanded="false">?</button><span class="th-ip" id="fo-ip" role="note" hidden>A Focus gathers the thoughts and topics on one subject. The map shows its topics on the right and the model they name on the left; tap one to read it below. Tick thoughts to take them out, then send the Focus to your designer as one task or split by feature.<span class="th-ih">More in <a href="#" data-go="/topics/Help:Thoughts#focus">Help: Thoughts</a></span></span></span><span class="fo-sp"></span>${V.sent?`<span class="fo-sentb">✓ Sent</span>`:`<button type="button" class="btn sm primary" id="fo-send"${n?"":" disabled"}>Send focus</button>`}<button type="button" class="btn sm" id="fo-dissolve" title="Every thought goes back to Thoughts; your notes go into the first one">${one?"Back to a thought":"Dissolve focus"}</button></div>`}
function render(){
const keep=new Set([...document.querySelectorAll(".fo-pick:checked")].map(i=>i.dataset.n)); // ticks survive a redraw
const main=$("fo-main"),folded=(()=>{try{return !!localStorage.getItem("fo-bfold")}catch(_){return false}})();
let first=false;try{first=!localStorage.getItem("fo-seen");if(first)localStorage.setItem("fo-seen","1")}catch(_){}
const small=V.members.length<1;
main.innerHTML=`<p class="small fo-back"><a href="#" data-go="/thoughts">‹ Thoughts</a></p>${head()}
${first?`<div class="fo-first" id="fo-first">New here: tap a node on the map to read it below, ⌄ folds the map to one line, and ? by the title says more. <button type="button" class="btn sm" id="fo-firstok">Got it</button></div>`:""}
${small?`<div id="fo-empty"><p>Nothing here yet. Add thoughts and topics on this subject, then work them down.</p></div>`:`<div class="fo-brain${folded?" fo-bfolded":""}" id="fo-brain"><button type="button" class="ibtn th-bfold fo-bfold" id="fo-bfold" aria-expanded="${!folded}" title="${folded?"Open the map":"Fold the map"}">${folded?"⌃":"⌄"}</button>${mapSvg()}<p class="th-bcap fo-bcap" id="fo-bcap" aria-live="polite"></p></div>
${(V.related||[]).length?`<p class="small" style="text-align:right;margin:4px 0 8px">Related topics in grey; Add makes them members</p>`:""}`}
<div id="fo-panel">${(()=>{const p=panel();return nav()+p})()}</div><p class="small" id="fo-msg"></p><div id="fo-below">${cards()}</div>`;
const tx=$("fo-text");if(tx){window.thTX(tx);tx.value=V.notes||"";if(V.sent)tx.contentEditable="false";tx.addEventListener("input",()=>{dirty=true;$("fo-saved").textContent="…";clearTimeout(timer);timer=setTimeout(saveNotes,800);follow(tx.value)})}
const nt=$("fo-newtext");if(nt)window.thTX(nt);
document.querySelectorAll(".fo-cedit").forEach(b=>{window.thTX(b);const m=V.members.find(x=>x.n===+b.dataset.n);b.value=m?m.text||"":"";setTimeout(()=>{b.focus();b.selectionStart=b.selectionEnd=b.value.length},0)});
if(view.k==="rel"){const r=V.related[view.i];api("GET","/api/v2/topics/"+encodeURIComponent(r.name)+"?format=json").then(j=>{const t=$("fo-rptext");if(t)t.textContent=cut(j.text||j.body||"",2400)}).catch(()=>{const t=$("fo-rptext");if(t)t.textContent="(can't read it from here: Open full)"})}
document.querySelectorAll(".fo-pick").forEach(i=>{if(keep.has(i.dataset.n))i.checked=true});
if(view.k==="model")cap(capModel(view.i));else if(view.k==="topic")cap(capTopic(view.n));else cap(capHome());
upd()}
// the classes (THO-89: from its own words, the notes and its thoughts; razie 2026-10-10 "don't see classes on left"): the
// read's model has none unless a member thought names one, so the page matches the brain's classes here too
let BRAIN=null;const brain=()=>BRAIN||(BRAIN=api("GET","/api/v2/brain").then(d=>d&&d.C||[]).catch(()=>[]));
const wordsOf=c=>[c.name,c.name+"s",c.name.replace(/([a-z])([A-Z])/g,"$1 $2"),...(c.also||[])].map(w=>String(w).toLowerCase());
async function addClasses(j){const C=await brain();if(!C.length)return;const have=new Set((j.model||[]).map(x=>String(x.label).toLowerCase()));
const ts=(j.members||[]).filter(m=>m.kind==="thought"&&!m.missing),tp=(j.members||[]).filter(m=>m.kind==="topic"&&!m.missing);
const hit=(t,c)=>{const low=" "+String(t||"").toLowerCase().replace(/[^a-z0-9 ]+/g," ").replace(/\s+/g," ")+" ";return wordsOf(c).some(w=>low.includes(" "+w+" "))};
const add=[];for(const c of C){if(have.has(c.name.toLowerCase()))continue;const inNotes=hit(j.notes,c),by=ts.filter(m=>hit(m.text,c));if(!inNotes&&!by.length)continue;
add.push({id:"c:"+c.name,kind:"concept",label:c.name,count:(inNotes?1:0)+by.length,thoughts:by.map(m=>m.project+"/"+m.name),topics:tp.filter(m=>hit(m.text,c)).map(m=>m.n),title:"Class · "+c.name,
text:"class "+c.name+" (\n"+(c.fields||[]).map(f=>" "+f.name+": "+f.type+(f.many?"*":"")).join(",\n")+"\n)"+(c.group?" @group(\""+c.group+"\")":"")+(c.tip?"\n\n"+c.tip:""),href:"/search?q="+encodeURIComponent(c.name)})}
add.sort((a,b)=>b.count-a.count);j.model=[...add,...(j.model||[])]}
async function load(keep){try{const j=await api("GET",fpath());await addClasses(j);V=j;document.title="Focus - "+(j.title||"");if(!keep||!dirty)render()}
catch(e){$("fo-main").innerHTML=`<p class="small fo-back"><a href="#" data-go="/thoughts">‹ Thoughts</a></p><h1>Focus</h1><p class="bad">Can't open this Focus: ${esc(why(e))}</p>`}}
// ---- saving ----
async function saveNotes(){clearTimeout(timer);if(!dirty||!V)return;dirty=false;const tx=$("fo-text");if(!tx)return;const notes=tx.value.trim();
try{const cur=await api("GET","/api/v2/thoughts/"+enc(NAME)+pq(PROJ));const L=String(cur.text||"").replace(/\r\n/g,"\n").split("\n");
let h=-1;for(let i=0;i<L.length;i++)if(/^##\s+In this focus\s*$/i.test(L[i]))h=i;const above=h<0?L:L.slice(0,h),f=above.findIndex(l=>l.trim());
const text=(f<0?"":above.slice(0,f+1).join("\n"))+"\n\n"+(notes?notes+"\n\n":"")+(h<0?"":L.slice(h).join("\n"));
const j=await api("PUT","/api/v2/thoughts/"+enc(NAME)+pq(PROJ),{text:text.replace(/^\n+/,""),ver:cur.ver});V.notes=notes;V.ver=j.ver;const s=$("fo-saved");if(s)s.textContent="Saved"}
catch(e){dirty=true;const s=$("fo-saved");if(s)s.textContent="";say("Not saved: "+why(e))}}
const memberOf=n=>V.members.find(x=>x.n===+n);
async function saveCard(n,send){const m=memberOf(n),b=document.querySelector(`.fo-cedit[data-n="${n}"]`);if(!m||!b)return;
try{if(send){await api("POST",tpath(m,"send"),{text:b.value,ver:m.ver,focus:KEY});say("Threaded: your designer has it, the answer comes back in the thought.",true)}
else{const j=await api("PUT",tpath(m),{text:b.value,ver:m.ver});m.ver=j.ver;m.text=b.value;say("Saved.",true)}openCard=null;await load()}catch(e){say((send?"Not sent: ":"Not saved: ")+why(e))}}
async function takeOut(ms){for(const m of ms){await api("POST",fpath("remove"),{member:{project:m.project,name:m.name}});await api("POST",tpath(m,"streak"),{on:true}).catch(()=>{})}}
// ---- the ask popup (Send's switch, Dissolve) ----
let onGo=null;const ask=(text,lbl,danger,withSw,fn)=>{const d=$("fo-ask");$("fo-askt").textContent=text;$("fo-askgo").textContent=lbl;$("fo-askgo").classList.toggle("fo-danger",!!danger);$("fo-asksw").hidden=!withSw;onGo=fn;if(d.showModal)d.showModal();else if(confirm(text))fn()};
$("fo-ask").addEventListener("click",e=>{const s=e.target.closest("#fo-asksw span");if(s)$("fo-asksw").querySelectorAll("span").forEach(x=>x.classList.toggle("on",x===s));
if(e.target.closest("#fo-askno")){e.preventDefault();$("fo-ask").close()}if(e.target.closest("#fo-askgo")){e.preventDefault();$("fo-ask").close();if(onGo)onGo()}});
const picked=()=>[...document.querySelectorAll(".fo-pick:checked")].map(i=>memberOf(i.dataset.n)).filter(Boolean);
function upd(){const p=picked(),o=$("fo-outn");document.querySelectorAll("#fo-cards .th-card").forEach(a=>{const i=a.querySelector(".fo-pick");a.classList.toggle("fo-picked",!!(i&&i.checked))});if(o){o.hidden=!p.length;o.textContent="Take "+p.length+" out"}}
async function show(v){if(dirty)await saveNotes();view=v;render();const p=$("fo-panel");if(p)p.scrollIntoView({block:"nearest",behavior:"smooth"})}
function tool(k,box){if(!box)return;const s=box.selectionStart,e=box.selectionEnd,v=box.value,sel=v.slice(s,e);box.focus();box.setSelectionRange(s,e);
const ins={b:`**${sel||"bold"}**`,i:`*${sel||"italic"}*`,h:`\n## ${sel||"Heading"}\n`,ul:`\n- ${sel}`,ol:`\n1. ${sel}`,link:`[[${sel||"Topic"}]]`,tag:`#${sel||"tag"}`}[k];document.execCommand("insertText",false,ins)}
document.addEventListener("click",async e=>{const t=e.target;if(!t.closest||t.closest("#fo-ask"))return;
const g=t.closest("[data-go]");if(g){e.preventDefault();if(dirty)await saveNotes();go(g.dataset.go);return}
if(t.closest("#fo-ib")){e.stopPropagation();const ip=$("fo-ip");ip.hidden=!ip.hidden;$("fo-ib").setAttribute("aria-expanded",String(!ip.hidden));return}
if(!t.closest(".th-info")){const ip=$("fo-ip");if(ip&&!ip.hidden){ip.hidden=true;$("fo-ib").setAttribute("aria-expanded","false")}}
if(t.closest("#fo-firstok")){$("fo-first").remove();return}
if(t.closest("#fo-bfold")){e.preventDefault();const b=$("fo-brain"),f=!b.classList.contains("fo-bfolded");b.classList.toggle("fo-bfolded",f);const bt=$("fo-bfold");bt.textContent=f?"⌃":"⌄";bt.title=f?"Open the map":"Fold the map";bt.setAttribute("aria-expanded",String(!f));try{localStorage.setItem("fo-bfold",f?"1":"")}catch(_){}return}
const tl=t.closest(".fo-tool");if(tl){e.preventDefault();tool(tl.dataset.md,$(tl.parentNode.dataset.for));return}
if(t.closest("#fo-add")){const m=$("fo-addmenu");m.hidden=!m.hidden;$("fo-add").setAttribute("aria-expanded",String(!m.hidden));return}
if(t.closest('[data-act="new"]')){e.preventDefault();$("fo-addmenu").hidden=true;$("fo-newbox").hidden=false;$("fo-newtext").focus();return}
if(t.closest("#fo-newno")){$("fo-newbox").hidden=true;$("fo-newtext").value="";return}
if(t.closest("#fo-newsave")){const v=$("fo-newtext").value.trim();if(!v)return;try{const c=await api("POST","/api/v2/thoughts",{text:v,project:PROJ});await api("POST",fpath("add"),{members:[{project:c.project||PROJ,name:c.name}]});say("Saved in this Focus.",true);await load()}catch(er){say("Not saved: "+why(er))}return}
if(t.closest(".fo-tpback")){show({k:"notes"});return}
const ra=t.closest(".fo-rpadd");if(ra){try{await api("POST",fpath("add"),{members:[{project:PROJ,name:ra.dataset.name}]});say("Added to this Focus.",true);view={k:"notes"};await load()}catch(er){say("Not added: "+why(er))}return}
const rg=t.closest(".fo-relg");if(rg){show({k:"rel",i:+rg.dataset.rel});return}
const mn=t.closest("[data-m]");if(mn&&!t.closest("#fo-cards")){e.preventDefault();show({k:"model",i:+mn.dataset.m});return}
const nn=t.closest("[data-n]");if(nn&&!t.closest("#fo-cards")){e.preventDefault();const n=+nn.dataset.n;show(n?{k:"topic",n}:{k:"notes"});return}
if(t.closest(".fo-pick")||t.closest(".th-tick")){setTimeout(upd,0);return}
if(t.closest("#fo-outn")){const p=picked();if(!p.length)return;try{await takeOut(p);say((p.length===1?"1 thought":p.length+" thoughts")+" taken out: back in Thoughts and the streak.",true);await load()}catch(er){say("Not taken out: "+why(er))}return}
const cd=t.closest("#fo-cards .th-card");if(cd){const n=+cd.dataset.n;
if(t.closest(".fo-csave")){saveCard(n,false);return}if(t.closest(".fo-csend")){saveCard(n,true);return}if(t.closest(".fo-cclose")){openCard=null;render();return}
if(openCard!==n&&!t.closest(".fo-cedit")){openCard=n;render()}return}
if(t.closest("#fo-send")){const nt=thoughts().length,np=topics().length;ask(`Send this Focus to your designer: ${nt} thought${nt===1?"":"s"}, ${np} topic link${np===1?"":"s"}${(V.notes||"").trim()?" and your notes":""}?`,"Send",false,true,async()=>{if(dirty)await saveNotes();const mode=$("fo-asksw").querySelector(".on").dataset.v;
try{const r=await api("POST",fpath("send"),{mode});say("Sent as "+(mode==="one"?"one task":"tasks split by feature")+(r&&r.tasks&&r.tasks.length?" ("+r.tasks.length+")":"")+".",true);await load()}catch(er){say("Not sent: "+why(er))}});return}
if(t.closest("#fo-dissolve")){ask("Dissolve this Focus? Its thoughts go back to Thoughts and the streak, and your notes go into the first one. The topics are not changed.","Dissolve",true,false,async()=>{if(dirty)await saveNotes();
try{const ts=thoughts(),notes=(V.notes||"").trim();if(notes&&ts[0]){const c=await api("GET",tpath(ts[0]));await api("PUT",tpath(ts[0]),{text:String(c.text||"").replace(/\s+$/,"")+"\n\n"+notes,ver:c.ver})}
await takeOut(ts);await api("POST","/api/v2/thoughts/"+enc(NAME)+"/archive"+pq(PROJ),{on:true});go("/thoughts")}catch(er){say("Not dissolved: "+why(er))}});return}
},false);
document.addEventListener("keydown",e=>{if((e.key==="Enter"||e.key===" ")&&e.target.closest&&e.target.closest('#fo-map [role="button"]')){e.preventDefault();e.target.dispatchEvent(new MouseEvent("click",{bubbles:true}))}});
// ---- kept current: the thoughts stamp, as Thoughts polls it; never while you type or edit a card ----
setInterval(async()=>{if(!V||dirty||openCard!==null||document.hidden||(window.d2typingAt&&performance.now()-window.d2typingAt<3000))return;
try{const s=await api("GET","/api/v2/thoughts/stamp");if(s&&s.stamp&&s.stamp!==stamp){stamp=s.stamp;await load()}}catch(_){}},5000);
if(!PARAMS.name||!PROJ){$("fo-main").innerHTML=`<h1>Focus</h1><p class="bad">Which Focus? This page opens as /thoughts/focus/<handle>-<n>?project=<project>.</p>`}else{api("GET","/api/v2/thoughts/stamp").then(s=>{stamp=s&&s.stamp||""}).catch(()=>{});load()}
</script></body></html>