@wenathlan/cobaia 2.0.165 → 2.0.167

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 (51) hide show
  1. package/.dockerignore +1 -1
  2. package/App.tsx +2 -31
  3. package/Dockerfile +162 -4
  4. package/biome.jsonc +3 -3
  5. package/build.gradle.kts +4 -4
  6. package/bun.lock +1820 -487
  7. package/cobaia.csproj +2 -2
  8. package/cobaia.gemspec +2 -2
  9. package/cobaia.java +2 -2
  10. package/db.ts +87 -3
  11. package/docker-compose.yml +58 -0
  12. package/docker.config +74 -0
  13. package/docs/cobaia.md +31 -0
  14. package/docs/distributionlanes.md +1 -1
  15. package/keywords.txt +1 -0
  16. package/labels.ts +23 -1
  17. package/monetization.ts +248 -0
  18. package/nginx.conf +106 -0
  19. package/onboardingstate.ts +211 -0
  20. package/package.json +430 -83
  21. package/pnpm-workspace.yaml +60 -38
  22. package/pom.xml +4 -4
  23. package/routerbase.ts +1 -0
  24. package/schema.prisma +23 -0
  25. package/sol/Sol.tsx +20 -15
  26. package/sol/apps/apps.tsx +1 -1
  27. package/sol/blog/blog.tsx +1 -1
  28. package/sol/home/home.tsx +14 -4
  29. package/sol/intro/intro.tsx +13 -5
  30. package/sol/onboarding/onboarding.tsx +146 -0
  31. package/sol/pricing/pricing.tsx +1 -1
  32. package/sol/shell/Shell.tsx +1 -1
  33. package/sol/sol.css +153 -0
  34. package/sol/support/support.tsx +1 -1
  35. package/sol/wingets/HomeEntrygate.tsx +64 -0
  36. package/sol/wingets/IntroFooter.tsx +1 -1
  37. package/sol/wingets/IntroPlans.tsx +1 -1
  38. package/sol/wingets/ShellAdsslot.tsx +1 -1
  39. package/sol/wingets/index.ts +3 -1
  40. package/tauri.conf.json +1 -1
  41. package/{tests → test}/adsconfig.test.ts +2 -2
  42. package/{tests → test}/checkoutlinks.test.ts +2 -2
  43. package/test/entryflow.test.ts +124 -0
  44. package/{tests → test}/familyurl.test.ts +1 -1
  45. package/{tests → test}/posts.test.ts +2 -2
  46. package/{tests → test}/sandboxmodality.test.ts +1 -1
  47. package/vitest.config.ts +1 -1
  48. package/adsconfig.ts +0 -59
  49. package/checkoutlinks.ts +0 -80
  50. package/posts.ts +0 -54
  51. package/roster.ts +0 -29
@@ -29,57 +29,79 @@ minimumReleaseAgeExclude:
29
29
  - fast-uri@3.1.9
30
30
  - follow-redirects@1.16.1
31
31
  - zod@4.6.6
32
- - '@biomejs/biome@2.6.3 || 2.6.4 || 2.6.5'
33
- - '@biomejs/cli-android-arm64@2.6.3 || 2.6.4 || 2.6.5'
34
- - '@biomejs/cli-android-x64@2.6.3 || 2.6.4 || 2.6.5'
35
- - '@biomejs/cli-darwin-arm64@2.6.3 || 2.6.4 || 2.6.5'
36
- - '@biomejs/cli-darwin-x64@2.6.3 || 2.6.4 || 2.6.5'
37
- - '@biomejs/cli-linux-arm64-musl@2.6.3 || 2.6.4 || 2.6.5'
38
- - '@biomejs/cli-linux-arm64@2.6.3 || 2.6.4 || 2.6.5'
39
- - '@biomejs/cli-linux-x64-musl@2.6.3 || 2.6.4 || 2.6.5'
40
- - '@biomejs/cli-linux-x64@2.6.3 || 2.6.4 || 2.6.5'
41
- - '@biomejs/cli-win32-arm64@2.6.3 || 2.6.4 || 2.6.5'
42
- - '@biomejs/cli-win32-x64@2.6.3 || 2.6.4 || 2.6.5'
32
+ - '@biomejs/biome@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
33
+ - '@biomejs/cli-android-arm64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
34
+ - '@biomejs/cli-android-x64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
35
+ - '@biomejs/cli-darwin-arm64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
36
+ - '@biomejs/cli-darwin-x64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
37
+ - '@biomejs/cli-linux-arm64-musl@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
38
+ - '@biomejs/cli-linux-arm64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
39
+ - '@biomejs/cli-linux-x64-musl@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
40
+ - '@biomejs/cli-linux-x64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
41
+ - '@biomejs/cli-win32-arm64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
42
+ - '@biomejs/cli-win32-x64@2.6.3 || 2.6.4 || 2.6.5 || 2.6.6'
43
43
  - '@types/node@26.6.6'
