# Layout UI icon contract

Read manifest.json and compare search.svg, folder.svg, and calendar.svg before creating icons.
Always inspect the corresponding icon at https://heroicons.com/ before designing or revising an icon. Use it as a reference for recognizable silhouettes, optical balance, spacing, and small-size clarity, then apply Layout UI’s soft geometry and shared stroke contract. If adopting actual artwork, preserve its license and record attribution rather than labeling it original.
The outline family contains original Layout UI drawings. The separate Brands category contains attributed Simple Icons artwork; see manifest.json and BRANDS-LICENSE.md.
No independent open-source license has been assigned: apply the project's licensing terms.
For imported artwork record its actual upstream URL, license, and required attribution.

## Drawing rules
- Use a 24×24 viewBox, roughly 2px safe padding, and simple legible geometry.
- Outline: fill="none", stroke="currentColor", stroke-width="1.75", round caps and joins.
- Solid: a separate family, fill="currentColor", stroke="none". Do not mix families casually.
- Use paths and basic SVG geometry only. No scripts, style blocks, event handlers, text, fonts,
  external references, embedded images, IDs, transforms, or arbitrary colors.
- Judge optical weight and alignment at 16, 20, and 24px. Matching attributes is not visual proof.

## Add or replace
1. Edit one named SVG, or save a new kebab-case file beside the manifest.
2. Register its semantic name, file, family, description, category, source, and license in manifest.json.
3. Keep semantic names stable when swapping artwork. Aliases map directly to registered names.
4. Run python3 ops/web/icons.py. This rejects unsupported SVG constructs; it does not certify artwork.
5. Run python3 ops/web/icon_gallery.py, python3 ops/web/library.py, and python3 ops/web/integrations.py to refresh the gallery, component consumers, Create icons, and integration assets. Run python3 ops/web/check.py to catch stale generated consumers.
6. Review /icons at all three sizes in light/dark modes. Check thin details and optical centering.
7. Rebuild any selected sprites, or replace inline SVG already copied into consuming pages.

Replace an icon set by making a project-local manifest with the same semantic names and normalized SVGs.
Pass --manifest path/to/manifest.json to the tool. Never blindly inline downloaded SVG files.
The deliberately narrow supported SVG subset may require redrawing or simplifying imported artwork.

## Usage
Inline SVG is the simplest option: copy the drawing, add class="icon" aria-hidden="true"
focusable="false", and place it next to visible text. The surrounding control owns its label.
For an icon-only button give the button an accessible name, such as aria-label="Close dialog".
For meaningful standalone artwork use role="img" and an accessible name instead of aria-hidden.
Status indicators need text as well as an icon/color. Do not make an SVG itself the button.

An optional selected sprite avoids duplicating paths:
python3 ops/web/icons.py --names search close arrow-right --output production/public/icons/project.svg
Use <svg class="icon" aria-hidden="true" focusable="false"><use href="/icons/project.svg#search"></use></svg>.
Only explicitly selected symbols enter the sprite. Serve it from the same origin.
Standalone <img> SVGs do not inherit page currentColor; use inline SVG or a sprite to follow themes.
Changes to copied inline icons require updating those copies; this is not a runtime icon registry.
There is no mandatory icon JavaScript and no automatic full-collection download.

## AI acceptance checklist
Search names, descriptions and aliases before drawing; reuse an existing semantic icon when possible. Add original artwork using the rules above, never generated raster images. Document intended meaning and ambiguous alternatives. Preserve a 44px control hit target independently of the 16–24px drawing. Loading icons need visible status and reduced-motion behavior; directional icons may need explicit RTL mirroring. Inline SVG works in every framework: JSX uses className, strokeWidth, strokeLinecap and strokeLinejoin; Vue, Svelte, Angular, Astro and standalone accept the SVG attributes directly. Keep size and color in CSS, and include only selected artwork.

## Visual character
Use soft, friendly geometry: rounded structural corners (typically 2px on the 24px canvas), open counters, and balanced proportions. Round line joins alone do not soften a pointed silhouette; use short curves at acute corners, as in warning.svg. Keep symbols immediately recognizable, especially warnings. Avoid decorative faces, excessive detail, or inconsistent stroke weights.

The gallery uses shared Panel styling on native copy buttons and the shared Toast component for feedback. The entire preview tile copies SVG on click, tap, Enter, or Space. Keep the icon visible during hover and focus; do not add an action overlay. Small screens show the Copy SVG hint without requiring hover.

