@escape-game-over/atlas 0.1.47 → 0.1.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
@@ -13,14 +13,19 @@
13
13
  * A site with a lead form passes `rememberCampaign`, so the `utm_*` tags a
14
14
  * visit landed with survive to the page the form is on; see `attribution.ts`.
15
15
  *
16
+ * `fonts={site.fonts}` writes the `@font-face` rules and preloads for the
17
+ * faces `siteFonts(site)` registered in the Astro config.
18
+ *
16
19
  * ```astro
17
- * <Document meta={meta} html={{ class: "scroll-smooth" }} body={{ class: "…" }}>
20
+ * <Document meta={meta} fonts={site.fonts} html={{ class: "scroll-smooth" }} body={{ class: "…" }}>
18
21
  * <Fragment slot="head"><link rel="preload" … /></Fragment>
19
22
  * …page…
20
23
  * </Document>
21
24
  * ```
22
25
  */
26
+ import { Font } from "astro:assets";
23
27
  import type { HTMLAttributes } from "astro/types";
28
+ import type { Fonts } from "../fonts.ts";
24
29
  import type { PageMeta } from "../site/index.ts";
25
30
  import MetaTags from "./MetaTags.astro";
26
31
  import RememberCampaign from "./RememberCampaign.astro";
@@ -28,6 +33,7 @@ import ScrollRestore from "./ScrollRestore.astro";
28
33
 
