@yuktishaalaa/yuktai 5.3.0 → 5.3.2

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.
package/vanilla/README.md CHANGED
@@ -1,162 +1,162 @@
1
- # yuktai — Vanilla JS
2
-
3
- The whole yuktai 5.2 toolkit for plain HTML pages: one script file, no React, no build step, no API keys.
4
-
5
- - **Data grid you can talk to**: search, sort, pagination, selection, CSV export, five WCAG themes, table/card views, Telugu UI, and keyboard navigation with ARIA grid roles.
6
- - **Agents**: smart grid queries, BI answers, multi-step tasks, an anomaly finder, a grid assistant with custom rules and voice, and WebMCP.
7
- - **Page tools**: Accessibility Copilot, QA report (15 WCAG checks), form review, design review, page Q&A and voice commands.
8
- - **Accessibility engine**: WCAG auto-fixes, high contrast, dark mode, reduced motion, large targets, colour-blind filters, a dyslexia font, text size 80–130%, skip links and speak-on-focus.
9
-
10
- The logic is bundled from this repo's `src/` (the same code as the npm package), so a question gets the same answer in React and in plain HTML. Only the React components and hooks are rewritten, in `vanilla/src/`.
11
-
12
- ## Use it
13
-
14
- ```html
15
- <script>
16
- window.YUKTAI_CONFIG = { position: "right", showPageTools: true } // optional
17
- </script>
18
- <script src="./index.js"></script> <!-- or index.min.js -->
19
-
20
- <div id="staff"></div>
21
- <script>
22
- const grid = yuktai.renderGrid("#staff", {
23
- data: rows,
24
- rowKey: "id",
25
- columns: [
26
- { key: "name", label: "Name" },
27
- { key: "salary", label: "Salary", type: "number" },
28
- { key: "joined", label: "Joined", type: "date" },
29
- { key: "status", label: "Status", type: "badge" },
30
- ],
31
- agentic: true, // question bar: "salary over 1 lakh", "average salary by department"
32
- ai: true, // assistant: custom rules → phrases → tools, with voice input
33
- })
34
- </script>
35
- ```
36
-
37
- Open `index.html` in this folder to see every feature with working examples, including a Telugu grid.
38
-
39
- ## `YUKTAI_CONFIG` (floating buttons)
40
-
41
- | Option | Default | Purpose |
42
- | --- | --- | --- |
43
- | `position` | `"left"` | Side for the floating buttons: `"left"` or `"right"` |
44
- | `primaryColor` | `#0f766e` | Accent colour (keep 4.5:1 against white) |
45
- | `title` | `"Accessibility"` | Settings dialog title |
46
- | `showPageTools` | `false` | Adds the 🛠 button that opens the six page tools |
47
- | `defaultTab`, `voiceLang` | `"qa"`, `"en-IN"` | Settings for the page tools |
48
- | `generate(q, ctx)`, `summarize(text)` | — | Optional hooks for your own model (Ask tab) |
49
- | `config` | — | Engine overrides, for example `{ autoFix: false }` |
50
- | `applyOnLoad` | `true` | Run the engine once on page load (restores saved settings) |
51
- | `autoInit` | `true` | Set `false` to call `yuktai.init()` yourself |
52
-
53
- `yuktai.open()`, `yuktai.close()`, `yuktai.openTools("forms")`, `yuktai.apply({ highContrast: true })`, `yuktai.reset()` and `yuktai.getSettings()` control the widget from code. Settings are saved in `localStorage` (`yuktai-a11y-prefs`), the same key the npm package uses.
54
-
55
- ## `renderGrid(container, options)`
56
-
57
- The v5.1 vanilla signature `renderGrid(container, rows, columns, options)` still works.
58
-
59
- | Option | Purpose |
60
- | --- | --- |
61
- | `data`, `columns`, `rowKey` (`"id"`) | Rows, columns, row ID field |
62
- | column: `key`, `label`, `type` (`text`/`number`/`date`/`boolean`/`badge`), `align`, `sortable`, `render(value, row, index)` returns text or a DOM node, `hiddenOnMobile`, `width` | |
63
- | `label` | Accessible name of the grid |
64
- | `locale` | `"en-IN"` (default), `"te-IN"` for Telugu UI and messages, or any Indian locale code |
65
- | `theme` | `default`, `high-contrast`, `dark`, `color-blind`, `dyslexia` |
66
- | `view`, `mobileBreakpoint` | `table`, `card` or `auto` (cards below 640px) |
67
- | `pagination` | `{ pageSize, showSizeChanger, sizeOptions }`, or `false` |
68
- | `search`, `selectable`, `exportable` | `false` turns each one off |
69
- | `selectedKeys`, `highlightIds`, `autoScrollToHighlight` | Initial state |
70
- | `loading`, `empty` | Loading state; empty message (text or a node) |
71
- | `agentic` | `true` or `{ title, placeholder, examples, hint }`: the question bar |
72
- | `ai`, `inputLanguage`, `assistantExamples` | Built-in assistant with voice input |
73
- | `customRules` | `[{ name, phrases, description, execute({ input, data, columns, executeTool }) }]`, in any language |
74
- | `parseIntent(text, ctx)` | Replace the built-in phrase parser |
75
- | `webmcp`, `toolName`, `toolDescriptions`, `onWebMCPStatusChange` | Expose the 11 grid tools to browser AI agents |
76
- | `referenceDate` | "Now" for "this month" questions |
77
- | `onRowClick(row)`, `onOpenRow(id)` | Enables the `open` tool and "open Ravi" |
78
- | `onSelectionChange(keys)`, `onSortChange(sort)`, `onFiltersChange(filters)`, `onAgentResult(r)` | Events |
79
- | `wrap`, `maxHeight`, `showTitle`, `className` | Layout |
80
-
81
- **Keyboard:** Tab into the table, then use the arrow keys to move between cells.
82
-
83
- - Home / End go to the first or last cell in a row; Ctrl+Home / Ctrl+End go to the first or last cell in the table.
84
- - Page Up / Page Down change the page.
85
- - Space selects a row; Enter sorts (on a column header) or opens the row.
86
- - Only one cell is in the Tab order.
87
-
88
- ### Grid API
89
-
90
- ```js
91
- await grid.ask("salary over 1 lakh in Hyderabad") // answer text (useAgenticGrid)
92
- await grid.askDetailed("average salary by department") // { answer, kind, table, steps, gridData, … }
93
- await grid.assistantAsk("sort by salary desc") // assistant pipeline result
94
- await grid.executeTool("filter", { key: "salary", operator: "greaterThan", value: 100000 })
95
- grid.search("Pune"); grid.setFilters([...]); grid.clearFilters(); grid.sortBy("name", "desc"); grid.clearSort()
96
- grid.highlight(["1001"]); grid.clearHighlight(); grid.select(["1001"]); grid.getSelectedRows()
97
- grid.goToPage(3); grid.nextPage(); grid.prevPage(); grid.setPageSize(50); grid.getPage()
98
- grid.setTheme("dark"); grid.setView("card"); grid.setLoading(true)
99
- grid.getRows(); grid.getPageRows(); grid.getData(); grid.refresh(newRows); grid.setData(rows, columns)
100
- grid.biSummary(); grid.anomalies(); grid.exportCSV(); grid.reset(); grid.destroy()
101
- grid.tools; grid.agent; grid.agentic; grid.webmcpStatus
102
- ```
103
-
104
- Tool results are always `{ success, message, data?, error?: { code } }` and never throw. The error codes are `NOT_FOUND`, `NOT_SUPPORTED`, `INVALID_INPUT`, `INTERNAL` and `NOT_UNDERSTOOD`.
105
-
106
- ## Without a grid
107
-
108
- ```js
109
- const agentic = yuktai.createAgenticGrid({ data: rows, columns, rowKey: "id" }) // useAgenticGrid
110
- yuktai.mountAgenticGridBar("#bar", agentic, { examples: ["salary under 50k"] }) // AgenticGridBar
111
- agentic.subscribe((s) => drawMyTable(s.gridData, s.highlightIds))
112
-
113
- const tools = yuktai.createGridTools({ data: rows, columns, rowKey: "id", onFiltersChange, onSortChange }, { name: "my_grid" })
114
- const agent = yuktai.createGridAgent({ tools, columns, customRules, locale: "te" }) // useYuktaiGridAgent
115
- await agent.ask("ఎన్ని")
116
- yuktai.registerWebMCP(tools, { onStatusChange: console.log }) // YuktaiGridWebMCP
117
- ```
118
-
119
- ## Page tools
120
-
121
- ```js
122
- yuktai.mountAgenticPanel("#tools", { defaultTab: "qa", voiceLang: "te-IN" }) // AgenticPanel, six tabs
123
- ```
124
-
125
- The panel is marked `data-yuktai-panel`, so the agents never audit their own UI.
126
-
127
- ## Core functions (same as npm)
128
-
129
- - **Grid queries:** `parseAmount`, `parseGridQuery`, `executeGridQuery`, `runGridQuery`, `answerGridQuestion`, `detectAnomalies`, `summarizeRows`
130
- - **BI:** `askInsight`, `groupBy`, `comparePeriods`, `trend`, `aggregate`
131
- - **Tasks:** `planTasks`, `runTasks`
132
- - **QA:** `runQaAudit`, `auditToMarkdown`, `auditToJSON`, `AUDIT_RULES`
133
- - **Copilot:** `createA11yCopilot`
134
- - **Page understanding:** `askAboutPage`, `analyzePage`, `detectPageIntent`
135
- - **Page and data RAG:** `createContextIndex`, `tokenize`
136
- - **Forms and design:** `reviewForms`, `reviewForm`, `readFormFields`, `reviewDesign`
137
- - **Voice:** `parseVoiceA11yCommand`, `createVoiceA11yController`, `moveGridRowFocus`
138
- - **Grid tools:** `createGridTools`, `getRowId`, `applyGridFilters`, `toGridToolColumns`, and each tool function
139
- - **Other:** `contrastRatio`, `parseColor`, `relativeLuminance`, `accessibleName`, `wcagPlugin`
140
- - **Icons:** `icon(name, size, label?)` and `iconNames`
141
-
142
- ## Build and test
143
-
144
- ```bash
145
- npm i -D esbuild # once
146
- node vanilla/build.mjs # → vanilla/index.js and vanilla/index.min.js
147
-
148
- # End-to-end checks in real Chromium (37 checks plus axe-core)
149
- npm i -D playwright-core axe-core
150
- CHROME=/path/to/chrome node vanilla/test/e2e.mjs
151
- ```
152
-
153
- Edit `vanilla/src/*` or `src/*`, then rebuild. Do not edit `vanilla/index.js` by hand.
154
-
155
- ## Honest limits
156
-
157
- - Questions are understood with rules, not a language model, so unusual phrasing may need rewording. Telugu and Hindi data questions need custom rules.
158
- - QA, form and design checks are heuristics. Test with a keyboard and a screen reader before release.
159
- - Voice depends on the browser's speech recognition. Typed commands always work.
160
- - WebMCP needs `document.modelContext`. The real status is always reported.
161
- - Filtering and paging run in the browser and suit a few thousand rows. For 100K+ rows, page on the server and call `grid.refresh(pageRows)`.
162
- - The floating buttons are added under `<html>` (not `<body>`), so the colour-blind filter on `<body>` cannot move them.
1
+ # yuktai — Vanilla JS
2
+
3
+ The whole yuktai 5.2 toolkit for plain HTML pages: one script file, no React, no build step, no API keys.
4
+
5
+ - **Data grid you can talk to**: search, sort, pagination, selection, CSV export, five WCAG themes, table/card views, Telugu UI, and keyboard navigation with ARIA grid roles.
6
+ - **Agents**: smart grid queries, BI answers, multi-step tasks, an anomaly finder, a grid assistant with custom rules and voice, and WebMCP.
7
+ - **Page tools**: Accessibility Copilot, QA report (15 WCAG checks), form review, design review, page Q&A and voice commands.
8
+ - **Accessibility engine**: WCAG auto-fixes, high contrast, dark mode, reduced motion, large targets, colour-blind filters, a dyslexia font, text size 80–130%, skip links and speak-on-focus.
9
+
10
+ The logic is bundled from this repo's `src/` (the same code as the npm package), so a question gets the same answer in React and in plain HTML. Only the React components and hooks are rewritten, in `vanilla/src/`.
11
+
12
+ ## Use it
13
+
14
+ ```html
15
+ <script>
16
+ window.YUKTAI_CONFIG = { position: "right", showPageTools: true } // optional
17
+ </script>
18
+ <script src="./index.js"></script> <!-- or index.min.js -->
19
+
20
+ <div id="staff"></div>
21
+ <script>
22
+ const grid = yuktai.renderGrid("#staff", {
23
+ data: rows,
24
+ rowKey: "id",
25
+ columns: [
26
+ { key: "name", label: "Name" },
27
+ { key: "salary", label: "Salary", type: "number" },
28
+ { key: "joined", label: "Joined", type: "date" },
29
+ { key: "status", label: "Status", type: "badge" },
30
+ ],
31
+ agentic: true, // question bar: "salary over 1 lakh", "average salary by department"
32
+ ai: true, // assistant: custom rules → phrases → tools, with voice input
33
+ })
34
+ </script>
35
+ ```
36
+
37
+ Open `index.html` in this folder to see every feature with working examples, including a Telugu grid.
38
+
39
+ ## `YUKTAI_CONFIG` (floating buttons)
40
+
41
+ | Option | Default | Purpose |
42
+ | --- | --- | --- |
43
+ | `position` | `"left"` | Side for the floating buttons: `"left"` or `"right"` |
44
+ | `primaryColor` | `#0f766e` | Accent colour (keep 4.5:1 against white) |
45
+ | `title` | `"Accessibility"` | Settings dialog title |
46
+ | `showPageTools` | `false` | Adds the 🛠 button that opens the six page tools |
47
+ | `defaultTab`, `voiceLang` | `"qa"`, `"en-IN"` | Settings for the page tools |
48
+ | `generate(q, ctx)`, `summarize(text)` | — | Optional hooks for your own model (Ask tab) |
49
+ | `config` | — | Engine overrides, for example `{ autoFix: false }` |
50
+ | `applyOnLoad` | `true` | Run the engine once on page load (restores saved settings) |
51
+ | `autoInit` | `true` | Set `false` to call `yuktai.init()` yourself |
52
+
53
+ `yuktai.open()`, `yuktai.close()`, `yuktai.openTools("forms")`, `yuktai.apply({ highContrast: true })`, `yuktai.reset()` and `yuktai.getSettings()` control the widget from code. Settings are saved in `localStorage` (`yuktai-a11y-prefs`), the same key the npm package uses.
54
+
55
+ ## `renderGrid(container, options)`
56
+
57
+ The v5.1 vanilla signature `renderGrid(container, rows, columns, options)` still works.
58
+
59
+ | Option | Purpose |
60
+ | --- | --- |
61
+ | `data`, `columns`, `rowKey` (`"id"`) | Rows, columns, row ID field |
62
+ | column: `key`, `label`, `type` (`text`/`number`/`date`/`boolean`/`badge`), `align`, `sortable`, `render(value, row, index)` returns text or a DOM node, `hiddenOnMobile`, `width` | |
63
+ | `label` | Accessible name of the grid |
64
+ | `locale` | `"en-IN"` (default), `"te-IN"` for Telugu UI and messages, or any Indian locale code |
65
+ | `theme` | `default`, `high-contrast`, `dark`, `color-blind`, `dyslexia` |
66
+ | `view`, `mobileBreakpoint` | `table`, `card` or `auto` (cards below 640px) |
67
+ | `pagination` | `{ pageSize, showSizeChanger, sizeOptions }`, or `false` |
68
+ | `search`, `selectable`, `exportable` | `false` turns each one off |
69
+ | `selectedKeys`, `highlightIds`, `autoScrollToHighlight` | Initial state |
70
+ | `loading`, `empty` | Loading state; empty message (text or a node) |
71
+ | `agentic` | `true` or `{ title, placeholder, examples, hint }`: the question bar |
72
+ | `ai`, `inputLanguage`, `assistantExamples` | Built-in assistant with voice input |
73
+ | `customRules` | `[{ name, phrases, description, execute({ input, data, columns, executeTool }) }]`, in any language |
74
+ | `parseIntent(text, ctx)` | Replace the built-in phrase parser |
75
+ | `webmcp`, `toolName`, `toolDescriptions`, `onWebMCPStatusChange` | Expose the 11 grid tools to browser AI agents |
76
+ | `referenceDate` | "Now" for "this month" questions |
77
+ | `onRowClick(row)`, `onOpenRow(id)` | Enables the `open` tool and "open Ravi" |
78
+ | `onSelectionChange(keys)`, `onSortChange(sort)`, `onFiltersChange(filters)`, `onAgentResult(r)` | Events |
79
+ | `wrap`, `maxHeight`, `showTitle`, `className` | Layout |
80
+
81
+ **Keyboard:** Tab into the table, then use the arrow keys to move between cells.
82
+
83
+ - Home / End go to the first or last cell in a row; Ctrl+Home / Ctrl+End go to the first or last cell in the table.
84
+ - Page Up / Page Down change the page.
85
+ - Space selects a row; Enter sorts (on a column header) or opens the row.
86
+ - Only one cell is in the Tab order.
87
+
88
+ ### Grid API
89
+
90
+ ```js
91
+ await grid.ask("salary over 1 lakh in Hyderabad") // answer text (useAgenticGrid)
92
+ await grid.askDetailed("average salary by department") // { answer, kind, table, steps, gridData, … }
93
+ await grid.assistantAsk("sort by salary desc") // assistant pipeline result
94
+ await grid.executeTool("filter", { key: "salary", operator: "greaterThan", value: 100000 })
95
+ grid.search("Pune"); grid.setFilters([...]); grid.clearFilters(); grid.sortBy("name", "desc"); grid.clearSort()
96
+ grid.highlight(["1001"]); grid.clearHighlight(); grid.select(["1001"]); grid.getSelectedRows()
97
+ grid.goToPage(3); grid.nextPage(); grid.prevPage(); grid.setPageSize(50); grid.getPage()
98
+ grid.setTheme("dark"); grid.setView("card"); grid.setLoading(true)
99
+ grid.getRows(); grid.getPageRows(); grid.getData(); grid.refresh(newRows); grid.setData(rows, columns)
100
+ grid.biSummary(); grid.anomalies(); grid.exportCSV(); grid.reset(); grid.destroy()
101
+ grid.tools; grid.agent; grid.agentic; grid.webmcpStatus
102
+ ```
103
+
104
+ Tool results are always `{ success, message, data?, error?: { code } }` and never throw. The error codes are `NOT_FOUND`, `NOT_SUPPORTED`, `INVALID_INPUT`, `INTERNAL` and `NOT_UNDERSTOOD`.
105
+
106
+ ## Without a grid
107
+
108
+ ```js
109
+ const agentic = yuktai.createAgenticGrid({ data: rows, columns, rowKey: "id" }) // useAgenticGrid
110
+ yuktai.mountAgenticGridBar("#bar", agentic, { examples: ["salary under 50k"] }) // AgenticGridBar
111
+ agentic.subscribe((s) => drawMyTable(s.gridData, s.highlightIds))
112
+
113
+ const tools = yuktai.createGridTools({ data: rows, columns, rowKey: "id", onFiltersChange, onSortChange }, { name: "my_grid" })
114
+ const agent = yuktai.createGridAgent({ tools, columns, customRules, locale: "te" }) // useYuktaiGridAgent
115
+ await agent.ask("ఎన్ని")
116
+ yuktai.registerWebMCP(tools, { onStatusChange: console.log }) // YuktaiGridWebMCP
117
+ ```
118
+
119
+ ## Page tools
120
+
121
+ ```js
122
+ yuktai.mountAgenticPanel("#tools", { defaultTab: "qa", voiceLang: "te-IN" }) // AgenticPanel, six tabs
123
+ ```
124
+
125
+ The panel is marked `data-yuktai-panel`, so the agents never audit their own UI.
126
+
127
+ ## Core functions (same as npm)
128
+
129
+ - **Grid queries:** `parseAmount`, `parseGridQuery`, `executeGridQuery`, `runGridQuery`, `answerGridQuestion`, `detectAnomalies`, `summarizeRows`
130
+ - **BI:** `askInsight`, `groupBy`, `comparePeriods`, `trend`, `aggregate`
131
+ - **Tasks:** `planTasks`, `runTasks`
132
+ - **QA:** `runQaAudit`, `auditToMarkdown`, `auditToJSON`, `AUDIT_RULES`
133
+ - **Copilot:** `createA11yCopilot`
134
+ - **Page understanding:** `askAboutPage`, `analyzePage`, `detectPageIntent`
135
+ - **Page and data RAG:** `createContextIndex`, `tokenize`
136
+ - **Forms and design:** `reviewForms`, `reviewForm`, `readFormFields`, `reviewDesign`
137
+ - **Voice:** `parseVoiceA11yCommand`, `createVoiceA11yController`, `moveGridRowFocus`
138
+ - **Grid tools:** `createGridTools`, `getRowId`, `applyGridFilters`, `toGridToolColumns`, and each tool function
139
+ - **Other:** `contrastRatio`, `parseColor`, `relativeLuminance`, `accessibleName`, `wcagPlugin`
140
+ - **Icons:** `icon(name, size, label?)` and `iconNames`
141
+
142
+ ## Build and test
143
+
144
+ ```bash
145
+ npm i -D esbuild # once
146
+ node vanilla/build.mjs # → vanilla/index.js and vanilla/index.min.js
147
+
148
+ # End-to-end checks in real Chromium (37 checks plus axe-core)
149
+ npm i -D playwright-core axe-core
150
+ CHROME=/path/to/chrome node vanilla/test/e2e.mjs
151
+ ```
152
+
153
+ Edit `vanilla/src/*` or `src/*`, then rebuild. Do not edit `vanilla/index.js` by hand.
154
+
155
+ ## Honest limits
156
+
157
+ - Questions are understood with rules, not a language model, so unusual phrasing may need rewording. Telugu and Hindi data questions need custom rules.
158
+ - QA, form and design checks are heuristics. Test with a keyboard and a screen reader before release.
159
+ - Voice depends on the browser's speech recognition. Typed commands always work.
160
+ - WebMCP needs `document.modelContext`. The real status is always reported.
161
+ - Filtering and paging run in the browser and suit a few thousand rows. For 100K+ rows, page on the server and call `grid.refresh(pageRows)`.
162
+ - The floating buttons are added under `<html>` (not `<body>`), so the colour-blind filter on `<body>` cannot move them.
package/vanilla/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! yuktai vanilla 5.2.0 — accessible data grid, page agents and WCAG tools for plain HTML.
1
+ /*! yuktai vanilla 5.3.0 — accessible data grid, page agents and WCAG tools for plain HTML.
2
2
  * No framework, no API keys, runs in the browser. ISC © Sandeep Miriyala, Yuktishaalaa AI Lab.
3
3
  * Built from github.com/sandeepmiriyala03/yuktai (vanilla/src + src/agentic, src/grid, src/core). */
