@zeropress/build-pages 0.6.1 → 0.6.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.
@@ -56,6 +56,12 @@
56
56
  "description": "Whether generated HTML should expose the ZeroPress generator meta tag. Set false for white-label sites.",
57
57
  "markdownDescription": "Whether generated HTML should expose `<meta name=\"generator\" content=\"ZeroPress\">`. Set `false` for white-label sites."
58
58
  },
59
+ "search": {
60
+ "type": "boolean",
61
+ "default": true,
62
+ "description": "Whether native ZeroPress search should be enabled when the selected theme supports search UI. Set false to omit native search artifacts and hide theme search UI.",
63
+ "markdownDescription": "Whether native ZeroPress search should be enabled when the selected theme supports search UI. Set `false` to omit native search artifacts and hide theme search UI."
64
+ },
59
65
  "indexing": {
60
66
  "type": "boolean",
61
67
  "default": true,
@@ -292,6 +298,7 @@
292
298
  "description": "Public documentation.",
293
299
  "url": "https://zeropress.dev",
294
300
  "expose_generator": true,
301
+ "search": true,
295
302
  "indexing": true
296
303
  },
297
304
  "front_page": {
package/src/action.js CHANGED
@@ -2,6 +2,7 @@ import { runBuildPages } from './index.js';
2
2
 
3
3
  const options = {
4
4
  source: input('source') || './docs',
5
+ publicDir: input('public-dir'),
5
6
  destination: input('destination') || './_site',
6
7
  theme: input('theme') || 'docs',
7
8
  themePath: input('theme-path'),
package/src/index.js CHANGED
@@ -39,6 +39,8 @@ export async function runBuildPages(options) {
39
39
  const cwd = path.resolve(options.cwd || process.cwd());
40
40
  const copyMarkdownSource = options.copyMarkdownSource !== false;
41
41
  const sourceDir = path.resolve(cwd, options.source);
42
+ const publicDirExplicit = hasExplicitPublicDir(options);
43
+ const publicDir = publicDirExplicit ? path.resolve(cwd, options.publicDir) : sourceDir;
42
44
  const destinationDir = path.resolve(cwd, options.destination);
43
45
  const generatedDir = path.join(cwd, '.zeropress');
44
46
  const stagingDir = path.join(cwd, STAGING_DIR);
@@ -48,18 +50,23 @@ export async function runBuildPages(options) {
48
50
  assertBuildPagesPathLayout({
49
51
  cwd,
50
52
  sourceDir,
53
+ publicDir,
54
+ publicDirExplicit,
51
55
  destinationDir,
52
56
  themeDir,
53
57
  generatedDir,
54
58
  });
55
59
  await assertDirectory(sourceDir, 'Source directory');
60
+ await assertPublicDirectory(publicDir, publicDirExplicit);
61
+ await assertDestinationPath(destinationDir);
56
62
  await fs.rm(generatedDir, { recursive: true, force: true });
57
63
  await fs.mkdir(generatedDir, { recursive: true });
58
64
 
59
65
  const env = {
60
66
  ...process.env,
61
67
  ZEROPRESS_BUILD_PAGES_SOURCE: sourceDir,
62
- ZEROPRESS_PUBLIC_DIR: sourceDir,
68
+ ZEROPRESS_BUILD_PAGES_PUBLIC_DIR: publicDir,
69
+ ZEROPRESS_PUBLIC_DIR: publicDir,
63
70
  ZEROPRESS_SKIP_UNTITLED_MARKDOWN: String(Boolean(options.skipUntitledMarkdown)),
64
71
  ZEROPRESS_COPY_MARKDOWN_SOURCE: String(copyMarkdownSource),
65
72
  };
@@ -85,10 +92,13 @@ export async function runBuildPages(options) {
85
92
  await fs.rm(destinationDir, { recursive: true, force: true });
86
93
  await fs.rm(stagingDir, { recursive: true, force: true });
87
94
  await fs.mkdir(stagingDir, { recursive: true });
88
- await copyPublicStaging(sourceDir, stagingDir, {
95
+ await copyPublicStaging(publicDir, stagingDir, {
89
96
  excludePaths: [destinationDir, themeDir, generatedDir],
90
97
  copyMarkdownSource,
91
98
  });
99
+ if (copyMarkdownSource) {
100
+ await copySourceMarkdownFiles(sourceDir, stagingDir, previewData);
101
+ }
92
102
 
93
103
  const previousPublicDir = process.env.ZEROPRESS_PUBLIC_DIR;
94
104
  process.env.ZEROPRESS_PUBLIC_DIR = stagingDir;
@@ -137,6 +147,7 @@ export function parseArgs(argv) {
137
147
 
138
148
  const valueOptions = new Set([
139
149
  '--source',
150
+ '--public-dir',
140
151
  '--destination',
141
152
  '--theme',
142
153
  '--theme-path',
@@ -167,6 +178,7 @@ export function parseArgs(argv) {
167
178
 
168
179
  return {
169
180
  source,
181
+ publicDir: flags['public-dir'] || '',
170
182
  destination,
171
183
  theme: flags.theme || DEFAULT_THEME,
172
184
  themePath: flags['theme-path'] || '',
@@ -186,6 +198,7 @@ Usage:
186
198
 
187
199
  Options:
188
200
  --source <dir> Dedicated source directory (required)
201
+ --public-dir <dir> Public passthrough directory (default: source)
189
202
  --destination <dir> Output directory (required)
190
203
  --theme docs Bundled theme name (default: docs)
191
204
  --theme-path <dir> Custom ZeroPress theme directory
@@ -208,6 +221,10 @@ function resolveThemeDir(cwd, options) {
208
221
  throw new Error(`Unknown bundled theme: ${options.theme}`);
209
222
  }
210
223
 
224
+ function hasExplicitPublicDir(options) {
225
+ return typeof options.publicDir === 'string' && Boolean(options.publicDir.trim());
226
+ }
227
+
211
228
  async function assertDirectory(dir, label) {
212
229
  let stat;
213
230
  try {
@@ -223,7 +240,55 @@ async function assertDirectory(dir, label) {
223
240
  }
224
241
  }
225
242
 
226
- function assertBuildPagesPathLayout({ cwd, sourceDir, destinationDir, themeDir, generatedDir }) {
243
+ async function assertPublicDirectory(publicDir, explicit) {
244
+ if (!explicit) {
245
+ return;
246
+ }
247
+
248
+ let stat;
249
+ try {
250
+ stat = await fs.lstat(publicDir);
251
+ } catch (error) {
252
+ if (error?.code === 'ENOENT') {
253
+ throw new Error(`Public directory not found: ${publicDir}`);
254
+ }
255
+ throw error;
256
+ }
257
+
258
+ if (stat.isSymbolicLink()) {
259
+ throw new Error(`Public directory must not be a symbolic link: ${publicDir}`);
260
+ }
261
+
262
+ if (!stat.isDirectory()) {
263
+ throw new Error(`Public path is not a directory: ${publicDir}`);
264
+ }
265
+ }
266
+
267
+ async function assertDestinationPath(destinationDir) {
268
+ let stat;
269
+ try {
270
+ stat = await fs.lstat(destinationDir);
271
+ } catch (error) {
272
+ if (error?.code === 'ENOENT') {
273
+ return;
274
+ }
275
+ throw error;
276
+ }
277
+
278
+ if (!stat.isDirectory()) {
279
+ throw new Error(`Destination path is not a directory: ${destinationDir}`);
280
+ }
281
+ }
282
+
283
+ function assertBuildPagesPathLayout({
284
+ cwd,
285
+ sourceDir,
286
+ publicDir,
287
+ publicDirExplicit,
288
+ destinationDir,
289
+ themeDir,
290
+ generatedDir,
291
+ }) {
227
292
  if (samePath(sourceDir, cwd)) {
228
293
  throw new Error(
229
294
  'Source directory must be a dedicated content directory, not the current working directory. '
@@ -231,11 +296,36 @@ function assertBuildPagesPathLayout({ cwd, sourceDir, destinationDir, themeDir,
231
296
  );
232
297
  }
233
298
 
299
+ if (publicDirExplicit && samePath(publicDir, cwd)) {
300
+ throw new Error(
301
+ 'Public directory must be a dedicated asset directory, not the current working directory. '
302
+ + `Received: ${formatPath(cwd, publicDir)}`,
303
+ );
304
+ }
305
+
234
306
  assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'internal .zeropress working directory', generatedDir);
235
307
  assertNoPathOverlap(cwd, 'Destination directory', destinationDir, 'internal .zeropress working directory', generatedDir);
236
308
  assertNoPathOverlap(cwd, 'Theme directory', themeDir, 'internal .zeropress working directory', generatedDir);
309
+ if (!samePath(publicDir, sourceDir)) {
310
+ assertNoPathOverlap(cwd, 'Public directory', publicDir, 'internal .zeropress working directory', generatedDir);
311
+ assertNoPathOverlap(cwd, 'Public directory', publicDir, 'destination directory', destinationDir);
312
+ assertNoPathOverlap(cwd, 'Public directory', publicDir, 'theme directory', themeDir);
313
+ }
237
314
  assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'destination directory', destinationDir);
238
315
  assertNoPathOverlap(cwd, 'Source directory', sourceDir, 'theme directory', themeDir);
316
+ assertSourceIsNotInsidePublicDirectory(cwd, sourceDir, publicDir);
317
+ }
318
+
319
+ function assertSourceIsNotInsidePublicDirectory(cwd, sourceDir, publicDir) {
320
+ if (samePath(sourceDir, publicDir) || !isPathInside(publicDir, sourceDir)) {
321
+ return;
322
+ }
323
+
324
+ throw new Error(
325
+ 'Source directory must not be inside the public directory. '
326
+ + `Source directory: ${formatPath(cwd, sourceDir)}; `
327
+ + `Public directory: ${formatPath(cwd, publicDir)}`,
328
+ );
239
329
  }
240
330
 
241
331
  function assertNoPathOverlap(cwd, firstLabel, firstPath, secondLabel, secondPath) {
@@ -282,6 +372,67 @@ async function copyPublicStaging(sourceDir, targetDir, options) {
282
372
  }
283
373
  }
284
374
 
375
+ async function copySourceMarkdownFiles(sourceDir, targetDir, previewData) {
376
+ const markdownUrls = new Set();
377
+
378
+ for (const page of previewData?.content?.pages || []) {
379
+ const sourceMarkdownUrl = page?.meta?.source_markdown_url;
380
+ if (typeof sourceMarkdownUrl === 'string' && sourceMarkdownUrl) {
381
+ markdownUrls.add(sourceMarkdownUrl);
382
+ }
383
+ }
384
+
385
+ for (const sourceMarkdownUrl of markdownUrls) {
386
+ const relativePath = sourceMarkdownUrlToRelativePath(sourceMarkdownUrl);
387
+ if (!relativePath) {
388
+ continue;
389
+ }
390
+
391
+ const sourcePath = path.join(sourceDir, relativePath);
392
+ if (!isPathInside(sourceDir, sourcePath)) {
393
+ continue;
394
+ }
395
+
396
+ const targetPath = path.join(targetDir, relativePath);
397
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
398
+ await fs.copyFile(sourcePath, targetPath);
399
+ }
400
+ }
401
+
402
+ function sourceMarkdownUrlToRelativePath(sourceMarkdownUrl) {
403
+ if (
404
+ !sourceMarkdownUrl.startsWith('/')
405
+ || sourceMarkdownUrl.includes('?')
406
+ || sourceMarkdownUrl.includes('#')
407
+ ) {
408
+ return '';
409
+ }
410
+
411
+ const rawSegments = sourceMarkdownUrl.slice(1).split('/');
412
+ const segments = [];
413
+ for (const rawSegment of rawSegments) {
414
+ if (!rawSegment) {
415
+ return '';
416
+ }
417
+
418
+ let segment;
419
+ try {
420
+ segment = decodeURIComponent(rawSegment);
421
+ } catch {
422
+ return '';
423
+ }
424
+
425
+ if (!segment || segment === '.' || segment === '..' || segment.includes('/') || segment.includes('\\')) {
426
+ return '';
427
+ }
428
+
429
+ segments.push(segment);
430
+ }
431
+
432
+ const relativePath = segments.join('/');
433
+ return relativePath.toLowerCase().endsWith('.md') ? relativePath : '';
434
+ }
435
+
285
436
  function shouldIgnorePublicEntry(name) {
286
437
  const basename = String(name || '');
287
438
  const lowerName = basename.toLowerCase();
package/src/prebuild.js CHANGED
@@ -6,6 +6,7 @@ import matter from 'gray-matter';
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
  const rootDir = process.cwd();
8
8
  const sourceDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_SOURCE'], 'docs');
9
+ const publicDir = resolveEnvPath(['ZEROPRESS_BUILD_PAGES_PUBLIC_DIR'], sourceDir);
9
10
  const defaultConfigPath = path.join(sourceDir, '.zeropress', 'config.json');
10
11
  const configPath = resolveOptionalEnvPath(['ZEROPRESS_BUILD_PAGES_CONFIG'], defaultConfigPath);
11
12
  const outDir = path.join(rootDir, '.zeropress');
@@ -22,6 +23,7 @@ const FRONT_MATTER_DATA_MAX_DEPTH = 4;
22
23
  const FRONT_MATTER_DATA_MAX_KEYS = 64;
23
24
  const FRONT_MATTER_DATA_MAX_ARRAY_LENGTH = 256;
24
25
  const FRONT_MATTER_DISCOVERABILITY_VALUES = new Set(['default', 'noindex', 'delist']);
26
+ const markdownDiscoverExcludeRoots = buildMarkdownDiscoverExcludeRoots();
25
27
 
26
28
  class PrebuildMarkdownError extends Error {
27
29
  constructor(sourcePath, reason, expected = '', code = 'invalid_markdown') {
@@ -243,6 +245,7 @@ function buildSiteData(config, frontPage) {
243
245
  },
244
246
  disallow_comments: true,
245
247
  expose_generator: configuredSite.expose_generator !== false,
248
+ search: configuredSite.search !== false,
246
249
  indexing: configuredSite.indexing !== false,
247
250
  };
248
251
 
@@ -278,12 +281,13 @@ function normalizeSiteConfig(value) {
278
281
  }
279
282
 
280
283
  const configuredSite = isPlainObject(value) ? value : {};
281
- assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'expose_generator', 'indexing', 'footer'], 'site');
284
+ assertKnownConfigKeys(configuredSite, ['title', 'description', 'url', 'expose_generator', 'search', 'indexing', 'footer'], 'site');
282
285
  const site = {
283
286
  title: readConfigString(configuredSite.title, 'Documentation'),
284
287
  description: readConfigString(configuredSite.description, 'A documentation site.'),
285
288
  url: readEnv('ZEROPRESS_SITE_URL', readConfigString(configuredSite.url, '')),
286
289
  expose_generator: readConfigBoolean(configuredSite.expose_generator, true, 'site.expose_generator'),
290
+ search: readConfigBoolean(configuredSite.search, true, 'site.search'),
287
291
  indexing: readConfigBoolean(configuredSite.indexing, true, 'site.indexing'),
288
292
  };
289
293
 
@@ -760,6 +764,7 @@ function buildPrebuildReport({
760
764
  return {
761
765
  generated_at: new Date().toISOString(),
762
766
  source_dir: formatSourcePath(sourceDir),
767
+ public_dir: formatSourcePath(publicDir),
763
768
  config_path: formatSourcePath(configPath),
764
769
  build_pages_config_path: formatSourcePath(buildPagesConfigPath),
765
770
  preview_data_path: formatSourcePath(previewDataPath),
@@ -786,7 +791,8 @@ function buildPrebuildReport({
786
791
  function printPrebuildSummary(report) {
787
792
  const lines = [
788
793
  'ZeroPress build report',
789
- `- Public root: ${report.source_dir}`,
794
+ `- Source root: ${report.source_dir}`,
795
+ `- Public root: ${report.public_dir}`,
790
796
  `- Markdown discovered: ${report.markdown.discovered}`,
791
797
  `- Markdown pages generated: ${report.markdown.generated_pages}`,
792
798
  `- Markdown skipped: ${report.markdown.skipped}`,
@@ -1134,6 +1140,10 @@ async function listMarkdownFiles(dir) {
1134
1140
  }
1135
1141
 
1136
1142
  const entryPath = path.join(dir, entry.name);
1143
+ if (isMarkdownDiscoverExcluded(entryPath)) {
1144
+ continue;
1145
+ }
1146
+
1137
1147
  if (entry.isDirectory()) {
1138
1148
  files.push(...await listMarkdownFiles(entryPath));
1139
1149
  continue;
@@ -1147,6 +1157,24 @@ async function listMarkdownFiles(dir) {
1147
1157
  return files.sort((left, right) => left.localeCompare(right));
1148
1158
  }
1149
1159
 
1160
+ function buildMarkdownDiscoverExcludeRoots() {
1161
+ if (samePath(sourceDir, publicDir) || !isPathInside(sourceDir, publicDir)) {
1162
+ return [];
1163
+ }
1164
+
1165
+ return [publicDir];
1166
+ }
1167
+
1168
+ function isMarkdownDiscoverExcluded(entryPath) {
1169
+ return markdownDiscoverExcludeRoots.some((excludeRoot) => (
1170
+ samePath(entryPath, excludeRoot) || isPathInside(excludeRoot, entryPath)
1171
+ ));
1172
+ }
1173
+
1174
+ function samePath(firstPath, secondPath) {
1175
+ return path.resolve(firstPath) === path.resolve(secondPath);
1176
+ }
1177
+
1150
1178
  function shouldIgnoreMarkdownDiscoverEntry(name) {
1151
1179
  const basename = String(name || '');
1152
1180
  const lowerName = basename.toLowerCase();
@@ -12,6 +12,15 @@
12
12
  --prose-ink: #344054;
13
13
  --pre-bg: #101828;
14
14
  --pre-ink: #f8fafc;
15
+ --syntax-comment: #98a2b3;
16
+ --syntax-keyword: #f472b6;
17
+ --syntax-title: #93c5fd;
18
+ --syntax-string: #86efac;
19
+ --syntax-number: #fbbf24;
20
+ --syntax-attr: #67e8f9;
21
+ --syntax-built-in: #c4b5fd;
22
+ --syntax-deletion: #fca5a5;
23
+ --syntax-addition: #bbf7d0;
15
24
  --button-ink: #ffffff;
16
25
  --alert-note: #2563eb;
17
26
  --alert-tip: #059669;
@@ -99,12 +108,31 @@ a {
99
108
  gap: 1.5rem;
100
109
  }
101
110
 
111
+ .visually-hidden {
112
+ position: absolute;
113
+ width: 1px;
114
+ height: 1px;
115
+ padding: 0;
116
+ overflow: hidden;
117
+ clip: rect(0, 0, 0, 0);
118
+ white-space: nowrap;
119
+ border: 0;
120
+ }
121
+
102
122
  .brand {
103
123
  color: var(--ink);
104
124
  font-weight: 750;
105
125
  text-decoration: none;
106
126
  }
107
127
 
128
+ .site-header__actions {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: flex-end;
132
+ gap: 1rem;
133
+ min-width: 0;
134
+ }
135
+
108
136
  .site-nav ul {
109
137
  list-style: none;
110
138
  margin: 0;
@@ -129,6 +157,109 @@ a {
129
157
  color: var(--accent);
130
158
  }
131
159
 
160
+ .site-search {
161
+ position: relative;
162
+ width: min(22rem, 34vw);
163
+ }
164
+
165
+ .site-search__form {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 0.4rem;
169
+ }
170
+
171
+ .site-search__input {
172
+ min-width: 0;
173
+ width: 100%;
174
+ height: 2.35rem;
175
+ padding: 0 0.75rem;
176
+ border: 1px solid var(--line);
177
+ border-radius: 6px;
178
+ background: var(--surface);
179
+ color: var(--ink);
180
+ font: inherit;
181
+ font-size: 0.92rem;
182
+ }
183
+
184
+ .site-search__input:focus {
185
+ border-color: var(--accent);
186
+ outline: 2px solid color-mix(in srgb, var(--accent) 22%, transparent);
187
+ outline-offset: 1px;
188
+ }
189
+
190
+ .site-search__button {
191
+ height: 2.35rem;
192
+ padding: 0 0.75rem;
193
+ border: 0;
194
+ border-radius: 6px;
195
+ background: var(--accent);
196
+ color: var(--button-ink);
197
+ font: inherit;
198
+ font-size: 0.88rem;
199
+ font-weight: 750;
200
+ cursor: pointer;
201
+ }
202
+
203
+ .site-search__panel {
204
+ position: absolute;
205
+ top: calc(100% + 0.55rem);
206
+ right: 0;
207
+ width: min(31rem, calc(100vw - 2rem));
208
+ max-height: min(31rem, calc(100vh - 6rem));
209
+ overflow: auto;
210
+ padding: 0.85rem;
211
+ border: 1px solid var(--line);
212
+ border-radius: var(--radius);
213
+ background: var(--surface);
214
+ box-shadow: 0 20px 45px rgb(15 23 42 / 18%);
215
+ }
216
+
217
+ .site-search__status {
218
+ margin: 0 0 0.75rem;
219
+ color: var(--muted);
220
+ font-size: 0.86rem;
221
+ }
222
+
223
+ .site-search__results {
224
+ list-style: none;
225
+ margin: 0;
226
+ padding: 0;
227
+ }
228
+
229
+ .site-search__results li + li {
230
+ margin-top: 0.65rem;
231
+ padding-top: 0.65rem;
232
+ border-top: 1px solid var(--line);
233
+ }
234
+
235
+ .site-search__results a {
236
+ display: inline-block;
237
+ color: var(--ink);
238
+ font-weight: 750;
239
+ text-decoration: none;
240
+ }
241
+
242
+ .site-search__results a:hover {
243
+ color: var(--accent);
244
+ text-decoration: underline;
245
+ }
246
+
247
+ .site-search__results span {
248
+ display: block;
249
+ margin-top: 0.1rem;
250
+ color: var(--accent);
251
+ font-size: 0.75rem;
252
+ font-weight: 750;
253
+ text-transform: uppercase;
254
+ }
255
+
256
+ .site-search__results p {
257
+ margin: 0.25rem 0 0;
258
+ color: var(--muted);
259
+ font-size: 0.88rem;
260
+ line-height: 1.45;
261
+ }
262
+
132
263
  .hero {
133
264
  background: var(--surface);
134
265
  border-bottom: 1px solid var(--line);
@@ -372,6 +503,71 @@ h1 {
372
503
  padding: 0;
373
504
  }
374
505
 
506
+ .prose pre code.hljs {
507
+ color: var(--pre-ink);
508
+ }
509
+
510
+ .prose .hljs-comment,
511
+ .prose .hljs-quote {
512
+ color: var(--syntax-comment);
513
+ font-style: italic;
514
+ }
515
+
516
+ .prose .hljs-keyword,
517
+ .prose .hljs-selector-tag,
518
+ .prose .hljs-subst {
519
+ color: var(--syntax-keyword);
520
+ }
521
+
522
+ .prose .hljs-title,
523
+ .prose .hljs-section,
524
+ .prose .hljs-name,
525
+ .prose .hljs-selector-id,
526
+ .prose .hljs-selector-class {
527
+ color: var(--syntax-title);
528
+ }
529
+
530
+ .prose .hljs-string,
531
+ .prose .hljs-regexp,
532
+ .prose .hljs-symbol,
533
+ .prose .hljs-bullet {
534
+ color: var(--syntax-string);
535
+ }
536
+
537
+ .prose .hljs-number,
538
+ .prose .hljs-literal {
539
+ color: var(--syntax-number);
540
+ }
541
+
542
+ .prose .hljs-attr,
543
+ .prose .hljs-attribute,
544
+ .prose .hljs-variable,
545
+ .prose .hljs-template-variable {
546
+ color: var(--syntax-attr);
547
+ }
548
+
549
+ .prose .hljs-built_in,
550
+ .prose .hljs-type,
551
+ .prose .hljs-class .hljs-title {
552
+ color: var(--syntax-built-in);
553
+ }
554
+
555
+ .prose .hljs-deletion {
556
+ color: var(--syntax-deletion);
557
+ }
558
+
559
+ .prose .hljs-addition {
560
+ color: var(--syntax-addition);
561
+ }
562
+
563
+ .prose .hljs-emphasis {
564
+ font-style: italic;
565
+ }
566
+
567
+ .prose .hljs-strong {
568
+ font-weight: 800;
569
+ }
570
+
375
571
  .prose table {
376
572
  display: block;
377
573
  width: 100%;
@@ -544,10 +740,26 @@ h1, h2, h3, h4, h5, h6 {
544
740
  padding: 1rem 0;
545
741
  }
546
742
 
743
+ .site-header__actions {
744
+ width: 100%;
745
+ align-items: stretch;
746
+ flex-direction: column;
747
+ }
748
+
547
749
  .site-nav ul {
548
750
  justify-content: flex-start;
549
751
  }
550
752
 
753
+ .site-search {
754
+ width: 100%;
755
+ }
756
+
757
+ .site-search__panel {
758
+ left: 0;
759
+ right: auto;
760
+ width: 100%;
761
+ }
762
+
551
763
  .docs-grid,
552
764
  .npm-links,
553
765
  .doc-layout,