@cyberskill/design 1.5.0 → 1.6.0

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 (196) hide show
  1. package/DESIGN.md +1 -1
  2. package/README.md +4 -4
  3. package/SKILL.md +3 -3
  4. package/VERSION +1 -1
  5. package/_ds_bundle.js +2 -2
  6. package/_esm/cs.mjs +2 -2
  7. package/docs/contrast-report.md +1 -1
  8. package/docs/conventions.md +4 -4
  9. package/docs/quality-gates.md +3 -1
  10. package/docs/release-notes.md +1 -1
  11. package/docs/vi/contrast-report.md +1 -1
  12. package/docs/vi/conventions.md +2 -2
  13. package/docs/vi/quality-gates.md +3 -1
  14. package/docs/vi/release-notes.md +1 -1
  15. package/llms.txt +3 -3
  16. package/package.json +6 -4
  17. package/templates/_vendor/doc-export.js +435 -0
  18. package/templates/_vendor/support.js +1860 -0
  19. package/templates/app-shell/ds-base.js +8 -0
  20. package/templates/app-shell/support.js +41 -15
  21. package/templates/article/ds-base.js +8 -0
  22. package/templates/article/support.js +41 -15
  23. package/templates/auth/ds-base.js +8 -0
  24. package/templates/auth/support.js +41 -15
  25. package/templates/bod-deck/ds-base.js +8 -0
  26. package/templates/bod-deck/support.js +41 -15
  27. package/templates/bod-investor-update/ds-base.js +8 -0
  28. package/templates/bod-investor-update/support.js +41 -15
  29. package/templates/bod-memo/ds-base.js +8 -0
  30. package/templates/bod-memo/support.js +41 -15
  31. package/templates/bod-minutes/ds-base.js +8 -0
  32. package/templates/bod-minutes/support.js +41 -15
  33. package/templates/bod-report/ds-base.js +8 -0
  34. package/templates/bod-report/support.js +41 -15
  35. package/templates/bod-resolution/ds-base.js +8 -0
  36. package/templates/bod-resolution/support.js +41 -15
  37. package/templates/culture-event-invite/ds-base.js +8 -0
  38. package/templates/culture-event-invite/support.js +41 -15
  39. package/templates/dashboard/ds-base.js +8 -0
  40. package/templates/dashboard/support.js +41 -15
  41. package/templates/delivery-change-note/ds-base.js +8 -0
  42. package/templates/delivery-change-note/support.js +41 -15
  43. package/templates/delivery-kickoff/ds-base.js +8 -0
  44. package/templates/delivery-kickoff/support.js +41 -15
  45. package/templates/delivery-qbr-deck/ds-base.js +8 -0
  46. package/templates/delivery-qbr-deck/support.js +41 -15
  47. package/templates/delivery-runbook/ds-base.js +8 -0
  48. package/templates/delivery-runbook/support.js +41 -15
  49. package/templates/delivery-status-email/ds-base.js +8 -0
  50. package/templates/delivery-status-email/support.js +41 -15
  51. package/templates/doc-style-guide/ds-base.js +8 -0
  52. package/templates/doc-style-guide/support.js +41 -15
  53. package/templates/doc-suite-index/ds-base.js +8 -0
  54. package/templates/doc-suite-index/support.js +41 -15
  55. package/templates/doc-templates/ds-base.js +8 -0
  56. package/templates/doc-templates/support.js +41 -15
  57. package/templates/email/ds-base.js +8 -0
  58. package/templates/email/support.js +41 -15
  59. package/templates/finance-dunning-email/ds-base.js +8 -0
  60. package/templates/finance-dunning-email/support.js +41 -15
  61. package/templates/finance-expense-report/ds-base.js +8 -0
  62. package/templates/finance-expense-report/support.js +41 -15
  63. package/templates/finance-invoice/ds-base.js +8 -0
  64. package/templates/finance-invoice/support.js +41 -15
  65. package/templates/finance-quote/ds-base.js +8 -0
  66. package/templates/finance-quote/support.js +41 -15
  67. package/templates/hr-announcement/ds-base.js +8 -0
  68. package/templates/hr-announcement/support.js +41 -15
  69. package/templates/hr-interview-kit/ds-base.js +8 -0
  70. package/templates/hr-interview-kit/support.js +41 -15
  71. package/templates/hr-offboarding/ds-base.js +8 -0
  72. package/templates/hr-offboarding/support.js +41 -15
  73. package/templates/hr-performance-review/ds-base.js +8 -0
  74. package/templates/hr-performance-review/support.js +41 -15
  75. package/templates/hr-pip/ds-base.js +8 -0
  76. package/templates/hr-pip/support.js +41 -15
  77. package/templates/legal-msa/ds-base.js +8 -0
  78. package/templates/legal-msa/support.js +41 -15
  79. package/templates/legal-sow/ds-base.js +8 -0
  80. package/templates/legal-sow/support.js +41 -15
  81. package/templates/marketing-campaign-brief/ds-base.js +8 -0
  82. package/templates/marketing-campaign-brief/support.js +41 -15
  83. package/templates/marketing-case-study/ds-base.js +8 -0
  84. package/templates/marketing-case-study/support.js +41 -15
  85. package/templates/marketing-launch/ds-base.js +8 -0
  86. package/templates/marketing-launch/support.js +41 -15
  87. package/templates/marketing-newsletter/ds-base.js +8 -0
  88. package/templates/marketing-newsletter/support.js +41 -15
  89. package/templates/marketing-page/ds-base.js +8 -0
  90. package/templates/marketing-page/support.js +41 -15
  91. package/templates/marketing-press-release/ds-base.js +8 -0
  92. package/templates/marketing-press-release/support.js +41 -15
  93. package/templates/marketing-social-kit/ds-base.js +8 -0
  94. package/templates/marketing-social-kit/support.js +41 -15
  95. package/templates/sales-one-pager/ds-base.js +8 -0
  96. package/templates/sales-one-pager/support.js +41 -15
  97. package/templates/sales-proposal-deck/ds-base.js +8 -0
  98. package/templates/sales-proposal-deck/support.js +41 -15
  99. package/templates/settings/ds-base.js +8 -0
  100. package/templates/settings/support.js +41 -15
  101. package/templates/slide-deck/ds-base.js +8 -0
  102. package/templates/slide-deck/support.js +41 -15
  103. package/templates/team-meeting-agenda/ds-base.js +8 -0
  104. package/templates/team-meeting-agenda/support.js +41 -15
  105. package/templates/tech-incident-report/ds-base.js +8 -0
  106. package/templates/tech-incident-report/support.js +41 -15
  107. package/templates/tech-release-notes/ds-base.js +8 -0
  108. package/templates/tech-release-notes/support.js +41 -15
  109. package/templates/tech-retro/ds-base.js +8 -0
  110. package/templates/tech-retro/support.js +41 -15
  111. package/templates/tech-rfc/ds-base.js +8 -0
  112. package/templates/tech-rfc/support.js +41 -15
  113. package/templates/vn-appointment-transfer/ds-base.js +8 -0
  114. package/templates/vn-appointment-transfer/support.js +41 -15
  115. package/templates/vn-certificate-of-employment/ds-base.js +8 -0
  116. package/templates/vn-certificate-of-employment/support.js +41 -15
  117. package/templates/vn-code-of-conduct/ds-base.js +8 -0
  118. package/templates/vn-code-of-conduct/support.js +41 -15
  119. package/templates/vn-compensation-regulation/ds-base.js +8 -0
  120. package/templates/vn-compensation-regulation/support.js +41 -15
  121. package/templates/vn-contract-amendment/ds-base.js +8 -0
  122. package/templates/vn-contract-amendment/support.js +41 -15
  123. package/templates/vn-contractor-agreement/ds-base.js +8 -0
  124. package/templates/vn-contractor-agreement/support.js +41 -15
  125. package/templates/vn-data-protection-policy/ds-base.js +8 -0
  126. package/templates/vn-data-protection-policy/support.js +41 -15
  127. package/templates/vn-disciplinary-case-file/ds-base.js +8 -0
  128. package/templates/vn-disciplinary-case-file/support.js +41 -15
  129. package/templates/vn-disciplinary-schedule/ds-base.js +8 -0
  130. package/templates/vn-disciplinary-schedule/support.js +41 -15
  131. package/templates/vn-employee-request/ds-base.js +8 -0
  132. package/templates/vn-employee-request/support.js +41 -15
  133. package/templates/vn-exit-handover/ds-base.js +8 -0
  134. package/templates/vn-exit-handover/support.js +41 -15
  135. package/templates/vn-foreign-employee-pack/ds-base.js +8 -0
  136. package/templates/vn-foreign-employee-pack/support.js +41 -15
  137. package/templates/vn-framework-agreement/ds-base.js +8 -0
  138. package/templates/vn-framework-agreement/support.js +41 -15
  139. package/templates/vn-grassroots-democracy/ds-base.js +8 -0
  140. package/templates/vn-grassroots-democracy/support.js +41 -15
  141. package/templates/vn-infosec-policy/ds-base.js +8 -0
  142. package/templates/vn-infosec-policy/support.js +41 -15
  143. package/templates/vn-internal-labor-regulations/ds-base.js +8 -0
  144. package/templates/vn-internal-labor-regulations/support.js +41 -15
  145. package/templates/vn-internship-agreement/ds-base.js +8 -0
  146. package/templates/vn-internship-agreement/support.js +41 -15
  147. package/templates/vn-job-description/ds-base.js +8 -0
  148. package/templates/vn-job-description/support.js +41 -15
  149. package/templates/vn-labor-contract/ds-base.js +8 -0
  150. package/templates/vn-labor-contract/support.js +41 -15
  151. package/templates/vn-labour-management-book/ds-base.js +8 -0
  152. package/templates/vn-labour-management-book/support.js +41 -15
  153. package/templates/vn-mutual-nda/ds-base.js +8 -0
  154. package/templates/vn-mutual-nda/support.js +41 -15
  155. package/templates/vn-ndnca-ip/ds-base.js +8 -0
  156. package/templates/vn-ndnca-ip/support.js +41 -15
  157. package/templates/vn-offer-letter/ds-base.js +8 -0
  158. package/templates/vn-offer-letter/support.js +41 -15
  159. package/templates/vn-onboarding-checklist/ds-base.js +8 -0
  160. package/templates/vn-onboarding-checklist/support.js +41 -15
  161. package/templates/vn-pdpl-consent/ds-base.js +8 -0
  162. package/templates/vn-pdpl-consent/support.js +41 -15
  163. package/templates/vn-performance-evaluation-regulation/ds-base.js +8 -0
  164. package/templates/vn-performance-evaluation-regulation/support.js +41 -15
  165. package/templates/vn-performance-review-form/ds-base.js +8 -0
  166. package/templates/vn-performance-review-form/support.js +41 -15
  167. package/templates/vn-phantom-stock/ds-base.js +8 -0
  168. package/templates/vn-phantom-stock/support.js +41 -15
  169. package/templates/vn-probation-contract/ds-base.js +8 -0
  170. package/templates/vn-probation-contract/support.js +41 -15
  171. package/templates/vn-receipt-acknowledgement/ds-base.js +8 -0
  172. package/templates/vn-receipt-acknowledgement/support.js +41 -15
  173. package/templates/vn-remote-work-policy/ds-base.js +8 -0
  174. package/templates/vn-remote-work-policy/support.js +41 -15
  175. package/templates/vn-resignation-letter/ds-base.js +8 -0
  176. package/templates/vn-resignation-letter/support.js +41 -15
  177. package/templates/vn-salary-scale/ds-base.js +8 -0
  178. package/templates/vn-salary-scale/support.js +41 -15
  179. package/templates/vn-sow-appendix/ds-base.js +8 -0
  180. package/templates/vn-sow-appendix/support.js +41 -15
  181. package/templates/vn-termination-decision/ds-base.js +8 -0
  182. package/templates/vn-termination-decision/support.js +41 -15
  183. package/templates/vn-total-rewards-appendix/ds-base.js +8 -0
  184. package/templates/vn-total-rewards-appendix/support.js +41 -15
  185. package/templates/vn-training-commitment/ds-base.js +8 -0
  186. package/templates/vn-training-commitment/support.js +41 -15
  187. package/templates/vn-travel-expense-policy/ds-base.js +8 -0
  188. package/templates/vn-travel-expense-policy/support.js +41 -15
  189. package/tokens/element-seeds.json +1 -1
  190. package/tokens/native/CSTokens.kt +1 -1
  191. package/tokens/native/CSTokens.swift +1 -1
  192. package/tokens/native/cs_tokens.dart +1 -1
  193. package/tokens/provenance.json +6 -6
  194. package/tokens/tokens.dtcg.json +1 -1
  195. package/tokens/tokens.js +2 -2
  196. package/tokens/tokens.json +1 -1
