@cyberskill/design 1.2.0 → 1.3.9

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 (232) hide show
  1. package/DESIGN.md +11 -9
  2. package/README.md +28 -24
  3. package/SKILL.md +18 -9
  4. package/THIRD-PARTY-NOTICES.md +35 -0
  5. package/VERSION +1 -1
  6. package/_ds_bundle.js +701 -1342
  7. package/_esm/cs.mjs +2 -2
  8. package/_esm/react.mjs +6 -1
  9. package/_vendor/NOTICE.md +16 -0
  10. package/_vendor/babel/README.md +7 -0
  11. package/_vendor/babel/babel.min.js +4 -0
  12. package/_vendor/react/README.md +3 -0
  13. package/_vendor/react/react-dom.production.min.js +1 -0
  14. package/_vendor/react/react.production.min.js +1 -0
  15. package/assets/aurora-hoa.webp +0 -0
  16. package/assets/aurora-kim.webp +0 -0
  17. package/assets/aurora-moc.webp +0 -0
  18. package/assets/aurora-tho.webp +0 -0
  19. package/assets/aurora-thuy.webp +0 -0
  20. package/base/ai.css +8 -3
  21. package/base/collections.css +10 -4
  22. package/base/components.css +5 -2
  23. package/base/controls.css +58 -21
  24. package/base/data.css +1 -1
  25. package/base/feedback.css +9 -2
  26. package/base/forms.css +1 -1
  27. package/base/glass.css +1 -1
  28. package/base/interaction.css +6 -2
  29. package/base/navigation.css +1 -1
  30. package/base/reset.css +7 -1
  31. package/base/responsive.css +13 -12
  32. package/base/shell.css +8 -2
  33. package/components/_i18n/i18n.js +28 -3
  34. package/components/_i18n/strings.js +42 -24
  35. package/components/ai/ChatMessage.d.ts +2 -0
  36. package/components/ai/ChatMessage.jsx +8 -4
  37. package/components/ai/ConfidenceMeter.d.ts +2 -0
  38. package/components/ai/HumanReviewGate.d.ts +2 -0
  39. package/components/ai/PromptInput.d.ts +2 -0
  40. package/components/ai/TypingIndicator.d.ts +2 -0
  41. package/components/ai/ai-conversation.card.html +3 -3
  42. package/components/ai/ai-lumi.card.html +3 -3
  43. package/components/ai/ai.card.html +3 -3
  44. package/components/brand/brand.card.html +3 -3
  45. package/components/button/buttons.card.html +3 -3
  46. package/components/data/Image.jsx +18 -2
  47. package/components/data/Sortable.d.ts +5 -1
  48. package/components/data/Sortable.jsx +42 -9
  49. package/components/data/Sortable.prompt.md +4 -2
  50. package/components/data/Terminal.jsx +3 -2
  51. package/components/data/Tooltip.d.ts +2 -0
  52. package/components/data/Tooltip.jsx +6 -3
  53. package/components/data/Tree.d.ts +2 -1
  54. package/components/data/Tree.jsx +137 -16
  55. package/components/data/Tree.prompt.md +1 -1
  56. package/components/data/collections.card.html +3 -3
  57. package/components/data/data.card.html +3 -3
  58. package/components/datatable/DataTable.d.ts +24 -1
  59. package/components/datatable/DataTable.jsx +78 -5
  60. package/components/datatable/DataTable.prompt.md +2 -1
  61. package/components/datatable/datatable.card.html +20 -16
  62. package/components/dialog/dialog.card.html +3 -3
  63. package/components/feedback/Alert.d.ts +2 -0
  64. package/components/feedback/Alert.jsx +8 -2
  65. package/components/feedback/EmptyState.d.ts +2 -0
  66. package/components/feedback/EmptyState.jsx +6 -3
  67. package/components/feedback/ProgressBar.d.ts +2 -0
  68. package/components/feedback/ProgressBar.jsx +6 -3
  69. package/components/feedback/Toast.jsx +15 -1
  70. package/components/feedback/Toast.prompt.md +2 -1
  71. package/components/feedback/feedback-2.card.html +3 -3
  72. package/components/feedback/feedback.card.html +3 -3
  73. package/components/forms/Cascader.jsx +7 -2
  74. package/components/forms/FileUpload.d.ts +2 -0
  75. package/components/forms/Form.d.ts +3 -1
  76. package/components/forms/Form.jsx +9 -1
  77. package/components/forms/Form.prompt.md +1 -1
  78. package/components/forms/Mentions.d.ts +1 -1
  79. package/components/forms/Mentions.jsx +59 -7
  80. package/components/forms/Mentions.prompt.md +3 -1
  81. package/components/forms/Rating.d.ts +3 -2
  82. package/components/forms/Rating.jsx +34 -3
  83. package/components/forms/Rating.prompt.md +1 -1
  84. package/components/forms/Select.d.ts +2 -0
  85. package/components/forms/Select.jsx +6 -2
  86. package/components/forms/Slider.d.ts +2 -0
  87. package/components/forms/Slider.jsx +5 -2
  88. package/components/forms/Switch.d.ts +2 -0
  89. package/components/forms/Switch.jsx +7 -9
  90. package/components/forms/Toggle.d.ts +2 -0
  91. package/components/forms/Toggle.jsx +14 -2
  92. package/components/forms/forms-2.card.html +3 -3
  93. package/components/forms/forms.card.html +3 -3
  94. package/components/icon/Icon.d.ts +1 -1
  95. package/components/icon/Icon.prompt.md +1 -1
  96. package/components/icon/icon.card.html +3 -3
  97. package/components/logo/logo.card.html +3 -3
  98. package/components/navigation/CommandPalette.d.ts +2 -0
  99. package/components/navigation/Menu.d.ts +2 -0
  100. package/components/navigation/Menu.jsx +60 -6
  101. package/components/navigation/Steps.d.ts +2 -0
  102. package/components/navigation/Steps.jsx +6 -3
  103. package/components/navigation/Tabs.d.ts +2 -0
  104. package/components/navigation/Tabs.jsx +6 -3
  105. package/components/navigation/Toolbar.d.ts +3 -2
  106. package/components/navigation/Toolbar.jsx +117 -11
  107. package/components/navigation/Toolbar.prompt.md +1 -1
  108. package/components/navigation/navigation.card.html +3 -3
  109. package/components/navigation/shell.card.html +3 -3
  110. package/components/overlays/ContextMenu.d.ts +2 -0
  111. package/components/overlays/ContextMenu.jsx +6 -3
  112. package/components/overlays/Drawer.d.ts +2 -0
  113. package/components/overlays/alertdialog.card.html +3 -3
  114. package/components/overlays/overlays.card.html +3 -3
  115. package/components/textfield/textfield.card.html +3 -3
  116. package/dist/styles.min.css +312 -64
  117. package/docs/benchmark-rubric.md +2 -2
  118. package/docs/ci-cd.md +18 -13
  119. package/docs/consumer-grant.md +4 -1
  120. package/docs/consuming.md +16 -12
  121. package/docs/contrast-report.md +81 -1
  122. package/docs/conventions.md +18 -5
  123. package/docs/decisions.md +41 -3
  124. package/docs/deploy.md +19 -6
  125. package/docs/design-styles.md +2 -2
  126. package/docs/doctrine.md +51 -0
  127. package/docs/figma.md +2 -0
  128. package/docs/live-hub.md +3 -1
  129. package/docs/products.md +1 -1
  130. package/docs/quality-gates.md +24 -12
  131. package/docs/release-notes.md +1 -1
  132. package/docs/release-runbook.md +78 -0
  133. package/docs/storybook.md +3 -3
  134. package/docs/sync.md +8 -8
  135. package/docs/template-schema-v2.md +1 -1
  136. package/docs/vi/benchmark-rubric.md +2 -2
  137. package/docs/vi/ci-cd.md +17 -14
  138. package/docs/vi/consumer-grant.md +4 -1
  139. package/docs/vi/consuming.md +16 -12
  140. package/docs/vi/contrast-report.md +81 -1
  141. package/docs/vi/conventions.md +19 -6
  142. package/docs/vi/decisions.md +40 -3
  143. package/docs/vi/deploy.md +19 -6
  144. package/docs/vi/design-styles.md +1 -1
  145. package/docs/vi/doctrine.md +51 -0
  146. package/docs/vi/figma.md +2 -0
  147. package/docs/vi/live-hub.md +3 -1
  148. package/docs/vi/products.md +1 -1
  149. package/docs/vi/quality-gates.md +23 -12
  150. package/docs/vi/release-notes.md +1 -1
  151. package/docs/vi/release-runbook.md +78 -0
  152. package/docs/vi/storybook.md +3 -3
  153. package/docs/vi/sync.md +7 -7
  154. package/docs/vi/template-schema-v2.md +1 -1
  155. package/docs/viewer.html +16 -5
  156. package/fonts/OFL.txt +98 -0
  157. package/guidelines/atomic-view.html +2 -2
  158. package/guidelines/brand-imagery.html +1 -1
  159. package/guidelines/elements-variants.html +5 -5
  160. package/guidelines/spacing-breakpoints.html +2 -1
  161. package/guidelines/surfaces-glass.html +1 -1
  162. package/llms.txt +18 -11
  163. package/package.json +28 -8
  164. package/templates/app-shell/AppShell.dc.html +3 -3
  165. package/templates/app-shell/content-schema.json +7 -0
  166. package/templates/article/Article.dc.html +3 -3
  167. package/templates/article/content-schema.json +7 -0
  168. package/templates/auth/Auth.dc.html +26 -10
  169. package/templates/auth/content-schema.json +25 -0
  170. package/templates/dashboard/Dashboard.dc.html +4 -4
  171. package/templates/dashboard/content-schema.json +7 -0
  172. package/templates/delivery-change-note/DeliveryChangeNote.dc.html +2 -2
  173. package/templates/delivery-runbook/DeliveryRunbook.dc.html +2 -2
  174. package/templates/email-safe/email-announcement.html +2 -2
  175. package/templates/email-safe/email-dunning.html +2 -2
  176. package/templates/email-safe/email-investor.html +2 -2
  177. package/templates/email-safe/email-launch.html +2 -2
  178. package/templates/email-safe/email-newsletter.html +2 -2
  179. package/templates/email-safe/email-status.html +2 -2
  180. package/templates/email-safe/email-transactional.html +2 -2
  181. package/templates/finance-dunning-email/FinanceDunningEmail.dc.html +2 -2
  182. package/templates/finance-expense-report/FinanceExpenseReport.dc.html +2 -2
  183. package/templates/finance-invoice/FinanceInvoice.dc.html +2 -2
  184. package/templates/finance-quote/FinanceQuote.dc.html +4 -4
  185. package/templates/hr-interview-kit/HrInterviewKit.dc.html +1 -1
  186. package/templates/marketing-case-study/MarketingCaseStudy.dc.html +1 -1
  187. package/templates/marketing-page/MarketingPage.dc.html +3 -3
  188. package/templates/marketing-page/content-schema.json +1 -0
  189. package/templates/settings/Settings.dc.html +3 -3
  190. package/templates/settings/content-schema.json +7 -0
  191. package/tokens/element-seeds.json +2 -2
  192. package/tokens/elements.css +193 -19
  193. package/tokens/native/CSTokens.kt +3 -2
  194. package/tokens/native/CSTokens.swift +3 -2
  195. package/tokens/native/cs_tokens.dart +3 -2
  196. package/tokens/provenance.json +8 -8
  197. package/tokens/spacing.css +2 -1
  198. package/tokens/tokens.dtcg.json +19 -14
  199. package/tokens/tokens.js +16 -15
  200. package/tokens/tokens.json +15 -14
  201. package/ui_kits/deck/index.html +1 -1
  202. package/ui_kits/status-hub/StatusHub.jsx +1 -1
  203. package/ui_kits/status-hub/login.html +52 -10
  204. package/ui_kits/status-hub/project.html +1 -1
  205. package/ui_kits/status-hub/settings.html +1 -1
  206. package/ui_kits/website/Website.jsx +1 -1
  207. package/ui_kits/website/careers.html +2 -2
  208. package/ui_kits/website/chat.html +1 -1
  209. package/ui_kits/website/site.css +1 -1
  210. package/ui_kits/website/work.html +2 -2
  211. package/assets/aurora-hoa.png +0 -0
  212. package/assets/aurora-kim.png +0 -0
  213. package/assets/aurora-moc.png +0 -0
  214. package/assets/aurora-thuy.png +0 -0
  215. package/docs/decisions/container-query-migration.md +0 -18
  216. package/docs/decisions/overlay-manager-design.md +0 -101
  217. package/docs/plans/cyberskill-design-system-audit-and-12-month-evolution-plan.md +0 -1490
  218. package/docs/status/assets/favicon.svg +0 -5
  219. package/docs/status/assets/status-legacy.css +0 -399
  220. package/docs/status/assets/status-legacy.js +0 -505
  221. package/docs/status/assets/status.css +0 -644
  222. package/docs/status/assets/status.js +0 -1308
  223. package/docs/status/data/status-feed.json +0 -1
  224. package/docs/status/data/task/TASK-REL-001.js +0 -1
  225. package/docs/status/data/task/TASK-REL-002.js +0 -1
  226. package/docs/status/index.html +0 -46
  227. package/docs/status/roadmap.html +0 -4
  228. package/docs/status/status-legacy.html +0 -122
  229. package/docs/tasks/BACKLOG.md +0 -26
  230. package/docs/tasks/_state/commit-links.yaml +0 -14
  231. package/docs/tasks/release/TASK-REL-001-launch-1-1-0/spec.md +0 -55
  232. package/docs/tasks/release/TASK-REL-002-republish-1-1-1/spec.md +0 -54
