@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.
- package/.dockerignore +1 -1
- package/App.tsx +2 -31
- package/Dockerfile +162 -4
- package/biome.jsonc +3 -3
- package/build.gradle.kts +4 -4
- package/bun.lock +1820 -487
- package/cobaia.csproj +2 -2
- package/cobaia.gemspec +2 -2
- package/cobaia.java +2 -2
- package/db.ts +87 -3
- package/docker-compose.yml +58 -0
- package/docker.config +74 -0
- package/docs/cobaia.md +31 -0
- package/docs/distributionlanes.md +1 -1
- package/keywords.txt +1 -0
- package/labels.ts +23 -1
- package/monetization.ts +248 -0
- package/nginx.conf +106 -0
- package/onboardingstate.ts +211 -0
- package/package.json +430 -83
- package/pnpm-workspace.yaml +60 -38
- package/pom.xml +4 -4
- package/routerbase.ts +1 -0
- package/schema.prisma +23 -0
- package/sol/Sol.tsx +20 -15
- package/sol/apps/apps.tsx +1 -1
- package/sol/blog/blog.tsx +1 -1
- package/sol/home/home.tsx +14 -4
- package/sol/intro/intro.tsx +13 -5
- package/sol/onboarding/onboarding.tsx +146 -0
- package/sol/pricing/pricing.tsx +1 -1
- package/sol/shell/Shell.tsx +1 -1
- package/sol/sol.css +153 -0
- package/sol/support/support.tsx +1 -1
- package/sol/wingets/HomeEntrygate.tsx +64 -0
- package/sol/wingets/IntroFooter.tsx +1 -1
- package/sol/wingets/IntroPlans.tsx +1 -1
- package/sol/wingets/ShellAdsslot.tsx +1 -1
- package/sol/wingets/index.ts +3 -1
- package/tauri.conf.json +1 -1
- package/{tests → test}/adsconfig.test.ts +2 -2
- package/{tests → test}/checkoutlinks.test.ts +2 -2
- package/test/entryflow.test.ts +124 -0
- package/{tests → test}/familyurl.test.ts +1 -1
- package/{tests → test}/posts.test.ts +2 -2
- package/{tests → test}/sandboxmodality.test.ts +1 -1
- package/vitest.config.ts +1 -1
- package/adsconfig.ts +0 -59
- package/checkoutlinks.ts +0 -80
- package/posts.ts +0 -54
- package/roster.ts +0 -29
package/pnpm-workspace.yaml
CHANGED
|
@@ -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
|
-
- '@
|
|
56
|
-
- '@
|
|
57
|
-
- '@
|
|
58
|
-
- '@tailwindcss/
|
|
59
|
-
- '@tailwindcss/oxide-
|
|
60
|
-
- '@tailwindcss/oxide-
|
|
61
|
-
- '@tailwindcss/oxide-
|
|
62
|
-
- '@tailwindcss/oxide-
|
|
63
|
-
- '@tailwindcss/oxide-linux-
|
|
64
|
-
- '@tailwindcss/oxide-linux-
|
|
65
|
-
- '@tailwindcss/oxide-
|
|
66
|
-
- '@tailwindcss/oxide-
|
|
67
|
-
- '@tailwindcss/oxide-
|
|
68
|
-
- '@tailwindcss/oxide
|
|
69
|
-
- '@tailwindcss/
|
|
70
|
-
- '@tailwindcss/
|
|
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>
|
|
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
|
|
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.
|
|
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>
|
|
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)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* content route renders as the page body of the ONE shared shell (the
|
|
19
|
-
* and the footer); every page
|
|
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
|
|
50
|
-
* (the
|
|
51
|
-
*
|
|
52
|
-
* the five public sections, and the catch-all
|
|
53
|
-
*
|
|
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
|
-
<
|
|
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 "../../
|
|
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 "../../
|
|
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
|
-
*
|
|
15
|
-
*
|
|
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 "../../
|
|
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">
|
package/sol/intro/intro.tsx
CHANGED
|
@@ -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
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
|
63
|
-
//
|
|
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
|
+
}
|
package/sol/pricing/pricing.tsx
CHANGED
|
@@ -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 "../../
|
|
19
|
+
import { checkoutlinks, type checkoutplan, isconfigured } from "../../monetization";
|
|
20
20
|
import { useContent } from "../../content.ts";
|
|
21
21
|
import { label, labelfill } from "../../labels";
|
|
22
22
|
|
package/sol/shell/Shell.tsx
CHANGED
|
@@ -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 "../../
|
|
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).
|