create-ailk 0.13.0 → 0.14.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 (113) hide show
  1. package/README.md +12 -4
  2. package/component-catalog.json +247 -33
  3. package/component-catalog.md +10 -6
  4. package/dist/apply-design.d.ts +21 -2
  5. package/dist/apply-design.js +120 -19
  6. package/dist/cli.js +1 -0
  7. package/dist/design-compile.d.ts +70 -2
  8. package/dist/design-compile.js +97 -1
  9. package/dist/design-home.d.ts +74 -0
  10. package/dist/design-home.js +256 -0
  11. package/dist/design-package.d.ts +39 -2
  12. package/dist/design-package.js +223 -9
  13. package/dist/design-presets.d.ts +17 -1
  14. package/dist/design-presets.js +17 -5
  15. package/dist/design-scaffold.d.ts +6 -25
  16. package/dist/design-scaffold.js +26 -74
  17. package/dist/index.d.ts +1 -0
  18. package/dist/lib/design-checks.d.ts +12 -1
  19. package/dist/lib/design-checks.js +38 -2
  20. package/dist/lib/module-license-gate.d.ts +9 -0
  21. package/dist/lib/module-license-gate.js +18 -0
  22. package/dist/programmatic.js +70 -26
  23. package/dist/site-chrome.d.ts +38 -0
  24. package/dist/site-chrome.js +109 -2
  25. package/dist/site-fonts.d.ts +21 -0
  26. package/dist/site-fonts.js +54 -0
  27. package/dist/site-roster.d.ts +16 -0
  28. package/dist/site-roster.js +81 -0
  29. package/dist/starter-grid.js +15 -0
  30. package/dist/surfaces.js +12 -0
  31. package/dist/team-profiles.d.ts +29 -0
  32. package/dist/team-profiles.js +43 -0
  33. package/package.json +3 -3
  34. package/release-scope.json +1 -1
  35. package/site-templates/_base/content/en/TeamList/team.mdx +68 -0
  36. package/site-templates/_base/content/en/TeamMember/first-member.mdx +22 -0
  37. package/site-templates/_base/content/en/TeamMember/second-member.mdx +22 -0
  38. package/site-templates/_base/content/en/TeamMember/third-member.mdx +22 -0
  39. package/site-templates/designs/minimalism/DESIGN.md +164 -34
  40. package/site-templates/designs/minimalism/home/local-business-service.mdx +119 -0
  41. package/site-templates/designs/minimalism/home/organization-product.mdx +132 -0
  42. package/site-templates/designs/minimalism/home/organization-service.mdx +131 -0
  43. package/site-templates/designs/minimalism/home/person-service.mdx +101 -0
  44. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +34 -1
  45. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +20 -0
  46. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +34 -1
  47. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +22 -1
  48. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  49. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +34 -1
  50. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -7
  51. package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
  52. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +34 -1
  53. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -7
  54. package/site-templates/shapes/organization-service/ailk-template.json +1 -1
  55. package/templates/README.md +2 -0
  56. package/templates/apps/api/CLAUDE.md +4 -1
  57. package/templates/apps/mcp/package.json +1 -1
  58. package/templates/apps/web/CLAUDE.md +11 -0
  59. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +10 -0
  60. package/templates/apps/web/app/[locale]/(authed)/account/settings/__tests__/theme-modes.test.ts +42 -0
  61. package/templates/apps/web/app/[locale]/(authed)/account/settings/theme-modes.ts +8 -0
  62. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +2 -2
  63. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  64. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +14 -1
  65. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +69 -0
  66. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +64 -0
  67. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +4 -4
  68. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +15 -3
  69. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +17 -6
  70. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +14 -1
  71. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +2 -2
  72. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +4 -6
  73. package/templates/apps/web/app/[locale]/layout.tsx +156 -53
  74. package/templates/apps/web/app/[locale]/page.tsx +37 -0
  75. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  76. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +4 -6
  77. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +15 -2
  78. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +43 -6
  79. package/templates/apps/web/app/[locale]/services/page.tsx +14 -2
  80. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +2 -2
  81. package/templates/apps/web/app/[locale]/team/[slug]/page.tsx +45 -0
  82. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +155 -0
  83. package/templates/apps/web/app/[locale]/team/page.tsx +49 -0
  84. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +2 -2
  85. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +14 -1
  86. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
  87. package/templates/apps/web/app/not-found.tsx +47 -0
  88. package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
  89. package/templates/apps/web/lib/__tests__/site-theme-head.test.ts +70 -0
  90. package/templates/apps/web/lib/__tests__/site-theme.test.ts +64 -1
  91. package/templates/apps/web/lib/profile-back-link.ts +51 -0
  92. package/templates/apps/web/lib/site-chrome.ts +12 -0
  93. package/templates/apps/web/lib/site-fonts.ts +15 -0
  94. package/templates/apps/web/lib/site-logo.tsx +39 -1
  95. package/templates/apps/web/lib/site-theme-head.ts +33 -0
  96. package/templates/apps/web/lib/site-theme.ts +13 -1
  97. package/templates/apps/web/messages/en.json +7 -1
  98. package/templates/database/CHANGELOG.md +7 -0
  99. package/templates/database/package.json +1 -1
  100. package/versions.json +34 -34
  101. package/site-templates/designs/minimalism/sample/README.md +0 -48
  102. package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +0 -244
  103. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-1.svg +0 -7
  104. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-2.svg +0 -7
  105. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-3.svg +0 -7
  106. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-4.svg +0 -7
  107. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-5.svg +0 -7
  108. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-6.svg +0 -7
  109. package/site-templates/designs/minimalism/sample/public/hero-backdrop.svg +0 -17
  110. package/site-templates/designs/minimalism/sample/public/logo-placeholder-1.svg +0 -6
  111. package/site-templates/designs/minimalism/sample/public/logo-placeholder-2.svg +0 -6
  112. package/site-templates/designs/minimalism/sample/public/logo-placeholder-3.svg +0 -6
  113. package/site-templates/designs/minimalism/sample/public/wordmark-placeholder.svg +0 -28
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Tests for the profile back-link rule (#7744 D7).
3
+ *
4
+ * On a deck home, a team profile's back arrow returns to the home's people
5
+ * slide (`#slide-people`, #7740's anchor for the section id `people`);
6
+ * everywhere else it goes home. Covers bare and locale-prefixed profiles, the
7
+ * `/team` index, near misses, a non-deck home and the absent-header fail-safe.
8
+ */
9
+
10
+ import {
11
+ backLinkHref,
12
+ isTeamProfilePath,
13
+ PEOPLE_SLIDE_ANCHOR,
14
+ } from "../profile-back-link";
15
+
16
+ const LOCALES = ["en", "fr"];
17
+
18
+ describe("isTeamProfilePath", () => {
19
+ it.each(["/team/first-member", "/fr/team/first-member", "/en/team/a"])(
20
+ "is true for the profile path %s",
21
+ (pathname) => {
22
+ expect(isTeamProfilePath(pathname, LOCALES)).toBe(true);
23
+ },
24
+ );
25
+
26
+ it.each([
27
+ "/team",
28
+ "/team/",
29
+ "/fr/team",
30
+ "/team/a/b",
31
+ "/teams/x",
32
+ "/about",
33
+ "/",
34
+ "",
35
+ ])("is false for %s", (pathname) => {
36
+ expect(isTeamProfilePath(pathname, LOCALES)).toBe(false);
37
+ });
38
+
39
+ it("treats a prefix that is not an enabled locale as the first segment", () => {
40
+ expect(isTeamProfilePath("/xx/team/a", ["en"])).toBe(false);
41
+ });
42
+
43
+ it("answers false for an absent pathname", () => {
44
+ expect(isTeamProfilePath(null, LOCALES)).toBe(false);
45
+ expect(isTeamProfilePath(undefined, LOCALES)).toBe(false);
46
+ });
47
+ });
48
+
49
+ describe("backLinkHref", () => {
50
+ it("names the people-slide anchor", () => {
51
+ expect(PEOPLE_SLIDE_ANCHOR).toBe("slide-people");
52
+ });
53
+
54
+ it("returns /#slide-people for a profile on a deck home", () => {
55
+ expect(
56
+ backLinkHref({
57
+ pathname: "/team/first-member",
58
+ homePath: "/",
59
+ home: "deck",
60
+ locales: LOCALES,
61
+ }),
62
+ ).toBe("/#slide-people");
63
+ });
64
+
65
+ it("keeps the /fr prefix on a non-default locale", () => {
66
+ expect(
67
+ backLinkHref({
68
+ pathname: "/fr/team/first-member",
69
+ homePath: "/fr",
70
+ home: "deck",
71
+ locales: LOCALES,
72
+ }),
73
+ ).toBe("/fr#slide-people");
74
+ });
75
+
76
+ it.each(["/team", "/team/a/b", "/about", "/teams/x", null, undefined])(
77
+ "returns the home for %s on a deck home",
78
+ (pathname) => {
79
+ expect(
80
+ backLinkHref({ pathname, homePath: "/", home: "deck", locales: LOCALES }),
81
+ ).toBe("/");
82
+ },
83
+ );
84
+
85
+ it("returns the home for a profile when the home is not a deck", () => {
86
+ expect(
87
+ backLinkHref({
88
+ pathname: "/team/first-member",
89
+ homePath: "/",
90
+ home: "default",
91
+ locales: LOCALES,
92
+ }),
93
+ ).toBe("/");
94
+ });
95
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `siteThemeHead` (#7742 D6 / AC-4) — the one helper behind the locale
3
+ * layout's `<style data-ailk="theme-vars">` and `<html>` class and the minimal
4
+ * 404's. Pinned against the expressions the layout computed inline before the
5
+ * extraction, so moving them into the helper changed no byte of the `<style>`
6
+ * text; `layout.test.tsx` and `site-fonts.test.tsx` pin the layout's own
7
+ * emitted text over it.
8
+ */
9
+ import { applyDesignFonts, themeToCssVarsBothModes } from "@working-theory/theme";
10
+
11
+ import type { SiteFonts } from "../site-fonts";
12
+
13
+ const mockSiteFonts: SiteFonts = { className: "", families: {} };
14
+ // eslint-disable-next-line no-restricted-syntax -- #507: `site-fonts.ts` is a generated data module with no external boundary to mock instead; this is the seam the helper reads.
15
+ jest.mock("../site-fonts", () => ({
16
+ __esModule: true,
17
+ siteFonts: mockSiteFonts,
18
+ }));
19
+
20
+ const mockLoadSiteBrandTokens = jest.fn();
21
+ const mockLoadSiteShapeCss = jest.fn();
22
+ // eslint-disable-next-line no-restricted-syntax -- #507: the helper's one seam onto `_site.mdx`; the overlay is the test's to set.
23
+ jest.mock("../site-theme", () => ({
24
+ __esModule: true,
25
+ loadSiteBrandTokens: (...args: unknown[]) => mockLoadSiteBrandTokens(...args),
26
+ loadSiteShapeCss: (...args: unknown[]) => mockLoadSiteShapeCss(...args),
27
+ }));
28
+
29
+ import { siteThemeHead } from "../site-theme-head";
30
+
31
+ /** The locale layout's pre-#7742 inline computation, verbatim. */
32
+ function layoutStyleText(): { css: string; className: string | undefined } {
33
+ const designFonts = applyDesignFonts(mockLoadSiteBrandTokens(), mockSiteFonts);
34
+ return {
35
+ css:
36
+ themeToCssVarsBothModes(designFonts.tokens ? { brand: designFonts.tokens } : {}) +
37
+ mockLoadSiteShapeCss(),
38
+ className: designFonts.className || undefined,
39
+ };
40
+ }
41
+
42
+ beforeEach(() => {
43
+ Object.assign(mockSiteFonts, { className: "", families: {} });
44
+ mockLoadSiteBrandTokens.mockReset().mockReturnValue(undefined);
45
+ mockLoadSiteShapeCss.mockReset().mockReturnValue("");
46
+ });
47
+
48
+ describe("siteThemeHead (#7742 D6)", () => {
49
+ it("no theme and no faces: today's CSS and no class", () => {
50
+ const head = siteThemeHead();
51
+ expect(head.css).toBe(themeToCssVarsBothModes({}));
52
+ expect(head.css).toBe(layoutStyleText().css);
53
+ expect(head.fontClassName).toBeUndefined();
54
+ });
55
+
56
+ it("a brand overlay, shape CSS and design faces: the layout's exact text and class", () => {
57
+ mockLoadSiteBrandTokens.mockReturnValue({ font: { body: "Brand" } });
58
+ mockLoadSiteShapeCss.mockReturnValue(":root{--radius-control:0}");
59
+ Object.assign(mockSiteFonts, {
60
+ className: "f1 f2",
61
+ families: { display: "var(--font-design-display), Georgia, serif" },
62
+ });
63
+ const head = siteThemeHead();
64
+ const expected = layoutStyleText();
65
+ expect(head.css).toBe(expected.css);
66
+ expect(head.css).toContain("--font-body: Brand;");
67
+ expect(head.css.endsWith(":root{--radius-control:0}")).toBe(true);
68
+ expect(head.fontClassName).toBe(expected.className);
69
+ });
70
+ });
@@ -29,9 +29,11 @@ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
29
29
  import { PartialColorPaletteSchema, SiteThemeSchema } from "@working-theory/site-config/schema";
30
30
  import {
31
31
  siteColorTokenMap,
32
+ siteThemeScopedDarkCss,
32
33
  siteThemeShapeCss,
33
34
  siteThemeToBrandTokens,
34
35
  themeToCssVarsBothModes,
36
+ themeToScopedDarkCss,
35
37
  } from "@working-theory/theme";
36
38
 
37
39
  import { loadSiteBrandTokens, loadSiteShapeCss } from "../site-theme";
@@ -268,8 +270,11 @@ describe("loadSiteBrandTokens — the brand triad", () => {
268
270
 
269
271
  const theme = SiteThemeSchema.parse(parsed);
270
272
  const before = siteThemeToBrandTokens(theme);
273
+ // Mirrors loadSiteShapeCss: a `darkBands: site` theme (minimalism's, since #7739) also scopes
274
+ // its dark ink; without it `scoped` is "" and the golden is the pre-triad CSS unchanged.
275
+ const scoped = theme.darkBands === "site" && before ? siteThemeScopedDarkCss(theme, before) : "";
271
276
  expect(layoutCss()).toBe(
272
- themeToCssVarsBothModes(before ? { brand: before } : {}) + siteThemeShapeCss(theme),
277
+ themeToCssVarsBothModes(before ? { brand: before } : {}) + siteThemeShapeCss(theme) + scoped,
273
278
  );
274
279
  });
275
280
 
@@ -318,3 +323,61 @@ describe("the two vocabularies agree on which slots exist", () => {
318
323
  );
319
324
  });
320
325
  });
326
+
327
+ // #7719 D6 — `darkBands: site` paints dark-scoped bands with the site's dark colours.
328
+ describe("loadSiteShapeCss — darkBands", () => {
329
+ const originalRoot = process.env.AILK_CONTENT_ROOT;
330
+ const tempRoots: string[] = [];
331
+
332
+ afterEach(() => {
333
+ if (originalRoot === undefined) delete process.env.AILK_CONTENT_ROOT;
334
+ else process.env.AILK_CONTENT_ROOT = originalRoot;
335
+ for (const root of tempRoots.splice(0)) rmSync(root, { recursive: true, force: true });
336
+ });
337
+
338
+ function useSiteMdx(frontmatter: string): void {
339
+ const root = mkdtempSync(join(tmpdir(), "ailk-site-dark-bands-"));
340
+ tempRoots.push(root);
341
+ writeFileSync(join(root, "_site.mdx"), `---\nsiteName: Test\n${frontmatter}---\n`, "utf8");
342
+ process.env.AILK_CONTENT_ROOT = root;
343
+ }
344
+
345
+ const THEME = [
346
+ "theme:",
347
+ " corners: square",
348
+ " colors:",
349
+ " dark:",
350
+ ' bg: "#1c1917"',
351
+ ' text: "#fafaf9"',
352
+ "",
353
+ ].join("\n");
354
+
355
+ it("without darkBands, the shape CSS is today's", () => {
356
+ useSiteMdx(THEME);
357
+ const theme = SiteThemeSchema.parse({ corners: "square", colors: { dark: { bg: "#1c1917", text: "#fafaf9" } } });
358
+ expect(loadSiteShapeCss()).toBe(siteThemeShapeCss(theme));
359
+ });
360
+
361
+ it("with darkBands: site, ends with the scoped dark rule carrying the site's dark --color-bg", () => {
362
+ useSiteMdx(`${THEME} darkBands: site\n`);
363
+ const css = loadSiteShapeCss();
364
+ const brand = loadSiteBrandTokens();
365
+ expect(brand).toBeDefined();
366
+ const scoped = themeToScopedDarkCss({ brand: brand! });
367
+ expect(css.endsWith(scoped)).toBe(true);
368
+ expect(scoped).toContain("--color-bg: #1c1917;");
369
+ expect(css.startsWith(siteThemeShapeCss(SiteThemeSchema.parse({ corners: "square" })))).toBe(true);
370
+ });
371
+
372
+ it("with darkBands: site, the scoped rule leaves square corners to inherit from :root", () => {
373
+ useSiteMdx(`${THEME} darkBands: site\n`);
374
+ const scoped = loadSiteShapeCss().slice(loadSiteShapeCss().indexOf(":not(:root)"));
375
+ expect(scoped).not.toMatch(/--radius-/);
376
+ expect(scoped).not.toMatch(/--font-/);
377
+ });
378
+
379
+ it("darkBands: platform adds nothing", () => {
380
+ useSiteMdx(`${THEME} darkBands: platform\n`);
381
+ expect(loadSiteShapeCss()).not.toContain(':not(:root)');
382
+ });
383
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The back arrow's href on a team profile page (#7744 D7).
3
+ *
4
+ * On a deck home, a profile at `/team/<slug>` returns the visitor to the
5
+ * home's people slide — `#slide-people`, the anchor #7740 gives the home
6
+ * section whose id is `people` — rather than to the top of the home
7
+ * (checklist D3). Every other inner page, the `/team` index included, and
8
+ * every page on a non-deck home goes home.
9
+ *
10
+ * No Next.js import — pure functions, asserted directly by their own suite,
11
+ * in the `auth-routes.ts` idiom.
12
+ */
13
+
14
+ /** The people slide's anchor: #7740's `slide-` prefix plus the section id `people`. */
15
+ export const PEOPLE_SLIDE_ANCHOR = "slide-people";
16
+
17
+ /**
18
+ * Whether `pathname` is a team profile: `/team/<slug>`, bare or under an
19
+ * enabled-locale prefix. Not `/team` (the index) and not a deeper path.
20
+ *
21
+ * Fail-safe direction: an absent or empty pathname answers `false`, so a
22
+ * missing `x-pathname` header leaves the back arrow pointing home.
23
+ */
24
+ export function isTeamProfilePath(
25
+ pathname: string | null | undefined,
26
+ locales: readonly string[],
27
+ ): boolean {
28
+ if (!pathname) return false;
29
+ const path = pathname.split(/[?#]/)[0] ?? "";
30
+ const bare =
31
+ locales.length > 0
32
+ ? path.replace(new RegExp(`^/(${locales.join("|")})(?=/|$)`), "")
33
+ : path;
34
+ const segments = bare.split("/").slice(1);
35
+ return (
36
+ segments.length === 2 && segments[0] === "team" && segments[1] !== ""
37
+ );
38
+ }
39
+
40
+ /** `${homePath}#slide-people` when the home is a deck and the path is a profile; otherwise `homePath`. */
41
+ export function backLinkHref(input: {
42
+ pathname: string | null | undefined;
43
+ homePath: string;
44
+ home: string;
45
+ locales: readonly string[];
46
+ }): string {
47
+ const { pathname, homePath, home, locales } = input;
48
+ return home === "deck" && isTeamProfilePath(pathname, locales)
49
+ ? `${homePath}#${PEOPLE_SLIDE_ANCHOR}`
50
+ : homePath;
51
+ }
@@ -13,12 +13,24 @@
13
13
  // - home: "deck" → the locale-root route mounts MarketingShell with
14
14
  // snap="mandatory" + deckHero (spec 5426 D7); every other route is
15
15
  // unaffected.
16
+ // - innerPages: "bare" → every non-home page renders the bare header and
17
+ // the one-bar footer.
18
+ // - deckPresentation → the deck home presents slide by slide: fitted slides, slide anchors, the mark follows the ground
19
+ // - colorMode: "light" → the site is pinned to light and shows no theme
20
+ // switch; dark appears only as a deck ground.
21
+ // - chromeStyle: "minimal" → the deck's closing slide is the wordmark,
22
+ // contact lines and one legal line; the consent bar is a hairline bar on
23
+ // the page ground; the 404 renders in the site's theme.
16
24
 
17
25
  export interface SiteChrome {
18
26
  nav: "full" | "mark-only";
19
27
  footer: "columns" | "centered";
20
28
  announcementBanner: boolean;
21
29
  home: "default" | "deck";
30
+ innerPages?: "bare";
31
+ deckPresentation?: true;
32
+ colorMode?: "light";
33
+ chromeStyle?: "minimal";
22
34
  }
23
35
 
24
36
  export const siteChrome: SiteChrome = {
@@ -0,0 +1,15 @@
1
+ // site-fonts.ts — the web faces the site's design sources.
2
+ //
3
+ // GENERATED by create-ailk from the design's `typography.<role>.fontSource`
4
+ // (#7720). A design that sources no face gets the empty default.
5
+ // `apps/web/app/[locale]/layout.tsx` passes this to `applyDesignFonts`
6
+ // (@working-theory/theme): `families` fills --font-display / --font-body
7
+ // unless the site's own `brand.fonts` sets them, and `className` (the
8
+ // next/font variable classes) goes on <html>.
9
+
10
+ export interface SiteFonts {
11
+ className: string;
12
+ families: { display?: string | undefined; body?: string | undefined };
13
+ }
14
+
15
+ export const siteFonts: SiteFonts = { className: "", families: {} };
@@ -5,7 +5,7 @@
5
5
  * Split from `site-brand.tsx`, which ten route and lib modules import for
6
6
  * `SITE_NAME` / `SITE_BASE_URL`: the mark needs `@working-theory/ui`'s
7
7
  * `Image` and site-config's schema, and those stay out of that wider graph
8
- * (spec #6820 § 5.4). The two layouts are the only callers.
8
+ * (spec #6820 § 5.4). The two layouts and the home page are the only callers.
9
9
  */
10
10
 
11
11
  import { BrandLogomarkSchema } from "@working-theory/site-config/schema";
@@ -14,6 +14,44 @@ import type { ReactElement } from "react";
14
14
 
15
15
  import { SITE_LOGOMARK, SITE_NAME } from "./site-brand";
16
16
 
17
+ /** The light-mode mark as a hero `lockup`: square, so the Hero shows it as a brand mark. */
18
+ export interface SiteLogomarkAsset {
19
+ src: string;
20
+ alt: string;
21
+ width: number;
22
+ height: number;
23
+ }
24
+
25
+ /**
26
+ * The same mark `siteLogo()` renders, as data (#7711): the deck home passes it
27
+ * to its landing hero, which carries the brand while the header is bare
28
+ * (#5697). It passes the light mark only, with no `darkSrc`, as #7711 shipped it.
29
+ */
30
+ export function siteLogomarkAsset(): SiteLogomarkAsset {
31
+ if (SITE_LOGOMARK === undefined) {
32
+ return { src: "/logomark.svg", alt: SITE_NAME, width: 28, height: 28 };
33
+ }
34
+ const { light, alt } = BrandLogomarkSchema.parse(SITE_LOGOMARK);
35
+ return { src: light, alt, width: 28, height: 28 };
36
+ }
37
+
38
+ /**
39
+ * The header's wordmark pair as a hero `lockup` (#7733): the deck home's
40
+ * landing puts it in the headline's place. The same files and size the
41
+ * layout's header wordmark uses; wide, so the Hero reads it as a wordmark,
42
+ * and `alt` is the site name, so the landing's one `h1` is named for the site.
43
+ * `darkSrc` is the light-ink wordmark the Hero swaps in on a dark ground.
44
+ */
45
+ export function siteWordmarkAsset(): SiteLogomarkAsset & { darkSrc: string } {
46
+ return {
47
+ src: "/wordmark.svg",
48
+ darkSrc: "/wordmark-light.svg",
49
+ alt: SITE_NAME,
50
+ width: 140,
51
+ height: 16,
52
+ };
53
+ }
54
+
17
55
  /**
18
56
  * Shared brand mark. The Nav pairs it with `/wordmark.svg`. RSC-safe (no
19
57
  * hooks, no client state).
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The site's theme head (#7742 D6): the inline CSS the locale layout puts in
3
+ * its `<style data-ailk="theme-vars">`, and the class its `<html>` carries
4
+ * for the design's web faces. One helper so the locale layout and the
5
+ * `chromeStyle: minimal` 404 (`app/not-found.tsx`), which renders its own
6
+ * `<html>`, can never drift apart.
7
+ *
8
+ * The site's own declared colours (`_site.mdx` `theme:` + `brand:`) become
9
+ * the token overlay; the design's faces (#7720, the generated
10
+ * `site-fonts.ts`) lie beneath `brand.fonts`; both theme modes are emitted,
11
+ * the dark one gated by `[data-theme="dark"]` (#1532); the site's shape
12
+ * settings (`theme.corners`, `theme.eyebrowCase`) follow so they win.
13
+ */
14
+
15
+ import { applyDesignFonts, themeToCssVarsBothModes } from "@working-theory/theme";
16
+
17
+ import { siteFonts } from "./site-fonts";
18
+ import { loadSiteBrandTokens, loadSiteShapeCss } from "./site-theme";
19
+
20
+ export interface SiteThemeHead {
21
+ /** The `<style data-ailk="theme-vars">` text. */
22
+ css: string;
23
+ /** The `<html>` class for the design's faces; absent when there are none. */
24
+ fontClassName?: string;
25
+ }
26
+
27
+ export function siteThemeHead(): SiteThemeHead {
28
+ const designFonts = applyDesignFonts(loadSiteBrandTokens(), siteFonts);
29
+ const css =
30
+ themeToCssVarsBothModes(designFonts.tokens ? { brand: designFonts.tokens } : {}) +
31
+ loadSiteShapeCss();
32
+ return designFonts.className ? { css, fontClassName: designFonts.className } : { css };
33
+ }
@@ -30,6 +30,7 @@ import { join } from "node:path";
30
30
  import { BrandSchema, SiteThemeSchema } from "@working-theory/site-config/schema";
31
31
  import {
32
32
  siteBrandToBrandTokens,
33
+ siteThemeScopedDarkCss,
33
34
  siteThemeShapeCss,
34
35
  type BrandTokenOverrides,
35
36
  } from "@working-theory/theme";
@@ -97,9 +98,20 @@ export function loadSiteBrandTokens(): BrandTokenOverrides | undefined {
97
98
  /**
98
99
  * The site's shape settings (`theme.corners`, `theme.eyebrowCase`) as a CSS
99
100
  * override block for `:root`. "" when the site sets neither.
101
+ *
102
+ * #7719 D6 — a theme with `darkBands: "site"` also gets its own dark palette
103
+ * on every dark-scoped band below the root (`siteThemeScopedDarkCss`), built
104
+ * from the overlay the root rule carries (the `brand:` triad included). Only
105
+ * mode-variant tokens are re-declared, so the shape settings above still
106
+ * reach inside the band.
107
+ * Without it the string is today's.
100
108
  */
101
109
  export function loadSiteShapeCss(): string {
102
110
  const { theme } = readSiteBlocks();
103
111
  if (theme === undefined) return "";
104
- return siteThemeShapeCss(SiteThemeSchema.parse(theme));
112
+ const parsed = SiteThemeSchema.parse(theme);
113
+ const shape = siteThemeShapeCss(parsed);
114
+ if (parsed.darkBands !== "site") return shape;
115
+ const brand = loadSiteBrandTokens();
116
+ return brand ? shape + siteThemeScopedDarkCss(parsed, brand) : shape;
105
117
  }
@@ -51,7 +51,9 @@
51
51
  "customer3": "'{Customer 3}'",
52
52
  "service1": "'{Service 1}'",
53
53
  "service2": "'{Service 2}'",
54
- "service3": "'{Service 3}'"
54
+ "service3": "'{Service 3}'",
55
+ "nextSlide": "Next slide",
56
+ "backToTop": "Back to top"
55
57
  },
56
58
  "consent": {
57
59
  "title": "Cookie Preferences",
@@ -96,6 +98,10 @@
96
98
  "heading": "Portfolio",
97
99
  "emptyState": "Portfolio coming soon."
98
100
  },
101
+ "team": {
102
+ "heading": "Team",
103
+ "emptyState": "Team listing coming soon."
104
+ },
99
105
  "courses": {
100
106
  "heading": "Courses",
101
107
  "emptyState": "Courses listing coming soon."
@@ -1,5 +1,12 @@
1
1
  # @working-theory/database
2
2
 
3
+ ## 0.36.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`14119fa`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/14119fac22bf0c26973d9286a74bced761ed1218), [`2475b60`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/2475b608d65880d9986d94628494509faedd0cd9), [`7fdf60d`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/7fdf60de18d86d878f5e567d6df5c2e0d64d97eb), [`47eeb95`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/47eeb958390f2eadd7b0c43a47c46fbbd1309872), [`4609e76`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/4609e7640798c53c730b7b9f0e723df1300ca379), [`91c5f56`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/91c5f56121d0efe475f918c493ed4245834d9e7f), [`994094f`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/994094f77619b8b0ff79688b24d7befb4b1b29fa)]:
8
+ - @working-theory/validation@0.36.0
9
+
3
10
  ## 0.35.0
4
11
 
5
12
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@working-theory/database",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/versions.json CHANGED
@@ -1,52 +1,52 @@
1
1
  {
2
- "@working-theory/admin-ui": "0.35.0",
3
- "@working-theory/aeo": "0.35.0",
4
- "@working-theory/agent-context": "0.35.0",
2
+ "@working-theory/admin-ui": "0.36.0",
3
+ "@working-theory/aeo": "0.36.0",
4
+ "@working-theory/agent-context": "0.36.0",
5
5
  "@working-theory/agent-contract": "0.1.0",
6
6
  "@working-theory/agent-memory": "0.0.0",
7
7
  "@working-theory/ai": "0.1.0",
8
- "@working-theory/analytics": "0.35.0",
9
- "@working-theory/api-client": "0.35.0",
8
+ "@working-theory/analytics": "0.36.0",
9
+ "@working-theory/api-client": "0.36.0",
10
10
  "@working-theory/cda-filesystem": "0.0.0",
11
- "@working-theory/cli": "0.35.0",
12
- "@working-theory/common": "0.35.0",
11
+ "@working-theory/cli": "0.36.0",
12
+ "@working-theory/common": "0.36.0",
13
13
  "@working-theory/consent": "0.1.0",
14
- "@working-theory/content-adapters": "0.35.0",
15
- "@working-theory/content-projection": "0.35.0",
16
- "@working-theory/email": "0.35.0",
14
+ "@working-theory/content-adapters": "0.36.0",
15
+ "@working-theory/content-projection": "0.36.0",
16
+ "@working-theory/email": "0.36.0",
17
17
  "@working-theory/env": "0.0.0",
18
- "@working-theory/eslint-config": "0.35.0",
19
- "@working-theory/fumadocs-schema": "0.35.0",
18
+ "@working-theory/eslint-config": "0.36.0",
19
+ "@working-theory/fumadocs-schema": "0.36.0",
20
20
  "@working-theory/ids": "0.10.0",
21
- "@working-theory/jsonld": "0.35.0",
22
- "@working-theory/license": "0.35.0",
23
- "@working-theory/metadata": "0.35.0",
24
- "@working-theory/observability": "0.35.0",
25
- "@working-theory/page-renderer": "0.35.0",
26
- "@working-theory/plugin-mcp": "0.1.1",
27
- "@working-theory/schema": "0.35.0",
28
- "@working-theory/schema-pack-registry": "0.35.0",
29
- "@working-theory/schema-validator": "0.35.0",
30
- "@working-theory/sections-registry": "0.35.0",
31
- "@working-theory/site-config": "0.35.0",
32
- "@working-theory/stripe-client": "0.35.0",
33
- "@working-theory/templates": "0.35.0",
34
- "@working-theory/theme": "0.35.0",
35
- "@working-theory/ui": "0.35.0",
36
- "@working-theory/validation": "0.35.0",
37
- "@working-theory/web-i18n": "0.35.0",
21
+ "@working-theory/jsonld": "0.36.0",
22
+ "@working-theory/license": "0.36.0",
23
+ "@working-theory/metadata": "0.36.0",
24
+ "@working-theory/observability": "0.36.0",
25
+ "@working-theory/page-renderer": "0.36.0",
26
+ "@working-theory/plugin-mcp": "0.1.2",
27
+ "@working-theory/schema": "0.36.0",
28
+ "@working-theory/schema-pack-registry": "0.36.0",
29
+ "@working-theory/schema-validator": "0.36.0",
30
+ "@working-theory/sections-registry": "0.36.0",
31
+ "@working-theory/site-config": "0.36.0",
32
+ "@working-theory/stripe-client": "0.36.0",
33
+ "@working-theory/templates": "0.36.0",
34
+ "@working-theory/theme": "0.36.0",
35
+ "@working-theory/ui": "0.36.0",
36
+ "@working-theory/validation": "0.36.0",
37
+ "@working-theory/web-i18n": "0.36.0",
38
38
  "@working-theory/billing-client": "0.0.0",
39
39
  "@working-theory/branding": "0.0.0",
40
40
  "@working-theory/license-client": "0.0.0",
41
41
  "@working-theory/license-manifest": "0.0.0",
42
42
  "@working-theory/partner-session": "0.0.0",
43
43
  "@working-theory/preview-protocol": "0.0.0",
44
- "@working-theory/ui-commerce": "0.23.13",
45
- "@working-theory/ui-docs-blog": "0.23.13",
46
- "@working-theory/ui-scheduling": "0.23.13",
47
- "@working-theory/ui-signal-collectors": "0.25.2",
44
+ "@working-theory/ui-commerce": "0.23.14",
45
+ "@working-theory/ui-docs-blog": "0.23.14",
46
+ "@working-theory/ui-scheduling": "0.23.14",
47
+ "@working-theory/ui-signal-collectors": "0.25.3",
48
48
  "@working-theory/web": "0.0.0",
49
49
  "@working-theory/api": "1.0.0",
50
50
  "@working-theory/mcp": "0.0.0",
51
- "@working-theory/database": "0.35.0"
51
+ "@working-theory/database": "0.36.0"
52
52
  }
@@ -1,48 +0,0 @@
1
- # The `minimalism` design's home-deck sample
2
-
3
- > Narrative moved here from the retired minimalism template's `TEMPLATE-MANIFEST.md` when
4
- > that tree retired (spec 6892 D4/D5) — `minimalism` is a design, not a template, so it
5
- > carries no generated manifest of its own. This file is NOT installed onto a scaffold
6
- > (`installDesignSample` copies only `content/**` and `public/**`); it documents the sample for
7
- > maintainers of this design.
8
-
9
- The second Pro template (#5589) that first wore this design was a reports-first site whose home
10
- is a **nine-slide scroll-snap deck**, shaped after the reference captured in
11
- `docs/research/bondcap-capture/`. Structure and design are the reference; **the copy is not** —
12
- every line in `content/en/HomePage/home.mdx` is placeholder English of the same shape and length.
13
-
14
- `layout.home: "deck"` (the `minimalism` design's own layout knob) makes every direct section
15
- child of `home.mdx` one full-viewport snap target, in order. The mark is hidden on slide 1 (the
16
- deck-hero landing state) and revealed from slide 2 on.
17
-
18
- | # | Slide | Section | Ground | Action |
19
- | --- | ----------------- | -------------------------------------------------------------- | ------------------------ | ---------------------------------------------------------- |
20
- | 1 | Landing | `hero` (`backdrop`) | gradient band + backdrop | `{Primary CTA} →` → `/reports`, centered; 44px scroll cue. |
21
- | 2 | Tagline | `content-block` (`align: center`, `fill: viewport`) | light | — |
22
- | 3 | Our community | `logoCloud` (`actions`) | light | `Refer someone →` → `/referral`, bottom-centered at 44px |
23
- | 4 | Reports headline | `content-block` (`align: center`, `fill: viewport`) | light | — |
24
- | 5 | Special reports | `reports-list` (`list`, `tone: on-dark`) | dark (`gradient-dark`) | four dated titles → `/reports/<slug>` |
25
- | 6 | Favorites | `reports-list` (`list`, `tone: on-dark`) | dark (`gradient-dark`) | year-only titles → `/reports/<slug>` |
26
- | 7 | Services headline | `content-block` (`align: center`, `fill: viewport`) | light | `Hire me →` → `/meet` |
27
- | 8 | Partners | `team` (`with-large-images`, `hoverReveal`, `phoneColumns: 2`) | light | `About us →` → `/about`, bottom-centered at 44px |
28
- | 9 | Closing | `cta` (`minimal`) | light | `Contact me →` → `/contact`, centered |
29
-
30
- ## The site principle
31
-
32
- Every slide's action sits at the bottom, centered, at 44px or larger; grids are 2-up on a phone.
33
- This is held by bounded section props on the sections above, never by a `className` override or
34
- hand-rolled markup (`.claude/rules/frontend.md` § Chrome Composes Blocks).
35
-
36
- ## The 11 SVGs
37
-
38
- `public/hero-backdrop.svg`, `public/wordmark-placeholder.svg`, three `public/logo-placeholder-*.svg`
39
- and six `public/avatar-placeholder-*.svg` — all placeholders, referenced only from this sample's
40
- `home.mdx` (the avatars are also referenced by the retired bespoke `AboutPage` seed, which did not
41
- move with this design — its team slide is the only remaining consumer). They ride with the design
42
- because the deck uses all 11; a shape/design pair that does not install this sample never ships
43
- them (D4).
44
-
45
- ## Copy density
46
-
47
- This design declares `copy: { density: "sparse" }` in its `DESIGN.md` — its pages, this deck
48
- included, carry far fewer words than a standard site.