Bundle the wireframe skill into the skills catalog

Adds the wireframe skill (low-fi black-and-white SVG wireframes + viewer
page) as a bundled catalog skill under
catalog/bundled/product/wireframe, alongside its references/ docs and
assets/ templates. Regenerates generated/catalog.json (8 -> 9 skills).

The skill ships static svg/html template assets, so its derived trust
level is "assets" rather than "markdown_only". The server's real
install-time security gate (assertCatalogSkillInstallable) blocks only
"scripts_executables", and "assets" skills are installable, so the
shipped-catalog markdown-only invariant is refined to gate on executable
scripts instead. No skill ships executable scripts.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-05-31 18:07:26 +00:00
parent 911a1e8b0d
commit 1cf3b792b5
9 changed files with 1624 additions and 3 deletions

View file

@ -0,0 +1,193 @@
---
name: wireframe
description: Produce low-fidelity black-and-white UI wireframes as standalone SVG files, optionally bundled into a single-page HTML viewer and published via the here-now skill. Use when the user asks to "wireframe X", "sketch a screen for", "draft a layout", "low-fi mockup", "rough mock", "make a page to view the wireframes", "build a viewer for these screens", or to "deploy / publish / host the wireframes". Do NOT use when the user wants production UI code, branded designs, hi-fi mockups, or animated/interactive prototypes — use frontend-design or similar instead.
key: paperclipai/bundled/product/wireframe
recommendedForRoles:
- designer
- product
- engineer
tags:
- design
- wireframe
- ux
- prototyping
- svg
---
# Wireframe
Produce low-fidelity, black-and-white UI wireframes as **standalone SVG files**. The goal is to communicate **structure** — what goes where, in what order, at roughly what size — without committing to colour, brand, or polish.
## When to use
Trigger on phrases like:
- "wireframe a [screen / page / flow] for X"
- "low-fi / lo-fi mockup of X"
- "draft a layout for X"
- "rough sketch of the [dashboard / settings / login / ...] page"
- "show me how X would lay out before I build it"
Skip and defer to `frontend-design` (or similar) when the request mentions: brand, polish, real components, "production-ready", colour palettes, hi-fi, Figma export, or actual code/HTML/React deliverables.
## House style — non-negotiable
Wireframes are diagnostic, not decorative. Lock these tokens on every output:
| Token | Value | Notes |
| ---------------- | ------------------------------------------------- | -------------------------------------- |
| Stroke | `#000` width `1.5` | All borders, dividers, outlines |
| Fill (boxes) | `#fff` | Default for cards/containers |
| Placeholder fill | `#e6e6e6` | Image/avatar/empty-state regions |
| Text colour | `#000` for labels, `#666` for placeholder text | No other colours |
| Accent | `#d33` (dashed) — annotation layer ONLY | Never inside real UI elements |
| Font | `font-family="-apple-system, system-ui, sans-serif"` | Single typeface across the whole file |
| Type scale | `12` caption · `14` body · `20` heading · `28` title | No other sizes |
| Grid | 8px snap, 24px gutter | All x/y/w/h must be multiples of 8 |
| Default canvas | `1280×800` desktop, `375×812` mobile, `768×1024` tablet | Pick one and state it in the comment |
If you need to highlight a specific region for a callout, use the **annotation layer** (red dashed). Never colourise the wireframe itself.
## Workflow
1. **Confirm scope.** What screen(s)? Which viewport (desktop / tablet / mobile)? Single screen or multi-screen flow? If unclear, ask one question, then proceed with the most likely default.
2. **Pick a canvas** from the table above. State the viewport in your reply.
3. **Compose from primitives.** Read `references/components.md` and assemble the screen from the primitive snippets. Snap every coordinate to 8px.
4. **Write the SVG to a file.** Default path: `wireframes/<slug>.svg` in the working directory. Filename slug describes the screen (`login.svg`, `dashboard.svg`, `settings-account.svg`).
5. **Emit a textual annotation list** in your reply, mapping each numbered region in the SVG to a one-line description ("1 — primary nav, 2 — search input, 3 — list of recent items"). This makes the wireframe accessible, queryable, and reviewable in text-only channels.
6. **For multi-screen flows**, produce one SVG per screen and a summary `flow.svg` that arranges thumbnails left-to-right with arrows between them.
## Quick start — minimal SVG
```svg
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<!-- canvas border -->
<rect x="0" y="0" width="1280" height="800" />
<!-- example: a button -->
<g transform="translate(48, 48)">
<rect width="120" height="40" rx="4" />
<text x="60" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#000">Continue</text>
</g>
</svg>
```
Two house-style gotchas worth memorising:
- Always set `stroke="none"` on `<text>` elements (text inherits the parent stroke and gets a halo otherwise).
- Always wrap text fill explicitly (`fill="#000"`) since the parent group fill is `#fff` for boxes.
## Primitive library
The full set of reusable primitives lives in `references/components.md`. Load it whenever you need a primitive whose exact markup you do not have in working memory. Do not re-derive primitives from scratch — copy the snippet and adjust coordinates.
Primitives provided:
- **Inputs:** button (filled, outlined, icon), text input, textarea, dropdown, checkbox, radio, toggle, search input
- **Layout:** card, section divider, sidebar, two-column, three-column
- **Navigation:** navbar, tab bar, breadcrumb, pagination, sidebar nav
- **Content:** heading, paragraph block, list row, table, key-value pair, metric tile
- **Media:** image placeholder, avatar (circle/square), video placeholder
- **Overlay:** modal, drawer, toast, tooltip, dropdown menu (open state)
- **Annotation:** numbered callout, dashed region highlight, arrow connector
## Grid, palette, and type scale
For exact pixel values, palette tokens, and type sizes, see `references/grid-system.md`.
## Worked examples
`references/examples.md` contains four complete wireframes you can copy and adapt:
1. Login screen (mobile, 375×812)
2. Admin dashboard (desktop, 1280×800)
3. Settings page with form (desktop, 1280×800)
4. Modal confirmation overlay (desktop, 1280×800)
When the user's request is close to one of these, start from the example and modify, rather than building from blank.
## Output convention
Every wireframe response should include:
1. The SVG file written to disk (path stated explicitly).
2. The SVG inlined in your reply (so it renders in markdown previews).
3. A short numbered annotation list mapping regions to intent.
4. Any explicit assumption you made (viewport, signed-in state, empty/populated, dark/light not applicable since monochrome).
## If the user requests a website / viewer page
Trigger on phrases like "make a page that shows the screens", "single page I can scroll", "build a viewer", "let me click through the wireframes", "show them all on one page", or any request to bundle multiple wireframes into a browsable artifact (not a production site).
Build **one static `index.html`** that loads the SVG wireframes directly. Do not turn this into a React app or component library — it's a review surface, not product UI.
**File layout** (default):
```
design/<task-slug>/
index.html
wireframes/ # the SVGs from this skill
screenshots/ # any reference screenshots
```
**Page anatomy** (start from `assets/site-template.html` and adjust — do not re-derive the CSS):
- **Sticky sidebar TOC** (240px on desktop) listing every screen with anchor links. Group by Flow / Screens / Open questions.
- **Hero header** at the top: crumb (issue id), title, one-paragraph summary, pill row of meta tags (`12 screens`, `Lo-fi · monochrome`, `Click any wireframe to zoom`).
- **One section per screen** with a 2-column grid: wireframe on the left, reference image + numbered annotations + a "Why this changes" callout on the right. The wireframe `<img>` points to the SVG file directly — do not inline it.
- **Click-to-zoom lightbox** for any element marked `[data-zoom]`. Esc and backdrop click both close.
- **Flow diagram** section near the top that loads `wireframes/flow.svg` full-width.
- **Open questions** section at the bottom for unresolved decisions.
**House style for the viewer** (matches the wireframes themselves):
- Palette: `--bg: #fafaf8`, `--panel: #fff`, `--ink: #111`, `--muted: #666`, `--line: #e5e5e0`, `--accent: #d33` (red dashed callouts only).
- System font stack only: `-apple-system, system-ui, "Segoe UI", sans-serif`. No web fonts.
- 8px-based spacing, `border-radius: 8px` on cards, 1px `--line` borders, no shadows except the hover lift on `.wire`.
- The viewer chrome is allowed to be slightly more polished than the wireframes (subtle hover, rounded cards) — but never colourful. The wireframes themselves stay strictly monochrome.
**Responsive** (verify before reporting done):
- ≥980px: two-column grid, sidebar TOC visible.
- 900980px: grid stacks to one column, TOC still sidebar.
- <900px (tablet/phone): TOC collapses to a sticky `<details>` disclosure at the top of the page, defaults closed; tapping a link auto-closes it. Sections get `scroll-margin-top: 80px` so anchor jumps clear the sticky bar.
- <560px (phone): tighter type/spacing scale, hero shrinks, lightbox switches from flex-centered to block layout at full viewport width with `touch-action: pinch-zoom` so users can pinch in further.
**Verification** before handing off: open the file in a browser and walk it at 1440×900, 768×1024, and 390×844. Confirm anchor jumps land cleanly, lightbox opens/closes, and SVGs render at the right aspect.
## If the user asks to deploy / publish / host the wireframes
Defer to the **`here-now` skill** — it owns publishing, anonymous vs. permanent sites, claim tokens, and credentials. Do not roll your own hosting.
Load the `here-now` skill and follow its `publish.sh` recipe. The shape is:
```bash
cd design/<task-slug>
{path-to-here-now}/scripts/publish.sh .
# → https://{adjective-noun-suffix}.here.now/
```
If `here-now` isn't installed for the current agent, install it (`npx skills add heredotnow/skill --skill here-now -g`) or escalate to whoever owns the agent's skill set. Do not roll your own hosting.
Things to remember when invoking it:
- Publish the **directory containing `index.html` at its root**, not the parent. `index.html` must be at the root of the published tree.
- Without saved credentials the site is **anonymous and expires in 24 hours**. With a saved API key, it's permanent. If the user wants a permanent URL, follow the `here-now` skill's sign-in-code flow — don't fake your way around it.
- For an update, pass `--slug {existing-slug}` so the URL stays stable across review rounds (the script auto-loads the claim token from `.herenow/state.json`).
- Read `publish_result.*` lines from script stderr to determine `auth_mode` and the claim URL — do not read `.herenow/state.json` and present its contents as the source of truth.
- Always share the `siteUrl` from the current run; if anonymous, also share the claim URL and the 24h expiry warning.
## What this skill is NOT for
- **Production UI code** — use `frontend-design` or write React/HTML directly.
- **Hi-fi or branded mockups** — use Figma or a design tool, not this.
- **Interactive prototypes** — SVG is static; export multi-screen flows as a flow.svg.
- **Diagrams of system architecture, sequence flows, or data models** — use mermaid or plantuml.
- **Illustrations or art** — use `example-skills:canvas-design` or `algorithmic-art`.
## Bundled assets
- `assets/template.svg` — blank desktop canvas with hidden 8px-grid guides; copy as a starting point.
- `assets/template-mobile.svg` — same for 375×812 mobile.
- `assets/site-template.html` — minimal review-viewer page (sticky TOC + responsive collapse + lightbox). Copy to `design/<task-slug>/index.html` and fill in the sections when the user requests a website.

