@glossarist/concept-browser 0.7.106 → 0.7.108
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/README.md +146 -1
- package/cli/index.mjs +133 -36
- package/cli/index.ts +137 -37
- package/package.json +1 -1
- package/src/__tests__/cli/favicon-config.test.ts +61 -0
- package/src/__tests__/utils/favicon-base.test.ts +89 -0
- package/src/__tests__/utils/favicon-links.test.ts +124 -0
- package/src/__tests__/utils/font-stack.test.ts +105 -0
- package/src/components/ConceptDetail.vue +1 -1
- package/src/config/types.ts +22 -0
- package/src/config/use-site-config.ts +22 -6
- package/src/layouts/Default.astro +46 -35
- package/src/styles/tailwind.css +5 -0
- package/src/utils/favicon-base.ts +45 -0
- package/src/utils/favicon-links.ts +79 -0
- package/src/utils/font-stack.ts +77 -0
- package/src/views/DatasetView.vue +1 -1
- package/src/views/GroupView.vue +1 -1
- package/src/views/HomeView.vue +1 -1
- package/tailwind.config.js +3 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Favicon link-tag builder — shared SSOT for the `<link rel="icon">`
|
|
3
|
+
* block emitted in `<head>`.
|
|
4
|
+
*
|
|
5
|
+
* Used by:
|
|
6
|
+
* - `src/layouts/Default.astro` (build-time SSG)
|
|
7
|
+
*
|
|
8
|
+
* Why this exists: a deployment at `/cie-eilv/` has its favicon files
|
|
9
|
+
* at `/cie-eilv/favicon.ico`, NOT `/favicon.ico`. Hardcoding `/` as
|
|
10
|
+
* the URL prefix (which 0.7.106 did) caused 404s on every sub-path
|
|
11
|
+
* deployment. This builder walks the configuration cascade via
|
|
12
|
+
* `resolveFaviconBase` and emits correctly-prefixed link tags.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { resolveFaviconBase } from './favicon-base';
|
|
16
|
+
|
|
17
|
+
export interface FaviconLinkTag {
|
|
18
|
+
rel: string;
|
|
19
|
+
type?: string;
|
|
20
|
+
sizes?: string;
|
|
21
|
+
href: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Default favicon file set emitted by the CLI's `favicons` pipeline. */
|
|
25
|
+
const DEFAULT_FAVICON_FILES: readonly FaviconLinkTag[] = [
|
|
26
|
+
{ rel: 'icon', type: 'image/x-icon', href: 'favicon.ico' },
|
|
27
|
+
{ rel: 'icon', type: 'image/svg+xml', href: 'favicon.svg' },
|
|
28
|
+
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: 'favicon-16x16.png' },
|
|
29
|
+
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: 'favicon-32x32.png' },
|
|
30
|
+
{ rel: 'icon', type: 'image/png', sizes: '48x48', href: 'favicon-48x48.png' },
|
|
31
|
+
{ rel: 'apple-touch-icon', sizes: '57x57', href: 'apple-touch-icon-57x57.png' },
|
|
32
|
+
{ rel: 'apple-touch-icon', sizes: '60x60', href: 'apple-touch-icon-60x60.png' },
|
|
33
|
+
{ rel: 'apple-touch-icon', sizes: '72x72', href: 'apple-touch-icon-72x72.png' },
|
|
34
|
+
{ rel: 'apple-touch-icon', sizes: '76x76', href: 'apple-touch-icon-76x76.png' },
|
|
35
|
+
{ rel: 'apple-touch-icon', sizes: '114x114',href: 'apple-touch-icon-114x114.png' },
|
|
36
|
+
{ rel: 'apple-touch-icon', sizes: '120x120',href: 'apple-touch-icon-120x120.png' },
|
|
37
|
+
{ rel: 'apple-touch-icon', sizes: '144x144',href: 'apple-touch-icon-144x144.png' },
|
|
38
|
+
{ rel: 'apple-touch-icon', sizes: '152x152',href: 'apple-touch-icon-152x152.png' },
|
|
39
|
+
{ rel: 'apple-touch-icon', sizes: '167x167',href: 'apple-touch-icon-167x167.png' },
|
|
40
|
+
{ rel: 'apple-touch-icon', sizes: '180x180',href: 'apple-touch-icon-180x180.png' },
|
|
41
|
+
{ rel: 'apple-touch-icon', sizes: '1024x1024',href: 'apple-touch-icon-1024x1024.png' },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export interface BuildFaviconLinksInput {
|
|
45
|
+
/** Explicit consumer override via `branding.favicon.base_path`. */
|
|
46
|
+
brandingFaviconBasePath?: string;
|
|
47
|
+
/** Consumer's declared sub-path via `site-config.yml:basePath`. */
|
|
48
|
+
siteConfigBasePath?: string;
|
|
49
|
+
/** Build-time BASE_PATH from Astro/Vite (`import.meta.env.BASE_URL`). */
|
|
50
|
+
astroBaseUrl?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the list of default favicon link tags with BASE_PATH-aware URLs.
|
|
55
|
+
*
|
|
56
|
+
* Returns the tag descriptors; the caller (Default.astro) renders them
|
|
57
|
+
* into <link> elements.
|
|
58
|
+
*/
|
|
59
|
+
export function buildDefaultFaviconLinks(input: BuildFaviconLinksInput): FaviconLinkTag[] {
|
|
60
|
+
const base = resolveFaviconBase(input);
|
|
61
|
+
return DEFAULT_FAVICON_FILES.map(tag => ({
|
|
62
|
+
...tag,
|
|
63
|
+
href: `${base}${tag.href}`,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render a single FaviconLinkTag to an HTML <link> string.
|
|
69
|
+
*
|
|
70
|
+
* Attribute order: rel, type (if present), sizes (if present), href.
|
|
71
|
+
* Deterministic for snapshot testing.
|
|
72
|
+
*/
|
|
73
|
+
export function renderFaviconLinkTag(tag: FaviconLinkTag): string {
|
|
74
|
+
const attrs: string[] = [`rel="${tag.rel}"`];
|
|
75
|
+
if (tag.type) attrs.push(`type="${tag.type}"`);
|
|
76
|
+
if (tag.sizes) attrs.push(`sizes="${tag.sizes}"`);
|
|
77
|
+
attrs.push(`href="${tag.href}"`);
|
|
78
|
+
return `<link ${attrs.join(' ')} />`;
|
|
79
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Font stack builder — shared SSOT for `branding.fonts.*` fallback chains.
|
|
3
|
+
*
|
|
4
|
+
* Used by:
|
|
5
|
+
* - `src/layouts/Default.astro` (build-time SSG CSS variables)
|
|
6
|
+
* - `src/config/use-site-config.ts` (runtime CSS variable overrides)
|
|
7
|
+
*
|
|
8
|
+
* Design principle: NO slot dictates a font category. The consumer picks
|
|
9
|
+
* `serif`, `sans-serif`, or `monospace` for each slot independently. The
|
|
10
|
+
* defaults below preserve the Glossarist visual identity (serif title +
|
|
11
|
+
* serif heading + sans-serif body + monospace code) but every slot can
|
|
12
|
+
* be overridden to any category.
|
|
13
|
+
*
|
|
14
|
+
* Slot semantics:
|
|
15
|
+
* - title : single most-prominent display text on the page
|
|
16
|
+
* (home hero h1, concept name on detail page, group title)
|
|
17
|
+
* - heading : h2–h6 section headings
|
|
18
|
+
* - body : paragraph text, lists, table cells
|
|
19
|
+
* - mono : code blocks, inline code, kbd
|
|
20
|
+
*
|
|
21
|
+
* Backward compat:
|
|
22
|
+
* - `branding.fonts.header` is accepted as a deprecated alias for
|
|
23
|
+
* `branding.fonts.heading` (both write to `--font-heading`, which
|
|
24
|
+
* also surfaces as `--font-header` for legacy CSS).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export type FontCategory = 'serif' | 'sans-serif' | 'monospace';
|
|
28
|
+
|
|
29
|
+
export interface FontConfigLike {
|
|
30
|
+
family: string;
|
|
31
|
+
category?: FontCategory;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const FALLBACKS: Readonly<Record<FontCategory, string>> = {
|
|
35
|
+
serif: 'Georgia, serif',
|
|
36
|
+
'sans-serif': 'system-ui, sans-serif',
|
|
37
|
+
monospace: 'ui-monospace, "JetBrains Mono", Menlo, Monaco, monospace',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a CSS font-family stack from a FontConfig-like object.
|
|
42
|
+
*
|
|
43
|
+
* - If `font` is null/undefined or has no family, returns `undefined`
|
|
44
|
+
* and the caller applies its slot-specific default.
|
|
45
|
+
* - If `font.category` is omitted, falls back to `defaultCategory`
|
|
46
|
+
* so existing configs preserve prior behavior.
|
|
47
|
+
*/
|
|
48
|
+
export function fontStack(
|
|
49
|
+
font: FontConfigLike | null | undefined,
|
|
50
|
+
defaultCategory: FontCategory,
|
|
51
|
+
): string | undefined {
|
|
52
|
+
if (!font?.family) return undefined;
|
|
53
|
+
const category: FontCategory = font.category ?? defaultCategory;
|
|
54
|
+
const fallback = FALLBACKS[category];
|
|
55
|
+
return `'${font.family}', ${fallback}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Default font stacks (used when the consumer declares nothing).
|
|
59
|
+
* Preserves the Glossarist visual identity. NOT a dictate — every
|
|
60
|
+
* slot can be overridden to any category via branding.fonts.*. */
|
|
61
|
+
export const DEFAULT_FONTS = {
|
|
62
|
+
title: `'DM Serif Display', ${FALLBACKS.serif}`,
|
|
63
|
+
heading: `'DM Serif Display', ${FALLBACKS.serif}`,
|
|
64
|
+
body: `'DM Sans', ${FALLBACKS['sans-serif']}`,
|
|
65
|
+
mono: `'JetBrains Mono', ${FALLBACKS.monospace}`,
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
/** Slot → default category, used when the consumer omits `category`.
|
|
69
|
+
* These defaults preserve prior behavior and are NOT a constraint —
|
|
70
|
+
* consumers can pick any category for any slot. */
|
|
71
|
+
export const DEFAULT_CATEGORY = {
|
|
72
|
+
title: 'serif' as FontCategory,
|
|
73
|
+
heading: 'serif' as FontCategory,
|
|
74
|
+
body: 'sans-serif' as FontCategory,
|
|
75
|
+
mono: 'monospace' as FontCategory,
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
@@ -321,7 +321,7 @@ function clearSection() {
|
|
|
321
321
|
|
|
322
322
|
<!-- Header -->
|
|
323
323
|
<div v-if="manifest" class="mb-8">
|
|
324
|
-
<h1 class="font-
|
|
324
|
+
<h1 class="font-title text-3xl text-ink-800 mb-2">{{ localizedTitle }}</h1>
|
|
325
325
|
<p class="text-ink-400 leading-relaxed max-w-2xl">{{ localizedDescription }}</p>
|
|
326
326
|
<div class="flex flex-wrap gap-2 mt-4">
|
|
327
327
|
<span class="badge" :style="conceptCountBadgeStyle">{{ manifest.conceptCount.toLocaleString() }} {{ t('dataset.concepts') }}</span>
|
package/src/views/GroupView.vue
CHANGED
|
@@ -96,7 +96,7 @@ function openDataset(id: string) {
|
|
|
96
96
|
<template v-else-if="group">
|
|
97
97
|
<div class="flex items-baseline gap-3 mb-2">
|
|
98
98
|
<span class="text-2xl">{{ groupMeta.glyph }}</span>
|
|
99
|
-
<h1 class="font-
|
|
99
|
+
<h1 class="font-title text-3xl text-ink-800 dark:text-ink-50">
|
|
100
100
|
{{ group.label }}
|
|
101
101
|
</h1>
|
|
102
102
|
</div>
|
package/src/views/HomeView.vue
CHANGED
|
@@ -77,7 +77,7 @@ function goToGraph() { router.push({ name: 'graph' }); }
|
|
|
77
77
|
<span class="text-[11px] font-semibold uppercase tracking-[0.2em] text-ink-300 hidden sm:inline">{{ localizedSubtitle }}</span>
|
|
78
78
|
</template>
|
|
79
79
|
</div>
|
|
80
|
-
<h1 class="font-
|
|
80
|
+
<h1 class="font-title text-[2rem] sm:text-[2.75rem] text-ink-800 leading-[1.1] mb-4 tracking-tight">
|
|
81
81
|
{{ localizedTitle }}
|
|
82
82
|
<template v-if="localizedSubtitle"><br class="hidden sm:block" /> {{ localizedSubtitle }}</template>
|
|
83
83
|
</h1>
|
package/tailwind.config.js
CHANGED
|
@@ -13,9 +13,11 @@ export default {
|
|
|
13
13
|
theme: {
|
|
14
14
|
extend: {
|
|
15
15
|
fontFamily: {
|
|
16
|
+
title: ['var(--font-title, "DM Serif Display")', 'Georgia', 'serif'],
|
|
17
|
+
heading: ['var(--font-heading, "DM Serif Display")', 'Georgia', 'serif'],
|
|
16
18
|
serif: ['var(--font-header, "DM Serif Display")', 'Georgia', 'serif'],
|
|
17
19
|
sans: ['var(--font-body, "DM Sans")', 'system-ui', 'sans-serif'],
|
|
18
|
-
mono: ['"JetBrains Mono"', 'monospace'],
|
|
20
|
+
mono: ['var(--font-mono, "JetBrains Mono")', 'ui-monospace', 'monospace'],
|
|
19
21
|
},
|
|
20
22
|
colors: {
|
|
21
23
|
ink: {
|