create-ailk 0.13.0 → 0.15.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 (188) hide show
  1. package/README.md +12 -4
  2. package/component-catalog.json +250 -36
  3. package/component-catalog.md +10 -6
  4. package/dist/add-module.d.ts +7 -0
  5. package/dist/add-module.js +34 -2
  6. package/dist/add-route.js +18 -2
  7. package/dist/apply-design.d.ts +32 -2
  8. package/dist/apply-design.js +155 -22
  9. package/dist/cli.js +24 -3
  10. package/dist/derive-architecture.d.ts +15 -2
  11. package/dist/derive-architecture.js +34 -6
  12. package/dist/design-compile.d.ts +70 -2
  13. package/dist/design-compile.js +97 -1
  14. package/dist/design-home.d.ts +74 -0
  15. package/dist/design-home.js +256 -0
  16. package/dist/design-package.d.ts +39 -2
  17. package/dist/design-package.js +224 -9
  18. package/dist/design-presets.d.ts +17 -1
  19. package/dist/design-presets.js +17 -5
  20. package/dist/design-scaffold.d.ts +6 -25
  21. package/dist/design-scaffold.js +26 -74
  22. package/dist/index.d.ts +2 -1
  23. package/dist/index.js +1 -1
  24. package/dist/lib/chrome-readers.d.ts +21 -0
  25. package/dist/lib/chrome-readers.js +100 -0
  26. package/dist/lib/design-checks.d.ts +12 -1
  27. package/dist/lib/design-checks.js +38 -2
  28. package/dist/lib/extract-module-bundle.d.ts +11 -0
  29. package/dist/lib/extract-module-bundle.js +36 -6
  30. package/dist/lib/license-key-files.d.ts +6 -1
  31. package/dist/lib/license-key-files.js +9 -4
  32. package/dist/lib/module-license-gate.d.ts +9 -0
  33. package/dist/lib/module-license-gate.js +18 -0
  34. package/dist/module-architecture.d.ts +9 -0
  35. package/dist/module-architecture.js +6 -0
  36. package/dist/parse-args.d.ts +22 -0
  37. package/dist/parse-args.js +73 -0
  38. package/dist/programmatic.d.ts +20 -0
  39. package/dist/programmatic.js +180 -35
  40. package/dist/site-chrome.d.ts +42 -0
  41. package/dist/site-chrome.js +140 -2
  42. package/dist/site-fonts.d.ts +21 -0
  43. package/dist/site-fonts.js +54 -0
  44. package/dist/site-manifest.d.ts +12 -5
  45. package/dist/site-manifest.js +25 -11
  46. package/dist/site-roster.d.ts +16 -0
  47. package/dist/site-roster.js +81 -0
  48. package/dist/starter-grid.js +15 -0
  49. package/dist/surfaces.js +12 -0
  50. package/dist/sync-routes.js +11 -2
  51. package/dist/team-profiles.d.ts +29 -0
  52. package/dist/team-profiles.js +43 -0
  53. package/dist/topology.d.ts +131 -0
  54. package/dist/topology.js +533 -0
  55. package/package.json +3 -3
  56. package/release-scope.json +1 -1
  57. package/site-templates/_base/content/en/TeamList/team.mdx +68 -0
  58. package/site-templates/_base/content/en/TeamMember/first-member.mdx +22 -0
  59. package/site-templates/_base/content/en/TeamMember/second-member.mdx +22 -0
  60. package/site-templates/_base/content/en/TeamMember/third-member.mdx +22 -0
  61. package/site-templates/designs/minimalism/DESIGN.md +166 -36
  62. package/site-templates/designs/minimalism/home/local-business-service.mdx +119 -0
  63. package/site-templates/designs/minimalism/home/organization-product.mdx +132 -0
  64. package/site-templates/designs/minimalism/home/organization-service.mdx +131 -0
  65. package/site-templates/designs/minimalism/home/person-service.mdx +101 -0
  66. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +34 -1
  67. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +20 -0
  68. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +34 -1
  69. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +22 -1
  70. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  71. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +34 -1
  72. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -7
  73. package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
  74. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +34 -1
  75. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -7
  76. package/site-templates/shapes/organization-service/ailk-template.json +1 -1
  77. package/templates/README.md +2 -0
  78. package/templates/apps/api/CLAUDE.md +6 -1
  79. package/templates/apps/api/package.json +1 -0
  80. package/templates/apps/api/src/__tests__/factories/agent-caller.ts +5 -1
  81. package/templates/apps/api/src/lib/__mocks__/prisma.ts +10 -0
  82. package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +114 -1
  83. package/templates/apps/api/src/lib/__tests__/site.test.ts +54 -0
  84. package/templates/apps/api/src/lib/agent-store.ts +95 -3
  85. package/templates/apps/api/src/lib/site.ts +37 -0
  86. package/templates/apps/api/src/lib/spoke-store.ts +18 -2
  87. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +4 -0
  88. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +22 -0
  89. package/templates/apps/api/src/middleware/auth.ts +3 -0
  90. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +23 -0
  91. package/templates/apps/api/src/openapi/spec.ts +38 -2
  92. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +50 -2
  93. package/templates/apps/api/src/routes/checkout/sessions.ts +7 -0
  94. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +275 -0
  95. package/templates/apps/api/src/routes/leads/index.ts +69 -6
  96. package/templates/apps/api/src/routes/webhooks/README.md +39 -0
  97. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +366 -0
  98. package/templates/apps/api/src/routes/webhooks/stripe.ts +61 -1
  99. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +199 -0
  100. package/templates/apps/api/src/services/default-tenant.ts +41 -7
  101. package/templates/apps/api/src/services/lead-routing.ts +148 -29
  102. package/templates/apps/api/src/services/public-site-resolver.ts +48 -0
  103. package/templates/apps/mcp/__tests__/index.test.ts +5 -5
  104. package/templates/apps/mcp/package.json +1 -1
  105. package/templates/apps/mcp/src/index.ts +6 -4
  106. package/templates/apps/mcp/src/lib/content-adapter.ts +2 -2
  107. package/templates/apps/mcp/src/lib/topology.generated.ts +24 -0
  108. package/templates/apps/web/CLAUDE.md +12 -0
  109. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +10 -0
  110. package/templates/apps/web/app/[locale]/(authed)/account/settings/__tests__/theme-modes.test.ts +42 -0
  111. package/templates/apps/web/app/[locale]/(authed)/account/settings/theme-modes.ts +8 -0
  112. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +3 -0
  113. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +3 -0
  114. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +2 -2
  115. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  116. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +14 -1
  117. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +66 -14
  118. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +163 -0
  119. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +68 -0
  120. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +4 -4
  121. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +15 -3
  122. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +17 -6
  123. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +14 -1
  124. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +2 -2
  125. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +4 -6
  126. package/templates/apps/web/app/[locale]/layout.tsx +212 -57
  127. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +3 -0
  128. package/templates/apps/web/app/[locale]/page.tsx +37 -0
  129. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  130. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +4 -6
  131. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +5 -2
  132. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +15 -2
  133. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +43 -6
  134. package/templates/apps/web/app/[locale]/services/page.tsx +14 -2
  135. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +2 -2
  136. package/templates/apps/web/app/[locale]/team/[slug]/page.tsx +45 -0
  137. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +155 -0
  138. package/templates/apps/web/app/[locale]/team/page.tsx +49 -0
  139. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +2 -2
  140. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +14 -1
  141. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +3 -0
  142. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
  143. package/templates/apps/web/app/not-found.tsx +47 -0
  144. package/templates/apps/web/app/openapi.json/__tests__/route.test.ts +89 -1
  145. package/templates/apps/web/app/openapi.json/route.ts +69 -1
  146. package/templates/apps/web/instrumentation.ts +7 -4
  147. package/templates/apps/web/lib/__tests__/closing-footer-path.test.ts +96 -0
  148. package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
  149. package/templates/apps/web/lib/__tests__/relay.test.ts +290 -0
  150. package/templates/apps/web/lib/__tests__/site-brand.test.ts +52 -0
  151. package/templates/apps/web/lib/__tests__/site-theme-head.test.ts +70 -0
  152. package/templates/apps/web/lib/__tests__/site-theme.test.ts +64 -1
  153. package/templates/apps/web/lib/closing-footer-path.ts +50 -0
  154. package/templates/apps/web/lib/profile-back-link.ts +51 -0
  155. package/templates/apps/web/lib/relay.ts +206 -2
  156. package/templates/apps/web/lib/site-brand.tsx +22 -1
  157. package/templates/apps/web/lib/site-chrome.ts +15 -0
  158. package/templates/apps/web/lib/site-fonts.ts +15 -0
  159. package/templates/apps/web/lib/site-logo.tsx +39 -1
  160. package/templates/apps/web/lib/site-theme-head.ts +33 -0
  161. package/templates/apps/web/lib/site-theme.ts +13 -1
  162. package/templates/apps/web/lib/topology.generated.ts +23 -0
  163. package/templates/apps/web/messages/en.json +7 -1
  164. package/templates/apps/web/package.json +1 -1
  165. package/templates/database/CHANGELOG.md +32 -0
  166. package/templates/database/__tests__/agents.db.test.ts +65 -0
  167. package/templates/database/__tests__/lead-idempotency-key.db.test.ts +92 -0
  168. package/templates/database/auth/schema.prisma +23 -0
  169. package/templates/database/inbox/schema.prisma +13 -0
  170. package/templates/database/jest.config.cjs +7 -11
  171. package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +35 -0
  172. package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +20 -0
  173. package/templates/database/migrations/20261009120000_agent_grants/migration.sql +33 -0
  174. package/templates/database/package.json +1 -1
  175. package/versions.json +35 -34
  176. package/site-templates/designs/minimalism/sample/README.md +0 -48
  177. package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +0 -244
  178. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-1.svg +0 -7
  179. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-2.svg +0 -7
  180. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-3.svg +0 -7
  181. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-4.svg +0 -7
  182. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-5.svg +0 -7
  183. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-6.svg +0 -7
  184. package/site-templates/designs/minimalism/sample/public/hero-backdrop.svg +0 -17
  185. package/site-templates/designs/minimalism/sample/public/logo-placeholder-1.svg +0 -6
  186. package/site-templates/designs/minimalism/sample/public/logo-placeholder-2.svg +0 -6
  187. package/site-templates/designs/minimalism/sample/public/logo-placeholder-3.svg +0 -6
  188. package/site-templates/designs/minimalism/sample/public/wordmark-placeholder.svg +0 -28