View file

@ -0,0 +1,356 @@
<!doctype html>
<!--
Wireframe review viewer — copy to design/<task-slug>/index.html and fill in.
House style: monochrome, system font, 8px grid, sticky TOC, lightbox.
Replace TODO markers; duplicate <section class="screen"> per wireframe.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TODO — review viewer title</title>
<style>
:root {
--bg: #fafaf8;
--panel: #ffffff;
--ink: #111111;
--muted: #666666;
--line: #e5e5e0;
--accent: #d33;
--maxw: 1200px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font: 15px/1.55 -apple-system, system-ui, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--ink);
-webkit-font-smoothing: antialiased;
}
a { color: inherit; }
.shell { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; }
.toc {
position: sticky; top: 0; align-self: start;
height: 100vh; overflow: auto;
padding: 24px 16px 24px 24px;
border-right: 1px solid var(--line);
background: var(--panel);
font-size: 13px;
z-index: 20;
}
.toc h1 { font-size: 14px; margin: 0 0 4px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.toc h2 { font-size: 12px; margin: 24px 0 6px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.toc a { display: block; text-decoration: none; padding: 6px 8px; margin-left: -8px; border-radius: 4px; color: var(--ink); }
.toc a:hover { background: var(--bg); }
.toc .num { display: inline-block; width: 22px; color: var(--muted); }
.toc .toc-summary { display: none; }
.toc .toc-body { display: block; }
main { padding: 48px 56px 96px; max-width: 1300px; min-width: 0; }
header.hero { max-width: var(--maxw); margin-bottom: 64px; }
header.hero .crumb { color: var(--muted); font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; }
header.hero h1 { font-size: 36px; line-height: 1.15; margin: 8px 0 16px; }
header.hero p { color: var(--muted); max-width: 720px; font-size: 16px; }
header.hero .pills { margin-top: 20px; display: flex; gap: 8px; flex-wrap: wrap; }
header.hero .pill { font-size: 12px; padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--panel); color: var(--muted); }
section { scroll-margin-top: 24px; max-width: var(--maxw); margin-bottom: 96px; }
section .lede { color: var(--muted); margin: 0 0 12px; font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; font-weight: 700; }
section h2 { font-size: 28px; line-height: 1.2; margin: 0 0 8px; }
section h2 .step-num { display: inline-block; width: 40px; color: var(--muted); }
section h3 { font-size: 16px; margin: 24px 0 8px; }
section .desc { color: var(--muted); margin: 0 0 24px; max-width: 760px; }
/* canvas: desktop wire (wide) on left, mobile wire (tall narrow) middle, notes right.
Mobile is a first-class deliverable — never tabbed, never stacked away. */
.grid {
display: grid; gap: 24px;
grid-template-columns: minmax(0, 1.4fr) 220px minmax(0, 0.9fr);
align-items: start;
}
@media (max-width: 1180px) {
.grid { grid-template-columns: minmax(0, 1fr) 200px; }
.grid > .notes-col { grid-column: 1 / -1; }
}
@media (max-width: 980px) { .grid { grid-template-columns: 1fr; } .grid > .mobile-col { max-width: 280px; } }
/* tablet */
@media (max-width: 900px) {
.shell { grid-template-columns: 1fr; }
.toc {
position: sticky; top: 0;
height: auto; max-height: 60vh;
padding: 12px 16px;
border-right: none;
border-bottom: 1px solid var(--line);
}
.toc .toc-summary {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
list-style: none;
padding: 4px 0;
}
.toc .toc-summary::-webkit-details-marker { display: none; }
.toc .toc-summary .title { font-size: 13px; font-weight: 600; }
.toc .toc-summary .crumb { font-size: 11px; color: var(--muted); letter-spacing: 0.04em; text-transform: uppercase; }
.toc .toc-summary .chevron {
width: 10px; height: 10px;
border-right: 1.5px solid var(--ink);
border-bottom: 1.5px solid var(--ink);
transform: rotate(45deg);
transition: transform 120ms ease;
margin-right: 4px;
}
.toc[open] .toc-summary .chevron { transform: rotate(225deg) translate(-2px, -2px); }
.toc .toc-body { display: none; padding-top: 8px; border-top: 1px solid var(--line); margin-top: 8px; }
.toc[open] .toc-body { display: block; }
main { padding: 24px 20px 64px; }
header.hero { margin-bottom: 40px; }
header.hero h1 { font-size: 28px; }
header.hero p { font-size: 15px; }
section { margin-bottom: 64px; scroll-margin-top: 80px; }
section h2 { font-size: 24px; }
section h2 .step-num { width: 32px; }
.flow-section { padding: 20px; margin-bottom: 40px; scroll-margin-top: 80px; }
}
/* phone */
@media (max-width: 560px) {
main { padding: 16px 14px 56px; }
header.hero { margin-bottom: 32px; }
header.hero h1 { font-size: 24px; line-height: 1.2; }
header.hero p { font-size: 14px; }
section { margin-bottom: 48px; }
section h2 { font-size: 20px; line-height: 1.25; }
section h2 .step-num { width: 28px; }
section h3 { font-size: 15px; margin: 20px 0 6px; }
section .desc { font-size: 14px; }
.grid { gap: 16px; }
.flow-section { padding: 16px; border-radius: 8px; }
.wire .label { padding: 8px 10px; font-size: 11px; gap: 8px; }
body { font-size: 14px; }
}
.wire {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
overflow: hidden;
cursor: zoom-in;
transition: transform 80ms ease, box-shadow 80ms ease;
}
.wire:hover { box-shadow: 0 8px 24px rgba(0,0,0,0.08); }
.wire .label {
padding: 10px 14px;
font-size: 12px;
color: var(--muted);
border-bottom: 1px solid var(--line);
display: flex; justify-content: space-between; align-items: center;
}
.wire img { display: block; width: 100%; height: auto; background: #fff; }
/* mobile wire renders narrow inside its 220px column so it reads as a phone */
.wire.mobile-wire img { max-width: 220px; margin: 0 auto; }
.ref {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 14px;
}
.ref .label { font-size: 12px; color: var(--muted); margin-bottom: 8px; display: flex; justify-content: space-between; align-items: baseline; }
.ref img { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: 4px; cursor: zoom-in; }
.notes ul { padding-left: 20px; margin: 8px 0; }
.notes li { margin-bottom: 6px; }
.notes .why {
background: var(--panel);
border: 1px solid var(--line);
border-left: 3px solid var(--ink);
padding: 14px 18px;
border-radius: 4px;
margin-top: 12px;
font-size: 14px;
}
.flow-section { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 32px; margin-bottom: 64px; max-width: var(--maxw); }
.flow-section h2 { margin-top: 0; }
.flow-section .wire { margin-top: 16px; }
/* lightbox */
.lightbox {
position: fixed; inset: 0;
background: rgba(0,0,0,0.86);
display: none;
align-items: center; justify-content: center;
z-index: 100;
cursor: zoom-out;
padding: 32px;
}
.lightbox.open { display: flex; }
.lightbox img { max-width: 100%; max-height: 100%; background: #fff; box-shadow: 0 30px 80px rgba(0,0,0,0.4); border-radius: 4px; }
.lightbox .close { position: absolute; top: 16px; right: 24px; font-size: 28px; color: #fff; cursor: pointer; }
.lightbox .caption { position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 13px; opacity: 0.9; }
/* mobile lightbox — must come after the desktop rules to win the cascade */
@media (max-width: 560px) {
.lightbox { padding: 12px; overflow: auto; touch-action: pinch-zoom; }
.lightbox.open { display: block; }
.lightbox img { display: block; width: 100%; max-width: none; max-height: none; height: auto; margin: 40px auto 56px; box-shadow: none; }
.lightbox .close { position: fixed; top: 8px; right: 12px; font-size: 32px; }
.lightbox .caption { position: fixed; left: 50%; transform: translateX(-50%); font-size: 12px; bottom: 12px; text-align: center; background: rgba(0,0,0,0.7); border-radius: 4px; padding: 6px 12px; }
}
.footer { color: var(--muted); font-size: 12px; max-width: var(--maxw); border-top: 1px solid var(--line); padding-top: 24px; }
</style>
</head>
<body>
<div class="shell">
<details class="toc">
<summary class="toc-summary">
<span>
<span class="crumb">TODO-ID · TODO short title</span><br>
<span class="title">Jump to a screen</span>
</span>
<span class="chevron" aria-hidden="true"></span>
</summary>
<nav class="toc-body" aria-label="Section navigation">
<h1>TODO-ID</h1>
<div style="font-size: 13px; color: var(--muted); margin-bottom: 16px;">TODO short title</div>
<h2>Flow</h2>
<a href="#flow"><span class="num"></span>Flow diagram</a>
<h2>Screens</h2>
<a href="#s1"><span class="num">1</span>TODO screen 1 name</a>
<!-- duplicate per screen -->
<h2>Open questions</h2>
<a href="#open"><span class="num">?</span>For reviewer</a>
</nav>
</details>
<main>
<header class="hero">
<div class="crumb">TODO-ID · Wireframes</div>
<h1>TODO — long title</h1>
<p>TODO — one-paragraph summary of what this set covers and why.</p>
<div class="pills">
<span class="pill">TODO N screens</span>
<span class="pill">Desktop + mobile</span>
<span class="pill">Lo-fi · monochrome</span>
<span class="pill">Click any wireframe to zoom</span>
</div>
</header>
<!-- ========== Flow diagram (optional) ========== -->
<section id="flow" class="flow-section">
<div class="lede">Flow</div>
<h2>TODO — flow title</h2>
<p class="desc">TODO — describe arrow conventions (solid = happy path, dashed = branch).</p>
<div class="wire" data-zoom data-caption="Flow diagram">
<div class="label"><span>flow.svg</span><span>TODO WxH</span></div>
<img src="wireframes/flow.svg" alt="Flow diagram" />
</div>
</section>
<!-- ========== Screen template — duplicate per screen ==========
Three cells per row: desktop wire | mobile wire | notes-col.
Mobile is mandatory; only drop it when the user explicitly opted out. -->
<section id="s1">
<div class="lede">Step 1 · TODO group</div>
<h2><span class="step-num">1.</span>TODO screen name</h2>
<p class="desc">TODO — one-paragraph describing the screen's purpose.</p>
<div class="grid">
<div class="wire" data-zoom data-caption="01 · TODO screen name (desktop)">
<div class="label"><span>01-screen.svg</span><span>1280×800 · desktop</span></div>
<img src="wireframes/01-screen.svg" alt="TODO screen name (desktop) wireframe" />
</div>
<div class="wire mobile-wire mobile-col" data-zoom data-caption="01 · TODO screen name (mobile)">
<div class="label"><span>mobile</span><span>375×812</span></div>
<img src="wireframes/01-screen-mobile.svg" alt="TODO screen name (mobile) wireframe" />
</div>
<div class="notes-col">
<!-- Optional reference (real-world example we're modelling) -->
<div class="ref">
<div class="label"><span>Reference</span><span>TODO source</span></div>
<img src="screenshots/TODO-ref.png" alt="TODO reference" data-zoom />
</div>
<div class="notes">
<h3>Annotations</h3>
<ul>
<li><b>1</b> — TODO callout 1.</li>
<li><b>2</b> — TODO callout 2 (note where mobile diverges).</li>
</ul>
<div class="why"><b>Why this changes:</b> TODO rationale vs current state.</div>
</div>
</div>
</div>
</section>
<!-- ========== Open questions ========== -->
<section id="open">
<div class="lede">For reviewer</div>
<h2>Open questions</h2>
<div class="notes">
<ul>
<li><b>TODO</b> — Open question 1.</li>
</ul>
</div>
</section>
<div class="footer">
Wireframes follow the in-tree <code>wireframe</code> skill house style — black on white, 1.5pt strokes, 8px grid, type sizes 12/14/20/28. Red dashed annotations are the call-out layer only and not part of the proposed UI.
</div>
</main>
</div>
<div class="lightbox" id="lb" aria-hidden="true">
<span class="close" id="lbClose">×</span>
<img id="lbImg" alt="" />
<div class="caption" id="lbCap"></div>
</div>
<script>
// Click-to-zoom: any element marked [data-zoom] OR any <img> inside [data-zoom]
const lb = document.getElementById('lb');
const lbImg = document.getElementById('lbImg');
const lbCap = document.getElementById('lbCap');
const lbClose = document.getElementById('lbClose');
document.querySelectorAll('[data-zoom]').forEach((el) => {
el.addEventListener('click', () => {
const target = el.tagName === 'IMG' ? el : el.querySelector('img');
if (!target) return;
lbImg.src = target.src;
lbImg.alt = target.alt;
lbCap.textContent = el.dataset.caption || target.alt || '';
lb.classList.add('open');
});
});
function close() { lb.classList.remove('open'); }
lb.addEventListener('click', close);
lbClose.addEventListener('click', close);
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
// TOC: open by default on desktop (<details> would otherwise hide body),
// closed by default on mobile so the long list doesn't dominate the viewport.
const toc = document.querySelector('details.toc');
if (toc) {
const mq = window.matchMedia('(max-width: 900px)');
const apply = () => { toc.open = !mq.matches; };
apply();
mq.addEventListener('change', apply);
// On mobile, collapse the TOC after the user taps a link.
toc.querySelectorAll('.toc-body a').forEach((a) => {
a.addEventListener('click', () => { if (mq.matches) toc.open = false; });
});
}
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<!-- canvas -->
<rect x="0" y="0" width="375" height="812" />
<!-- 8px grid guides -->
<g data-region="grid" stroke="#e6e6e6" stroke-width="0.5" fill="none">
<path d="M 16 0 V 812" stroke="#d33" stroke-dasharray="2 4" />
<path d="M 359 0 V 812" stroke="#d33" stroke-dasharray="2 4" />
<!-- safe-area top (status bar 44px) and bottom (home indicator 34px) -->
<line x1="0" y1="44" x2="375" y2="44" />
<line x1="0" y1="778" x2="375" y2="778" />
</g>
<!-- replace this comment with your mobile wireframe primitives.
Mobile defaults:
- 16px outer margin (content width 343)
- 48px tap target minimum
- status-bar reserve 0-44, home-indicator reserve 778-812
-->
</svg>

After

Width:  |  Height:  |  Size: 925 B

View file

@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<!-- canvas -->
<rect x="0" y="0" width="1280" height="800" />
<!-- 8px grid guides (toggle visibility by removing or commenting this group) -->
<g data-region="grid" stroke="#e6e6e6" stroke-width="0.5" fill="none">
<!-- vertical lines every 8px -->
<path d="M 8 0 V 800 M 16 0 V 800 M 24 0 V 800 M 32 0 V 800 M 40 0 V 800 M 48 0 V 800 M 56 0 V 800 M 64 0 V 800 M 72 0 V 800 M 80 0 V 800 M 88 0 V 800 M 96 0 V 800" />
<!-- column ruler at 24px outer margin + 12 columns of 88 with 8 gutter -->
<path d="M 48 0 V 800" stroke="#d33" stroke-dasharray="2 4" />
<path d="M 1232 0 V 800" stroke="#d33" stroke-dasharray="2 4" />
</g>
<!-- replace this comment block with your wireframe primitives.
Each primitive should be wrapped in its own <g transform="translate(x, y)"> with a numbered comment:
<!- - 1: navbar - ->
<g transform="translate(0, 0)" data-region="navbar"> ... </g>
Then in your reply, list:
1 - navbar with global search
2 - sidebar with primary nav
...
-->
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,482 @@
# Component primitives
Copy these snippets directly into your SVG. Each primitive is wrapped in a `<g transform="translate(0, 0)">` so you can position it by changing the translate values. All sizes follow the 8px grid and the type scale defined in `grid-system.md`.
## Conventions used below
- `W`, `H` placeholders mean "pick a multiple of 8 that fits your layout".
- `Label`, `Placeholder`, etc. mean "replace with the actual copy".
- The root `<svg>` is assumed to set: `font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5"`.
- Always set `stroke="none"` on `<text>` elements.
---
## Inputs
### Button (filled)
```svg
<g transform="translate(0,0)">
<rect width="120" height="40" rx="4" fill="#000" />
<text x="60" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#fff">Continue</text>
</g>
```
### Button (outlined / secondary)
```svg
<g transform="translate(0,0)">
<rect width="120" height="40" rx="4" />
<text x="60" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#000">Cancel</text>
</g>
```
### Button (icon-only square)
```svg
<g transform="translate(0,0)">
<rect width="40" height="40" rx="4" />
<!-- glyph: a plus -->
<line x1="14" y1="20" x2="26" y2="20" />
<line x1="20" y1="14" x2="20" y2="26" />
</g>
```
### Text input
```svg
<g transform="translate(0,0)">
<rect width="320" height="40" rx="4" />
<text x="12" y="25" font-size="14" stroke="none" fill="#666">Placeholder text</text>
</g>
```
### Search input (with magnifier)
```svg
<g transform="translate(0,0)">
<rect width="480" height="40" rx="20" />
<circle cx="20" cy="20" r="6" />
<line x1="24" y1="24" x2="30" y2="30" />
<text x="40" y="25" font-size="14" stroke="none" fill="#666">Search…</text>
</g>
```
### Textarea
```svg
<g transform="translate(0,0)">
<rect width="480" height="120" rx="4" />
<text x="12" y="24" font-size="14" stroke="none" fill="#666">Type your message…</text>
</g>
```
### Dropdown (collapsed)
```svg
<g transform="translate(0,0)">
<rect width="200" height="40" rx="4" />
<text x="12" y="25" font-size="14" stroke="none" fill="#000">Selected value</text>
<!-- chevron -->
<polyline points="180,17 188,25 180,33" fill="none" />
</g>
```
### Checkbox (unchecked / checked)
```svg
<!-- unchecked -->
<g transform="translate(0,0)">
<rect width="20" height="20" rx="2" />
</g>
<!-- checked -->
<g transform="translate(0,0)">
<rect width="20" height="20" rx="2" fill="#000" />
<polyline points="4,11 9,16 16,6" stroke="#fff" fill="none" />
</g>
```
### Radio (unselected / selected)
```svg
<!-- unselected -->
<g transform="translate(0,0)">
<circle cx="10" cy="10" r="9" />
</g>
<!-- selected -->
<g transform="translate(0,0)">
<circle cx="10" cy="10" r="9" />
<circle cx="10" cy="10" r="4" fill="#000" />
</g>
```
### Toggle (off / on)
```svg
<!-- off -->
<g transform="translate(0,0)">
<rect width="40" height="20" rx="10" />
<circle cx="10" cy="10" r="6" fill="#000" />
</g>
<!-- on -->
<g transform="translate(0,0)">
<rect width="40" height="20" rx="10" fill="#000" />
<circle cx="30" cy="10" r="6" fill="#fff" />
</g>
```
### Form field (label + input + help)
```svg
<g transform="translate(0,0)">
<text x="0" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Email address</text>
<g transform="translate(0,24)">
<rect width="320" height="40" rx="4" />
<text x="12" y="25" font-size="14" stroke="none" fill="#666">you@example.com</text>
</g>
<text x="0" y="84" font-size="12" stroke="none" fill="#666">We'll never share this with anyone.</text>
</g>
```
---
## Layout
### Card
```svg
<g transform="translate(0,0)">
<rect width="384" height="200" rx="6" />
<text x="24" y="40" font-size="20" font-weight="600" stroke="none" fill="#000">Card title</text>
<text x="24" y="68" font-size="14" stroke="none" fill="#666">Supporting copy goes here.</text>
<line x1="0" y1="160" x2="384" y2="160" />
<text x="24" y="184" font-size="14" stroke="none" fill="#000">Footer action</text>
</g>
```
### Section divider
```svg
<g transform="translate(0,0)">
<line x1="0" y1="0" x2="1184" y2="0" stroke="#666" />
</g>
```
### Two-column split (60 / 40)
```svg
<g transform="translate(0,0)">
<rect width="704" height="400" />
<rect x="728" width="456" height="400" />
</g>
```
---
## Navigation
### Navbar (top)
```svg
<g transform="translate(0,0)">
<rect width="1280" height="64" />
<!-- logo placeholder -->
<rect x="24" y="16" width="32" height="32" rx="4" fill="#e6e6e6" />
<!-- nav items -->
<text x="80" y="40" font-size="14" stroke="none" fill="#000">Dashboard</text>
<text x="184" y="40" font-size="14" stroke="none" fill="#666">Projects</text>
<text x="272" y="40" font-size="14" stroke="none" fill="#666">Reports</text>
<text x="352" y="40" font-size="14" stroke="none" fill="#666">Settings</text>
<!-- right side: avatar -->
<circle cx="1240" cy="32" r="16" fill="#e6e6e6" />
</g>
```
### Sidebar nav
```svg
<g transform="translate(0,0)">
<rect width="240" height="800" />
<text x="24" y="40" font-size="20" font-weight="600" stroke="none" fill="#000">App</text>
<!-- active item -->
<rect x="0" y="80" width="240" height="40" fill="#e6e6e6" />
<text x="24" y="105" font-size="14" stroke="none" fill="#000">Dashboard</text>
<!-- inactive items -->
<text x="24" y="153" font-size="14" stroke="none" fill="#666">Projects</text>
<text x="24" y="201" font-size="14" stroke="none" fill="#666">Reports</text>
<text x="24" y="249" font-size="14" stroke="none" fill="#666">Team</text>
<text x="24" y="297" font-size="14" stroke="none" fill="#666">Settings</text>
</g>
```
### Tab bar
```svg
<g transform="translate(0,0)">
<line x1="0" y1="48" x2="640" y2="48" />
<!-- active tab -->
<text x="24" y="32" font-size="14" font-weight="600" stroke="none" fill="#000">Overview</text>
<line x1="16" y1="48" x2="104" y2="48" stroke-width="3" />
<!-- inactive tabs -->
<text x="136" y="32" font-size="14" stroke="none" fill="#666">Activity</text>
<text x="232" y="32" font-size="14" stroke="none" fill="#666">Members</text>
<text x="328" y="32" font-size="14" stroke="none" fill="#666">Settings</text>
</g>
```
### Breadcrumb
```svg
<g transform="translate(0,0)">
<text x="0" y="14" font-size="14" stroke="none" fill="#666">Workspace</text>
<text x="80" y="14" font-size="14" stroke="none" fill="#666">/</text>
<text x="96" y="14" font-size="14" stroke="none" fill="#666">Projects</text>
<text x="160" y="14" font-size="14" stroke="none" fill="#666">/</text>
<text x="176" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Project name</text>
</g>
```
### Pagination
```svg
<g transform="translate(0,0)">
<rect width="32" height="32" />
<text x="16" y="21" font-size="14" text-anchor="middle" stroke="none" fill="#666"></text>
<rect x="40" width="32" height="32" fill="#000" />
<text x="56" y="21" font-size="14" text-anchor="middle" stroke="none" fill="#fff">1</text>
<rect x="80" width="32" height="32" />
<text x="96" y="21" font-size="14" text-anchor="middle" stroke="none" fill="#000">2</text>
<rect x="120" width="32" height="32" />
<text x="136" y="21" font-size="14" text-anchor="middle" stroke="none" fill="#000">3</text>
<rect x="160" width="32" height="32" />
<text x="176" y="21" font-size="14" text-anchor="middle" stroke="none" fill="#666"></text>
</g>
```
---
## Content
### Heading
```svg
<text x="0" y="28" font-size="28" font-weight="700" stroke="none" fill="#000">Page title</text>
```
### Section heading
```svg
<text x="0" y="20" font-size="20" font-weight="600" stroke="none" fill="#000">Section</text>
```
### Paragraph block (placeholder lines)
```svg
<g transform="translate(0,0)">
<line x1="0" y1="8" x2="480" y2="8" stroke="#666" />
<line x1="0" y1="24" x2="480" y2="24" stroke="#666" />
<line x1="0" y1="40" x2="320" y2="40" stroke="#666" />
</g>
```
### List row (avatar + title + subtitle + chevron)
```svg
<g transform="translate(0,0)">
<rect width="800" height="56" />
<circle cx="32" cy="28" r="16" fill="#e6e6e6" />
<text x="64" y="24" font-size="14" font-weight="600" stroke="none" fill="#000">Primary text</text>
<text x="64" y="40" font-size="12" stroke="none" fill="#666">Secondary text</text>
<polyline points="772,20 780,28 772,36" fill="none" />
</g>
```
### Table
```svg
<g transform="translate(0,0)">
<!-- header -->
<rect width="800" height="48" fill="#f4f4f4" />
<text x="16" y="30" font-size="12" font-weight="600" stroke="none" fill="#000">Name</text>
<text x="280" y="30" font-size="12" font-weight="600" stroke="none" fill="#000">Status</text>
<text x="480" y="30" font-size="12" font-weight="600" stroke="none" fill="#000">Updated</text>
<text x="680" y="30" font-size="12" font-weight="600" stroke="none" fill="#000">Owner</text>
<!-- rows -->
<g transform="translate(0,48)">
<rect width="800" height="48" />
<text x="16" y="30" font-size="14" stroke="none" fill="#000">Row title 1</text>
<text x="280" y="30" font-size="14" stroke="none" fill="#666">Active</text>
<text x="480" y="30" font-size="14" stroke="none" fill="#666">2h ago</text>
<text x="680" y="30" font-size="14" stroke="none" fill="#666">Alex</text>
</g>
<g transform="translate(0,96)">
<rect width="800" height="48" />
<text x="16" y="30" font-size="14" stroke="none" fill="#000">Row title 2</text>
<text x="280" y="30" font-size="14" stroke="none" fill="#666">Pending</text>
<text x="480" y="30" font-size="14" stroke="none" fill="#666">5h ago</text>
<text x="680" y="30" font-size="14" stroke="none" fill="#666">Sam</text>
</g>
</g>
```
### Key-value pair
```svg
<g transform="translate(0,0)">
<text x="0" y="14" font-size="12" stroke="none" fill="#666">Created</text>
<text x="0" y="34" font-size="14" stroke="none" fill="#000">Mar 12, 2026</text>
</g>
```
### Metric tile
```svg
<g transform="translate(0,0)">
<rect width="240" height="120" rx="6" />
<text x="24" y="40" font-size="12" stroke="none" fill="#666">Active users</text>
<text x="24" y="80" font-size="28" font-weight="700" stroke="none" fill="#000">1,284</text>
<text x="24" y="104" font-size="12" stroke="none" fill="#666">+12% vs last week</text>
</g>
```
---
## Media
### Image placeholder (with diagonal cross)
```svg
<g transform="translate(0,0)">
<rect width="240" height="160" fill="#e6e6e6" />
<line x1="0" y1="0" x2="240" y2="160" stroke="#666" />
<line x1="240" y1="0" x2="0" y2="160" stroke="#666" />
</g>
```
### Avatar (circular, with cross)
```svg
<g transform="translate(0,0)">
<circle cx="24" cy="24" r="24" fill="#e6e6e6" />
<line x1="7" y1="7" x2="41" y2="41" stroke="#666" />
<line x1="41" y1="7" x2="7" y2="41" stroke="#666" />
</g>
```
### Video placeholder (image + play triangle)
```svg
<g transform="translate(0,0)">
<rect width="320" height="180" fill="#e6e6e6" />
<line x1="0" y1="0" x2="320" y2="180" stroke="#666" />
<line x1="320" y1="0" x2="0" y2="180" stroke="#666" />
<circle cx="160" cy="90" r="32" fill="#fff" />
<polygon points="150,76 150,104 178,90" fill="#000" stroke="none" />
</g>
```
---
## Overlay
### Modal (with backdrop)
```svg
<!-- backdrop dims the canvas; render this inside an SVG that already has the underlying screen -->
<rect x="0" y="0" width="1280" height="800" fill="#000" fill-opacity="0.4" stroke="none" />
<g transform="translate(320, 200)">
<rect width="640" height="400" rx="6" />
<text x="24" y="48" font-size="20" font-weight="600" stroke="none" fill="#000">Confirm action</text>
<line x1="0" y1="72" x2="640" y2="72" />
<text x="24" y="112" font-size="14" stroke="none" fill="#000">Body copy goes here describing what's about to happen.</text>
<line x1="0" y1="328" x2="640" y2="328" />
<!-- footer actions -->
<g transform="translate(384, 348)">
<rect width="120" height="40" rx="4" />
<text x="60" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#000">Cancel</text>
</g>
<g transform="translate(512, 348)">
<rect width="104" height="40" rx="4" fill="#000" />
<text x="52" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#fff">Confirm</text>
</g>
</g>
```
### Toast
```svg
<g transform="translate(0,0)">
<rect width="320" height="56" rx="6" fill="#000" />
<text x="24" y="34" font-size="14" stroke="none" fill="#fff">Saved successfully</text>
<text x="280" y="34" font-size="14" stroke="none" fill="#fff">×</text>
</g>
```
### Tooltip
```svg
<g transform="translate(0,0)">
<rect width="160" height="32" rx="4" fill="#000" />
<text x="80" y="20" font-size="12" text-anchor="middle" stroke="none" fill="#fff">Helpful explanation</text>
<polygon points="76,32 80,40 84,32" fill="#000" stroke="none" />
</g>
```
### Dropdown menu (open state)
```svg
<g transform="translate(0,0)">
<rect width="200" height="160" rx="4" />
<text x="16" y="28" font-size="14" stroke="none" fill="#000">Menu item one</text>
<text x="16" y="60" font-size="14" stroke="none" fill="#000">Menu item two</text>
<text x="16" y="92" font-size="14" stroke="none" fill="#000">Menu item three</text>
<line x1="0" y1="108" x2="200" y2="108" />
<text x="16" y="136" font-size="14" stroke="none" fill="#000">Sign out</text>
</g>
```
---
## Annotation layer
Use these for callouts and reviewer notes. Render them in a final `<g data-region="annotations">` so reviewers can hide them by toggling the group.
### Numbered callout
```svg
<g transform="translate(0,0)">
<circle cx="0" cy="0" r="12" fill="#fff" stroke="#d33" stroke-dasharray="4 2" />
<text x="0" y="4" font-size="12" font-weight="700" text-anchor="middle" stroke="none" fill="#d33">1</text>
</g>
```
### Dashed region highlight
```svg
<rect x="0" y="0" width="240" height="120" fill="none" stroke="#d33" stroke-dasharray="6 3" />
```
### Arrow connector (between two screens)
```svg
<g transform="translate(0,0)">
<line x1="0" y1="0" x2="120" y2="0" stroke="#000" />
<polygon points="120,0 110,-6 110,6" fill="#000" stroke="none" />
</g>
```
---
## Common composition mistakes (avoid)
- **Text with halo:** forgetting `stroke="none"` on `<text>`. The text inherits the parent stroke.
- **Off-grid coordinates:** values like `x="37"` instead of `x="40"`. Snap everything to multiples of 8.
- **Solid fills sneaking in:** anything other than `#fff`, `#e6e6e6`, `#f4f4f4`, or `#000` is a mistake.
- **Multiple typefaces:** stick to one font-family across the whole file.
- **Annotation colour bleeding into UI:** `#d33` only ever appears inside the annotation `<g>`.
- **Missing `viewBox`:** without it, the SVG won't scale when embedded in different containers.

View file

@ -0,0 +1,362 @@
# Worked examples
Four complete wireframes you can copy and modify. Each one is a valid standalone SVG file. The annotation list under each example is what you should reproduce in your reply when emitting a wireframe.
---
## 1. Login screen (mobile, 375×812)
```svg
<svg xmlns="http://www.w3.org/2000/svg" width="375" height="812" viewBox="0 0 375 812"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<rect x="0" y="0" width="375" height="812" />
<!-- 1: status bar (placeholder) -->
<g transform="translate(16, 16)">
<text x="0" y="14" font-size="12" stroke="none" fill="#666">9:41</text>
<text x="343" y="14" font-size="12" text-anchor="end" stroke="none" fill="#666">100%</text>
</g>
<!-- 2: brand mark -->
<g transform="translate(159, 120)">
<rect width="56" height="56" rx="8" fill="#e6e6e6" />
</g>
<!-- 3: title -->
<text x="187" y="216" font-size="28" font-weight="700" text-anchor="middle" stroke="none" fill="#000">Welcome back</text>
<text x="187" y="248" font-size="14" text-anchor="middle" stroke="none" fill="#666">Sign in to continue</text>
<!-- 4: email field -->
<g transform="translate(16, 296)">
<text x="0" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Email</text>
<g transform="translate(0, 24)">
<rect width="343" height="48" rx="4" />
<text x="12" y="30" font-size="14" stroke="none" fill="#666">you@example.com</text>
</g>
</g>
<!-- 5: password field -->
<g transform="translate(16, 400)">
<text x="0" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Password</text>
<g transform="translate(0, 24)">
<rect width="343" height="48" rx="4" />
<text x="12" y="30" font-size="14" stroke="none" fill="#666">••••••••</text>
<text x="331" y="30" font-size="12" text-anchor="end" stroke="none" fill="#666">show</text>
</g>
<text x="343" y="92" font-size="12" text-anchor="end" stroke="none" fill="#666">Forgot password?</text>
</g>
<!-- 6: primary CTA -->
<g transform="translate(16, 528)">
<rect width="343" height="48" rx="4" fill="#000" />
<text x="171" y="30" font-size="14" font-weight="600" text-anchor="middle" stroke="none" fill="#fff">Sign in</text>
</g>
<!-- 7: divider -->
<g transform="translate(16, 600)">
<line x1="0" y1="8" x2="140" y2="8" stroke="#666" />
<text x="171" y="12" font-size="12" text-anchor="middle" stroke="none" fill="#666">or</text>
<line x1="203" y1="8" x2="343" y2="8" stroke="#666" />
</g>
<!-- 8: secondary CTA -->
<g transform="translate(16, 632)">
<rect width="343" height="48" rx="4" />
<text x="171" y="30" font-size="14" text-anchor="middle" stroke="none" fill="#000">Continue with SSO</text>
</g>
<!-- 9: footer -->
<text x="187" y="744" font-size="14" text-anchor="middle" stroke="none" fill="#000">New here? <tspan font-weight="600">Create an account</tspan></text>
</svg>
```
**Annotations:**
1. Status bar placeholder
2. Brand mark
3. Page title + subtitle
4. Email input
5. Password input + reveal control + forgot link
6. Primary CTA (sign in)
7. SSO divider
8. Secondary CTA (SSO)
9. Sign-up link
---
## 2. Admin dashboard (desktop, 1280×800)
```svg
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<rect x="0" y="0" width="1280" height="800" />
<!-- 1: sidebar -->
<g transform="translate(0, 0)" data-region="sidebar">
<rect width="240" height="800" />
<text x="24" y="40" font-size="20" font-weight="600" stroke="none" fill="#000">Acme</text>
<rect x="0" y="80" width="240" height="40" fill="#e6e6e6" />
<text x="24" y="105" font-size="14" stroke="none" fill="#000">Dashboard</text>
<text x="24" y="153" font-size="14" stroke="none" fill="#666">Projects</text>
<text x="24" y="201" font-size="14" stroke="none" fill="#666">Reports</text>
<text x="24" y="249" font-size="14" stroke="none" fill="#666">Team</text>
<text x="24" y="297" font-size="14" stroke="none" fill="#666">Settings</text>
</g>
<!-- 2: top bar -->
<g transform="translate(240, 0)" data-region="topbar">
<rect width="1040" height="64" />
<g transform="translate(24, 12)">
<rect width="400" height="40" rx="20" />
<circle cx="20" cy="20" r="6" />
<line x1="24" y1="24" x2="30" y2="30" />
<text x="40" y="25" font-size="14" stroke="none" fill="#666">Search…</text>
</g>
<circle cx="1000" cy="32" r="16" fill="#e6e6e6" />
</g>
<!-- 3: page header -->
<g transform="translate(264, 96)" data-region="header">
<text x="0" y="28" font-size="28" font-weight="700" stroke="none" fill="#000">Dashboard</text>
<text x="0" y="56" font-size="14" stroke="none" fill="#666">Overview of activity for the last 7 days</text>
<g transform="translate(872, 0)">
<rect width="120" height="40" rx="4" fill="#000" />
<text x="60" y="25" font-size="14" font-weight="600" text-anchor="middle" stroke="none" fill="#fff">New project</text>
</g>
</g>
<!-- 4: metric tiles -->
<g transform="translate(264, 184)" data-region="metrics">
<g transform="translate(0, 0)">
<rect width="232" height="120" rx="6" />
<text x="24" y="40" font-size="12" stroke="none" fill="#666">Active users</text>
<text x="24" y="80" font-size="28" font-weight="700" stroke="none" fill="#000">1,284</text>
<text x="24" y="104" font-size="12" stroke="none" fill="#666">+12% vs last week</text>
</g>
<g transform="translate(256, 0)">
<rect width="232" height="120" rx="6" />
<text x="24" y="40" font-size="12" stroke="none" fill="#666">New signups</text>
<text x="24" y="80" font-size="28" font-weight="700" stroke="none" fill="#000">312</text>
<text x="24" y="104" font-size="12" stroke="none" fill="#666">+4%</text>
</g>
<g transform="translate(512, 0)">
<rect width="232" height="120" rx="6" />
<text x="24" y="40" font-size="12" stroke="none" fill="#666">Revenue</text>
<text x="24" y="80" font-size="28" font-weight="700" stroke="none" fill="#000">$24.1k</text>
<text x="24" y="104" font-size="12" stroke="none" fill="#666">+8%</text>
</g>
<g transform="translate(768, 0)">
<rect width="232" height="120" rx="6" />
<text x="24" y="40" font-size="12" stroke="none" fill="#666">Churn</text>
<text x="24" y="80" font-size="28" font-weight="700" stroke="none" fill="#000">1.4%</text>
<text x="24" y="104" font-size="12" stroke="none" fill="#666">0.3%</text>
</g>
</g>
<!-- 5: chart placeholder -->
<g transform="translate(264, 328)" data-region="chart">
<rect width="640" height="320" rx="6" />
<text x="24" y="40" font-size="20" font-weight="600" stroke="none" fill="#000">Activity</text>
<rect x="24" y="64" width="592" height="232" fill="#e6e6e6" />
<line x1="24" y1="64" x2="616" y2="296" stroke="#666" />
<line x1="616" y1="64" x2="24" y2="296" stroke="#666" />
</g>
<!-- 6: recent items list -->
<g transform="translate(920, 328)" data-region="recent">
<rect width="336" height="320" rx="6" />
<text x="24" y="40" font-size="20" font-weight="600" stroke="none" fill="#000">Recent</text>
<g transform="translate(0, 64)">
<line x1="0" y1="0" x2="336" y2="0" stroke="#666" />
<circle cx="32" cy="28" r="12" fill="#e6e6e6" />
<text x="56" y="24" font-size="14" font-weight="600" stroke="none" fill="#000">Item one</text>
<text x="56" y="40" font-size="12" stroke="none" fill="#666">2h ago</text>
</g>
<g transform="translate(0, 120)">
<line x1="0" y1="0" x2="336" y2="0" stroke="#666" />
<circle cx="32" cy="28" r="12" fill="#e6e6e6" />
<text x="56" y="24" font-size="14" font-weight="600" stroke="none" fill="#000">Item two</text>
<text x="56" y="40" font-size="12" stroke="none" fill="#666">5h ago</text>
</g>
<g transform="translate(0, 176)">
<line x1="0" y1="0" x2="336" y2="0" stroke="#666" />
<circle cx="32" cy="28" r="12" fill="#e6e6e6" />
<text x="56" y="24" font-size="14" font-weight="600" stroke="none" fill="#000">Item three</text>
<text x="56" y="40" font-size="12" stroke="none" fill="#666">1d ago</text>
</g>
</g>
</svg>
```
**Annotations:**
1. Sidebar nav with active "Dashboard"
2. Top bar with global search and account menu
3. Page header with title, subtitle, and primary CTA
4. Four KPI metric tiles
5. Activity chart panel (chart area shown as placeholder)
6. Recent items list (right rail)
---
## 3. Settings page with form (desktop, 1280×800)
```svg
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<rect x="0" y="0" width="1280" height="800" />
<!-- 1: top bar -->
<g transform="translate(0, 0)">
<rect width="1280" height="64" />
<text x="24" y="40" font-size="20" font-weight="600" stroke="none" fill="#000">Settings</text>
<circle cx="1240" cy="32" r="16" fill="#e6e6e6" />
</g>
<!-- 2: settings nav (left rail) -->
<g transform="translate(24, 96)">
<text x="0" y="14" font-size="12" font-weight="600" stroke="none" fill="#666">PERSONAL</text>
<rect x="-8" y="32" width="216" height="32" fill="#e6e6e6" />
<text x="0" y="52" font-size="14" stroke="none" fill="#000">Account</text>
<text x="0" y="84" font-size="14" stroke="none" fill="#666">Notifications</text>
<text x="0" y="116" font-size="14" stroke="none" fill="#666">Sessions</text>
<text x="0" y="160" font-size="12" font-weight="600" stroke="none" fill="#666">WORKSPACE</text>
<text x="0" y="200" font-size="14" stroke="none" fill="#666">Members</text>
<text x="0" y="232" font-size="14" stroke="none" fill="#666">Billing</text>
<text x="0" y="264" font-size="14" stroke="none" fill="#666">Integrations</text>
</g>
<!-- 3: form content -->
<g transform="translate(264, 96)">
<text x="0" y="28" font-size="28" font-weight="700" stroke="none" fill="#000">Account</text>
<text x="0" y="56" font-size="14" stroke="none" fill="#666">Manage your personal account details.</text>
<!-- avatar field -->
<g transform="translate(0, 96)">
<text x="0" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Profile photo</text>
<g transform="translate(0, 24)">
<circle cx="32" cy="32" r="32" fill="#e6e6e6" />
<line x1="9" y1="9" x2="55" y2="55" stroke="#666" />
<line x1="55" y1="9" x2="9" y2="55" stroke="#666" />
</g>
<g transform="translate(80, 36)">
<rect width="120" height="40" rx="4" />
<text x="60" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#000">Upload</text>
</g>
</g>
<!-- name field -->
<g transform="translate(0, 216)">
<text x="0" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Display name</text>
<g transform="translate(0, 24)">
<rect width="480" height="40" rx="4" />
<text x="12" y="25" font-size="14" stroke="none" fill="#000">Alex Morgan</text>
</g>
</g>
<!-- email field -->
<g transform="translate(0, 312)">
<text x="0" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Email</text>
<g transform="translate(0, 24)">
<rect width="480" height="40" rx="4" />
<text x="12" y="25" font-size="14" stroke="none" fill="#000">alex@acme.com</text>
</g>
<text x="0" y="84" font-size="12" stroke="none" fill="#666">Used for sign-in and notifications.</text>
</g>
<!-- role dropdown -->
<g transform="translate(0, 432)">
<text x="0" y="14" font-size="14" font-weight="600" stroke="none" fill="#000">Role</text>
<g transform="translate(0, 24)">
<rect width="200" height="40" rx="4" />
<text x="12" y="25" font-size="14" stroke="none" fill="#000">Admin</text>
<polyline points="180,17 188,25 180,33" fill="none" />
</g>
</g>
<!-- footer actions -->
<g transform="translate(0, 552)">
<line x1="0" y1="0" x2="800" y2="0" stroke="#666" />
<g transform="translate(560, 24)">
<rect width="120" height="40" rx="4" />
<text x="60" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#000">Cancel</text>
</g>
<g transform="translate(688, 24)">
<rect width="112" height="40" rx="4" fill="#000" />
<text x="56" y="25" font-size="14" font-weight="600" text-anchor="middle" stroke="none" fill="#fff">Save</text>
</g>
</g>
</g>
</svg>
```
**Annotations:**
1. Top bar with section name
2. Settings sub-nav (Personal / Workspace groups)
3. Form: avatar with upload, display name, email + help text, role dropdown, save / cancel actions
---
## 4. Modal confirmation overlay (desktop, 1280×800)
```svg
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="800" viewBox="0 0 1280 800"
font-family="-apple-system, system-ui, sans-serif" fill="#fff" stroke="#000" stroke-width="1.5">
<rect x="0" y="0" width="1280" height="800" />
<!-- underlying screen (faded) -->
<g transform="translate(0,0)" opacity="0.5">
<rect width="1280" height="64" />
<text x="24" y="40" font-size="20" font-weight="600" stroke="none" fill="#000">Projects</text>
</g>
<!-- 1: backdrop -->
<rect x="0" y="0" width="1280" height="800" fill="#000" fill-opacity="0.4" stroke="none" />
<!-- 2: modal -->
<g transform="translate(320, 240)" data-region="modal">
<rect width="640" height="320" rx="6" />
<text x="24" y="48" font-size="20" font-weight="600" stroke="none" fill="#000">Delete project?</text>
<line x1="0" y1="72" x2="640" y2="72" />
<text x="24" y="112" font-size="14" stroke="none" fill="#000">This permanently deletes the project and all its data.</text>
<text x="24" y="136" font-size="14" stroke="none" fill="#000">This action can't be undone.</text>
<!-- confirm input -->
<g transform="translate(24, 168)">
<text x="0" y="14" font-size="12" stroke="none" fill="#666">Type the project name to confirm</text>
<g transform="translate(0, 24)">
<rect width="592" height="40" rx="4" />
<text x="12" y="25" font-size="14" stroke="none" fill="#666">acme-prod</text>
</g>
</g>
<!-- footer -->
<line x1="0" y1="248" x2="640" y2="248" />
<g transform="translate(384, 268)">
<rect width="120" height="40" rx="4" />
<text x="60" y="25" font-size="14" text-anchor="middle" stroke="none" fill="#000">Cancel</text>
</g>
<g transform="translate(512, 268)">
<rect width="104" height="40" rx="4" fill="#000" />
<text x="52" y="25" font-size="14" font-weight="600" text-anchor="middle" stroke="none" fill="#fff">Delete</text>
</g>
</g>
<!-- 3: annotation -->
<g data-region="annotations">
<circle cx="976" cy="556" r="12" fill="#fff" stroke="#d33" stroke-dasharray="4 2" />
<text x="976" y="560" font-size="12" font-weight="700" text-anchor="middle" stroke="none" fill="#d33">1</text>
<text x="996" y="564" font-size="12" stroke="none" fill="#d33">Disable until input matches project name</text>
</g>
</svg>
```
**Annotations:**
1. Backdrop dims the underlying page
2. Confirmation modal: title, body copy, type-to-confirm field, Cancel + destructive Confirm
3. Reviewer note (red dashed): the destructive button must remain disabled until the typed input matches
---
## Multi-screen flow
When emitting a flow, render each screen as its own `<g transform="translate(x,0)">` inside one SVG, separated by 80px gutters and connected with arrow primitives from `components.md`. Or emit one SVG per screen and a `flow.svg` summary that arranges thumbnails (scaled with `transform="scale(0.25)"`) left-to-right. Either is acceptable; pick whichever the reviewer can scan faster.

View file

@ -0,0 +1,107 @@
# Grid, palette, and type scale
These are the only values you may use. Do not introduce new colours, sizes, or grid units.
## Canvas presets
| Viewport | Width × Height | Use for |
| -------- | -------------- | ----------------------------- |
| Desktop | 1280 × 800 | Default for web app screens |
| Wide | 1440 × 900 | Marketing landing pages |
| Tablet | 768 × 1024 | iPad-class screens |
| Mobile | 375 × 812 | iPhone-class screens |
Always include `viewBox="0 0 W H"` matching the canvas so it scales when embedded.
## Grid
- Base unit: **8px**. All `x`, `y`, `width`, `height` values must be multiples of 8.
- Outer page margin: **24px** on desktop/tablet, **16px** on mobile.
- Column gutter: **24px** desktop, **16px** mobile.
- Vertical rhythm: **24px** between sibling components.
### Desktop 12-column grid
- Total width: 1280
- Outer margin (each side): 48
- Inner content width: 1184
- Column width: 88, gutter 8 → 12 × (88 + 8) 8 = 1144 + 40 = 1184 ✓
In practice, snap to common widths:
- Sidebar: 240
- Content max: 944 (after sidebar)
- Card grid: 3 × 384 with 24 gutters or 4 × 280 with 24 gutters
- Modal width: 480 (small), 640 (default), 800 (wide)
### Mobile single column
- Total width: 375
- Outer margin: 16 each side → content 343
- Tap targets: 44 minimum height (snap to 48)
## Palette (the only colours allowed)
| Name | Hex | Use |
| ---------------- | ---------- | -------------------------------------------------------- |
| Ink | `#000` | Strokes, primary text |
| Paper | `#fff` | Default fill |
| Mute text | `#666` | Placeholder text inside inputs, secondary labels |
| Placeholder grey | `#e6e6e6` | Image/avatar/empty-state regions |
| Subtle grey | `#f4f4f4` | Optional zebra rows in tables; nothing else |
| Annotation red | `#d33` | Annotation layer ONLY — dashed borders, callout numbers |
That's the entire palette. No hover states, no focus rings, no brand colours.
## Type scale
Single typeface: `font-family="-apple-system, system-ui, sans-serif"`.
| Role | Size | Weight | Use |
| -------- | ---- | ------ | -------------------------------- |
| Caption | 12 | 400 | Help text, metadata, table footnotes |
| Body | 14 | 400 | Default text, button labels, list rows |
| Heading | 20 | 600 | Section headings, card titles |
| Title | 28 | 700 | Page title (one per screen) |
Font-weight is the only typographic variation allowed beyond size. No italics, no underline (except links — see below).
### Link convention
For text links, render as body 14, with `text-decoration="underline"`. No colour change.
### Strokes on text
Always set `stroke="none"` on `<text>` elements. The wireframe SVG sets a default stroke at the `<svg>` root for boxes; text inherits it as an unwanted halo unless overridden.
## Standard component sizes
These appear so often you should memorise them.
| Component | Size (W × H) |
| ----------------- | ------------ |
| Button (default) | 120 × 40 |
| Button (small) | 80 × 32 |
| Button (icon) | 40 × 40 |
| Text input | 320 × 40 |
| Text input (full) | 100% × 40 |
| Search input | 480 × 40 |
| Dropdown | 200 × 40 |
| Checkbox / radio | 20 × 20 |
| Avatar (small) | 32 × 32 circle |
| Avatar (medium) | 48 × 48 circle |
| Navbar | 100% × 64 |
| Tab | (auto) × 48 |
| List row | 100% × 56 |
| Table row | 100% × 48 |
| Card padding | 24 inside |
| Modal | 480 / 640 / 800 wide, height auto |
## Coordinate conventions
- Place every primitive inside a `<g transform="translate(x, y)">` so its internal coordinates start at `(0, 0)`. This makes primitives copy-pastable across screens.
- Use comments above each primitive: `<!-- 1: nav -->`, `<!-- 2: search -->` matching the annotation list you write below the SVG.
- Group related primitives under a parent `<g>` with a `data-region="..."` attribute for searchability.
## Negative space
Empty space is part of the design. Do not fill the canvas. A wireframe with one card centered in the viewport is a valid wireframe if that's the screen's intent.

View file

@ -2,7 +2,7 @@
"schemaVersion": 1,
"packageName": "@paperclipai/skills-catalog",
"packageVersion": "0.3.1",
"generatedAt": "2026-05-28T03:02:49.579Z",
"generatedAt": "2026-05-31T18:06:11.126Z",
"skills": [
{
"id": "paperclipai:bundled:docs:doc-maintenance",
@ -108,6 +108,78 @@
],
"contentHash": "sha256:4fb46a4bcefad4fd46fae48c433ee497112509a8e19fb8a7745ead44d219b498"
},
{
"id": "paperclipai:bundled:product:wireframe",
"key": "paperclipai/bundled/product/wireframe",
"kind": "bundled",
"category": "product",
"slug": "wireframe",
"name": "wireframe",
"description": "Produce low-fidelity black-and-white UI wireframes as standalone SVG files, optionally bundled into a single-page HTML viewer and published via the here-now skill. Use when the user asks to \"wireframe X\", \"sketch a screen for\", \"draft a layout\", \"low-fi mockup\", \"rough mock\", \"make a page to view the wireframes\", \"build a viewer for these screens\", or to \"deploy / publish / host the wireframes\". Do NOT use when the user wants production UI code, branded designs, hi-fi mockups, or animated/interactive prototypes — use frontend-design or similar instead.",
"path": "catalog/bundled/product/wireframe",
"entrypoint": "SKILL.md",
"trustLevel": "assets",
"compatibility": "compatible",
"defaultInstall": false,
"recommendedForRoles": [
"designer",
"product",
"engineer"
],
"requires": [],
"tags": [
"design",
"wireframe",
"ux",
"prototyping",
"svg"
],
"files": [
{
"path": "SKILL.md",
"kind": "skill",
"sizeBytes": 11887,
"sha256": "a910d038d8cdd13615b82f44a2195b781ebccf01c49f9c58babd43b0701fe644"
},
{
"path": "assets/site-template.html",
"kind": "asset",
"sizeBytes": 14791,
"sha256": "8886ad77c386c17b0457f11624aab136c33db7f28dcd6f10fc18e9d833d0950f"
},
{
"path": "assets/template-mobile.svg",
"kind": "asset",
"sizeBytes": 925,
"sha256": "dc32af257df1f986b87a7b30a6104de825057dc4d6f12b381f5a28308f0002f0"
},
{
"path": "assets/template.svg",
"kind": "asset",
"sizeBytes": 1262,
"sha256": "63f8adb8eb4b21dea6fd442b9e9312704c419535eb577d4ef0bfb54410fd576b"
},
{
"path": "references/components.md",
"kind": "reference",
"sizeBytes": 14806,
"sha256": "4ce18f418d78b13cdfa6fe8070173044a631576cd2b999571e6703f58894496c"
},
{
"path": "references/examples.md",
"kind": "reference",
"sizeBytes": 16108,
"sha256": "18f486e95a578a50cf6c9f93394b82534392771320692e4876edf17054bfae55"
},
{
"path": "references/grid-system.md",
"kind": "reference",
"sizeBytes": 4581,
"sha256": "017260d2216859d57e5fa869e0efc6b88c7ed4d8a8a4f5af04fe89a3ef2445bd"
}
],
"contentHash": "sha256:0bd9a9fdc656d529e3f97c00cd504dcf72d3a4fecb8b0504ca2fe3e00d63287f"
},
{
"id": "paperclipai:bundled:quality:qa-acceptance",
"key": "paperclipai/bundled/quality/qa-acceptance",

View file

@ -6,6 +6,7 @@ const EXPECTED_BUNDLED_KEYS = [
"paperclipai/bundled/docs/doc-maintenance",
"paperclipai/bundled/paperclip-operations/issue-triage",
"paperclipai/bundled/paperclip-operations/task-planning",
"paperclipai/bundled/product/wireframe",
"paperclipai/bundled/quality/qa-acceptance",
"paperclipai/bundled/software-development/github-pr-workflow",
];
@ -31,8 +32,11 @@ describe("shipped skills catalog", () => {
expect(optionalKeys).toEqual(EXPECTED_OPTIONAL_KEYS);
});
it("keeps every shipped skill markdown-only until a script-bearing skill clears security review", () => {
const scriptBearing = catalogSkills.filter((skill) => skill.trustLevel !== "markdown_only");
it("keeps every shipped skill free of executable scripts until script-bearing skills clear security review", () => {
// The real install-time security boundary (server assertCatalogSkillInstallable) blocks
// only "scripts_executables". Static assets (svg/html templates, e.g. the wireframe skill)
// carry the "assets" trust level and are installable, so they are allowed in the catalog.
const scriptBearing = catalogSkills.filter((skill) => skill.trustLevel === "scripts_executables");
expect(scriptBearing, formatViolations("script-bearing skills require security review", scriptBearing)).toEqual([]);
});