@abreen/tada 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +96 -48
  2. package/bin/tada.ts +621 -0
  3. package/bin/validators.test.ts +205 -0
  4. package/bin/validators.ts +85 -0
  5. package/{webpack/apply-base-path-plugin.js → build/apply-base-path-plugin.ts} +37 -17
  6. package/build/build-manifest.test.ts +349 -0
  7. package/build/build-manifest.ts +165 -0
  8. package/build/bundle.ts +116 -0
  9. package/{webpack/code.test.js → build/code.test.ts} +10 -7
  10. package/build/colors.ts +25 -0
  11. package/build/content-watch.ts +107 -0
  12. package/build/copy.ts +118 -0
  13. package/{webpack/deflist-id-plugin.js → build/deflist-id-plugin.ts} +7 -6
  14. package/{webpack/external-links-plugin.js → build/external-links-plugin.ts} +14 -5
  15. package/build/features.ts +11 -0
  16. package/build/generate-content-assets.ts +320 -0
  17. package/build/generate-favicon.ts +172 -0
  18. package/build/generate-fonts.ts +31 -0
  19. package/build/generate-katex-assets.ts +35 -0
  20. package/{webpack/generate-manifest-plugin.js → build/generate-manifest.ts} +29 -36
  21. package/build/globals.test.ts +99 -0
  22. package/build/globals.ts +88 -0
  23. package/{webpack/heading-subtitle-plugin.js → build/heading-subtitle-plugin.ts} +4 -2
  24. package/build/json-schema.test.ts +57 -0
  25. package/build/json-schema.ts +33 -0
  26. package/build/log.test.ts +111 -0
  27. package/build/log.ts +167 -0
  28. package/build/markdown-plugins.test.ts +396 -0
  29. package/{webpack/pagefind-plugin.test.js → build/pagefind.test.ts} +74 -13
  30. package/build/pagefind.ts +339 -0
  31. package/build/pdf-text.test.ts +52 -0
  32. package/{webpack/pdf-text.js → build/pdf-text.ts} +47 -27
  33. package/build/pipeline.ts +120 -0
  34. package/{webpack/reachability.test.js → build/reachability.test.ts} +3 -3
  35. package/{webpack/reachability.js → build/reachability.ts} +77 -34
  36. package/build/serve.ts +112 -0
  37. package/build/site-variables.ts +68 -0
  38. package/{webpack/templates.js → build/templates.ts} +38 -35
  39. package/{webpack/text-to-id.js → build/text-to-id.ts} +2 -2
  40. package/build/toc-plugin.test.ts +105 -0
  41. package/{webpack/toc-plugin.js → build/toc-plugin.ts} +32 -13
  42. package/build/types.ts +181 -0
  43. package/build/util.ts +26 -0
  44. package/{webpack/utils/code.js → build/utils/code.ts} +119 -60
  45. package/{webpack/utils/content-files.js → build/utils/content-files.ts} +40 -35
  46. package/build/utils/derive-theme.test.ts +111 -0
  47. package/build/utils/derive-theme.ts +85 -0
  48. package/build/utils/file-types.test.ts +61 -0
  49. package/build/utils/file-types.ts +13 -0
  50. package/build/utils/front-matter.test.ts +77 -0
  51. package/{webpack/utils/front-matter.js → build/utils/front-matter.ts} +22 -9
  52. package/{webpack → build}/utils/jdi-runner/LiterateRunner.java +1 -1
  53. package/{webpack/utils/literate-java.js → build/utils/literate-java.ts} +68 -38
  54. package/{webpack/utils/markdown.js → build/utils/markdown.ts} +126 -49
  55. package/build/utils/paths.test.ts +91 -0
  56. package/{webpack/utils/paths.js → build/utils/paths.ts} +18 -22
  57. package/{webpack/utils/render.js → build/utils/render.ts} +208 -130
  58. package/build/utils/shiki-highlighter.ts +29 -0
  59. package/build/validate-internal-links-plugin.test.ts +106 -0
  60. package/{webpack/validate-internal-links-plugin.js → build/validate-internal-links-plugin.ts} +47 -20
  61. package/{webpack/watch-reachability-state.test.js → build/watch-reachability-state.test.ts} +8 -8
  62. package/{webpack/watch-reachability-state.js → build/watch-reachability-state.ts} +63 -24
  63. package/{webpack/watch-reload-client.js → build/watch-reload-client.ts} +3 -1
  64. package/build/watch.ts +549 -0
  65. package/fonts/google-sans-code/woff2/GoogleSansCodeVariable-Italic.woff2 +0 -0
  66. package/fonts/google-sans-code/woff2/GoogleSansCodeVariable.woff2 +0 -0
  67. package/fonts/inter/woff2/InterVariable-Italic.woff2 +0 -0
  68. package/fonts/inter/woff2/InterVariable.woff2 +0 -0
  69. package/{content → init/content}/index.md +9 -3
  70. package/{content → init/content}/lectures/index.md +1 -1
  71. package/{content → init/content}/markdown.md +28 -8
  72. package/init/content/problem_sets/index.html +14 -0
  73. package/package.json +33 -22
  74. package/{templates → schema}/nav.schema.json +1 -2
  75. package/{webpack → schema}/site.schema.json +4 -12
  76. package/src/_alerts.scss +96 -0
  77. package/src/_base.scss +145 -0
  78. package/src/{layout.scss → _layout.scss} +36 -1
  79. package/src/anchor/index.ts +0 -1
  80. package/src/anchor/style.scss +1 -9
  81. package/src/code/index.ts +3 -3
  82. package/src/code.scss +1 -1
  83. package/src/critical.scss +5 -0
  84. package/src/header/_base.scss +129 -0
  85. package/src/header/style.scss +3 -131
  86. package/src/index.ts +4 -5
  87. package/src/question/style.scss +1 -1
  88. package/src/search/index.test.ts +203 -0
  89. package/src/search/index.ts +38 -93
  90. package/src/search/pdf-utils.ts +79 -0
  91. package/src/search/style.scss +8 -28
  92. package/src/style.scss +15 -326
  93. package/src/timezone/index.ts +5 -6
  94. package/src/toc/style.scss +5 -39
  95. package/src/util.ts +9 -6
  96. package/templates/_theme.scss +38 -14
  97. package/templates/_top.html +1 -1
  98. package/tsconfig.json +10 -6
  99. package/types/dev.d.ts +1 -0
  100. package/types/file-system-access.d.ts +5 -0
  101. package/types/markdown-it-plugins.d.ts +11 -0
  102. package/types/site-variables.d.ts +6 -10
  103. package/types/untyped-modules.d.ts +47 -0
  104. package/bin/tada.js +0 -361
  105. package/config/authors.json +0 -1
  106. package/config/nav.json +0 -28
  107. package/content/problem_sets/index.md +0 -6
  108. package/types/dev.ts +0 -3
  109. package/webpack/build-state.js +0 -97
  110. package/webpack/colors.js +0 -15
  111. package/webpack/config.base.js +0 -151
  112. package/webpack/config.dev.js +0 -23
  113. package/webpack/config.prod.js +0 -32
  114. package/webpack/content-watch-plugin.js +0 -153
  115. package/webpack/features.js +0 -5
  116. package/webpack/generate-content-assets-plugin.js +0 -308
  117. package/webpack/generate-favicon-plugin.js +0 -198
  118. package/webpack/generate-fonts-plugin.js +0 -69
  119. package/webpack/globals.js +0 -74
  120. package/webpack/json-schema.js +0 -19
  121. package/webpack/log.js +0 -143
  122. package/webpack/markdown-plugins.test.js +0 -203
  123. package/webpack/pagefind-plugin.js +0 -379
  124. package/webpack/print-flair-plugin.js +0 -22
  125. package/webpack/serve.js +0 -104
  126. package/webpack/site-variables.js +0 -53
  127. package/webpack/util.js +0 -49
  128. package/webpack/utils/define-plugin.js +0 -20
  129. package/webpack/utils/file-types.js +0 -26
  130. package/webpack/utils/parse-hsl.js +0 -8
  131. package/webpack/utils/shiki-highlighter.js +0 -26
  132. package/webpack/watch.js +0 -166
  133. /package/{webpack → build}/flair.json +0 -0
  134. /package/{webpack → build}/utils/jdi-runner/LiterateRunner.class +0 -0
  135. /package/fonts/google-sans-code/{GoogleSansCodeVariable-Italic.ttf → ttf/GoogleSansCodeVariable-Italic.ttf} +0 -0
  136. /package/fonts/google-sans-code/{GoogleSansCodeVariable.ttf → ttf/GoogleSansCodeVariable.ttf} +0 -0
  137. /package/fonts/inter/{InterVariable-Italic.ttf → ttf/InterVariable-Italic.ttf} +0 -0
  138. /package/fonts/inter/{InterVariable.ttf → ttf/InterVariable.ttf} +0 -0
  139. /package/{content → init/content}/lectures/01/Pair.java.md +0 -0
  140. /package/{content → init/content}/lectures/01/Rectangle.java +0 -0
  141. /package/{content → init/content}/lectures/01/demo.py +0 -0
  142. /package/{content → init/content}/lectures/01/index.md +0 -0
  143. /package/{content → init/content}/lectures/01/lecture1.pdf +0 -0
  144. /package/{public → init/public}/avatars/alex.jpg +0 -0
  145. /package/{public → init/public}/test.txt +0 -0
  146. /package/{templates → schema}/authors.schema.json +0 -0
