{
  "name": "Focus Desk",
  "slug": "focus-desk",
  "version": "1.0.0",
  "description": "A calm focus timer and personal scratchpad beside your Devdevian conversations. No network access.",
  "manifest": {
    "schema": 2,
    "kind": "extension",
    "permissions": [
      "ui.panel",
      "storage"
    ],
    "entry": "main.js",
    "files": {
      "main.js": "(()=>{(async()=>{await devdevian.panel.configure({placement:\"right\",title:\"Focus Desk\",width:330,height:560});let s=document.getElementById(\"root\");s.innerHTML=`<style>\n    *{box-sizing:border-box}body{margin:0;background:#161225;color:#f2edff;font:15px/1.55 system-ui,sans-serif}\n    body[data-mode=light]{background:#fcf8ff;color:#241c39}main{padding:22px}header{display:flex;justify-content:space-between;align-items:center}small{opacity:.65}h1{font-size:20px;margin:0}h2{font-size:14px;margin:24px 0 8px}\n    .timer{padding:24px 0;text-align:center}output{display:block;font:500 54px/1.3 ui-monospace,monospace;color:#b99bff;font-variant-numeric:tabular-nums}body[data-mode=light] output{color:#7251ae}\n    button,textarea{font:inherit;color:inherit;border:1px solid #9c80c64d;background:transparent;border-radius:12px}button{padding:8px 13px;cursor:pointer}button:hover{background:#9c80c622}button:focus-visible,textarea:focus-visible{outline:2px solid #aa8ce3;outline-offset:3px}.row{display:flex;gap:8px;justify-content:center;margin-top:12px}.primary{background:#7355ab;color:white}textarea{width:100%;height:160px;resize:vertical;padding:12px}p{font-size:12px;opacity:.7}.status{min-height:20px}button:disabled{opacity:.5}\n    </style><main><header><h1>Focus Desk</h1><small>Just your space.</small></header><div class=\"timer\"><small id=\"mode\">Focus session</small><output id=\"time\" aria-label=\"Time remaining\">25:00</output><div class=\"row\"><button id=\"toggle\" class=\"primary\">Start</button><button id=\"reset\">Reset</button><button id=\"switch\">Take a break</button></div><p id=\"timer-status\" role=\"status\"></p></div><h2><label for=\"note\">A note for your next step</label></h2><textarea id=\"note\" maxlength=\"3000\" placeholder=\"What will you build next?\"></textarea><p id=\"save-status\" class=\"status\" role=\"status\"></p><button id=\"clear\">Clear note</button><p>Saved on this device. No network access.<br>Keep passwords and secrets elsewhere.</p></main>`;let e=n=>document.getElementById(n),l=n=>{document.body.dataset.mode=n};l((await devdevian.context()).mode),devdevian.onTheme(l);let t={break:!1,remaining:1500,deadline:null};try{let n=await devdevian.storage.get(\"timer\");n&&typeof n.break==\"boolean\"&&Number.isFinite(n.remaining)&&n.remaining>=0&&n.remaining<=1500&&(n.deadline===null||Number.isFinite(n.deadline))&&(t=n);let o=await devdevian.storage.get(\"note\");typeof o==\"string\"&&(e(\"note\").value=o.slice(0,3e3))}catch{e(\"save-status\").textContent=\"Could not load local storage.\"}let d=()=>t.deadline===null?t.remaining:Math.max(0,Math.ceil((t.deadline-Date.now())/1e3)),i=()=>devdevian.storage.set(\"timer\",t).catch(()=>{e(\"timer-status\").textContent=\"Timer state could not be saved.\"}),a=()=>{let n=d();e(\"time\").textContent=`${String(Math.floor(n/60)).padStart(2,\"0\")}:${String(n%60).padStart(2,\"0\")}`,e(\"toggle\").textContent=t.deadline===null?\"Start\":\"Pause\",e(\"mode\").textContent=t.break?\"A little breathing room\":\"Focus session\",e(\"switch\").textContent=t.break?\"Back to focus\":\"Take a break\",!n&&t.deadline!==null&&(t.deadline=null,t.remaining=0,e(\"timer-status\").textContent=\"Session complete. Take a breath.\",i(),e(\"toggle\").textContent=\"Start\")};e(\"toggle\").onclick=()=>{t.remaining=d(),t.deadline=t.deadline===null?Date.now()+(t.remaining||(t.break?300:1500))*1e3:null,e(\"timer-status\").textContent=\"\",i(),a()},e(\"reset\").onclick=()=>{t.remaining=t.break?300:1500,t.deadline=null,e(\"timer-status\").textContent=\"\",i(),a()},e(\"switch\").onclick=()=>{t={break:!t.break,remaining:t.break?1500:300,deadline:null},e(\"timer-status\").textContent=\"\",i(),a()};let c=Promise.resolve(),r=0,u=()=>{let n=e(\"note\").value,o=++r;e(\"save-status\").textContent=\"Saving\\u2026\",c=c.catch(()=>{}).then(()=>devdevian.storage.set(\"note\",n)).then(()=>{o===r&&(e(\"save-status\").textContent=\"Saved on this device.\")},()=>{o===r&&(e(\"save-status\").textContent=\"Could not save. Keep a copy before closing.\")})};e(\"note\").oninput=u,e(\"clear\").onclick=()=>{e(\"note\").value=\"\",u()},a(),setInterval(a,500)})().catch(s=>{document.getElementById(\"root\").textContent=\"Focus Desk could not start. Disable and enable the extension to retry.\",console.error(s)});})();\n",
      "sources/main.js": "(async () => {\n  await devdevian.panel.configure({\n    placement: \"right\",\n    title: \"Focus Desk\",\n    width: 330,\n    height: 560,\n  });\n  const root = document.getElementById(\"root\");\n  root.innerHTML = `<style>\n    *{box-sizing:border-box}body{margin:0;background:#161225;color:#f2edff;font:15px/1.55 system-ui,sans-serif}\n    body[data-mode=light]{background:#fcf8ff;color:#241c39}main{padding:22px}header{display:flex;justify-content:space-between;align-items:center}small{opacity:.65}h1{font-size:20px;margin:0}h2{font-size:14px;margin:24px 0 8px}\n    .timer{padding:24px 0;text-align:center}output{display:block;font:500 54px/1.3 ui-monospace,monospace;color:#b99bff;font-variant-numeric:tabular-nums}body[data-mode=light] output{color:#7251ae}\n    button,textarea{font:inherit;color:inherit;border:1px solid #9c80c64d;background:transparent;border-radius:12px}button{padding:8px 13px;cursor:pointer}button:hover{background:#9c80c622}button:focus-visible,textarea:focus-visible{outline:2px solid #aa8ce3;outline-offset:3px}.row{display:flex;gap:8px;justify-content:center;margin-top:12px}.primary{background:#7355ab;color:white}textarea{width:100%;height:160px;resize:vertical;padding:12px}p{font-size:12px;opacity:.7}.status{min-height:20px}button:disabled{opacity:.5}\n    </style><main><header><h1>Focus Desk</h1><small>Just your space.</small></header><div class=\"timer\"><small id=\"mode\">Focus session</small><output id=\"time\" aria-label=\"Time remaining\">25:00</output><div class=\"row\"><button id=\"toggle\" class=\"primary\">Start</button><button id=\"reset\">Reset</button><button id=\"switch\">Take a break</button></div><p id=\"timer-status\" role=\"status\"></p></div><h2><label for=\"note\">A note for your next step</label></h2><textarea id=\"note\" maxlength=\"3000\" placeholder=\"What will you build next?\"></textarea><p id=\"save-status\" class=\"status\" role=\"status\"></p><button id=\"clear\">Clear note</button><p>Saved on this device. No network access.<br>Keep passwords and secrets elsewhere.</p></main>`;\n  const $ = (id) => document.getElementById(id);\n  const applyMode = (mode) => {\n    document.body.dataset.mode = mode;\n  };\n  applyMode((await devdevian.context()).mode);\n  devdevian.onTheme(applyMode);\n  let state = { break: false, remaining: 25 * 60, deadline: null };\n  try {\n    const saved = await devdevian.storage.get(\"timer\");\n    if (\n      saved &&\n      typeof saved.break === \"boolean\" &&\n      Number.isFinite(saved.remaining) &&\n      saved.remaining >= 0 &&\n      saved.remaining <= 1500 &&\n      (saved.deadline === null || Number.isFinite(saved.deadline))\n    )\n      state = saved;\n    const note = await devdevian.storage.get(\"note\");\n    if (typeof note === \"string\") $(\"note\").value = note.slice(0, 3000);\n  } catch {\n    $(\"save-status\").textContent = \"Could not load local storage.\";\n  }\n  const remaining = () =>\n    state.deadline === null\n      ? state.remaining\n      : Math.max(0, Math.ceil((state.deadline - Date.now()) / 1000));\n  const persistTimer = () =>\n    devdevian.storage.set(\"timer\", state).catch(() => {\n      $(\"timer-status\").textContent = \"Timer state could not be saved.\";\n    });\n  const draw = () => {\n    const n = remaining();\n    $(\"time\").textContent =\n      `${String(Math.floor(n / 60)).padStart(2, \"0\")}:${String(n % 60).padStart(2, \"0\")}`;\n    $(\"toggle\").textContent = state.deadline === null ? \"Start\" : \"Pause\";\n    $(\"mode\").textContent = state.break\n      ? \"A little breathing room\"\n      : \"Focus session\";\n    $(\"switch\").textContent = state.break ? \"Back to focus\" : \"Take a break\";\n    if (!n && state.deadline !== null) {\n      state.deadline = null;\n      state.remaining = 0;\n      $(\"timer-status\").textContent = \"Session complete. Take a breath.\";\n      persistTimer();\n      $(\"toggle\").textContent = \"Start\";\n    }\n  };\n  $(\"toggle\").onclick = () => {\n    state.remaining = remaining();\n    state.deadline =\n      state.deadline === null\n        ? Date.now() + (state.remaining || (state.break ? 300 : 1500)) * 1000\n        : null;\n    $(\"timer-status\").textContent = \"\";\n    persistTimer();\n    draw();\n  };\n  $(\"reset\").onclick = () => {\n    state.remaining = state.break ? 300 : 1500;\n    state.deadline = null;\n    $(\"timer-status\").textContent = \"\";\n    persistTimer();\n    draw();\n  };\n  $(\"switch\").onclick = () => {\n    state = {\n      break: !state.break,\n      remaining: state.break ? 1500 : 300,\n      deadline: null,\n    };\n    $(\"timer-status\").textContent = \"\";\n    persistTimer();\n    draw();\n  };\n  // Serialize saves so an older slow write cannot replace a newer note.\n  let saveQueue = Promise.resolve(),\n    revision = 0;\n  const saveNote = () => {\n    const text = $(\"note\").value,\n      current = ++revision;\n    $(\"save-status\").textContent = \"Saving…\";\n    saveQueue = saveQueue\n      .catch(() => {})\n      .then(() => devdevian.storage.set(\"note\", text))\n      .then(\n        () => {\n          if (current === revision)\n            $(\"save-status\").textContent = \"Saved on this device.\";\n        },\n        () => {\n          if (current === revision)\n            $(\"save-status\").textContent =\n              \"Could not save. Keep a copy before closing.\";\n        },\n      );\n  };\n  $(\"note\").oninput = saveNote;\n  $(\"clear\").onclick = () => {\n    $(\"note\").value = \"\";\n    saveNote();\n  };\n  draw();\n  setInterval(draw, 500);\n})().catch((error) => {\n  document.getElementById(\"root\").textContent =\n    \"Focus Desk could not start. Disable and enable the extension to retry.\";\n  console.error(error);\n});\n"
    },
    "styles": {
      "light": "",
      "dark": ""
    }
  },
  "listing": {
    "full_description": "## A little room to focus\n\nKeep a 25-minute focus timer and a personal scratchpad beside the forum. Start, pause or reset the timer; notes save automatically on this device.\n\nSwitch between Focus (25 minutes) and Break (5 minutes). No sound, notification spam, GitHub permissions or network requests.\n\n### Privacy and storage\n\nNotes and timer state use the SDK’s account/extension-scoped device storage. They are not synced between devices and are not a secret vault. Disabling this extension hides the panel; your saved note remains until browser storage is cleared or you clear it here.\n\nThe timer uses a wall-clock deadline, so background tab throttling does not slow it down. Close the panel from Devdevian’s extension controls.\n\nSource: examples/extensions/focus-desk in the Devdevian repository.",
    "tags": [
      "productivity",
      "focus",
      "notes",
      "timer"
    ],
    "links": [
      {
        "label": "Source code",
        "url": "https://github.com/Tenyokj/devdevian/tree/main/examples/extensions/focus-desk"
      }
    ],
    "screenshots": []
  }
}