Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
50ff03c
Align the site with the LeanCode palette, from one source
claude Sep 9, 2026
9bd3648
Landing: move the stylesheet into @css getters next to the components
claude Sep 9, 2026
16a9045
Merge origin/main into claude/lncd-brand-palette
claude Sep 9, 2026
87dde22
Generate the whole palette into Dart, as ciach does
claude Sep 9, 2026
9c099c9
Write the landing page's theme from the Dart palette
claude Sep 9, 2026
d1c5acc
Merge origin/claude/busy-bell-xjh9eo into claude/lncd-brand-palette
claude Sep 9, 2026
8d4cd30
Landing: derive the token classes from a list of names
claude Sep 9, 2026
cb6d87e
Build the TS themes from the swatches; drop the ciach mentions
claude Sep 9, 2026
d55102a
Give every landing class one owner
claude Sep 10, 2026
fee6bbb
Check that every landing class name has one owner
claude Sep 10, 2026
9ddf572
Revert "Check that every landing class name has one owner"
claude Sep 10, 2026
d804a71
Scope the landing's class names to their component
claude Sep 10, 2026
35830c7
One class scope per component
claude Sep 10, 2026
e5b1952
landing: move class scoping to jaspr_class_scope
claude Sep 11, 2026
56a287c
landing: take the graph-wide scope check
claude Sep 11, 2026
ce3a8ce
landing: take the formatter marker in the part files
claude Sep 11, 2026
b537458
landing: follow the scope constant's rename
claude Sep 14, 2026
df7d228
landing: take the resolved annotation check
claude Sep 14, 2026
9987735
landing: take the builder's review pass
claude Sep 14, 2026
d06bd19
landing: take jaspr_class_scope from pub.dev
claude Sep 15, 2026
011c57f
landing: trim the README's class-scoping paragraph
claude Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ jobs:
- name: Check the example manifest
run: npm run examples:check

# Fails when a generated copy of the palette (the landing page's and the
# docs' tokens, the social cards, the Flutter demos, the logos) no longer
# matches palette.json; `npm run palette:generate` rewrites them.
- name: Check the palette
run: npm run palette:check

- name: Generate the example sources
run: npm run examples:generate

Expand All @@ -128,6 +134,14 @@ jobs:
run: dart pub get
working-directory: landing

# The components declare part files holding their class-name scopes, and
# main.server.dart imports the options jaspr_builder writes from the
# `@css` getters. Both are gitignored, so both have to exist before
# analysis.
- name: Generate the landing page's part files
run: dart run build_runner build --delete-conflicting-outputs
working-directory: landing

- name: Analyze the landing page
run: dart analyze --fatal-infos
working-directory: landing
Expand Down
3 changes: 3 additions & 0 deletions docs_app/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ flutter/
# Build output and codegen.
public/flutter-examples/
lib/flutter-examples/manifest.generated.ts
# The palette, written by `npm run palette:generate` from palette.json.
app/af-tokens.css
lib/palette.generated.ts
# The landing page, built by Jaspr in ../landing and copied here.
public/index.html
public/landing.*
Expand Down
51 changes: 28 additions & 23 deletions docs_app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ AI-Provenance:
# advanced-forms-docs