44
44
  - '@capacitor/android@8.6.0'
45
45
  - '@capacitor/cli@8.6.0'
46
46
  - '@capacitor/core@8.6.0'
47
47
  - '@capacitor/ios@8.6.0'
48
48
  - '@opencode-ai/sdk@1.19.5'
49
- - react-resizable-panels@4.15.1 || 4.15.2 || 4.15.3 || 4.16.0 || 4.16.1 || 4.17.0 || 4.18.0 || 4.18.1
50
- - '@types/google.maps@3.66.9'
51
- - lucide-react@1.64.0 || 1.65.0 || 1.66.0 || 1.67.0 || 1.68.0
49
+ - react-resizable-panels@4.15.1 || 4.15.2 || 4.15.3 || 4.16.0 || 4.16.1 || 4.17.0 || 4.18.0 || 4.18.1 || 4.19.0 || 4.19.1
50
+ - '@types/google.maps@3.66.9 || 3.66.10'
51
+ - lucide-react@1.64.0 || 1.65.0 || 1.66.0 || 1.67.0 || 1.68.0 || 1.69.0 || 1.70.0
52
+ - playwright@1.65.0
53
+ - playwright-core@1.65.0
54
+ - '@anthropic-ai/sdk@1.6.0'
55
+ - '@hookform/resolvers@7.0.3'
56
+ - '@tanstack/query-core@5.106.1 || 5.107.1'
57
+ - '@tanstack/react-query@5.106.1 || 5.107.1'
58
+ - openai@7.52.0 || 7.53.0
59
+ - '@wenathlan/argan@2.0.155 || 2.0.165 || 2.0.166'
60
+ - '@wenathlan/cadria@2.0.154 || 2.0.155 || 2.0.156 || 2.0.165 || 2.0.166 || 2.1.8'
61
+ - '@wenathlan/debonair@2.0.154 || 2.0.155 || 2.0.156 || 2.0.165 || 2.0.166 || 2.1.8'
62
+ - '@wenathlan/devthink@2.0.154 || 2.0.155 || 2.0.156 || 2.0.165 || 2.0.166 || 2.1.7'
63
+ - '@wenathlan/getry@2.0.154 || 2.0.155 || 2.0.156 || 2.0.165 || 2.0.166'
64
+ - '@wenathlan/saddle@2.0.154 || 2.0.155 || 2.0.156 || 2.0.165 || 2.0.166 || 2.1.8'
65
+ - '@wenathlan/vault@2.0.152 || 2.0.153 || 2.0.154 || 2.0.155 || 2.0.156 || 2.0.165 || 2.0.166 || 2.1.8'
66
+ - jsdom@31.0.0
67
+ - pnpm@12.18.0
68
+ - "@pnpm/exe.linux-x64@12.18.0"
52
69
  - '@fluentui/react-icons@2.0.345'
53
- - '@wenathlan/vault@2.0.152 || 2.0.153 || 2.0.154 || 2.0.155 || 2.0.156 || 2.1.8'
54
70
  - hono@4.13.16