package/llms.txt CHANGED
@@ -1,20 +1,23 @@
1
1
  # CyberSkill Design System
2
2
 
3
- > HTML-first, Vietnamese-first design system, LAUNCHED **v1.1.0**; current **v1.1.1**. Immutable anchors: Umber #45210E · Ochre #F4BA17. Four independent axes — Theme (light · dark · system) × Element (Ngũ Hành, 15 variants) × Language (EN · VI) × Style (`data-cs-style` — sole pack **liquid-glass**; absent ≡ default). It ships 106 components / 122 exports, 84 copyable templates (EN·VN for emails + team/legal/finance; client/media collateral EN-first), and tokens in CSS + JSON + ESM + W3C DTCG + native mirrors. This file is the agent/tool front door: it maps each audience to the format it should consume. npm package name is **`@cyberskill/design`** (not `@cyberskill/react`). Release signal: tip SHA + [docs/release-notes.md](docs/release-notes.md) — VERSION **1.1.1**, no CHANGELOG.
3
+ > HTML-first, Vietnamese-first design system, LAUNCHED **v1.1.0**; current **v1.3.9**. Immutable anchors: Umber #45210E · Ochre #F4BA17. Four independent axes — Theme (light · dark · system) × Element (Ngũ Hành, 15 variants) × Language (EN · VI) × Style (`data-cs-style` — sole pack **liquid-glass**; absent ≡ default). It ships 106 components / 122 exports, 84 copyable templates (EN·VN for emails + team/legal/finance; client/media collateral EN-first), and tokens in CSS + JSON + ESM + W3C DTCG + native mirrors. This file is the agent/tool front door: it maps each **capability** to the format it should consume. npm package name is **`@cyberskill/design`** (not `@cyberskill/react`). Release signal: tip SHA + [docs/release-notes.md](docs/release-notes.md) — VERSION **1.3.9**, no CHANGELOG.
4
4
 
5
- The portable contract covers browser and bundler entry points: `styles.css` · `_esm/cs.mjs` legacy · `_ds_bundle.js` · tokens work without a build step; `_esm/react.mjs` is bundler-native JSX (Next/Vite must transpile — not a bare-browser import). The DC layer (`templates/**/*.dc.html`) is **Claude Design / authoring-environment-only**. **Google Stitch must not treat `*.dc.html` as source of truth** — use `DESIGN.md`, DTCG, `styles.css`, kitchen-sink, and `.cs-*` static HTML instead. Dual path: [docs/consuming.md](docs/consuming.md). The full non-negotiables list lives in `CONTRIBUTING.md` and `docs/conventions.md`.
5
+ The portable contract covers browser and bundler entry points: `styles.css` · `_esm/cs.mjs` legacy · `_ds_bundle.js` · tokens work without a build step; `_esm/react.mjs` is bundler-native JSX (Next/Vite must transpile — not a bare-browser import). The DC layer (`templates/**/*.dc.html`) needs a **Design Components compiler**. **Google Stitch must not treat `*.dc.html` as source of truth** — use `DESIGN.md`, DTCG, `styles.css`, kitchen-sink, and `.cs-*` static HTML instead. Dual path: [docs/consuming.md](docs/consuming.md). The full non-negotiables list lives in `CONTRIBUTING.md` and `docs/doctrine.md`. Operator docs are published on the Storybook **Docs** sidebar at `design.cyberskill.world`.
6
6
 
7
7
  ## Consume by audience
8
8
 
9
- - **Claude Code** → [SKILL.md](SKILL.md) first, then [README.md](README.md): compose with `styles.css` + bundler `_esm/react.mjs` / legacy `_esm/cs.mjs` / `_ds_bundle.js` (prefix resolve). Optional DC templates only when the session has the compiler. Gates/`_audit/` need a full clone.
10
- - **Google Stitch** → [DESIGN.md](DESIGN.md) first, then this file + [tokens/tokens.dtcg.json](tokens/tokens.dtcg.json): generate **static** UI. Do **not** open or rewrite `templates/**/*.dc.html` as if they were plain HTML.
9
+ - **DC-capable authoring** (compiler present — e.g. Claude Design, Claude Code with a full clone) → [SKILL.md](SKILL.md) first, then [README.md](README.md): compose with `styles.css` + bundler `_esm/react.mjs` / legacy `_esm/cs.mjs` / `_ds_bundle.js` (prefix resolve). Optional DC templates only when the session has the compiler. Gates/`_audit/` need a full clone.
10
+ - **Static / open-spec** (e.g. Google Stitch) → [DESIGN.md](DESIGN.md) first, then this file + [tokens/tokens.dtcg.json](tokens/tokens.dtcg.json): generate **static** UI. Do **not** open or rewrite `templates/**/*.dc.html` as if they were plain HTML.
11
+ - **Designers** → Storybook at `design.cyberskill.world` (`/` / `npm run storybook`): Foundations, Components, Docs, axis toolbar.
12
+ - **Implementers (npm / Next / Vite)** → [docs/consuming.md](docs/consuming.md) + default `@cyberskill/design` React entry.
13
+ - **Maintainers** → [docs/doctrine.md](docs/doctrine.md) + [docs/quality-gates.md](docs/quality-gates.md).
11
14
  - [styles.css](styles.css): any web / framework / email (Vue, Svelte, plain HTML, React) — link this one file for every `--cs-*` token, the `.cs-*` component classes, and the Liquid Glass surfaces. Framework-agnostic, no build step.