@@ -7,6 +7,7 @@
7
7
  // Auth route group — the marketing chrome is dropped on the five (auth) paths (#5963)
8
8
  // Account area — the marketing chrome AND the consent Banner are dropped on /account paths (#7430)
9
9
  // Site-declared theme — `_site.mdx` `theme:` block → token overlay → CSS vars
10
+ // Design web faces — generated `lib/site-fonts.ts` → beneath `brand.fonts` (#7720)
10
11
  //
11
12
  // Sits under app/[locale]/ and sets the <html lang> attribute to the
12
13
  // active locale. next-intl's NextIntlClientProvider is wired here so
@@ -26,7 +27,6 @@ import "../globals.css";
26
27
 
27
28
  import { Telemetry, VerificationMeta } from "@working-theory/analytics";
28
29
  import { MarketingShell } from "@working-theory/templates/shells";
29
- import { themeToCssVarsBothModes } from "@working-theory/theme";
30
30
  import { Link, isRtl, localeHref } from "@working-theory/ui";
31
31
  import { BRAND_PATHS, LocaleSwitcherNav } from "@working-theory/ui/blocks";
32
32
  import {
@@ -47,18 +47,19 @@ import { NextIntlClientProvider } from "next-intl";
47
47
  import { getMessages, getTranslations } from "next-intl/server";
48
48
  import type { ReactElement, ReactNode } from "react";
49
49
 
50
- import { auth } from "../../auth";
51
50
  import { GoogleAnalytics } from "../../components/google-analytics";
52
51
  import {
53
52
  SignedInNav,
54
53
  type SignedInNavProps,
55
54
  } from "../../components/signed-in-nav";
56
55
  import { isAccountPath, isAuthPath } from "../../lib/auth-routes";
56
+ import { isClosingFooterPath } from "../../lib/closing-footer-path";
57
57
  import { assertEnabledLocale } from "../../lib/locale-guard";
58
58
  import { footerColumns, navItems } from "../../lib/nav-config";
59
59
  import { getOgImageUrl } from "../../lib/og";
60
- import { SITE_NAME } from "../../lib/site-brand";
61
- import { siteChrome } from "../../lib/site-chrome";
60
+ import { backLinkHref } from "../../lib/profile-back-link";
61
+ import { COPYRIGHT_HOLDER, SITE_NAME } from "../../lib/site-brand";
62
+ import { siteChrome, type SiteChrome } from "../../lib/site-chrome";
62
63
  import { siteLogo } from "../../lib/site-logo";
63
64
  // Registers the `_site.mdx` identity with page-renderer at module load, so
64
65
  // every page rendered under this layout carries the sitewide JSON-LD (#6597).
@@ -67,9 +68,25 @@ import "../../lib/site-identity";
67
68
  // live listicle's landing page gets its shelves and each entry its page
68
69
  // (#7521). Inert without AILK_LISTING_PUBLIC_KEY or the paid package.
69
70
  import "../../lib/listings";
70
- import { loadSiteBrandTokens, loadSiteShapeCss } from "../../lib/site-theme";
71
+ import { siteThemeHead } from "../../lib/site-theme-head";
72
+ import { SIGN_IN_ENABLED, readSession } from "../../lib/topology.generated";
71
73
  import { resolveBaseUrl } from "../../src/lib/routes";
72
74
 
75
+ import { readSiteContactLines } from "./contact/site-contact-lines";
76
+
77
+ /**
78
+ * The generated `siteChrome`, read through a widened type (#7740 D8): a
79
+ * scaffold generated before `deckPresentation` existed has no such member,
80
+ * so this compiles against both shapes of the generated file, as the home
81
+ * page reads `deckGrounds`. `colorMode` and `chromeStyle` (#7742 D1) likewise.
82
+ */
83
+ const chrome: SiteChrome & {
84
+ deckPresentation?: true;
85
+ colorMode?: "light";
86
+ chromeStyle?: "minimal";
87
+ closingFooterPaths?: readonly string[];
88
+ } = siteChrome;
89
+
73
90
  // ── Constants ────────────────────────────────────────────────────────────────
74
91
 
75
92
  const DEFAULT_LOCALE = "en";
@@ -127,6 +144,15 @@ async function resolveThemeMode(): Promise<ThemeMode> {
127
144
  }
128
145
  }
