@justai/cuts 0.150.0 → 0.151.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/Base.astro +53 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.150.0",
3
+ "version": "0.151.0",
4
4
  "description": "A persona's named parts — the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
package/src/Base.astro CHANGED
@@ -100,6 +100,34 @@ interface Props {
100
100
  * where it wanted none — rather than an app persona bleeding to the edges.
101
101
  */
102
102
  kind?: "app" | "game" | "art" | "now" | "expert" | "knower" | "platform";
103
+ /**
104
+ * queries: a persona with pages of its own · canonical points at the home · every page has the
105
+ * persona's og image · per-page title · per-page share card · hreflang for a sub-page ·
106
+ * a QR opens a page that previews as the home
107
+ *
108
+ * A PAGE OF ITS OWN — for a persona whose kernel is many named things, each at its own address
109
+ * (`card.cityinmotion.nl/where-waters-meet`). Leave it out and this layout writes the persona's
110
+ * HOME, which is right for every persona that has one page: the persona's title, its `/og.jpg`, and
111
+ * a canonical at the locale's root.
112
+ *
113
+ * Written for a page that is NOT the home, those three are false in the worst way: nothing is red,
114
+ * the page renders, and every one of its tags says "I am the home". A search engine then folds all
115
+ * of them into one result (a canonical is an instruction, not a hint it ignores), and a link to one
116
+ * of them pasted into a chat previews as the persona's generic card instead of the thing linked —
117
+ * which, for a printed QR code, is the one impression there is. Birth 10 found it before the first
118
+ * page shipped.
119
+ *
120
+ * `path(locale)` is the page's address in each locale, so the alternates name THIS page in every
121
+ * language rather than the other languages' homes. `image` is absolute or site-relative; its size is
122
+ * required because a preview that has to fetch the image to learn its shape renders late or not at
123
+ * all.
124
+ */
125
+ page?: {
126
+ path: (locale: string) => string;
127
+ title: string;
128
+ description: string;
129
+ image?: { url: string; width: number; height: number; type?: string; alt?: string };
130
+ };
103
131
  }
104
132
 
105
133
  const {
@@ -108,6 +136,7 @@ const {
108
136
  themeLight,
109
137
  gaId = "G-13L3BP6XCW",
110
138
  kind = "app",
139
+ page,
111
140
  } = Astro.props;
112
141
 
113
142
  // ── ONE HOME FOR THE ORIGIN ───────────────────────────────────────────────────────────────────
@@ -157,6 +186,16 @@ const light = themeLight ?? (placeKind ? GAME_GROUND : "#f2f2f7");
157
186
  const d = dirOf(lang);
158
187
  const consent = consentCopy(lang);
159
188
  const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
189
+
190
+ // The head speaks for the page when the page has its own address, and for the persona otherwise —
191
+ // see `page` above. One set of values, so the canonical, og:url and the alternates cannot disagree.
192
+ const hrefOf = (l: string) => `${site}${page ? page.path(l) : localePath(l)}`;
193
+ const canonical = hrefOf(lang);
194
+ const headTitle = page?.title ?? t.metaTitle;
195
+ const headDescription = page?.description ?? t.metaDescription;
196
+ const ogImage = page?.image
197
+ ? { ...page.image, url: new URL(page.image.url, Astro.site).href }
198
+ : { url: new URL("/og.jpg", Astro.site).href, width: 1200, height: 630, type: "image/jpeg", alt: undefined };
160
199
  ---
161
200
 
162
201
  <!doctype html>
@@ -248,26 +287,27 @@ const localePath = (l: string) => (l === defaultLocale ? "/" : `/${l}/`);
248
287
  } catch (e) {}
249
288
  })();
250
289
  </script>
251
- <title>{t.metaTitle}</title>
252
- <meta name="description" content={t.metaDescription} />
253
- <meta property="og:title" content={t.metaTitle} />
254
- <meta property="og:description" content={t.metaDescription} />
290
+ <title>{headTitle}</title>
291
+ <meta name="description" content={headDescription} />
292
+ <meta property="og:title" content={headTitle} />
293
+ <meta property="og:description" content={headDescription} />
255
294
  <meta property="og:type" content="website" />
256
- <meta property="og:image" content={new URL("/og.jpg", Astro.site)} />
257
- <meta property="og:image:type" content="image/jpeg" />
258
- <meta property="og:image:width" content="1200" />
259
- <meta property="og:image:height" content="630" />
260
- <meta property="og:url" content={new URL(Astro.url.pathname, Astro.site)} />
295
+ <meta property="og:image" content={ogImage.url} />
296
+ {ogImage.type && <meta property="og:image:type" content={ogImage.type} />}
297
+ <meta property="og:image:width" content={String(ogImage.width)} />
298
+ <meta property="og:image:height" content={String(ogImage.height)} />
299
+ {ogImage.alt && <meta property="og:image:alt" content={ogImage.alt} />}
300
+ <meta property="og:url" content={page ? canonical : new URL(Astro.url.pathname, Astro.site).href} />
261
301
  <meta property="og:site_name" content={new URL(site).host} />
262
302
  <meta name="twitter:card" content="summary_large_image" />
263
- <meta name="twitter:image" content={new URL("/og.jpg", Astro.site)} />
303
+ <meta name="twitter:image" content={ogImage.url} />
264
304
  <meta name="theme-color" content={dark} media="(prefers-color-scheme: dark)" />
265
305
  <meta name="theme-color" content={light} media="(prefers-color-scheme: light)" />
266
- <link rel="canonical" href={`${site}${localePath(lang)}`} />
306
+ <link rel="canonical" href={canonical} />
267
307
  {locales.map((l) => (
268
- <link rel="alternate" hreflang={l} href={`${site}${localePath(l)}`} />
308
+ <link rel="alternate" hreflang={l} href={hrefOf(l)} />
269
309
  ))}
270
- <link rel="alternate" hreflang="x-default" href={`${site}/`} />
310
+ <link rel="alternate" hreflang="x-default" href={page ? hrefOf(defaultLocale) : `${site}/`} />
271
311
  <link rel="icon" href="/favicon.ico" sizes="32x32" />
272
312
  <link
273
313
  rel="icon"