55
- - '@wenathlan/cadria@2.0.154 || 2.0.155 || 2.0.156 || 2.1.8'
56
- - '@wenathlan/debonair@2.0.154 || 2.0.155 || 2.0.156 || 2.1.8'
57
- - '@wenathlan/getry@2.0.154 || 2.0.155 || 2.0.156'
58
- - '@tailwindcss/node@4.3.4'
59
- - '@tailwindcss/oxide-android-arm64@4.3.4'
60
- - '@tailwindcss/oxide-darwin-arm64@4.3.4'
61
- - '@tailwindcss/oxide-darwin-x64@4.3.4'
62
- - '@tailwindcss/oxide-freebsd-x64@4.3.4'
63
- - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.4'
64
- - '@tailwindcss/oxide-linux-arm64-gnu@4.3.4'
65
- - '@tailwindcss/oxide-linux-arm64-musl@4.3.4'
66
- - '@tailwindcss/oxide-linux-x64-gnu@4.3.4'
67
- - '@tailwindcss/oxide-linux-x64-musl@4.3.4'
68
- - '@tailwindcss/oxide-wasm32-wasi@4.3.4'
69
- - '@tailwindcss/oxide-win32-arm64-msvc@4.3.4'
70
- - '@tailwindcss/oxide-win32-x64-msvc@4.3.4'
71
- - '@tailwindcss/oxide@4.3.4'
72
- - '@tailwindcss/postcss@4.3.4'
73
- - '@tailwindcss/vite@4.3.4'
71
+ - '@tailwindcss/node@4.3.4 || 4.3.5'
72
+ - '@tailwindcss/oxide-android-arm64@4.3.4 || 4.3.5'
73
+ - '@tailwindcss/oxide-darwin-arm64@4.3.4 || 4.3.5'
74
+ - '@tailwindcss/oxide-darwin-x64@4.3.4 || 4.3.5'
75
+ - '@tailwindcss/oxide-freebsd-x64@4.3.4 || 4.3.5'
76
+ - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.4 || 4.3.5'
77
+ - '@tailwindcss/oxide-linux-arm64-gnu@4.3.4 || 4.3.5'
78
+ - '@tailwindcss/oxide-linux-arm64-musl@4.3.4 || 4.3.5'
79
+ - '@tailwindcss/oxide-linux-x64-gnu@4.3.4 || 4.3.5'
80
+ - '@tailwindcss/oxide-linux-x64-musl@4.3.4 || 4.3.5'
81
+ - '@tailwindcss/oxide-wasm32-wasi@4.3.4 || 4.3.5'
82
+ - '@tailwindcss/oxide-win32-arm64-msvc@4.3.4 || 4.3.5'
83
+ - '@tailwindcss/oxide-win32-x64-msvc@4.3.4 || 4.3.5'
84
+ - '@tailwindcss/oxide@4.3.4 || 4.3.5'
85
+ - '@tailwindcss/postcss@4.3.4 || 4.3.5'
86
+ - '@tailwindcss/vite@4.3.4 || 4.3.5'
74
87
  - '@vitejs/plugin-react@6.2.1'
75
88
  - '@vitest/mocker@5.1.0'
76
89
  - '@vitest/spy@5.1.0'
77
- - tailwindcss@4.3.4
90
+ - tailwindcss@4.3.4 || 4.3.5
78
91
  - vitest@5.1.0
79
- - '@wenathlan/saddle@2.0.154 || 2.0.155 || 2.0.156 || 2.1.8'
80
- - '@wenathlan/devthink@2.0.154 || 2.0.155 || 2.0.156 || 2.1.7'
81
- - '@wenathlan/argan@2.0.155'
82
92
  - gsap@3.15.1
93
+ - '@wenathlan/cobaia@2.0.166'
94
+ - '@wenathlan/forge@2.0.166'
95
+ - '@wenathlan/foundry@2.0.166'
96
+ - '@wenathlan/stealthhead@2.0.166'
97
+ - framer-motion@14.10.1
98
+ - motion-dom@14.10.1
99
+ - motion@14.10.1
100
+ - zustand@5.1.0
101
+ - '@ai-sdk/gateway@4.0.137'
102
+ - '@ai-sdk/openai-compatible@3.0.86'
103
+ - '@ai-sdk/openai@4.0.110'
104
+ - ai@7.0.164
83
105
  # the pnpm settings live here since pnpm 12 (the package.json pnpm field is