4
4
  "use strict";
@@ -3083,7 +3083,8 @@ Missing landmarks: ${missing.join(", ")}.` : ""}`,
3083
3083
  success: { bg: "#0a7a44", border: "#075e34", icon: "\u2713" },
3084
3084
  error: { bg: "#b52a1c", border: "#8f2116", icon: "\u2715" },
3085
3085
  warning: { bg: "#a35f00", border: "#7a4700", icon: "\u26A0" },
3086
- info: { bg: "#1557b0", border: "#0f4287", icon: "\u2139" }
3086
+ info: { bg: "#005A9C", border: "#003F6E", icon: "\u2139" }
3087
+ // W3C blue
3087
3088
  };
3088
3089
  const c = colors[type];
3089
3090
  if (!_visualAlertNode) {
@@ -3170,7 +3171,7 @@ Missing landmarks: ${missing.join(", ")}.` : ""}`,
3170
3171
  flex-wrap: wrap;
3171
3172
  gap: 4px;
3172
3173
  padding: 4px;
3173
- background: #111;
3174
+ background: #001124;
3174
3175
  transform: translateY(-100%);
3175
3176
  transition: transform 0.2s ease;
3176
3177
  font-family: system-ui, sans-serif;
@@ -3188,7 +3189,7 @@ Missing landmarks: ${missing.join(", ")}.` : ""}`,
3188
3189
  link.textContent = label;
3189
3190
  link.style.cssText = `
3190
3191
  color: #fff;
3191
- background: #1a73e8;
3192
+ background: #005A9C;
3192
3193
  padding: 8px 14px;
3193
3194
  border-radius: 4px;
3194
3195
  font-size: 13px;
@@ -4722,39 +4723,8 @@ Missing landmarks: ${missing.join(", ")}.` : ""}`,
4722
4723
  };
4723
4724
  }
4724
4725
 
4725
- // vanilla/src/dom.js
4726
- var isBrowser2 = () => typeof window !== "undefined" && typeof document !== "undefined";
4727
- function esc(value) {
4728
- return String(value == null ? "" : value).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
4729
- }
4730
- function h(tag, attrs, ...children) {
4731
- const el = document.createElement(tag);
4732
- if (attrs) {
4733
- for (const [key, value] of Object.entries(attrs)) {
4734
- if (value == null || value === false) continue;
4735
- if (key.startsWith("on") && typeof value === "function") el.addEventListener(key.slice(2), value);
4736
- else if (key === "html") el.innerHTML = value;
4737
- else if (key === "text") el.textContent = value;
4738
- else if (value === true) el.setAttribute(key, "");
4739
- else el.setAttribute(key, String(value));
4740
- }
4741
- }
4742
- for (const child of children.flat()) {
4743
- if (child == null || child === false) continue;
4744
- el.append(child instanceof Node ? child : document.createTextNode(String(child)));
4745
- }
4746
- return el;
4747
- }
4748
- var uid = 0;
4749
- var nextId = (prefix = "yv") => `${prefix}-${++uid}`;
4750
- function safeColor(value, fallback) {
4751
- if (typeof value !== "string" || !isBrowser2()) return fallback;
4752
- const probe = document.createElement("span");
4753
- probe.style.color = value;
4754
- return probe.style.color ? value : fallback;
4755
- }
4756
- var prefersReducedMotion = () => isBrowser2() && (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches || document.documentElement.hasAttribute("data-yuktai-reduce-motion"));
4757
- var PATHS = {
4726
+ // src/icons/paths.ts
4727
+ var ICON_PATHS = {
4758
4728
  accessibility: '<circle cx="12" cy="4" r="2"/><path d="M4 8l8-1 8 1M12 7v5m-5 9 5-8 5 8M7 12l-2 5m12-5 2 5"/>',
4759
4729
  contrast: '<circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z"/>',
4760
4730
  moon: '<path d="M20 14.5A8.5 8.5 0 0 1 9.5 4 8.5 8.5 0 1 0 20 14.5z"/>',
@@ -4795,12 +4765,129 @@ Missing landmarks: ${missing.join(", ")}.` : ""}`,
4795
4765
  wand: '<path d="m15 4 5 5L9 20H4v-5z"/><path d="m13 6 5 5"/>',
4796
4766
  undo: '<path d="M9 14 4 9l5-5"/><path d="M4 9h10a6 6 0 0 1 0 12h-3"/>',
4797
4767
  tools: '<path d="M14.7 6.3a4 4 0 0 0-5.4 5.4L3 18l3 3 6.3-6.3a4 4 0 0 0 5.4-5.4l-2.5 2.5-2.4-.6-.6-2.4z"/>',
4798
- plug: '<path d="M9 2v6M15 2v6M6 8h12v4a6 6 0 0 1-12 0zM12 18v4"/>'
4768
+ plug: '<path d="M9 2v6M15 2v6M6 8h12v4a6 6 0 0 1-12 0zM12 18v4"/>',
4769
+ // v5.3.2 — 33 more, first used on the Sri Yagnavalkya Jayanti site
4770
+ home: '<path d="M3 11 12 4l9 7"/><path d="M5 10v10h5v-6h4v6h5V10"/>',
4771
+ menu: '<path d="M4 6h16M4 12h16M4 18h16"/>',
4772
+ book: '<path d="M12 6c-2-1.5-5-2-8-2v14c3 0 6 .5 8 2 2-1.5 5-2 8-2V4c-3 0-6 .5-8 2zM12 6v14"/>',
4773
+ calendar: '<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4M7.5 14h2M14.5 14h2M7.5 17.5h2"/>',
4774
+ clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
4775
+ person: '<circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/>',
4776
+ users: '<circle cx="9" cy="8" r="3.5"/><path d="M2 20a7 7 0 0 1 14 0"/><path d="M16 4.5a3.5 3.5 0 0 1 0 7M18 13.5a7 7 0 0 1 4 6.5"/>',
4777
+ globe: '<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/>',
4778
+ lamp: '<path d="M12 3c2 2.6 3 4.3 3 6a3 3 0 0 1-6 0c0-1.7 1-3.4 3-6z"/><path d="M3 14h18c0 3.5-4 6-9 6s-9-2.5-9-6z"/><path d="M12 12v2"/>',
4779
+ play: '<circle cx="12" cy="12" r="9"/><path d="M10 8.5v7l6-3.5z"/>',
4780
+ pause: '<circle cx="12" cy="12" r="9"/><path d="M10 9v6M14 9v6"/>',
4781
+ send: '<path d="M21 3 3 10.5l7 3 3 7.5z"/><path d="m10 13.5 4.5-4.5"/>',
4782
+ share: '<circle cx="18" cy="5" r="2.5"/><circle cx="6" cy="12" r="2.5"/><circle cx="18" cy="19" r="2.5"/><path d="m8.2 10.8 7.6-4.6M8.2 13.2l7.6 4.6"/>',
4783
+ gift: '<rect x="3" y="8" width="18" height="4" rx="1"/><path d="M5 12v9h14v-9M12 8v13M12 8C10.5 5 7 4 7 6.5S10 8 12 8zm0 0c1.5-3 5-4 5-1.5S14 8 12 8z"/>',
4784
+ image: '<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8.5" cy="9.5" r="1.5"/><path d="m21 15-5-5-10 10"/>',
4785
+ file: '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5M9 13h6M9 17h6"/>',
4786
+ external: '<path d="M14 4h6v6M20 4l-9 9"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>',
4787
+ arrowUp: '<path d="M12 19V5m-6 6 6-6 6 6"/>',
4788
+ arrowLeft: '<path d="M19 12H5m6-6-6 6 6 6"/>',
4789
+ arrowRight: '<path d="M5 12h14m-6-6 6 6-6 6"/>',
4790
+ headphones: '<path d="M4 15v-3a8 8 0 0 1 16 0v3"/><rect x="3" y="14" width="4" height="7" rx="1.5"/><rect x="17" y="14" width="4" height="7" rx="1.5"/>',
4791
+ music: '<path d="M9 18V5l11-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="17" cy="16" r="3"/>',
4792
+ pin: '<path d="M12 21s7-6.2 7-11.5a7 7 0 0 0-14 0C5 14.8 12 21 12 21z"/><circle cx="12" cy="9.5" r="2.5"/>',
4793
+ map: '<path d="M3 6l6-3 6 3 6-3v15l-6 3-6-3-6 3z"/><path d="M9 3v15M15 6v15"/>',
4794
+ pen: '<path d="M12 3 6 9l1.5 8h9L18 9z"/><path d="M12 3v7.5"/><circle cx="12" cy="12" r="1.5"/><path d="M8 21h8"/>',
4795
+ wallet: '<rect x="3" y="7" width="18" height="13" rx="2"/><path d="M16 13.5h2M6 7l9-4 2 4"/>',
4796
+ install: '<rect x="6" y="2" width="12" height="20" rx="2"/><path d="M12 6v8m-3-3 3 3 3-3M10 18h4"/>',
4797
+ help: '<circle cx="12" cy="12" r="9"/><path d="M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.6.3-1 .9-1 1.6v.6M12 17h.01"/>',
4798
+ temple: '<path d="M12 2v2M10 4h4l1 3H9zM8 7h8l1.5 4h-11zM6.5 11h11L19 15H5zM4 15h16v6H4z"/><path d="M10 21v-3a2 2 0 0 1 4 0v3"/>',
4799
+ meditate: '<circle cx="12" cy="4.5" r="2.2"/><path d="M12 8c-2.2 0-3.3 1.3-3.7 3.2L7.5 16M12 8c2.2 0 3.3 1.3 3.7 3.2l.8 4.8"/><path d="M4 18c3 2 5.5 2.4 8 1.2 2.5 1.2 5 .8 8-1.2"/><path d="M7.5 16c1.4 1.4 3 2 4.5 2s3.1-.6 4.5-2"/>',
4800
+ trophy: '<path d="M8 4h8v5a4 4 0 0 1-8 0z"/><path d="M8 6H5a3 3 0 0 0 3 4M16 6h3a3 3 0 0 1-3 4M12 13v4M8 21h8M9 17h6"/>',
4801
+ bulb: '<path d="M9 18h6M10 21h4"/><path d="M12 3a6 6 0 0 0-4 10.5c.7.7 1 1.5 1 2.5h6c0-1 .3-1.8 1-2.5A6 6 0 0 0 12 3z"/>',
4802
+ sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2.5M12 19.5V22M4.9 4.9l1.8 1.8M17.3 17.3l1.8 1.8M2 12h2.5M19.5 12H22M4.9 19.1l1.8-1.8M17.3 6.7l1.8-1.8"/>'
4799
4803
  };
4800
- var ICON_NAMES = Object.keys(PATHS);
4801
- function icon(name, size = 20, label) {
4802
- const a11y = label ? `role="img" aria-label="${esc(label)}"` : 'aria-hidden="true" focusable="false"';
4803
- return `<svg ${a11y} width="${Number(size) || 20}" height="${Number(size) || 20}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">${PATHS[name] || PATHS.spark}</svg>`;
4804
+ var ICON_NAMES = Object.keys(ICON_PATHS);
4805
+
4806
+ // src/icons/variant.ts
4807
+ var ICON_MIN_CONTRAST = 3;
4808
+ var WHITE = "#ffffff";
4809
+ var INK = "#111827";
4810
+ function iconContrast(fg, bg) {
4811
+ const a = parseColor(fg);
4812
+ const b = parseColor(bg);
4813
+ return a && b ? contrastRatio(a, b) : null;
4814
+ }
4815
+ function pickGlyphColor(background) {
4816
+ const white = iconContrast(WHITE, background);
4817
+ const ink = iconContrast(INK, background);
4818
+ if (white === null || ink === null) return WHITE;
4819
+ return white >= ink ? WHITE : INK;
4820
+ }
4821
+ var warned = /* @__PURE__ */ new Set();
4822
+ function warnOnce(key, message) {
4823
+ if (warned.has(key)) return;
4824
+ warned.add(key);
4825
+ if (typeof console !== "undefined") console.warn(`[yuktai icons] ${message}`);
4826
+ }
4827
+ function resolveIconColours(options = {}) {
4828
+ const variant = options.variant === "filled" ? "filled" : "outline";
4829
+ const color = options.color || "currentColor";
4830
+ if (variant === "outline") return { variant, color, glyph: color, contrast: null };
4831
+ const glyph = options.glyphColor || pickGlyphColor(color);
4832
+ const contrast = iconContrast(glyph, color);
4833
+ if (contrast !== null && contrast < ICON_MIN_CONTRAST) {
4834
+ warnOnce(`${glyph}|${color}`, `glyph ${glyph} on ${color} is ${contrast}:1. WCAG 1.4.11 needs at least 3:1 for icons.`);
4835
+ }
4836
+ return { variant, color, glyph, contrast };
4837
+ }
4838
+ var FILLED_GLYPH_TRANSFORM = "translate(3.6 3.6) scale(0.7)";
4839
+ var FORCED_COLORS_CSS = "@media (forced-colors: active){.yi-bg{fill:CanvasText}.yi-glyph{stroke:Canvas}}";
4840
+ var escAttr = (v) => String(v).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
4841
+ function iconSvg(name, options = {}) {
4842
+ const paths = ICON_PATHS[name] ?? ICON_PATHS.spark;
4843
+ const size = options.size ?? 20;
4844
+ const { variant, color, glyph } = resolveIconColours(options);
4845
+ const sw = options.strokeWidth ?? (variant === "filled" ? 2.4 : 1.9);
4846
+ const a11y = options.label ? `role="img" aria-label="${escAttr(options.label)}"` : 'aria-hidden="true"';
4847
+ const open = `<svg xmlns="http://www.w3.org/2000/svg" ${a11y} focusable="false" width="${escAttr(size)}" height="${escAttr(size)}" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round" data-variant="${variant}">`;
4848
+ if (variant === "outline") {
4849
+ return `${open}<g stroke="${escAttr(color)}" stroke-width="${sw}">${paths}</g></svg>`;
4850
+ }
4851
+ const bg = options.shape === "circle" ? `<circle class="yi-bg" cx="12" cy="12" r="12" fill="${escAttr(color)}"/>` : `<rect class="yi-bg" width="24" height="24" rx="6" fill="${escAttr(color)}"/>`;
4852
+ return `${open}<style>${FORCED_COLORS_CSS}</style>${bg}<g class="yi-glyph" stroke="${escAttr(glyph)}" stroke-width="${sw}" transform="${FILLED_GLYPH_TRANSFORM}">${paths}</g></svg>`;
4853
+ }
4854
+
4855
+ // vanilla/src/dom.js
4856
+ var isBrowser2 = () => typeof window !== "undefined" && typeof document !== "undefined";
4857
+ function esc(value) {
4858
+ return String(value == null ? "" : value).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
4859
+ }
4860
+ function h(tag, attrs, ...children) {
4861
+ const el = document.createElement(tag);
4862
+ if (attrs) {
4863
+ for (const [key, value] of Object.entries(attrs)) {
4864
+ if (value == null || value === false) continue;
4865
+ if (key.startsWith("on") && typeof value === "function") el.addEventListener(key.slice(2), value);
4866
+ else if (key === "html") el.innerHTML = value;
4867
+ else if (key === "text") el.textContent = value;
4868
+ else if (value === true) el.setAttribute(key, "");
4869
+ else el.setAttribute(key, String(value));
4870
+ }
4871
+ }
4872
+ for (const child of children.flat()) {
4873
+ if (child == null || child === false) continue;
4874
+ el.append(child instanceof Node ? child : document.createTextNode(String(child)));
4875
+ }
4876
+ return el;
4877
+ }
4878
+ var uid = 0;
4879
+ var nextId = (prefix = "yv") => `${prefix}-${++uid}`;
4880
+ function safeColor(value, fallback) {
4881
+ if (typeof value !== "string" || !isBrowser2()) return fallback;
4882
+ const probe = document.createElement("span");
4883
+ probe.style.color = value;
4884
+ return probe.style.color ? value : fallback;
4885
+ }
4886
+ var prefersReducedMotion = () => isBrowser2() && (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches || document.documentElement.hasAttribute("data-yuktai-reduce-motion"));
4887
+ var ICON_NAMES2 = Object.keys(ICON_PATHS);
4888
+ function icon(name, size = 20, label, options) {
4889
+ const opts = typeof size === "object" && size !== null ? { ...size } : { ...options || {}, size, label };
4890
+ return iconSvg(name, opts);
4804
4891
  }
