{
  "schemaVersion": 1,
  "canonical": "/design-system",
  "data": "/design-system/reference.json",
  "policy": {
    "title": "The working reference for this site",
    "description": "These are the foundations, controls and composition rules used to build this desktop. Explore the examples, then open their usage notes for the exact implementation.",
    "authority": "This page and its reference data are the canonical design-system contract. Explicit direction from Sylvain takes precedence. Production components and tokens implement the contract; discrepancies must be reconciled rather than copied as new conventions.",
    "workflow": [
      "Find the matching entry and inspect its production source before building. Reuse shared components and CSS; preserve the documented HTML and accessibility behaviour.",
      "Use the listed component aliases. Theme files change tokens, not component selectors. Keep surface and text colours paired; never substitute approximate local colours.",
      "Keep bespoke compositions specific to their feature. Reuse an established pattern before extracting a new abstraction; there is no universal card.",
      "When a recurring convention changes, update the production implementation, this reference and its checks together. A code change alone does not approve a new design convention.",
      "Verify direct routes, browser history, keyboard focus, reduced motion, narrow screens and contrasting themes. Review screenshots against the approved design; do not automatically accept new visual baselines."
    ],
    "scope": "Shared components are reusable implementations. Shared CSS requires the documented native markup. Compositions describe relationships rather than universal components. Demonstrations explain behaviour and do not establish a new site-wide control. Journal experiments are not production conventions unless explicitly adopted here."
  },
  "entries": [
    {
      "id": "themes",
      "title": "Theme behaviour",
      "kind": "Foundation",
      "summary": "One token system powers the entire interface. The preview can follow the site or explore a local variation.",
      "sources": [
        "tokens.css",
        "src/styles/base-theme.css",
        "src/data/theme-controls.ts",
        "src/components/launcher/ThemeControls.astro",
        "src/scripts/theme-controls.ts"
      ],
      "tokens": [
        "--theme-hue",
        "--theme-lightness",
        "--theme-chroma",
        "--surface-distance",
        "--contrast-low",
        "--contrast-high"
      ],
      "rules": [
        "Reuse ThemeControls for the theme panel and its existing saved-state behaviour. Do not build another preference store.",
        "The specimen presets and its buttons affect only the embedded preview. Its field layout and Reset fields button are demonstration UI, not general-purpose production components.",
        "System preferences apply until explicitly overridden. Reset restores the authored preference behaviour. Theme changes must preserve reduced motion."
      ],
      "states": [
        "Site",
        "Local preview",
        "Light and dark",
        "High contrast",
        "Reset"
      ],
      "usedOn": [
        {
          "label": "Theme controls",
          "href": "/theme"
        }
      ]
    },
    {
      "id": "colour",
      "title": "Colour and contrast",
      "kind": "Foundation",
      "summary": "Derive surfaces from the colour axis and use the ink generated for each surface.",
      "sources": [
        "tokens.css",
        "src/styles/base-theme.css",
        "src/scripts/color-contrast-fallback.ts"
      ],
      "tokens": [
        "--color-surface-base",
        "--color-surface-sunken",
        "--color-text-base-high",
        "--color-text-base-supporting",
        "--color-text-sunken-high",
        "--contrast-supporting"
      ],
      "rules": [
        "Component CSS consumes its own aliases from tokens.css, not source colour tokens or literal colours.",
        "Use high ink for primary content and supporting ink for small readable labels. Low ink is available for lower-emphasis decoration; it is not a substitute for supporting text.",
        "Keep borders, focus and selection token-controlled. Do not use the accent as a text-bearing fill without a verified matching foreground."
      ],
      "states": [
        "Base",
        "Sunken",
        "Dark",
        "Chromatic",
        "High contrast"
      ],
      "usedOn": [
        {
          "label": "Desktop",
          "href": "/"
        }
      ]
    },
    {
      "id": "typography",
      "title": "Typography",
      "kind": "Foundation",
      "summary": "42dot Sans and a shared modular scale give each text role a consistent size.",
      "sources": [
        "tokens.css",
        "src/lib/type-scale.ts",
        "src/scripts/type-scale-fallback.ts"
      ],
      "tokens": [
        "--font-sans",
        "--type-base",
        "--type-ratio",
        "--type-rounding",
        "--font-size-body",
        "--font-size-supporting"
      ],
      "rules": [
        "Map feature aliases to semantic type roles; do not invent local font sizes for an existing role.",
        "Heading levels describe document structure, not visual size. Keep one page h1 and a meaningful heading order.",
        "Keep rem-based sizing and allow text to wrap when the visitor increases their text size. The scale slider here is a local demonstration."
      ],
      "states": [
        "Display",
        "Panel title",
        "Reading",
        "Body",
        "Supporting",
        "Larger text"
      ],
      "usedOn": [
        {
          "label": "Profile",
          "href": "/profile"
        }
      ]
    },
    {
      "id": "shape",
      "title": "Nested surfaces",
      "kind": "Composition",
      "summary": "A nested curve follows the outside curve, its padding and the border between them.",
      "sources": [
        "tokens.css",
        "src/styles/segmented-control.css"
      ],
      "tokens": [
        "--segment-radius",
        "--segment-option-radius",
        "--segment-padding",
        "--segment-border-width"
      ],
      "rules": [
        "For inset shapes, subtract padding and border from the outside radius and clamp at zero.",
        "Keep corner shape and radius controlled by the owning component aliases. The geometry sliders illustrate the relationship; their values are not saved site settings."
      ],
      "states": [
        "Square",
        "Rounded",
        "Visible border"
      ],
      "usedOn": [
        {
          "label": "Type archive",
          "href": "/work/36-days-of-type"
        }
      ]
    },
    {
      "id": "split-surfaces",
      "title": "Split surfaces",
      "kind": "Composition",
      "summary": "A header and body share an outside silhouette with smaller corners where they meet.",
      "sources": [
        "tokens.css",
        "src/styles/surface-edge.css",
        "src/components/launcher/WorkLauncher.astro",
        "src/styles/work-panels.css"
      ],
      "tokens": [
        "--launcher-cap-radius",
        "--launcher-cap-gap",
        "--surface-cap-radius-max",
        "--surface-cap-radius-factor"
      ],
      "rules": [
        "Use the existing cap geometry: joining corners follow half the outside radius, bounded by the shared maximum. Preserve the independent gap.",
        "Apply the feature's surface and edge aliases. Do not turn Work, Journal and the other bespoke compositions into one configurable card."
      ],
      "states": [
        "Small and large radii",
        "Visible border",
        "Narrow screen"
      ],
      "usedOn": [
        {
          "label": "Work",
          "href": "/work"
        },
        {
          "label": "Journal",
          "href": "/journal"
        }
      ]
    },
    {
      "id": "icons",
      "title": "Icons",
      "kind": "Foundation",
      "summary": "Use the site's prepared Lucide SVGs and the owning control's dimensions.",
      "sources": [
        "src/icons/README.md",
        "src/icons/SOURCE.json",
        "src/components/PanelAction.astro"
      ],
      "tokens": [
        "--panel-action-icon-size"
      ],
      "rules": [
        "Import prepared SVGs from src/icons. Decorative icons are hidden from assistive technology; icon-only actions require an accessible name.",
        "Use currentColor and component size aliases. Do not restore the archived custom-icon generator or introduce an icon runtime."
      ],
      "states": [
        "Labelled",
        "Icon-only",
        "Keyboard focus"
      ],
      "usedOn": [
        {
          "label": "Work",
          "href": "/work"
        }
      ]
    },
    {
      "id": "motion",
      "title": "Motion and focus",
      "kind": "Shared CSS",
      "summary": "Motion explains selection and position; the same state remains clear without animation.",
      "sources": [
        "src/styles/segmented-control.css",
        "src/styles/hover-list.css",
        "src/scripts/hover-list.ts",
        "src/scripts/desktop-shell.ts",
        "src/styles/design-system.css"
      ],
      "tokens": [
        "--segment-duration",
        "--hover-list-duration",
        "--hover-list-ease",
        "--ds-motion-duration",
        "--ds-motion-ease"
      ],
      "rules": [
        "Reuse SegmentedControl for moving selection and data-hover-list with the existing helper for list highlights.",
        "Keep focus visible independently of hover. Respect prefers-reduced-motion before starting scripted animation and when it changes.",
        "The disclosure example and usage notes share native details/summary and the same CSS content reveal and chevron motion. Opening and closing animate where supported, and become immediate with reduced motion; the collection content is illustrative."
      ],
      "states": [
        "Hover",
        "Keyboard focus",
        "Selected",
        "Reduced motion"
      ],
      "usedOn": [
        {
          "label": "Journal",
          "href": "/journal"
        }
      ],
      "example": "<ul data-hover-list=\".collection-link\">\n  <li><a class=\"collection-link\" href=\"/work\">Work</a></li>\n</ul>"
    },
    {
      "id": "actions",
      "title": "Panel actions",
      "kind": "Shared component",
      "summary": "Reuse PanelAction for Back, Close and Open controls. Navigation uses links; local actions use buttons.",
      "sources": [
        "src/components/PanelAction.astro",
        "src/styles/panel-action.css"
      ],
      "tokens": [
        "--panel-action-background",
        "--panel-action-color",
        "--panel-action-radius",
        "--panel-action-focus-color"
      ],
      "rules": [
        "The default is a link and requires href. Use as=\"button\" only for an action, never to imitate navigation.",
        "Supply text for the Back treatment, or label for an icon-only control. The icon choices are close and arrow.",
        "The buttons here provide local feedback so the examples keep you on this page. Production navigation still uses real destinations. This is not a generic submit-button component."
      ],
      "states": [
        "Back",
        "Close",
        "Open",
        "Disabled button",
        "Keyboard focus"
      ],
      "usedOn": [
        {
          "label": "Profile",
          "href": "/profile"
        },
        {
          "label": "Work",
          "href": "/work"
        }
      ],
      "example": "---\nimport PanelAction from \"../components/PanelAction.astro\";\n---\n<PanelAction href=\"/work\" text=\"Work\" />\n<PanelAction href=\"/\" label=\"Close panel\" />\n<PanelAction as=\"button\" icon=\"arrow\" label=\"Next item\" />"
    },
    {
      "id": "fields",
      "title": "Study fields and selects",
      "kind": "Shared component",
      "summary": "StudyFrame supplies the real field and button treatment used in Journal studies. Native selects share their own CSS.",
      "sources": [
        "src/components/StudyFrame.astro",
        "src/styles/study-frame.css",
        "src/styles/native-select.css"
      ],
      "tokens": [
        "--study-control-background",
        "--study-card-color",
        "--study-control-radius",
        "--select-field-background",
        "--select-field-color"
      ],
      "rules": [
        "Use StudyFrame around a Journal study; as=\"div\" is available when figure semantics do not fit. Label every input and connect feedback with aria-describedby.",
        "Use native select elements with class native-select. StudyFrame maps the closed select to the same background and foreground as its other fields; the open menu keeps its own surface pair. Keep feature mappings to --select-*; native browser fallback remains available.",
        "Contact and tool fields have their own feature contracts. Do not copy the reference page's ds__field or ds__button helpers into a feature, or apply study styling to unrelated forms."
      ],
      "states": [
        "Editable",
        "Disabled",
        "Invalid",
        "Reset",
        "Keyboard focus"
      ],
      "usedOn": [
        {
          "label": "Native controls study",
          "href": "/journal/styling-a-native-select"
        }
      ],
      "example": "---\nimport StudyFrame from \"../components/StudyFrame.astro\";\n---\n<StudyFrame>\n  <label for=\"view\"><span>View</span>\n    <select id=\"view\" class=\"native-select\"><option>List</option><option>Grid</option></select>\n  </label>\n</StudyFrame>"
    },
    {
      "id": "selection",
      "title": "Selection controls",
      "kind": "Shared component",
      "summary": "SegmentedControl renders native radios. Checkboxes and the theme switch use established native markup and shared CSS.",
      "sources": [
        "src/components/SegmentedControl.astro",
        "src/styles/segmented-control.css",
        "src/styles/primitive-study.css",
        "src/styles/theme-controls.css",
        "src/components/launcher/ThemeControls.astro"
      ],
      "tokens": [
        "--segment-background",
        "--segment-selected-background",
        "--segment-radius",
        "--study-checkbox-background",
        "--theme-control-switch-background",
        "--theme-control-switch-thumb-background",
        "--theme-control-switch-thumb-inset"
      ],
      "rules": [
        "Give each radio group a unique name, a label, two to eight uniquely valued options and a matching selected value. Use disabled for an unavailable group. Named slots can provide option icons.",
        "Keep native radio keyboard behaviour. The selected value must remain distinguishable with motion disabled.",
        "Checkboxes use a primitive-study ancestor and data-study-checkbox. Theme switches use the ThemeControls markup and theme-toggle classes, following the range input: off slightly darkens its unfilled track, on uses its filled track, and the solid thumb shares the range thumb fill and dimensions. High contrast uses the range track directly. Keep the thumb flush, without an inset ring. Thumb borders are reserved for high contrast; keyboard focus remains visible in every mode. Neither requires a custom JavaScript state library."
      ],
      "states": [
        "Checked and unchecked",
        "Selected",
        "Disabled",
        "Keyboard arrows",
        "Reduced motion"
      ],
      "usedOn": [
        {
          "label": "3D work",
          "href": "/work/3d-work"
        },
        {
          "label": "Theme",
          "href": "/theme"
        }
      ],
      "example": "---\nimport SegmentedControl from \"../components/SegmentedControl.astro\";\n---\n<SegmentedControl name=\"collection-view\" label=\"Collection view\" value=\"list\"\n  options={[{ value: \"list\", label: \"List\" }, { value: \"grid\", label: \"Grid\" }]} />"
    },
    {
      "id": "ranges",
      "title": "Range inputs",
      "kind": "Shared CSS",
      "summary": "Native range inputs share the site's track, thumb, focus and progress treatment.",
      "sources": [
        "src/styles/range-input.css",
        "src/scripts/range-input.ts",
        "src/scripts/desktop-shell.ts"
      ],
      "tokens": [
        "--range-track-background",
        "--range-thumb-background",
        "--range-focus-color"
      ],
      "rules": [
        "Use a labelled input[type=range] with explicit bounds and an associated output. Keep arrow-key operation native.",
        "The shell installs range enhancement. Call syncRangeInput when feature code sets a value programmatically.",
        "The amount meter is an illustrative output, not a reusable progress component."
      ],
      "states": [
        "Minimum",
        "Maximum",
        "Disabled",
        "Keyboard adjustment"
      ],
      "usedOn": [
        {
          "label": "Theme",
          "href": "/theme"
        }
      ],
      "example": "<label for=\"amount\">Amount <output for=\"amount\">50%</output></label>\n<input id=\"amount\" type=\"range\" min=\"0\" max=\"100\" value=\"50\" />"
    },
    {
      "id": "panel-navigation",
      "title": "Panel navigation",
      "kind": "Shared component",
      "summary": "PanelNavigation groups real Back and Close links, with a slot for contextual actions.",
      "sources": [
        "src/components/PanelNavigation.astro",
        "src/components/PanelAction.astro",
        "src/styles/panel-navigation.css"
      ],
      "tokens": [
        "--panel-navigation-gap",
        "--panel-navigation-cap-background",
        "--panel-navigation-cap-radius"
      ],
      "rules": [
        "Supply label and closeLabel. Supply backHref and backText together when the panel has a parent.",
        "Use the default slot for contextual actions. Enable cap only for a split-stage header; it changes the surrounding stage geometry.",
        "Keep navigation as anchors so Back, Forward, opening in a new tab and copying a link remain available."
      ],
      "states": [
        "Back and Close",
        "Contextual action",
        "Cap variant",
        "Keyboard focus"
      ],
      "usedOn": [
        {
          "label": "Profile",
          "href": "/profile"
        },
        {
          "label": "Work",
          "href": "/work"
        }
      ],
      "example": "---\nimport PanelNavigation from \"../components/PanelNavigation.astro\";\n---\n<PanelNavigation label=\"Collection navigation\" backHref=\"/work\" backText=\"Work\" closeLabel=\"Close collection\" />"
    },
    {
      "id": "working-area",
      "title": "Toolbar and collection",
      "kind": "Demonstration",
      "summary": "This composition demonstrates a shared selector with a local list/grid arrangement.",
      "sources": [
        "src/components/design-system/Patterns.astro",
        "src/components/SegmentedControl.astro",
        "src/styles/design-system.css"
      ],
      "tokens": [
        "--ds-collection-duration",
        "--ds-collection-ease"
      ],
      "rules": [
        "Reuse the selector, not the demo collection as a universal layout. Production collections retain their own feature source.",
        "The ds-collection classes and sample data belong to this demonstration. List/grid changes must retain readable order, keyboard control and a reduced-motion state."
      ],
      "states": [
        "List",
        "Grid",
        "Narrow screen",
        "Reduced motion"
      ],
      "usedOn": [
        {
          "label": "Production 3D collection",
          "href": "/work/3d-work"
        }
      ]
    },
    {
      "id": "overflow",
      "title": "Overflow and edge fades",
      "kind": "Shared component",
      "summary": "ScrollFade marks remaining content and clears each edge when there is nothing more to scroll.",
      "sources": [
        "src/components/ScrollFade.astro",
        "src/styles/scroll-fade.css",
        "src/scripts/scroll-fade.ts",
        "src/scripts/desktop-shell.ts"
      ],
      "tokens": [
        "--scroll-fade-start-distance"
      ],
      "rules": [
        "Place data-scroll-fade on the direct scrollable child of ScrollFade. The shell initialises the helper and handles teardown.",
        "Provide a keyboard-focusable, labelled scroll region when its content needs keyboard scrolling. The feature owns its height and overflow styling.",
        "Use normal end padding; do not add empty clearance for the fade. Keep content available without JavaScript and respect reduced motion."
      ],
      "states": [
        "Start",
        "Middle",
        "End",
        "No overflow",
        "Keyboard scrolling"
      ],
      "usedOn": [
        {
          "label": "Journal",
          "href": "/journal"
        },
        {
          "label": "Work",
          "href": "/work"
        }
      ],
      "example": "---\nimport ScrollFade from \"../components/ScrollFade.astro\";\n---\n<ScrollFade>\n  <div class=\"collection-scroll\" data-scroll-fade tabindex=\"0\" role=\"region\" aria-label=\"Collection\">\n    <!-- Feature content; collection-scroll owns height and overflow-y: auto. -->\n  </div>\n</ScrollFade>"
    }
  ],
  "compositions": [
    {
      "title": "Desktop and route panels",
      "source": "src/components/DesktopShell.astro",
      "href": "/",
      "rule": "Use the shell's panel slot and existing activePanel values. Preserve direct routes and the existing Astro navigation lifecycle; do not add another router."
    },
    {
      "title": "Launcher tiles",
      "source": "src/components/launcher/WorkLauncher.astro",
      "href": "/",
      "rule": "Each tile in src/components/launcher owns its composition. Shared tokens connect them; a universal card would erase their differences."
    },
    {
      "title": "Journal articles",
      "source": "src/components/JournalPostLayout.astro",
      "href": "/journal",
      "rule": "Use the article layout, journal-posts data and the relevant study component. Experimental specimens do not become production conventions automatically."
    },
    {
      "title": "Work entries",
      "source": "src/pages/work/[slug].astro",
      "href": "/work",
      "rule": "Use work-items data and its existing presentation choices. Tool pages retain their dedicated components and direct routes."
    },
    {
      "title": "Context notes",
      "source": "src/components/ContextNote.astro",
      "href": "/profile",
      "rule": "Reuse the existing contextual-note structure and theme-note aliases when the same inline disclosure pattern is needed."
    },
    {
      "title": "Media collections",
      "source": "src/components/ArtefactCarousel.astro",
      "href": "/",
      "rule": "Use the appropriate existing media feature: ArtefactCarousel, TypeArchive or the 3D Work implementation. Their behaviour is feature-specific."
    }
  ]
}