129
146
 
147
+ /**
148
+ * The deck home's first slide (#7782): the element the consent bar waits on.
149
+ * The same section-slide selector `DeckSlideAdvance` walks (its `SLIDES`, minus
150
+ * the closing footer slide), so the first match is slide 1. The story that pins
151
+ * the geometry (`Shells/MarketingShell` → `Deck Consent Bar`) carries a copy.
152
+ */
153
+ const DECK_FIRST_SLIDE_SELECTOR =
154
+ '.ailk-snap-deck .ailk-section-renderer > :not(.ailk-section-separator):not([data-section="separator"])';
155
+
130
156
  /**
131
157
  * Whether this visitor has already accepted or declined cookies (#5557).
132
158
  *
@@ -226,11 +252,19 @@ export default async function LocaleLayout({
226
252
  // message load or session read.
227
253
  assertEnabledLocale(locale);
228
254
  const messages = await getMessages();
229
- const themeMode = await resolveThemeMode();
255
+ // #7742 D2 — `colorMode: "light"` pins the mode: a saved dark cookie is
256
+ // ignored, no flash script runs and no theme switch renders.
257
+ const pinLight = chrome.colorMode === "light";
258
+ const themeMode: ThemeMode = pinLight ? "light" : await resolveThemeMode();
259
+ // #7742 D3/D5 — the minimal chrome: the closing slide, the hairline consent
260
+ // bar and the cue offset.
261
+ const isMinimalChrome = chrome.chromeStyle === "minimal";
230
262
  const consentDecided = await hasConsentChoice();
231
263
  // Server-side session read (#4984) — the same `auth.api.getSession` idiom
232
- // the `(authed)` segment guard uses. Null when signed out or auth is unset.
233
- const session = await auth.api.getSession({ headers: await headers() });
264
+ // the `(authed)` segment guard uses, through the generated topology seam
265
+ // (#7426 D5): a spoke has no sign-in, so its render always answers null.
266
+ // Null when signed out or auth is unset.
267
+ const session = await readSession(await headers());
234
268
 
235
269
  // Resolve nav labels server-side so Nav + Footer components stay
236
270
  // i18n-hook-free and are testable without a NextIntlClientProvider wrapper.
@@ -325,6 +359,18 @@ export default async function LocaleLayout({
325
359
  // `kind: "flow"` so Nav hands their triggers back through
326
360
  // `renderFlowTrigger`, which `SignedInNav` owns (see NAV_CLIENT_ACTION_FLOW
327
361
  // above).
362
+ // The signed-out auth pair, shared by the header (every page but the deck
363
+ // home) and the deck home's closing footer slide (#7718 D4).
364
+ // A spoke (#7426 D11) has no sign-in, so it offers no auth pair anywhere:
365
+ // not in the header, the deck's closing footer or a bare page's footer.
366
+ const authActions: readonly Extract<NavAction, { path: string }>[] =
367
+ SIGN_IN_ENABLED
368
+ ? [
369
+ // NO_HAND_IMPLEMENT_OK: operator-directed live Free-template pass.
370
+ { labelKey: "signIn", path: "/sign-in", variant: "outline-strong" },
371
+ { labelKey: "signUp", path: "/sign-up", variant: "primary" },
372
+ ]
373
+ : [];
328
374
  const navActions: NavAction[] = session
329
375
  ? [
330
376
  { labelKey: "account", kind: "flow", flow: NAV_CLIENT_ACTION_FLOW },
@@ -335,11 +381,47 @@ export default async function LocaleLayout({
335
381
  variant: "outline-strong",
336
382
  },
337
383
  ]
338
- : [
339
- // NO_HAND_IMPLEMENT_OK: operator-directed live Free-template pass.
340
- { labelKey: "signIn", path: "/sign-in", variant: "outline-strong" },
341
- { labelKey: "signUp", path: "/sign-up", variant: "primary" },
342
- ];
384
+ : [...authActions];
385
+
386
+ // The announcement bar is HOME-ONLY (#5469 D5): its route gate matches the
387
+ // pathname literally, and under next-intl's `as-needed` prefixing the home
388
+ // path is `/` for the default locale and `/<locale>` for every other one.
389
+ const homePath = locale === DEFAULT_LOCALE ? "/" : `/${locale}`;
390
+
391
+ // Deck home (#5612 D3): `siteChrome.home === "deck"` only ever applies
392
+ // `snap="mandatory"` + `deckHero` on the locale-root route — every other
393
+ // path renders byte-identically regardless of the knob. `x-pathname` is the
394
+ // same header `generateMetadata` above reads (forwarded by middleware.ts);
395
+ // Next.js's `headers()` is request-scoped and cheap to call again here.
396
+ const pathname = (await headers()).get("x-pathname") ?? homePath;
397
+ const isDeckHome = siteChrome.home === "deck" && pathname === homePath;
398
+ // Inner pages (#7721 D4): under `innerPages: "bare"` every page but the
399
+ // home wears the bare header (back arrow + the mark) and the one-bar
400
+ // footer. Fail-safe like the deck gate: a missing `x-pathname` resolves to
401
+ // the home path, so the full bar and its auth buttons render.
402
+ const isBareInner = siteChrome.innerPages === "bare" && pathname !== homePath;
403
+
404
+ // The deck home's header is bare (#7718 D1): no actions, signed in or out.
405
+ // Its auth links move to the footer, the deck's closing slide (D4) — the
406
+ // auth pair signed out, an Account link signed in (the route where Sign
407
+ // out lives; its label is the user's name, `navLabels.items.account`).
408
+ const headerActions: NavAction[] = isDeckHome || isBareInner ? [] : navActions;
409
+ const deckFooterActions = session
410
+ ? [{ labelKey: "account", path: "/account", variant: "outline-strong" as const }]
411
+ : [...authActions];
412
+ // A bare inner page's auth (#7721 D3/D4) rides the one-bar footer's link
413
+ // group: Sign in / Sign up signed out, Account signed in (where Sign out
414
+ // lives). Labels from the existing `nav` namespace.
415
+ const accountLinks = !isBareInner
416
+ ? []
417
+ : session
418
+ ? [{ label: tNav("account"), href: localeHref(locale, "/account") }]
419
+ : SIGN_IN_ENABLED
420
+ ? [
421
+ { label: tNav("signIn"), href: localeHref(locale, "/sign-in") },
422
+ { label: tNav("signUp"), href: localeHref(locale, "/sign-up") },
423
+ ]
424
+ : [];
343
425
 
344
426
  // A flow action needs `renderFlowTrigger`, a function — and this layout is a
345
427
  // Server Component. Nav forwards that function into NavMobileMenu ("use
@@ -352,7 +434,7 @@ export default async function LocaleLayout({
352
434
  // `renderFlowTrigger` and imports `navItems` (icon components are functions
353
435
  // too) on the client side. Without one, Nav stays a Server Component and
354
436
  // nothing callable crosses.
355
- const hasFlowAction = navActions.some((action) => action.kind === "flow");
437
+ const hasFlowAction = headerActions.some((action) => action.kind === "flow");
356
438
 
357
439
  // Shared brand mark injected into both Nav and Footer chrome slots. The
358
440
  // mark comes from site-logo.tsx (the mark the /sign-in card also renders);
@@ -372,18 +454,17 @@ export default async function LocaleLayout({
372
454
  // middleware and hreflang alternates read (NEXT_PUBLIC_ENABLED_LOCALES).
373
455
  const enabledLocales = readLocales();
374
456
 
375
- // The announcement bar is HOME-ONLY (#5469 D5): its route gate matches the
376
- // pathname literally, and under next-intl's `as-needed` prefixing the home
377
- // path is `/` for the default locale and `/<locale>` for every other one.
378
- const homePath = locale === DEFAULT_LOCALE ? "/" : `/${locale}`;
379
-
380
- // Deck home (#5612 D3): `siteChrome.home === "deck"` only ever applies
381
- // `snap="mandatory"` + `deckHero` on the locale-root route — every other
382
- // path renders byte-identically regardless of the knob. `x-pathname` is the
383
- // same header `generateMetadata` above reads (forwarded by middleware.ts);
384
- // Next.js's `headers()` is request-scoped and cheap to call again here.
385
- const pathname = (await headers()).get("x-pathname") ?? homePath;
386
- const isDeckHome = siteChrome.home === "deck" && pathname === homePath;
457
+ // #7797 — an inner page the site lists in `closingFooterPaths` ends on the
458
+ // closing footer: the wordmark, the contact lines and the legal line. The
459
+ // home is never an inner page, and an absent or empty list is false for
460
+ // every page, so a site that lists none renders exactly as before.
461
+ const isClosingInner =
462
+ pathname !== homePath &&
463
+ isClosingFooterPath({
464
+ pathname,
465
+ paths: chrome.closingFooterPaths,
466
+ locales: enabledLocales,
467
+ });
387
468
 
388
469
  // The (auth) route group's chrome branch (#5963).
389
470
  //
@@ -424,7 +505,13 @@ export default async function LocaleLayout({
424
505
  // `wordmark` (Nav.tsx:344), which are passed below. Scoped to the SAME
425
506
  // `isDeckHome` gate MarketingShell's snap/deckHero use, so a non-deck
426
507
  // route's render is byte-identical to before this branch existed.
427
- ...(isDeckHome ? { sticky: true, deckMorph: true } : {}),
508
+ // #7718 D1 — and `bar: "bare"`: no rule, fill or actions, the logomark
509
+ // alone, centred.
510
+ // #7721 D4 (gap F) — `mark-only` means the mark alone: no wordmark beside
511
+ // it on any page. On the deck home `deckMorph` still governs the brand:
512
+ // Nav spreads its `full-logo` composition after `logoDisplay`'s.
513
+ ...(siteChrome.nav === "mark-only" ? { logoDisplay: "mark" as const } : {}),
514
+ ...(isDeckHome ? { sticky: true, deckMorph: true, bar: "bare" as const } : {}),
428
515
  siteName: SITE_NAME,
429
516
  frame: "contained",
430
517
  navAlignment: "right",
@@ -432,7 +519,7 @@ export default async function LocaleLayout({
432
519
  logo: brandLogo,
433
520
  wordmark: brandWordmark,
434
521
  overflowLabel: navOverflowLabel,
435
- actions: navActions,
522
+ ...(headerActions.length > 0 ? { actions: headerActions } : {}),
436
523
  };
437
524
 
438
525
  // Placeholder social handles (#5469 D5): `{{COMPANY_HANDLE}}` is a
@@ -464,7 +551,8 @@ export default async function LocaleLayout({
464
551
 
465
552
  // The site's own name (#6942) — `SITE_NAME` resolves from `_site.mdx`'s
466
553
  // `siteName:`, so a customer's footer never carries the kit vendor's name.
467
- const copyrightText = `© ${new Date().getFullYear()} ${SITE_NAME}. All rights reserved.`;
554
+ // The holder is `siteIdentity.legalName` when declared (#7796), else the name.
555
+ const copyrightText = `© ${new Date().getFullYear()} ${COPYRIGHT_HOLDER}. All rights reserved.`;
468
556
 
469
557
  // Footer legal links (#5612 D3). The centered variant folds in FAQ — the
470
558
  // roster's third always-scaffolded core route (`/faq`, alongside `/privacy`
@@ -483,40 +571,38 @@ export default async function LocaleLayout({
483
571
  : []),
484
572
  ];
485
573
 
486
- // The site's own declared colours, read from `content/_site.mdx`'s `theme:`
487
- // block and translated from that customer-facing vocabulary onto a token
488
- // overlay (`siteThemeToBrandTokens`). `undefined` when the site declares no
489
- // theme — which is the regression floor: no `brand` key is passed, the
490
- // resolver takes its untouched fast path, and the emitted CSS is
491
- // byte-for-byte what it was before this wiring existed.
492
- const siteBrandTokens = loadSiteBrandTokens();
493
-
494
- // Both theme modes, gated by [data-theme="dark"] (#1532 — sibling fix; the
495
- // single-mode call inlined an unconditional :root block that never
496
- // responded to a client-side data-theme toggle). themeMode still drives
497
- // the initial data-theme attribute below for a no-flash first paint.
498
- // Site shape settings (`theme.corners`, `theme.eyebrowCase`) appended after
499
- // the token block so they win; "" for a site that sets neither.
500
- const inlineThemeStyle =
501
- themeToCssVarsBothModes(siteBrandTokens ? { brand: siteBrandTokens } : {}) +
502
- loadSiteShapeCss();
574
+ // The site's own declared colours (`content/_site.mdx` `theme:` + `brand:`)
575
+ // as a token overlay, the design's web faces (#7720) beneath `brand.fonts`,
576
+ // both theme modes gated by [data-theme="dark"] (#1532) and the site's shape
577
+ // settings after them — one helper, shared with the minimal 404 (#7742 D6).
578
+ // A site that declares no theme gets byte-for-byte the CSS it got before
579
+ // this wiring existed; with no design faces <html> gets no class.
580
+ // themeMode still drives the initial data-theme attribute below.
581
+ const themeHead = siteThemeHead();
503
582
 
504
583
  return (
505
584
  <html
506
585
  lang={locale}
507
586
  dir={isRtl(locale) ? "rtl" : "ltr"}
508
587
  data-theme={themeMode}
588
+ className={themeHead.fontClassName}
589
+ // #7742 D5 — scopes the cue's consent-bar offset; absent otherwise.
590
+ {...(isMinimalChrome ? { "data-chrome-style": "minimal" } : {})}
509
591
  >
510
592
  <head>
511
- {/* Flash-prevention: runs synchronously before any styled element */}
512
- <script
513
- data-testid="theme-flash-prevention"
514
- dangerouslySetInnerHTML={{ __html: FLASH_PREVENTION_SCRIPT }}
515
- />
593
+ {/* Flash-prevention: runs synchronously before any styled element.
594
+ Not under `colorMode: light` (#7742 D2) — the mode is pinned, and
595
+ the script would apply the OS dark preference. */}
596
+ {pinLight ? null : (
597
+ <script
598
+ data-testid="theme-flash-prevention"
599
+ dangerouslySetInnerHTML={{ __html: FLASH_PREVENTION_SCRIPT }}
600
+ />
601
+ )}
516
602
  {/* Inline CSS-variable declarations for the active theme mode */}