12
15
  - [_esm/react.mjs](_esm/react.mjs): **default** npm entry — `import { Button, … } from "@cyberskill/design"` (React peer; Next/Vite transpile). Also `@cyberskill/design/react`.
13
16
  - [_esm/cs.mjs](_esm/cs.mjs): browser / no-build legacy — `import { Button, … } from "@cyberskill/design/legacy"` (self-ensures React; still link `styles.css`). **Not for Next/SSR.**
14
17
  - [_ds_bundle.js](_ds_bundle.js): React (production) — `styles.css` + `<script src="_ds_bundle.js">`, then resolve the bundle global by prefix (its `_<projectId>` suffix is compiler-assigned and changes on re-import): `const CS = window[Object.keys(window).find(k => /^CyberSkillDesignSystem_/.test(k))]; const { Button, … } = CS`. Proven green by `_audit/consumer-smoke-test.html`. (Templates load via `ds-base.js`, which publishes a stable `window.CyberSkillDS` alias for the same reason.)
15
18
  - [tokens/tokens.json](tokens/tokens.json): design tools / native / token pipelines / other agents (Figma, Google Stitch, Style Dictionary) — every token grouped by category, plus theme/element override maps; also [tokens/tokens.js](tokens/tokens.js) (ESM) and [tokens/tokens.dtcg.json](tokens/tokens.dtcg.json) (**W3C DTCG** format for Tokens Studio for Figma / Style Dictionary). This is the canonical cross-tool contract.
16
19
  - [DESIGN.md](DESIGN.md): agents/tools that read a root DESIGN.md (Google Stitch open-spec style) — **this repo's** generated file with the brand doctrine, every token value (light + dark), and the component/template inventory. Generated from the DTCG source by `npm run build:design-md`; the `design-md-parity` gate pins it — do not hand-edit.
17
- - [templates/](templates/): Design Components (`templates/**/*.dc.html`) + the compiler (tweaks, `x-import`, `@dsCard` cards) for **Claude Design** only. Stitch and other non-DC tools: [templates/kitchen-sink.html](templates/kitchen-sink.html) + `.cs-*` / [examples/static-hello/](examples/static-hello/) — **not** `*.dc.html`.
20
+ - [templates/](templates/): Design Components (`templates/**/*.dc.html`) + the compiler (tweaks, `x-import`, `@dsCard` cards) for **DC-capable authoring** only. Stitch and other non-DC tools: [templates/kitchen-sink.html](templates/kitchen-sink.html) + `.cs-*` / [examples/static-hello/](examples/static-hello/) — **not** `*.dc.html`.
18
21
 
19
22
  ## Key files
20
23
 
@@ -28,7 +31,7 @@ The portable contract covers browser and bundler entry points: `styles.css` · `
28
31
  - [templates/](templates/): 84 copyable starting points (`.dc.html`); `email-safe/` send-ready emails; `kitchen-sink.html`; `playground.html`
29
32
  - [guidelines/](guidelines/): specimen cards + `atomic-view.html` (portable component grid) — host product surface is Storybook at `/`
30
33
  - [examples/static-hello/](examples/static-hello/): zero-install static consumer (Stitch / plain HTML)
31
- - [examples/npm-hello/](examples/npm-hello/): first registry **browser** consumer — `@cyberskill/design@1.1.1` legacy entry (Lumi · Hỏa · plasma); Status Hub teams swap to `thuy`; Next/SSR apps use default `@cyberskill/design` React entry instead
34
+ - [examples/npm-hello/](examples/npm-hello/): first registry **browser** consumer — `@cyberskill/design@1.3.9` legacy entry (Lumi · Hỏa · plasma); Status Hub teams swap to `thuy`; Next/SSR apps use default `@cyberskill/design` React entry instead
32
35
  - [examples/native/](examples/native/): SwiftUI / Compose / Flutter sample apps + store scaffolds — Compose is **Android Studio**-first (no committed `gradlew`; see `examples/native/README.md`)
33
36
  - [ui_kits/](ui_kits/): product recreations — status-hub · website · deck
34
37
  - [fonts/](fonts/): self-hosted Be Vietnam Pro (UI) + Space Grotesk (opt-in display, `--cs-font-family-display`) + JetBrains Mono woff2
@@ -37,12 +40,15 @@ The portable contract covers browser and bundler entry points: `styles.css` · `
37
40
  - [DESIGN.md](DESIGN.md): generated open-spec surface (doctrine + tokens + inventory) for Stitch-style consumers — pinned to DTCG by the design-md-parity gate
38
41
  - [SKILL.md](SKILL.md): agent entry — hard rules + fast orientation
39
42
  - [CONTRIBUTING.md](CONTRIBUTING.md): one-page onboarding for extending the system
40
- - [VERSION](VERSION): pinned 1.1.1 (matches [package.json](package.json))
43
+ - [docs/doctrine.md](docs/doctrine.md): expansion rule, verification depth, immutables
44
+ - [VERSION](VERSION): pinned 1.3.9 (matches [package.json](package.json))
41
45
 
42
46
  ## Docs — consumers
43
47
 
44
- - [docs/consuming.md](docs/consuming.md): adopting & upgrading, Claude Code vs Stitch dual path, package name `@cyberskill/design`; first registry install at [examples/npm-hello/](examples/npm-hello/) (Lumi · Hỏa · plasma); native mirrors at [examples/native/](examples/native/) (SwiftUI / Compose / Flutter samples)
45
- - [docs/release-notes.md](docs/release-notes.md): curated product highlights (not a changelog) — includes `@cyberskill/design@1.1.1` Trusted Publishing / grant pointer
48
+ Published on Storybook **Docs** (`design.cyberskill.world`) and in [docs/viewer.html](docs/viewer.html) (EN|VI).
49
+
50
+ - [docs/consuming.md](docs/consuming.md): adopting & upgrading, DC-capable vs static dual path, package name `@cyberskill/design`; first registry install at [examples/npm-hello/](examples/npm-hello/) (Lumi · Hỏa · plasma); native mirrors at [examples/native/](examples/native/) (SwiftUI / Compose / Flutter samples)
51
+ - [docs/release-notes.md](docs/release-notes.md): curated product highlights (not a changelog) — includes `@cyberskill/design@1.3.9` Trusted Publishing / grant pointer
46
52
  - [docs/consumer-grant.md](docs/consumer-grant.md): written npm grant (CyberSkill portfolio; UNLICENSED)
47
53
  - [docs/conventions.md](docs/conventions.md): authoring rules (naming grammar, the four axes)
48
54
  - [docs/design-styles.md](docs/design-styles.md): the live styling axes and the fixed liquid-glass treatment
