claudecode-omc 5.6.6 → 5.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.local/skills/h5-to-swiftui/SKILL.md +201 -0
  2. package/.local/skills/h5-to-swiftui/assets/calibration/README.md +176 -0
  3. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/index.html +52 -0
  4. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/style.css +133 -0
  5. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Package.swift +26 -0
  6. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Sources/CalibrationScreen/CalibrationScreen.swift +142 -0
  7. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Package.swift +32 -0
  8. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Sources/CalibrationScreenDivergent/CalibrationScreenDivergent.swift +122 -0
  9. package/.local/skills/h5-to-swiftui/assets/calibration/tokens.json +42 -0
  10. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/index.html +14 -0
  11. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/package.json +20 -0
  12. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/001.json +96 -0
  13. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/index.json +89 -0
  14. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.jsx +22 -0
  15. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.module.css +11 -0
  16. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.jsx +53 -0
  17. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.module.css +139 -0
  18. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.jsx +37 -0
  19. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.module.css +72 -0
  20. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.jsx +30 -0
  21. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.module.css +50 -0
  22. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.jsx +159 -0
  23. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.module.css +21 -0
  24. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/main.jsx +12 -0
  25. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.jsx +182 -0
  26. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.module.css +294 -0
  27. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.jsx +147 -0
  28. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.module.css +161 -0
  29. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/global.css +50 -0
  30. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/tokens.css +103 -0
  31. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/vite.config.js +6 -0
  32. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/data/tasks.js +67 -0
  33. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/index.html +26 -0
  34. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/router.js +73 -0
  35. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/detail.js +164 -0
  36. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/home.js +53 -0
  37. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/list.js +87 -0
  38. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/app.css +342 -0
  39. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/tokens.css +68 -0
  40. package/.local/skills/h5-to-swiftui/references/css-to-swiftui-map.md +205 -0
  41. package/.local/skills/h5-to-swiftui/references/design-token-extraction.md +209 -0
  42. package/.local/skills/h5-to-swiftui/references/high-risk-triage.md +209 -0
  43. package/.local/skills/h5-to-swiftui/references/render-equivalence-calibration.md +193 -0
  44. package/.local/skills/h5-to-swiftui/references/stack-detection.md +160 -0
  45. package/.local/skills/h5-to-swiftui/references/visual-diff-loop-protocol.md +365 -0
  46. package/.local/skills/h5-to-swiftui/scripts/_calib-consts.mjs +150 -0
  47. package/.local/skills/h5-to-swiftui/scripts/_imglib.mjs +547 -0
  48. package/.local/skills/h5-to-swiftui/scripts/_provenance.mjs +123 -0
  49. package/.local/skills/h5-to-swiftui/scripts/calibrate-render.mjs +625 -0
  50. package/.local/skills/h5-to-swiftui/scripts/capture-reference.mjs +386 -0
  51. package/.local/skills/h5-to-swiftui/scripts/detect-stack.mjs +305 -0
  52. package/.local/skills/h5-to-swiftui/scripts/evaluate-convergence.mjs +1093 -0
  53. package/.local/skills/h5-to-swiftui/scripts/extract-tokens.mjs +600 -0
  54. package/.local/skills/h5-to-swiftui/scripts/mark-overlay.mjs +379 -0
  55. package/.local/skills/h5-to-swiftui/scripts/pixel-diff.mjs +530 -0
  56. package/.local/skills/h5-to-swiftui/scripts/sim-screenshot.sh +544 -0
  57. package/bundled/manifest.json +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,103 @@
