@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.
@@ -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-serif text-3xl text-ink-800 mb-2">{{ localizedTitle }}</h1>
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>
@@ -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-serif text-3xl text-ink-800 dark:text-ink-50">
99
+ <h1 class="font-title text-3xl text-ink-800 dark:text-ink-50">
100
100
  {{ group.label }}
101
101
  </h1>
102
102
  </div>
@@ -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-serif text-[2rem] sm:text-[2.75rem] text-ink-800 leading-[1.1] mb-4 tracking-tight">
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>
@@ -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: {