29
34
  interface Props {
30
35
  readonly meta: PageMeta;
36
+ readonly fonts?: Fonts;
31
37
  /** `lang` and `dir` are the page's locale's, from `meta`. */
32
38
  readonly html?: Omit<HTMLAttributes<"html">, "lang" | "dir">;
33
39
  readonly body?: HTMLAttributes<"body">;
@@ -39,6 +45,7 @@ interface Props {
39
45
 
40
46
  const {
41
47
  meta,
48
+ fonts = {},
42
49
  html,
43
50
  body,
44
51
  restoreScroll = true,
@@ -55,6 +62,9 @@ const {
55
62
  <head>
56
63
  <slot name="head-start" />
57
64
  <MetaTags tags={meta.tags} />
65
+ {Object.entries(fonts).map(([key, font]) => (
66
+ <Font cssVariable={`--${key}`} preload={font.preload === true} />
67
+ ))}
58
68
  <slot name="head" />
59
69
  </head>
60
70
  <body {...body}>
@@ -0,0 +1,21 @@
1
+ import type { AstroUserConfig } from "astro";
2
+ import { fontProviders } from "astro/config";
3
+ import type { Fonts } from "../fonts.ts";
4
+
5
+ /**
6
+ * `site.fonts` as Astro's `fonts` config: `fonts: siteFonts(site)`.
7
+ *
8
+ * `display` defaults to `fallback`, not Astro's `swap`: `swap` paints the
9
+ * fallback for a frame even when the font is cached, so text jumps on reload.
10
+ */
11
+ export function siteFonts(site: {
12
+ readonly fonts: Fonts;
13
+ }): NonNullable<AstroUserConfig["fonts"]> {
14
+ return Object.entries(site.fonts).map(([key, { preload, ...font }]) => ({
15
+ display: "fallback",
16
+ styles: ["normal"],
17
+ ...font,
18
+ provider: fontProviders.google(),
19
+ cssVariable: `--${key}`,
20
+ }));
21
+ }
@@ -26,6 +26,7 @@
26
26
  * `…/astro/document` from a layout, `…/client` from a script.
27
27
  */
28
28
 
29
+ export { siteFonts } from "./fonts.ts";
29
30
  export {
30
31
  type PublicFilesOptions,
31
32
  publicFiles,
@@ -212,6 +212,7 @@ export function siteRoutes(options: SiteRoutesOptions): AstroIntegration {
212
212
  "astro:config:setup": ({
213
213
  config: current,
214
214
  updateConfig,
215
+ addWatchFile,
215
216
  logger,
216
217
  }) => {
217
218
  /**
@@ -290,6 +291,16 @@ export function siteRoutes(options: SiteRoutesOptions): AstroIntegration {
290
291
  `updated config: ${JSON.stringify(config, null, 2)}`
291
292
  );
292
293
  reportOrphans();
294
+
295
+ // `atlas use` swaps the project by relinking `config/project`,
296
+ // and nothing Vite watches changes when it does: the files
297
+ // behind the link are new, but every module already loaded
298
+ // through it stays cached, so a running `astro dev` keeps
299
+ // serving the old project — its messages, its routes, a map
300
+ // from one country with the pins of another. Watching the link
301
+ // itself turns the relink into a restart. A project without
302
+ // overlays has no such link, and the watch is simply idle.
303
+ addWatchFile(new URL("config/project", current.root));
293
304
  },
294
305
 
295
306
  /**
package/src/config.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Fonts } from "./fonts.ts";
1
2
  import type { LlmsConfig } from "./llms.ts";
2
3
  import type { RobotsConfig } from "./robots.ts";
3
4
  import type { SitemapConfig } from "./sitemap.ts";
@@ -97,6 +98,8 @@ export interface SiteConfigShape {
97
98
  * reading this file is not enough to know how one deployment's URLs look.
98
99
  */
99
100
  readonly defaultRouting: RoutingConfig<string>;
101
+ /** A project's `overrideFonts` adds to these or replaces one by key. */
102
+ readonly defaultFonts?: Fonts;
100
103
  readonly sitemap?: SitemapConfig;
101
104
  readonly robots?: RobotsConfig;
102
105
  /** `false` publishes no `llms.txt`, and no page links to one. */
package/src/fonts.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { AstroUserConfig } from "astro";
2
+
3
+ type FontFamily = NonNullable<AstroUserConfig["fonts"]>[number];
4
+
5
+ /**
6
+ * One typeface, self-hosted from Google Fonts. Astro's own family options, less
7
+ * the two atlas fills in: the provider, and the CSS variable (the key).
8
+ */
9
+ export type Font = Omit<FontFamily, "provider" | "cssVariable"> & {
10
+ /** Preloads its files on every page. */
11
+ readonly preload?: boolean;
12
+ };
13
+
14
+ /**
15
+ * Keyed by the CSS variable the face is exposed as, without the dashes:
16
+ * `raleway` is `--raleway`.
17
+ */
18
+ export type Fonts = Readonly<Record<string, Font>>;
19
+
20
+ const CSS_IDENT = /^[a-z][a-z0-9-]*$/;
21
+
22
+ /** A project's fonts: new keys are added, a key the site has replaces it. */
23
+ export function mergeFonts(
24
+ base: Fonts | undefined,
25
+ overrides: Fonts | undefined
26
+ ): Fonts {
27
+ const merged = { ...base, ...overrides };
28
+ for (const key of Object.keys(merged)) {
29
+ if (!CSS_IDENT.test(key)) {
30
+ throw new Error(
31
+ `Font key "${key}" must be lowercase kebab-case: it becomes the CSS variable --${key}.`
32
+ );
33
+ }
34
+ }
35
+ return merged;
36
+ }
package/src/index.ts CHANGED
@@ -85,6 +85,7 @@ export {
85
85
  } from "./countries.ts";
86
86
  export type { GeneratedFile } from "./file.ts";
87
87
  export type { PublicFilePath, PublicFileRegistry } from "./files.ts";
88
+ export type { Font, Fonts } from "./fonts.ts";
88
89
  export {
89
90
  assertHours,
90
91
  type DayHours,
package/src/project.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  RoutingConfig,
7
7
  SiteConfigShape,
8
8
  } from "./config.ts";
9
+ import type { Fonts } from "./fonts.ts";
9
10
  import type {
10
11
  BaseCatalog,
11
12
  OverrideCatalog,
@@ -108,6 +109,8 @@ export interface ProjectInput<L extends string, Routes> {
108
109
  readonly overrideLocaleMeta?: Readonly<
109
110
  Partial<Record<L, LocaleMetaOverride>>
110
111
  >;
112
+ /** Adds to the site's `defaultFonts`, or replaces one by key. */
113
+ readonly overrideFonts?: Fonts;
111
114
  /**
112
115
  * Copy overrides: any subset of keys, any subset of locales.
113
116
  *
package/src/site/api.ts CHANGED
@@ -2,6 +2,7 @@ import type { ResolvedLocaleMeta } from "../config.ts";
2
2
  import type { PlainTextFor, RichTextFor } from "../content/index.ts";
3
3
  import type { GeneratedFile } from "../file.ts";
4
4
  import type { PublicFilePath } from "../files.ts";
5
+ import type { Fonts } from "../fonts.ts";
5
6
  import type { TranslateFor } from "../i18n/translate.ts";
6
7
  import type { LlmsSection } from "../llms.ts";
7
8
  import type { ThemeColor } from "../meta/index.ts";
@@ -151,6 +152,8 @@ export interface Site<
151
152
  */
152
153
  readonly themeColor: ThemeColor;
153
154
  readonly localeMeta: Readonly<Record<L, ResolvedLocaleMeta>>;
155
+ /** The site's `defaultFonts` with this project's `overrideFonts` applied. */
156
+ readonly fonts: Fonts;
154
157
  /**
155
158
  * Every route this project builds, in the order the base registry declares
156
159
  * them. Derived — a project never lists these. Build menus by filtering it.
@@ -11,6 +11,7 @@ import {
11
11
  } from "../content/index.ts";
12
12
  import type { GeneratedFile } from "../file.ts";
13
13
  import type { PublicFilePath } from "../files.ts";
14
+ import { mergeFonts } from "../fonts.ts";
14
15
  import { type BaseCatalog, mergeCatalog } from "../i18n/define.ts";
15
16
  import { createTranslateFactory } from "../i18n/translate.ts";
16
17
  import type { ImageAsset } from "../image.ts";
@@ -158,6 +159,7 @@ export function createSite<
158
159
  project.overrideLocaleMeta,
159
160
  locales
160
161
  );
162
+ const fonts = mergeFonts(config_.defaultFonts, project.overrideFonts);
161
163
  const pathContext: PathContext<L> = {
162
164
  defaultLocale,
163
165
  prefixDefaultLocale,
@@ -797,6 +799,7 @@ export function createSite<
797
799
  siteName: project.siteName,
798
800
  themeColor: project.themeColor,
799
801
  localeMeta,
802
+ fonts,
800
803
  routes: enabledRouteIds,
801
804
 
802
805
  translate: (locale) => translateFactory(catalog, locale),