package/package.json CHANGED
@@ -1,70 +1,81 @@
1
1
  {
2
2
  "name": "@abreen/tada",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
+ "type": "module",
4
5
  "description": "A static site generator",
6
+ "license": "MIT",
5
7
  "bin": {
6
- "tada": "bin/tada.js"
8
+ "tada": "bin/tada.ts"
7
9
  },
8
10
  "files": [
9
11
  "bin/",
10
- "webpack/",
12
+ "build/",
11
13
  "src/",
12
14
  "templates/",
13
15
  "fonts/",
14
- "config/",
15
- "content/",
16
- "public/",
16
+ "init/",
17
+ "schema/",
17
18
  "types/",
18
19
  "tsconfig.json"
19
20
  ],
20
21
  "scripts": {
21
- "format": "bunx prettier --write \"**/*.(t|j)s\" \"**/*.scss\" \"**/*.json\"",
22
- "init-example": "bun run bin/tada.js init example --default"
22
+ "prepare": "husky && bun run scripts/check-deps.ts",
23
+ "init-example": "bun run bin/tada.ts init example --no-interactive",
24
+ "lint": "eslint src/ build/ bin/",
25
+ "typecheck": "tsc --noEmit",
26
+ "test:functional": "python3 -m pip install -q -r functional_tests/requirements.txt && python3 -m pytest functional_tests/ -v -n auto --durations=10",
27
+ "test:all": "bun test && bun run test:functional"
23
28
  },
24
29
  "engines": {
25
30
  "bun": ">=1.0.0"
26
31
  },
27
32
  "dependencies": {
28
- "@babel/core": "^7.26.10",
29
- "@babel/preset-env": "^7.26.9",
30
33
  "@tsconfig/node23": "^23.0.2",
34
+ "@vscode/markdown-it-katex": "^1.1.2",
31
35
  "ajv": "^8.17.1",
32
- "babel-loader": "^10.0.0",
33
- "copy-webpack-plugin": "^13.0.1",
34
- "css-loader": "^7.1.2",
36
+ "chokidar": "^4.0.0",
37
+ "culori": "^4.0.2",
35
38
  "fontkit": "^2.0.4",
36
39
  "front-matter": "^4.0.2",
37
40
  "java-parser": "^3.0.1",
38
41
  "jsdom": "^26.1.0",
42
+ "katex": "^0.16.40",
39
43
  "lodash": "^4.17.21",
40
44
  "markdown-it": "^14.1.0",
41
45
  "markdown-it-anchor": "^9.2.0",
42
46
  "markdown-it-container": "^4.0.0",
43
47
  "markdown-it-deflist": "^3.0.0",
44
48
  "markdown-it-footnote": "^4.0.0",
45
- "mini-css-extract-plugin": "^2.9.4",
46
49
  "pagefind": "^1.4.0",
47
50
  "png-to-ico": "^3.0.1",
48
51
  "quote-quote": "^1.2.0",
49
52
  "sass": "^1.91.0",
50
- "sass-loader": "^16.0.5",
51
53
  "sharp": "^0.34.5",
52
54
  "shiki": "^4.0.1",
53
55
  "string-strip-html": "^8.5.0",
54
- "terser-webpack-plugin": "^5.3.14",
55
- "ts-loader": "^9.5.2",
56
- "typescript": "^5.4.5",
57
- "wawoff2": "^2.0.1",
58
- "webpack": "^5.99.6",
59
- "webpack-cli": "^6.0.1",
60
56
  "ws": "^8.18.0"
61
57
  },
58
+ "lint-staged": {
59
+ "*.{ts,js}": "prettier --write",
60
+ "*.scss": "prettier --write",
61
+ "*.json": "prettier --write"
62
+ },
62
63
  "devDependencies": {
64
+ "@eslint/js": "^10.0.1",
63
65
  "@playwright/test": "^1.43.0",
64
66
  "@types/bun": "^1.3.10",
67
+ "@types/culori": "^4.0.1",
65
68
  "@types/fontkit": "^2.0.8",
69
+ "@types/lodash": "^4.17.24",
70
+ "@types/markdown-it-container": "^4.0.0",
71
+ "@types/ws": "^8.18.1",
72
+ "eslint": "^10.0.3",
73
+ "husky": "^9.1.7",
74
+ "lint-staged": "^16.4.0",
66
75
  "prettier": "^3.6.2",
67
76
  "prettier-plugin-curly": "^0.4.1",
68
- "rimraf": "^6.1.3"
77
+ "rimraf": "^6.1.3",
78
+ "typescript": "^5.4.5",
79
+ "typescript-eslint": "^8.57.1"
69
80
  }
70
81
  }