1
+ /* Design tokens — harvested by extract-tokens.mjs */
2
+
3
+ :root {
4
+ /* ── Color: Light theme ─────────────────────────────────────── */
5
+ --color-bg-primary: #ffffff;
6
+ --color-bg-secondary: #f5f5f7;
7
+ --color-bg-card: #ffffff;
8
+ --color-bg-tag: #e8f0fe;
9
+
10
+ --color-text-primary: #1d1d1f;
11
+ --color-text-secondary: #6e6e73;
12
+ --color-text-caption: #9a9a9f;
13
+ --color-text-accent: #0071e3;
14
+ --color-text-on-accent: #ffffff;
15
+
16
+ --color-border: #d2d2d7;
17
+ --color-divider: #e5e5ea;
18
+
19
+ --color-accent: #0071e3;
20
+ --color-accent-hover: #0077ed;
21
+ --color-danger: #ff3b30;
22
+ --color-success: #34c759;
23
+ --color-warning: #ff9f0a;
24
+
25
+ /* ── Typography ─────────────────────────────────────────────── */
26
+ --font-family-base: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif;
27
+ --font-family-display: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif;
28
+ --font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
29
+
30
+ --font-size-xs: 11px;
31
+ --font-size-sm: 13px;
32
+ --font-size-body: 16px;
33
+ --font-size-body-lg: 17px;
34
+ --font-size-subhead: 19px;
35
+ --font-size-headline: 22px;
36
+ --font-size-title: 28px;
37
+ --font-size-display: 40px;
38
+
39
+ --font-weight-regular: 400;
40
+ --font-weight-medium: 500;
41
+ --font-weight-semibold: 600;
42
+ --font-weight-bold: 700;
43
+ --font-weight-heavy: 800;
44
+
45
+ --line-height-tight: 1.2;
46
+ --line-height-normal: 1.5;
47
+ --line-height-relaxed: 1.75;
48
+
49
+ /* ── Spacing ─────────────────────────────────────────────────── */
50
+ --space-1: 4px;
51
+ --space-2: 8px;
52
+ --space-3: 12px;
53
+ --space-4: 16px;
54
+ --space-5: 20px;
55
+ --space-6: 24px;
56
+ --space-8: 32px;
57
+ --space-10: 40px;
58
+ --space-12: 48px;
59
+
60
+ /* ── Radius ──────────────────────────────────────────────────── */
61
+ --radius-sm: 6px;
62
+ --radius-md: 10px;
63
+ --radius-lg: 16px;
64
+ --radius-full: 9999px;
65
+
66
+ /* ── Shadow ──────────────────────────────────────────────────── */
67
+ --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.06);
68
+ --shadow-elevated: 0 8px 24px rgba(0, 0, 0, 0.12);
69
+
70
+ /* ── Transitions ─────────────────────────────────────────────── */
71
+ --duration-fast: 120ms;
72
+ --duration-normal: 220ms;
73
+ --duration-slow: 400ms;
74
+ --easing-default: cubic-bezier(0.25, 0.1, 0.25, 1);
75
+ }
76
+
77
+ /* ── Dark theme ───────────────────────────────────────────────── */
78
+ @media (prefers-color-scheme: dark) {
79
+ :root {
80
+ --color-bg-primary: #000000;
81
+ --color-bg-secondary: #1c1c1e;
82
+ --color-bg-card: #1c1c1e;
83
+ --color-bg-tag: #1a2744;
84
+
85
+ --color-text-primary: #f5f5f7;
86
+ --color-text-secondary: #aeaeb2;
87
+ --color-text-caption: #636366;
88
+ --color-text-accent: #2997ff;
89
+ --color-text-on-accent: #ffffff;
90
+
91
+ --color-border: #38383a;
92
+ --color-divider: #2c2c2e;
93
+
94
+ --color-accent: #2997ff;
95
+ --color-accent-hover: #409cff;
96
+ --color-danger: #ff453a;
97
+ --color-success: #32d74b;
98
+ --color-warning: #ffd60a;
99
+
100
+ --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.2);
101
+ --shadow-elevated: 0 8px 24px rgba(0, 0, 0, 0.4);
102
+ }
103
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ });
@@ -0,0 +1,67 @@
1
+ /* ── Task data store ─────────────────────────────────────────────────────── *
2
+ * In-file JS array — no network fetch required.
3
+ * Stage 6 behavioral-parity will port this to a Swift @State array.
4
+ * ─────────────────────────────────────────────────────────────────────────── */
5
+
6
+ window.TASKS = [
7
+ {
8
+ id: 1,
9
+ title: 'Design system tokens',
10
+ description: 'Define all color, spacing, and typography tokens as CSS custom properties so they can be extracted by automated tooling and mapped to SwiftUI theme values.',
11
+ category: 'Design',
12
+ priority: 'high',
13
+ done: false,
14
+ dueDate: '2026-05-22',
15
+ progress: [40, 55, 60, 72, 85, 90, 88, 95],
16
+ },
17
+ {
18
+ id: 2,
19
+ title: 'Implement hash router',
20
+ description: 'Build a minimal client-side router using the hashchange event. Supports three routes: home, list, and detail. No library dependencies.',
21
+ category: 'Engineering',
22
+ priority: 'high',
23
+ done: false,
24
+ dueDate: '2026-05-23',
25
+ progress: [10, 25, 45, 68, 80, 90, 95, 100],
26
+ },
27
+ {
28
+ id: 3,
29
+ title: 'Write unit tests for router',
30
+ description: 'Cover route matching, missing routes, and the detail screen id extraction from the URL hash fragment.',
31
+ category: 'QA',
32
+ priority: 'medium',
33
+ done: true,
34
+ dueDate: '2026-05-20',
35
+ progress: [20, 40, 60, 80, 100, 100, 100, 100],
36
+ },
37
+ {
38
+ id: 4,
39
+ title: 'Canvas sparkline Tier-3 spike',
40
+ description: 'Prototype the canvas-based sparkline widget on the detail screen. This surface is explicitly Tier-3 (high risk) and will require human review before SwiftUI porting.',
41
+ category: 'Research',
42
+ priority: 'medium',
43
+ done: false,
44
+ dueDate: '2026-05-28',
45
+ progress: [5, 10, 15, 20, 30, 35, 42, 50],
46
+ },
47
+ {
48
+ id: 5,
49
+ title: 'Accessibility audit',
50
+ description: 'Check all screens for WCAG 2.2 AA compliance: color contrast ratios, focus management, ARIA labels, and keyboard navigation.',
51
+ category: 'Accessibility',
52
+ priority: 'low',
53
+ done: false,
54
+ dueDate: '2026-06-01',
55
+ progress: [0, 0, 5, 5, 10, 15, 20, 28],
56
+ },
57
+ {
58
+ id: 6,
59
+ title: 'Dark mode tokens',
60
+ description: 'Add @media (prefers-color-scheme: dark) overrides for every CSS custom property in tokens.css.',
61
+ category: 'Design',
62
+ priority: 'low',
63
+ done: true,
64
+ dueDate: '2026-05-19',
65
+ progress: [30, 55, 70, 85, 90, 95, 98, 100],
66
+ },
67
+ ];
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>TaskFlow</title>
7
+ <link rel="stylesheet" href="styles/tokens.css" />
8
+ <link rel="stylesheet" href="styles/app.css" />
9
+ </head>
10
+ <body>
11
+ <!-- Root shell — screens are injected here by router.js -->
12
+ <div id="app">
13
+ <nav id="nav-bar">
14
+ <a href="#/home" class="nav-link" data-route="home">Home</a>
15
+ <a href="#/list" class="nav-link" data-route="list">Tasks</a>
16
+ </nav>
17
+ <main id="screen-root"></main>
18
+ </div>
19
+
20
+ <script src="data/tasks.js"></script>
21
+ <script src="screens/home.js"></script>
22
+ <script src="screens/list.js"></script>
23
+ <script src="screens/detail.js"></script>
24
+ <script src="router.js"></script>
25
+ </body>
26
+ </html>
@@ -0,0 +1,73 @@
1
+ /* ── Client-side hash router ─────────────────────────────────────────────── *
2
+ * Routes:
3
+ * #/home → HomeScreen
4
+ * #/list → ListScreen
5
+ * #/detail?id=<N> → DetailScreen
6
+ *
7
+ * Plain DOM addEventListener — no library, no framework.
8
+ * Stage 1 will inventory these routes for NavigationStack mapping.
9
+ * ─────────────────────────────────────────────────────────────────────────── */
10
+
11
+ (function () {
12
+ 'use strict';
13
+
14
+ /** @type {HTMLElement} */
15
+ const screenRoot = document.getElementById('screen-root');
16
+
17
+ // Route table: path prefix → screen factory function
18
+ const routes = {
19
+ '/home': renderHome,
20
+ '/list': renderList,
21
+ '/detail': renderDetail,
22
+ };
23
+
24
+ /**
25
+ * Parse the current window.location.hash into { path, params }.
26
+ * Hash format: #/path?key=value&key2=value2
27
+ */
28
+ function parseHash() {
29
+ const raw = window.location.hash.replace(/^#/, '') || '/home';
30
+ const [path, qs] = raw.split('?');
31
+ const params = {};
32
+ if (qs) {
33
+ qs.split('&').forEach(pair => {
34
+ const [k, v] = pair.split('=');
35
+ if (k) params[decodeURIComponent(k)] = decodeURIComponent(v ?? '');
36
+ });
37
+ }
38
+ return { path: path || '/home', params };
39
+ }
40
+
41
+ /** Update nav link active state */
42
+ function syncNav(path) {
43
+ document.querySelectorAll('.nav-link').forEach(link => {
44
+ const route = link.dataset.route;
45
+ const active = path.startsWith('/' + route);
46
+ link.classList.toggle('active', active);
47
+ });
48
+ }
49
+
50
+ /** Dispatch to the correct screen factory */
51
+ function navigate() {
52
+ const { path, params } = parseHash();
53
+ syncNav(path);
54
+
55
+ // Find the matching route (longest prefix wins)
56
+ const match = Object.keys(routes).find(r => path === r || path.startsWith(r));
57
+ const factory = match ? routes[match] : null;
58
+
59
+ if (!factory) {
60
+ screenRoot.innerHTML = `<div class="empty-state"><p>404 — route not found: <code>${path}</code></p></div>`;
61
+ return;
62
+ }
63
+
64
+ // Clear previous content, then render
65
+ screenRoot.innerHTML = '';
66
+ const node = factory(params);
67
+ if (node) screenRoot.appendChild(node);
68
+ }
69
+
70
+ // Trigger on hash change and on first load
71
+ window.addEventListener('hashchange', navigate);
72
+ window.addEventListener('DOMContentLoaded', navigate);
73
+ }());
@@ -0,0 +1,164 @@
1
+ /* ── Detail screen ───────────────────────────────────────────────────────── *
2
+ * Route: #/detail?id=<N>
3
+ * Shows: task card with image placeholder, metadata chips, description,
4
+ * and a canvas-based progress sparkline (Tier-3 surface).
5
+ *
6
+ * The sparkline uses <canvas> with plain 2D context drawing.
7
+ * Stage 1 route inventory will capture this screen.
8
+ * Stage 3 NavigationStack will map it as a pushed detail view.
9
+ * Stage risk-triage.json will flag the canvas section as Tier-3.
10
+ * ─────────────────────────────────────────────────────────────────────────── */
11
+
12
+ /* global TASKS */
13
+
14
+ function renderDetail(params) {
15
+ 'use strict';
16
+
17
+ const id = parseInt(params.id, 10);
18
+ const task = TASKS.find(t => t.id === id);
19
+
20
+ const screen = document.createElement('section');
21
+ screen.className = 'screen';
22
+ screen.setAttribute('aria-label', 'Task detail');
23
+
24
+ if (!task) {
25
+ screen.innerHTML = `
26
+ <a href="#/list" class="detail-back">← Back to list</a>
27
+ <div class="empty-state">
28
+ <p>Task #${id} not found.</p>
29
+ <a href="#/list" class="btn btn-secondary">Go to list</a>
30
+ </div>
31
+ `;
32
+ return screen;
33
+ }
34
+
35
+ // ── Static markup for Tier-1 surfaces ──
36
+ screen.innerHTML = `
37
+ <a href="#/list" class="detail-back">← Back to list</a>
38
+
39
+ <article class="detail-card">
40
+ <!-- Image placeholder — maps to AsyncImage in SwiftUI -->
41
+ <div class="detail-image-placeholder" aria-label="Task cover image placeholder">
42
+ Cover image
43
+ </div>
44
+
45
+ <div class="detail-body">
46
+ <h1 class="detail-title">${task.title}</h1>
47
+
48
+ <div class="detail-meta-row">
49
+ <span class="detail-chip">${task.category}</span>
50
+ <span class="detail-chip priority-chip" data-priority="${task.priority}">${task.priority} priority</span>
51
+ <span class="detail-chip">${task.done ? '✓ Done' : 'Pending'}</span>
52
+ <span class="detail-chip">Due ${task.dueDate}</span>
53
+ </div>
54
+
55
+ <p class="detail-desc">${task.description}</p>
56
+ </div>
57
+ </article>
58
+
59
+ <!-- TIER3: canvas custom drawing -->
60
+ <div class="sparkline-section">
61
+ <h3>Progress history (last 8 entries)</h3>
62
+ <canvas id="sparkline-canvas"
63
+ width="600"
64
+ height="80"
65
+ aria-label="Progress sparkline chart"
66
+ role="img">
67
+ Progress values: ${task.progress.join(', ')}
68
+ </canvas>
69
+ </div>
70
+
71
+ <div style="display:flex; gap:var(--space-3); flex-wrap:wrap;">
72
+ <a href="#/list" class="btn btn-secondary">← All tasks</a>
73
+ <a href="#/home" class="btn btn-secondary">Home</a>
74
+ </div>
75
+ `;
76
+
77
+ // ── Canvas sparkline — Tier-3 surface ─────────────────────────────────────
78
+ // This tiny canvas draw is the explicit Tier-3 region.
79
+ // risk-triage.json should flag #sparkline-canvas as requiring human review
80
+ // before any SwiftUI port attempt (Charts.framework vs custom Path).
81
+ // <!-- TIER3: canvas custom drawing -->
82
+ drawSparkline(screen.querySelector('#sparkline-canvas'), task.progress);
83
+
84
+ return screen;
85
+ }
86
+
87
+ /**
88
+ * Draw a minimal line sparkline onto the given canvas element.
89
+ * Uses only the 2D canvas API — no library.
90
+ *
91
+ * @param {HTMLCanvasElement} canvas
92
+ * @param {number[]} values Array of progress percentages (0–100)
93
+ */
94
+ function drawSparkline(canvas, values) {
95
+ 'use strict';
96
+
97
+ if (!canvas || !canvas.getContext) return;
98
+
99
+ const ctx = canvas.getContext('2d');
100
+ const dpr = window.devicePixelRatio || 1;
101
+ const w = canvas.clientWidth || 600;
102
+ const h = canvas.clientHeight || 80;
103
+
104
+ // Resize for device pixel ratio
105
+ canvas.width = w * dpr;
106
+ canvas.height = h * dpr;
107
+ ctx.scale(dpr, dpr);
108
+
109
+ const pad = 12;
110
+ const min = 0;
111
+ const max = 100;
112
+ const rangeX = w - pad * 2;
113
+ const rangeY = h - pad * 2;
114
+
115
+ // Map a value to canvas coordinates
116
+ function xAt(i) { return pad + (i / (values.length - 1)) * rangeX; }
117
+ function yAt(v) { return pad + (1 - (v - min) / (max - min)) * rangeY; }
118
+
119
+ // Detect dark mode for color
120
+ const dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
121
+ const lineColor = dark ? '#0a84ff' : '#007aff';
122
+ const fillFrom = dark ? 'rgba(10,132,255,0.25)' : 'rgba(0,122,255,0.12)';
123
+ const dotColor = dark ? '#ffffff' : '#007aff';
124
+
125
+ // Area fill
126
+ ctx.beginPath();
127
+ ctx.moveTo(xAt(0), yAt(values[0]));
128
+ for (let i = 1; i < values.length; i++) {
129
+ ctx.lineTo(xAt(i), yAt(values[i]));
130
+ }
131
+ ctx.lineTo(xAt(values.length - 1), h - pad);
132
+ ctx.lineTo(xAt(0), h - pad);
133
+ ctx.closePath();
134
+ ctx.fillStyle = fillFrom;
135
+ ctx.fill();
136
+
137
+ // Line stroke
138
+ ctx.beginPath();
139
+ ctx.moveTo(xAt(0), yAt(values[0]));
140
+ for (let i = 1; i < values.length; i++) {
141
+ ctx.lineTo(xAt(i), yAt(values[i]));
142
+ }
143
+ ctx.strokeStyle = lineColor;
144
+ ctx.lineWidth = 2;
145
+ ctx.lineJoin = 'round';
146
+ ctx.stroke();
147
+
148
+ // Data point dots
149
+ ctx.fillStyle = dotColor;
150
+ values.forEach((v, i) => {
151
+ ctx.beginPath();
152
+ ctx.arc(xAt(i), yAt(v), 3, 0, Math.PI * 2);
153
+ ctx.fill();
154
+ });
155
+
156
+ // Last value label
157
+ const lastX = xAt(values.length - 1);
158
+ const lastY = yAt(values[values.length - 1]);
159
+ ctx.fillStyle = lineColor;
160
+ ctx.font = `bold ${11 * dpr / dpr}px -apple-system, sans-serif`;
161
+ ctx.textAlign = 'right';
162
+ ctx.textBaseline = 'bottom';
163
+ ctx.fillText(`${values[values.length - 1]}%`, lastX - 4, lastY - 4);
164
+ }
@@ -0,0 +1,53 @@
1
+ /* ── Home screen ─────────────────────────────────────────────────────────── *
2
+ * Route: #/home
3
+ * Shows: hero text, summary stats derived from TASKS, two CTA buttons.
4
+ * Tier-1 surface: text, buttons, stat cards, flex layout.
5
+ * ─────────────────────────────────────────────────────────────────────────── */
6
+
7
+ /* global TASKS */
8
+
9
+ function renderHome(/* params */) {
10
+ 'use strict';
11
+
12
+ const total = TASKS.length;
13
+ const done = TASKS.filter(t => t.done).length;
14
+ const pending = total - done;
15
+ const highPri = TASKS.filter(t => t.priority === 'high' && !t.done).length;
16
+
17
+ const screen = document.createElement('section');
18
+ screen.className = 'screen';
19
+ screen.setAttribute('aria-label', 'Home');
20
+
21
+ screen.innerHTML = `
22
+ <div class="home-hero">
23
+ <h1>TaskFlow</h1>
24
+ <p>A simple task manager — open <code>index.html</code> directly, no build required.</p>
25
+ </div>
26
+
27
+ <div class="home-stats" role="list" aria-label="Summary stats">
28
+ <div class="stat-card" role="listitem">
29
+ <span class="stat-value">${total}</span>
30
+ <span class="stat-label">Total</span>
31
+ </div>
32
+ <div class="stat-card" role="listitem">
33
+ <span class="stat-value">${pending}</span>
34
+ <span class="stat-label">Pending</span>
35
+ </div>
36
+ <div class="stat-card" role="listitem">
37
+ <span class="stat-value">${done}</span>
38
+ <span class="stat-label">Done</span>
39
+ </div>
40
+ <div class="stat-card" role="listitem">
41
+ <span class="stat-value">${highPri}</span>
42
+ <span class="stat-label">High priority</span>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="home-cta">
47
+ <a href="#/list" class="btn btn-primary">View Tasks</a>
48
+ <a href="#/detail?id=1" class="btn btn-secondary">Sample Detail</a>
49
+ </div>
50
+ `;
51
+
52
+ return screen;
53
+ }
@@ -0,0 +1,87 @@
1
+ /* ── List screen ─────────────────────────────────────────────────────────── *
2
+ * Route: #/list
3
+ * Shows: filterable task list loaded from window.TASKS (in-file JS array).
4
+ * Tier-1 surface: list rows, badges, flex layout, text.
5
+ * ─────────────────────────────────────────────────────────────────────────── */
6
+
7
+ /* global TASKS */
8
+
9
+ function renderList(/* params */) {
10
+ 'use strict';
11
+
12
+ const screen = document.createElement('section');
13
+ screen.className = 'screen';
14
+ screen.setAttribute('aria-label', 'Task list');
15
+
16
+ // ── State ──
17
+ let filter = 'all'; // 'all' | 'pending' | 'done'
18
+
19
+ function filteredTasks() {
20
+ if (filter === 'pending') return TASKS.filter(t => !t.done);
21
+ if (filter === 'done') return TASKS.filter(t => t.done);
22
+ return TASKS;
23
+ }
24
+
25
+ // ── Render task rows ──
26
+ function buildTaskList() {
27
+ const tasks = filteredTasks();
28
+
29
+ if (tasks.length === 0) {
30
+ return `<div class="empty-state"><p>No tasks in this filter.</p></div>`;
31
+ }
32
+
33
+ const rows = tasks.map(task => `
34
+ <li class="task-row ${task.done ? 'task-done' : ''}"
35
+ data-id="${task.id}"
36
+ role="button"
37
+ tabindex="0"
38
+ aria-label="${task.title}, ${task.priority} priority, ${task.done ? 'done' : 'pending'}">
39
+ <span class="task-dot priority-${task.priority}" aria-hidden="true"></span>
40
+ <div class="task-info">
41
+ <span class="task-title">${task.title}</span>
42
+ <span class="task-meta">${task.category} · Due ${task.dueDate}</span>
43
+ </div>
44
+ <span class="task-badge">${task.priority}</span>
45
+ </li>
46
+ `).join('');
47
+
48
+ return `<ul class="task-list" aria-label="Tasks">${rows}</ul>`;
49
+ }
50
+
51
+ // ── Full render ──
52
+ function render() {
53
+ screen.innerHTML = `
54
+ <div class="list-header">
55
+ <h2>Tasks</h2>
56
+ <div style="display:flex; gap:var(--space-2)">
57
+ <button class="btn btn-secondary ${filter === 'all' ? 'active' : ''}" data-filter="all">All</button>
58
+ <button class="btn btn-secondary ${filter === 'pending' ? 'active' : ''}" data-filter="pending">Pending</button>
59
+ <button class="btn btn-secondary ${filter === 'done' ? 'active' : ''}" data-filter="done">Done</button>
60
+ </div>
61
+ </div>
62
+ ${buildTaskList()}
63
+ `;
64
+
65
+ // Bind filter buttons
66
+ screen.querySelectorAll('[data-filter]').forEach(btn => {
67
+ btn.addEventListener('click', () => {
68
+ filter = btn.dataset.filter;
69
+ render();
70
+ });
71
+ });
72
+
73
+ // Bind task rows → navigate to detail
74
+ screen.querySelectorAll('.task-row').forEach(row => {
75
+ function goDetail() {
76
+ window.location.hash = `#/detail?id=${row.dataset.id}`;
77
+ }
78
+ row.addEventListener('click', goDetail);
79
+ row.addEventListener('keydown', e => {
80
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); goDetail(); }
81
+ });
82
+ });
83
+ }
84
+
85
+ render();
86
+ return screen;
87
+ }