[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D1S1PSH4V7EPQXQZEHTJC8":3,"skill-same:01M6D1S1PSH4V7EPQXQZEHTJC8":71},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":48,"commitSha":49,"skillDirectory":50,"forksCount":51,"repositoryHtmlUrl":52,"repositoryHomepageUrl":53,"ownerHtmlUrl":54,"ownerBlogUrl":53,"ownerDescription":55,"updatedAt":56,"install":57,"resources":70},"01M6D1S1PSH4V7EPQXQZEHTJC8","canvas-data",null,"Holding and shaping data inside a standalone HTML page: localStorage done safely as a per-viewer convenience, a light\u002Fdark toggle that persists, importing and exporting JSON or CSV with no server, rendering a list or table from an array, and drawing a chart to scale. Read when a canvas page remembers, loads, saves or visualises data.","enowdev\u002Fenowxcli","enowdev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F179420349?v=4","01M6D1MVX2KC0X3Z9NESE7P22K","Enow Dev.","01M6D1MW1FMZGBDKWAJY4GQX81",67,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fcanvas-data\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42],"canvas","data","canvas data","server","csv","shaping","json","table","page","read","list","html","done","drawing","2026-11-10 10:12:30",false,"---\nname: canvas-data\ndescription: \"Holding and shaping data inside a standalone HTML page: localStorage done safely as a per-viewer convenience, a light\u002Fdark toggle that persists, importing and exporting JSON or CSV with no server, rendering a list or table from an array, and drawing a chart to scale. Read when a canvas page remembers, loads, saves or visualises data.\"\n---\n\n# Data in a standalone page, with no server\n\nA page that opens from disk has no database and no backend. Everything it\nremembers lives in the one browser it was opened in; everything it loads, the\nperson hands it as a file; everything it saves, it hands back as a file. This\nskill is those four jobs: persist, toggle, move data in and out, and render\nit.\n\n## 1. localStorage, as a convenience only\n\n`localStorage` is per-origin, per-browser, and for a `file:\u002F\u002F` page the origin\nis fragile. Treat it as a nicety that may be absent, never as the source of\ntruth the page depends on.\n\n```js\nconst store = {\n  get(key, fallback) {\n    try { const v = localStorage.getItem(key); return v == null ? fallback : JSON.parse(v); }\n    catch { return fallback; }\n  },\n  set(key, value) {\n    try { localStorage.setItem(key, JSON.stringify(value)); } catch { \u002F* private window, full quota *\u002F }\n  },\n};\n```\n\n- Every read and write is wrapped in try\u002Fcatch: a private window throws, a\n  full quota throws, a blocked origin returns nothing. The page renders\n  correctly when it comes back empty.\n- Store per-viewer conveniences only: a remembered tab, a theme choice, a\n  draft, the rows the person entered on this machine. Not anything that must\n  be shared, must survive a move to another machine, or must be trusted.\n- Namespace the key so two pages opened from the same folder do not collide:\n  `tipsplitter.rows`, not `rows`.\n\n## 2. A theme toggle that persists\n\nCombine the OS default (the media query in the `canvas` skill) with an\nexplicit choice stored per viewer. Set the attribute before the first paint\nso there is no flash.\n\n```html\n\u003Cscript>\n  \u002F\u002F in \u003Chead>, before the body paints:\n  try {\n    const t = localStorage.getItem(\"app.theme\");\n    if (t === \"dark\" || t === \"light\") document.documentElement.dataset.theme = t;\n  } catch {}\n\u003C\u002Fscript>\n```\n\n```js\nfunction setTheme(t) {                 \u002F\u002F \"dark\" | \"light\"\n  document.documentElement.dataset.theme = t;\n  store.set(\"app.theme\", t);\n}\ntoggle.addEventListener(\"click\", () => {\n  const dark = matchMedia(\"(prefers-color-scheme: dark)\").matches;\n  const current = document.documentElement.dataset.theme || (dark ? \"dark\" : \"light\");\n  setTheme(current === \"dark\" ? \"light\" : \"dark\");\n});\n```\n\nThe toggle itself is an icon button, a sun or a moon and no text beside\nit, named by `aria-label` (and `title`) for what it does, updated on each\nswitch:\n\n```html\n\u003Cbutton id=\"theme\" class=\"icon-button\" type=\"button\" aria-label=\"Switch to dark theme\"\n        title=\"Switch to dark theme\">\n  \u003Csvg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"20\" height=\"20\">\u003C!-- moon -->\u003C\u002Fsvg>\n\u003C\u002Fbutton>\n```\n\nThe CSS has the three blocks from the `canvas` skill: bare `:root` (light),\n`@media (prefers-color-scheme: dark) :root` (OS dark), and\n`:root[data-theme=\"dark\"]` \u002F `:root[data-theme=\"light\"]` so the toggle wins\nboth ways.\n\n## 3. Loading a file the person gives you\n\nNo `fetch`; the person chooses a file and the page reads it with `FileReader`\nor the File API. This works from a `file:\u002F\u002F` page.\n\n```js\nfileInput.addEventListener(\"change\", async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  try {\n    const text = await file.text();\n    const rows = file.name.endsWith(\".csv\") ? parseCsv(text) : JSON.parse(text);\n    set({ rows });\n  } catch (err) {\n    showError(\"That file could not be read as \" + (file.name.endsWith(\".csv\") ? \"CSV\" : \"JSON\") + \".\");\n  }\n});\n```\n\n- Support drag and drop too when it suits: `dragover` (preventDefault) and\n  `drop` (read `e.dataTransfer.files[0]`).\n- Parsing can fail; wrap it and show a message on the page, never a thrown\n  error or a silent nothing.\n- A tiny CSV parser is fine for simple data; for quoted fields and embedded\n  commas, load a small parser from a CDN rather than getting it subtly wrong.\n\n## 4. Saving without a server\n\nThe page builds the file in memory and offers it as a download through a\n`blob:` URL. This works from disk in a browser.\n\n```js\nfunction download(filename, text, type = \"application\u002Fjson\") {\n  const blob = new Blob([text], { type });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url; a.download = filename;\n  document.body.appendChild(a); a.click(); a.remove();\n  URL.revokeObjectURL(url);\n}\nsaveBtn.addEventListener(\"click\", () => {\n  download(\"data.json\", JSON.stringify(state.rows, null, 2));\n});\n```\n\n- Export JSON for round-tripping (the page can load its own export), CSV for\n  a spreadsheet. Offer whichever the person actually needs.\n- Revoke the object URL after the click so it is not held forever.\n- Note: a download started this way works in a normal browser opening the\n  file; some embedded or locked-down viewers block it. For a page that is\n  only ever opened as a local file in a browser, this is the right tool.\n\n## 5. Rendering a list or table from an array\n\nRender from the state array; rebuild the rows on change rather than mutating\nthem in place for a small list. Build nodes, do not concatenate HTML from\nuser data (an imported name with `\u003C` must not become markup).\n\n```js\nfunction renderRows() {\n  tbody.replaceChildren(...state.rows.map((r) => {\n    const tr = document.createElement(\"tr\");\n    const name = document.createElement(\"td\"); name.textContent = r.name;\n    const amt = document.createElement(\"td\");\n    amt.textContent = r.amount.toFixed(2);\n    amt.style.fontVariantNumeric = \"tabular-nums\";   \u002F\u002F digits line up\n    tr.append(name, amt);\n    return tr;\n  }));\n}\n```\n\n- `textContent`, never `innerHTML`, for any value that came from a file or an\n  input: it escapes automatically and closes the injection door.\n- `font-variant-numeric: tabular-nums` on any column of figures so they align.\n- An empty array renders a designed empty state (\"No rows yet. Add one\n  above, or load a file.\"), not a blank table.\n- For a long list (thousands of rows), render the visible window and page or\n  virtualise; a `file:\u002F\u002F` page still has one main thread.\n\n## 6. Drawing a chart to scale\n\nFor a sparkline or one bar row, hand-drawn SVG is enough and stays inside the\nfile; for anything richer, load a charting library (`canvas-interactive`,\nsection 5). Whichever you use, the marks must be true.\n\n- One scale maps data to pixels; every tick and label names a value the chart\n  actually reaches. Compute the domain from the data\n  (`Math.min`\u002F`Math.max`), do not assume 0 to 100.\n- Leave room in the SVG `viewBox` for the outermost labels, so they are not\n  clipped.\n- Colour chart text and lines from the theme tokens, so the chart is legible\n  in both themes; give every drawn shape an explicit fill.\n- Label the axes with the subject's real units (the detail the `canvas` skill\n  asks for), not bare numbers.\n\n```js\nconst xs = state.points, max = Math.max(...xs, 1), W = 300, H = 60;\nconst d = xs.map((v, i) => `${(i \u002F (xs.length - 1)) * W},${H - (v \u002F max) * H}`).join(\" \");\nspark.innerHTML = `\u003Cpolyline fill=\"none\" stroke=\"var(--accent)\" stroke-width=\"2\" points=\"${d}\"\u002F>`;\n```\n\n(`innerHTML` here is safe: the string is built from numbers the page\ncomputed, never from user text.)\n\n## 7. Check it\n\n- Every `localStorage` read and write is wrapped; the page works with it\n  empty.\n- The theme choice persists and is set before first paint (no flash).\n- Files are read with the File API and parsed inside a try\u002Fcatch with a\n  visible error on failure.\n- Saving builds a `blob:` and revokes the URL; export can be re-imported.\n- Rows render with `textContent`; figures use tabular numerals; an empty\n  array shows a designed empty state.\n- Charts are drawn to a scale computed from the data, with labels in real\n  units, legible in both themes.\n","# Data in a standalone page, with no server\n\nA page that opens from disk has no database and no backend. Everything it\nremembers lives in the one browser it was opened in; everything it loads, the\nperson hands it as a file; everything it saves, it hands back as a file. This\nskill is those four jobs: persist, toggle, move data in and out, and render\nit.\n\n## 1. localStorage, as a convenience only\n\n`localStorage` is per-origin, per-browser, and for a `file:\u002F\u002F` page the origin\nis fragile. Treat it as a nicety that may be absent, never as the source of\ntruth the page depends on.\n\n```js\nconst store = {\n  get(key, fallback) {\n    try { const v = localStorage.getItem(key); return v == null ? fallback : JSON.parse(v); }\n    catch { return fallback; }\n  },\n  set(key, value) {\n    try { localStorage.setItem(key, JSON.stringify(value)); } catch { \u002F* private window, full quota *\u002F }\n  },\n};\n```\n\n- Every read and write is wrapped in try\u002Fcatch: a private window throws, a\n  full quota throws, a blocked origin returns nothing. The page renders\n  correctly when it comes back empty.\n- Store per-viewer conveniences only: a remembered tab, a theme choice, a\n  draft, the rows the person entered on this machine. Not anything that must\n  be shared, must survive a move to another machine, or must be trusted.\n- Namespace the key so two pages opened from the same folder do not collide:\n  `tipsplitter.rows`, not `rows`.\n\n## 2. A theme toggle that persists\n\nCombine the OS default (the media query in the `canvas` skill) with an\nexplicit choice stored per viewer. Set the attribute before the first paint\nso there is no flash.\n\n```html\n\u003Cscript>\n  \u002F\u002F in \u003Chead>, before the body paints:\n  try {\n    const t = localStorage.getItem(\"app.theme\");\n    if (t === \"dark\" || t === \"light\") document.documentElement.dataset.theme = t;\n  } catch {}\n\u003C\u002Fscript>\n```\n\n```js\nfunction setTheme(t) {                 \u002F\u002F \"dark\" | \"light\"\n  document.documentElement.dataset.theme = t;\n  store.set(\"app.theme\", t);\n}\ntoggle.addEventListener(\"click\", () => {\n  const dark = matchMedia(\"(prefers-color-scheme: dark)\").matches;\n  const current = document.documentElement.dataset.theme || (dark ? \"dark\" : \"light\");\n  setTheme(current === \"dark\" ? \"light\" : \"dark\");\n});\n```\n\nThe toggle itself is an icon button, a sun or a moon and no text beside\nit, named by `aria-label` (and `title`) for what it does, updated on each\nswitch:\n\n```html\n\u003Cbutton id=\"theme\" class=\"icon-button\" type=\"button\" aria-label=\"Switch to dark theme\"\n        title=\"Switch to dark theme\">\n  \u003Csvg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"20\" height=\"20\">\u003C!-- moon -->\u003C\u002Fsvg>\n\u003C\u002Fbutton>\n```\n\nThe CSS has the three blocks from the `canvas` skill: bare `:root` (light),\n`@media (prefers-color-scheme: dark) :root` (OS dark), and\n`:root[data-theme=\"dark\"]` \u002F `:root[data-theme=\"light\"]` so the toggle wins\nboth ways.\n\n## 3. Loading a file the person gives you\n\nNo `fetch`; the person chooses a file and the page reads it with `FileReader`\nor the File API. This works from a `file:\u002F\u002F` page.\n\n```js\nfileInput.addEventListener(\"change\", async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  try {\n    const text = await file.text();\n    const rows = file.name.endsWith(\".csv\") ? parseCsv(text) : JSON.parse(text);\n    set({ rows });\n  } catch (err) {\n    showError(\"That file could not be read as \" + (file.name.endsWith(\".csv\") ? \"CSV\" : \"JSON\") + \".\");\n  }\n});\n```\n\n- Support drag and drop too when it suits: `dragover` (preventDefault) and\n  `drop` (read `e.dataTransfer.files[0]`).\n- Parsing can fail; wrap it and show a message on the page, never a thrown\n  error or a silent nothing.\n- A tiny CSV parser is fine for simple data; for quoted fields and embedded\n  commas, load a small parser from a CDN rather than getting it subtly wrong.\n\n## 4. Saving without a server\n\nThe page builds the file in memory and offers it as a download through a\n`blob:` URL. This works from disk in a browser.\n\n```js\nfunction download(filename, text, type = \"application\u002Fjson\") {\n  const blob = new Blob([text], { type });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url; a.download = filename;\n  document.body.appendChild(a); a.click(); a.remove();\n  URL.revokeObjectURL(url);\n}\nsaveBtn.addEventListener(\"click\", () => {\n  download(\"data.json\", JSON.stringify(state.rows, null, 2));\n});\n```\n\n- Export JSON for round-tripping (the page can load its own export), CSV for\n  a spreadsheet. Offer whichever the person actually needs.\n- Revoke the object URL after the click so it is not held forever.\n- Note: a download started this way works in a normal browser opening the\n  file; some embedded or locked-down viewers block it. For a page that is\n  only ever opened as a local file in a browser, this is the right tool.\n\n## 5. Rendering a list or table from an array\n\nRender from the state array; rebuild the rows on change rather than mutating\nthem in place for a small list. Build nodes, do not concatenate HTML from\nuser data (an imported name with `\u003C` must not become markup).\n\n```js\nfunction renderRows() {\n  tbody.replaceChildren(...state.rows.map((r) => {\n    const tr = document.createElement(\"tr\");\n    const name = document.createElement(\"td\"); name.textContent = r.name;\n    const amt = document.createElement(\"td\");\n    amt.textContent = r.amount.toFixed(2);\n    amt.style.fontVariantNumeric = \"tabular-nums\";   \u002F\u002F digits line up\n    tr.append(name, amt);\n    return tr;\n  }));\n}\n```\n\n- `textContent`, never `innerHTML`, for any value that came from a file or an\n  input: it escapes automatically and closes the injection door.\n- `font-variant-numeric: tabular-nums` on any column of figures so they align.\n- An empty array renders a designed empty state (\"No rows yet. Add one\n  above, or load a file.\"), not a blank table.\n- For a long list (thousands of rows), render the visible window and page or\n  virtualise; a `file:\u002F\u002F` page still has one main thread.\n\n## 6. Drawing a chart to scale\n\nFor a sparkline or one bar row, hand-drawn SVG is enough and stays inside the\nfile; for anything richer, load a charting library (`canvas-interactive`,\nsection 5). Whichever you use, the marks must be true.\n\n- One scale maps data to pixels; every tick and label names a value the chart\n  actually reaches. Compute the domain from the data\n  (`Math.min`\u002F`Math.max`), do not assume 0 to 100.\n- Leave room in the SVG `viewBox` for the outermost labels, so they are not\n  clipped.\n- Colour chart text and lines from the theme tokens, so the chart is legible\n  in both themes; give every drawn shape an explicit fill.\n- Label the axes with the subject's real units (the detail the `canvas` skill\n  asks for), not bare numbers.\n\n```js\nconst xs = state.points, max = Math.max(...xs, 1), W = 300, H = 60;\nconst d = xs.map((v, i) => `${(i \u002F (xs.length - 1)) * W},${H - (v \u002F max) * H}`).join(\" \");\nspark.innerHTML = `\u003Cpolyline fill=\"none\" stroke=\"var(--accent)\" stroke-width=\"2\" points=\"${d}\"\u002F>`;\n```\n\n(`innerHTML` here is safe: the string is built from numbers the page\ncomputed, never from user text.)\n\n## 7. Check it\n\n- Every `localStorage` read and write is wrapped; the page works with it\n  empty.\n- The theme choice persists and is set before first paint (no flash).\n- Files are read with the File API and parsed inside a try\u002Fcatch with a\n  visible error on failure.\n- Saving builds a `blob:` and revokes the URL; export can be re-imported.\n- Rows render with `textContent`; figures use tabular numerals; an empty\n  array shows a designed empty state.\n- Charts are drawn to a scale computed from the data, with labels in real\n  units, legible in both themes.\n",{"name":5,"description":7},"main","38293fe1b096e6bf3646c7344c38786ed688f464","crates\u002Fenowx-core\u002Fskills\u002Fcanvas-data",21,"https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli","https:\u002F\u002Fenowx.ai","https:\u002F\u002Fgithub.com\u002Fenowdev","Pretending to be a Developer","2026-10-06 02:05:35",{"source":8,"skillName":5,"commands":58,"prompt":68,"zipAvailable":69},[59,62,65],{"tool":60,"command":61},"npx","npx skills add enowdev\u002Fenowxcli --skill canvas-data",{"tool":63,"command":64},"bunx","bunx skills add enowdev\u002Fenowxcli --skill canvas-data",{"tool":66,"command":67},"pnpm","pnpm dlx skills add enowdev\u002Fenowxcli --skill canvas-data","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fcanvas-data\u002FSKILL.md",true,[],[72,86,108,124,148,169],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":81,"publishedAt":43},"01M6D2HQYSQD9N7MTNDT4A7CBM","demo-skill","Project-scope demo skill for tests.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fworkspace\u002F.agents\u002Fskills\u002Fdemo-skill\u002FSKILL.md",[78],{"categoryNo":79,"categoryName":80,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[82,83,84,85,38],"demo skill","demo","tests","project",{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":98,"publishedAt":43},"01M6D2HM0GCPM0TM2HPG9HHR2R","writing","Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fwriting\u002FSKILL.md",[92,95],{"categoryNo":93,"categoryName":94,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":96,"categoryName":97,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000036","内容创作者",[88,99,100,101,102,103,38,37,104,105,106,107],"text","emails","empty","drop","document","voice","notifications","documentation","translate",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":117,"publishedAt":43},"01M6D2HG07YS49NM196Q0R3AZ8","ui","Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui\u002FSKILL.md",[114],{"categoryNo":115,"categoryName":116,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000032","设计师",[110,118,119,120,121,37,122,38,123],"icons","components","SET","responsive","layout","before building",{"skillNo":125,"name":126,"nameZh":6,"description":127,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":128,"categories":129,"keywords":134,"publishedAt":43},"01M6D2HB59HZHWG1RYZDV4SJ91","ui-themes","Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-themes\u002FSKILL.md",[130,131],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":132,"categoryName":133,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师",[110,135,136,137,138,42,139,140,141,38,123,142,37,143,144,145,118,146,147],"ui themes","switching","photos","tokens","focus","maps","Art","flash","code","Charts","content","screenshots","logo",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":157,"publishedAt":43},"01M6D2H5R5KRAE524C8QX0T7Q0","ui-structure","Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-structure\u002FSKILL.md",[154,155,156],{"categoryNo":93,"categoryName":94,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":132,"categoryName":133,"taxonomyType":26,"taxonomyTypeDesc":27},{"categoryNo":96,"categoryName":97,"taxonomyType":26,"taxonomyTypeDesc":27},[158,110,159,119,160,161,143,162,163,164,122,138,165,166,39,38,123,37,167,168],"structure","ui structure","pricing","dashboard","Blueprint","article","anatomy","styling","primitives","why","how",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":173,"categories":174,"keywords":182,"publishedAt":43},"01M6D2H0ZZBJG77MBGPZHA6MWD","ui-stack-vue","Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",[175,178,181],{"categoryNo":176,"categoryName":177,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":179,"categoryName":180,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":115,"categoryName":116,"taxonomyType":26,"taxonomyTypeDesc":27},[110,183,184,185,186,187,188,189,122,85,190,191,38,192,193,194,32,195,196,197],"vue","stack","ui stack vue","forms","pages","watch","runtime","transitions","fetch","pinia","nuxt","template","config","directory","testing"]