4805
4892
  function iconEl(name, size, label) {
4806
4893
  const span = document.createElement("span");
@@ -4897,8 +4984,8 @@ Missing landmarks: ${missing.join(", ")}.` : ""}`,
4897
4984
  .yv-sev-high{color:#b91c1c}.yv-sev-medium{color:#a16207}.yv-sev-low{color:#475569}
4898
4985
 
4899
4986
  /* themes (shared by grid, boxes and dialogs) */
4900
- .yv-grid,.yv-box,.yv-dialog{--yv-bg:#fff;--yv-surface:#fff;--yv-soft:#f8fafc;--yv-head:#f8fafc;--yv-hover:#f1f5f9;--yv-text:#0f172a;--yv-muted:#475569;--yv-border:#e2e8f0;--yv-border-strong:#94a3b8;--yv-accent-bg:#ccfbf1;--yv-accent-text:#115e59;--yv-hl-bg:#fef3c7;--yv-hl-edge:#b45309;--yv-sel-bg:#f0fdfa}
4901
- .yv-theme-dark{--yv-bg:#0f172a;--yv-surface:#111c33;--yv-soft:#16233f;--yv-head:#16233f;--yv-hover:#1e2d4d;--yv-text:#f1f5f9;--yv-muted:#cbd5e1;--yv-border:#334155;--yv-border-strong:#64748b;--yv-accent-bg:#134e4a;--yv-accent-text:#ccfbf1;--yv-hl-bg:#4a3a0c;--yv-hl-edge:#fbbf24;--yv-sel-bg:#123a3a}
4987
+ .yv-grid,.yv-box,.yv-dialog{--yv-bg:#fff;--yv-surface:#fff;--yv-soft:#f8fafc;--yv-head:#f8fafc;--yv-hover:#f1f5f9;--yv-text:#0f172a;--yv-muted:#475569;--yv-border:#e2e8f0;--yv-border-strong:#94a3b8;--yv-accent-bg:#E8F1F8;--yv-accent-text:#003F6E;--yv-hl-bg:#fef3c7;--yv-hl-edge:#b45309;--yv-sel-bg:#EEF5FB}
4988
+ .yv-theme-dark{--yv-bg:#0f172a;--yv-surface:#111c33;--yv-soft:#16233f;--yv-head:#16233f;--yv-hover:#1e2d4d;--yv-text:#f1f5f9;--yv-muted:#cbd5e1;--yv-border:#334155;--yv-border-strong:#64748b;--yv-accent-bg:#0B2A4A;--yv-accent-text:#DCEBFA;--yv-hl-bg:#4a3a0c;--yv-hl-edge:#fbbf24;--yv-sel-bg:#12304f}
4902
4989
  .yv-theme-high-contrast{--yv-bg:#fff;--yv-surface:#fff;--yv-soft:#fff;--yv-head:#fff;--yv-hover:#e5e5e5;--yv-text:#000;--yv-muted:#000;--yv-border:#000;--yv-border-strong:#000;--yv-accent-bg:#000;--yv-accent-text:#fff;--yv-hl-bg:#ffff00;--yv-hl-edge:#000;--yv-sel-bg:#e5e5e5}
4903
4990
  .yv-theme-high-contrast.yv-grid,.yv-theme-high-contrast .yv-btn,.yv-theme-high-contrast .yv-input{border-width:2px}
4904
4991
  .yv-theme-high-contrast .yv-btn.yv-primary{background:#000;border-color:#000}
@@ -4984,7 +5071,8 @@ Missing landmarks: ${missing.join(", ")}.` : ""}`,
4984
5071
  /* floating buttons and dialogs */
4985
5072
  .yv-fabs{position:fixed;bottom:max(16px,env(safe-area-inset-bottom));${side}:16px;display:flex;flex-direction:column;gap:10px;z-index:2147483000}
4986
5073
  .yv-fab{width:56px;height:56px;display:grid;place-items:center;border:2px solid #fff;border-radius:50%;background:var(--yv-primary);color:#fff;box-shadow:0 4px 16px rgba(0,0,0,.25);cursor:pointer}
4987
- .yv-fab.yv-fab-alt{background:#1e293b}
5074
+ .yv-fab.yv-fab-alt{background:#fff;color:var(--yv-primary);border-color:var(--yv-primary)}
5075
+ .yv-fab:hover{filter:brightness(1.08)}
4988
5076
  .yv-dialog{position:fixed;bottom:max(90px,calc(env(safe-area-inset-bottom) + 84px));${side}:12px;width:min(440px,calc(100vw - 24px));max-height:calc(100dvh - 110px);overflow:auto;background:var(--yv-bg);color:var(--yv-text);border:1px solid var(--yv-border);border-radius:16px;box-shadow:0 12px 40px rgba(15,23,42,.25);z-index:2147483001;font:15px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
4989
5077
  .yv-dialog.yv-wide{width:min(620px,calc(100vw - 24px))}
4990
5078
  .yv-dialog-head{position:sticky;top:0;z-index:1;display:flex;justify-content:space-between;align-items:flex-start;gap:10px;padding:14px 16px;border-bottom:1px solid var(--yv-border);background:var(--yv-bg)}
@@ -6494,7 +6582,7 @@ ${command ? command.say : "Not a voice command. Try: make text bigger, high cont
6494
6582
  function createWidget(userConfig = {}) {
6495
6583
  const cfg = { position: "left", title: "Accessibility", showPageTools: false, ...userConfig };
6496
6584
  const side = cfg.position === "right" ? "right" : "left";
6497
- const primary = safeColor(cfg.primaryColor, "#0f766e");
6585
+ const primary = safeColor(cfg.primaryColor, "#005A9C");
6498
6586
  const base = {
6499
6587
  enabled: true,
6500
6588
  autoFix: true,
@@ -6721,12 +6809,12 @@ ${command ? command.say : "Not a voice command. Try: make text bigger, high cont
6721
6809
  }
6722
6810
 
6723
6811
  // vanilla/src/index.js
6724
- var VERSION = "5.2.0";
6812
+ var VERSION = "5.3.0";
6725
6813
  (function boot() {
6726
6814
  if (!isBrowser2()) return;
6727
6815
  if (window.yuktai && window.yuktai.__vanillaInitialized) return;
6728
6816
  const cfg = window.YUKTAI_CONFIG || {};
6729
- const primary = safeColor(cfg.primaryColor, "#0f766e");
6817
+ const primary = safeColor(cfg.primaryColor, "#005A9C");
6730
6818
  const side = cfg.position === "right" ? "right" : "left";
6731
6819
  const widget = createWidget(cfg);
6732
6820
  const ensureStyles = () => addStyles(primary, side);
@@ -6765,7 +6853,9 @@ ${command ? command.say : "Not a voice command. Try: make text bigger, high cont
6765
6853
  // <YuktaiGridWebMCP>
6766
6854
  // ── icons ──
6767
6855
  icon,
6768
- iconNames: ICON_NAMES.slice(),
6856
+ iconNames: ICON_NAMES2.slice(),
6857
+ iconContrast,
6858
+ pickGlyphColor,
6769
6859
  // ── everything from the package core (same code as npm) ──
6770
6860
  ...core_exports,
6771
6861
  // ── v5.1 vanilla compatibility ──