The documentation site for `advanced_forms`, under `/docs`: built with [Fumadocs](https://fumadocs.dev) on Next.js,
styled after the LeanCode design system used on [ciach.leancode.co](https://ciach.leancode.co), and running its own code
examples in Flutter, in the browser. The landing page at `/` is a separate static site written in
[Jaspr](https://jaspr.site), in the repo-root `landing/` folder; this app serves its build output from `public/`, so the
two ship as one deployment on one domain.
styled after the LeanCode design system, and running its own code examples in Flutter, in the browser. The landing page
at `/` is a separate static site written in [Jaspr](https://jaspr.site), in the repo-root `landing/` folder; this app
serves its build output from `public/`, so the two ship as one deployment on one domain.

MDX for the docs lives in the repo-root `docs/` folder; the landing page's live demos live in `content/landing/`. From
this directory:
Expand All @@ -25,17 +24,18 @@ changes; `npm run dev` without it serves the docs and 404s on `/`.

## Layout

| Path | What lives there |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `../docs/*.mdx`, `../docs/meta.json` | The documentation pages and their order. Section separators are `---Name---` entries in `meta.json`. |
| `content/landing/*.mdx` | The landing page's live demos: the Dart the Jaspr site shows and compiles, written like any docs example. |
| `../landing/` | The landing page, a Jaspr static site. `scripts/landing.mjs` builds it and copies the result into `public/`. |
| `app/docs/` | The notebook layout and the page renderer for `/docs/*`. |
| `app/global.css` | The design system: tokens (`--af-*`), the Fumadocs variables they map onto, the example frame. |
| `lib/shared.ts` | Routes, external URLs, site copy. |
| `lib/layout.shared.tsx` | The header: logo (`components/nav-title.tsx`, a plain link to the landing page), links, GitHub. |
| `components/mdx.tsx` | The components MDX can use: Fumadocs' set, plus `Tabs`, `Steps`, `Accordions`, `TypeTable` and `AdvancedFormsExample`. |
| `app/og/` | Branded Open Graph images, one per docs page. |
| Path | What lives there |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `../docs/*.mdx`, `../docs/meta.json` | The documentation pages and their order. Section separators are `---Name---` entries in `meta.json`. |
| `content/landing/*.mdx` | The landing page's live demos: the Dart the Jaspr site shows and compiles, written like any docs example. |
| `../landing/` | The landing page, a Jaspr static site. `scripts/landing.mjs` builds it and copies the result into `public/`. |
| `app/docs/` | The notebook layout and the page renderer for `/docs/*`. |
| `palette.json`, `scripts/palette.mjs` | The palette — the LeanCode swatches and the light and dark `--af-*` tokens — and the generator that writes every copy of it. |
| `app/global.css` | The stylesheet: the Fumadocs variables mapped onto the `--af-*` tokens (generated into `app/af-tokens.css`), the example frame. |
| `lib/shared.ts` | Routes, external URLs, site copy. |
| `lib/layout.shared.tsx` | The header: logo (`components/nav-title.tsx`, a plain link to the landing page), links, GitHub. |
| `components/mdx.tsx` | The components MDX can use: Fumadocs' set, plus `Tabs`, `Steps`, `Accordions`, `TypeTable` and `AdvancedFormsExample`. |
| `app/og/` | Branded Open Graph images, one per docs page. |

## Live Flutter examples

Expand Down Expand Up @@ -134,14 +134,19 @@ debug asset server sends no CORS headers, unlike the release one.

## Design

The stylesheet is hand-written on top of the Fumadocs preset. Tokens are CSS custom properties prefixed `--af-` at the
top of `app/global.css` — the ciach palette: `#050505` ground, `#101013` surfaces, `#edff2f` accent, Space Grotesk and
JetBrains Mono — and the `--color-fd-*` variables Fumadocs paints with are re-pointed at them. Dark is the design and
the default; light is a paper variant where the accent is a fill with dark ink on it, the way leancode.co does it.

Code blocks and live examples share one window frame: a title bar with three dots (the first one lime), a mono title,
and the content below. The landing page in `../landing/web/landing.css` uses the same tokens and the same frame, so the
homepage and the docs read as one site — change a colour in one place and mirror it in the other.
The colors are the LeanCode design system — black ground, warm surfaces, one CTA yellow `#f0ff00` — and live in one
place, `palette.json`: the swatches as the design system names them, and the light and dark themes as `--af-*` tokens
built from them. `npm run palette:generate` writes every copy a consumer needs (`app/af-tokens.css` for this stylesheet,
`lib/palette.generated.ts` for the social cards and the logo, `flutter/lib/support/palette.generated.dart` for the live
demos, `../landing/lib/palette.generated.dart` for the landing page, which writes its own `--af-*` variables from it)
and recolors the shapes marked `data-palette` in the logo and icon SVGs; `npm run palette:check` fails CI when they
drift. The stylesheet is hand-written on top of the Fumadocs preset, with the `--color-fd-*` variables Fumadocs paints
with pointed at the tokens. Space Grotesk and JetBrains Mono. Dark is the design and the default; light is a paper
variant where the accent is a fill with black ink on it, the way leancode.co does it.

Code blocks and live examples share one window frame: a title bar with three dots (the first one yellow), a mono title,
and the content below. The landing page (`../landing/lib/styles.dart` writes the tokens from the generated palette, the
components style the frame) uses the same tokens and the same frame, so the homepage and the docs read as one site.

## Deployment

Expand Down
82 changes: 82 additions & 0 deletions docs_app/app/af-tokens.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* Generated by docs_app/scripts/palette.mjs from docs_app/palette.json — do not edit. Run `npm run palette:generate` in docs_app after changing the palette. */

:root {
/* Page background. */
--af-bg: #ffffff;

/* Section background: footer, code panels. */
--af-bg-2: #f7f7f5;

/* Cards and frames. */
--af-surface: #ffffff;

/* Bars and chips on a surface. */
--af-surface-2: #f2f2ef;

/* The hairline. */
--af-border: #e5e5e5;

/* The stronger hairline, for controls. */
--af-border-2: #cfcfcb;

/* Headings and primary text. */
--af-text: #000000;

/* Body copy. */
--af-text-2: #3f3f3c;

/* Captions and metadata. */
--af-muted: #6f6f6f;

/* The CTA yellow, as a fill. */
--af-accent: #f0ff00;

/* Text on the accent. */
--af-accent-ink: #000000;

/* The accent as text. */
--af-accent-text: #606a00;

/* The primary button's hover fill. */
--af-accent-hover: #f5ff4d;

/* The accent as a wash behind icons, pills and glows. */
--af-accent-soft: rgba(240, 255, 0, 0.45);

/* The docs' primary action fill. */
--af-primary: #000000;

/* Text on the primary fill. */
--af-primary-ink: #f0ff00;

/* The scrim behind dialogs. */
--af-overlay: rgba(0, 0, 0, 0.3);

/* Error signal. */
--af-danger: #be2119;

/* Success signal. */
--af-ok: #527b28;
}

.dark {
--af-bg: #000000;
--af-bg-2: #0a0a08;
--af-surface: #151513;
--af-surface-2: #1d1d1a;
--af-border: rgba(255, 255, 255, 0.12);
--af-border-2: rgba(255, 255, 255, 0.2);
--af-text: #ffffff;
--af-text-2: #d8d8d4;
--af-muted: #a3a3a0;
--af-accent: #f0ff00;
--af-accent-ink: #000000;
--af-accent-text: #f0ff00;
--af-accent-hover: #f5ff4d;
--af-accent-soft: rgba(240, 255, 0, 0.06);
--af-primary: #f0ff00;
--af-primary-ink: #000000;
--af-overlay: rgba(0, 0, 0, 0.6);
--af-danger: #e64239;
--af-ok: #80c340;
}
128 changes: 39 additions & 89 deletions docs_app/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,20 @@
* model: Claude Fable 5.1
* harness: Claude Code
*
* The LeanCode design system as used on ciach.leancode.co — near-black
* surfaces, one lime accent, Space Grotesk and JetBrains Mono — mapped onto
* the variables Fumadocs paints with. Dark is the design; light is a paper
* variant that keeps the same accent as a fill (black ink on lime), the way
* leancode.co does it.
* The LeanCode design system — black surfaces, one CTA yellow, Space Grotesk
* and JetBrains Mono — mapped onto the variables Fumadocs paints with. Dark is
* the design; light is a paper variant that keeps the same accent as a fill
* (black ink on yellow), the way leancode.co does it. The colors themselves
* are the `--af-*` variables of af-tokens.css, generated from ../palette.json
* and shared with the landing page; this file holds everything else.
*/
@import "tailwindcss";
@import "fumadocs-ui/css/preset.css";
@import "./af-tokens.css";

/* ---------- Tokens ---------- */

:root {
--af-bg: #f6f6f2;
--af-bg-2: #ffffff;
--af-surface: #ffffff;
--af-surface-2: #efefe9;
--af-border: #e2e2dc;
--af-border-2: #cfcfc7;
--af-text: #0b0b0d;
--af-text-2: #4b4b52;
--af-muted: #7d7d84;
--af-accent: #edff2f;
--af-accent-2: #c9dc00;
--af-accent-ink: #0b0c00;
/* Accent used *as text* on a light ground has to be darker to read. */
--af-accent-text: #5f6e00;
--af-accent-soft: rgba(237, 255, 47, 0.45);
--af-danger: #d93636;
--af-ok: #1f9d55;
--af-shadow: 0 24px 60px -32px rgba(0, 0, 0, 0.25);
--af-radius: 14px;
--af-radius-sm: 8px;
Expand All @@ -40,78 +25,43 @@
}

.dark {
--af-bg: #050505;
--af-bg-2: #0b0b0d;
--af-surface: #101013;
--af-surface-2: #16161b;
--af-border: #23232b;
--af-border-2: #33333e;
--af-text: #f4f4f1;
--af-text-2: #b7b7b3;
--af-muted: #7d7d84;
--af-accent: #edff2f;
--af-accent-2: #c9dc00;
--af-accent-ink: #0b0c00;
--af-accent-text: #edff2f;
--af-accent-soft: rgba(237, 255, 47, 0.12);
--af-danger: #ff5d5d;
--af-ok: #58e08a;
--af-shadow: 0 24px 60px -32px rgba(0, 0, 0, 0.9);
}

/* Fumadocs paints with these. Light values in @theme so Tailwind emits the
utilities; the dark block re-points the same variables. */
@theme {
--color-fd-background: #f6f6f2;
--color-fd-foreground: #0b0b0d;
--color-fd-muted: #efefe9;
--color-fd-muted-foreground: #55555c;
--color-fd-popover: #ffffff;
--color-fd-popover-foreground: #0b0b0d;
--color-fd-card: #ffffff;
--color-fd-card-foreground: #0b0b0d;
--color-fd-border: #e2e2dc;
--color-fd-primary: #0b0b0d;
--color-fd-primary-foreground: #edff2f;
--color-fd-secondary: #efefe9;
--color-fd-secondary-foreground: #0b0b0d;
--color-fd-accent: rgba(11, 11, 13, 0.06);
--color-fd-accent-foreground: #0b0b0d;
--color-fd-ring: #0b0b0d;
--color-fd-overlay: rgba(0, 0, 0, 0.3);
}

.dark {
--color-fd-background: #050505;
--color-fd-foreground: #f4f4f1;
--color-fd-muted: #16161b;
--color-fd-muted-foreground: #a8a8a4;
--color-fd-popover: #101013;
--color-fd-popover-foreground: #f4f4f1;
--color-fd-card: #101013;
--color-fd-card-foreground: #f4f4f1;
--color-fd-border: #23232b;
--color-fd-primary: #edff2f;
--color-fd-primary-foreground: #0b0c00;
--color-fd-secondary: #16161b;
--color-fd-secondary-foreground: #f4f4f1;
--color-fd-accent: rgba(255, 255, 255, 0.06);
--color-fd-accent-foreground: #f4f4f1;
--color-fd-ring: #edff2f;
--color-fd-overlay: rgba(0, 0, 0, 0.6);
}

@theme static {
/* Fumadocs paints with these. Inline, so the utilities point at the `--af-*`
variables and follow the theme with them; no dark block needed. */
@theme inline {
--color-fd-background: var(--af-bg);
--color-fd-foreground: var(--af-text);
--color-fd-muted: var(--af-surface-2);
--color-fd-muted-foreground: var(--af-muted);
--color-fd-popover: var(--af-surface);
--color-fd-popover-foreground: var(--af-text);
--color-fd-card: var(--af-surface);
--color-fd-card-foreground: var(--af-text);
--color-fd-border: var(--af-border);
--color-fd-primary: var(--af-primary);
--color-fd-primary-foreground: var(--af-primary-ink);
--color-fd-secondary: var(--af-surface-2);
--color-fd-secondary-foreground: var(--af-text);
--color-fd-accent: color-mix(in srgb, var(--af-text) 6%, transparent);
--color-fd-accent-foreground: var(--af-text);
--color-fd-ring: var(--af-primary);
--color-fd-overlay: var(--af-overlay);
}

@theme inline static {
/* Info and warning are the code palette's blue and amber, not brand colors. */
--color-fd-info: #82aaff;
--color-fd-warning: #ffcb6b;
--color-fd-error: #ff5d5d;
--color-fd-success: #58e08a;
--color-fd-idea: #edff2f;
--color-fd-error: var(--af-danger);
--color-fd-success: var(--af-ok);
--color-fd-idea: var(--af-accent);
/* Diff markers in code blocks; the preset's Shiki styles apply these. */
--color-fd-diff-remove: rgba(255, 93, 93, 0.12);
--color-fd-diff-remove-symbol: #ff5d5d;
--color-fd-diff-add: rgba(88, 224, 138, 0.12);
--color-fd-diff-add-symbol: #58e08a;
--color-fd-diff-remove: color-mix(in srgb, var(--af-danger) 12%, transparent);
--color-fd-diff-remove-symbol: var(--af-danger);
--color-fd-diff-add: color-mix(in srgb, var(--af-ok) 12%, transparent);
--color-fd-diff-add-symbol: var(--af-ok);
}

@theme inline {
Expand Down Expand Up @@ -247,7 +197,7 @@ kbd {
content: none;
}

/* ---------- Code blocks: the ciach window frame ---------- */
/* ---------- Code blocks: the window frame ---------- */

.prose figure.shiki,
.af-landing figure.shiki {
Expand Down
4 changes: 2 additions & 2 deletions docs_app/app/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 3 additions & 7 deletions docs_app/app/og/[...slug]/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@ import { getPageImageUrl, source } from "@/lib/source"
import { notFound } from "next/navigation"
import { ImageResponse } from "next/og"
import { LogoMark } from "@/components/logo"
import { dark, withAlpha } from "@/lib/palette"
import { appName } from "@/lib/shared"

export const revalidate = false

const ink = "#050505"
const surface = "#101013"
const border = "#23232b"
const text = "#f4f4f1"
const muted = "#b7b7b3"
const accent = "#edff2f"
const { bg: ink, surface, border, text, muted, accent } = dark

export async function GET(_req: Request, { params }: RouteContext<"/og/[...slug]">) {
const { slug } = await params
Expand All @@ -32,7 +28,7 @@ export async function GET(_req: Request, { params }: RouteContext<"/og/[...slug]
flexDirection: "column",
justifyContent: "space-between",
padding: 64,
background: `radial-gradient(60% 50% at 85% 10%, rgba(237, 255, 47, 0.18), transparent 65%), ${ink}`,
background: `radial-gradient(60% 50% at 85% 10%, ${withAlpha(accent, 0.18)}, transparent 65%), ${ink}`,
color: text,
fontFamily: "sans-serif",
}}>
Expand Down
Loading