methanol 0.0.8 → 0.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "methanol",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Static site generator powered by rEFui and MDX",
5
5
  "main": "./index.js",
6
6
  "type": "module",
@@ -39,6 +39,7 @@
39
39
  "refurbish": "^0.1.8",
40
40
  "rehype-slug": "^6.0.0",
41
41
  "rehype-starry-night": "^2.2.0",
42
+ "remark-gfm": "^4.0.1",
42
43
  "unist-util-visit": "^5.0.0",
43
44
  "vite": "^7.3.0",
44
45
  "yargs": "^18.0.0"
@@ -67,7 +67,7 @@ export const buildHtmlEntries = async () => {
67
67
  await ensureDir(state.INTERMEDIATE_DIR)
68
68
  }
69
69
 
70
- const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build'
70
+ const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
71
71
  const stageLogger = createStageLogger(logEnabled)
72
72
  const themeComponentsDir = state.THEME_COMPONENTS_DIR
73
73
  const themeEnv = state.THEME_ENV
@@ -164,6 +164,10 @@ export const buildHtmlEntries = async () => {
164
164
  }
165
165
 
166
166
  export const runViteBuild = async (entry, htmlCache) => {
167
+ const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
168
+ const stageLogger = createStageLogger(logEnabled)
169
+ const token = stageLogger.start('Building bundle')
170
+
167
171
  if (state.STATIC_DIR !== false && state.MERGED_ASSETS_DIR) {
168
172
  await preparePublicAssets({
169
173
  themeDir: state.THEME_ASSETS_DIR,
@@ -177,6 +181,7 @@ export const runViteBuild = async (entry, htmlCache) => {
177
181
  root: state.PAGES_DIR,
178
182
  appType: 'mpa',
179
183
  publicDir: state.STATIC_DIR === false ? false : state.STATIC_DIR,
184
+ logLevel: cli.CLI_VERBOSE ? 'info' : 'silent',
180
185
  build: {
181
186
  outDir: state.DIST_DIR,
182
187
  emptyOutDir: true,
@@ -198,4 +203,5 @@ export const runViteBuild = async (entry, htmlCache) => {
198
203
  const userConfig = await resolveUserViteConfig('build')
199
204
  const finalConfig = userConfig ? mergeConfig(baseConfig, userConfig) : baseConfig
200
205
  await viteBuild(finalConfig)
206
+ stageLogger.end(token)
201
207
  }
package/src/config.js CHANGED
@@ -24,6 +24,7 @@ import { resolve, isAbsolute, extname, basename } from 'path'
24
24
  import { pathToFileURL } from 'url'
25
25
  import { mergeConfig } from 'vite'
26
26
  import { cli, state } from './state.js'
27
+ import { logger } from './logger.js'
27
28
  import { HTMLRenderer } from './renderer.js'
28
29
  import { rewindEnv } from './components.js'
29
30
  import { env as createEnv } from './rewind.js'
@@ -123,7 +124,7 @@ const warnDevBase = (value) => {
123
124
  if (devBaseWarningShown) return
124
125
  devBaseWarningShown = true
125
126
  const label = value ? ` (received "${value}")` : ''
126
- console.warn(`Methanol: \`base\`${label} is disabled in dev mode due to module resolution inconsistencies in Vite. Using "/".\n`)
127
+ logger.warn(`Methanol: \`base\`${label} is disabled in dev mode due to module resolution inconsistencies in Vite. Using "/".\n`)
127
128
  }
128
129
 
129
130
  const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key)
@@ -264,7 +265,7 @@ export const applyConfig = async (config, mode) => {
264
265
  const configSiteName = cli.CLI_SITE_NAME ?? config.site?.name ?? null
265
266
  state.SITE_NAME = configSiteName || basename(root) || 'Methanol Site'
266
267
  const userSite = config.site && typeof config.site === 'object' ? { ...config.site } : null
267
- const siteBase = normalizeSiteBase(userSite?.base)
268
+ const siteBase = normalizeSiteBase(cli.CLI_BASE || userSite?.base)
268
269
  state.SITE_BASE = siteBase
269
270
  if (userSite) {
270
271
  if (siteBase == null) {
@@ -392,6 +393,9 @@ export const applyConfig = async (config, mode) => {
392
393
  state.THEME_POST_BUILD_HOOKS = normalizeHooks(state.USER_THEME?.postBuild)
393
394
  state.THEME_PRE_BUNDLE_HOOKS = normalizeHooks(state.USER_THEME?.preBundle)
394
395
  state.THEME_POST_BUNDLE_HOOKS = normalizeHooks(state.USER_THEME?.postBundle)
396
+ if (hasOwn(config, 'gfm')) {
397
+ state.GFM_ENABLED = config.gfm !== false
398
+ }
395
399
  const starryNight = resolveStarryNightConfig(config.starryNight)
396
400
  const cliCodeHighlighting = cli.CLI_CODE_HIGHLIGHTING
397
401
  if (cliCodeHighlighting != null) {
@@ -448,6 +452,7 @@ export const resolveUserViteConfig = async (command) => {
448
452
  if (state.RESOLVED_VITE_CONFIG !== undefined) {
449
453
  return state.RESOLVED_VITE_CONFIG
450
454
  }
455
+
451
456
  const resolveConfig = async (config) => {
452
457
  if (!config) return null
453
458
  if (typeof config === 'function') {
@@ -463,9 +468,10 @@ export const resolveUserViteConfig = async (command) => {
463
468
  }
464
469
  return config || null
465
470
  }
471
+
466
472
  const themeConfig = await resolveConfig(state.USER_THEME.vite)
467
473
  const userConfig = await resolveConfig(state.USER_VITE_CONFIG)
468
- const userHasBase = userConfig && hasOwn(userConfig, 'base')
474
+
469
475
  if (!themeConfig && !userConfig) {
470
476
  if (state.SITE_BASE) {
471
477
  state.RESOLVED_VITE_CONFIG = { base: state.SITE_BASE }
@@ -484,15 +490,20 @@ export const resolveUserViteConfig = async (command) => {
484
490
  }
485
491
  return state.RESOLVED_VITE_CONFIG
486
492
  }
493
+
487
494
  state.RESOLVED_VITE_CONFIG = themeConfig
488
495
  ? userConfig
489
496
  ? mergeConfig(themeConfig, userConfig)
490
497
  : themeConfig
491
498
  : userConfig
492
- if (state.SITE_BASE && !userHasBase) {
499
+
500
+ const userHasBase = userConfig && hasOwn(userConfig, 'base')
501
+ if (state.SITE_BASE && (cli.CLI_BASE || !userHasBase)) {
493
502
  state.RESOLVED_VITE_CONFIG.base = state.SITE_BASE
494
503
  }
504
+
495
505
  state.VITE_BASE = normalizeViteBase(state.RESOLVED_VITE_CONFIG?.base || state.SITE_BASE || '/')
506
+
496
507
  if (command === 'serve') {
497
508
  if (state.VITE_BASE !== '/' || (state.SITE_BASE && state.SITE_BASE !== '/')) {
498
509
  warnDevBase(state.RESOLVED_VITE_CONFIG?.base || state.SITE_BASE || '')
@@ -500,6 +511,7 @@ export const resolveUserViteConfig = async (command) => {
500
511
  state.RESOLVED_VITE_CONFIG.base = '/'
501
512
  state.VITE_BASE = '/'
502
513
  }
514
+
503
515
  return state.RESOLVED_VITE_CONFIG
504
516
  }
505
517
 
package/src/dev-server.js CHANGED
@@ -344,6 +344,8 @@ export const runViteDev = async () => {
344
344
  } else {
345
345
  return next()
346
346
  }
347
+ } else if (requestedPath.endsWith('/index')) {
348
+ renderRoutePath = requestedPath.slice(0, -5) // remove last 'index'
347
349
  }
348
350
 
349
351
  try {
package/src/logger.js ADDED
@@ -0,0 +1,80 @@
1
+ /* Copyright Yukino Song, SudoMaker Ltd.
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements. See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership. The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License. You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied. See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ */
20
+
21
+ const supportColor =
22
+ typeof process !== 'undefined' &&
23
+ process.stdout &&
24
+ (process.stdout.isTTY || process.env.FORCE_COLOR)
25
+
26
+ const formatter = (open, close, replace = open) =>
27
+ supportColor
28
+ ? (input) => {
29
+ const string = '' + input
30
+ const index = string.indexOf(close, open.length)
31
+ return ~index
32
+ ? open + getReplace(string, index, close, replace) + close
33
+ : open + string + close
34
+ }
35
+ : (input) => '' + input
36
+
37
+ const getReplace = (string, index, close, replace) => {
38
+ const head = string.substring(0, index) + replace
39
+ const tail = string.substring(index + close.length)
40
+ const next = tail.indexOf(close)
41
+ return ~next ? head + getReplace(tail, next, close, replace) : head + tail
42
+ }
43
+
44
+ export const style = {
45
+ reset: formatter('\x1b[0m', '\x1b[0m'),
46
+ bold: formatter('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m'),
47
+ dim: formatter('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m'),
48
+ italic: formatter('\x1b[3m', '\x1b[23m'),
49
+ underline: formatter('\x1b[4m', '\x1b[24m'),
50
+ inverse: formatter('\x1b[7m', '\x1b[27m'),
51
+ hidden: formatter('\x1b[8m', '\x1b[28m'),
52
+ strikethrough: formatter('\x1b[9m', '\x1b[29m'),
53
+
54
+ black: formatter('\x1b[30m', '\x1b[39m'),
55
+ red: formatter('\x1b[31m', '\x1b[39m'),
56
+ green: formatter('\x1b[32m', '\x1b[39m'),
57
+ yellow: formatter('\x1b[33m', '\x1b[39m'),
58
+ blue: formatter('\x1b[34m', '\x1b[39m'),
59
+ magenta: formatter('\x1b[35m', '\x1b[39m'),
60
+ cyan: formatter('\x1b[36m', '\x1b[39m'),
61
+ white: formatter('\x1b[37m', '\x1b[39m'),
62
+ gray: formatter('\x1b[90m', '\x1b[39m'),
63
+
64
+ bgBlack: formatter('\x1b[40m', '\x1b[49m'),
65
+ bgRed: formatter('\x1b[41m', '\x1b[49m'),
66
+ bgGreen: formatter('\x1b[42m', '\x1b[49m'),
67
+ bgYellow: formatter('\x1b[43m', '\x1b[49m'),
68
+ bgBlue: formatter('\x1b[44m', '\x1b[49m'),
69
+ bgMagenta: formatter('\x1b[45m', '\x1b[49m'),
70
+ bgCyan: formatter('\x1b[46m', '\x1b[49m'),
71
+ bgWhite: formatter('\x1b[47m', '\x1b[49m')
72
+ }
73
+
74
+ export const logger = {
75
+ info: (msg) => console.log(`${style.blue('ℹ')} ${msg}`),
76
+ success: (msg) => console.log(`${style.green('✔')} ${msg}`),
77
+ warn: (msg) => console.log(`${style.yellow('⚠')} ${msg}`),
78
+ error: (msg) => console.log(`${style.red('✖')} ${msg}`),
79
+ dim: (msg) => console.log(style.dim(msg))
80
+ }
package/src/main.js CHANGED
@@ -26,6 +26,7 @@ import { runVitePreview } from './preview-server.js'
26
26
  import { cli, state } from './state.js'
27
27
  import { HTMLRenderer } from './renderer.js'
28
28
  import { readFile } from 'fs/promises'
29
+ import { style, logger } from './logger.js'
29
30
 
30
31
  const printBanner = async () => {
31
32
  try {
@@ -107,6 +108,7 @@ const main = async () => {
107
108
  return
108
109
  }
109
110
  if (isBuild) {
111
+ const startTime = performance.now()
110
112
  await runHooks(state.USER_PRE_BUILD_HOOKS)
111
113
  await runHooks(state.THEME_PRE_BUILD_HOOKS)
112
114
  const { entry, htmlCache, pagesContext } = await buildHtmlEntries()
@@ -129,6 +131,11 @@ const main = async () => {
129
131
  }
130
132
  await runHooks(state.THEME_POST_BUILD_HOOKS, buildContext)
131
133
  await runHooks(state.USER_POST_BUILD_HOOKS, buildContext)
134
+ const endTime = performance.now()
135
+ const duration = endTime - startTime
136
+ const timeString = duration > 1000 ? `${(duration / 1000).toFixed(2)}s` : `${Math.round(duration)}ms`
137
+ console.log()
138
+ logger.success(`Build completed in ${style.bold(timeString)}.`)
132
139
  return
133
140
  }
134
141
  cli.showHelp()
package/src/mdx.js CHANGED
@@ -25,6 +25,7 @@ import rehypeSlug from 'rehype-slug'
25
25
  import extractToc from '@stefanprobst/rehype-extract-toc'
26
26
  import withTocExport from '@stefanprobst/rehype-extract-toc/mdx'
27
27
  import rehypeStarryNight from 'rehype-starry-night'
28
+ import remarkGfm from 'remark-gfm'
28
29
  import { HTMLRenderer } from './renderer.js'
29
30
  import { signal, computed, read, Suspense, nextTick } from 'refui'
30
31
  import { createPortal } from 'refui/extras'
@@ -260,11 +261,21 @@ const resolveBaseMdxConfig = async () => {
260
261
  jsxImportSource: 'refui',
261
262
  development: state.CURRENT_MODE !== 'production',
262
263
  elementAttributeNameCase: 'html',
263
- rehypePlugins: [rehypeSlug, extractToc, [withTocExport, { name: 'toc' }]]
264
+ rehypePlugins: [rehypeSlug, extractToc, [withTocExport, { name: 'toc' }]],
265
+ remarkPlugins: []
264
266
  }
267
+
268
+ if (state.GFM_ENABLED) {
269
+ baseMdxConfig.remarkPlugins.push(remarkGfm)
270
+ }
271
+
265
272
  const mdxConfig = { ...baseMdxConfig, ...userMdxConfig }
266
273
  const userRehypePlugins = Array.isArray(userMdxConfig.rehypePlugins) ? userMdxConfig.rehypePlugins : []
267
274
  mdxConfig.rehypePlugins = [...baseMdxConfig.rehypePlugins, ...userRehypePlugins]
275
+
276
+ const userRemarkPlugins = Array.isArray(userMdxConfig.remarkPlugins) ? userMdxConfig.remarkPlugins : []
277
+ mdxConfig.remarkPlugins = [...baseMdxConfig.remarkPlugins, ...userRemarkPlugins]
278
+
268
279
  mdxConfig.rehypePlugins.push(linkResolve)
269
280
  mdxConfig.rehypePlugins.push(methanolCtx)
270
281
  return (cachedMdxConfig = mdxConfig)
package/src/pagefind.js CHANGED
@@ -22,7 +22,9 @@ import { access } from 'fs/promises'
22
22
  import { constants } from 'fs'
23
23
  import { join, delimiter } from 'path'
24
24
  import { spawn } from 'child_process'
25
- import { state } from './state.js'
25
+ import { state, cli } from './state.js'
26
+ import { createStageLogger } from './stage-logger.js'
27
+ import { logger } from './logger.js'
26
28
 
27
29
  const resolvePagefindBin = async () => {
28
30
  const binName = process.platform === 'win32' ? 'pagefind.cmd' : 'pagefind'
@@ -92,16 +94,25 @@ const runCommand = (command, args, options) =>
92
94
  export const runPagefind = async () => {
93
95
  const bin = await resolvePagefindBin()
94
96
  if (!bin) {
95
- console.log('Pagefind not found; skipping search indexing.')
97
+ logger.warn('Pagefind not found; skipping search indexing.')
96
98
  return false
97
99
  }
98
- console.log('Running Pagefind search indexing...')
100
+ const logEnabled = state.CURRENT_MODE === 'production' && cli.command === 'build' && !cli.CLI_VERBOSE
101
+ const stageLogger = createStageLogger(logEnabled)
102
+ const token = stageLogger.start('Indexing search')
103
+
104
+ if (cli.CLI_VERBOSE) {
105
+ logger.info('Running Pagefind search indexing...')
106
+ }
107
+
99
108
  const extraArgs = buildArgsFromOptions(state.PAGEFIND_BUILD)
100
109
  const ok = await runCommand(bin, ['--site', state.DIST_DIR, ...extraArgs], {
101
- cwd: state.PROJECT_ROOT
110
+ cwd: state.PROJECT_ROOT,
111
+ stdio: cli.CLI_VERBOSE ? 'inherit' : 'ignore'
102
112
  })
103
113
  if (!ok) {
104
- console.warn('Pagefind failed to build search index.')
114
+ logger.warn('Pagefind failed to build search index.')
105
115
  }
116
+ stageLogger.end(token)
106
117
  return ok
107
118
  }
@@ -18,6 +18,8 @@
18
18
  * under the License.
19
19
  */
20
20
 
21
+ import { style } from './logger.js'
22
+
21
23
  const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now())
22
24
 
23
25
  export const createStageLogger = (enabled) => {
@@ -43,17 +45,18 @@ export const createStageLogger = (enabled) => {
43
45
  }
44
46
  const start = (label) => {
45
47
  if (!enabled) return null
46
- writeLine(`${label}...`, false)
48
+ writeLine(`${style.cyan('◼')} ${label}...`, false)
47
49
  return { label, start: now() }
48
50
  }
49
51
  const update = (token, message) => {
50
52
  if (!enabled || !token || !message) return
51
- writeLine(message, false)
53
+ writeLine(`${style.cyan('◼')} ${message}`, false)
52
54
  }
53
55
  const end = (token) => {
54
56
  if (!enabled || !token) return
55
- const duration = Math.round(now() - token.start)
56
- writeLine(`${token.label}...\t${duration}ms`, true)
57
+ const duration = now() - token.start
58
+ const timeString = duration > 1000 ? `${(duration / 1000).toFixed(2)}s` : `${Math.round(duration)}ms`
59
+ writeLine(`${style.green('✔')} ${token.label}\t${style.dim(timeString)}`, true)
57
60
  }
58
61
  return { start, update, end }
59
62
  }
package/src/state.js CHANGED
@@ -103,10 +103,9 @@ const withCommonOptions = (y) =>
103
103
  })
104
104
  .option('code-highlighting', {
105
105
  describe: 'Enable or disable code highlighting',
106
- type: 'string',
106
+ type: 'boolean',
107
107
  coerce: (value) => {
108
108
  if (value == null) return null
109
- if (value === true || value === '') return true
110
109
  if (typeof value === 'boolean') return value
111
110
  const normalized = String(value).trim().toLowerCase()
112
111
  if (normalized === 'true') return true
@@ -114,6 +113,18 @@ const withCommonOptions = (y) =>
114
113
  return null
115
114
  }
116
115
  })
116
+ .option('verbose', {
117
+ alias: 'v',
118
+ describe: 'Enable verbose output',
119
+ type: 'boolean',
120
+ default: false
121
+ })
122
+ .option('base', {
123
+ describe: 'Base URL override',
124
+ type: 'string',
125
+ requiresArg: true,
126
+ nargs: 1
127
+ })
117
128
 
118
129
  const parser = yargs(hideBin(process.argv))
119
130
  .scriptName('methanol')
@@ -141,7 +152,9 @@ export const cli = {
141
152
  CLI_OUTPUT_DIR: argv.output || null,
142
153
  CLI_CONFIG_PATH: argv.config || null,
143
154
  CLI_SITE_NAME: argv['site-name'] || null,
144
- CLI_CODE_HIGHLIGHTING: typeof argv['code-highlighting'] === 'boolean' ? argv['code-highlighting'] : null
155
+ CLI_CODE_HIGHLIGHTING: typeof argv['code-highlighting'] === 'boolean' ? argv['code-highlighting'] : null,
156
+ CLI_VERBOSE: Boolean(argv.verbose),
157
+ CLI_BASE: argv.base || null
145
158
  }
146
159
 
147
160
  export const state = {
@@ -180,6 +193,7 @@ export const state = {
180
193
  THEME_POST_BUNDLE_HOOKS: [],
181
194
  STARRY_NIGHT_ENABLED: false,
182
195
  STARRY_NIGHT_OPTIONS: null,
196
+ GFM_ENABLED: true,
183
197
  CURRENT_MODE: 'production',
184
198
  RESOLVED_MDX_CONFIG: undefined,
185
199
  RESOLVED_VITE_CONFIG: undefined
@@ -1735,6 +1735,47 @@ a {
1735
1735
  justify-content: flex-end;
1736
1736
  }
1737
1737
 
1738
+ /* --- Tables (GFM) --- */
1739
+
1740
+ .main-content table {
1741
+ width: 100%;
1742
+ border-collapse: collapse;
1743
+ margin: 1.5rem 0;
1744
+ font-size: 0.95rem;
1745
+ overflow-x: auto;
1746
+ display: block; /* Enables horizontal scroll for large tables */
1747
+ }
1748
+
1749
+ .main-content table thead tr {
1750
+ border-bottom: 2px solid var(--border);
1751
+ }
1752
+
1753
+ .main-content table th {
1754
+ text-align: left;
1755
+ padding: 0.75rem 1rem;
1756
+ font-weight: 600;
1757
+ color: var(--text);
1758
+ white-space: nowrap;
1759
+ }
1760
+
1761
+ .main-content table tbody tr {
1762
+ border-bottom: 1px solid var(--border);
1763
+ transition: background-color 0.1s ease;
1764
+ }
1765
+
1766
+ .main-content table tbody tr:last-child {
1767
+ border-bottom: none;
1768
+ }
1769
+
1770
+ .main-content table td {
1771
+ padding: 0.75rem 1rem;
1772
+ color: var(--muted);
1773
+ }
1774
+
1775
+ .main-content table tbody tr:hover {
1776
+ background-color: var(--hover-bg);
1777
+ }
1778
+
1738
1779
  /* --- View Transitions --- */
1739
1780
 
1740
1781
  @view-transition {
@@ -1747,3 +1788,151 @@ a {
1747
1788
  view-transition-name: active-sidebar-bg;
1748
1789
  contain: layout paint;
1749
1790
  }
1791
+
1792
+ /* --- Print Styles --- */
1793
+
1794
+ @media print {
1795
+ :root {
1796
+ --bg: #ffffff;
1797
+ --surface: #ffffff;
1798
+ --surface-muted: #f4f4f5;
1799
+ --surface-elevated: #ffffff;
1800
+ --text: #09090b;
1801
+ --muted: #52525b;
1802
+ --border: #e4e4e7;
1803
+ --accent: #000000;
1804
+ --accent-soft: #f4f4f5;
1805
+ --hover-bg: transparent;
1806
+ }
1807
+
1808
+ body {
1809
+ background-color: white !important;
1810
+ background-image: none !important;
1811
+ color: black !important;
1812
+ width: 100% !important;
1813
+ margin: 0 !important;
1814
+ padding: 0 !important;
1815
+ overflow: visible !important;
1816
+ }
1817
+
1818
+ /* Hide UI Elements */
1819
+ .sidebar,
1820
+ .toc-panel,
1821
+ .nav-toggle-label,
1822
+ .toc-toggle-label,
1823
+ .search-toggle-label,
1824
+ .search-modal,
1825
+ .page-nav,
1826
+ .copy-btn,
1827
+ .heading-anchor,
1828
+ .theme-switch-wrapper,
1829
+ .lang-switch-wrapper,
1830
+ .page-meta {
1831
+ display: none !important;
1832
+ }
1833
+
1834
+ /* Layout Overrides */
1835
+ .layout-container,
1836
+ .layout-container.no-toc {
1837
+ display: block !important;
1838
+ max-width: none !important;
1839
+ width: 100% !important;
1840
+ margin: 0 !important;
1841
+ padding: 0 !important;
1842
+ }
1843
+
1844
+ .main-content {
1845
+ padding: 0 !important;
1846
+ width: 100% !important;
1847
+ max-width: none !important;
1848
+ }
1849
+
1850
+ /* Typography & Content */
1851
+ body {
1852
+ font-size: 11pt !important;
1853
+ line-height: 1.5 !important;
1854
+ }
1855
+
1856
+ .main-content {
1857
+ h1 {
1858
+ font-size: 24pt !important;
1859
+ margin-bottom: 1rem !important;
1860
+ }
1861
+
1862
+ h2 {
1863
+ font-size: 18pt !important;
1864
+ margin-top: 1.5rem !important;
1865
+ margin-bottom: 0.75rem !important;
1866
+ }
1867
+
1868
+ h3 {
1869
+ font-size: 14pt !important;
1870
+ margin-top: 1.25rem !important;
1871
+ margin-bottom: 0.5rem !important;
1872
+ }
1873
+
1874
+ p, li {
1875
+ font-size: 11pt !important;
1876
+ margin-bottom: 0.75rem !important;
1877
+ }
1878
+
1879
+ pre, code {
1880
+ font-size: 10pt !important;
1881
+ }
1882
+ }
1883
+
1884
+ h1, h2, h3, h4, h5, h6 {
1885
+ color: black !important;
1886
+ page-break-after: avoid;
1887
+ break-after: avoid;
1888
+ }
1889
+
1890
+ h2 {
1891
+ border-bottom: 1px solid #000 !important;
1892
+ }
1893
+
1894
+ p, li {
1895
+ orphans: 3;
1896
+ widows: 3;
1897
+ }
1898
+
1899
+ a {
1900
+ text-decoration: underline !important;
1901
+ color: black !important;
1902
+ }
1903
+
1904
+ /* Expand external links */
1905
+ a[href^="http"]::after {
1906
+ content: " (" attr(href) ")";
1907
+ font-size: 0.85em;
1908
+ color: #555;
1909
+ }
1910
+
1911
+ /* Clean up code blocks */
1912
+ pre {
1913
+ border: 1px solid #ccc !important;
1914
+ white-space: pre-wrap !important;
1915
+ break-inside: avoid;
1916
+ background: #fafafa !important;
1917
+ }
1918
+
1919
+ /* Blockquotes */
1920
+ blockquote {
1921
+ border-left: 3px solid #ccc !important;
1922
+ background: transparent !important;
1923
+ color: #333 !important;
1924
+ break-inside: avoid;
1925
+ }
1926
+
1927
+ /* Tables */
1928
+ table {
1929
+ break-inside: avoid;
1930
+ }
1931
+
1932
+ /* Page Setup */
1933
+ @page {
1934
+ margin: 2cm;
1935
+ size: auto;
1936
+ }
1937
+ }
1938
+