@@ -14,8 +14,7 @@
14
14
  "text": { "type": "string" },
15
15
  "internal": { "type": "string", "pattern": "^\\/.*$" },
16
16
  "external": { "type": "string", "pattern": "^https?:\\/\\/.*$" },
17
- "disabled": { "type": "boolean" },
18
- "keywords": { "type": "array", "items": { "type": "string" } }
17
+ "disabled": { "type": "boolean" }
19
18
  },
20
19
  "oneOf": [{ "required": ["internal"] }, { "required": ["external"] }],
21
20
  "additionalProperties": false
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "object",
3
- "required": ["defaultTimeZone"],
3
+ "required": ["base", "title", "defaultTimeZone", "themeColor"],
4
4
  "properties": {
5
5
  "features": {
6
6
  "type": "object",
@@ -8,8 +8,7 @@
8
8
  "properties": {
9
9
  "search": { "type": "boolean", "default": true },
10
10
  "code": { "type": "boolean", "default": true },
11
- "favicon": { "type": "boolean", "default": false },
12
- "literateJava": { "type": "boolean", "default": false }
11
+ "favicon": { "type": "boolean", "default": false }
13
12
  },
14
13
  "required": ["search", "code", "favicon"],
15
14
  "additionalProperties": false
@@ -28,11 +27,7 @@
28
27
  "title": { "type": "string" },
29
28
  "symbol": { "type": "string", "pattern": "^[A-Z0-9\\- ]{1,5}$" },
30
29
  "titlePostfix": { "type": "string" },
31
- "courseId": { "type": "string" },
32
- "themeColor": {
33
- "type": "string",
34
- "pattern": "^hsl\\(\\d+(deg)? \\d+% \\d+%\\)$"
35
- },
30
+ "themeColor": { "type": "string" },
36
31
  "tintHue": {
37
32
  "type": "integer",
38
33
  "minimum": 0,
@@ -45,10 +40,7 @@
45
40
  "maximum": 100,
46
41
  "default": 100
47
42
  },
48
- "faviconColor": {
49
- "type": "string",
50
- "pattern": "^hsl\\(\\d+(deg)? \\d+% \\d+%\\)$"
51
- },
43
+ "faviconColor": { "type": "string" },
52
44
  "faviconSymbol": { "type": "string", "pattern": "^[A-Z0-9\\- ]{1,5}$" },
53
45
  "faviconFontWeight": {
54
46
  "type": "integer",
@@ -0,0 +1,96 @@
1
+ @use 'mixins' as *;
2
+
3
+ .alert {
4
+ border: 3px solid var(--bg2-color);
5
+ position: relative;
6
+ margin: 1rem 0;
7
+ }
8
+
9
+ .alert.warning {
10
+ background: var(--warning-bg-color);
11
+ border-color: var(--warning-bg-color);
12
+ }
13
+
14
+ .alert.note {
15
+ background: var(--note-bg-color);
16
+ border-color: var(--note-bg-color);
17
+ }
18
+
19
+ .alert .title {
20
+ padding: 10px 6px;
21
+ }
22
+
23
+ .alert .content {
24
+ padding: 12px;
25
+
26
+ .styled-list {
27
+ color: var(--fg-color);
28
+ }
29
+
30
+ tt,
31
+ code {
32
+ background: inherit;
33
+ padding: inherit;
34
+ }
35
+
36
+ time[datetime].is-modified {
37
+ // Reset the underline color
38
+ text-decoration: underline dotted;
39
+ }
40
+
41
+ a {
42
+ text-decoration-color: var(--fg-color);
43
+
44
+ &:hover {
45
+ text-decoration-color: inherit;
46
+ }
47
+ }
48
+
49
+ dfn {
50
+ color: inherit;
51
+ }
52
+ }
53
+
54
+ .alert .content > *:first-child {
55
+ margin-top: 0;
56
+ }
57
+
58
+ .alert .content > *:last-child {
59
+ margin-bottom: 0;
60
+ }
61
+
62
+ .alert .title::before {
63
+ left: 8px;
64
+ top: 8px;
65
+ position: absolute;
66
+ width: 32px;
67
+ height: 32px;
68
+ }
69
+
70
+ .alert.warning .title::before {
71
+ content: var(--icon-warning);
72
+ }
73
+
74
+ i.warning {
75
+ background: var(--icon-warning);
76
+ background-size: contain;
77
+ display: inline-block;
78
+ height: 1em;
79
+ width: 1em;
80
+ vertical-align: sub;
81
+ }
82
+
83
+ .alert.note .title::before {
84
+ content: var(--icon-note);
85
+ }
86
+
87
+ .alert .title {
88
+ font-weight: 700;
89
+ @include user-select(none);
90
+ padding-left: 48px;
91
+ margin: 0;
92
+ }
93
+
94
+ .alert > p:last-child {
95
+ margin-bottom: 0;
96
+ }
package/src/_base.scss ADDED
@@ -0,0 +1,145 @@
1
+ :root {
2
+ --box-border-width: 5px;
3
+
4
+ --gap: 12px;
5
+ --border-radius: 12px;
6
+ --control-border-width: 2px;
7
+ --label-border-width: 2px;
8
+ --focus-border-width: 3px;
9
+ --table-padding: 12px;
10
+ --details-padding: 12px;
11
+ --section-padding: 64px;
12
+
13
+ --main-width: 900px;
14
+ }
15
+
16
+ p {
17
+ margin: 0 0 1rem 0;
18
+ }
19
+
20
+ h1,
21
+ h2,
22
+ h3,
23
+ h4,
24
+ h5,
25
+ h6 {
26
+ font-family: var(--heading-font);
27
+ -webkit-break-after: avoid;
28
+ break-after: avoid;
29
+ line-height: 1.3;
30
+
31
+ /*
32
+ * Enable Inter's features:
33
+ * ss03 - round quotes & commas
34
+ * ss04 - disambiguation (make uppercase i and lowercase L distinct)
35
+ * dlig - discretionary ligatures (for style)
36
+ */
37
+ font-feature-settings: 'ss03', 'ss04', 'dlig';
38
+ }
39
+
40
+ h1 {
41
+ font-size: 250%;
42
+ font-weight: 700;
43
+ }
44
+
45
+ body {
46
+ color: var(--fg-color);
47
+ background-color: var(--bg-color);
48
+
49
+ font-family: var(--font);
50
+ font-size: var(--font-size);
51
+ font-weight: var(--font-weight);
52
+ line-height: var(--line-height);
53
+
54
+ /*
55
+ * Enable Inter's features for legibility:
56
+ * ss03 - round quotes & commas
57
+ * ss04 - disambiguation (make uppercase i and lowercase L distinct)
58
+ */
59
+ font-feature-settings: 'ss03', 'ss04';
60
+
61
+ @media (prefers-color-scheme: dark) {
62
+ & {
63
+ text-rendering: optimizeLegibility;
64
+ -webkit-font-smoothing: antialiased;
65
+ }
66
+ }
67
+ }
68
+
69
+ html {
70
+ overscroll-behavior: none;
71
+ background: var(--bg-color);
72
+ }
73
+
74
+ a {
75
+ color: var(--fg-color);
76
+ text-decoration-color: var(--fg2-color);
77
+ text-decoration-thickness: 1.5px;
78
+
79
+ @media (hover: hover) {
80
+ &:hover {
81
+ color: var(--fg2-color);
82
+ }
83
+ }
84
+ }
85
+
86
+ a:active {
87
+ color: var(--link-active-color);
88
+
89
+ @media (hover: hover) {
90
+ text-decoration-color: var(--link-active-color);
91
+ }
92
+ }
93
+
94
+ pre,
95
+ tt,
96
+ code {
97
+ font-family: var(--mono-font);
98
+ }
99
+
100
+ tt,
101
+ code {
102
+ font-size: var(--mono-font-size);
103
+ background: var(--bg2-color);
104
+ border-radius: var(--mono-border-radius);
105
+ padding: 0.12em 0.25em;
106
+ overflow-wrap: anywhere;
107
+ }
108
+
109
+ pre {
110
+ background: var(--bg2-color);
111
+ border-radius: var(--mono-border-radius);
112
+ line-height: var(--mono-line-height);
113
+ overflow: auto;
114
+ padding: var(--gap);
115
+
116
+ code {
117
+ padding: 0;
118
+ }
119
+ }
120
+
121
+ input[type='text'],
122
+ input[type='search'] {
123
+ background: var(--bg-color);
124
+ color: var(--fg-color);
125
+ border: var(--control-border-width) solid var(--fg2-color);
126
+ border-radius: var(--border-radius);
127
+ font-size: 12pt;
128
+ padding: 4px 4px;
129
+ }
130
+
131
+ input[type='search'] {
132
+ background-color: var(--bg-color);
133
+ background-image: var(--icon-search);
134
+ background-repeat: no-repeat;
135
+ background-position: left 6px center;
136
+ background-size: 16px 16px;
137
+ padding-left: 2rem;
138
+ border-radius: 18px;
139
+ min-width: 0;
140
+
141
+ &:disabled {
142
+ opacity: 0.4;
143
+ cursor: not-allowed;
144
+ }
145
+ }
@@ -1,4 +1,4 @@
1
- @import './_mixins.scss';
1
+ @use 'mixins' as *;
2
2
 
3
3
  :root {
4
4
  --toc-width: 260px;
@@ -10,6 +10,7 @@ html {
10
10
  overflow-y: auto;
11
11
  scroll-padding-top: calc(var(--header-collapsed-height) + var(--gap));
12
12
  }
13
+
13
14
  body {
14
15
  margin: 0;
15
16
  padding: 0;
@@ -134,6 +135,40 @@ body.literate main table {
134
135
  }
135
136
  }
136
137
 
138
+ .info {
139
+ @include user-select(none);
140
+ display: grid;
141
+ grid-template-columns: 1fr auto;
142
+ align-items: baseline;
143
+ color: var(--fg2-color);
144
+ font-size: var(--font-size-smaller);
145
+
146
+ @media (width < 450px) {
147
+ grid-template-columns: 1fr;
148
+ }
149
+
150
+ aside.author {
151
+ a {
152
+ text-decoration: none;
153
+
154
+ .text {
155
+ color: var(--fg2-color);
156
+ text-decoration: underline;
157
+ text-decoration-color: var(--fg2-color);
158
+ text-decoration-thickness: 1px;
159
+ text-underline-offset: 3px;
160
+ }
161
+ }
162
+
163
+ .avatar {
164
+ width: 24px;
165
+ height: 24px;
166
+ vertical-align: middle;
167
+ margin-left: 4px;
168
+ }
169
+ }
170
+ }
171
+
137
172
  .columns {
138
173
  display: grid;
139
174
  grid-template-columns: max-content auto;
@@ -17,7 +17,6 @@ export default (window: Window) => {
17
17
  link.className = 'heading-anchor';
18
18
  link.href = `#${el.id}`;
19
19
  link.title = 'Link to this heading';
20
- link.setAttribute('aria-label', 'Link to this heading');
21
20
 
22
21
  // Move all existing child nodes into the link
23
22
  while (el.firstChild) {
@@ -1,5 +1,3 @@
1
- @import '../_mixins.scss';
2
-
3
1
  .heading-anchor {
4
2
  position: relative;
5
3
  display: inline-block;
@@ -8,7 +6,7 @@
8
6
  }
9
7
 
10
8
  .heading-anchor::after {
11
- content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="hsl(20 8% 8%)" fill-rule="evenodd" clip-rule="evenodd" d="M10.975 14.51a1.05 1.05 0 0 0 0-1.485 2.95 2.95 0 0 1 0-4.172l3.536-3.535a2.95 2.95 0 1 1 4.172 4.172l-1.093 1.092a1.05 1.05 0 0 0 1.485 1.485l1.093-1.092a5.05 5.05 0 0 0-7.142-7.142L9.49 7.368a5.05 5.05 0 0 0 0 7.142c.41.41 1.075.41 1.485 0zm2.05-5.02a1.05 1.05 0 0 0 0 1.485 2.95 2.95 0 0 1 0 4.172l-3.5 3.5a2.95 2.95 0 1 1-4.171-4.172l1.025-1.025a1.05 1.05 0 0 0-1.485-1.485L3.87 12.99a5.05 5.05 0 0 0 7.142 7.142l3.5-3.5a5.05 5.05 0 0 0 0-7.142 1.05 1.05 0 0 0-1.485 0z"/></svg>');
9
+ content: var(--icon-anchor);
12
10
  display: inline-block;
13
11
  height: 0.75em;
14
12
  width: 0.75em;
@@ -16,12 +14,6 @@
16
14
  opacity: 0;
17
15
  }
18
16
 
19
- @media (prefers-color-scheme: dark) {
20
- .heading-anchor::after {
21
- content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="hsl(20 20% 90%)" fill-rule="evenodd" clip-rule="evenodd" d="M10.975 14.51a1.05 1.05 0 0 0 0-1.485 2.95 2.95 0 0 1 0-4.172l3.536-3.535a2.95 2.95 0 1 1 4.172 4.172l-1.093 1.092a1.05 1.05 0 0 0 1.485 1.485l1.093-1.092a5.05 5.05 0 0 0-7.142-7.142L9.49 7.368a5.05 5.05 0 0 0 0 7.142c.41.41 1.075.41 1.485 0zm2.05-5.02a1.05 1.05 0 0 0 0 1.485 2.95 2.95 0 0 1 0 4.172l-3.5 3.5a2.95 2.95 0 1 1-4.171-4.172l1.025-1.025a1.05 1.05 0 0 0-1.485-1.485L3.87 12.99a5.05 5.05 0 0 0 7.142 7.142l3.5-3.5a5.05 5.05 0 0 0 0-7.142 1.05 1.05 0 0 0-1.485 0z"/></svg>');
22
- }
23
- }
24
-
25
17
  .heading-anchor:focus::after,
26
18
  .heading-anchor:focus-visible::after {
27
19
  opacity: 1;
package/src/code/index.ts CHANGED
@@ -60,15 +60,15 @@ export default async function mount(window: Window): Promise<void> {
60
60
  downloadLink.addEventListener('click', async (e: MouseEvent) => {
61
61
  e.preventDefault();
62
62
  try {
63
- const handle = await (window as any).showSaveFilePicker({
63
+ const handle = await window.showSaveFilePicker!({
64
64
  suggestedName: downloadLink.download,
65
65
  });
66
66
  const response = await fetch(downloadLink.href);
67
67
  const writable = await handle.createWritable();
68
68
  await writable.write(await response.blob());
69
69
  await writable.close();
70
- } catch (err: any) {
71
- if (err.name !== 'AbortError') {
70
+ } catch (err: unknown) {
71
+ if (err instanceof Error && err.name !== 'AbortError') {
72
72
  throw err;
73
73
  }
74
74
  }
package/src/code.scss CHANGED
@@ -1,4 +1,4 @@
1
- @import './_mixins.scss';
1
+ @use 'mixins' as *;
2
2
 
3
3
  body.code,
4
4
  body.literate {
@@ -0,0 +1,5 @@
1
+ @use 'config/theme';
2
+ @use 'base';
3
+ @use 'layout';
4
+ @use 'alerts';
5
+ @use 'header/base' as *;
@@ -0,0 +1,129 @@
1
+ :root {
2
+ --header-collapsed-height: 45px;
3
+ }
4
+
5
+ header {
6
+ backdrop-filter: var(--backdrop-filter);
7
+ background: var(--bg-color-translucent);
8
+
9
+ width: 100%;
10
+ position: fixed;
11
+ top: 0;
12
+
13
+ z-index: 1000;
14
+
15
+ display: grid;
16
+ grid-template-columns: 1fr;
17
+
18
+ view-transition-name: site-header;
19
+ }
20
+
21
+ header details summary {
22
+ display: flex;
23
+ align-items: center;
24
+ height: var(--header-collapsed-height);
25
+ padding: 0;
26
+ list-style-type: none;
27
+ cursor: pointer;
28
+ position: relative;
29
+ container-type: inline-size;
30
+
31
+ @media (hover: hover) {
32
+ &:hover {
33
+ color: inherit;
34
+ }
35
+ }
36
+
37
+ &:focus-visible {
38
+ outline-offset: -2px;
39
+ }
40
+ }
41
+
42
+ header details summary::-webkit-details-marker {
43
+ display: none;
44
+ }
45
+
46
+ header details summary::marker {
47
+ content: '';
48
+ }
49
+
50
+ header summary::after {
51
+ order: -1;
52
+ height: 24px;
53
+ content: var(--icon-chevron);
54
+ margin-left: var(--gap);
55
+ margin-right: 8px;
56
+ }
57
+
58
+ header details[open] summary::after {
59
+ transform: rotate(90deg);
60
+ }
61
+
62
+ header .content {
63
+ position: relative;
64
+ }
65
+
66
+ header summary .logo {
67
+ flex: 0 0 auto;
68
+ color: var(--text-on-theme);
69
+ background: var(--theme-color);
70
+ padding: 1px 6px;
71
+ margin: 0 8px 0 0;
72
+ font-size: var(--font-size-smaller);
73
+ font-weight: 600;
74
+ pointer-events: none;
75
+ }
76
+
77
+ header summary .site-title {
78
+ flex: 1 1 0;
79
+ overflow: hidden;
80
+ text-overflow: ellipsis;
81
+ white-space: nowrap;
82
+ min-width: 0;
83
+ font-weight: 500;
84
+ pointer-events: none;
85
+ margin: 0 8px 0 0;
86
+ }
87
+
88
+ @container (max-width: 520px) {
89
+ header summary .site-title {
90
+ display: none;
91
+ }
92
+ }
93
+
94
+ header summary .to-top-container {
95
+ display: none;
96
+ align-items: center;
97
+ height: var(--header-collapsed-height);
98
+ pointer-events: none;
99
+ white-space: nowrap;
100
+
101
+ &:has(.is-visible) {
102
+ display: flex;
103
+ pointer-events: auto;
104
+ }
105
+ }
106
+
107
+ // Hide until the full stylesheet loads (prevents FOUC where the container
108
+ // is visible as a block element before it gets position: absolute)
109
+ header .results-container {
110
+ display: none;
111
+ }
112
+
113
+ header summary .search-controls {
114
+ margin-left: auto;
115
+ pointer-events: auto;
116
+ display: flex;
117
+ align-items: center;
118
+ height: var(--header-collapsed-height);
119
+ padding: 0 var(--gap);
120
+ min-width: 0;
121
+
122
+ input.search {
123
+ width: 180px;
124
+
125
+ @container (max-width: 280px) {
126
+ display: none;
127
+ }
128
+ }
129
+ }