spec-up-t 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/create-pdf.js CHANGED
@@ -6,52 +6,45 @@ const pdfLib = require('pdf-lib');
6
6
  (async () => {
7
7
  try {
8
8
  // Launch a new browser instance
9
- // Production
10
9
  const browser = await puppeteer.launch();
11
-
12
- // Test
13
- // const browser = await puppeteer.launch({ headless: false, devtools: true }); // Open DevTools automatically
14
-
15
10
  const page = await browser.newPage();
16
11
 
17
12
  // Read and parse the specs.json file
18
13
  const config = fs.readJsonSync('specs.json');
19
14
 
20
- // Extract the output_path from the specs.json file
15
+ // Extract configuration details
21
16
  const outputPath = config.specs[0].output_path;
22
-
23
- // Extract title and description from the config
24
17
  const title = config.specs[0].title || '';
25
18
  const description = config.specs[0].description || '';
26
19
  const logo = config.specs[0].logo || '';
27
20
  const logoLink = config.specs[0].logo_link || '#';
28
21
 
29
- // Define the path to the HTML file based on the directory where the script is called from
22
+ // Define the HTML file path
30
23
  const filePath = path.resolve(process.cwd(), outputPath, 'index.html');
31
24
  const fileUrl = `file://${filePath}`;
32
25
 
33
- // Path to Bootstrap CSS file
26
+ // Path to Bootstrap CSS
34
27
  const bootstrapCssPath = path.resolve(process.cwd(), 'assets/css/bootstrap.min.css');
35
-
36
- // Check if Bootstrap CSS file exists
37
28
  const bootstrapExists = fs.existsSync(bootstrapCssPath);
38
-
39
- // Read Bootstrap CSS if it exists
40
- let bootstrapCss = '';
41
- if (bootstrapExists) {
42
- bootstrapCss = fs.readFileSync(bootstrapCssPath, 'utf8');
43
- }
29
+ let bootstrapCss = bootstrapExists ? fs.readFileSync(bootstrapCssPath, 'utf8') : '';
30
+
31
+ // Path to PDF styles CSS
32
+ const pdfStylesPath = path.resolve(process.cwd(), 'assets/css/pdf-styles.css');
33
+ const pdfStylesExist = fs.existsSync(pdfStylesPath);
34
+ const pdfStylesCss = pdfStylesExist ? fs.readFileSync(pdfStylesPath, 'utf8') : '';
44
35
 
45
36
  // Navigate to the HTML file
46
37
  await page.goto(fileUrl, { waitUntil: 'networkidle2' });
47
38
 
48
- // Handle cross-reference terms that may be fetched from another domain
39
+ // Clean up unnecessary elements but be careful not to remove styles we need
40
+ await page.evaluate(() => {
41
+ document.querySelectorAll('[style*="display: none"], .d-print-none, script').forEach(element => element.remove());
42
+ // Don't remove all style elements as some might be important
43
+ });
44
+
45
+ // Handle dynamically fetched cross-reference terms
49
46
  const targetClass = '.fetched-xref-term';
50
-
51
- // Try to get the first element with the target class
52
47
  const targetElement = await page.$(targetClass);
53
-
54
- // If such an element exists, wait for its content to be fully loaded
55
48
  if (targetElement) {
56
49
  const targetText = await page.evaluate(el => el.innerText, targetElement);
57
50
  await page.waitForFunction(
@@ -59,428 +52,188 @@ const pdfLib = require('pdf-lib');
59
52
  const element = document.querySelector(targetClass);
60
53
  return element && element.innerText !== targetText;
61
54
  },
62
- {}, // Additional options if needed
55
+ {},
63
56
  targetClass,
64
57
  targetText
65
58
  );
66
59
  }
67
60
 
68
- // Inject CSS to set padding, enforce system fonts, and apply Bootstrap print styles
69
- await page.evaluate((bootstrapCss) => {
70
- // Add Bootstrap CSS including its print media queries
61
+ // Force larger width on page content BEFORE injecting styles
62
+ await page.evaluate(() => {
63
+ // Select main content containers and expand them
64
+ const containers = document.querySelectorAll('.container, main, section, article, .content');
65
+ containers.forEach(container => {
66
+ container.style.maxWidth = '95%';
67
+ container.style.width = '95%';
68
+ container.style.margin = '0 auto';
69
+ container.style.padding = '0';
70
+ });
71
+
72
+ // Override any Bootstrap column constraints
73
+ const columns = document.querySelectorAll('[class*="col-"]');
74
+ columns.forEach(col => {
75
+ col.style.maxWidth = '100%';
76
+ col.style.width = '100%';
77
+ col.style.paddingLeft = '0';
78
+ col.style.paddingRight = '0';
79
+ });
80
+
81
+ // Ensure body takes full width
82
+ document.body.style.maxWidth = '100%';
83
+ document.body.style.width = '100%';
84
+ document.body.style.padding = '0';
85
+ document.body.style.margin = '0';
86
+ });
87
+
88
+ // Inject Bootstrap CSS and PDF styles CSS - No inline styling
89
+ await page.evaluate((bootstrapCss, pdfStylesCss) => {
90
+ // Add bootstrap if it exists
71
91
  if (bootstrapCss) {
72
92
  const bootstrapStyle = document.createElement('style');
73
93
  bootstrapStyle.innerHTML = bootstrapCss;
94
+ bootstrapStyle.setAttribute('data-bootstrap', 'true');
74
95
  document.head.appendChild(bootstrapStyle);
75
96
  }
76
-
77
- const style = document.createElement('style');
78
- style.innerHTML = `
79
- @page {
80
- margin-top: 15mm;
81
- margin-bottom: 15mm;
82
- margin-left: 15mm;
83
- margin-right: 15mm;
84
- border: none;
85
- }
86
97
 
87
- /* Override all fonts with system fonts and increase base font size */
88
- * {
89
- font-family: Arial, Helvetica, sans-serif !important;
90
- }
91
-
92
- /* Set base font size to improve readability */
93
- body {
94
- font-size: 14pt !important; /* Medium font size */
95
- line-height: 1.5 !important;
96
- }
97
-
98
- /* Adjust heading sizes proportionally */
99
- h1 { font-size: 22pt !important; font-weight: bold !important; }
100
- h2 { font-size: 20pt !important; font-weight: bold !important; }
101
- h3 { font-size: 18pt !important; font-weight: bold !important; }
102
- h4 { font-size: 16pt !important; font-weight: bold !important; }
103
- h5 { font-size: 15pt !important; font-weight: bold !important; }
104
- h6 { font-size: 14pt !important; font-weight: bold !important; }
105
-
106
- /* Make pre and code blocks more readable */
107
- pre, code {
108
- font-size: 13pt !important;
109
- line-height: 1.4 !important;
110
- }
111
-
112
- /* Make table text more readable */
113
- table, th, td {
114
- font-size: 13pt !important;
115
- }
116
-
117
- /* Additional print-specific styles */
118
- @media print {
119
- a[href]:after {
120
- content: none !important;
121
- }
122
-
123
- .container {
124
- width: auto;
125
- max-width: 100%;
126
- }
127
-
128
- /* Improve page breaks */
129
- h1, h2, h3, h4, h5, h6 {
130
- page-break-after: avoid;
131
- margin-top: 1.2em !important;
132
- margin-bottom: 0.6em !important;
133
- }
134
-
135
- img {
136
- page-break-inside: avoid;
137
- }
138
-
139
- table {
140
- page-break-inside: avoid;
141
- }
142
-
143
- pre, blockquote {
144
- page-break-inside: avoid;
145
- }
146
-
147
- /* Add Bootstrap print classes explicitly */
148
- .d-print-none {
149
- display: none !important;
150
- }
151
-
152
- .d-print-block {
153
- display: block !important;
154
- }
155
-
156
- .d-print-inline {
157
- display: inline !important;
158
- }
159
-
160
- .d-print-inline-block {
161
- display: inline-block !important;
162
- }
163
-
164
- .d-print-flex {
165
- display: flex !important;
166
- }
167
-
168
- .d-print-inline-flex {
169
- display: inline-flex !important;
170
- }
171
- }
172
- `;
173
- document.head.appendChild(style);
174
- }, bootstrapCss);
98
+ // Add PDF styles CSS - all styling is defined here
99
+ if (pdfStylesCss) {
100
+ const style = document.createElement('style');
101
+ style.id = 'pdf-styles';
102
+ style.innerHTML = pdfStylesCss;
103
+ document.head.appendChild(style);
104
+ }
105
+
106
+ // Add print-specific class
107
+ document.body.classList.add('pdf-document', 'print');
108
+ }, bootstrapCss, pdfStylesCss);
175
109
 
176
- // Apply Bootstrap classes to elements in the page
110
+ // Add necessary Bootstrap classes to elements
177
111
  await page.evaluate(() => {
178
- // Add container class to body for better formatting
179
- document.body.classList.add('container-fluid', 'print');
180
-
181
- // Add Bootstrap classes to headings
182
- document.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
183
- heading.classList.add('mt-4', 'mb-3');
184
- });
185
-
186
112
  // Add Bootstrap classes to tables
187
113
  document.querySelectorAll('table').forEach(table => {
188
- table.classList.add('table', 'table-bordered', 'mb-4');
114
+ table.classList.add('table', 'table-bordered', 'table-striped', 'table-hover');
189
115
  });
190
116
 
191
- // Add Bootstrap classes to code blocks
192
- document.querySelectorAll('pre').forEach(pre => {
193
- pre.classList.add('p-3', 'bg-light', 'border', 'rounded');
117
+ // Make sure meta-info is visible and toggle buttons are hidden
118
+ document.querySelectorAll('.meta-info, .term-meta').forEach(meta => {
119
+ meta.style.display = 'block';
120
+ });
121
+
122
+ document.querySelectorAll('.meta-toggle, button.meta-toggle').forEach(button => {
123
+ button.style.display = 'none';
194
124
  });
195
125
  });
196
126
 
197
- // Inject logo, title and description at the top of the document
127
+ // Inject logo, title, and description
198
128
  await page.evaluate((logo, logoLink, title, description) => {
199
- // Create wrapper for the logo, title and description using Bootstrap classes
200
129
  const titleWrapper = document.createElement('div');
201
130
  titleWrapper.className = 'text-center mb-5 pb-4 border-bottom';
202
-
203
- // Add the logo if it exists
131
+
204
132
  if (logo) {
205
133
  const logoContainer = document.createElement('a');
206
- logoContainer.href = logoLink || '#';
134
+ logoContainer.href = logoLink;
207
135
  logoContainer.className = 'd-block mb-3';
208
-
209
136
  const logoImg = document.createElement('img');
210
137
  logoImg.src = logo;
211
138
  logoImg.className = 'img-fluid';
212
- logoImg.style.maxHeight = '90px'; // Moderate logo size
213
- logoImg.alt = title || 'Logo';
214
-
215
139
  logoContainer.appendChild(logoImg);
216
140
  titleWrapper.appendChild(logoContainer);
217
141
  }
218
-
219
- // Create and style the title element
142
+
220
143
  if (title) {
221
144
  const titleElement = document.createElement('h1');
222
145
  titleElement.textContent = title;
223
- titleElement.className = 'display-4 mb-2'; // Moderate title size
224
- titleElement.style.fontSize = '26pt'; // Explicitly set title font size
146
+ titleElement.className = 'display-4 mb-2 pdf-title';
225
147
  titleWrapper.appendChild(titleElement);
226
148
  }
227
-
228
- // Create and style the description element
149
+
229
150
  if (description) {
230
151
  const descriptionElement = document.createElement('p');
231
152
  descriptionElement.textContent = description;
232
- descriptionElement.className = 'lead';
233
- descriptionElement.style.fontSize = '16pt'; // Explicitly set description font size
153
+ descriptionElement.className = 'lead mb-0';
234
154
  titleWrapper.appendChild(descriptionElement);
235
155
  }
236
-
237
- // Insert at the top of the body
238
- const body = document.body;
239
- body.insertBefore(titleWrapper, body.firstChild);
240
- }, logo, logoLink, title, description);
241
156
 
242
- // Remove or hide elements not needed for print
157
+ document.body.insertBefore(titleWrapper, document.body.firstChild);
158
+ }, logo, logoLink, title, description);
159
+
160
+ // Direct manipulation of definition lists to ensure proper styling in PDF
243
161
  await page.evaluate(() => {
244
- // Remove elements with display: none
245
- const hiddenElements = document.querySelectorAll('[style*="display: none"]');
246
- hiddenElements.forEach((element) => {
247
- element.remove();
248
- });
249
-
250
- // Remove elements that should be hidden in print
251
- document.querySelectorAll('.d-print-none').forEach(element => {
252
- element.remove();
253
- });
254
-
255
- // Remove all script elements
256
- const scriptElements = document.querySelectorAll('script');
257
- scriptElements.forEach((element) => {
258
- element.remove();
259
- });
260
-
261
- // Remove most style elements (keeping Bootstrap)
262
- const styleElements = document.querySelectorAll('style:not([data-bootstrap])');
263
- styleElements.forEach((element) => {
264
- element.remove();
265
- });
266
-
267
- // Improve styling for definition terms and descriptions
268
- const termsAndDefs = document.querySelectorAll('dt,dd');
269
- termsAndDefs.forEach((element) => {
270
- // Base styling for all terms and definitions
271
- element.style.backgroundColor = 'white';
272
- element.style.border = 'none';
273
- element.style.pageBreakInside = 'avoid'; // Avoid page breaks within terms/definitions
162
+ // Process all definition lists
163
+ const definitionLists = document.querySelectorAll('dl.terms-and-definitions-list');
164
+ definitionLists.forEach(list => {
165
+ // Process all terms and definitions - target all dt and dd elements regardless of class
166
+ const terms = list.querySelectorAll('dt, dd');
167
+ terms.forEach(term => {
168
+ // Remove background and borders with !important to override any existing styles
169
+ term.setAttribute('style', term.getAttribute('style') + '; background: transparent !important; background-color: transparent !important; background-image: none !important; border: none !important; border-radius: 0 !important; padding: 0.5rem 0 !important;');
170
+ });
274
171
 
275
- if (element.tagName === 'DT') {
276
- // Styling specifically for definition terms
277
- element.style.fontWeight = 'bold';
278
- element.style.marginTop = '1.3em';
279
- element.style.paddingBottom = '0.4em';
280
- element.style.borderBottom = '1px solid #e0e0e0';
281
- element.style.fontSize = '16pt'; // Moderate font size for definition terms
282
- } else if (element.tagName === 'DD') {
283
- // Styling specifically for definition descriptions
284
- element.style.paddingLeft = '1.5em';
285
- element.style.paddingTop = '0.6em';
286
- element.style.paddingBottom = '0.8em';
287
- element.style.marginBottom = '0.8em';
288
- element.style.textAlign = 'justify';
289
- element.style.fontSize = '14pt'; // Moderate font size for definition descriptions
290
-
291
- // Style tables inside definition descriptions
292
- const tables = element.querySelectorAll('table');
293
- tables.forEach(table => {
294
- table.style.width = '100%';
295
- table.style.marginTop = '0.6em';
296
- table.style.marginBottom = '0.6em';
297
- table.style.borderCollapse = 'collapse';
298
-
299
- // Style table cells
300
- const cells = table.querySelectorAll('th, td');
301
- cells.forEach(cell => {
302
- cell.style.border = '1px solid #ddd';
303
- cell.style.padding = '0.4em';
304
- cell.style.fontSize = '13pt'; // Moderate font size for table cells
305
- });
306
- });
307
- }
308
- });
309
-
310
- // Ensure proper spacing between definition groups
311
- const dts = document.querySelectorAll('dt');
312
- dts.forEach((dt, index) => {
313
- if (index > 0) {
314
- dt.style.marginTop = '2em'; // Add moderate space between definition groups
315
- }
172
+ // Ensure all meta-info content is visible
173
+ const metaInfoContents = list.querySelectorAll('dd.meta-info-content-wrapper');
174
+ metaInfoContents.forEach(content => {
175
+ content.style.display = 'block';
176
+ content.style.maxHeight = 'none';
177
+ content.style.height = 'auto';
178
+ content.style.overflow = 'visible';
179
+ content.style.padding = '0.5rem 0';
180
+ content.style.margin = '0';
181
+ content.style.lineHeight = 'normal';
182
+
183
+ // Remove the collapsed class if present
184
+ content.classList.remove('collapsed');
185
+ });
186
+
187
+ // Hide all meta-info toggle buttons
188
+ const toggleButtons = list.querySelectorAll('.meta-info-toggle-button');
189
+ toggleButtons.forEach(button => {
190
+ button.style.display = 'none';
191
+ });
316
192
  });
317
193
 
318
- // Set moderate font size for paragraphs and list items
319
- document.querySelectorAll('p, li').forEach(element => {
320
- element.style.fontSize = '14pt'; // Moderate size for paragraphs and lists
321
- element.style.margin = '0.4em 0';
322
- element.style.lineHeight = '1.5';
194
+ // Special handling for ALL transcluded terms with blue background - no class restrictions
195
+ document.querySelectorAll('.transcluded-xref-term').forEach(el => {
196
+ // Use the most aggressive approach possible to override the blue background
197
+ el.setAttribute('style', el.getAttribute('style') + '; background: transparent !important; background-color: transparent !important; background-image: none !important;');
198
+
199
+ // Also process any child elements to ensure complete removal of background
200
+ Array.from(el.children).forEach(child => {
201
+ child.setAttribute('style', child.getAttribute('style') + '; background: transparent !important; background-color: transparent !important; background-image: none !important;');
202
+ });
323
203
  });
324
204
 
325
- // Set moderate font size for code blocks and inline code
326
- document.querySelectorAll('pre code, code').forEach(element => {
327
- element.style.fontSize = '13pt'; // Moderate size for code blocks
328
- });
329
- });
330
-
331
- // Add hierarchical section numbering for PDF output only
332
- await page.evaluate(() => {
333
- const style = document.createElement('style');
334
- style.innerHTML = `
335
- @media print {
336
- /* Base styles for all numbered elements */
337
- article#content h1,
338
- article#content h2,
339
- article#content h3,
340
- article#content h4,
341
- article#content h5,
342
- article#content h6,
343
- article#content dt {
344
- position: relative;
345
- padding-left: 2.8em; /* Moderate padding for better readability */
346
- text-indent: 0;
347
- }
348
-
349
- /* Number styling for all heading levels */
350
- article#content h1::before,
351
- article#content h2::before,
352
- article#content h3::before,
353
- article#content h4::before,
354
- article#content h5::before,
355
- article#content h6::before,
356
- article#content dt::before {
357
- position: absolute;
358
- left: 0;
359
- font-weight: bold;
360
- color: #333;
361
- font-size: 100%; /* Match the font size of the respective heading */
362
- }
363
-
364
- /* Counter setup for the root element */
365
- article#content {
366
- counter-reset: h1;
367
- }
368
-
369
- /* Level 1 headings */
370
- article#content h1 {
371
- counter-increment: h1;
372
- counter-reset: h2;
373
- padding-left: 1.9em; /* Specific padding for h1 */
374
- }
375
-
376
- article#content h1::before {
377
- content: counter(h1) ".";
378
- }
379
-
380
- /* Level 2 headings */
381
- article#content h2 {
382
- counter-increment: h2;
383
- counter-reset: h3;
384
- padding-left: 2.6em; /* Specific padding for h2 */
385
- }
386
-
387
- article#content h2::before {
388
- content: counter(h1) "." counter(h2) ".";
389
- }
390
-
391
- /* Level 3 headings */
392
- article#content h3 {
393
- counter-increment: h3;
394
- counter-reset: h4;
395
- padding-left: 3.3em; /* Specific padding for h3 */
396
- }
397
-
398
- article#content h3::before {
399
- content: counter(h1) "." counter(h2) "." counter(h3) ".";
400
- }
401
-
402
- /* Level 4 headings */
403
- article#content h4 {
404
- counter-increment: h4;
405
- counter-reset: h5;
406
- padding-left: 4em; /* Specific padding for h4 */
407
- }
408
-
409
- article#content h4::before {
410
- content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) ".";
411
- }
412
-
413
- /* Level 5 headings */
414
- article#content h5 {
415
- counter-increment: h5;
416
- counter-reset: h6;
417
- padding-left: 4.7em; /* Specific padding for h5 */
418
- }
419
-
420
- article#content h5::before {
421
- content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) ".";
422
- }
423
-
424
- /* Level 6 headings */
425
- article#content h6 {
426
- counter-increment: h6;
427
- padding-left: 5.4em; /* Specific padding for h6 */
428
- }
429
-
430
- article#content h6::before {
431
- content: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) ".";
432
- }
433
-
434
- /* Definition terms - treat as level 3 since they appear under level 2 headings */
435
- article#content dt {
436
- counter-increment: h3;
437
- padding-left: 3.3em; /* Match h3 padding */
438
- }
439
-
440
- article#content dt::before {
441
- content: counter(h1) "." counter(h2) "." counter(h3) ".";
442
- position: absolute;
443
- left: 0;
444
- /* Improve vertical alignment with the text */
445
- top: 50%;
446
- transform: translateY(-50%);
447
- line-height: normal;
448
- }
449
-
450
- /* Add spacing after the numbers */
451
- article#content h1::before,
452
- article#content h2::before,
453
- article#content h3::before,
454
- article#content h4::before,
455
- article#content h5::before,
456
- article#content h6::before,
457
- article#content dt::before {
458
- margin-right: 0.4em;
459
- }
205
+ // Remove any inline styles that might be setting backgrounds
206
+ document.querySelectorAll('style').forEach(styleTag => {
207
+ let cssText = styleTag.textContent;
208
+ // If the style tag contains transcluded-xref-term styles, modify them
209
+ if (cssText.includes('transcluded-xref-term') && cssText.includes('background')) {
210
+ cssText = cssText.replace(/dt\.transcluded-xref-term[^}]+}/g,
211
+ 'dt.transcluded-xref-term, dd.transcluded-xref-term { background: transparent !important; background-color: transparent !important; background-image: none !important; }');
212
+ styleTag.textContent = cssText;
460
213
  }
461
- `;
462
- document.head.appendChild(style);
214
+ });
463
215
  });
464
216
 
465
- // Generate the PDF with optimized settings
217
+ // Generate PDF
466
218
  const pdfBuffer = await page.pdf({
467
- path: path.resolve(process.cwd(), 'docs/index.pdf'), // Output file path
468
- format: 'A4', // Paper format
469
- displayHeaderFooter: false, // Do not display header and footer
470
- preferCSSPageSize: true, // Use CSS-defined page size
471
- printBackground: true, // Enable background graphics for Bootstrap styling
472
- quality: 100, // Maximum quality for images
473
- margin: {
474
- top: '18mm', // Moderate margins for better readability
475
- bottom: '18mm',
476
- left: '18mm',
477
- right: '18mm',
478
- }
219
+ path: path.resolve(process.cwd(), 'docs/index.pdf'),
220
+ format: 'A4',
221
+ displayHeaderFooter: true,
222
+ footerTemplate: `
223
+ <div style="width: 100%; text-align: center; font-size: 10pt; margin-top: 10mm;">
224
+ Page <span class="pageNumber"></span> of <span class="totalPages"></span>
225
+ </div>
226
+ `,
227
+ headerTemplate: '<div></div>',
228
+ preferCSSPageSize: true,
229
+ printBackground: true,
230
+ quality: 100,
231
+ margin: { top: '10mm', bottom: '10mm', left: '3mm', right: '3mm' }
479
232
  });
480
233
 
481
234
  await browser.close();
482
235
 
483
- // Load the PDF with pdf-lib to remove metadata and optimize fonts
236
+ // Optimize PDF with pdf-lib
484
237
  const pdfDoc = await pdfLib.PDFDocument.load(pdfBuffer);
485
238
  pdfDoc.setTitle(title);
486
239
  pdfDoc.setAuthor('');
@@ -488,8 +241,6 @@ const pdfLib = require('pdf-lib');
488
241
  pdfDoc.setKeywords([]);
489
242
  pdfDoc.setProducer('');
490
243
  pdfDoc.setCreator('');
491
-
492
- // Save the optimized PDF
493
244
  const optimizedPdfBytes = await pdfDoc.save();
494
245
  fs.writeFileSync('docs/index.pdf', optimizedPdfBytes);
495
246
 
@@ -19,6 +19,9 @@
19
19
  </head>
20
20
 
21
21
  <body features="${features}">
22
+ <!-- Skip to content link for accessibility -->
23
+ <a href="#content" class="screen-reader-text">Skip to content</a>
24
+
22
25
  ${assetsSvg}
23
26
 
24
27
  <!-- Icons for hamburger menu, dark/light buttons -->