Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions docs/add-a-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

How a Project gets into the Index and gets its Project page. Each step ends when its check holds; the next step uses what the previous one left. Fields are those of [content-model.md](content-model.md), and the result is a new entry in [content.md](content.md), with the same stored fields in `src/content/projects/<name>.yaml`, which the site is built from.

1. **It belongs.** It is Variable Lab's own, it is published, and it isn't a fork. Today portless (a fork) and gh-actions (not a Project) stay out, and nothing private gets in until it is published.
Check: it has a published Package and a public Repository.
1. **It belongs.** It is Variable Lab's own, it is published, and it isn't a fork. Even then, the Keeper keeps only the most relevant Projects in the Index. Today portless (a fork), gh-actions (not a Project) and theme-toggle (not among the most relevant) stay out, and nothing private gets in until it is published.
Check: it has a published Package and a public Repository, and the Keeper approves it for the Index.
2. **Its Class.** `class`, by the rule in [content-model.md](content-model.md#project).
Check: one Class, which matches how its README uses it.
3. **Its facts.** `package`, `executable` (Apps only), `installCommand`, `requirement` and/or `runtimes`, `repository` and `links`.
Expand Down
8 changes: 4 additions & 4 deletions docs/content-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ One. The page at `/`.
| intro | stored | The first sentence. It names apps before libraries, as the sections run. |
| unpublishedLine | stored | The Unpublished line. |
| keeper | stored | One Keeper. |
| projects | stored | The Projects in order. The page groups them by Class, Apps first; within a Class they keep this order. A new Project goes last in its Class unless the Keeper places it. |
| projects | stored | The Projects in order: only the most relevant, as [add-a-project.md](add-a-project.md) says. The page groups them by Class, Apps first; within a Class they keep this order. A new Project goes last in its Class unless the Keeper places it. |
| description | stored | The head description. |
| address | derived | `lab.variable.land`. |
| title | derived | `Variable Lab`. |
Expand All @@ -39,7 +39,7 @@ One per Project in the Index. Every Project has a Project page at `lab.variable.
| name | stored | Lowercase kebab-case, never `og`, which holds the `og:image`s. It is the title and the last segment of the Address. | The Project's own name, never its Executable or Package. |
| class | stored | `App` or `Library`. | App if it is run (it has an Executable); Library if it is imported. |
| sign | stored, optional | The Sign: one path on a 24×24 viewBox, filled with `fill-rule="evenodd"`. Without one, the page shows the Class stamp. | Copied from the Project's repository, `brand/sign.svg`. Drawing one follows the `vlab-brand` skill. |
| tagline | stored | One sentence, 34–41 characters, ending with a period. It fits one line on the Project page at desktop and tablet widths. | Says what the Project is or guarantees. Example: `The theme is set before first paint.` |
| tagline | stored | One sentence, 34–41 characters, ending with a period. It fits one line on the Project page at desktop and tablet widths. | Says what the Project is or guarantees. Example: `Only declared variables, checked at boot.` |
| body | stored | One to three sentences. | Facts from the README: what it does and how. It follows the Tagline without repeating it. |
| installCommand | stored | One Command. | The one the Project's README recommends, as written: `pnpm add` for a Library, `pnpm add -g` or `pnpm add -D` for an App, by where it gets installed. |
| executable | stored, Apps only | The Executable. | As its `package.json` `bin` names it. |
Expand All @@ -62,7 +62,7 @@ A Project has a requirement, runtimes, or both.

### Closing line

It says what the Project leaves out, as the skill's `voice.md` describes, and like every claim it is checked against the code. The pattern is "No [something concrete]. [A fact]." Example: `No CSS to import. No provider to wrap.`
It says what the Project leaves out, as the skill's `voice.md` describes, and like every claim it is checked against the code. The pattern is "No [something concrete]. [A fact]." Example: `No upgrade command. After init, the project is yours.`

Before it ships:

Expand All @@ -87,7 +87,7 @@ One per Project: one or more Snippets, in order.

| Field | Value |
| --- | --- |
| file | The file name, kebab-case: `env.ts`, `theme-switch.tsx`. |
| file | The file name, kebab-case: `env.ts`. |
| placement | Optional. Where the code goes when that matters, shown after the name: `index.html · <head>`. Placement is said here, not with more code. |
| language | The language label: `TypeScript`, `TSX`, `HTML`. |
| code | The lines. They are numbered on the page. |
Expand Down
43 changes: 3 additions & 40 deletions docs/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ The approved content of the site, field by field as [content-model.md](content-m
| --- | --- |
| intro | `A public index of apps and libraries.` |
| unpublishedLine | `Variable Land also builds software that isn't published here.` |
| projects | run-run, vland, env, theme-toggle |
| projects | run-run, vland, env |
| description | `A public index of apps and libraries. Variable Land also builds software that isn't published here.` |

theme-toggle is published but stays out of `projects`, as [add-a-project.md](add-a-project.md) says.

Keeper:

| Field | Value |
Expand Down Expand Up @@ -110,42 +112,3 @@ const Env = schema({

export const env = defineEnv({ schema: Env });
```

## theme-toggle

| Field | Value |
| --- | --- |
| class | Library |
| sign | `M2 2h4v20H2V2zm8 0h12v20H10V2z` |
| tagline | `The theme is set before first paint.` |
| body | `Light, dark or system. A blocking inline script puts the dark class on the html element before the page paints. A React hook reads and changes it.` |
| installCommand | `pnpm add @variablelab/theme-toggle` |
| package | `@variablelab/theme-toggle` |
| requirement | `React 18 or later` |
| repository | `https://github.com/withvariablelab/theme-toggle` |
| links | `GitHub` → the repository. It has no docs of its own. |
| closingLine | `No CSS to import. No provider to wrap.` |
| description | `The theme is set before first paint. Light, dark or system.` |

Example: two File snippets, and the `og:image` shows the second (ogSnippet: `theme-switch.tsx`). The first is `index.html`, placement `<head>`, language `HTML`:

```html
<script
src="https://unpkg.com/@variablelab/theme-toggle/dist/initial.js"
></script>
```

The second is `theme-switch.tsx`, language `TSX`:

```tsx
import { useColorMode } from "@variablelab/theme-toggle";

export function ThemeSwitch() {
const { colorMode, setColorMode } = useColorMode();
const next = colorMode === "dark" ? "light" : "dark";

return (
<button onClick={() => setColorMode(next)}>{colorMode}</button>
);
}
```
25 changes: 13 additions & 12 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ The canvas in Claude Design is the visual source of truth: <https://claude.ai/ar
| Artboards | Page |
| --- | --- |
| `Main`, `Main-tablet`, `Main-phone` | Index |
| `RunRun`, `Vland`, `Env`, `ThemeToggle`, and their `-tablet` and `-phone` | Project pages |
| `OG-Index`, `OG-RunRun`, `OG-Vland`, `OG-Env`, `OG-ThemeToggle` | `og:image` |
| `RunRun`, `Vland`, `Env`, and their `-tablet` and `-phone` | Project pages |
| `OG-Index`, `OG-RunRun`, `OG-Vland`, `OG-Env` | `og:image` |

The canvas also draws theme-toggle, which stays out of the Index, as [add-a-project.md](add-a-project.md) says. The site builds none of what the canvas draws for it: its `ThemeToggle` and `OG-ThemeToggle` artboards, its row on the Index and in `OG-Index`, and its place in each Also indexed.

Build by looking at the canvas, not from exported files. If the canvas and this file disagree, ask the Keeper.

Expand Down Expand Up @@ -79,14 +81,13 @@ Every page:

1. On the Index, a Project's name links to its Project page, and its "Published as" line links to its Repository. On the Project page, the Package links to npm and the Executable is plain text.
2. "Published as" shows the Project's `publishedAs` ([content-model.md](content-model.md#project)) in `dim`, with an App's Executable in `ink`. On a Project page, a Library's Package is in `ink` too.
3. Also indexed shows the Project's `alsoIndexed` ([content-model.md](content-model.md#project)). In the canvas that gives:
3. Also indexed shows the Project's `alsoIndexed` ([content-model.md](content-model.md#project)). With today's Index that gives:

| Page | Also indexed |
| --- | --- |
| run-run | vland, env, theme-toggle |
| vland | run-run, env, theme-toggle |
| env | theme-toggle, run-run, vland |
| theme-toggle | env, run-run, vland |
| run-run | vland, env |
| vland | run-run, env |
| env | run-run, vland |
4. The header shows the page's Address on the left, in mono, and the Signature on the right. On a Project page the Signature is one link to `/`; on the Index it isn't a link.
5. The Keeper's column is only on the Index: the name (not a link), the site (a link), the statement, and `GitHub` to the org.
6. The whole Install command box is the Copy button.
Expand Down Expand Up @@ -159,9 +160,9 @@ The Index is read, not browsed: a list under the house's own sentence, not a gri
4. The Example, 12px below the Install command. Both share the same left edge.
5. The Closing line and Also indexed.
- **Focus order in one column:** focus moves through the facts as they are drawn, row by row (`reading-flow: grid-rows`), so the Install command comes last. Where `reading-flow` isn't supported, the DOM order stands: Install first.
- **Install command width:** half the width at 600–899px, growing past half only as far as its Command needs to fit one line (theme-toggle's, near 600px), and the full width at ≤ 599px.
- **Published as:** at ≤ 599px it spans both columns for an App, always, and for a Library whose Package is wider than one column at the phone width, 390px. In the canvas that happens for the two Apps and theme-toggle.
- **Package:** whole wherever it fits; an App's line wraps after the separator, with the Package whole on the next line. Only when its column is narrower than it does it break, after its scope (`@variablelab/`, then `theme-toggle`), never at a hyphen and never past its column: theme-toggle's does at 600–651px. Its link's accessible name stays exactly the Package.
- **Install command width:** half the width at 600–899px, growing past half only as far as its Command needs to fit one line, and the full width at ≤ 599px.
- **Published as:** at ≤ 599px it spans both columns for an App, always, and for a Library whose Package is wider than one column at the phone width, 390px.
- **Package:** whole wherever it fits; an App's line wraps after the separator, with the Package whole on the next line. Only when its column is narrower than it does it break, after its scope (`@variablelab/`, then the rest), never at a hyphen and never past its column. Its link's accessible name stays exactly the Package.
- **Runs on:** its last two Runtimes and the separator between them stay on one line, so the list never ends on a single Runtime, as env's would at 351px.

| Element | Type | Color | Size and space |
Expand Down Expand Up @@ -236,11 +237,11 @@ Every `og:image` has the same frame:

- **Middle:**
- The Sign at 72px, 24px from the name, shifted 6px left so its edge lines up with the text.
- The name at 84px / 1, 500, `-0.04em`. If it doesn't fit the column, it shrinks to the largest whole size that does: `theme-toggle` is 71px.
- The name at 84px / 1, 500, `-0.04em`. If it doesn't fit the column, it shrinks to the largest whole size that does.
- The Tagline at 34px / 1.22, `-0.02em`, `max-width: 20ch`, balance, 28px below.
- **Panel**, in mono, as three rows:
1. **Install command:** `padding: 24px 28px`, 21px, a 1px `rule` below. A `$ ` prompt in `gutter` comes first, then the Command colored by token.
2. **Snippet name:** the `ogSnippet` name (`env.ts`, `theme-switch.tsx` or `Terminal`), `padding: 16px 28px`, 17px, in the syntax `keyword` color, a 1px `rule` below.
2. **Snippet name:** the `ogSnippet` name (`env.ts` or `Terminal`), `padding: 16px 28px`, 17px, in the syntax `keyword` color, a 1px `rule` below.
3. **Code:** that Snippet's code at 20px / 34px, colored with the syntax palette.
- A File snippet has `padding: 22px 0` and line numbers 30px wide, right-aligned, in `gutter`, 24px before the code.
- A Terminal snippet has `padding: 22px 28px` and no line numbers. Its comments are in `comment`, each Command gets a `$ ` prompt, and the line between groups is a blank 34px.
Expand Down
6 changes: 2 additions & 4 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ const link = stored({
});

const fileSnippet = stored({
file: z
.string()
.regex(kebabCaseFile, { error: "A File snippet's file is a kebab-case file name, such as env.ts or theme-switch.tsx." }),
file: z.string().regex(kebabCaseFile, { error: "A File snippet's file is a kebab-case file name, such as env.ts." }),
placement: text("A File snippet's placement").optional(),
language: text("A File snippet's language"),
code: text("A File snippet's code"),
Expand Down Expand Up @@ -80,7 +78,7 @@ const example = stored({
const project = stored({
name: z
.string()
.regex(kebabCase, { error: "A Project's name is lowercase kebab-case, such as theme-toggle." })
.regex(kebabCase, { error: "A Project's name is lowercase kebab-case, such as run-run." })
.refine((name) => name !== "og", { error: 'A Project is never named "og": /og/ holds the og:images.' }),
class: z.enum(["App", "Library"], { error: 'class is "App" or "Library".' }),
// The Sign: the d attribute of the one path in the Project's brand/sign.svg.
Expand Down
1 change: 0 additions & 1 deletion src/content/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@ index:
- "run-run"
- "vland"
- "env"
- "theme-toggle"
description: "A public index of apps and libraries. Variable Land also builds software that isn't published here."
37 changes: 0 additions & 37 deletions src/content/projects/theme-toggle.yaml

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/[project].astro
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const phoneColumn = (390 - 2 * 16 - 24) / 2;
const metaAdvance = 0.6 * 12;
const publishedAsWide = publishedAs.executable !== undefined || [...publishedAs.package].length * metaAdvance > phoneColumn;

// A scoped Package, such as @variablelab/theme-toggle, splits after its scope: the one place it may break.
// A scoped Package, such as @variablelab/env, splits after its scope: the one place it may break.
const scopeLength = publishedAs.package.startsWith("@") ? publishedAs.package.indexOf("/") + 1 : 0;
const packageScope = publishedAs.package.slice(0, scopeLength);
const packageName = publishedAs.package.slice(scopeLength);
Expand Down
Loading