84
106
  # no longer read, and onlyBuiltDependencies died in pnpm 11 — the map below
85
107
  # is allowBuilds). THE 2.0.155 BUILDS GATE HEAL: the four-entry map froze
package/pom.xml CHANGED
@@ -1,18 +1,18 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3
3
  <modelVersion>4.0.0</modelVersion>
4
- <groupId>io.github.wenathlan</groupId>
4
+ <groupId>com.wenathlan</groupId>
5
5
  <artifactId>cobaia</artifactId>
6
6
  <version>${revision}</version>
7
7
  <packaging>jar</packaging>
8
8
  <name>cobaia single distribution</name>
9
- <description>The single cobaia maven distribution: the documentation set and the tests directory embedded as jar resources in the one io.github.wenathlan.cobaia artifact. cobaia is the monetization surface — the plans, the support rails and the honest advertisement slots.</description>
9
+ <description>The single cobaia maven distribution: the documentation set and the tests directory embedded as jar resources in the one com.wenathlan.cobaia artifact. cobaia is the monetization surface — the plans, the support rails and the honest advertisement slots.</description>
10
10
  <url>https://github.com/wenathlan/devthink</url>
11
11
  <licenses><license><name>GPL-3.0-only</name><url>https://www.gnu.org/licenses/gpl-3.0.html</url></license></licenses>
12
12
  <scm><url>https://github.com/wenathlan/devthink</url><connection>scm:git:https://github.com/wenathlan/devthink.git</connection></scm>
13
13
  <distributionManagement><repository><id>github</id><name>GitHub Packages</name><url>https://maven.pkg.github.com/wenathlan/devthink</url></repository></distributionManagement>
14
14
  <properties>
15
- <revision>2.0.165</revision>
15
+ <revision>2.0.167</revision>
16
16
  </properties>
17
17
  <build>
18
18
  <resources>
@@ -21,7 +21,7 @@
21
21
  <includes>
22
22
  <include>README.md</include>
23
23
  <include>docs/**</include>
24
- <include>tests/**</include>
24
+ <include>test/**</include>
25
25
  </includes>
26
26
  </resource>
27
27
  </resources>
package/routerbase.ts ADDED
@@ -0,0 +1 @@
1
+ export { routerBase } from "../devthink/routerbase.ts";
package/schema.prisma CHANGED
@@ -29,3 +29,26 @@ model OnboardingState {
29
29
  onboardingseen Boolean @default(false)
30
30
  updatedAt DateTime @updatedAt
31
31
  }
32
+
33
+ /// one interface copy row: the id is the page.widget.key path, the text
34
+ /// rides the english source of truth (labels.ts), and the translated flag
35
+ /// protects a runtime translation from the seed overwrite
36
+ model Label {
37
+ id String @id
38
+ text String
39
+ revision Int @default(0)
40
+ updatedAt DateTime @updatedAt
41
+ translated Boolean @default(false)
42
+ }
43
+
44
+ /// one content row of the site: the kind names the manifest table, the
45
+ /// ordinal pins the row order inside the kind, and the payload carries the
46
+ /// json encoding of the row (the platform gateway serves the rows over
47
+ /// GET /api/v1/ui/content — the content law: the pages render db rows,
48
+ /// the manifest is only the build seed)
49
+ model ContentRow {
50
+ @@id([kind, ordinal])
51
+ kind String
52
+ ordinal Int
53
+ payload String
54
+ }
package/sol/Sol.tsx CHANGED
@@ -9,15 +9,15 @@
9
9
  * this file follows the new name and App.tsx keeps importing the anchor by
10
10
  * the folder path.
11
11
  *
12
- * The cobaia is a CONTENT site (the monetization surface of the family): no
13
- * onboarding, no entry gate — every content route is a page of the site, and
14
- * the home renders directly on the bare root. The intro (/intro) is the one
15
- * public presentation page of the family grammar (every app carries one) and
16
- * mounts BARE — outside the window chrome, like the sibling sites mount
17
- * theirs; the shell's close-caption door relaunches the site at it. Every
18
- * content route renders as the page body of the ONE shared shell (the header
19
- * and the footer); every page is freely reachable, and the catch-all lands
20
- * on the 404 anchor.
12
+ * The cobaia is a CONTENT site (the monetization surface of the family)
13
+ * riding the standardized family entry flow: the intro (/intro) is the
14
+ * presentation splash and the onboarding (/onboarding) the first-run
15
+ * frames — both mount BARE, outside the site chrome, like the sibling apps
16
+ * mount theirs — and the home renders behind the entry gate (the entrada:
17
+ * the flow decides, a finished visitor sees the content). Every other
18
+ * content route renders as the page body of the ONE shared shell (the
19
+ * header and the footer); every page stays freely reachable, and the
20
+ * catch-all lands on the 404 anchor.
21
21
  */
