@udx/md2html 1.2.1 → 1.3.1

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/index.js CHANGED
@@ -89,7 +89,7 @@ const CHAPTER_NAV_STYLES_PATH = path.join(path.dirname(new URL(import.meta.url).
89
89
  const SCRIPTS_PATH = path.join(path.dirname(new URL(import.meta.url).pathname), 'static/scripts.js');
90
90
 
91
91
  program
92
- .version('1.2.0')
92
+ .version('1.3.0')
93
93
  .description('Convert markdown files to a single HTML document with Google Docs styling')
94
94
  .option('-s, --src <file>', 'Source markdown file or directory')
95
95
  .option('-o, --out <file>', 'Output HTML file path (optional - defaults to ./output.html)')
@@ -97,6 +97,8 @@ program
97
97
  .option('-d, --debug', 'Enable debug logging', false)
98
98
  .option('-p, --preview', 'Open generated HTML in browser with live preview server', false)
99
99
  .option('--port <number>', 'Port for preview server (default: random)', parseInt)
100
+ .option('-c, --css <file>', 'Custom CSS file to include (or auto-detect custom.css in source directory)')
101
+ .option('--theme <name>', 'Built-in theme: default, legal', 'default')
100
102
  .addHelpText('after', `
101
103
  Examples:
102
104
  Basic conversion:
@@ -111,7 +113,11 @@ Examples:
111
113
  md2html -s content/docs --preview --port 3000
112
114
 
113
115
  Single file:
114
- md2html -s document.md --preview`)
116
+ md2html -s document.md --preview
117
+
118
+ Custom styling:
119
+ md2html -s content/docs --css=custom.css
120
+ md2html -s content/docs --theme=legal`)
115
121
  .parse(process.argv);
116
122
 
117
123
  const options = program.opts();
@@ -147,6 +153,40 @@ async function buildHtml(srcDir, outputFile) {
147
153
  const chapterNavStyles = fs.readFileSync(CHAPTER_NAV_STYLES_PATH, 'utf8');
148
154
  const jsScripts = fs.readFileSync(SCRIPTS_PATH, 'utf8');
149
155
 
156
+ // Load custom CSS if specified or auto-detect
157
+ let customStyles = '';
158
+ const srcDirResolved = fs.statSync(srcDir).isDirectory() ? srcDir : path.dirname(srcDir);
159
+
160
+ // Check for custom CSS in order of priority:
161
+ // 1. Explicit --css flag
162
+ // 2. custom.css in source directory
163
+ // 3. styles.css in source directory
164
+ const customCssPaths = [
165
+ options.css ? path.resolve(options.css) : null,
166
+ path.join(srcDirResolved, 'custom.css'),
167
+ path.join(srcDirResolved, 'styles.css')
168
+ ].filter(Boolean);
169
+
170
+ for (const cssPath of customCssPaths) {
171
+ if (fs.existsSync(cssPath)) {
172
+ customStyles = fs.readFileSync(cssPath, 'utf8');
173
+ debug(`Loaded custom CSS from: ${cssPath}`);
174
+ break;
175
+ }
176
+ }
177
+
178
+ // Apply built-in theme styles from files
179
+ let themeStyles = '';
180
+ if (options.theme && options.theme !== 'default') {
181
+ const themePath = path.join(path.dirname(new URL(import.meta.url).pathname), `static/themes/${options.theme}.css`);
182
+ if (fs.existsSync(themePath)) {
183
+ themeStyles = fs.readFileSync(themePath, 'utf8');
184
+ debug(`Applied ${options.theme} theme from: ${themePath}`);
185
+ } else {
186
+ console.warn(`Warning: Theme '${options.theme}' not found at ${themePath}`);
187
+ }
188
+ }
189
+
150
190
  // Register custom Handlebars helpers
151
191
  Handlebars.registerHelper('slugify', function(text) {
152
192
  return text
@@ -552,6 +592,41 @@ async function buildHtml(srcDir, outputFile) {
552
592
 
553
593
  processedContent = processedContent.replace(/<table>/g, '<table aria-hidden="true" role="presentation">');
554
594
 
595
+ // Convert checkbox syntax to FontAwesome icons
596
+ // Unchecked: - [ ] or * [ ] becomes FontAwesome square icon
597
+ // Checked: - [x] or * [x] becomes FontAwesome check-square icon
598
+ processedContent = processedContent.replace(
599
+ /<li>\s*\[\s*\]\s*/gi,
600
+ '<li class="checkbox-item"><i class="far fa-square checkbox-icon"></i> '
601
+ );
602
+ processedContent = processedContent.replace(
603
+ /<li>\s*\[x\]\s*/gi,
604
+ '<li class="checkbox-item checkbox-checked"><i class="fas fa-check-square checkbox-icon"></i> '
605
+ );
606
+
607
+ // Process inline SVG elements for proper sizing
608
+ // Add responsive wrapper and ensure viewBox is preserved
609
+ processedContent = processedContent.replace(
610
+ /<svg([^>]*)>/gi,
611
+ (match, attrs) => {
612
+ // Check if it already has a class
613
+ if (attrs.includes('class=')) {
614
+ return match.replace(/class="([^"]*)"/, 'class="$1 svg-responsive"');
615
+ }
616
+ return `<svg class="svg-responsive"${attrs}>`;
617
+ }
618
+ );
619
+
620
+ // Wrap standalone SVG elements in a figure for better layout control
621
+ processedContent = processedContent.replace(
622
+ /(<p>)?(<svg[^>]*>[\s\S]*?<\/svg>)(<\/p>)?/gi,
623
+ (match, openP, svg, closeP) => {
624
+ // If SVG is already in a figure, don't wrap again
625
+ if (match.includes('<figure')) return match;
626
+ return `<figure class="svg-container">${svg}</figure>`;
627
+ }
628
+ );
629
+
555
630
  // If it's an imgix URL, add width parameter
556
631
  processedContent = processedContent.replace(/src="(https:\/\/[^"]*imgix\.net\/[^"]+)(?:\?([^"]*))?"/g, (match, url, params) => {
557
632
  if (params && params.includes('w=')) {
@@ -635,6 +710,11 @@ async function buildHtml(srcDir, outputFile) {
635
710
  processedWithHeadingAttrs += `\n<script type="application/json" id="document-chapters-data">${chaptersJSON}</script>\n`;
636
711
 
637
712
  // Prepare data for template
713
+ // Combine all styles: base + chapter nav + theme + custom (custom overrides all)
714
+ const allStyles = [cssStyles, chapterNavStyles, themeStyles, customStyles]
715
+ .filter(Boolean)
716
+ .join('\n\n');
717
+
638
718
  const templateData = {
639
719
  title,
640
720
  description,
@@ -642,7 +722,7 @@ async function buildHtml(srcDir, outputFile) {
642
722
  date,
643
723
  version,
644
724
  content: processedWithHeadingAttrs,
645
- styles: cssStyles + '\n' + chapterNavStyles,
725
+ styles: allStyles,
646
726
  scripts: jsScripts,
647
727
  chapters: chaptersInfo
648
728
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@udx/md2html",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "Magazine-quality Markdown to HTML converter with professional styling",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,11 +1,10 @@
1
1
  /* Chapter Navigation Styles */
2
2
  :root {
3
- --chapter-nav-width: 180px;
4
- --chapter-nav-text-color: #333;
5
- --chapter-nav-text-faded: rgba(51, 51, 51, 0.4);
6
- --chapter-text-normal-size: 0.9rem;
7
- --chapter-text-active-size: calc(var(--chapter-text-normal-size) * 1.5);
8
- --chapter-text-inactive-size: calc(var(--chapter-text-normal-size) * 0.8);
3
+ --chapter-nav-width: 220px;
4
+ --chapter-nav-text-color: #1a1a1a;
5
+ --chapter-nav-text-faded: rgba(51, 51, 51, 0.65);
6
+ --chapter-nav-active-color: #991b1b;
7
+ --chapter-text-size: 0.85rem;
9
8
  }
10
9
 
11
10
  /* Container for the floating navigation */
@@ -47,20 +46,24 @@
47
46
  }
48
47
 
49
48
  .chapter-nav-item {
50
- margin-bottom: 10px;
49
+ margin-bottom: 12px;
51
50
  transition: all 0.3s ease;
52
51
  }
53
52
 
54
53
  .chapter-nav-link {
55
54
  display: block;
56
- padding: 4px 0;
57
- font-size: var(--chapter-text-inactive-size);
55
+ padding: 6px 0;
56
+ font-size: var(--chapter-text-size);
58
57
  color: var(--chapter-nav-text-faded);
59
58
  text-decoration: none;
60
- transition: all 0.3s ease;
59
+ transition: color 0.2s ease, text-decoration 0.2s ease;
61
60
  background-color: transparent;
62
61
  border-radius: 0;
63
- line-height: 1.3;
62
+ line-height: 1.4;
63
+ /* Better text handling for long titles */
64
+ word-wrap: break-word;
65
+ overflow-wrap: break-word;
66
+ hyphens: auto;
64
67
  }
65
68
 
66
69
  .chapter-nav-link:hover {
@@ -68,9 +71,12 @@
68
71
  }
69
72
 
70
73
  .chapter-nav-item.active .chapter-nav-link {
71
- font-size: var(--chapter-text-active-size);
72
- color: var(--chapter-nav-text-color);
74
+ /* Use color and underline instead of font-size to prevent jumping */
75
+ font-size: var(--chapter-text-size);
76
+ color: var(--chapter-nav-active-color);
73
77
  font-weight: 500;
78
+ text-decoration: underline;
79
+ text-underline-offset: 3px;
74
80
  }
75
81
 
76
82
  /* Mobile dropdown styles - native-looking and minimal */
@@ -169,8 +175,9 @@
169
175
 
170
176
  .chapter-dropdown-menu li.active a {
171
177
  font-weight: 500;
172
- color: var(--chapter-nav-text-color);
173
- font-size: calc(0.95rem * 1.2);
178
+ color: var(--chapter-nav-active-color);
179
+ text-decoration: underline;
180
+ text-underline-offset: 3px;
174
181
  }
175
182
 
176
183
  /* Add padding to the content to prevent floating nav from overlapping */
package/static/scripts.js CHANGED
@@ -44,51 +44,90 @@ document.addEventListener('DOMContentLoaded', (event) => {
44
44
  }
45
45
  });
46
46
 
47
- // Ensure all tables are 100% width with stronger enforcement
47
+ // Smart table sizing - auto-fit columns based on content
48
48
  document.querySelectorAll('table').forEach(table => {
49
- // Force full width with inline style AND classes
50
- table.style.width = '100%';
51
- table.style.tableLayout = 'fixed';
52
- table.setAttribute('width', '100%');
53
- table.classList.add('w-full');
54
- table.classList.add('table-fixed');
49
+ // Use auto layout for smart column sizing
50
+ table.style.tableLayout = 'auto';
51
+ table.style.width = 'auto';
52
+ table.style.maxWidth = '100%';
53
+ table.classList.add('auto-size');
55
54
 
56
55
  // Add responsive wrapper if not already wrapped
57
56
  if (table.parentElement.tagName !== 'DIV' || !table.parentElement.classList.contains('table-responsive')) {
58
57
  const wrapper = document.createElement('div');
59
- wrapper.className = 'table-responsive w-full overflow-x-auto my-8';
60
- wrapper.style.width = '100%';
58
+ wrapper.className = 'table-responsive overflow-x-auto my-8';
61
59
  wrapper.style.maxWidth = '100%';
62
60
  table.parentNode.insertBefore(wrapper, table);
63
61
  wrapper.appendChild(table);
64
62
  }
65
63
 
64
+ // Analyze columns and apply smart sizing
65
+ const rows = table.querySelectorAll('tr');
66
+ if (rows.length > 0) {
67
+ const headerCells = table.querySelectorAll('th');
68
+ const numCols = headerCells.length || (rows[0] ? rows[0].querySelectorAll('td').length : 0);
69
+
70
+ // Analyze each column's content
71
+ for (let colIdx = 0; colIdx < numCols; colIdx++) {
72
+ let isNumeric = true;
73
+ let isCurrency = true;
74
+ let isCompact = true;
75
+ let maxLength = 0;
76
+
77
+ rows.forEach(row => {
78
+ const cells = row.querySelectorAll('th, td');
79
+ if (cells[colIdx]) {
80
+ const text = cells[colIdx].textContent.trim();
81
+ maxLength = Math.max(maxLength, text.length);
82
+
83
+ // Check if numeric (including decimals and commas)
84
+ if (!/^[\d,.\-\s]+$/.test(text) && text !== '') {
85
+ isNumeric = false;
86
+ }
87
+
88
+ // Check if currency (starts with $ or ends with .00)
89
+ if (!/^\$?[\d,]+\.?\d*$/.test(text) && text !== '') {
90
+ isCurrency = false;
91
+ }
92
+
93
+ // Compact if short content (less than 15 chars)
94
+ if (text.length > 15) {
95
+ isCompact = false;
96
+ }
97
+ }
98
+ });
99
+
100
+ // Apply column classes based on analysis
101
+ rows.forEach(row => {
102
+ const cells = row.querySelectorAll('th, td');
103
+ if (cells[colIdx]) {
104
+ if (isCurrency && maxLength <= 15) {
105
+ cells[colIdx].classList.add('col-currency');
106
+ } else if (isNumeric && maxLength <= 10) {
107
+ cells[colIdx].classList.add('col-numeric');
108
+ } else if (isCompact && maxLength <= 10) {
109
+ cells[colIdx].classList.add('col-compact');
110
+ } else {
111
+ cells[colIdx].classList.add('col-text');
112
+ }
113
+ }
114
+ });
115
+ }
116
+ }
117
+
66
118
  // Add proper classes and styles to table headers
67
119
  table.querySelectorAll('th').forEach(th => {
68
120
  th.classList.add('bg-gray-50');
69
121
  th.classList.add('font-semibold');
70
- th.classList.add('p-3');
71
- th.classList.add('text-left');
72
- th.classList.add('border');
73
- th.classList.add('border-gray-200');
74
- th.style.padding = '0.75rem 1rem';
122
+ th.style.padding = '0.5rem 0.75rem';
123
+ th.style.borderBottom = '2px solid #e5e7eb';
75
124
  });
76
125
 
77
126
  // Add proper classes and styles to table cells
78
127
  table.querySelectorAll('td').forEach(td => {
79
- td.classList.add('p-3');
80
- td.classList.add('text-left');
81
- td.classList.add('border');
82
- td.classList.add('border-gray-200');
83
- td.style.padding = '0.75rem 1rem';
84
- td.style.borderColor = 'rgba(0, 0, 0, 0.1)';
128
+ td.style.padding = '0.5rem 0.75rem';
129
+ td.style.borderBottom = '1px solid #e5e7eb';
85
130
  });
86
-
87
- // Force table to use entire container width
88
- const parentWidth = table.parentElement.offsetWidth;
89
- if (parentWidth > 0) {
90
- table.style.minWidth = parentWidth + 'px';
91
- }
92
131
  });
93
132
 
94
133
  // Remove any blue highlights
@@ -122,24 +161,6 @@ document.addEventListener('DOMContentLoaded', (event) => {
122
161
  }
123
162
  });
124
163
 
125
- // Improve table layout and ensure proper alignment
126
- document.querySelectorAll('table').forEach(table => {
127
- // Add a container for horizontal scrolling if needed
128
- const wrapper = document.createElement('div');
129
- wrapper.style.width = '100%';
130
- wrapper.style.overflowX = 'auto';
131
- table.parentNode.insertBefore(wrapper, table);
132
- wrapper.appendChild(table);
133
-
134
- // Ensure consistent styling
135
- table.querySelectorAll('th, td').forEach(cell => {
136
- cell.style.padding = '0.75em 1em';
137
- cell.style.verticalAlign = 'top';
138
- cell.style.textAlign = 'left';
139
- cell.style.fontSize = '18px';
140
- });
141
- });
142
-
143
164
  // Process quote attributions with em dash
144
165
  document.querySelectorAll('blockquote + p').forEach(p => {
145
166
  const text = p.textContent || '';
package/static/styles.css CHANGED
@@ -338,26 +338,60 @@ pre:has(.file-type-badge) .copy-button {
338
338
  display: none;
339
339
  }
340
340
 
341
- /* Image handling */
341
+ /* ============================================
342
+ VISUAL ELEMENT SPACING
343
+ Consistent spacing for images, SVG, mermaid, figures
344
+ ============================================ */
345
+
346
+ /* Base visual element spacing - consistent 1.5em above and below */
347
+ :root {
348
+ --visual-spacing-top: 1.5em;
349
+ --visual-spacing-bottom: 1.5em;
350
+ --visual-spacing-print-top: 1em;
351
+ --visual-spacing-print-bottom: 1em;
352
+ }
353
+
354
+ /* Figure element - wrapper for images with captions */
342
355
  figure {
343
- margin-top: 2em;
344
- margin-bottom: 2em;
356
+ margin-top: var(--visual-spacing-top);
357
+ margin-bottom: var(--visual-spacing-bottom);
345
358
  text-align: center;
359
+ display: block;
346
360
  }
347
361
 
362
+ /* Images - standalone or within figures */
348
363
  img {
349
364
  max-width: 100%;
350
365
  height: auto;
351
366
  border-radius: 0.25em;
352
- margin-top: 2em;
353
- margin-bottom: 0.5em;
367
+ display: block;
368
+ margin-left: auto;
369
+ margin-right: auto;
370
+ }
371
+
372
+ /* Standalone images (not in figure) get vertical spacing */
373
+ p > img,
374
+ article > img,
375
+ section > img,
376
+ main > img,
377
+ .prose > img {
378
+ margin-top: var(--visual-spacing-top);
379
+ margin-bottom: var(--visual-spacing-bottom);
354
380
  }
355
381
 
382
+ /* Images inside figures don't need extra spacing */
383
+ figure img {
384
+ margin-top: 0;
385
+ margin-bottom: 0;
386
+ }
387
+
388
+ /* Figure captions */
356
389
  figcaption {
357
- margin-top: 0.5em;
390
+ margin-top: 0.75em;
358
391
  font-size: 0.9em;
359
392
  color: #64748b;
360
393
  font-style: italic;
394
+ text-align: center;
361
395
  }
362
396
 
363
397
  /* Alt text is visually hidden but accessible for screen readers */
@@ -482,3 +516,250 @@ a:hover {
482
516
  .MathJax {
483
517
  font-size: 1.1em !important;
484
518
  }
519
+
520
+ /* Checkbox styling with FontAwesome icons */
521
+ .checkbox-item {
522
+ list-style: none;
523
+ margin-left: -1.5em;
524
+ padding-left: 0;
525
+ }
526
+
527
+ .checkbox-icon {
528
+ color: #6b7280;
529
+ font-size: 1.1em;
530
+ margin-right: 0.5em;
531
+ vertical-align: middle;
532
+ }
533
+
534
+ .checkbox-checked .checkbox-icon {
535
+ color: #10b981;
536
+ }
537
+
538
+ /* SVG responsive styling */
539
+ .svg-responsive {
540
+ max-width: 100%;
541
+ height: auto;
542
+ display: block;
543
+ margin-left: auto;
544
+ margin-right: auto;
545
+ }
546
+
547
+ /* SVG container with consistent spacing */
548
+ .svg-container {
549
+ margin-top: var(--visual-spacing-top);
550
+ margin-bottom: var(--visual-spacing-bottom);
551
+ margin-left: auto;
552
+ margin-right: auto;
553
+ text-align: center;
554
+ max-width: 100%;
555
+ overflow-x: auto;
556
+ }
557
+
558
+ .svg-container svg {
559
+ max-width: 100%;
560
+ height: auto;
561
+ display: block;
562
+ margin-left: auto;
563
+ margin-right: auto;
564
+ }
565
+
566
+ /* Standalone SVG elements (not in container) */
567
+ article > svg,
568
+ section > svg,
569
+ main > svg,
570
+ .prose > svg {
571
+ margin-top: var(--visual-spacing-top);
572
+ margin-bottom: var(--visual-spacing-bottom);
573
+ display: block;
574
+ margin-left: auto;
575
+ margin-right: auto;
576
+ max-width: 100%;
577
+ height: auto;
578
+ }
579
+
580
+ /* Mermaid diagram containers - consistent spacing */
581
+ .mermaid {
582
+ margin-top: var(--visual-spacing-top);
583
+ margin-bottom: var(--visual-spacing-bottom);
584
+ text-align: center;
585
+ overflow-x: auto;
586
+ }
587
+
588
+ .mermaid svg {
589
+ max-width: 100%;
590
+ height: auto;
591
+ display: block;
592
+ margin-left: auto;
593
+ margin-right: auto;
594
+ }
595
+
596
+ /* Signature block styling for SVG signatures */
597
+ .signature-block {
598
+ margin-top: var(--visual-spacing-top);
599
+ margin-bottom: var(--visual-spacing-bottom);
600
+ padding: 1em;
601
+ border-top: 1px solid #e5e7eb;
602
+ }
603
+
604
+ .signature-block svg {
605
+ max-width: 300px;
606
+ height: auto;
607
+ }
608
+
609
+ /* Print styling for visual elements */
610
+ @media print {
611
+ /* Consistent print spacing for all visual elements */
612
+ figure,
613
+ .svg-container,
614
+ .mermaid,
615
+ article > img,
616
+ section > img,
617
+ main > img,
618
+ .prose > img {
619
+ margin-top: var(--visual-spacing-print-top);
620
+ margin-bottom: var(--visual-spacing-print-bottom);
621
+ page-break-inside: avoid;
622
+ }
623
+
624
+ .svg-responsive {
625
+ max-width: 6in;
626
+ }
627
+
628
+ /* Ensure images don't overflow in print */
629
+ img, svg {
630
+ max-width: 100% !important;
631
+ height: auto !important;
632
+ }
633
+ }
634
+
635
+ /* ============================================
636
+ PRINT PAGE BREAK SUPPORT
637
+ ============================================ */
638
+
639
+ /* Manual page break - use <!-- pagebreak --> in markdown */
640
+ .page-break {
641
+ page-break-before: always;
642
+ break-before: page;
643
+ height: 0;
644
+ margin: 0;
645
+ padding: 0;
646
+ border: none;
647
+ }
648
+
649
+ /* Print-specific page break rules */
650
+ @media print {
651
+ /* Force page break before h1 (new chapters/volumes) */
652
+ h1 {
653
+ page-break-before: always;
654
+ break-before: page;
655
+ }
656
+
657
+ /* First h1 should not have page break before it */
658
+ section:first-of-type h1:first-child,
659
+ article:first-of-type h1:first-child,
660
+ main > h1:first-child {
661
+ page-break-before: auto;
662
+ break-before: auto;
663
+ }
664
+
665
+ /* Keep headings with their following content */
666
+ h1, h2, h3, h4, h5, h6 {
667
+ page-break-after: avoid;
668
+ break-after: avoid;
669
+ }
670
+
671
+ /* Prevent page breaks inside these elements */
672
+ table, figure, pre, blockquote, ul, ol {
673
+ page-break-inside: avoid;
674
+ break-inside: avoid;
675
+ }
676
+
677
+ /* For very long tables, allow breaks but keep header visible */
678
+ table.allow-break {
679
+ page-break-inside: auto;
680
+ break-inside: auto;
681
+ }
682
+
683
+ table.allow-break thead {
684
+ display: table-header-group;
685
+ }
686
+
687
+ table.allow-break tr {
688
+ page-break-inside: avoid;
689
+ break-inside: avoid;
690
+ }
691
+
692
+ /* Orphan and widow control for paragraphs */
693
+ p {
694
+ orphans: 3;
695
+ widows: 3;
696
+ }
697
+
698
+ /* Keep images with their captions */
699
+ figure {
700
+ page-break-inside: avoid;
701
+ break-inside: avoid;
702
+ }
703
+
704
+ /* Keep list items together */
705
+ li {
706
+ page-break-inside: avoid;
707
+ break-inside: avoid;
708
+ }
709
+ }
710
+
711
+ /* ============================================
712
+ SMART TABLE SIZING
713
+ ============================================ */
714
+
715
+ /* Auto-sized tables - columns fit content */
716
+ table.auto-size {
717
+ table-layout: auto;
718
+ width: auto;
719
+ max-width: 100%;
720
+ }
721
+
722
+ /* Compact columns for short content (numbers, codes) */
723
+ table .col-compact {
724
+ width: 1%;
725
+ white-space: nowrap;
726
+ }
727
+
728
+ /* Numeric columns - right aligned, compact */
729
+ table .col-numeric {
730
+ width: 1%;
731
+ white-space: nowrap;
732
+ text-align: right;
733
+ font-variant-numeric: tabular-nums;
734
+ }
735
+
736
+ /* Currency columns */
737
+ table .col-currency {
738
+ width: 1%;
739
+ white-space: nowrap;
740
+ text-align: right;
741
+ font-variant-numeric: tabular-nums;
742
+ }
743
+
744
+ /* Description/text columns - expand to fill */
745
+ table .col-text {
746
+ width: auto;
747
+ }
748
+
749
+ /* Shrink-to-fit table wrapper */
750
+ .table-auto-wrapper {
751
+ display: inline-block;
752
+ max-width: 100%;
753
+ overflow-x: auto;
754
+ }
755
+
756
+ /* Print table sizing */
757
+ @media print {
758
+ table {
759
+ font-size: 10pt;
760
+ }
761
+
762
+ table th, table td {
763
+ padding: 4pt 8pt;
764
+ }
765
+ }
@@ -0,0 +1,134 @@
1
+ /* Legal Theme - Professional government proposal and legal document styling */
2
+ /* Inspired by NSF GOALI proposal formatting and federal document standards */
3
+
4
+ body {
5
+ font-family: "Times New Roman", Times, Georgia, serif;
6
+ font-size: 12pt;
7
+ line-height: 1.5;
8
+ }
9
+
10
+ .content-container {
11
+ max-width: 8.5in;
12
+ padding: 1in;
13
+ }
14
+
15
+ /* Formal heading styles */
16
+ .prose h1, h1 {
17
+ font-size: 18pt !important;
18
+ font-weight: bold !important;
19
+ text-align: center !important;
20
+ margin-bottom: 24pt !important;
21
+ font-family: "Times New Roman", Times, Georgia, serif !important;
22
+ }
23
+
24
+ .prose h2, h2 {
25
+ font-size: 14pt !important;
26
+ font-weight: bold !important;
27
+ margin-top: 18pt !important;
28
+ margin-bottom: 12pt !important;
29
+ font-family: "Times New Roman", Times, Georgia, serif !important;
30
+ }
31
+
32
+ .prose h3, h3 {
33
+ font-size: 12pt !important;
34
+ font-weight: bold !important;
35
+ margin-top: 12pt !important;
36
+ margin-bottom: 6pt !important;
37
+ font-family: "Times New Roman", Times, Georgia, serif !important;
38
+ }
39
+
40
+ .prose h4, h4, .prose h5, h5, .prose h6, h6 {
41
+ font-size: 12pt !important;
42
+ font-weight: bold !important;
43
+ font-style: italic !important;
44
+ margin-top: 12pt !important;
45
+ margin-bottom: 6pt !important;
46
+ font-family: "Times New Roman", Times, Georgia, serif !important;
47
+ }
48
+
49
+ .prose p, p {
50
+ font-size: 12pt !important;
51
+ text-align: justify !important;
52
+ margin-bottom: 12pt !important;
53
+ font-family: "Times New Roman", Times, Georgia, serif !important;
54
+ }
55
+
56
+ .prose li {
57
+ font-size: 12pt !important;
58
+ font-family: "Times New Roman", Times, Georgia, serif !important;
59
+ }
60
+
61
+ /* Table styling for government documents */
62
+ .prose table {
63
+ font-size: 10pt !important;
64
+ border-collapse: collapse !important;
65
+ }
66
+
67
+ .prose table th {
68
+ background-color: #f0f0f0 !important;
69
+ font-weight: bold !important;
70
+ border: 1px solid #333 !important;
71
+ }
72
+
73
+ .prose table td {
74
+ border: 1px solid #333 !important;
75
+ }
76
+
77
+ /* Sidebar adjustments for formal documents */
78
+ .chapter-navigation {
79
+ font-family: Arial, Helvetica, sans-serif;
80
+ }
81
+
82
+ /* Visual element spacing for legal documents - tighter than default */
83
+ :root {
84
+ --visual-spacing-top: 12pt;
85
+ --visual-spacing-bottom: 12pt;
86
+ --visual-spacing-print-top: 10pt;
87
+ --visual-spacing-print-bottom: 10pt;
88
+ }
89
+
90
+ /* Figure and image styling for legal documents */
91
+ figure {
92
+ margin-top: 12pt;
93
+ margin-bottom: 12pt;
94
+ }
95
+
96
+ figcaption {
97
+ font-size: 10pt !important;
98
+ font-style: italic;
99
+ text-align: center;
100
+ margin-top: 6pt;
101
+ }
102
+
103
+ /* SVG and diagram styling */
104
+ .svg-container,
105
+ .mermaid {
106
+ margin-top: 12pt;
107
+ margin-bottom: 12pt;
108
+ }
109
+
110
+ /* Images in legal documents */
111
+ img {
112
+ margin-top: 12pt;
113
+ margin-bottom: 12pt;
114
+ }
115
+
116
+ figure img {
117
+ margin-top: 0;
118
+ margin-bottom: 0;
119
+ }
120
+
121
+ /* Print-optimized styling */
122
+ @media print {
123
+ body {
124
+ font-size: 12pt !important;
125
+ }
126
+
127
+ .chapter-navigation, .mobile-chapter-nav {
128
+ display: none !important;
129
+ }
130
+
131
+ .chapter-content {
132
+ padding-left: 0 !important;
133
+ }
134
+ }
package/static/view.hbs CHANGED
@@ -317,6 +317,8 @@
317
317
  if (code) {
318
318
  const mermaidDiv = document.createElement('div');
319
319
  mermaidDiv.className = 'mermaid';
320
+ mermaidDiv.setAttribute('aria-hidden', 'true');
321
+ mermaidDiv.setAttribute('role', 'presentation');
320
322
  mermaidDiv.textContent = code.textContent;
321
323
  pre.parentNode.replaceChild(mermaidDiv, pre);
322
324
  }