fontist 3.0.8 → 3.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.
- checksums.yaml +4 -4
- data/docs/.vitepress/config.ts +49 -8
- data/docs/.vitepress/theme/style.css +575 -99
- data/docs/package.json +1 -1
- data/docs/public/robots.txt +4 -0
- data/docs/scripts/post-build.mjs +81 -0
- data/lib/fontist/utils/downloader.rb +55 -17
- data/lib/fontist/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5fa7d3f053af64da4931ed4d3808f703b5d984850d974d562fa5152b3f9f2ef
|
|
4
|
+
data.tar.gz: 2de87f3478a68c3486d426ccd327a99d7c8e5fcc9bb096f24497abb7790e7432
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec8d3ddcd93d2bc7167ee7d08165934adba5a7feb71a54a828a81fa244f969359f3205a0964feb04c85f8b56437a70d9b0b084dcdd11f1c399ea86b135881a13
|
|
7
|
+
data.tar.gz: f3926005478cf49b46d160570c2265aeccc31e6726f6cd52caa32b0bf557ba6e72915897d451e3f907909dc68988f20f17545e3279890cf16dcd994d46927276
|
data/docs/.vitepress/config.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import { defineConfig } from "vitepress";
|
|
1
|
+
import { defineConfig, type HeadConfig } from "vitepress";
|
|
2
2
|
import { fileURLToPath, URL } from "node:url";
|
|
3
3
|
|
|
4
|
+
const SITE_ORIGIN = "https://www.fontist.org";
|
|
5
|
+
const BASE_PATH = "/fontist/";
|
|
6
|
+
const DEFAULT_DESCRIPTION =
|
|
7
|
+
"Fontist brings cross-platform font management to the command line for Windows, Linux, and macOS. Free and open source.";
|
|
8
|
+
const DEFAULT_OG_IMAGE = `${SITE_ORIGIN}/og-image.png`;
|
|
9
|
+
|
|
10
|
+
// Map a page's source path to its canonical public URL (origin + base + clean path).
|
|
11
|
+
function pathToUrl(relativePath: string): string {
|
|
12
|
+
const bare = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
|
|
13
|
+
if (bare === "index") return `${SITE_ORIGIN}${BASE_PATH}`;
|
|
14
|
+
if (bare.endsWith("/index"))
|
|
15
|
+
return `${SITE_ORIGIN}${BASE_PATH}${bare.slice(0, -6)}/`;
|
|
16
|
+
return `${SITE_ORIGIN}${BASE_PATH}${bare}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
4
19
|
// https://vitepress.dev/reference/site-config
|
|
5
20
|
export default defineConfig({
|
|
6
21
|
// Register custom components
|
|
@@ -22,6 +37,11 @@ export default defineConfig({
|
|
|
22
37
|
|
|
23
38
|
lastUpdated: true,
|
|
24
39
|
|
|
40
|
+
// https://vitepress.dev/reference/site-config#sitemap
|
|
41
|
+
sitemap: {
|
|
42
|
+
hostname: SITE_ORIGIN,
|
|
43
|
+
},
|
|
44
|
+
|
|
25
45
|
// Base path for deployment (e.g., /fontist/ for fontist.org/fontist/)
|
|
26
46
|
base: process.env.BASE_PATH || "/fontist/",
|
|
27
47
|
|
|
@@ -38,19 +58,40 @@ export default defineConfig({
|
|
|
38
58
|
],
|
|
39
59
|
["link", { rel: "manifest", href: "/site.webmanifest" }],
|
|
40
60
|
["meta", { property: "og:type", content: "website" }],
|
|
41
|
-
["
|
|
61
|
+
["link", { rel: "preconnect", href: "https://fonts.googleapis.com" }],
|
|
62
|
+
["link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: "" }],
|
|
42
63
|
[
|
|
43
|
-
"
|
|
64
|
+
"link",
|
|
44
65
|
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"Fontist brings cross-platform font management to the command line for Windows, Linux, and macOS.",
|
|
66
|
+
rel: "stylesheet",
|
|
67
|
+
href: "https://fonts.googleapis.com/css2?family=Spectral:ital,opsz,wght@0,7..72,200..800;1,7..72,200..800&family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap",
|
|
48
68
|
},
|
|
49
69
|
],
|
|
50
|
-
["meta", { property: "og:image", content: "/logo-full.svg" }],
|
|
51
|
-
["meta", { name: "twitter:card", content: "summary_large_image" }],
|
|
52
70
|
],
|
|
53
71
|
|
|
72
|
+
// Per-page og:* and twitter:* tags are derived from each page's frontmatter.
|
|
73
|
+
// https://vitepress.dev/reference/site-config#transformhead
|
|
74
|
+
transformHead(ctx) {
|
|
75
|
+
const { pageData } = ctx;
|
|
76
|
+
const url = pathToUrl(pageData.relativePath);
|
|
77
|
+
const title = pageData.title || "Fontist";
|
|
78
|
+
const description = pageData.description || DEFAULT_DESCRIPTION;
|
|
79
|
+
const image = pageData.frontmatter.image || DEFAULT_OG_IMAGE;
|
|
80
|
+
|
|
81
|
+
const tags: HeadConfig[] = [
|
|
82
|
+
["meta", { property: "og:title", content: title }],
|
|
83
|
+
["meta", { property: "og:description", content: description }],
|
|
84
|
+
["meta", { property: "og:url", content: url }],
|
|
85
|
+
["meta", { property: "og:image", content: image }],
|
|
86
|
+
["meta", { name: "twitter:card", content: "summary_large_image" }],
|
|
87
|
+
["meta", { name: "twitter:title", content: title }],
|
|
88
|
+
["meta", { name: "twitter:description", content: description }],
|
|
89
|
+
["meta", { name: "twitter:image", content: image }],
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
return tags;
|
|
93
|
+
},
|
|
94
|
+
|
|
54
95
|
// https://vitepress.dev/reference/default-theme-config
|
|
55
96
|
themeConfig: {
|
|
56
97
|
logo: "/logo-full.svg",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
/* ================================================================
|
|
2
|
+
Fontist — Type Specimen design system (site-wide)
|
|
3
|
+
One palette + type system applied across every surface:
|
|
4
|
+
body, VPNav, VPFooter, and .vp-doc (all content pages).
|
|
5
|
+
================================================================ */
|
|
6
|
+
|
|
7
|
+
/* ── Brand + specimen palette ─────────────────────────────────── */
|
|
1
8
|
:root {
|
|
2
|
-
/* Brand
|
|
9
|
+
/* Brand colors from the logo */
|
|
3
10
|
--fontist-rose: #bf4e6a;
|
|
4
11
|
--fontist-rose-light: #d4718a;
|
|
5
12
|
--fontist-dark: #4d4b54;
|
|
@@ -8,145 +15,614 @@
|
|
|
8
15
|
--fontist-beige: #bebbac;
|
|
9
16
|
--fontist-pale: #dddac8;
|
|
10
17
|
|
|
11
|
-
/*
|
|
12
|
-
--
|
|
18
|
+
/* Specimen tokens — LIGHT (default) */
|
|
19
|
+
--spec-paper: #f1ece1;
|
|
20
|
+
--spec-paper-deep: #e7e0d1;
|
|
21
|
+
--spec-ink: #1c1a18;
|
|
22
|
+
--spec-ink-soft: #4a4744;
|
|
23
|
+
--spec-mute: #75716c;
|
|
24
|
+
--spec-rose: #b8475f;
|
|
25
|
+
--spec-rose-soft: #d4718a;
|
|
26
|
+
--spec-rule: rgba(28, 26, 24, 0.16);
|
|
27
|
+
--spec-rule-strong: rgba(28, 26, 24, 0.5);
|
|
28
|
+
--spec-term-bg: #1c1a18;
|
|
29
|
+
--spec-term-ink: #ecdfd0;
|
|
30
|
+
|
|
31
|
+
/* VitePress brand wiring */
|
|
32
|
+
--vp-c-brand-1: var(--spec-rose);
|
|
13
33
|
--vp-c-brand-2: #a3435a;
|
|
14
|
-
--vp-c-brand-3: var(--
|
|
15
|
-
--vp-c-brand-soft: rgba(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
--vp-c-text-
|
|
19
|
-
--vp-c-
|
|
20
|
-
--vp-c-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
--vp-c-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
--vp-c-brand-3: var(--spec-ink);
|
|
35
|
+
--vp-c-brand-soft: rgba(184, 71, 95, 0.12);
|
|
36
|
+
--vp-c-text-1: var(--spec-ink);
|
|
37
|
+
--vp-c-text-2: var(--spec-ink-soft);
|
|
38
|
+
--vp-c-text-3: var(--spec-mute);
|
|
39
|
+
--vp-c-bg: var(--spec-paper);
|
|
40
|
+
--vp-c-bg-soft: var(--spec-paper-deep);
|
|
41
|
+
--vp-c-bg-alt: var(--spec-paper-deep);
|
|
42
|
+
--vp-c-divider: var(--spec-rule);
|
|
43
|
+
--vp-c-gutter: var(--spec-rule);
|
|
44
|
+
|
|
45
|
+
/* Nav bg must be opaque (paper) so scrolled content doesn't leak through */
|
|
46
|
+
--vp-nav-bg-color: var(--spec-paper);
|
|
47
|
+
--vp-sidebar-bg-color: var(--spec-paper);
|
|
48
|
+
|
|
49
|
+
/* Type */
|
|
50
|
+
--spec-font-display: "Spectral", Georgia, serif;
|
|
51
|
+
--spec-font-body: "IBM Plex Sans", -apple-system, system-ui, sans-serif;
|
|
52
|
+
--spec-font-mono: "IBM Plex Mono", ui-monospace, "JetBrains Mono", monospace;
|
|
53
|
+
|
|
54
|
+
/* Override VitePress's default Inter with our specimen fonts everywhere */
|
|
55
|
+
--vp-font-family-base: var(--spec-font-body);
|
|
56
|
+
--vp-font-family-mono: var(--spec-font-mono);
|
|
34
57
|
|
|
35
58
|
/* Buttons */
|
|
36
|
-
--vp-button-brand-border: var(--
|
|
59
|
+
--vp-button-brand-border: var(--spec-rose);
|
|
37
60
|
--vp-button-brand-text: #ffffff;
|
|
38
|
-
--vp-button-brand-bg: var(--
|
|
61
|
+
--vp-button-brand-bg: var(--spec-rose);
|
|
39
62
|
--vp-button-brand-hover-border: #a3435a;
|
|
40
63
|
--vp-button-brand-hover-text: #ffffff;
|
|
41
64
|
--vp-button-brand-hover-bg: #a3435a;
|
|
42
|
-
--vp-button-brand-active-border: #8a3849;
|
|
43
|
-
--vp-button-brand-active-text: #ffffff;
|
|
44
|
-
--vp-button-brand-active-bg: #8a3849;
|
|
45
|
-
|
|
46
|
-
/* Sidebar */
|
|
47
|
-
--vp-sidebar-bg-color: var(--vp-c-bg-soft);
|
|
48
65
|
|
|
49
|
-
|
|
50
|
-
--vp-
|
|
66
|
+
--vp-button-alt-border: var(--spec-rule-strong);
|
|
67
|
+
--vp-button-alt-text: var(--spec-ink);
|
|
68
|
+
--vp-button-alt-bg: transparent;
|
|
69
|
+
--vp-button-alt-hover-border: var(--spec-rose);
|
|
70
|
+
--vp-button-alt-hover-text: var(--spec-rose);
|
|
71
|
+
--vp-button-alt-hover-bg: transparent;
|
|
51
72
|
}
|
|
52
73
|
|
|
53
|
-
/* Dark
|
|
74
|
+
/* ── Dark mode specimen ───────────────────────────────────────── */
|
|
54
75
|
html.dark {
|
|
55
|
-
--
|
|
76
|
+
--spec-paper: #161513;
|
|
77
|
+
--spec-paper-deep: #211f1c;
|
|
78
|
+
--spec-ink: #ecdfd0;
|
|
79
|
+
--spec-ink-soft: #b8ada0;
|
|
80
|
+
--spec-mute: #8a857f;
|
|
81
|
+
--spec-rose: #d4718a;
|
|
82
|
+
--spec-rose-soft: #e08a9e;
|
|
83
|
+
--spec-rule: rgba(236, 223, 208, 0.14);
|
|
84
|
+
--spec-rule-strong: rgba(236, 223, 208, 0.42);
|
|
85
|
+
--spec-term-bg: #0d0c0c;
|
|
86
|
+
--spec-term-ink: #ecdfd0;
|
|
87
|
+
|
|
88
|
+
--vp-c-brand-1: var(--spec-rose);
|
|
56
89
|
--vp-c-brand-2: var(--fontist-rose);
|
|
57
|
-
--vp-c-brand-3:
|
|
90
|
+
--vp-c-brand-3: var(--spec-ink);
|
|
58
91
|
--vp-c-brand-soft: rgba(212, 113, 138, 0.14);
|
|
92
|
+
--vp-c-text-1: var(--spec-ink);
|
|
93
|
+
--vp-c-text-2: var(--spec-ink-soft);
|
|
94
|
+
--vp-c-text-3: var(--spec-mute);
|
|
95
|
+
--vp-c-bg: var(--spec-paper);
|
|
96
|
+
--vp-c-bg-soft: var(--spec-paper-deep);
|
|
97
|
+
--vp-c-bg-alt: var(--spec-paper-deep);
|
|
98
|
+
--vp-c-divider: var(--spec-rule);
|
|
99
|
+
--vp-c-gutter: var(--spec-rule);
|
|
59
100
|
|
|
60
|
-
|
|
61
|
-
--vp-
|
|
62
|
-
--vp-
|
|
63
|
-
--vp-c-text-3: #8a8888;
|
|
64
|
-
|
|
65
|
-
/* Background Colors - Dark Mode */
|
|
66
|
-
--vp-c-bg: #1a1918;
|
|
67
|
-
--vp-c-bg-soft: #222120;
|
|
68
|
-
--vp-c-bg-alt: #2a2826;
|
|
69
|
-
--vp-c-bg-elv: #2a2826;
|
|
70
|
-
|
|
71
|
-
/* Hero - Dark Mode */
|
|
72
|
-
--vp-home-hero-name-background: linear-gradient(
|
|
73
|
-
120deg,
|
|
74
|
-
var(--fontist-rose-light) 30%,
|
|
75
|
-
#e08a9e
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
/* Buttons - Dark Mode */
|
|
79
|
-
--vp-button-brand-border: var(--fontist-rose-light);
|
|
80
|
-
--vp-button-brand-text: #1a1918;
|
|
81
|
-
--vp-button-brand-bg: var(--fontist-rose-light);
|
|
101
|
+
--vp-button-brand-border: var(--spec-rose);
|
|
102
|
+
--vp-button-brand-text: #161513;
|
|
103
|
+
--vp-button-brand-bg: var(--spec-rose);
|
|
82
104
|
--vp-button-brand-hover-border: var(--fontist-rose);
|
|
83
|
-
--vp-button-brand-hover-text: #
|
|
105
|
+
--vp-button-brand-hover-text: #161513;
|
|
84
106
|
--vp-button-brand-hover-bg: var(--fontist-rose);
|
|
85
|
-
--vp-button-brand-active-border: #a3435a;
|
|
86
|
-
--vp-button-brand-active-text: #1a1918;
|
|
87
|
-
--vp-button-brand-active-bg: #a3435a;
|
|
88
107
|
}
|
|
89
108
|
|
|
90
|
-
/*
|
|
91
|
-
|
|
92
|
-
color: var(--
|
|
109
|
+
/* ── Site-wide body: paper + grain + base type ────────────────── */
|
|
110
|
+
body {
|
|
111
|
+
background-color: var(--spec-paper);
|
|
112
|
+
font-family: var(--spec-font-body);
|
|
113
|
+
color: var(--spec-ink);
|
|
93
114
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
/* paper grain overlay (every page) */
|
|
116
|
+
body::before {
|
|
117
|
+
content: "";
|
|
118
|
+
position: fixed;
|
|
119
|
+
inset: 0;
|
|
120
|
+
z-index: 200;
|
|
121
|
+
pointer-events: none;
|
|
122
|
+
opacity: 0.04;
|
|
123
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.9 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
|
|
124
|
+
mix-blend-mode: multiply;
|
|
125
|
+
}
|
|
126
|
+
html.dark body::before {
|
|
127
|
+
opacity: 0.06;
|
|
128
|
+
mix-blend-mode: screen;
|
|
97
129
|
}
|
|
98
130
|
|
|
99
|
-
|
|
100
|
-
|
|
131
|
+
/* ── Top navigation (VPNav) — specimen restyle ─────────────────── */
|
|
132
|
+
/* Use box-shadow for the rule, not border-bottom — VPNavBar is height:64px
|
|
133
|
+
with box-sizing:border-box, so border-bottom is inside the 64px and gets
|
|
134
|
+
overlapped by .content (also 64px). box-shadow renders outside the box. */
|
|
135
|
+
.VPNavBar {
|
|
136
|
+
background-color: var(--spec-paper) !important;
|
|
137
|
+
border-bottom: none !important;
|
|
138
|
+
box-shadow: 0 1px 0 var(--spec-rule) !important;
|
|
139
|
+
}
|
|
140
|
+
.VPNavBar.has-sidebar .content,
|
|
141
|
+
.VPNavBar .content {
|
|
142
|
+
background-color: transparent !important;
|
|
143
|
+
}
|
|
144
|
+
.VPNavBar .content .curtain {
|
|
145
|
+
display: none !important;
|
|
101
146
|
}
|
|
102
147
|
|
|
103
|
-
/*
|
|
104
|
-
|
|
105
|
-
|
|
148
|
+
/* Nav links + dropdown groups: mono, tracked */
|
|
149
|
+
.VPNavBarMenuLink,
|
|
150
|
+
.VPNavBarMenuGroup {
|
|
151
|
+
font-family: var(--spec-font-mono) !important;
|
|
152
|
+
font-size: 12px !important;
|
|
153
|
+
letter-spacing: 0.14em !important;
|
|
154
|
+
text-transform: uppercase !important;
|
|
155
|
+
color: var(--spec-ink-soft) !important;
|
|
156
|
+
font-weight: 500 !important;
|
|
157
|
+
transition: color 0.2s ease;
|
|
158
|
+
}
|
|
159
|
+
.VPNavBarMenuLink:hover,
|
|
160
|
+
.VPNavBarMenuGroup:hover {
|
|
161
|
+
color: var(--spec-rose) !important;
|
|
162
|
+
}
|
|
163
|
+
.VPNavBarMenuLink .text,
|
|
164
|
+
.VPNavBarMenuGroup .text {
|
|
165
|
+
font-family: inherit !important;
|
|
166
|
+
}
|
|
167
|
+
/* Dropdown button — VitePress styles .button directly, so inheritance isn't enough */
|
|
168
|
+
.VPNavBarMenuGroup .button,
|
|
169
|
+
.VPNavBarExtra .button {
|
|
170
|
+
font-family: var(--spec-font-mono) !important;
|
|
171
|
+
font-size: 12px !important;
|
|
172
|
+
letter-spacing: 0.14em !important;
|
|
173
|
+
text-transform: uppercase !important;
|
|
174
|
+
font-weight: 500 !important;
|
|
175
|
+
color: var(--spec-ink-soft) !important;
|
|
176
|
+
line-height: 64px !important;
|
|
177
|
+
transition: color 0.2s ease;
|
|
178
|
+
}
|
|
179
|
+
.VPNavBarMenuGroup:hover .button,
|
|
180
|
+
.VPNavBarExtra:hover .button {
|
|
181
|
+
color: var(--spec-rose) !important;
|
|
182
|
+
}
|
|
183
|
+
/* VitePress renders the visible text in an inner .text span with its own
|
|
184
|
+
font-size (14px) and color — must target it directly, not via .button */
|
|
185
|
+
.VPNavBarMenuGroup .button .text,
|
|
186
|
+
.VPNavBarExtra .button .text {
|
|
187
|
+
font-size: 12px !important;
|
|
188
|
+
color: var(--spec-ink-soft) !important;
|
|
189
|
+
font-weight: 500 !important;
|
|
190
|
+
}
|
|
191
|
+
.VPNavBarMenuGroup:hover .button .text,
|
|
192
|
+
.VPNavBarExtra:hover .button .text {
|
|
193
|
+
color: var(--spec-rose) !important;
|
|
106
194
|
}
|
|
107
195
|
|
|
108
|
-
|
|
109
|
-
|
|
196
|
+
/* Flyout menu panel (shared by Integrations dropdown + ellipsis extra menu) */
|
|
197
|
+
.VPMenu {
|
|
198
|
+
background: var(--spec-paper) !important;
|
|
199
|
+
border: 1px solid var(--spec-rule) !important;
|
|
200
|
+
border-radius: 0 !important;
|
|
201
|
+
box-shadow: none !important;
|
|
202
|
+
}
|
|
203
|
+
.VPMenu .group-title {
|
|
204
|
+
font-family: var(--spec-font-mono) !important;
|
|
205
|
+
font-size: 11px !important;
|
|
206
|
+
letter-spacing: 0.14em !important;
|
|
207
|
+
text-transform: uppercase !important;
|
|
208
|
+
color: var(--spec-mute) !important;
|
|
209
|
+
}
|
|
210
|
+
/* Menu links — must out-specify VitePress's .VPLink.link (0-2-0),
|
|
211
|
+
so we chain .VPMenu parent to reach 0-2-1. Actual DOM:
|
|
212
|
+
.VPMenu > .items > .VPMenuLink > a.VPLink.link > span */
|
|
213
|
+
.VPMenu .VPMenuLink a,
|
|
214
|
+
.VPMenu .VPMenuLink a span {
|
|
215
|
+
font-family: var(--spec-font-mono) !important;
|
|
216
|
+
font-size: 12px !important;
|
|
217
|
+
letter-spacing: 0.14em !important;
|
|
218
|
+
text-transform: uppercase !important;
|
|
219
|
+
font-weight: 500 !important;
|
|
220
|
+
color: var(--spec-ink-soft) !important;
|
|
221
|
+
transition: color 0.2s ease, background 0.2s ease;
|
|
222
|
+
}
|
|
223
|
+
.VPMenu .VPMenuLink a:hover {
|
|
224
|
+
color: var(--spec-rose) !important;
|
|
225
|
+
background: var(--spec-paper-deep) !important;
|
|
226
|
+
}
|
|
227
|
+
.VPMenu .VPMenuLink a:hover span {
|
|
228
|
+
color: var(--spec-rose) !important;
|
|
229
|
+
}
|
|
230
|
+
/* Ellipsis menu: Appearance label + item labels — .VPMenu parent
|
|
231
|
+
boosts specificity above VitePress's scoped component styles */
|
|
232
|
+
.VPMenu .item .label,
|
|
233
|
+
.VPMenu .group .item {
|
|
234
|
+
font-family: var(--spec-font-mono) !important;
|
|
235
|
+
font-size: 12px !important;
|
|
236
|
+
letter-spacing: 0.14em !important;
|
|
237
|
+
text-transform: uppercase !important;
|
|
238
|
+
font-weight: 500 !important;
|
|
239
|
+
color: var(--spec-ink-soft) !important;
|
|
110
240
|
}
|
|
111
241
|
|
|
112
|
-
/*
|
|
113
|
-
.
|
|
114
|
-
|
|
242
|
+
/* Logo / title */
|
|
243
|
+
.VPNavBar .VPNavBarTitle .title {
|
|
244
|
+
font-family: var(--spec-font-display) !important;
|
|
245
|
+
font-weight: 400 !important;
|
|
246
|
+
font-size: 18px !important;
|
|
247
|
+
letter-spacing: 0.02em !important;
|
|
248
|
+
color: var(--spec-ink) !important;
|
|
249
|
+
}
|
|
250
|
+
.VPNav .VPImage {
|
|
251
|
+
height: 32px;
|
|
115
252
|
}
|
|
116
253
|
|
|
117
|
-
|
|
118
|
-
|
|
254
|
+
/* Social links + appearance toggle + search: quieter */
|
|
255
|
+
.VPNavBar .VPNavBarSocialLinks .social-link,
|
|
256
|
+
.VPNavBar .VPSocialLink,
|
|
257
|
+
.VPNavBarAppearance,
|
|
258
|
+
.VPNavBarExtra {
|
|
259
|
+
color: var(--spec-ink-soft) !important;
|
|
260
|
+
}
|
|
261
|
+
.VPNavBar .VPNavBarSearch {
|
|
262
|
+
--vp-nav-height: 56px;
|
|
263
|
+
}
|
|
264
|
+
.VPNavBarSearch .DocSearch-Button {
|
|
265
|
+
background: transparent !important;
|
|
266
|
+
border: 1px solid var(--spec-rule) !important;
|
|
267
|
+
font-family: var(--spec-font-mono) !important;
|
|
268
|
+
font-size: 12px !important;
|
|
269
|
+
letter-spacing: 0.06em !important;
|
|
270
|
+
}
|
|
271
|
+
.VPNavBarSearch .DocSearch-Button:hover {
|
|
272
|
+
border-color: var(--spec-rose) !important;
|
|
119
273
|
}
|
|
120
274
|
|
|
121
|
-
|
|
122
|
-
|
|
275
|
+
/* ── Footer — specimen colophon ───────────────────────────────── */
|
|
276
|
+
.VPFooter {
|
|
277
|
+
border-top: 1px solid var(--spec-rule) !important;
|
|
278
|
+
background-color: transparent !important;
|
|
279
|
+
padding: 28px 24px 40px !important;
|
|
280
|
+
}
|
|
281
|
+
.VPFooter .copyright,
|
|
282
|
+
.VPFooter .message {
|
|
283
|
+
font-family: var(--spec-font-mono) !important;
|
|
284
|
+
font-size: 11px !important;
|
|
285
|
+
letter-spacing: 0.1em !important;
|
|
286
|
+
color: var(--spec-mute) !important;
|
|
287
|
+
}
|
|
288
|
+
.VPFooter .message a {
|
|
289
|
+
color: var(--spec-ink-soft) !important;
|
|
290
|
+
border-bottom: 1px solid var(--spec-rule-strong);
|
|
291
|
+
text-decoration: none;
|
|
292
|
+
}
|
|
293
|
+
.VPFooter .message a:hover {
|
|
294
|
+
color: var(--spec-rose) !important;
|
|
123
295
|
}
|
|
124
296
|
|
|
125
|
-
|
|
126
|
-
|
|
297
|
+
/* ── .vp-doc typography — every content page (about, blog, docs) ─ */
|
|
298
|
+
.vp-doc {
|
|
299
|
+
font-family: var(--spec-font-body);
|
|
300
|
+
color: var(--spec-ink);
|
|
301
|
+
}
|
|
302
|
+
.vp-doc h1,
|
|
303
|
+
.vp-doc h2,
|
|
304
|
+
.vp-doc h3,
|
|
305
|
+
.vp-doc h4,
|
|
306
|
+
.vp-doc h5,
|
|
307
|
+
.vp-doc h6 {
|
|
308
|
+
font-family: var(--spec-font-display);
|
|
309
|
+
color: var(--spec-ink);
|
|
310
|
+
letter-spacing: -0.018em;
|
|
311
|
+
font-weight: 380;
|
|
312
|
+
}
|
|
313
|
+
.vp-doc h1 {
|
|
314
|
+
font-size: clamp(40px, 6vw, 64px);
|
|
315
|
+
font-weight: 360;
|
|
316
|
+
font-variation-settings: "opsz" 120;
|
|
317
|
+
line-height: 1.02;
|
|
318
|
+
letter-spacing: -0.025em;
|
|
319
|
+
margin-top: 0;
|
|
320
|
+
}
|
|
321
|
+
.vp-doc h2 {
|
|
322
|
+
font-size: clamp(28px, 3.4vw, 40px);
|
|
323
|
+
font-variation-settings: "opsz" 60;
|
|
324
|
+
border-top: 1px solid var(--spec-rule);
|
|
325
|
+
padding-top: 1.6em;
|
|
326
|
+
margin-top: 2.2em;
|
|
327
|
+
}
|
|
328
|
+
.vp-doc h3 {
|
|
329
|
+
font-size: clamp(20px, 2vw, 24px);
|
|
330
|
+
font-variation-settings: "opsz" 36;
|
|
331
|
+
}
|
|
332
|
+
.vp-doc p,
|
|
333
|
+
.vp-doc li {
|
|
334
|
+
font-size: 16.5px;
|
|
335
|
+
line-height: 1.65;
|
|
336
|
+
color: var(--spec-ink-soft);
|
|
337
|
+
}
|
|
338
|
+
.vp-doc p {
|
|
339
|
+
margin: 1.1em 0;
|
|
340
|
+
}
|
|
341
|
+
.vp-doc a {
|
|
342
|
+
color: var(--spec-rose);
|
|
343
|
+
text-decoration: none;
|
|
344
|
+
border-bottom: 1px solid var(--spec-rule);
|
|
345
|
+
transition: color 0.2s, border-color 0.2s;
|
|
346
|
+
}
|
|
347
|
+
.vp-doc a:hover {
|
|
348
|
+
color: var(--spec-rose);
|
|
349
|
+
border-color: var(--spec-rose);
|
|
350
|
+
}
|
|
351
|
+
.vp-doc strong {
|
|
352
|
+
color: var(--spec-ink);
|
|
353
|
+
font-weight: 600;
|
|
354
|
+
}
|
|
355
|
+
.vp-doc blockquote {
|
|
356
|
+
font-family: var(--spec-font-display);
|
|
357
|
+
font-style: italic;
|
|
358
|
+
font-weight: 380;
|
|
359
|
+
font-size: 1.25em;
|
|
360
|
+
line-height: 1.5;
|
|
361
|
+
color: var(--spec-ink);
|
|
362
|
+
border-left: 3px solid var(--spec-rose);
|
|
363
|
+
padding-left: 1.5em;
|
|
364
|
+
margin: 2em 0;
|
|
365
|
+
}
|
|
366
|
+
.vp-doc hr {
|
|
367
|
+
border: none;
|
|
368
|
+
border-top: 1px solid var(--spec-rule-strong);
|
|
369
|
+
width: 48px;
|
|
370
|
+
margin: 3em auto;
|
|
371
|
+
}
|
|
372
|
+
/* Drop cap — rose Spectral italic on the first paragraph of content pages */
|
|
373
|
+
.vp-doc > p:first-of-type::first-letter {
|
|
374
|
+
font-family: var(--spec-font-display);
|
|
375
|
+
font-style: italic;
|
|
376
|
+
font-weight: 460;
|
|
377
|
+
font-size: 3.4em;
|
|
378
|
+
float: left;
|
|
379
|
+
line-height: 0.8;
|
|
380
|
+
padding: 0.04em 0.1em 0 0;
|
|
381
|
+
color: var(--spec-rose);
|
|
382
|
+
font-variation-settings: "opsz" 72, "wght" 520;
|
|
383
|
+
}
|
|
384
|
+
/* Code blocks — light: warm paper-deep (matches Shiki light tokens);
|
|
385
|
+
dark: terminal ink (matches Shiki dark tokens) */
|
|
386
|
+
.vp-doc [class*="language-"] {
|
|
387
|
+
background-color: var(--spec-paper-deep) !important;
|
|
388
|
+
border: 1px solid var(--spec-rule);
|
|
389
|
+
font-family: var(--spec-font-mono);
|
|
390
|
+
font-size: 13.5px;
|
|
391
|
+
}
|
|
392
|
+
html.dark .vp-doc [class*="language-"] {
|
|
393
|
+
background-color: var(--spec-term-bg) !important;
|
|
394
|
+
}
|
|
395
|
+
.vp-doc :not(pre) > code {
|
|
396
|
+
font-family: var(--spec-font-mono);
|
|
397
|
+
font-size: 0.9em;
|
|
398
|
+
background: var(--spec-paper-deep);
|
|
399
|
+
border: 1px solid var(--spec-rule);
|
|
400
|
+
padding: 0.1em 0.4em;
|
|
401
|
+
border-radius: 2px;
|
|
402
|
+
color: var(--spec-ink);
|
|
403
|
+
}
|
|
404
|
+
/* Tables */
|
|
405
|
+
.vp-doc table {
|
|
406
|
+
font-size: 14px;
|
|
407
|
+
border-collapse: collapse;
|
|
408
|
+
}
|
|
409
|
+
.vp-doc table th,
|
|
410
|
+
.vp-doc table td {
|
|
411
|
+
border: 1px solid var(--spec-rule);
|
|
412
|
+
padding: 8px 12px;
|
|
413
|
+
}
|
|
414
|
+
.vp-doc table th {
|
|
415
|
+
font-family: var(--spec-font-mono);
|
|
416
|
+
font-size: 11px;
|
|
417
|
+
letter-spacing: 0.1em;
|
|
418
|
+
text-transform: uppercase;
|
|
419
|
+
color: var(--spec-mute);
|
|
420
|
+
background: var(--spec-paper-deep);
|
|
127
421
|
}
|
|
128
422
|
|
|
129
|
-
/*
|
|
130
|
-
.
|
|
131
|
-
background-color: var(--
|
|
132
|
-
border-
|
|
423
|
+
/* ── Sidebar (if used) ────────────────────────────────────────── */
|
|
424
|
+
.VPSidebar {
|
|
425
|
+
background-color: var(--spec-paper) !important;
|
|
426
|
+
border-right: 1px solid var(--spec-rule) !important;
|
|
427
|
+
font-family: var(--spec-font-mono);
|
|
428
|
+
}
|
|
429
|
+
.VPSidebar .VPSidebarItem .text {
|
|
430
|
+
font-size: 13px;
|
|
133
431
|
}
|
|
134
432
|
|
|
135
|
-
|
|
136
|
-
|
|
433
|
+
/* ── 404 page — specimen ───────────────────────────────────────── */
|
|
434
|
+
.NotFound {
|
|
435
|
+
text-align: center;
|
|
436
|
+
padding: clamp(3rem, 8vw, 6rem) 1.5rem clamp(4rem, 10vw, 8rem);
|
|
437
|
+
font-family: var(--spec-font-body);
|
|
438
|
+
}
|
|
439
|
+
.NotFound .code {
|
|
440
|
+
font-family: var(--spec-font-display);
|
|
441
|
+
font-style: italic;
|
|
442
|
+
font-weight: 300;
|
|
443
|
+
font-variation-settings: "opsz" 144;
|
|
444
|
+
font-size: clamp(120px, 24vw, 280px);
|
|
445
|
+
line-height: 1;
|
|
446
|
+
color: var(--spec-rose);
|
|
447
|
+
opacity: 0.9;
|
|
448
|
+
margin: 0 0 0.5rem;
|
|
449
|
+
}
|
|
450
|
+
.NotFound .title {
|
|
451
|
+
font-family: var(--spec-font-mono);
|
|
452
|
+
font-size: 12px;
|
|
453
|
+
letter-spacing: 0.22em;
|
|
454
|
+
text-transform: uppercase;
|
|
455
|
+
color: var(--spec-ink-soft);
|
|
456
|
+
margin: 0;
|
|
457
|
+
}
|
|
458
|
+
.NotFound .divider {
|
|
459
|
+
width: 60px;
|
|
460
|
+
border-top: 1px solid var(--spec-rule-strong);
|
|
461
|
+
margin: 2rem auto;
|
|
462
|
+
}
|
|
463
|
+
.NotFound .quote {
|
|
464
|
+
font-family: var(--spec-font-display);
|
|
465
|
+
font-style: italic;
|
|
466
|
+
font-weight: 380;
|
|
467
|
+
font-size: clamp(18px, 2.2vw, 26px);
|
|
468
|
+
line-height: 1.5;
|
|
469
|
+
color: var(--spec-ink);
|
|
470
|
+
border: none;
|
|
471
|
+
max-width: 32ch;
|
|
472
|
+
margin: 0 auto 2rem;
|
|
473
|
+
padding: 0;
|
|
474
|
+
}
|
|
475
|
+
.NotFound .action .link {
|
|
476
|
+
font-family: var(--spec-font-mono);
|
|
477
|
+
font-size: 13px;
|
|
478
|
+
letter-spacing: 0.12em;
|
|
479
|
+
text-transform: uppercase;
|
|
480
|
+
color: var(--spec-paper);
|
|
481
|
+
background: var(--spec-ink);
|
|
482
|
+
padding: 12px 22px;
|
|
483
|
+
text-decoration: none;
|
|
484
|
+
display: inline-block;
|
|
485
|
+
transition: background 0.25s ease;
|
|
486
|
+
}
|
|
487
|
+
.NotFound .action .link:hover {
|
|
488
|
+
background: var(--spec-rose);
|
|
137
489
|
}
|
|
138
490
|
|
|
139
|
-
/*
|
|
140
|
-
|
|
141
|
-
|
|
491
|
+
/* ================================================================
|
|
492
|
+
VitePress home layout — VPHero + VPFeatures specimen restyle
|
|
493
|
+
Used by the fontist/docs and fontisan/docs homepages.
|
|
494
|
+
================================================================ */
|
|
495
|
+
|
|
496
|
+
/* ── VPHero ───────────────────────────────────────────────────── */
|
|
497
|
+
.VPHero .container { gap: 48px; }
|
|
498
|
+
.VPHero .heading {
|
|
499
|
+
display: flex;
|
|
500
|
+
flex-direction: column;
|
|
501
|
+
margin-bottom: 0.5rem;
|
|
502
|
+
}
|
|
503
|
+
.VPHero .name {
|
|
504
|
+
font-family: var(--spec-font-display) !important;
|
|
505
|
+
font-weight: 360 !important;
|
|
506
|
+
font-variation-settings: "opsz" 72;
|
|
507
|
+
font-size: clamp(40px, 6vw, 72px) !important;
|
|
508
|
+
line-height: 1 !important;
|
|
509
|
+
letter-spacing: -0.02em !important;
|
|
510
|
+
background: none !important;
|
|
511
|
+
-webkit-background-clip: unset !important;
|
|
512
|
+
-webkit-text-fill-color: var(--spec-ink) !important;
|
|
513
|
+
color: var(--spec-ink) !important;
|
|
514
|
+
}
|
|
515
|
+
.VPHero .text {
|
|
516
|
+
font-family: var(--spec-font-display) !important;
|
|
517
|
+
font-weight: 340 !important;
|
|
518
|
+
font-size: clamp(28px, 4vw, 44px) !important;
|
|
519
|
+
line-height: 1.05 !important;
|
|
520
|
+
letter-spacing: -0.02em !important;
|
|
521
|
+
color: var(--spec-rose) !important;
|
|
522
|
+
margin-top: 0.1em !important;
|
|
523
|
+
display: block !important;
|
|
524
|
+
}
|
|
525
|
+
.VPHero .tagline {
|
|
526
|
+
font-family: var(--spec-font-body) !important;
|
|
527
|
+
font-size: 16px !important;
|
|
528
|
+
line-height: 1.6 !important;
|
|
529
|
+
color: var(--spec-ink-soft) !important;
|
|
530
|
+
max-width: 480px !important;
|
|
531
|
+
}
|
|
532
|
+
.VPHero .actions { gap: 12px; }
|
|
533
|
+
.VPHero .VPButton {
|
|
534
|
+
font-family: var(--spec-font-mono) !important;
|
|
535
|
+
font-size: 12px !important;
|
|
536
|
+
letter-spacing: 0.12em !important;
|
|
537
|
+
text-transform: uppercase !important;
|
|
538
|
+
border-radius: 0 !important;
|
|
539
|
+
}
|
|
540
|
+
.VPHero .VPButton.brand {
|
|
541
|
+
border: 1px solid var(--spec-ink) !important;
|
|
542
|
+
background: var(--spec-ink) !important;
|
|
543
|
+
color: var(--spec-paper) !important;
|
|
544
|
+
}
|
|
545
|
+
.VPHero .VPButton.brand:hover {
|
|
546
|
+
background: var(--spec-rose) !important;
|
|
547
|
+
border-color: var(--spec-rose) !important;
|
|
548
|
+
}
|
|
549
|
+
.VPHero .VPButton.alt {
|
|
550
|
+
border: 1px solid var(--spec-rule-strong) !important;
|
|
551
|
+
background: transparent !important;
|
|
552
|
+
color: var(--spec-ink) !important;
|
|
553
|
+
}
|
|
554
|
+
.VPHero .VPButton.alt:hover {
|
|
555
|
+
border-color: var(--spec-rose) !important;
|
|
556
|
+
color: var(--spec-rose) !important;
|
|
557
|
+
background: transparent !important;
|
|
142
558
|
}
|
|
143
559
|
|
|
144
|
-
/*
|
|
145
|
-
|
|
146
|
-
background
|
|
560
|
+
/* ── HeroCodeBlock → specimen terminal plate ──────────────────── */
|
|
561
|
+
.hero-code-block {
|
|
562
|
+
background: var(--spec-term-bg) !important;
|
|
563
|
+
border: 1px solid var(--spec-rule) !important;
|
|
564
|
+
border-radius: 0 !important;
|
|
565
|
+
box-shadow: 12px 12px 0 -1px var(--spec-paper-deep), 12px 12px 0 var(--spec-rule) !important;
|
|
566
|
+
max-width: 520px !important;
|
|
567
|
+
}
|
|
568
|
+
.code-header {
|
|
569
|
+
background: rgba(0, 0, 0, 0.2) !important;
|
|
570
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
147
571
|
}
|
|
572
|
+
.code-title {
|
|
573
|
+
font-family: var(--spec-font-mono) !important;
|
|
574
|
+
font-size: 11px !important;
|
|
575
|
+
letter-spacing: 0.12em !important;
|
|
576
|
+
text-transform: uppercase !important;
|
|
577
|
+
color: var(--spec-rose-soft) !important;
|
|
578
|
+
}
|
|
579
|
+
.code-content {
|
|
580
|
+
font-family: var(--spec-font-mono) !important;
|
|
581
|
+
font-size: 13px !important;
|
|
582
|
+
line-height: 1.7 !important;
|
|
583
|
+
color: var(--spec-term-ink) !important;
|
|
584
|
+
}
|
|
585
|
+
.code-content .prompt { color: var(--spec-rose-soft) !important; font-weight: 500; }
|
|
586
|
+
.code-content .cmd { color: #93c5fd !important; }
|
|
587
|
+
.code-content .success { color: #8fb98a !important; }
|
|
588
|
+
.code-content .comment { color: rgba(236, 223, 208, 0.45) !important; }
|
|
589
|
+
.code-content .line { padding: 1px 0; }
|
|
148
590
|
|
|
149
|
-
/*
|
|
150
|
-
.
|
|
151
|
-
|
|
591
|
+
/* ── VPFeatures — only style cards, DON'T override grid layout ─── */
|
|
592
|
+
/* VitePress renders responsive columns on .item.grid-N (child of .items).
|
|
593
|
+
Overriding .items as a grid fights VitePress's own engine. Only style .box. */
|
|
594
|
+
.VPFeatures .container { padding-top: 2rem; }
|
|
595
|
+
|
|
596
|
+
.VPFeature {
|
|
597
|
+
background: transparent !important;
|
|
598
|
+
border: none !important;
|
|
599
|
+
border-radius: 0 !important;
|
|
600
|
+
box-shadow: none !important;
|
|
601
|
+
}
|
|
602
|
+
.VPFeature .box {
|
|
603
|
+
background: var(--spec-paper) !important;
|
|
604
|
+
border: 1px solid var(--spec-rule) !important;
|
|
605
|
+
border-radius: 0 !important;
|
|
606
|
+
box-shadow: none !important;
|
|
607
|
+
height: 100% !important;
|
|
608
|
+
transition: border-color 0.2s ease, background 0.2s ease !important;
|
|
609
|
+
}
|
|
610
|
+
.VPFeature .box:hover {
|
|
611
|
+
background: var(--spec-paper-deep) !important;
|
|
612
|
+
border-color: var(--spec-rose) !important;
|
|
152
613
|
}
|
|
614
|
+
.VPFeature .title {
|
|
615
|
+
font-family: var(--spec-font-display) !important;
|
|
616
|
+
font-weight: 400 !important;
|
|
617
|
+
font-size: 1.1rem !important;
|
|
618
|
+
color: var(--spec-ink) !important;
|
|
619
|
+
}
|
|
620
|
+
.VPFeature .details {
|
|
621
|
+
font-family: var(--spec-font-body) !important;
|
|
622
|
+
font-size: 14px !important;
|
|
623
|
+
line-height: 1.55 !important;
|
|
624
|
+
color: var(--spec-ink-soft) !important;
|
|
625
|
+
}
|
|
626
|
+
html.dark .VPFeature .box { background: var(--spec-paper) !important; }
|
|
627
|
+
html.dark .VPFeature .box:hover { background: var(--spec-paper-deep) !important; }
|
|
628
|
+
|
data/docs/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Post-build fixes for GitHub Pages subpath deployment.
|
|
3
|
+
//
|
|
4
|
+
// 1. Dirify: convert VitePress file-style output (foo.html) to directory-style
|
|
5
|
+
// (foo/index.html) so GitHub Pages resolves BOTH /foo and /foo/. Without
|
|
6
|
+
// this, /foo/ 404s because foo.html is a file, not a directory.
|
|
7
|
+
//
|
|
8
|
+
// 2. Sitemap base: insert the deployment base path into sitemap.xml URLs.
|
|
9
|
+
// VitePress omits `base` from sitemap route paths (and ignores the path
|
|
10
|
+
// portion of `sitemap.hostname`), so the generated URLs point at the origin
|
|
11
|
+
// root instead of the subsite. We rewrite each <loc> to include the base.
|
|
12
|
+
//
|
|
13
|
+
// Idempotent. Kept at the dist root: index.html (site root), 404.html (GHP 404 page).
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
renameSync,
|
|
20
|
+
statSync,
|
|
21
|
+
writeFileSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
|
|
25
|
+
const DIST = ".vitepress/dist";
|
|
26
|
+
const HTML = ".html";
|
|
27
|
+
const KEEP_AT_ROOT = new Set(["index.html", "404.html"]);
|
|
28
|
+
// Subsite identity — must match config.ts base and the real deployment URL.
|
|
29
|
+
const ORIGIN = "https://www.fontist.org";
|
|
30
|
+
const BASE = "fontist"; // path segment under ORIGIN (no slashes)
|
|
31
|
+
|
|
32
|
+
let moved = 0;
|
|
33
|
+
let skipped = 0;
|
|
34
|
+
|
|
35
|
+
function dirify(dir) {
|
|
36
|
+
for (const entry of readdirSync(dir)) {
|
|
37
|
+
const full = join(dir, entry);
|
|
38
|
+
if (statSync(full).isDirectory()) {
|
|
39
|
+
dirify(full);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!entry.endsWith(HTML)) continue;
|
|
43
|
+
if (entry === "index.html") continue; // already directory-style
|
|
44
|
+
if (dir === DIST && KEEP_AT_ROOT.has(entry)) {
|
|
45
|
+
skipped++; // root index.html / 404.html stay put
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const name = entry.slice(0, -HTML.length);
|
|
49
|
+
const targetDir = join(dir, name);
|
|
50
|
+
const targetIndex = join(targetDir, "index.html");
|
|
51
|
+
if (existsSync(targetIndex)) {
|
|
52
|
+
console.warn(`[post-build] skip ${full.replace(DIST + "/", "")}: target exists`);
|
|
53
|
+
skipped++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
mkdirSync(targetDir, { recursive: true });
|
|
57
|
+
renameSync(full, targetIndex);
|
|
58
|
+
moved++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fixSitemapBase() {
|
|
63
|
+
const sitemap = `${DIST}/sitemap.xml`;
|
|
64
|
+
if (!existsSync(sitemap)) return false;
|
|
65
|
+
let xml = readFileSync(sitemap, "utf8");
|
|
66
|
+
// Insert BASE after the origin unless the path already starts with BASE
|
|
67
|
+
// (VitePress emits the homepage route as "/<base>"; other routes omit it).
|
|
68
|
+
const originPattern = new RegExp(
|
|
69
|
+
`(<loc>${ORIGIN.replaceAll("/", "\\/")}\\/)(?!${BASE}(\\/|$))`,
|
|
70
|
+
"g",
|
|
71
|
+
);
|
|
72
|
+
xml = xml.replace(originPattern, `$1${BASE}/`);
|
|
73
|
+
writeFileSync(sitemap, xml);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
dirify(DIST);
|
|
78
|
+
const sitemapFixed = fixSitemapBase();
|
|
79
|
+
console.log(
|
|
80
|
+
`[post-build] dirified ${moved} route(s), kept ${skipped} at root; sitemap ${sitemapFixed ? "rewritten with /" + BASE + "/" : "absent (skipped)"}`,
|
|
81
|
+
);
|
|
@@ -25,27 +25,61 @@ module Fontist
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def download
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
check_tampered(file)
|
|
33
|
-
|
|
28
|
+
cached = use_cached_file?
|
|
29
|
+
file = fetch_file
|
|
30
|
+
verify_checksum!(file)
|
|
34
31
|
file
|
|
32
|
+
rescue Fontist::Errors::TamperedFileError => e
|
|
33
|
+
delete_cached_file(file)
|
|
34
|
+
raise invalid_resource_error(e) unless cached
|
|
35
|
+
|
|
36
|
+
retry_download_after_cache_mismatch
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
private
|
|
38
40
|
|
|
39
41
|
attr_reader :file, :sha, :file_size
|
|
40
42
|
|
|
41
|
-
def
|
|
43
|
+
def retry_download_after_cache_mismatch
|
|
44
|
+
file = fetch_file
|
|
45
|
+
verify_checksum!(file)
|
|
46
|
+
file
|
|
47
|
+
rescue Fontist::Errors::TamperedFileError => e
|
|
48
|
+
delete_cached_file(file)
|
|
49
|
+
raise invalid_resource_error(e)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fetch_file
|
|
53
|
+
@cache.fetch(url) { download_file }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def use_cached_file?
|
|
57
|
+
Fontist.use_cache? && !!@cache.already_fetched?([url])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete_cached_file(file)
|
|
61
|
+
cached_path = file&.path
|
|
62
|
+
file&.close unless file&.closed?
|
|
63
|
+
@cache.delete(url)
|
|
64
|
+
|
|
65
|
+
FileUtils.rm_rf(Pathname.new(cached_path).dirname) if cached_path
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def verify_checksum!(file)
|
|
69
|
+
return if sha.empty?
|
|
70
|
+
|
|
42
71
|
file_checksum = Digest::SHA256.file(file).to_s
|
|
43
|
-
if
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
return if sha.include?(file_checksum)
|
|
73
|
+
|
|
74
|
+
raise Fontist::Errors::TamperedFileError,
|
|
75
|
+
"SHA256 checksum mismatch for #{url}: #{file_checksum}, " \
|
|
76
|
+
"should be #{sha.join(', or ')}."
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def invalid_resource_error(error)
|
|
80
|
+
Fontist::Errors::InvalidResourceError.new(
|
|
81
|
+
"Invalid resource: #{@file}. Error: #{error.message}.",
|
|
82
|
+
)
|
|
49
83
|
end
|
|
50
84
|
|
|
51
85
|
def byte_to_megabyte
|
|
@@ -62,15 +96,19 @@ module Fontist
|
|
|
62
96
|
print_download_start if @verbose
|
|
63
97
|
do_download_file
|
|
64
98
|
rescue Down::Error => e
|
|
65
|
-
if
|
|
66
|
-
sleep(backoff_time(@tries))
|
|
67
|
-
retry
|
|
68
|
-
end
|
|
99
|
+
retry if retry_download?
|
|
69
100
|
|
|
70
101
|
raise Fontist::Errors::InvalidResourceError,
|
|
71
102
|
"Invalid URL: #{@file}. Error: #{e.inspect}."
|
|
72
103
|
end
|
|
73
104
|
|
|
105
|
+
def retry_download?
|
|
106
|
+
return false unless @tries < max_retries
|
|
107
|
+
|
|
108
|
+
sleep(backoff_time(@tries))
|
|
109
|
+
true
|
|
110
|
+
end
|
|
111
|
+
|
|
74
112
|
def max_retries
|
|
75
113
|
@max_retries ||= 3
|
|
76
114
|
end
|
data/lib/fontist/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: down
|
|
@@ -343,9 +343,11 @@ files:
|
|
|
343
343
|
- docs/public/favicon.svg
|
|
344
344
|
- docs/public/logo-full.svg
|
|
345
345
|
- docs/public/logo.svg
|
|
346
|
+
- docs/public/robots.txt
|
|
346
347
|
- docs/public/site.webmanifest
|
|
347
348
|
- docs/public/web-app-manifest-192x192.png
|
|
348
349
|
- docs/public/web-app-manifest-512x512.png
|
|
350
|
+
- docs/scripts/post-build.mjs
|
|
349
351
|
- exe/fontist
|
|
350
352
|
- fontist.gemspec
|
|
351
353
|
- formula_filename_index.yml
|