Skip to content
Closed
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
14 changes: 14 additions & 0 deletions docs/catalog/blocks/hw-write-title.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ It runs for 6 seconds at 1920×1080. Paste this into your composition:
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.

## Controls

The designed levers this item publishes, set in its top-of-script `CONFIG`
object. Anything not listed here — and not a content field or a documented
CSS token — is locked: changing it means editing the item source.

| Control | Type | Accepts | Default | Drives |
| --- | --- | --- | --- | --- |
| `scheme` | variant | `chalk`, `ink` | `chalk` | which authored token set inks the letterforms (chalk-white over footage / marker-ink on boards) |
| `animation` (`animationIn` + `animationOut`) | toggle | `true` / `false` | `animationIn: true`, `animationOut: true` | entrance (the write-on itself; false = hard-on) and exit fade — durations derived, never exposed |
| `boil` | variant | `off`, `calm`, `lively` | `calm` | boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked |
| `writeOn` | variant | `trace`, `reveal`, `off` | `trace` | reveal mechanic: pen-traced stroke order with velocity physics / per-glyph staggered fade / hard-on — trace duration derived from stroke count |
| `underline` | toggle | `true` / `false` | `true` | seeded accent squiggle underline drawn after the write lands |

## Source

<Accordion title={`hw-write-title.html`}>
Expand Down
12 changes: 12 additions & 0 deletions docs/catalog/components/hw-arrow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ Open `compositions/components/hw-arrow.html` and copy what is inside into your o
A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Controls

The designed levers this item publishes, set in its top-of-script `CONFIG`
object. Anything not listed here — and not a content field or a documented
CSS token — is locked: changing it means editing the item source.

| Control | Type | Accepts | Default | Drives |
| --- | --- | --- | --- | --- |
| `arrowStyle` | variant | `straight`, `gentle`, `swoop` | `gentle` | curve bend pose over the seeded wobble math (bend constant + both cubic control points) |
| `strokeType` | variant | `plain`, `soft`, `sharp`, `spray` | `plain` | curve texture via hwStrokeApply (soft blur / dry-marker dasharray on a mask clone / seeded deterministic spray dots); the head stays plain |
| `boil` | variant | `off`, `calm`, `lively` | `calm` | boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked |

## Source

<Accordion title={`hw-arrow.html`}>
Expand Down
11 changes: 11 additions & 0 deletions docs/catalog/components/hw-boil.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ Open `compositions/components/hw-boil.html` and copy what is inside into your ow
A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Controls

The designed levers this item publishes, set in its top-of-script `CONFIG`
object. Anything not listed here — and not a content field or a documented
CSS token — is locked: changing it means editing the item source.

| Control | Type | Accepts | Default | Drives |
| --- | --- | --- | --- | --- |
| `boil` | variant | `off`, `calm`, `lively` | `calm` | boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked |
| `strokeType` | variant | `plain`, `soft`, `sharp`, `spray` | `plain` | stroke texture of the drawn path (soft = slight blur; sharp = micro-dash dry marker; spray = seeded deterministic dot scatter) — textured types draw on via a mask clone so the texture itself never animates |

## Source

<Accordion title={`hw-boil.html`}>
Expand Down
13 changes: 13 additions & 0 deletions docs/catalog/components/hw-box-label.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ Open `compositions/components/hw-box-label.html` and copy what is inside into yo
A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Controls

The designed levers this item publishes, set in its top-of-script `CONFIG`
object. Anything not listed here — and not a content field or a documented
CSS token — is locked: changing it means editing the item source.

| Control | Type | Accepts | Default | Drives |
| --- | --- | --- | --- | --- |
| `entrance` | variant | `draw`, `drop` | `draw` | how the box arrives: stroke draw-on + label fade, or the landing squash (drop-in, contact squash on the deform wrapper, label counter-scaled to net identity, bouncy spring recovery) — travel/duration derived, never exposed |
| `strokeType` | variant | `plain`, `soft`, `sharp`, `spray` | `plain` | authored stroke texture on the box path (per-type constants: blur, dry-marker dash, seeded spray dots); draw-on composes via the mask clone |
| `labelPos` | variant | `above`, `inside` | `above` | label placement: above the box or vertically centered inside it |
| `boil` | variant | `off`, `calm`, `lively` | `calm` | boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked |

## Source

<Accordion title={`hw-box-label.html`}>
Expand Down
12 changes: 12 additions & 0 deletions docs/catalog/components/hw-callout-circle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ Open `compositions/components/hw-callout-circle.html` and copy what is inside in
A component has no size or duration of its own. It takes both from the composition
you paste it into.

## Controls

The designed levers this item publishes, set in its top-of-script `CONFIG`
object. Anything not listed here — and not a content field or a documented
CSS token — is locked: changing it means editing the item source.

| Control | Type | Accepts | Default | Drives |
| --- | --- | --- | --- | --- |
| `labelAt` | variant | `left`, `right`, `below` | `right` | connector start point, sweep direction, and label anchor pose (6 geometry params — promoted from the former raw build opt) |
| `strokeType` | variant | `plain`, `soft`, `sharp`, `spray` | `plain` | outline + connector stroke texture as authored constants per type (dash geometry, blur, seeded spray dots, mask-clone draw routing); the scribble fill stays plain — texture-on-texture reads as mud |
| `boil` | variant | `off`, `calm`, `lively` | `calm` | boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked |

## Source

<Accordion title={`hw-callout-circle.html`}>
Expand Down
70 changes: 70 additions & 0 deletions docs/schema/registry-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,76 @@
"poster": { "type": "string" }
}
},
"controls": {
"type": "array",
"minItems": 1,
"description": "The item's declared control surface — the single authoring source for control documentation. Each entry mirrors one control in the item's top-of-script CONFIG object and is rendered as a controls table on the item's generated catalog page.",
"items": {
"type": "object",
"required": ["name", "type", "drives"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"pattern": "^[a-zA-Z][a-zA-Z0-9]*$",
"description": "Control name as it appears in CONFIG. Intent-named (e.g. \"energy\", \"layout\"), never implementation-named (e.g. \"staggerMs\")."
},
"type": {
"type": "string",
"enum": ["toggle", "variant", "scalar"],
"description": "toggle = boolean between two authored states; variant = enum over N authored poses; scalar = bounded number interpolating authored anchor states through a mapping."
},
"values": {
"type": "array",
"minItems": 2,
"items": { "type": "string", "minLength": 1 },
"description": "variant only: the authored poses. Toggles omit this (always true | false)."
},
"fields": {
"type": "array",
"minItems": 2,
"items": { "type": "string", "minLength": 1 },
"description": "Maps one declared control to N CONFIG keys (e.g. a paired animationIn/animationOut toggle counting as one control). default is then keyed per field."
},
"min": {
"type": "number",
"description": "scalar only: lower declared bound (out-of-bounds input clamps here, loudly)."
},
"max": {
"type": "number",
"description": "scalar only: upper declared bound (out-of-bounds input clamps here, loudly)."
},
"default": {
"description": "The shipped state: variant = one of values; toggle = boolean (or a per-field object when fields is set); scalar = a number within min..max."
},
"drives": {
"type": "string",
"minLength": 1,
"description": "What the control drives — the designed state group or mapping behind it."
}
},
"allOf": [
{
"if": {
"required": ["type"],
"properties": { "type": { "const": "variant" } }
},
"then": { "required": ["values"] },
"else": { "not": { "required": ["values"] } }
},
{
"if": {
"required": ["type"],
"properties": { "type": { "const": "scalar" } }
},
"then": { "required": ["min", "max"] },
"else": {
"not": { "anyOf": [{ "required": ["min"] }, { "required": ["max"] }] }
}
}
]
}
},
"relatedSkill": {
"type": "string",
"minLength": 1
Expand Down
70 changes: 70 additions & 0 deletions packages/core/schemas/registry-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,76 @@
"poster": { "type": "string" }
}
},
"controls": {
"type": "array",
"minItems": 1,
"description": "The item's declared control surface — the single authoring source for control documentation. Each entry mirrors one control in the item's top-of-script CONFIG object and is rendered as a controls table on the item's generated catalog page.",
"items": {
"type": "object",
"required": ["name", "type", "drives"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"pattern": "^[a-zA-Z][a-zA-Z0-9]*$",
"description": "Control name as it appears in CONFIG. Intent-named (e.g. \"energy\", \"layout\"), never implementation-named (e.g. \"staggerMs\")."
},
"type": {
"type": "string",
"enum": ["toggle", "variant", "scalar"],
"description": "toggle = boolean between two authored states; variant = enum over N authored poses; scalar = bounded number interpolating authored anchor states through a mapping."
},
"values": {
"type": "array",
"minItems": 2,
"items": { "type": "string", "minLength": 1 },
"description": "variant only: the authored poses. Toggles omit this (always true | false)."
},
"fields": {
"type": "array",
"minItems": 2,
"items": { "type": "string", "minLength": 1 },
"description": "Maps one declared control to N CONFIG keys (e.g. a paired animationIn/animationOut toggle counting as one control). default is then keyed per field."
},
"min": {
"type": "number",
"description": "scalar only: lower declared bound (out-of-bounds input clamps here, loudly)."
},
"max": {
"type": "number",
"description": "scalar only: upper declared bound (out-of-bounds input clamps here, loudly)."
},
"default": {
"description": "The shipped state: variant = one of values; toggle = boolean (or a per-field object when fields is set); scalar = a number within min..max."
},
"drives": {
"type": "string",
"minLength": 1,
"description": "What the control drives — the designed state group or mapping behind it."
}
},
"allOf": [
{
"if": {
"required": ["type"],
"properties": { "type": { "const": "variant" } }
},
"then": { "required": ["values"] },
"else": { "not": { "required": ["values"] } }
},
{
"if": {
"required": ["type"],
"properties": { "type": { "const": "scalar" } }
},
"then": { "required": ["min", "max"] },
"else": {
"not": { "anyOf": [{ "required": ["min"] }, { "required": ["max"] }] }
}
}
]
}
},
"relatedSkill": {
"type": "string",
"minLength": 1
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export type {
export {
ITEM_TYPES,
FILE_TYPES,
CONTROL_TYPES,
ITEM_TYPE_DIRS,
isExampleItem,
isBlockItem,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type {
export {
ITEM_TYPES,
FILE_TYPES,
CONTROL_TYPES,
ITEM_TYPE_DIRS,
BLOCK_CATEGORIES,
resolveBlockCategory,
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/registry/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import {
CONTROL_TYPES,
FILE_TYPES,
ITEM_TYPES,
isBlockItem,
Expand Down Expand Up @@ -120,13 +121,15 @@ describe("registry types", () => {
expect(setEquals(enums[0]!, ITEM_TYPES)).toBe(true);
});

it("registry-item.json has exactly two `type` enums: one ITEM_TYPES, one FILE_TYPES", () => {
it("registry-item.json has exactly three `type` enums: ITEM_TYPES, FILE_TYPES, CONTROL_TYPES", () => {
const enums = collectEnums(registryItemSchema, "type");
const distinct = new Set(enums.map(setKey));
// Two semantically distinct enums — the item's `type` and each file's `type`.
expect(distinct.size).toBe(2);
// Three semantically distinct enums — the item's `type`, each file's
// `type`, and each declared control's `type`.
expect(distinct.size).toBe(3);
expect(enums.some((e) => setEquals(e, ITEM_TYPES))).toBe(true);
expect(enums.some((e) => setEquals(e, FILE_TYPES))).toBe(true);
expect(enums.some((e) => setEquals(e, CONTROL_TYPES))).toBe(true);
});
});

Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ export const FILE_TYPES = [
"hyperframes:timeline",
] as const satisfies readonly FileType[];

/**
* The declared-control types in a registry item's `controls` field: toggle
* (boolean between two authored states), variant (enum over N authored poses),
* scalar (bounded number interpolating authored anchor states through a
* mapping). Kept in sync with the `controls` enum in registry-item.json.
*/
export const CONTROL_TYPES = ["toggle", "variant", "scalar"] as const;

/**
* Directory segment where each item type lives under a registry root — both
* on disk (`registry/examples/…`) and in URL construction
Expand Down
Loading
Loading