@@ -0,0 +1,435 @@
1
+ /**
2
+ * CyberSkill document export — PDF (print) + editable OOXML DOCX.
3
+ * Loaded by ds-base.js when <meta name="omelette-owns-print"> is present.
4
+ * No CDN; ZIP via CompressionStream (deflate-raw).
5
+ */
6
+ (() => {
7
+ const UMBER = '45210E';
8
+ const A4 = { w: 11906, h: 16838, name: 'A4' }; // twips
9
+ const LETTER = { w: 12240, h: 15840, name: 'Letter' };
10
+
11
+ function pageSizeFromCss() {
12
+ const styles = [...document.querySelectorAll('style')].map((s) => s.textContent || '').join('\n');
13
+ if (/@page\s*\{[^}]*size:\s*letter/i.test(styles)) return LETTER;
14
+ return A4;
15
+ }
16
+
17
+ function xmlEscape(s) {
18
+ return String(s)
19
+ .replace(/&/g, '&amp;')
20
+ .replace(/</g, '&lt;')
21
+ .replace(/>/g, '&gt;')
22
+ .replace(/"/g, '&quot;');
23
+ }
24
+
25
+ function crc32(buf) {
26
+ let c = ~0;
27
+ for (let i = 0; i < buf.length; i++) {
28
+ c ^= buf[i];
29
+ for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
30
+ }
31
+ return ~c >>> 0;
32
+ }
33
+
34
+ async function deflateRaw(u8) {
35
+ if (typeof CompressionStream === 'undefined') {
36
+ // Store uncompressed (ZIP method 0) — still a valid OOXML package.
37
+ return { data: u8, method: 0 };
38
+ }
39
+ const cs = new CompressionStream('deflate-raw');
40
+ const writer = cs.writable.getWriter();
41
+ writer.write(u8);
42
+ writer.close();
43
+ const ab = await new Response(cs.readable).arrayBuffer();
44
+ return { data: new Uint8Array(ab), method: 8 };
45
+ }
46
+
47
+ async function buildZip(files) {
48
+ // files: [{ name, bytes: Uint8Array }]
49
+ const enc = new TextEncoder();
50
+ const locals = [];
51
+ const centrals = [];
52
+ let offset = 0;
53
+ for (const f of files) {
54
+ const nameBytes = enc.encode(f.name);
55
+ const { data, method } = await deflateRaw(f.bytes);
56
+ const crc = crc32(f.bytes);
57
+ const local = new Uint8Array(30 + nameBytes.length + data.length);
58
+ const dv = new DataView(local.buffer);
59
+ dv.setUint32(0, 0x04034b50, true);
60
+ dv.setUint16(4, 20, true);
61
+ dv.setUint16(6, 0, true);
62
+ dv.setUint16(8, method, true);
63
+ dv.setUint16(10, 0, true);
64
+ dv.setUint16(12, 0, true);
65
+ dv.setUint32(14, crc, true);
66
+ dv.setUint32(18, data.length, true);
67
+ dv.setUint32(22, f.bytes.length, true);
68
+ dv.setUint16(26, nameBytes.length, true);
69
+ dv.setUint16(28, 0, true);
70
+ local.set(nameBytes, 30);
71
+ local.set(data, 30 + nameBytes.length);
72
+ locals.push(local);
73
+
74
+ const central = new Uint8Array(46 + nameBytes.length);
75
+ const cv = new DataView(central.buffer);
76
+ cv.setUint32(0, 0x02014b50, true);
77
+ cv.setUint16(4, 20, true);
78
+ cv.setUint16(6, 20, true);
79
+ cv.setUint16(8, 0, true);
80
+ cv.setUint16(10, method, true);
81
+ cv.setUint16(12, 0, true);
82
+ cv.setUint16(14, 0, true);
83
+ cv.setUint32(16, crc, true);
84
+ cv.setUint32(20, data.length, true);
85
+ cv.setUint32(24, f.bytes.length, true);
86
+ cv.setUint16(28, nameBytes.length, true);
87
+ cv.setUint16(30, 0, true);
88
+ cv.setUint16(32, 0, true);
89
+ cv.setUint16(34, 0, true);
90
+ cv.setUint16(36, 0, true);
91
+ cv.setUint32(38, 0, true);
92
+ cv.setUint32(42, offset, true);
93
+ central.set(nameBytes, 46);
94
+ centrals.push(central);
95
+ offset += local.length;
96
+ }
97
+ const centralSize = centrals.reduce((n, b) => n + b.length, 0);
98
+ const end = new Uint8Array(22);
99
+ const ev = new DataView(end.buffer);
100
+ ev.setUint32(0, 0x06054b50, true);
101
+ ev.setUint16(4, 0, true);
102
+ ev.setUint16(6, 0, true);
103
+ ev.setUint16(8, files.length, true);
104
+ ev.setUint16(10, files.length, true);
105
+ ev.setUint32(12, centralSize, true);
106
+ ev.setUint32(16, offset, true);
107
+ ev.setUint16(20, 0, true);
108
+
109
+ const total =
110
+ locals.reduce((n, b) => n + b.length, 0) + centralSize + end.length;
111
+ const out = new Uint8Array(total);
112
+ let o = 0;
113
+ for (const b of locals) {
114
+ out.set(b, o);
115
+ o += b.length;
116
+ }
117
+ for (const b of centrals) {
118
+ out.set(b, o);
119
+ o += b.length;
120
+ }
121
+ out.set(end, o);
122
+ return out;
123
+ }
124
+
125
+ function textRuns(el) {
126
+ const parts = [];
127
+ const walk = (node, italic, bold, color) => {
128
+ if (node.nodeType === 3) {
129
+ const t = node.textContent;
130
+ if (t && t.replace(/\s+/g, '').length) parts.push({ t, italic, bold, color });
131
+ return;
132
+ }
133
+ if (node.nodeType !== 1) return;
134
+ if (node.getAttribute && node.getAttribute('data-omelette-chrome') != null) return;
135
+ const tag = node.tagName.toLowerCase();
136
+ if (tag === 'script' || tag === 'style' || tag === 'noscript') return;
137
+ const st = window.getComputedStyle(node);
138
+ const nextItalic =
139
+ italic || st.fontStyle === 'italic' || tag === 'i' || tag === 'em';
140
+ const nextBold =
141
+ bold ||
142
+ Number(st.fontWeight) >= 600 ||
143
+ tag === 'b' ||
144
+ tag === 'strong' ||
145
+ /^h[1-6]$/.test(tag);
146
+ let nextColor = color;
147
+ if (/^h[1-6]$/.test(tag) || (st.color && /rgb\(\s*69\s*,\s*33\s*,\s*14\s*\)/.test(st.color))) {
148
+ nextColor = UMBER;
149
+ }
150
+ for (const c of node.childNodes) walk(c, nextItalic, nextBold, nextColor);
151
+ };
152
+ walk(el, false, false, null);
153
+ return parts;
154
+ }
155
+
156
+ function runsToXml(runs) {
157
+ if (!runs.length) {
158
+ return '<w:r><w:t xml:space="preserve"></w:t></w:r>';
159
+ }
160
+ return runs
161
+ .map((r) => {
162
+ const rPr = [];
163
+ if (r.bold) rPr.push('<w:b/>');
164
+ if (r.italic) rPr.push('<w:i/>');
165
+ if (r.color) rPr.push(`<w:color w:val="${r.color}"/>`);
166
+ rPr.push('<w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/>');
167
+ const pr = rPr.length ? `<w:rPr>${rPr.join('')}</w:rPr>` : '';
168
+ return `<w:r>${pr}<w:t xml:space="preserve">${xmlEscape(r.t)}</w:t></w:r>`;
169
+ })
170
+ .join('');
171
+ }
172
+
173
+ function paraXml(el, style) {
174
+ const runs = textRuns(el);
175
+ if (!runs.length && !el.querySelector?.('br')) return '';
176
+ const pPr = style ? `<w:pPr><w:pStyle w:val="${style}"/></w:pPr>` : '';
177
+ return `<w:p>${pPr}${runsToXml(runs)}</w:p>`;
178
+ }
179
+
180
+ function tableXml(table) {
181
+ const rows = [...table.querySelectorAll(':scope > tbody > tr, :scope > thead > tr, :scope > tr')];
182
+ if (!rows.length) return '';
183
+ const gridCols = Math.max(...rows.map((r) => r.children.length), 1);
184
+ const colW = Math.floor(9000 / gridCols);
185
+ let xml = `<w:tbl><w:tblPr><w:tblW w:w="5000" w:type="pct"/><w:tblBorders>
186
+ <w:top w:val="single" w:sz="4" w:color="C9B8A8"/>
187
+ <w:left w:val="single" w:sz="4" w:color="C9B8A8"/>
188
+ <w:bottom w:val="single" w:sz="4" w:color="C9B8A8"/>
189
+ <w:right w:val="single" w:sz="4" w:color="C9B8A8"/>
190
+ <w:insideH w:val="single" w:sz="4" w:color="C9B8A8"/>
191
+ <w:insideV w:val="single" w:sz="4" w:color="C9B8A8"/>
192
+ </w:tblBorders></w:tblPr><w:tblGrid>${'<w:gridCol w:w="' + colW + '"/>'.repeat(gridCols)}</w:tblGrid>`;
193
+ for (const tr of rows) {
194
+ xml += '<w:tr>';
195
+ for (const cell of tr.children) {
196
+ const bg = window.getComputedStyle(cell).backgroundColor;
197
+ const shade =
198
+ bg && /rgb\(\s*69\s*,\s*33\s*,\s*14\s*\)/.test(bg)
199
+ ? `<w:tcPr><w:shd w:val="clear" w:fill="${UMBER}"/><w:tcW w:w="${colW}" w:type="dxa"/></w:tcPr>`
200
+ : `<w:tcPr><w:tcW w:w="${colW}" w:type="dxa"/></w:tcPr>`;
201
+ const cellColor = shade.includes(UMBER) ? 'FFFFFF' : null;
202
+ const runs = textRuns(cell).map((r) =>
203
+ cellColor ? { ...r, color: r.color || cellColor } : r,
204
+ );
205
+ xml += `<w:tc>${shade}<w:p>${runsToXml(runs)}</w:p></w:tc>`;
206
+ }
207
+ xml += '</w:tr>';
208
+ }
209
+ xml += '</w:tbl>';
210
+ return xml;
211
+ }
212
+
213
+ function sheetBodyXml(sheet) {
214
+ const blocks = [];
215
+ const kids = [...sheet.children];
216
+ const visit = (nodes) => {
217
+ for (const el of nodes) {
218
+ if (el.nodeType !== 1) continue;
219
+ if (el.getAttribute('data-omelette-chrome') != null) continue;
220
+ const tag = el.tagName.toLowerCase();
221
+ if (tag === 'table') {
222
+ blocks.push(tableXml(el));
223
+ continue;
224
+ }
225
+ if (/^h[1-6]$/.test(tag)) {
226
+ blocks.push(paraXml(el, 'Heading1'));
227
+ continue;
228
+ }
229
+ if (tag === 'p' || tag === 'div' || tag === 'section' || tag === 'li') {
230
+ // Prefer leaf paragraphs; if a div only wraps structure, descend.
231
+ const hasBlock = [...el.children].some((c) =>
232
+ /^(DIV|TABLE|SECTION|UL|OL|H1|H2|H3|H4|H5|H6)$/.test(c.tagName),
233
+ );
234
+ if (hasBlock && tag !== 'p') {
235
+ visit([...el.children]);
236
+ } else {
237
+ const px = paraXml(el);
238
+ if (px) blocks.push(px);
239
+ }
240
+ continue;
241
+ }
242
+ if (tag === 'ul' || tag === 'ol') {
243
+ visit([...el.children]);
244
+ continue;
245
+ }
246
+ visit([...el.children]);
247
+ }
248
+ };
249
+ visit(kids);
250
+ return blocks.filter(Boolean).join('');
251
+ }
252
+
253
+ function headerFooterText(sheet) {
254
+ const headerEl = sheet.querySelector(':scope > div');
255
+ const header = headerEl ? textRuns(headerEl).map((r) => r.t).join('').trim() : 'CyberSkill';
256
+ const footer = 'CyberSkill · MST 0316475200 · Hiện Thực Hoá Ý Chí';
257
+ return { header: header.slice(0, 200) || 'CyberSkill', footer };
258
+ }
259
+
260
+ async function buildDocxBlob(sheet) {
261
+ const page = pageSizeFromCss();
262
+ const enc = new TextEncoder();
263
+ const { header, footer } = headerFooterText(sheet);
264
+ const body = sheetBodyXml(sheet);
265
+
266
+ const contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
267
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
268
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
269
+ <Default Extension="xml" ContentType="application/xml"/>
270
+ <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
271
+ <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
272
+ <Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/>
273
+ <Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>
274
+ </Types>`;
275
+
276
+ const rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
277
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
278
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
279
+ </Relationships>`;
280
+
281
+ const docRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
282
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
283
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
284
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/>
285
+ <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/>
286
+ </Relationships>`;
287
+
288
+ const styles = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
289
+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
290
+ <w:style w:type="paragraph" w:default="1" w:styleId="Normal">
291
+ <w:name w:val="Normal"/><w:qFormat/>
292
+ <w:rPr><w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/><w:sz w:val="22"/></w:rPr>
293
+ </w:style>
294
+ <w:style w:type="paragraph" w:styleId="Heading1">
295
+ <w:name w:val="heading 1"/><w:basedOn w:val="Normal"/><w:qFormat/>
296
+ <w:rPr><w:b/><w:color w:val="${UMBER}"/><w:sz w:val="28"/></w:rPr>
297
+ </w:style>
298
+ </w:styles>`;
299
+
300
+ const headerXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
301
+ <w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
302
+ <w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:color="C9B8A8"/></w:pBdr></w:pPr>
303
+ <w:r><w:rPr><w:b/><w:color w:val="${UMBER}"/><w:sz w:val="18"/></w:rPr>
304
+ <w:t xml:space="preserve">${xmlEscape(header)}</w:t></w:r>
305
+ </w:p>
306
+ </w:hdr>`;
307
+
308
+ const footerXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
309
+ <w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
310
+ xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml">
311
+ <w:p>
312
+ <w:r><w:rPr><w:sz w:val="16"/><w:color w:val="6B5D4F"/></w:rPr>
313
+ <w:t xml:space="preserve">${xmlEscape(footer)} · </w:t></w:r>
314
+ <w:r><w:fldChar w:fldCharType="begin"/></w:r>
315
+ <w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
316
+ <w:r><w:fldChar w:fldCharType="end"/></w:r>
317
+ </w:p>
318
+ </w:ftr>`;
319
+
320
+ const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
321
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
322
+ xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
323
+ <w:body>
324
+ ${body || '<w:p><w:r><w:t></w:t></w:r></w:p>'}
325
+ <w:sectPr>
326
+ <w:headerReference w:type="default" r:id="rId2"/>
327
+ <w:footerReference w:type="default" r:id="rId3"/>
328
+ <w:pgSz w:w="${page.w}" w:h="${page.h}"/>
329
+ <w:pgMar w:top="1440" w:right="1134" w:bottom="1134" w:left="1134" w:header="720" w:footer="720"/>
330
+ </w:sectPr>
331
+ </w:body>
332
+ </w:document>`;
333
+
334
+ const zip = await buildZip([
335
+ { name: '[Content_Types].xml', bytes: enc.encode(contentTypes) },
336
+ { name: '_rels/.rels', bytes: enc.encode(rels) },
337
+ { name: 'word/document.xml', bytes: enc.encode(documentXml) },
338
+ { name: 'word/_rels/document.xml.rels', bytes: enc.encode(docRels) },
339
+ { name: 'word/styles.xml', bytes: enc.encode(styles) },
340
+ { name: 'word/header1.xml', bytes: enc.encode(headerXml) },
341
+ { name: 'word/footer1.xml', bytes: enc.encode(footerXml) },
342
+ ]);
343
+ return new Blob([zip], {
344
+ type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
345
+ });
346
+ }
347
+
348
+ function downloadBlob(blob, filename) {
349
+ const a = document.createElement('a');
350
+ a.href = URL.createObjectURL(blob);
351
+ a.download = filename;
352
+ a.rel = 'noopener';
353
+ document.body.appendChild(a);
354
+ a.click();
355
+ setTimeout(() => {
356
+ URL.revokeObjectURL(a.href);
357
+ a.remove();
358
+ }, 1500);
359
+ }
360
+
361
+ function suggestName(ext) {
362
+ const t = (document.title || 'document').replace(/\s*[·|].*$/, '').trim() || 'document';
363
+ return t.replace(/[^\w\u00C0-\u024F\- ]+/g, '').replace(/\s+/g, '-').slice(0, 80) + ext;
364
+ }
365
+
366
+ function injectToolbar() {
367
+ if (document.querySelector('[data-omelette-chrome="export"]')) return;
368
+ const meta = document.querySelector('meta[name="omelette-owns-print"]');
369
+ if (!meta) return;
370
+
371
+ const style = document.createElement('style');
372
+ style.setAttribute('data-omelette-chrome', 'export-style');
373
+ style.textContent = `
374
+ [data-omelette-chrome="export"]{
375
+ position:fixed;z-index:2147483000;top:12px;right:12px;display:flex;gap:8px;
376
+ font-family:var(--cs-font-family-ui,system-ui,sans-serif);pointer-events:auto;
377
+ }
378
+ [data-omelette-chrome="export"] button{
379
+ appearance:none;border:1px solid #c9b8a8;background:#fff;color:#45210E;
380
+ border-radius:8px;padding:8px 12px;font-size:12.5px;font-weight:700;cursor:pointer;
381
+ box-shadow:0 1px 2px rgba(35,26,18,.08);
382
+ }
383
+ [data-omelette-chrome="export"] button:hover{background:#f7f0e6}
384
+ [data-omelette-chrome="export"] button:focus-visible{outline:2px solid #F4BA17;outline-offset:2px}
385
+ @media print{
386
+ [data-omelette-chrome="export"],[data-omelette-chrome="export-style"]{display:none!important}
387
+ }
388
+ `;
389
+ document.head.appendChild(style);
390
+
391
+ const bar = document.createElement('div');
392
+ bar.setAttribute('data-omelette-chrome', 'export');
393
+ bar.setAttribute('role', 'toolbar');
394
+ bar.setAttribute('aria-label', 'Document export · Xuất tài liệu');
395
+
396
+ const pdfBtn = document.createElement('button');
397
+ pdfBtn.type = 'button';
398
+ pdfBtn.textContent = 'PDF · In / Print';
399
+ pdfBtn.addEventListener('click', () => window.print());
400
+
401
+ const docxBtn = document.createElement('button');
402
+ docxBtn.type = 'button';
403
+ docxBtn.textContent = 'DOCX · Tải / Download';
404
+ docxBtn.addEventListener('click', async () => {
405
+ try {
406
+ const sheet = document.querySelector('.cs-sheet') || document.body;
407
+ const blob = await buildDocxBlob(sheet);
408
+ downloadBlob(blob, suggestName('.docx'));
409
+ } catch (e) {
410
+ console.error('[doc-export] DOCX failed', e);
411
+ alert('DOCX export failed: ' + (e && e.message ? e.message : e));
412
+ }
413
+ });
414
+
415
+ bar.appendChild(pdfBtn);
416
+ bar.appendChild(docxBtn);
417
+ document.body.appendChild(bar);
418
+ }
419
+
420
+ // Test / automation hooks
421
+ window.__csDocExport = {
422
+ buildDocxBlob,
423
+ pageSizeFromCss,
424
+ injectToolbar,
425
+ };
426
+
427
+ const boot = () => injectToolbar();
428
+ if (document.readyState === 'loading') {
429
+ document.addEventListener('DOMContentLoaded', boot);
430
+ } else {
431
+ boot();
432
+ }
433
+ // DC boots async — re-try after paint so .cs-sheet exists for future exports.
434
+ setTimeout(boot, 800);
435
+ })();