517
603
  <style
518
604
  data-ailk="theme-vars"
519
- dangerouslySetInnerHTML={{ __html: inlineThemeStyle }}
605
+ dangerouslySetInnerHTML={{ __html: themeHead.css }}
520
606
  />
521
607
  {/* Favicons — single brand mark (theme-independent), PNG fallbacks, ICO legacy */}
522
608
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
@@ -628,10 +714,41 @@ export default async function LocaleLayout({
628
714
  }
629
715
  : {})}
630
716
  {...(isDeckHome
631
- ? { snap: "mandatory" as const, deckHero: true }
717
+ ? {
718
+ snap: "mandatory" as const,
719
+ deckHero: true,
720
+ // #7740 D8 — the design's deck presentation dial.
721
+ ...(chrome.deckPresentation === true ? { presentation: true } : {}),
722
+ // The deck's slide-advance controls (#7732) — named from
723
+ // the `nav` catalog, never the shell's English default.
724
+ slideAdvanceLabels: {
725
+ next: tNav("nextSlide"),
726
+ top: tNav("backToTop"),
727
+ },
728
+ }
632
729
  : {})}
633
730
  nav={
634
- hasFlowAction ? (
731
+ // #7721 D4 — a bare inner page's header: the back arrow home
732
+ // and the mark. It carries no actions, so it is always the
733
+ // server Nav (nothing callable crosses the RSC boundary).
734
+ // #7744 D7 — on a deck home a team profile's arrow returns
735
+ // to the people slide (`/#slide-people`) instead.
736
+ isBareInner ? (
737
+ <Nav
738
+ {...navProps}
739
+ navItems={[]}
740
+ bar="bare"
741
+ backLink={{
742
+ href: backLinkHref({
743
+ pathname,
744
+ homePath,
745
+ home: siteChrome.home,
746
+ locales: enabledLocales,
747
+ }),
748
+ label: tNav("home"),
749
+ }}
750
+ />
751
+ ) : hasFlowAction ? (
635
752
  <SignedInNav {...navProps} />
636
753
  ) : (
637
754
  <Nav
@@ -650,10 +767,40 @@ export default async function LocaleLayout({
650
767
  logo={brandLogo}
651
768
  wordmark={brandWordmark}
652
769
  social={[...socialLinks]}
653
- {...(siteChrome.footer === "centered"
654
- ? { layout: "centered" as const }
770
+ // #7742 D3 — under `chromeStyle: minimal` the deck home's
771
+ // closing slide is the wordmark, the contact lines and the
772
+ // legal line: no actions and no centred lockup, so Sign in /
773
+ // Sign up are on no part of the home.
774
+ // #7721 D4 — a bare inner page's footer is the one bar, its
775
+ // link group carrying the account links the header drops.
776
+ // #7797 — a listed inner page takes the same closing footer.
777
+ // Under `innerPages: bare` the one-bar footer is where the
778
+ // account links live (the header drops them), so the closing
779
+ // footer carries the deck's auth actions in their place; on
780
+ // any other inner page the header still holds them.
781
+ {...(isDeckHome && isMinimalChrome
782
+ ? { layout: "closing" as const, contactLines: readSiteContactLines() }
783
+ : isClosingInner
784
+ ? {
785
+ layout: "closing" as const,
786
+ contactLines: readSiteContactLines(),
787
+ ...(isBareInner ? { actions: deckFooterActions } : {}),
788
+ }
789
+ : isBareInner
790
+ ? { layout: "line" as const, accountLinks }
791
+ : siteChrome.footer === "centered"
792
+ ? { layout: "centered" as const }
793
+ : {})}
794
+ // #7718 D4 — the deck home's closing slide carries the
795
+ // centred brand and the auth links its bare header drops.
796
+ {...(isDeckHome && !isMinimalChrome
797
+ ? { centeredBrand: true, actions: deckFooterActions }
655
798
  : {})}
656
- showThemeToggle
799
+ // #7823 — the minimal closing slide's actions are a button,
800
+ // then a text link; Footer reads the style from this.
801
+ {...(isMinimalChrome ? { chromeStyle: "minimal" as const } : {})}
802
+ // #7742 D2 — no theme switch under `colorMode: light`.
803
+ showThemeToggle={!pinLight}
657
804
  bottomBarAlign="content"
658
805
  copyrightText={copyrightText}
659
806
  legalLinks={legalLinks}
@@ -713,6 +860,14 @@ export default async function LocaleLayout({
713
860
  secondaryAction={{ label: tConsent("rejectLabel") }}
714
861
  rejectLabel={tConsent("rejectLabel")}
715
862
  locale={locale}
863
+ // #7742 D5 — the minimal chrome's consent bar: the page ground,
864
+ // a hairline, no shadow.
865
+ {...(isMinimalChrome ? { consentAppearance: "hairline" as const } : {})}
866
+ // #7782 — on the minimal deck home the bar first shows once the
867
+ // first slide has left view; every other page shows it on load.
868
+ {...(isDeckHome && isMinimalChrome
869
+ ? { consentRevealAfter: DECK_FIRST_SLIDE_SELECTOR }
870
+ : {})}
716
871
  />
717
872
  )}
718
873
  <Telemetry />
@@ -11,6 +11,9 @@ import React from "react";
11
11
  import { render, screen } from "@testing-library/react";
12
12
 
13
13
  jest.mock("@working-theory/ui", () => ({
14
+ // #7789: renderPage's fallback page head renders inside the section Container.
15
+ Container: ({ children }: { children?: React.ReactNode }) =>
16
+ React.createElement("div", null, children),
14
17
  Heading: ({ children, level }: { children: React.ReactNode; level: number }) =>
15
18
  React.createElement(`h${level}`, null, children),
16
19
  SectionRenderer: ({
@@ -14,6 +14,19 @@ import {
14
14
  import type { Metadata } from "next";
15
15
  import type { ReactElement } from "react";
16
16
 
17
+ import { siteChrome, type SiteChrome } from "../../lib/site-chrome";
18
+ import { siteLogomarkAsset, siteWordmarkAsset } from "../../lib/site-logo";
19
+
20
+ /** The ground sequence `renderPage` cycles over a deck home's slides (#7719). */
21
+ type DeckGrounds = NonNullable<Parameters<typeof renderPage>[0]["deckGrounds"]>;
22
+
23
+ /**
24
+ * The generated `siteChrome`, read through a widened type: its interface
25
+ * carries `deckGrounds` only when the site's design sets one (#7719 D3), so
26
+ * this compiles against both shapes of the generated file.
27
+ */
28
+ const chrome: SiteChrome & { deckGrounds?: DeckGrounds; deckPresentation?: true } = siteChrome;
29
+
17
30
  interface Props {
18
31
  params: Promise<{ locale: string }>;
19
32
  /**
@@ -22,6 +35,12 @@ interface Props {
22
35
  */
23
36
  getMdxSource?: typeof defaultGetMdxSource;
24
37
  getPageSections?: typeof defaultGetPageSections;
38
+ /** The site's `layout.home` knob; defaults to the generated `siteChrome`. */
39
+ home?: SiteChrome["home"];
40
+ /** The design's `layout.deckGrounds`; defaults to the generated `siteChrome`'s. */
41
+ deckGrounds?: DeckGrounds;
42
+ /** The design's `layout.deckPresentation` (#7740); defaults to the generated `siteChrome`'s. */
43
+ deckPresentation?: boolean;
25
44
  }
26
45
 
27
46
  export async function generateMetadata({
@@ -35,6 +54,9 @@ export default async function Page({
35
54
  params,
36
55
  getMdxSource,
37
56
  getPageSections,
57
+ home = chrome.home,
58
+ deckGrounds = chrome.deckGrounds,
59
+ deckPresentation = chrome.deckPresentation,
38
60
  }: Props): Promise<ReactElement> {
39
61
  const { locale } = await params;
40
62
  return renderPage({
@@ -46,5 +68,20 @@ export default async function Page({
46
68
  emptyStateTestId: "home-description",
47
69
  ...(getMdxSource !== undefined && { getMdxSource }),
48
70
  ...(getPageSections !== undefined && { getPageSections }),
71
+ // #7733 — the deck home's header is bare on slide 1 (#5697), so its
72
+ // landing carries the brand: a centred leading hero renders as bondcap's
73
+ // landing with the site's wordmark in the headline's place; any other
74
+ // leading hero keeps its composition and takes the logomark (#7711).
75
+ // Content that authors its own lockup keeps it.
76
+ ...(home === "deck" && {
77
+ deckLanding: { wordmark: siteWordmarkAsset(), logomark: siteLogomarkAsset() },
78
+ }),
79
+ // #7719 — a deck home cycles its design's ground sequence, slide by
80
+ // slide, onto every slide that authors no surface of its own.
81
+ ...(home === "deck" && deckGrounds !== undefined && { deckGrounds }),
82
+ // #7740 — a deck home under the presentation dial: every slide with an
83
+ // alignment field centred, card grids one column on phones, and slide
84
+ // anchors clear of the page's JSON-LD @id fragments.
85
+ ...(home === "deck" && deckPresentation === true && { deckPresentation: true }),
49
86
  });
50
87
  }
@@ -68,7 +68,7 @@ describe("partners/page — snapshot regression", () => {
68
68
  });
69
69
  const { container } = render(element);
70
70
  expect(container.innerHTML).toMatchInlineSnapshot(
71
- `"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Partners","description":"Organisations partnering with AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Organization"}]}}</script><main id="main"><h1>Partners</h1><p data-testid="partners-description">Organisations partnering with AI Launch Kit.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
71
+ `"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Partners","description":"Organisations partnering with AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Organization"}]}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Partners</h1><p data-testid="partners-description" class="mt-block-tight text-lead">Organisations partnering with AI Launch Kit.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&amp;>*:first-child]:mt-0 [&amp;_h2]:mt-block-loose [&amp;_h2]:scroll-mt-24 [&amp;_h2]:text-heading-3 [&amp;_h2]:font-display [&amp;_h2]:text-(--color-fg) [&amp;_h3]:mt-block-loose [&amp;_h3]:scroll-mt-24 [&amp;_h3]:text-heading-4 [&amp;_h3]:font-display [&amp;_h3]:text-(--color-fg) [&amp;_h4]:mt-block-tight [&amp;_h4]:scroll-mt-24 [&amp;_h4]:text-heading-4 [&amp;_h4]:font-display [&amp;_h4]:text-(--color-fg) [&amp;_h5]:mt-block-tight [&amp;_h5]:scroll-mt-24 [&amp;_h5]:text-heading-4 [&amp;_h5]:font-display [&amp;_h5]:text-(--color-fg) [&amp;_h6]:mt-block-tight [&amp;_h6]:scroll-mt-24 [&amp;_h6]:text-heading-4 [&amp;_h6]:font-display [&amp;_h6]:text-(--color-fg) [&amp;_p]:mt-paragraph [&amp;_strong]:font-semibold [&amp;_strong]:text-(--color-fg) [&amp;_em]:italic [&amp;_ul]:mt-paragraph [&amp;_ul]:list-disc [&amp;_ul]:ps-6 [&amp;_ol]:mt-paragraph [&amp;_ol]:list-decimal [&amp;_ol]:ps-6 [&amp;_li]:mt-1 [&amp;_li]:ps-1 [&amp;_li]:marker:text-(--color-text-muted) [&amp;_li>ul]:mt-1 [&amp;_li>ol]:mt-1 [&amp;_blockquote]:mt-paragraph [&amp;_blockquote]:border-s-4 [&amp;_blockquote]:border-(--color-border-strong) [&amp;_blockquote]:ps-4 [&amp;_blockquote]:italic [&amp;_blockquote]:text-(--color-text-muted) [&amp;_hr]:my-block-loose [&amp;_hr]:border-(--color-border) [&amp;_a]:text-(--color-accent) [&amp;_a]:underline [&amp;_a]:underline-offset-4 [&amp;_:not(pre)>code]:rounded [&amp;_:not(pre)>code]:bg-(--color-surface-secondary) [&amp;_:not(pre)>code]:px-1.5 [&amp;_:not(pre)>code]:py-0.5 [&amp;_:not(pre)>code]:text-code [&amp;_:not(pre)>code]:font-mono [&amp;_:not(pre)>code]:text-(--color-fg) [&amp;_pre]:mt-paragraph [&amp;_pre]:overflow-auto [&amp;_pre]:rounded-md [&amp;_pre]:border-hairline [&amp;_pre]:border-(--color-border) [&amp;_pre]:bg-(--color-bg) [&amp;_pre]:p-4 [&amp;_pre]:font-mono [&amp;_pre]:text-body-sm [&amp;_img]:mt-paragraph [&amp;_img]:h-auto [&amp;_img]:max-w-full [&amp;_img]:rounded-lg [&amp;_table]:mt-paragraph [&amp;_table]:w-full [&amp;_table]:border-collapse [&amp;_table]:text-body-sm [&amp;_th]:border-b [&amp;_th]:border-(--color-border) [&amp;_th]:bg-(--color-surface-secondary) [&amp;_th]:px-3 [&amp;_th]:py-2 [&amp;_th]:text-start [&amp;_th]:font-semibold [&amp;_th]:text-(--color-fg) [&amp;_td]:border-b [&amp;_td]:border-(--color-border) [&amp;_td]:px-3 [&amp;_td]:py-2 [&amp;_kbd]:rounded [&amp;_kbd]:border-hairline [&amp;_kbd]:border-(--color-border) [&amp;_kbd]:bg-(--color-surface-secondary) [&amp;_kbd]:px-1.5 [&amp;_kbd]:py-0.5 [&amp;_kbd]:text-caption [&amp;_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-rendered">mdx-body-content</div></div></article></div></div></div></main>"`,
72
72
  );
73
73
  });
74
74
  });
@@ -93,7 +93,7 @@ describe("partners/[slug]/page — snapshot regression", () => {
93
93
  });
94
94
  const { container } = render(element);
95
95
  expect(container.innerHTML).toMatchInlineSnapshot(
96
- `"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Acme Corp","description":"Acme Corp is a strategic partner.","mainEntity":{"@context":"https://schema.org","@type":"Organization","name":"Acme Corp","description":"Acme Corp is a strategic partner."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Partners","item":"http://localhost:3000/en/partners"},{"@type":"ListItem","position":3,"name":"Acme Corp","item":"http://localhost:3000/en/partners/acme-corp"}]}]}</script><main id="main"><h1>Acme Corp</h1><p data-testid="acme-corp-description">Acme Corp is a strategic partner.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
96
+ `"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Acme Corp","description":"Acme Corp is a strategic partner.","mainEntity":{"@context":"https://schema.org","@type":"Organization","name":"Acme Corp","description":"Acme Corp is a strategic partner."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Partners","item":"http://localhost:3000/en/partners"},{"@type":"ListItem","position":3,"name":"Acme Corp","item":"http://localhost:3000/en/partners/acme-corp"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Acme Corp</h1><p data-testid="acme-corp-description" class="mt-block-tight text-lead">Acme Corp is a strategic partner.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&amp;>*:first-child]:mt-0 [&amp;_h2]:mt-block-loose [&amp;_h2]:scroll-mt-24 [&amp;_h2]:text-heading-3 [&amp;_h2]:font-display [&amp;_h2]:text-(--color-fg) [&amp;_h3]:mt-block-loose [&amp;_h3]:scroll-mt-24 [&amp;_h3]:text-heading-4 [&amp;_h3]:font-display [&amp;_h3]:text-(--color-fg) [&amp;_h4]:mt-block-tight [&amp;_h4]:scroll-mt-24 [&amp;_h4]:text-heading-4 [&amp;_h4]:font-display [&amp;_h4]:text-(--color-fg) [&amp;_h5]:mt-block-tight [&amp;_h5]:scroll-mt-24 [&amp;_h5]:text-heading-4 [&amp;_h5]:font-display [&amp;_h5]:text-(--color-fg) [&amp;_h6]:mt-block-tight [&amp;_h6]:scroll-mt-24 [&amp;_h6]:text-heading-4 [&amp;_h6]:font-display [&amp;_h6]:text-(--color-fg) [&amp;_p]:mt-paragraph [&amp;_strong]:font-semibold [&amp;_strong]:text-(--color-fg) [&amp;_em]:italic [&amp;_ul]:mt-paragraph [&amp;_ul]:list-disc [&amp;_ul]:ps-6 [&amp;_ol]:mt-paragraph [&amp;_ol]:list-decimal [&amp;_ol]:ps-6 [&amp;_li]:mt-1 [&amp;_li]:ps-1 [&amp;_li]:marker:text-(--color-text-muted) [&amp;_li>ul]:mt-1 [&amp;_li>ol]:mt-1 [&amp;_blockquote]:mt-paragraph [&amp;_blockquote]:border-s-4 [&amp;_blockquote]:border-(--color-border-strong) [&amp;_blockquote]:ps-4 [&amp;_blockquote]:italic [&amp;_blockquote]:text-(--color-text-muted) [&amp;_hr]:my-block-loose [&amp;_hr]:border-(--color-border) [&amp;_a]:text-(--color-accent) [&amp;_a]:underline [&amp;_a]:underline-offset-4 [&amp;_:not(pre)>code]:rounded [&amp;_:not(pre)>code]:bg-(--color-surface-secondary) [&amp;_:not(pre)>code]:px-1.5 [&amp;_:not(pre)>code]:py-0.5 [&amp;_:not(pre)>code]:text-code [&amp;_:not(pre)>code]:font-mono [&amp;_:not(pre)>code]:text-(--color-fg) [&amp;_pre]:mt-paragraph [&amp;_pre]:overflow-auto [&amp;_pre]:rounded-md [&amp;_pre]:border-hairline [&amp;_pre]:border-(--color-border) [&amp;_pre]:bg-(--color-bg) [&amp;_pre]:p-4 [&amp;_pre]:font-mono [&amp;_pre]:text-body-sm [&amp;_img]:mt-paragraph [&amp;_img]:h-auto [&amp;_img]:max-w-full [&amp;_img]:rounded-lg [&amp;_table]:mt-paragraph [&amp;_table]:w-full [&amp;_table]:border-collapse [&amp;_table]:text-body-sm [&amp;_th]:border-b [&amp;_th]:border-(--color-border) [&amp;_th]:bg-(--color-surface-secondary) [&amp;_th]:px-3 [&amp;_th]:py-2 [&amp;_th]:text-start [&amp;_th]:font-semibold [&amp;_th]:text-(--color-fg) [&amp;_td]:border-b [&amp;_td]:border-(--color-border) [&amp;_td]:px-3 [&amp;_td]:py-2 [&amp;_kbd]:rounded [&amp;_kbd]:border-hairline [&amp;_kbd]:border-(--color-border) [&amp;_kbd]:bg-(--color-surface-secondary) [&amp;_kbd]:px-1.5 [&amp;_kbd]:py-0.5 [&amp;_kbd]:text-caption [&amp;_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-rendered">mdx-body-content</div></div></article></div></div></div></main>"`,
97
97
  );
98
98
  });
99
99
 
@@ -87,9 +87,8 @@ describe("ProductsPage — snapshot regression", () => {
87
87
  });
88
88
  const { container } = render(element);
89
89
  expect(container.innerHTML).toMatchInlineSnapshot(`
90
- "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Products","description":"All products.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"SoftwareApplication"}]}}</script><main id="main"><h1>Products</h1><p data-testid="products-description">All products.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source="# Products
91
-
92
- Body."></div></article></div></div></main>"
90
+ "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Products","description":"All products.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"SoftwareApplication"}]}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Products</h1><p data-testid="products-description" class="mt-block-tight text-lead">All products.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&amp;>*:first-child]:mt-0 [&amp;_h2]:mt-block-loose [&amp;_h2]:scroll-mt-24 [&amp;_h2]:text-heading-3 [&amp;_h2]:font-display [&amp;_h2]:text-(--color-fg) [&amp;_h3]:mt-block-loose [&amp;_h3]:scroll-mt-24 [&amp;_h3]:text-heading-4 [&amp;_h3]:font-display [&amp;_h3]:text-(--color-fg) [&amp;_h4]:mt-block-tight [&amp;_h4]:scroll-mt-24 [&amp;_h4]:text-heading-4 [&amp;_h4]:font-display [&amp;_h4]:text-(--color-fg) [&amp;_h5]:mt-block-tight [&amp;_h5]:scroll-mt-24 [&amp;_h5]:text-heading-4 [&amp;_h5]:font-display [&amp;_h5]:text-(--color-fg) [&amp;_h6]:mt-block-tight [&amp;_h6]:scroll-mt-24 [&amp;_h6]:text-heading-4 [&amp;_h6]:font-display [&amp;_h6]:text-(--color-fg) [&amp;_p]:mt-paragraph [&amp;_strong]:font-semibold [&amp;_strong]:text-(--color-fg) [&amp;_em]:italic [&amp;_ul]:mt-paragraph [&amp;_ul]:list-disc [&amp;_ul]:ps-6 [&amp;_ol]:mt-paragraph [&amp;_ol]:list-decimal [&amp;_ol]:ps-6 [&amp;_li]:mt-1 [&amp;_li]:ps-1 [&amp;_li]:marker:text-(--color-text-muted) [&amp;_li>ul]:mt-1 [&amp;_li>ol]:mt-1 [&amp;_blockquote]:mt-paragraph [&amp;_blockquote]:border-s-4 [&amp;_blockquote]:border-(--color-border-strong) [&amp;_blockquote]:ps-4 [&amp;_blockquote]:italic [&amp;_blockquote]:text-(--color-text-muted) [&amp;_hr]:my-block-loose [&amp;_hr]:border-(--color-border) [&amp;_a]:text-(--color-accent) [&amp;_a]:underline [&amp;_a]:underline-offset-4 [&amp;_:not(pre)>code]:rounded [&amp;_:not(pre)>code]:bg-(--color-surface-secondary) [&amp;_:not(pre)>code]:px-1.5 [&amp;_:not(pre)>code]:py-0.5 [&amp;_:not(pre)>code]:text-code [&amp;_:not(pre)>code]:font-mono [&amp;_:not(pre)>code]:text-(--color-fg) [&amp;_pre]:mt-paragraph [&amp;_pre]:overflow-auto [&amp;_pre]:rounded-md [&amp;_pre]:border-hairline [&amp;_pre]:border-(--color-border) [&amp;_pre]:bg-(--color-bg) [&amp;_pre]:p-4 [&amp;_pre]:font-mono [&amp;_pre]:text-body-sm [&amp;_img]:mt-paragraph [&amp;_img]:h-auto [&amp;_img]:max-w-full [&amp;_img]:rounded-lg [&amp;_table]:mt-paragraph [&amp;_table]:w-full [&amp;_table]:border-collapse [&amp;_table]:text-body-sm [&amp;_th]:border-b [&amp;_th]:border-(--color-border) [&amp;_th]:bg-(--color-surface-secondary) [&amp;_th]:px-3 [&amp;_th]:py-2 [&amp;_th]:text-start [&amp;_th]:font-semibold [&amp;_th]:text-(--color-fg) [&amp;_td]:border-b [&amp;_td]:border-(--color-border) [&amp;_td]:px-3 [&amp;_td]:py-2 [&amp;_kbd]:rounded [&amp;_kbd]:border-hairline [&amp;_kbd]:border-(--color-border) [&amp;_kbd]:bg-(--color-surface-secondary) [&amp;_kbd]:px-1.5 [&amp;_kbd]:py-0.5 [&amp;_kbd]:text-caption [&amp;_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-remote" data-source="
91
+ Body."></div></div></article></div></div></div></main>"
93
92
  `);
94
93
  });
95
94
  });
@@ -111,9 +110,8 @@ describe("ProductSlugPage — snapshot regression", () => {
111
110
  });
112
111
  const { container } = render(element);
113
112
  expect(container.innerHTML).toMatchInlineSnapshot(`
114
- "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AI Launch Kit","description":"The AX-first OSS template.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"AI Launch Kit","description":"The AX-first OSS template."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Products","item":"http://localhost:3000/en/products"},{"@type":"ListItem","position":3,"name":"AI Launch Kit","item":"http://localhost:3000/en/products/launch-kit"}]}]}</script><main id="main"><h1>AI Launch Kit</h1><p data-testid="product-description">The AX-first OSS template.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source="# AI Launch Kit
115
-
116
- Body."></div></article></div></div></main>"
113
+ "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AI Launch Kit","description":"The AX-first OSS template.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"AI Launch Kit","description":"The AX-first OSS template."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Products","item":"http://localhost:3000/en/products"},{"@type":"ListItem","position":3,"name":"AI Launch Kit","item":"http://localhost:3000/en/products/launch-kit"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">AI Launch Kit</h1><p data-testid="product-description" class="mt-block-tight text-lead">The AX-first OSS template.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle mt-block-loose"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-fg) text-body-sm leading-relaxed [&amp;>*:first-child]:mt-0 [&amp;_h2]:mt-block-loose [&amp;_h2]:scroll-mt-24 [&amp;_h2]:text-heading-3 [&amp;_h2]:font-display [&amp;_h2]:text-(--color-fg) [&amp;_h3]:mt-block-loose [&amp;_h3]:scroll-mt-24 [&amp;_h3]:text-heading-4 [&amp;_h3]:font-display [&amp;_h3]:text-(--color-fg) [&amp;_h4]:mt-block-tight [&amp;_h4]:scroll-mt-24 [&amp;_h4]:text-heading-4 [&amp;_h4]:font-display [&amp;_h4]:text-(--color-fg) [&amp;_h5]:mt-block-tight [&amp;_h5]:scroll-mt-24 [&amp;_h5]:text-heading-4 [&amp;_h5]:font-display [&amp;_h5]:text-(--color-fg) [&amp;_h6]:mt-block-tight [&amp;_h6]:scroll-mt-24 [&amp;_h6]:text-heading-4 [&amp;_h6]:font-display [&amp;_h6]:text-(--color-fg) [&amp;_p]:mt-paragraph [&amp;_strong]:font-semibold [&amp;_strong]:text-(--color-fg) [&amp;_em]:italic [&amp;_ul]:mt-paragraph [&amp;_ul]:list-disc [&amp;_ul]:ps-6 [&amp;_ol]:mt-paragraph [&amp;_ol]:list-decimal [&amp;_ol]:ps-6 [&amp;_li]:mt-1 [&amp;_li]:ps-1 [&amp;_li]:marker:text-(--color-text-muted) [&amp;_li>ul]:mt-1 [&amp;_li>ol]:mt-1 [&amp;_blockquote]:mt-paragraph [&amp;_blockquote]:border-s-4 [&amp;_blockquote]:border-(--color-border-strong) [&amp;_blockquote]:ps-4 [&amp;_blockquote]:italic [&amp;_blockquote]:text-(--color-text-muted) [&amp;_hr]:my-block-loose [&amp;_hr]:border-(--color-border) [&amp;_a]:text-(--color-accent) [&amp;_a]:underline [&amp;_a]:underline-offset-4 [&amp;_:not(pre)>code]:rounded [&amp;_:not(pre)>code]:bg-(--color-surface-secondary) [&amp;_:not(pre)>code]:px-1.5 [&amp;_:not(pre)>code]:py-0.5 [&amp;_:not(pre)>code]:text-code [&amp;_:not(pre)>code]:font-mono [&amp;_:not(pre)>code]:text-(--color-fg) [&amp;_pre]:mt-paragraph [&amp;_pre]:overflow-auto [&amp;_pre]:rounded-md [&amp;_pre]:border-hairline [&amp;_pre]:border-(--color-border) [&amp;_pre]:bg-(--color-bg) [&amp;_pre]:p-4 [&amp;_pre]:font-mono [&amp;_pre]:text-body-sm [&amp;_img]:mt-paragraph [&amp;_img]:h-auto [&amp;_img]:max-w-full [&amp;_img]:rounded-lg [&amp;_table]:mt-paragraph [&amp;_table]:w-full [&amp;_table]:border-collapse [&amp;_table]:text-body-sm [&amp;_th]:border-b [&amp;_th]:border-(--color-border) [&amp;_th]:bg-(--color-surface-secondary) [&amp;_th]:px-3 [&amp;_th]:py-2 [&amp;_th]:text-start [&amp;_th]:font-semibold [&amp;_th]:text-(--color-fg) [&amp;_td]:border-b [&amp;_td]:border-(--color-border) [&amp;_td]:px-3 [&amp;_td]:py-2 [&amp;_kbd]:rounded [&amp;_kbd]:border-hairline [&amp;_kbd]:border-(--color-border) [&amp;_kbd]:bg-(--color-surface-secondary) [&amp;_kbd]:px-1.5 [&amp;_kbd]:py-0.5 [&amp;_kbd]:text-caption [&amp;_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-remote" data-source="
114
+ Body."></div></div></article></div></div></div></main>"
117
115
  `);
118
116
  });
119
117