@@ -51,13 +57,14 @@ The portable contract covers browser and bundler entry points: `styles.css` · `
51
57
  - [docs/template-schema-v2.md](docs/template-schema-v2.md): typed content-slot spec for templates
52
58
  - [docs/deploy.md](docs/deploy.md): deploying the live site (Vercel / VPS / any static host)
53
59
  - [docs/live-hub.md](docs/live-hub.md): Storybook at `/` is the product surface; surface map
54
- - [docs/storybook.md](docs/storybook.md): host Storybook product surface at `/`
60
+ - [docs/storybook.md](docs/storybook.md): host Storybook product surface at `/` — Docs sidebar is the published reading surface for operator docs
55
61
  - [docs/figma.md](docs/figma.md): Figma / Tokens Studio import recipe
56
62
  - [docs/vi/](docs/vi/): Vietnamese mirror of every operator-facing `docs/*.md`
57
63
  - [docs/viewer.html](docs/viewer.html): reader UI (EN|VI toggle for `docs/*.md`)
58
64
 
59
65
  ## Docs — maintainers
60
66
 
67
+ - [docs/doctrine.md](docs/doctrine.md): expansion, verification, immutables
61
68
  - [docs/decisions.md](docs/decisions.md): recorded owner decisions + open maintainer tasks
62
69
  - [docs/ci-cd.md](docs/ci-cd.md): CI/CD automation — workflow, headless gate runner, provenance pre-check
63
70
  - [docs/quality-gates.md](docs/quality-gates.md): benchmark reference — every quality gate, its assertion, pass criterion, where it runs
@@ -66,7 +73,7 @@ The portable contract covers browser and bundler entry points: `styles.css` · `
66
73
 
67
74
  ## Non-negotiables
68
75
 
69
- - [CONTRIBUTING.md](CONTRIBUTING.md): the full non-negotiables list (with `docs/conventions.md`)
76
+ - [CONTRIBUTING.md](CONTRIBUTING.md): the full non-negotiables list (with `docs/doctrine.md` and `docs/conventions.md`)
70
77
  - [tokens/colors.css](tokens/colors.css): immutable Umber/Ochre anchors · one accent per surface
71
78
  - [base/a11y.css](base/a11y.css): APCA Lc ≥ 75 body text · ≥44px touch targets · 3px ochre focus ring · honour `prefers-reduced-motion`/`prefers-contrast`
72
79
  - [docs/conventions.md](docs/conventions.md): Vietnamese-first bilingual — every UI string ships an EN + VN pair, diacritics preserved
package/package.json CHANGED
@@ -1,16 +1,24 @@
1
1
  {
2
2
  "name": "@cyberskill/design",
3
- "version": "1.2.0",
3
+ "version": "1.3.9",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/cyberskill-official/design-system.git"
10
+ "url": "git+https://github.com/cyberskill-official/design.git"
11
11
  },
12
12
  "module": "_esm/react.mjs",
13
13
  "types": "_esm/react.d.ts",
14
+ "sideEffects": [
15
+ "**/*.css",
16
+ "./_ds_bundle.js",
17
+ "./_esm/cs.mjs",
18
+ "./image-slot.js",
19
+ "./ui_kits/**/*.js",
20
+ "./ui_kits/**/*.jsx"
21
+ ],
14
22
  "exports": {
15
23
  ".": {
16
24
  "types": "./_esm/react.d.ts",
@@ -23,6 +31,7 @@
23
31
  "./legacy": {
24
32
  "import": "./_esm/cs.mjs"
25
33
  },
34
+ "./components/*": "./components/*",
26
35
  "./styles.css": "./styles.css",
27
36
  "./tokens/*": "./tokens/*",
28
37
  "./_ds_bundle.js": "./_ds_bundle.js",
@@ -46,14 +55,18 @@
46
55
  "_ds_manifest.json",
47
56
  "llms.txt",
48
57
  "SKILL.md",
49
- "docs/",
58
+ "docs/*.md",
59
+ "docs/vi/",
60
+ "docs/viewer.html",
50
61
  "components/",
51
62
  "templates/",
52
63
  "guidelines/",
53
64
  "ui_kits/",
65
+ "_vendor/",
54
66
  "DESIGN.md",
55
67
  "VERSION",
56
- "dist/styles.min.css"
68
+ "dist/styles.min.css",
69
+ "THIRD-PARTY-NOTICES.md"
57
70
  ],
58
71
  "keywords": [
59
72
  "design-system",
@@ -68,12 +81,12 @@
68
81
  "build:bundle": "node scripts/build-bundle.mjs",
69
82
  "build:react-entry": "node scripts/generate-react-entry.mjs",
70
83
  "build:design-md": "node scripts/generate-design-md.mjs",
71
- "prepublishOnly": "npm run build:bundle && npm run build:react-entry && node scripts/generate-design-md.mjs --check",
84
+ "prepublishOnly": "npm run build:bundle && npm run build:react-entry && npm run build:styles && node scripts/generate-design-md.mjs --check && node scripts/flatten-styles.mjs --check && node scripts/generate-contrast-report.mjs --check && npm run version:stamp:check",
72
85
  "code-connect:generate": "node _audit/ci/generate-code-connect.mjs",
73
86
  "code-connect:dry-run": "node _audit/ci/code-connect-publish.mjs --dry-run",
74
87
  "npm:pack-dry-run": "node _audit/ci/npm-publish.mjs --dry-run",
75
88
  "native:store-dry-run": "node _audit/ci/native-store-dry-run.mjs --dry-run",
76
- "storybook": "storybook dev -p 6006",
89
+ "storybook": "storybook dev -p 6006 --host 127.0.0.1",
77
90
  "build:storybook": "storybook build -o storybook-static && node scripts/stamp-storybook-freshness.mjs",
78
91
  "build:site": "npm run build:storybook && node scripts/vercel-static-output.mjs",
79
92
  "verify:serve": "python3 -m http.server 8765 --bind 127.0.0.1",
@@ -82,7 +95,7 @@
82
95
  "test:nav-model": "node _audit/ci/test-nav-model.mjs",
83
96
  "test:ui-contracts": "node _audit/ci/test-health-tokens-contract.mjs",
84
97
  "test:form-paths": "node _audit/ci/test-form-paths.mjs",
85
- "test:unit": "node _audit/ci/test-nav-model.mjs && node _audit/ci/test-health-tokens-contract.mjs && node _audit/ci/test-form-paths.mjs && node _audit/ci/test-storybook-contract.mjs && node _audit/ci/test-axe-coverage.mjs && node _audit/ci/test-figma-push-helpers.mjs && node _audit/ci/test-native-samples.mjs && node _audit/ci/test-code-connect.mjs && node _audit/ci/test-subtree-consume.mjs && node _audit/ci/test-react-entry.mjs && node _audit/ci/test-element-packs.mjs && node _audit/ci/test-i18n-builtins.mjs && node _audit/ci/test-storybook-freshness.mjs",
98
+ "test:unit": "node _audit/ci/test-nav-model.mjs && node _audit/ci/test-health-tokens-contract.mjs && node _audit/ci/test-docs-consistency.mjs && node _audit/ci/test-base-hex-allowlist.mjs && node _audit/ci/test-form-paths.mjs && node _audit/ci/test-storybook-contract.mjs && node _audit/ci/test-axe-coverage.mjs && node _audit/ci/test-figma-push-helpers.mjs && node _audit/ci/test-native-samples.mjs && node _audit/ci/test-code-connect.mjs && node _audit/ci/test-npm-publish.mjs && node _audit/ci/test-subtree-consume.mjs && node _audit/ci/test-react-entry.mjs && node _audit/ci/test-element-packs.mjs && node _audit/ci/test-i18n-builtins.mjs && node _audit/ci/test-format-currency.mjs && node _audit/ci/check-manifest-coverage.mjs && node _audit/ci/ci-verdict.mjs --selftest && node _audit/ci/test-pack-hygiene.mjs && npm run version:stamp:check && node _audit/ci/test-docs-lang-parity.mjs && node _audit/ci/test-docs-storybook-coverage.mjs && node _audit/ci/test-docs-link-check.mjs && node _audit/ci/test-email-safe.mjs && node _audit/ci/test-prop-parity.mjs",
86
99
  "test:audit-probe": "node _audit/ci/audit-probe-suite.mjs",
87
100
  "test:element-packs": "node _audit/ci/test-element-packs.mjs",
88
101
  "test:react-entry": "node _audit/ci/test-react-entry.mjs",
@@ -93,13 +106,20 @@
93
106
  "test:figma-helpers": "node _audit/ci/test-figma-push-helpers.mjs",
94
107
  "test:code-connect": "node _audit/ci/test-code-connect.mjs",
95
108
  "test:native-samples": "node _audit/ci/test-native-samples.mjs",
109
+ "test:storybook-freshness": "node _audit/ci/test-storybook-freshness.mjs",
110
+ "test:manifest-coverage": "node _audit/ci/check-manifest-coverage.mjs",
111
+ "test:pack-hygiene": "node _audit/ci/test-pack-hygiene.mjs",
96
112
  "tokens:elements": "node scripts/generate-element-packs.mjs",
97
113
  "tokens:contrast": "node scripts/generate-contrast-report.mjs",
98
114
  "build:styles": "node scripts/flatten-styles.mjs",
99
115
  "version:check": "node scripts/ds-version.mjs --check",
100
116
  "version:selftest": "node scripts/ds-version.mjs --selftest",
101
117
  "version:stamp": "node scripts/stamp-release-version.mjs",
102
- "version:stamp:apply": "node scripts/stamp-release-version.mjs --apply"
118
+ "version:stamp:apply": "node scripts/stamp-release-version.mjs --apply",
119
+ "version:stamp:check": "node scripts/stamp-release-version.mjs --check",
120
+ "sbom": "npx --yes @cyclonedx/cyclonedx-npm@3.1.0 --output-file bom.cdx.json --output-reproducible --spec-version 1.5",
121
+ "test:docs-lang-parity": "node _audit/ci/test-docs-lang-parity.mjs",
122
+ "test:email-safe": "node _audit/ci/test-email-safe.mjs"
103
123
  },
104
124
  "devDependencies": {
105
125
  "@figma/code-connect": "^1.4.9",
@@ -13,7 +13,7 @@
13
13
  <script src="./ds-base.js"></script>
14
14
  </helmet>
15
15
  <div data-theme="{{ rootTheme }}" lang="{{ langAttr }}" data-cs-element="{{ elAttr }}" data-cs-variant="{{ vaAttr }}" style="display:grid;grid-template-columns:248px 1fr;min-height:100vh;font-family:var(--cs-font-family-ui);color:var(--cs-color-text-primary);background:var(--cs-color-surface-page)">
16
- <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">Skip to content</a>
16
+ <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">{{ skipLabel }}</a>
17
17
  <nav class="cs-sidebar" aria-label="{{ navLabel }}">
18
18
  <div style="display:flex;align-items:center;gap:10px;font-weight:800;font-size:17px;color:var(--cs-color-text-primary);padding:6px 12px 14px">
19
19
  <x-import component-from-global-scope="CyberSkillDS.Logo" size="28" hint-size="28px,28px"></x-import>CyberSkill
@@ -64,8 +64,8 @@ class Component extends DCLogic {
64
64
  const EL = {"Thổ · studio":["tho",""],"Thổ · clay":["tho","clay"],"Thổ · sand":["tho","sand"],"Hỏa · ember":["hoa",""],"Hỏa · lava":["hoa","lava"],"Hỏa · plasma":["hoa","plasma"],"Thủy · river":["thuy",""],"Thủy · ocean":["thuy","ocean"],"Thủy · mist":["thuy","mist"],"Mộc · leaf":["moc",""],"Mộc · bamboo":["moc","bamboo"],"Mộc · forest":["moc","forest"],"Kim · champagne":["kim",""],"Kim · steel":["kim","steel"],"Kim · titanium":["kim","titanium"]};
65
65
  const [elAttr, vaAttr] = EL[this.props.element] || EL["Thổ · studio"];
66
66
  const vn = this.props.language === "Tiếng Việt";
67
- const en = { navLabel:"Workspace", nav1:"Overview", nav2:"Projects", nav3:"Releases", nav4:"Team", nav5:"Settings", searchPh:"Search projects…", newBtn:"New project", h1:"Overview", sub:"Every wish in flight, at a glance.", s1:"Projects", s2:"In progress", s3:"Shipped", s4:"Avg progress", s4d:"+6 this week", tblCap:"Projects", cKey:"Key", cName:"Project", cSquad:"Squad", cProg:"Progress", cStatus:"Status", stInprog:"In progress", stBlocked:"Blocked" };
68
- const vi = { navLabel:"Không gian", nav1:"Tổng quan", nav2:"Dự án", nav3:"Phát hành", nav4:"Đội ngũ", nav5:"Cài đặt", searchPh:"Tìm dự án…", newBtn:"Dự án mới", h1:"Tổng quan", sub:"Mọi điều ước đang chạy, trong một cái nhìn.", s1:"Dự án", s2:"Đang làm", s3:"Đã ra mắt", s4:"Tiến độ TB", s4d:"+6 tuần này", tblCap:"Dự án", cKey:"Mã", cName:"Dự án", cSquad:"Nhóm", cProg:"Tiến độ", cStatus:"Trạng thái", stInprog:"Đang làm", stBlocked:"Bị chặn" };
67
+ const en = { skipLabel:"Skip to content", navLabel:"Workspace", nav1:"Overview", nav2:"Projects", nav3:"Releases", nav4:"Team", nav5:"Settings", searchPh:"Search projects…", newBtn:"New project", h1:"Overview", sub:"Every wish in flight, at a glance.", s1:"Projects", s2:"In progress", s3:"Shipped", s4:"Avg progress", s4d:"+6 this week", tblCap:"Projects", cKey:"Key", cName:"Project", cSquad:"Squad", cProg:"Progress", cStatus:"Status", stInprog:"In progress", stBlocked:"Blocked" };
68
+ const vi = { skipLabel:"Bỏ qua nội dung", navLabel:"Không gian", nav1:"Tổng quan", nav2:"Dự án", nav3:"Phát hành", nav4:"Đội ngũ", nav5:"Cài đặt", searchPh:"Tìm dự án…", newBtn:"Dự án mới", h1:"Tổng quan", sub:"Mọi điều ước đang chạy, trong một cái nhìn.", s1:"Dự án", s2:"Đang làm", s3:"Đã ra mắt", s4:"Tiến độ TB", s4d:"+6 tuần này", tblCap:"Dự án", cKey:"Mã", cName:"Dự án", cSquad:"Nhóm", cProg:"Tiến độ", cStatus:"Trạng thái", stInprog:"Đang làm", stBlocked:"Bị chặn" };
69
69
  const t = vn ? vi : en;
70
70
  return Object.assign({ langAttr: (this.props.language ?? "English") === "Tiếng Việt" ? "vi" : "en", elAttr, vaAttr, rootTheme: this.props.theme === "dark" ? "dark" : "",
71
71
  cols: [ { key: "key", header: t.cKey }, { key: "name", header: t.cName }, { key: "squad", header: t.cSquad }, { key: "pct", header: t.cProg, render: (r) => r.pct + "%" }, { key: "status", header: t.cStatus } ],
@@ -2,6 +2,13 @@
2
2
  "$schemaVersion": "2.0",
3
3
  "template": "app-shell",
4
4
  "slots": [
5
+ {
6
+ "id": "skipLabel",
7
+ "type": "text",
8
+ "label": "Skip-to-content link label",
9
+ "maxLength": 40,
10
+ "required": true
11
+ },
5
12
  {
6
13
  "id": "navLabel",
7
14
  "type": "text",
@@ -13,7 +13,7 @@
13
13
  <script src="./ds-base.js"></script>
14
14
  </helmet>
15
15
  <div data-theme="{{ rootTheme }}" lang="{{ langAttr }}" data-cs-element="{{ elAttr }}" data-cs-variant="{{ vaAttr }}" style="font-family:var(--cs-font-family-ui);color:var(--cs-color-text-primary);background:var(--cs-color-surface-page);min-height:100vh">
16
- <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">Skip to content</a>
16
+ <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">{{ skipLabel }}</a>
17
17
  <main id="main">
18
18
  <article style="max-width:720px;margin:0 auto;padding:56px 28px 80px">
19
19
  <div style="font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:0.12em;color:var(--cs-color-text-accent)">{{ eyebrow }}</div>
@@ -49,14 +49,14 @@ class Component extends DCLogic {
49
49
  const [elAttr, vaAttr] = EL[this.props.element] || EL["Thổ · studio"];
50
50
  const vn = this.props.language === "Tiếng Việt";
51
51
  const code = '<div class="cs-glass-card">\n <h3>Design System v1.6</h3>\n</div>';
52
- const en = {
52
+ const en = { skipLabel:"Skip to content",
53
53
  eyebrow:"Engineering · Design systems", h1:"Liquid Glass, now bound to components", meta:"18 Jul 2026 · 6 min read", badge:"New", authorTeam:"Platform",
54
54
  lede:"Five glass materials over the immutable Umber and Ochre anchors — and now they are one class away on any surface. Here's how the bindings work, and when to reach for each.",
55
55
  p1:"Liquid Glass is CyberSkill's default surface treatment: translucent, refractive layers that let warm gold light show through. The material scalars live in tokens; this release wires them to real components so a card, a nav, or a modal can opt in with a single class.",
56
56
  h2:"When to reach for glass", p2:"Keep dense content on solid surfaces. Reserve glass for nav, cards over imagery, and modals — always after an APCA check against the backdrop.",
57
57
  alertTitle:"Rule of thumb", alertBody:"If text sits directly on a busy photo, step up one glass tier or drop to a solid surface.",
58
58
  p3:"That's the whole surface. Everything paints from the same anchors — warm, honest, and Vietnamese-first by default."};
59
- const vi = {
59
+ const vi = { skipLabel:"Bỏ qua nội dung",
60
60
  eyebrow:"Kỹ thuật · Design system", h1:"Liquid Glass, nay gắn vào component", meta:"18/07/2026 · đọc 6 phút", badge:"Mới", authorTeam:"Nền tảng",
61
61
  lede:"Năm vật liệu kính trên hai màu neo bất biến Umber và Ochre — và giờ chỉ cách một class trên mọi bề mặt. Đây là cách các liên kết hoạt động, và khi nào nên dùng từng loại.",
62
62
  p1:"Liquid Glass là lớp bề mặt mặc định của CyberSkill: những lớp mờ, khúc xạ, để ánh vàng ấm hiện qua. Hệ số vật liệu nằm trong token; bản phát hành này gắn chúng vào component thật, để một card, một nav hay một modal có thể bật lên chỉ bằng một class.",
@@ -2,6 +2,13 @@
2
2
  "$schemaVersion": "2.0",
3
3
  "template": "article",
4
4
  "slots": [
5
+ {
6
+ "id": "skipLabel",
7
+ "type": "text",
8
+ "label": "Skip-to-content link label",
9
+ "maxLength": 40,
10
+ "required": true
11
+ },
5
12
  {
6
13
  "id": "eyebrow",
7
14
  "type": "text",
@@ -8,7 +8,7 @@
8
8
  </head>
9
9
  <body>
10
10
  <x-dc>
11
- <!-- @template name="Sign in" description="CyberSkill split sign-in — umber brand panel + form built from TextField, Switch, and Button. Bilingual (English · Tiếng Việt)." -->
11
+ <!-- @template name="Sign in" description="CyberSkill split sign-in — umber brand panel + form built from TextField, Switch, Button, and Alert. formState tweak: default | loading | error | invalid. Bilingual (English · Tiếng Việt)." -->
12
12
  <helmet>
13
13
  <script src="./ds-base.js"></script>
14
14
  </helmet>
@@ -25,7 +25,7 @@
25
25
  }
26
26
  </style>
27
27
  <div class="cs-auth-shell" data-theme="{{ rootTheme }}" lang="{{ langAttr }}" data-cs-element="{{ elAttr }}" data-cs-variant="{{ vaAttr }}">
28
- <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">Skip to content</a>
28
+ <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">{{ skipLabel }}</a>
29
29
  <main id="main">
30
30
  <sc-if value="{{ showBrand }}" hint-placeholder-val="{{ true }}">
31
31
  <div class="cs-auth-brand">
@@ -34,7 +34,7 @@
34
34
  <div style="position:relative;z-index:1;display:flex;align-items:center;gap:12px;font-weight:800;font-size:20px">
35
35
  <x-import component-from-global-scope="CyberSkillDS.Logo" size="40" hint-size="40px,40px"></x-import>CyberSkill
36
36
  </div>
37
- <h2 style="position:relative;z-index:1;font-size:40px;font-weight:800;letter-spacing:-0.02em;line-height:1.08;color:var(--cs-accent-bright);margin:auto 0 8px">{{ brandH }}</h2>
37
+ <p style="position:relative;z-index:1;font-size:40px;font-weight:800;letter-spacing:-0.02em;line-height:1.08;color:var(--cs-accent-bright);margin:auto 0 8px">{{ brandH }}</p>
38
38
  <p style="position:relative;z-index:1;font-size:17px;opacity:0.9;margin:0;max-width:420px">{{ brandP }}</p>
39
39
  </div>
40
40
  </sc-if>
@@ -43,10 +43,13 @@
43
43
  <h1 style="font-size:30px;font-weight:800;letter-spacing:-0.02em;margin:0 0 4px">{{ title }}</h1>
44
44
  <p style="color:var(--cs-color-text-muted);margin:0 0 28px">{{ sub }}</p>
45
45
  <div style="display:grid;gap:16px">
46
- <x-import component-from-global-scope="CyberSkillDS.TextField" label="{{ emailL }}" type="email" placeholder="you@cyberskill.world" hint-size="100%,74px"></x-import>
46
+ <sc-if value="{{ showError }}" hint-placeholder-val="{{ false }}">
47
+ <x-import component-from-global-scope="CyberSkillDS.Alert" variant="danger" title="{{ errTitle }}" hint-size="100%,64px">{{ errBody }}</x-import>
48
+ </sc-if>
49
+ <x-import component-from-global-scope="CyberSkillDS.TextField" label="{{ emailL }}" type="email" placeholder="you@cyberskill.world" error="{{ emailError }}" hint-size="100%,74px"></x-import>
47
50
  <x-import component-from-global-scope="CyberSkillDS.TextField" label="{{ pwL }}" type="password" placeholder="••••••••" hint-size="100%,74px"></x-import>
48
51
  <x-import component-from-global-scope="CyberSkillDS.Switch" label="{{ rememberL }}" checked="{{ false }}" hint-size="140px,26px"></x-import>
49
- <x-import component-from-global-scope="CyberSkillDS.Button" variant="primary" full-width="{{ true }}" hint-size="100%,44px">{{ signInBtn }}</x-import>
52
+ <x-import component-from-global-scope="CyberSkillDS.Button" variant="primary" full-width="{{ true }}" loading="{{ isLoading }}" hint-size="100%,44px">{{ signInBtn }}</x-import>
50
53
  <x-import component-from-global-scope="CyberSkillDS.Button" variant="ghost" full-width="{{ true }}" hint-size="100%,44px">{{ lumiBtn }}</x-import>
51
54
  </div>
52
55
  <p style="text-align:center;font-size:13px;color:var(--cs-color-text-muted);margin-top:16px">{{ newHere }} <a href="#">{{ askAccess }}</a></p>
@@ -60,16 +63,29 @@
60
63
  &quot;language&quot;: {&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;English&quot;,&quot;Tiếng Việt&quot;],&quot;default&quot;:&quot;English&quot;,&quot;tsType&quot;:&quot;\&quot;English\&quot; | \&quot;Tiếng Việt\&quot;&quot;,&quot;section&quot;:&quot;Content&quot;},
61
64
  &quot;element&quot;: {&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;Thổ · studio&quot;,&quot;Thổ · clay&quot;,&quot;Thổ · sand&quot;,&quot;Hỏa · ember&quot;,&quot;Hỏa · lava&quot;,&quot;Hỏa · plasma&quot;,&quot;Thủy · river&quot;,&quot;Thủy · ocean&quot;,&quot;Thủy · mist&quot;,&quot;Mộc · leaf&quot;,&quot;Mộc · bamboo&quot;,&quot;Mộc · forest&quot;,&quot;Kim · champagne&quot;,&quot;Kim · steel&quot;,&quot;Kim · titanium&quot;],&quot;default&quot;:&quot;Thổ · studio&quot;,&quot;tsType&quot;:&quot;string&quot;,&quot;section&quot;:&quot;Element (Ngũ Hành)&quot;},
62
65
  &quot;theme&quot;: {&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;light&quot;,&quot;dark&quot;],&quot;default&quot;:&quot;light&quot;,&quot;tsType&quot;:&quot;\&quot;light\&quot; | \&quot;dark\&quot;&quot;,&quot;section&quot;:&quot;Theme&quot;},
63
- &quot;showBrandPanel&quot;: {&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Layout&quot;}
66
+ &quot;showBrandPanel&quot;: {&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;,&quot;section&quot;:&quot;Layout&quot;},
67
+ &quot;formState&quot;: {&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;default&quot;,&quot;loading&quot;,&quot;error&quot;,&quot;invalid&quot;],&quot;default&quot;:&quot;default&quot;,&quot;tsType&quot;:&quot;\&quot;default\&quot; | \&quot;loading\&quot; | \&quot;error\&quot; | \&quot;invalid\&quot;&quot;,&quot;section&quot;:&quot;Form states&quot;}
64
68
  }">
65
69
  class Component extends DCLogic {
66
70
  renderVals() {
67
71
  const EL = {"Thổ · studio":["tho",""],"Thổ · clay":["tho","clay"],"Thổ · sand":["tho","sand"],"Hỏa · ember":["hoa",""],"Hỏa · lava":["hoa","lava"],"Hỏa · plasma":["hoa","plasma"],"Thủy · river":["thuy",""],"Thủy · ocean":["thuy","ocean"],"Thủy · mist":["thuy","mist"],"Mộc · leaf":["moc",""],"Mộc · bamboo":["moc","bamboo"],"Mộc · forest":["moc","forest"],"Kim · champagne":["kim",""],"Kim · steel":["kim","steel"],"Kim · titanium":["kim","titanium"]};
68
72
  const [elAttr, vaAttr] = EL[this.props.element] || EL["Thổ · studio"];
69
- const vn = this.props.language === "Tiếng Việt";
70
- const en = { brandH:"Turn Your Will Into Real", brandP:"Sign in to your workspace — one page, three lenses across every wish in flight.", title:"Sign in", sub:"Welcome back. Sign in to continue.", emailL:"Work email", pwL:"Password", rememberL:"Remember me", signInBtn:"Sign in", lumiBtn:"✦ Talk to Lumi instead", newHere:"New here?", askAccess:"Ask for access", resetHint:"Forgot password?", resetLink:"Email your workspace admin to reset." };
71
- const vi = { brandH:"Hiện Thực Hoá Ý Chí", brandP:"Đăng nhập vào không gian làm việc — một trang, ba lăng kính cho mọi điều ước đang chạy.", title:"Đăng nhập", sub:"Mừng bạn quay lại. Đăng nhập để tiếp tục.", emailL:"Email công việc", pwL:"Mật khẩu", rememberL:"Ghi nhớ đăng nhập", signInBtn:"Đăng nhập", lumiBtn:"✦ Trò chuyện với Lumi", newHere:"Lần đầu?", askAccess:"Xin quyền truy cập", resetHint:"Quên mật khẩu?", resetLink:"Gửi email cho quản trị không gian làm việc để đặt lại." };
72
- return Object.assign({ langAttr: (this.props.language ?? "English") === "Tiếng Việt" ? "vi" : "en", elAttr, vaAttr, rootTheme: this.props.theme === "dark" ? "dark" : "", showBrand: this.props.showBrandPanel ?? true }, vn ? vi : en);
73
+ const vn = this.props.language === "Tiếng Việt";
74
+ const state = this.props.formState || "default";
75
+ const en = { skipLabel:"Skip to content", brandH:"Turn Your Will Into Real", brandP:"Sign in to your workspace — one page, three lenses across every wish in flight.", title:"Sign in", sub:"Welcome back. Sign in to continue.", emailL:"Work email", pwL:"Password", rememberL:"Remember me", signInBtn: state === "loading" ? "Signing in…" : "Sign in", lumiBtn:"✦ Talk to Lumi instead", newHere:"New here?", askAccess:"Ask for access", resetHint:"Forgot password?", resetLink:"Email your workspace admin to reset.", errTitle:"Sign-in failed", errBody:"Check your email and password, then try again.", emailErr:"Enter a valid work email." };
76
+ const vi = { skipLabel:"Bỏ qua nội dung", brandH:"Hiện Thực Hoá Ý Chí", brandP:"Đăng nhập vào không gian làm việc — một trang, ba lăng kính cho mọi điều ước đang chạy.", title:"Đăng nhập", sub:"Mừng bạn quay lại. Đăng nhập để tiếp tục.", emailL:"Email công việc", pwL:"Mật khẩu", rememberL:"Ghi nhớ đăng nhập", signInBtn: state === "loading" ? "Đang đăng nhập…" : "Đăng nhập", lumiBtn:"✦ Trò chuyện với Lumi", newHere:"Lần đầu?", askAccess:"Xin quyền truy cập", resetHint:"Quên mật khẩu?", resetLink:"Gửi email cho quản trị không gian làm việc để đặt lại.", errTitle:"Đăng nhập thất bại", errBody:"Kiểm tra email và mật khẩu, rồi thử lại.", emailErr:"Nhập email công việc hợp lệ." };
77
+ const copy = vn ? vi : en;
78
+ return Object.assign({
79
+ langAttr: (this.props.language ?? "English") === "Tiếng Việt" ? "vi" : "en",
80
+ elAttr, vaAttr,
81
+ rootTheme: this.props.theme === "dark" ? "dark" : "",
82
+ showBrand: this.props.showBrandPanel ?? true,
83
+ showError: state === "error",
84
+ isLoading: state === "loading",
85
+ emailError: state === "invalid" ? copy.emailErr : "",
86
+ errTitle: copy.errTitle,
87
+ errBody: copy.errBody
88
+ }, copy);
73
89
  }
74
90
  }
75
91
  </script>
@@ -2,6 +2,13 @@
2
2
  "$schemaVersion": "2.0",
3
3
  "template": "auth",
4
4
  "slots": [
5
+ {
6
+ "id": "skipLabel",
7
+ "type": "text",
8
+ "label": "Skip-to-content link label",
9
+ "maxLength": 40,
10
+ "required": true
11
+ },
5
12
  {
6
13
  "id": "brandH",
7
14
  "type": "text",
@@ -81,6 +88,24 @@
81
88
  "type": "text",
82
89
  "label": "Password-reset action link",
83
90
  "maxLength": 64
91
+ },
92
+ {
93
+ "id": "errTitle",
94
+ "type": "text",
95
+ "label": "Sign-in error alert title (error/invalid states)",
96
+ "maxLength": 40
97
+ },
98
+ {
99
+ "id": "errBody",
100
+ "type": "text",
101
+ "label": "Sign-in error alert body",
102
+ "maxLength": 120
103
+ },
104
+ {
105
+ "id": "emailError",
106
+ "type": "text",
107
+ "label": "Email field validation error (invalid state)",
108
+ "maxLength": 64
84
109
  }
85
110
  ]
86
111
  }
@@ -13,7 +13,7 @@
13
13
  <script src="./ds-base.js"></script>
14
14
  </helmet>
15
15
  <div data-theme="{{ rootTheme }}" lang="{{ langAttr }}" data-cs-element="{{ elAttr }}" data-cs-variant="{{ vaAttr }}" style="font-family:var(--cs-font-family-ui);color:var(--cs-color-text-primary);background:var(--cs-color-surface-page);min-height:100vh">
16
- <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">Skip to content</a>
16
+ <a class="cs-skip" href="#main" style="position:absolute;left:-999px;top:auto;width:1px;height:1px;overflow:hidden">{{ skipLabel }}</a>
17
17
  <main id="main">
18
18
  <header style="background:var(--cs-accent-strong);color:var(--cs-accent-on-strong);border-bottom:3px solid var(--cs-accent)">
19
19
  <div style="max-width:1280px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;gap:24px;padding:18px 28px;flex-wrap:wrap">
@@ -23,7 +23,7 @@
23
23
  </span>
24
24
  <div>
25
25
  <h1 style="margin:0;font-size:22px;letter-spacing:0.2px">Status Hub</h1>
26
- <p style="margin:2px 0 0;font-size:13px;opacity:0.8">{{ tagline }}</p>
26
+ <p style="margin:2px 0 0;font-size:13px;color:var(--cs-accent-on-strong)">{{ tagline }}</p>
27
27
  </div>
28
28
  </div>
29
29
  <span>
@@ -105,8 +105,8 @@ class Component extends DCLogic {
105
105
  const [elAttr, vaAttr] = EL[this.props.element] || EL["Thổ · studio"];
106
106
  const lens = this.lens;
107
107
  const vn = this.props.language === "Tiếng Việt";
108
- const en = { tagline:"Portfolio · one page, three lenses", newBtn:"New project", s1:"Projects", s2:"In progress", s3:"Shipped", s4:"Avg progress", s4d:"+6 this week", boardL:"Board", tableL:"Table", c1:"Liquid Glass component bindings + 15 element packs.", c2:"Bilingual storytelling landing; lead capture live.", c3:"BRAIN memory store + human-in-the-loop gates.", stInprog:"In progress", stBlocked:"Blocked", tblCap:"Projects", cKey:"Key", cName:"Project", cSquad:"Squad", cProg:"Progress", cStatus:"Status" };
109
- const vi = { tagline:"Danh mục · một trang, ba lăng kính", newBtn:"Dự án mới", s1:"Dự án", s2:"Đang làm", s3:"Đã ra mắt", s4:"Tiến độ TB", s4d:"+6 tuần này", boardL:"Bảng", tableL:"Danh sách", c1:"Gắn component Liquid Glass + 15 element pack.", c2:"Landing kể chuyện song ngữ; thu lead đã chạy.", c3:"Kho ký ức BRAIN + cổng có con người kiểm duyệt.", stInprog:"Đang làm", stBlocked:"Bị chặn", tblCap:"Dự án", cKey:"Mã", cName:"Dự án", cSquad:"Nhóm", cProg:"Tiến độ", cStatus:"Trạng thái" };
108
+ const en = { skipLabel:"Skip to content", tagline:"Portfolio · one page, three lenses", newBtn:"New project", s1:"Projects", s2:"In progress", s3:"Shipped", s4:"Avg progress", s4d:"+6 this week", boardL:"Board", tableL:"Table", c1:"Liquid Glass component bindings + 15 element packs.", c2:"Bilingual storytelling landing; lead capture live.", c3:"BRAIN memory store + human-in-the-loop gates.", stInprog:"In progress", stBlocked:"Blocked", tblCap:"Projects", cKey:"Key", cName:"Project", cSquad:"Squad", cProg:"Progress", cStatus:"Status" };
109
+ const vi = { skipLabel:"Bỏ qua nội dung", tagline:"Danh mục · một trang, ba lăng kính", newBtn:"Dự án mới", s1:"Dự án", s2:"Đang làm", s3:"Đã ra mắt", s4:"Tiến độ TB", s4d:"+6 tuần này", boardL:"Bảng", tableL:"Danh sách", c1:"Gắn component Liquid Glass + 15 element pack.", c2:"Landing kể chuyện song ngữ; thu lead đã chạy.", c3:"Kho ký ức BRAIN + cổng có con người kiểm duyệt.", stInprog:"Đang làm", stBlocked:"Bị chặn", tblCap:"Dự án", cKey:"Mã", cName:"Dự án", cSquad:"Nhóm", cProg:"Tiến độ", cStatus:"Trạng thái" };
110
110
  const t = vn ? vi : en;
111
111
  return Object.assign({ langAttr: (this.props.language ?? "English") === "Tiếng Việt" ? "vi" : "en", elAttr, vaAttr, rootTheme: this.props.theme === "dark" ? "dark" : "", lens, isBoard: lens === "board", isTable: lens === "table",
112
112
  tabs: [{ value: "board", label: t.boardL }, { value: "table", label: t.tableL, count: 3 }],
@@ -2,6 +2,13 @@
2
2
  "$schemaVersion": "2.0",
3
3
  "template": "dashboard",
4
4
  "slots": [
5
+ {
6
+ "id": "skipLabel",
7
+ "type": "text",
8
+ "label": "Skip-to-content link label",
9
+ "maxLength": 40,
10
+ "required": true
11
+ },
5
12
  {
6
13
  "id": "tagline",
7
14
  "type": "text",
@@ -60,7 +60,7 @@ class Component extends DCLogic {
60
60
  const vn = this.props.language === "Tiếng Việt";
61
61
  const en = {
62
62
  eyebrow:"Change note · under SOW-2026-014", h1:"Add A/B experiment kit to the checkout scope",
63
- altTitle:"Văn bản thay đổi phạm vi",
63
+ altTitle:"Scope amendment note",
64
64
  intro:"Per the MSA, changes happen in writing, signed by both parties. This note states the change, why, and exactly what it does to scope, fee, and timeline — nothing else in the SOW moves.",
65
65
  changeH:"The change", changeP:"Add the optional A/B kit from quote QT-2026-0088: experiment framework on the new checkout plus the first two tests (copy variants; shipping-threshold banner).",
66
66
  whyH:"Why now", whyP:"The canary shows recoverable drop-off at the shipping step. Testing while the team is in the code is cheaper than a second engagement later. Honest trade-off: it adds a week to M3.",
@@ -70,7 +70,7 @@ const vn = this.props.language === "Tiếng Việt";
70
70
  sigMeta:"Name / title / date", clientName:"[Client Ltd]", rTimeB:"31 Oct 2026", rTimeA:"07 Nov 2026", partyA:"CyberSkill Co., Ltd"};
71
71
  const vi = {
72
72
  eyebrow:"Văn bản thay đổi · theo SOW-2026-014", h1:"Bổ sung bộ thử nghiệm A/B vào phạm vi thanh toán",
73
- altTitle:"Change note · scope amendment",
73
+ altTitle:"Văn bản thay đổi phạm vi",
74
74
  intro:"Theo Hợp đồng khung (MSA), mọi thay đổi được lập bằng văn bản, có chữ ký hai bên. Văn bản này nêu thay đổi, lý do, và tác động chính xác đến phạm vi, phí và tiến độ — không điều nào khác trong SOW thay đổi.",
75
75
  changeH:"Nội dung thay đổi", changeP:"Bổ sung bộ A/B tùy chọn từ báo giá QT-2026-0088: khung thử nghiệm trên trang thanh toán mới cùng hai test đầu tiên (biến thể nội dung; banner ngưỡng phí vận chuyển).",
76
76
  whyH:"Vì sao lúc này", whyP:"Bản canary cho thấy tỷ lệ rớt có thể thu hồi ở bước vận chuyển. Thử nghiệm khi đội còn trong mã nguồn rẻ hơn một đợt hợp tác thứ hai về sau. Đánh đổi thành thật: thêm một tuần vào M3.",
@@ -65,7 +65,7 @@ class Component extends DCLogic {
65
65
  const [elAttr, vaAttr] = EL[this.props.element] || EL["Thổ · studio"];
66
66
  const vn = this.props.language === "Tiếng Việt";
67
67
  const en = {
68
- eyebrow:"Handover · SOW-2026-014", h1:"Checkout & ops console — run it yourself", altTitle:"Sổ tay vận hành & bàn giao",
68
+ eyebrow:"Handover · SOW-2026-014", h1:"Checkout & ops console — run it yourself", altTitle:"Operations runbook & handover",
69
69
  intro:"Everything your team needs to own the system: what it is, where it runs, how to deploy, what watches it, and who to call. Built to last means you don't need us — and we're one message away if you do.",
70
70
  ovH:"System overview", ovP1:"Next.js storefront checkout (behind flag ", ovP2:") → pricing service (cache TTL 300s) → payment provider. Ops console reads the orders API with support-role auth. Recovery emails run on a nightly job.",
71
71
  envH:"Environments", thEnv:"Env", thFrom:"Deploys from", envProd:"Production", envProdFrom:"main · tagged release", envStg:"Staging", envStgFrom:"main · every merge", envSb:"Support sandbox", envSbFrom:"weekly snapshot",
@@ -75,7 +75,7 @@ const vn = this.props.language === "Tiếng Việt";
75
75
  contactsL:"Contacts", contactsBody:"Your on-call rota (primary) · CyberSkill: minh@cyberskill.world · escalation: vu@cyberskill.world · we reply within one business day",
76
76
  handedBy:"Handed over — CyberSkill Co., Ltd", acceptedBy:"Accepted — [Client Ltd]", sigMeta:"Name / title / date"};
77
77
  const vi = {
78
- eyebrow:"Bàn giao · SOW-2026-014", h1:"Thanh toán & ops console — tự vận hành", altTitle:"Runbook & handover",
78
+ eyebrow:"Bàn giao · SOW-2026-014", h1:"Thanh toán & ops console — tự vận hành", altTitle:"Sổ tay vận hành & bàn giao",
79
79
  intro:"Tất cả những gì đội bạn cần để làm chủ hệ thống: nó là gì, chạy ở đâu, triển khai thế nào, ai giám sát, và gọi ai. Làm để bền lâu nghĩa là bạn không cần đến chúng tôi — và chúng tôi luôn cách một tin nhắn nếu cần.",
80
80
  ovH:"Tổng quan hệ thống", ovP1:"Trang thanh toán Next.js (sau cờ ", ovP2:") → dịch vụ định giá (cache TTL 300s) → nhà cung cấp thanh toán. Ops console đọc API đơn hàng với quyền support. Email khôi phục chạy theo tác vụ hằng đêm.",
81
81
  envH:"Môi trường", thEnv:"Môi trường", thFrom:"Triển khai từ", envProd:"Production", envProdFrom:"main · bản gắn thẻ", envStg:"Staging", envStgFrom:"main · mỗi lần merge", envSb:"Sandbox hỗ trợ", envSbFrom:"ảnh chụp hằng tuần",
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
2
+ <html lang="vi" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -77,7 +77,7 @@
77
77
  <tr>
78
78
  <td class="px" bgcolor="#fbf4e9" style="background:#fbf4e9;padding:22px 32px;border-top:1px solid #e7d9c6;">
79
79
  <p class="muted" style="margin:0 0 6px;font-family:Arial,Helvetica,sans-serif;font-size:13px;color:#6e5a4c;">Hiện Thực Hoá Ý Chí · Turn Your Will Into Real</p>
80
- <p class="muted" style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:1.5;color:#6e5a4c;">CyberSkill Co., Ltd · 12 Nguyễn Huệ, Quận 1, Sài Gòn, Việt Nam<br><a href="https://cyberskill.world/unsubscribe" style="color:#6E3B0E;">Unsubscribe / Huỷ đăng ký</a> · <a href="https://cyberskill.world/preferences" style="color:#6E3B0E;">Preferences / Tuỳ chọn</a></p>
80
+ <p class="muted" style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:1.5;color:#6e5a4c;">CyberSkill Co., Ltd · 12 Nguyễn Huệ, Quận 1, Sài Gòn, Việt Nam<br><a href="https://cyberskill.world/unsubscribe" style="color:#6E3B0E;"><span lang="en">Unsubscribe</span> · <span lang="vi">Huỷ đăng ký</span></a> · <a href="https://cyberskill.world/preferences" style="color:#6E3B0E;"><span lang="en">Preferences</span> · <span lang="vi">Tuỳ chọn</span></a></p>
81
81
  </td>
82
82
  </tr>
83
83
  </table>
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
2
+ <html lang="vi" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -22,7 +22,7 @@
22
22
  <span style="font-family:Arial,Helvetica,sans-serif;font-size:19px;font-weight:bold;color:#ffffff;">CyberSkill</span>
23
23
  </td></tr>
24
24
  <!-- English -->
25
- <tr><td class="px card" bgcolor="#ffffff" style="background:#ffffff;padding:32px 32px 4px;">
25
+ <tr><td class="px card" lang="en" bgcolor="#ffffff" style="background:#ffffff;padding:32px 32px 4px;">
26
26
  <p style="margin:0 0 8px;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:bold;letter-spacing:1px;text-transform:uppercase;color:#6E3B0E;">A gentle nudge</p>
27
27
  <h1 class="ink" style="margin:0 0 14px;font-family:Arial,Helvetica,sans-serif;font-size:25px;line-height:1.25;font-weight:bold;color:#45210E;">Invoice INV-2026-0142 is 7 days past due</h1>
28
28
  <p class="ink" style="margin:0 0 18px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:1.6;color:#3d2c1e;">These things slip, no drama. If it's already on its way, thank you — please ignore this. If something about the invoice needs a look, just reply.</p>
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
2
+ <html lang="vi" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -77,7 +77,7 @@
77
77
  <tr>
78
78
  <td class="px" bgcolor="#fbf4e9" style="background:#fbf4e9;padding:22px 32px;border-top:1px solid #e7d9c6;">
79
79
  <p class="muted" style="margin:0 0 6px;font-family:Arial,Helvetica,sans-serif;font-size:13px;color:#6e5a4c;">Hiện Thực Hoá Ý Chí · Turn Your Will Into Real</p>
80
- <p class="muted" style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:1.5;color:#6e5a4c;">CyberSkill Co., Ltd · 12 Nguyễn Huệ, Quận 1, Sài Gòn, Việt Nam<br><a href="https://cyberskill.world/unsubscribe" style="color:#6E3B0E;">Unsubscribe / Huỷ đăng ký</a> · <a href="https://cyberskill.world/preferences" style="color:#6E3B0E;">Preferences / Tuỳ chọn</a></p>
80
+ <p class="muted" style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:1.5;color:#6e5a4c;">CyberSkill Co., Ltd · 12 Nguyễn Huệ, Quận 1, Sài Gòn, Việt Nam<br><a href="https://cyberskill.world/unsubscribe" style="color:#6E3B0E;"><span lang="en">Unsubscribe</span> · <span lang="vi">Huỷ đăng ký</span></a> · <a href="https://cyberskill.world/preferences" style="color:#6E3B0E;"><span lang="en">Preferences</span> · <span lang="vi">Tuỳ chọn</span></a></p>
81
81
  </td>
82
82
  </tr>
83
83
  </table>
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
2
+ <html lang="vi" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -77,7 +77,7 @@
77
77
  <tr>
78
78
  <td class="px" bgcolor="#fbf4e9" style="background:#fbf4e9;padding:22px 32px;border-top:1px solid #e7d9c6;">
79
79
  <p class="muted" style="margin:0 0 6px;font-family:Arial,Helvetica,sans-serif;font-size:13px;color:#6e5a4c;">Hiện Thực Hoá Ý Chí · Turn Your Will Into Real</p>
80
- <p class="muted" style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:1.5;color:#6e5a4c;">CyberSkill Co., Ltd · 12 Nguyễn Huệ, Quận 1, Sài Gòn, Việt Nam<br><a href="https://cyberskill.world/unsubscribe" style="color:#6E3B0E;">Unsubscribe / Huỷ đăng ký</a> · <a href="https://cyberskill.world/preferences" style="color:#6E3B0E;">Preferences / Tuỳ chọn</a></p>
80
+ <p class="muted" style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:1.5;color:#6e5a4c;">CyberSkill Co., Ltd · 12 Nguyễn Huệ, Quận 1, Sài Gòn, Việt Nam<br><a href="https://cyberskill.world/unsubscribe" style="color:#6E3B0E;"><span lang="en">Unsubscribe</span> · <span lang="vi">Huỷ đăng ký</span></a> · <a href="https://cyberskill.world/preferences" style="color:#6E3B0E;"><span lang="en">Preferences</span> · <span lang="vi">Tuỳ chọn</span></a></p>
81
81
  </td>
82
82
  </tr>
83
83
  </table>