22
22
  import type { ReactNode } from "react";
23
23
  import { Route, Switch } from "wouter";
@@ -25,9 +25,11 @@ import AppsAnchor from "./apps/apps";
25
25
  import BlogAnchor from "./blog/blog";
26
26
  import HomeAnchor from "./home/home";
27
27
  import IntroAnchor from "./intro/intro";
28
+ import OnboardingAnchor from "./onboarding/onboarding";
28
29
  import LegalAnchor from "./legal/legal";
29
30
  import NotFoundAnchor from "./notfound/notfound";
30
31
  import PricingAnchor from "./pricing/pricing";
32
+ import { EntryGate } from "./wingets";
31
33
  import { Shell } from "./shell/Shell";
32
34
  import SupportAnchor from "./support/support";
33
35
 
@@ -46,18 +48,21 @@ function Unrouted() {
46
48
  );
47
49
  }
48
50
 
49
- /** Mounts the theme surface through the page anchors: the bare intro first
50
- * (the public presentation, outside the chrome, like the siblings mount
51
- * theirs), then the content pages of the site — the home on the bare root,
52
- * the five public sections, and the catch-all last. No entry flow: the site
53
- * is content end to end. */
51
+ /** Mounts the theme surface through the page anchors: the entry flow first
52
+ * (the bare intro splash, then the bare onboarding frames — the family
53
+ * law), then the content pages of the site — the home gated behind the
54
+ * entrada on the bare root, the five public sections, and the catch-all
55
+ * last. */
54
56
  export default function Sol() {
55
57
  return (
56
58
  <Switch>
57
59
  <Route path="/intro" component={IntroAnchor} />
60
+ <Route path="/onboarding" component={OnboardingAnchor} />
58
61
  <Route path="/">
59
62
  <Content>
60
- <HomeAnchor />
63
+ <EntryGate>
64
+ <HomeAnchor />
65
+ </EntryGate>
61
66
  </Content>
62
67
  </Route>
63
68
  <Route path="/apps">
package/sol/apps/apps.tsx CHANGED
@@ -16,7 +16,7 @@ import type { CSSProperties } from "react";
16
16
  import { useContent } from "../../content.ts";
17
17
  import { familyurl } from "../../familyurl";
18
18
  import { label, labelfill } from "../../labels";
19
- import { roster } from "../../roster.ts";
19
+ import { roster } from "../../monetization.ts";
20
20
  import { AdSlot } from "../wingets";
21
21
 
22
22
  export function Apps() {
package/sol/blog/blog.tsx CHANGED
@@ -16,7 +16,7 @@
16
16
  import type { CSSProperties } from "react";
17
17
  import { useContent } from "../../content.ts";
18
18
  import { label, labelfill } from "../../labels";
19
- import { type blogpost, postindex, postwords } from "../../posts";
19
+ import { type blogpost, postindex, postwords } from "../../monetization";
20
20
  import { AdSlot } from "../wingets";
21
21
 
22
22
  /** the meta line of one post: the date and the honest word count. */
package/sol/home/home.tsx CHANGED
@@ -11,8 +11,9 @@
11
11
  * three how-it-works steps on hairline seams (01 engine · 02 static · 03
12
12
  * honest), a four-card preview of the directory leading to /apps, the
13
13
  * support strip, and the one reserved ad slot at the end of the content.
14
- * No entry gate and no first-run flow: cobaia is a content site — the home
15
- * renders directly, every page is public.
14
+ * The home rides the entrada: the entry gate consults the first-run flow
15
+ * (intro → onboarding) before the landing shows — the family law every
16
+ * sibling app already answers.
16
17
  * Styled entirely by sol/sol.css.
17
18
  */
18
19
  import type { CSSProperties } from "react";
@@ -20,8 +21,8 @@ import { Link } from "wouter";
20
21
  import { useContent } from "../../content.ts";
21
22
  import { familyurl } from "../../familyurl";
22
23
  import { type LabelKey, label, labelfill } from "../../labels";
23
- import { roster } from "../../roster.ts";
24
- import { AdSlot, BrandMark } from "../wingets";
24
+ import { roster } from "../../monetization.ts";
25
+ import { AdSlot, BrandMark, EntryGate } from "../wingets";
25
26
 
26
27
  /** the featured deploy units of the landing preview (the full directory
27
28
  * lives on /apps): the OS, the engine, the store and one studio — read
@@ -41,6 +42,15 @@ const STEPS: readonly { n: LabelKey; title: LabelKey; text: LabelKey }[] = [
41
42
  ];
42
43
 
43
44
  export function Home() {
45
+ return (
46
+ <EntryGate>
47
+ <HomeSurface />
48
+ </EntryGate>
49
+ );
50
+ }
51
+
52
+ /** the home surface the gate reveals: the landing itself. */
53
+ function HomeSurface() {
44
54
  useContent();
45
55
  return (
46
56
  <div className="cb-page">
@@ -15,9 +15,13 @@
15
15
  * zone renders the real offer from the one checkoutlinks table (the not
16
16
  * configured state stays honest), the doors panes open the app directory
17
17
  * and the legal page, and the footer carries the doors (the site row, the
18
- * family row, the legal line). The entrance rides the cb-in stagger under
19
- * the second, the layers ride the surface-ladder bands, the door panes ride
20
- * the silk law. Public-site rules: zero network calls from the page itself,
18
+ * family row, the legal line). The visit marks the intro stage seen
19
+ * through the shared onboardingstate accessor (the family entry-flow
20
+ * contract: intro → onboarding → entrada — the stage flag rides the
21
+ * platform db with the localStorage cache as the offline mirror). The
22
+ * entrance rides the cb-in stagger under the second, the layers ride the
23
+ * surface-ladder bands, the door panes ride the silk law. Public-site
24
+ * rules: zero network calls from the page itself beyond the stage flag,
21
25
  * React state only; zero decorative dots; zero invented numbers and zero
22
26
  * fake testimonials — the real offer, read from the data modules.
23
27
  */
@@ -26,6 +30,7 @@ import type { CSSProperties } from "react";
26
30
  import { useCallback, useEffect, useState } from "react";
27
31
  import { Link } from "wouter";
28
32
  import { label } from "../../labels";
33
+ import { markOnboardingStage } from "../../onboardingstate.ts";
29
34
  import { BrandMark, IntroFooter, IntroPlans, IntroSplash } from "../wingets";
30
35
 
31
36
  /** the lifecycle of the opening act: resolving (the motion preference
@@ -59,9 +64,12 @@ function DoorGlyph() {
59
64
  export function Intro() {
60
65
  const [splash, setSplash] = useState<SplashPhase>("resolving");
61
66
 
62
- // the splash decision rides the motion preference alone — nothing is read
63
- // from the visitor machine and nothing is written back (the no storage law)
67
+ // the splash decision rides the motion preference, and the visit marks
68
+ // the intro stage seen through the shared accessor (the family flow
69
+ // contract: the db holds the flag, the cache mirrors it offline — the
70
+ // splash never replays for a visitor that finished the flow)
64
71
  useEffect(() => {
72
+ void markOnboardingStage("intro");
65
73
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
66
74
  setSplash("done");
67
75
  return;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * onboarding.tsx — the first-run steps of the ENTRY FLOW (the flow contract
3
+ * of the reform the family rides: intro → onboarding → entrada). Three
4
+ * short frames on the site voice over the deep canvas — what the site IS
5
+ * (the honest role: the family's monetization face, kept apart so the apps
6
+ * stay clean), what it DOES (the real offer read from the one content
7
+ * lane: the plans, the support rails, the honest ad slots) and the ENTER
8
+ * frame with the hand-over button. The finish AND the skip mark the
9
+ * onboarding stage seen through the shared onboardingstate accessor
10
+ * (DB-backed, cache-mirrored — seeing once is enough; the flow never
11
+ * replays) and navigate to "/", where the entry gate now lets the site
12
+ * take over. The copy rides the label store; no icon dependency (the site
13
+ * grammar draws its own glyphs); reduced motion rides the stylesheet hard
14
+ * stop. No opt-in switches here: the site holds no account and no visitor
15
+ * storage beyond the first-run stage itself.
16
+ */
17
+
18
+ import { useState } from "react";
19
+ import { useLocation } from "wouter";
20
+ import { type LabelKey, label } from "../../labels";
21
+ import { markOnboardingStage } from "../../onboardingstate.ts";
22
+ import { BrandMark } from "../wingets";
23
+
24
+ /** the three frames of the first run, in order — the copy rides the store
25
+ * (the id keeps the ladder segments keyed, the key answers the eyebrow). */
26
+ const STEPS: readonly { id: string; eyebrowKey: LabelKey }[] = [
27
+ { id: "is", eyebrowKey: "onboarding.steps.is.eyebrow" },
28
+ { id: "does", eyebrowKey: "onboarding.steps.does.eyebrow" },
29
+ { id: "enter", eyebrowKey: "onboarding.steps.enter.eyebrow" },
30
+ ];
31
+
32
+ /** the does-frame rows: the real offer the site carries, one label each
33
+ * (the plans, the support rails, the honest ad slots — nothing invented). */
34
+ const FACTS: readonly LabelKey[] = ["onboarding.facts.plans", "onboarding.facts.support", "onboarding.facts.ads"];
35
+
36
+ /** the chevron glyph of the pager actions: the inline 10×10 chevron the
37
+ * site grammar draws (no icon dependency rides the content site). */
38
+ function StepGlyph({ back }: { back?: boolean }) {
39
+ return (
40
+ <svg
41
+ className="cb-onboard__glyph"
42
+ width={10}
43
+ height={10}
44
+ viewBox="0 0 10 10"
45
+ aria-hidden="true"
46
+ focusable="false"
47
+ style={back ? { transform: "rotate(180deg)" } : undefined}
48
+ >
49
+ <path d="M3 0l5 5-5 5" fill="none" stroke="currentColor" strokeWidth="1.5" />
50
+ </svg>
51
+ );
52
+ }
53
+
54
+ /**
55
+ * The onboarding page: a three-step first-run dialog that hands the
56
+ * surface to the home of the site. Finish and skip both mark the
57
+ * onboarding stage seen and navigate to the gated home.
58
+ *
59
+ * @returns the onboarding element.
60
+ */
61
+ export default function Onboarding() {
62
+ const [, navigate] = useLocation();
63
+ const [step, setStep] = useState(0);
64
+ const last = step === STEPS.length - 1;
65
+
66
+ /** marks the stage seen and hands the surface to the home (the entry gate lets it through). */
67
+ const finish = (): void => {
68
+ void markOnboardingStage("onboarding").then(() => navigate("/"));
69
+ };
70
+
71
+ return (
72
+ <div className="cb-onboard" data-page="onboarding">
73
+ <section className="cb-onboard__card" aria-label={label("onboarding.aria")}>
74
+ <header className="cb-onboard__head">
75
+ <BrandMark size={18} />
76
+ <span className="cb-onboard__title">{label("onboarding.title")}</span>
77
+ </header>
78
+
79
+ <div className="cb-onboard__body">
80
+ {step === 0 && (
81
+ <article className="cb-onboard__step" key="is">
82
+ <p className="cb-eyebrow">{label(STEPS[0].eyebrowKey)}</p>
83
+ <h2 className="cb-onboard__lead">{label("onboarding.steps.is.title")}</h2>
84
+ <p className="cb-onboard__lede">{label("onboarding.steps.is.text")}</p>
85
+ </article>
86
+ )}
87
+ {step === 1 && (
88
+ <article className="cb-onboard__step" key="does">
89
+ <p className="cb-eyebrow">{label(STEPS[1].eyebrowKey)}</p>
90
+ <h2 className="cb-onboard__lead">{label("onboarding.steps.does.title")}</h2>
91
+ <p className="cb-onboard__lede">{label("onboarding.steps.does.text")}</p>
92
+ <ul className="cb-onboard__facts">
93
+ {FACTS.map((key) => (
94
+ <li key={key} className="cb-onboard__fact">
95
+ {label(key)}
96
+ </li>
97
+ ))}
98
+ </ul>
99
+ </article>
100
+ )}
101
+ {step === 2 && (
102
+ <article className="cb-onboard__step" key="enter">
103
+ <p className="cb-eyebrow">{label(STEPS[2].eyebrowKey)}</p>
104
+ <h2 className="cb-onboard__lead">{label("onboarding.steps.enter.title")}</h2>
105
+ <p className="cb-onboard__lede">{label("onboarding.steps.enter.text")}</p>
106
+ </article>
107
+ )}
108
+ </div>
109
+
110
+ <footer className="cb-onboard__foot">
111
+ <div className="cb-onboard__ladder" aria-hidden="true">
112
+ {STEPS.map((frame) => (
113
+ <span
114
+ key={frame.id}
115
+ className="cb-onboard__segrun"
116
+ data-active={STEPS[step].id === frame.id ? "true" : undefined}
117
+ />
118
+ ))}
119
+ </div>
120
+ <div className="cb-onboard__actions">
121
+ <button type="button" className="cb-onboard__skip" onClick={finish}>
122
+ {label("onboarding.skip")}
123
+ </button>
124
+ {step > 0 && (
125
+ <button type="button" className="cb-btn cb-btn--ghost" onClick={() => setStep((value) => value - 1)}>
126
+ <StepGlyph back />
127
+ {label("onboarding.back")}
128
+ </button>
129
+ )}
130
+ {last ? (
131
+ <button type="button" className="cb-btn" onClick={finish}>
132
+ {label("onboarding.enterCta")}
133
+ <StepGlyph />
134
+ </button>
135
+ ) : (
136
+ <button type="button" className="cb-btn" onClick={() => setStep((value) => value + 1)}>
137
+ {label("onboarding.next")}
138
+ <StepGlyph />
139
+ </button>
140
+ )}
141
+ </div>
142
+ </footer>
143
+ </section>
144
+ </div>
145
+ );
146
+ }
@@ -16,7 +16,7 @@
16
16
  * are plain hrefs the visitor chooses to follow.
17
17
  */
18
18
  import type { CSSProperties } from "react";
19
- import { checkoutlinks, type checkoutplan, isconfigured } from "../../checkoutlinks";
19
+ import { checkoutlinks, type checkoutplan, isconfigured } from "../../monetization";
20
20
  import { useContent } from "../../content.ts";
21
21
  import { label, labelfill } from "../../labels";
22
22
 
@@ -26,7 +26,7 @@ import { Link, useLocation } from "wouter";
26
26
  import { useContent } from "../../content.ts";
27
27
  import { familyurl } from "../../familyurl";
28
28
  import { type LabelKey, label } from "../../labels";
29
- import { roster } from "../../roster.ts";
29
+ import { roster } from "../../monetization.ts";
30
30
  import { BrandMark } from "../wingets";
31
31
 
32
32
  /** the section links of the top bar, in reading order (home is the mark).