## Optical sizing system
- Keep the shared 24×24 canvas and 1.75px stroke; never stretch width and height independently.
- Center the perceived mass around (12,12), not merely the bounding box. Allow up to 1px optical adjustment for asymmetric forms such as Play.
- Round forms: use an 18px diameter (3–21). Square forms: use a 16–18px body and 2px corner radii. Tall forms: aim for 18px height with naturally narrower width. Wide forms: aim for 18–20px width with naturally shorter height.
- Sparse symbols (plus, minus, close, chevrons) intentionally occupy 12–16px; making them as large as enclosing shapes makes them too loud. Match opposite directions exactly.
- Preserve at least 1px outside the painted stroke; exceptions need a documented optical reason. Keep small interior gaps around 2px or more.
- Use actual curve segments at acute silhouette corners: Play, Star, Bookmark, Filter, and Warning. Round stroke joins are not a substitute. Keep short directional arrowheads crisp enough to read.
- Eyedropper stays diagonal with a rounded bulb, shoulder and narrow stem. Edit stays diagonal with a slim pencil body and short end cap. They must remain distinguishable at 16px.
- Review the entire collection together at 16, 20, and 24px on light and dark backgrounds before accepting changes. Check silhouette, perceived weight, whitespace, crowding and alignment. Do not normalize every icon to an identical bounding box.

## Help and brand variants
`help` is the standalone question mark; `help-circle` preserves the previous enclosed symbol. Existing consumers of `help.svg` now receive the standalone glyph; choose `help-circle.svg` to preserve the circular presentation.
Brand names use the `brand-` prefix and the `brands` category. They use the solid family with currentColor and a 24×24 viewBox, preserving the source geometry. Do not soften, distort or redraw trademark shapes to match interface icons. Keep source, version and license per entry. The selectable sprite still ships only requested icons.

## Repeatable full-set review
Run `python3 ops/web/icon_review.py --references /path/to/heroicons/optimized/24/outline --output preview/icon-review` to compare every icon at 16/20/24px with its Heroicons reference in light and dark modes. Related references are explicitly labeled when no exact counterpart exists. These review pages are local tooling, not shipped runtime assets.

Search aliases keep synonymous names on one gallery tile. `brand-microsoft` is the shared four-pane mark: `windows`, `microsoft`, and `brand-windows` resolve to it for search and selected sprites. `/icons/brand-windows.svg` remains as a compatibility copy. Use one canonical catalog entry rather than duplicate tiles.

For site-owned icon consumers, use `{{icon:name}}` in generated HTML templates via `icons.render_icons`, or a generated selected-icon ESM export for browser code. Create uses `library/page-icons.js` for Canvas and catalog markers for its toolbar. Avoid copying path strings into another runtime; no full icon registry is shipped.

## Status families
Use symbol-first names with explicit shape suffixes in new code:
- Warning: `warning-plain`, `warning-circle`, `warning-triangle`, `warning-shield`.
- Error: `error-plain`, `error-circle`.
- Info: `info-plain`, `info-circle`.
- Check: `check-plain`, `check-circle`, `check-shield`.
- Help: `help-plain`, `help-circle`.

Shapes carry meaning; do not generate every combination. Circle is general status, triangle means caution, and shield means protection or verification. Include text rather than relying on shape or color alone. Triangle interiors sit lower to preserve space near the apex.

The gallery shows one tile per unique drawing. `check-plain` and `help-plain` resolve to `check` and `help`; `error-plain` resolves to `close`. Success, question, exclamation, information, and x aliases support discovery without duplicate tiles. For compatibility, `warning` remains the triangle, `info` remains the circle, and `shield-check` resolves to `check-shield`; existing SVG URLs remain available.

`sparkle` is a single sparkle; `sparkles` is a group of three with one dominant shape and two smaller companions. Keep the same curved silhouette and open spacing at small sizes.

## Status icon usage across components
Shared notifications map success → `check-circle`, information → `info-circle`, warning → `warning-triangle`, and error → `error-circle`. `ops/web/icons.py` owns this mapping; generated Toast assets use current catalog artwork. Tooltip explanations use `info-circle`; verified/security badges use `check-shield`. Plain checks suit selection and completed checklist items, while plain close marks remain dismiss actions. Keep decorative SVG hidden from assistive technology and retain visible status text. Do not substitute a warning triangle for an error or use a shield for routine completion.

## Gallery order
The manifest’s groups list owns semantic family ordering. Keep every canonical icon in exactly one group. Directional families use left, right, up, down; status variants use plain, circle, triangle, shield where meaningful. Keep opposite pairs and singular/plural variants adjacent. Brand logos are alphabetical within their group. Search and category filtering preserve this order. Register new artwork in its family instead of appending it arbitrarily.

## Filled variants and Developer comparison
Developer → Icon Comparison (`/__dev/icons/`) shows the complete catalog at 16/20/24/32/48px in paired light/dark previews, with outline, filled, or both styles. Filled variants are separate SVG files under `solid/`, indexed by `solid/manifest.json`. The 84 initial counterparts are Heroicons v2.2.0 artwork under MIT; preserve `solid/LICENSE.txt` and per-entry provenance. Do not relabel them as original Layout UI artwork. Custom concepts without an exact counterpart are explicitly unavailable; brand artwork is unchanged. Never create a filled variant by changing the outline SVG's fill attribute.

After artwork/manifest changes run `python3 ops/web/icon_comparison.py`. CI checks the generated local-only catalog with `--check`. To export selected filled icons use `python3 ops/web/icons.py --manifest production/public/icons/solid/manifest.json --names home user --output preview/filled.svg`. Existing outline names and consumers are unchanged.
