openink 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ All notable changes are documented here. The format follows [Keep a Changelog](h
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.0] - 2026-10-05
8
+
9
+ ### Added
10
+
11
+ - 73 more icons, 110 in all: arrows, status, files, shopping, weather and 20 brand logos (Google, Apple, GitHub,
12
+ Facebook, X, Instagram, LinkedIn, YouTube, TikTok, WhatsApp and more) for sign-in buttons, share bars and footers.
13
+ The gallery has a new Icons screen that shows every icon with its name (#20).
14
+
15
+ ## [0.2.0] - 2026-10-02
16
+
17
+ ### Added
18
+
19
+ - A `wireframe` skill for AI coding agents that turns a description into a validated, screenshot-checked prototype in
20
+ any folder. Claude Code: `claude plugin marketplace add mediuswareltd/openink`, then
21
+ `claude plugin install openink@openink`. Cursor, Codex, Gemini CLI, GitHub Copilot and others:
22
+ `npx skills add mediuswareltd/openink`.
23
+
24
+ ### Fixed
25
+
26
+ - An image, video, map, dropzone or chart placed directly in a `row` is drawn as a 4:3 thumbnail of its height. It
27
+ used to collapse to the width of its label and showed no box (#17).
28
+ - The PDF button (and Ctrl+P) no longer prints image, video and map placeholders and charts as empty boxes with only
29
+ their label. Screens that had not been opened were printed before their sketches were drawn.
30
+
7
31
  ## [0.1.1] - 2026-10-02
8
32
 
9
33
  ### Added
package/README.md CHANGED
@@ -82,6 +82,27 @@ openink dev
82
82
 
83
83
  Requires Node 20+. PDF and PNG export need Chrome, Chromium or Edge installed (set `CHROME_PATH` if it isn't found).
84
84
 
85
+ ### With an AI coding agent
86
+
87
+ Give your agent the Open Ink skill once, then just describe what you want ("wireframe a booking app for a hair salon").
88
+
89
+ **Claude Code**: install the plugin:
90
+
91
+ ```bash
92
+ claude plugin marketplace add mediuswareltd/openink
93
+ claude plugin install openink@openink
94
+ ```
95
+
96
+ To get updates automatically, turn on auto-update for the **openink** marketplace in `/plugin` → **Marketplaces**.
97
+
98
+ **Cursor, Codex, Gemini CLI, GitHub Copilot, Windsurf and other agents**: add the skill with the [skills](https://skills.sh) CLI, which asks which of your agents to install it for:
99
+
100
+ ```bash
101
+ npx skills add mediuswareltd/openink
102
+ ```
103
+
104
+ The agent writes and validates the spec, checks screenshots of every screen and tells you where the prototype is. See [docs/ai-assistants.md](docs/ai-assistants.md).
105
+
85
106
  ## What you can draw
86
107
 
87
108
  48 blocks in 10 groups; see the [block reference](docs/blocks.md) and the [gallery example](examples/gallery), which shows every one.
@@ -90,7 +111,7 @@ Requires Node 20+. PDF and PNG export need Chrome, Chromium or Edge installed (s
90
111
  |---|---|
91
112
  | **Layout** | `stack` `row` `grid` `card` `accordion` `device` (phone / tablet / browser frame) `divider` `spacer` |
92
113
  | **Text** | `h1` `h2` `h3` `text` `list` `note` `badge` |
93
- | **Content** | `icon` (37 hand-drawn icons) `avatar` `hero` `stat` `rating` `link` |
114
+ | **Content** | `icon` (110 hand-drawn icons, brand logos included) `avatar` `hero` `stat` `rating` `link` |
94
115
  | **Media** | `image` `map` `video` `carousel` `dropzone` `box` `chart` (line, area, bar, pie, donut) |
95
116
  | **Forms** | `input` `search` `textarea` `select` `checkbox` `toggle` `radio` `slider` |
96
117
  | **Actions** | `button` (with icon) `chips` `tabs` `nextbar` |
@@ -169,3 +190,5 @@ Issues and pull requests are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md). Ad
169
190
  ## License
170
191
 
171
192
  [MIT](LICENSE)
193
+
194
+ Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.
@@ -6,6 +6,35 @@ Open Ink is designed so that an assistant (Claude Code, Cursor, Copilot, ChatGPT
6
6
 
7
7
  The assistant writes `spec.yaml`, the tool checks it, and you get a consistent hand-drawn result. Because the spec is small and validated, it is cheap to iterate: "add a second tab to the listing page" is a five-line diff.
8
8
 
9
+ ## Install the Open Ink skill
10
+
11
+ Open Ink comes with a `wireframe` skill that teaches your agent the whole loop. Once it is installed, the agent uses it whenever you ask for a wireframe, mockup or clickable prototype, in any folder, so you do not need to mention Open Ink or run `init` first.
12
+
13
+ The skill follows the same steps as the `AGENTS.md` below: it reads the block reference, edits only `spec.yaml`, validates until the spec is clean, checks a PNG of every screen and tells you where the result is. In a folder without a spec, it creates the project in a new subfolder so it does not overwrite your files.
14
+
15
+ ### Claude Code
16
+
17
+ Install the plugin once:
18
+
19
+ ```bash
20
+ claude plugin marketplace add mediuswareltd/openink
21
+ claude plugin install openink@openink
22
+ ```
23
+
24
+ Inside a session the same is `/plugin marketplace add mediuswareltd/openink` and `/plugin install openink@openink`. You can also call the skill directly with `/openink:wireframe`.
25
+
26
+ For updates, turn on auto-update for the **openink** marketplace in `/plugin` → **Marketplaces**, or run `/plugin marketplace update openink`.
27
+
28
+ ### Cursor, Codex, Gemini CLI, GitHub Copilot and other agents
29
+
30
+ Add the skill with the [skills](https://skills.sh) CLI:
31
+
32
+ ```bash
33
+ npx skills add mediuswareltd/openink
34
+ ```
35
+
36
+ It asks which of your agents to install it for, and whether to install it for this project or for your user. To skip the questions, name the agents and add `-y`, for example `npx skills add mediuswareltd/openink -a cursor -a codex -y`, and add `-g` to install it for every project. Run `npx skills update` to get the latest version.
37
+
9
38
  ## How it works
10
39
 
11
40
  `openink init` puts an `AGENTS.md` in your project. Most assistants read it automatically; it tells them to:
package/docs/blocks.md CHANGED
@@ -27,7 +27,9 @@ Blocks that list `go`, `toast`, `open` or `close` in their props are clickable (
27
27
 
28
28
  Used by `icon`, `button.icon`, nav items and `tabbar`:
29
29
 
30
- `home` · `search` · `heart` · `comment` · `share` · `bookmark` · `plus` · `bell` · `user` · `users` · `mail` · `settings` · `camera` · `image` · `video` · `star` · `menu` · `more` · `close` · `check` · `arrow-right` · `arrow-left` · `chevron-down` · `chevron-right` · `play` · `pin` · `trash` · `edit` · `upload` · `download` · `lock` · `cart` · `chart` · `calendar` · `filter` · `info` · `send`
30
+ `home` · `search` · `heart` · `comment` · `share` · `bookmark` · `plus` · `bell` · `user` · `users` · `mail` · `settings` · `camera` · `image` · `video` · `star` · `menu` · `more` · `close` · `check` · `arrow-right` · `arrow-left` · `chevron-down` · `chevron-right` · `play` · `pin` · `trash` · `edit` · `upload` · `download` · `lock` · `cart` · `chart` · `calendar` · `filter` · `info` · `send` · `minus` · `arrow-up` · `arrow-down` · `chevron-up` · `chevron-left` · `external` · `link` · `refresh` · `login` · `logout` · `grid` · `list` · `pause` · `check-circle` · `warning` · `help` · `eye` · `eye-off` · `unlock` · `key` · `thumbs-up` · `smile` · `flag` · `zap` · `phone` · `inbox` · `paperclip` · `mic` · `volume` · `music` · `wifi` · `hash` · `at` · `rss` · `file` · `folder` · `copy` · `book` · `briefcase` · `printer` · `code` · `clock` · `bag` · `tag` · `gift` · `credit-card` · `dollar` · `truck` · `globe` · `map` · `sun` · `moon` · `cloud` · `facebook` · `x` · `instagram` · `linkedin` · `youtube` · `github` · `tiktok` · `whatsapp` · `telegram` · `discord` · `pinterest` · `reddit` · `snapchat` · `slack` · `spotify` · `apple` · `google` · `microsoft` · `dribbble` · `twitch`
31
+
32
+ Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.
31
33
 
32
34
  ## Layout
33
35
 
@@ -43,7 +45,7 @@ Vertical stack of blocks.
43
45
 
44
46
  ### `row` · children
45
47
 
46
- Horizontal row that wraps on small screens.
48
+ Horizontal row that wraps on small screens. Images, videos, maps and charts in a row are 4:3 thumbnails of their height.
47
49
 
48
50
  | Prop | Type | Description |
49
51
  |---|---|---|
@@ -198,7 +200,7 @@ Hand-drawn icon.
198
200
 
199
201
  | Prop | Type | Description |
200
202
  |---|---|---|
201
- | `name` * | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` | Icon name. |
203
+ | `name` * | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` \| `minus` \| `arrow-up` \| `arrow-down` \| `chevron-up` \| `chevron-left` \| `external` \| `link` \| `refresh` \| `login` \| `logout` \| `grid` \| `list` \| `pause` \| `check-circle` \| `warning` \| `help` \| `eye` \| `eye-off` \| `unlock` \| `key` \| `thumbs-up` \| `smile` \| `flag` \| `zap` \| `phone` \| `inbox` \| `paperclip` \| `mic` \| `volume` \| `music` \| `wifi` \| `hash` \| `at` \| `rss` \| `file` \| `folder` \| `copy` \| `book` \| `briefcase` \| `printer` \| `code` \| `clock` \| `bag` \| `tag` \| `gift` \| `credit-card` \| `dollar` \| `truck` \| `globe` \| `map` \| `sun` \| `moon` \| `cloud` \| `facebook` \| `x` \| `instagram` \| `linkedin` \| `youtube` \| `github` \| `tiktok` \| `whatsapp` \| `telegram` \| `discord` \| `pinterest` \| `reddit` \| `snapchat` \| `slack` \| `spotify` \| `apple` \| `google` \| `microsoft` \| `dribbble` \| `twitch` | Icon name. |
202
204
  | `size` | number | Size in px (default 22). |
203
205
  | `filled` | boolean | Solid instead of outline (heart, star, bookmark…). |
204
206
 
@@ -458,7 +460,7 @@ Button, optionally with an icon. Navigates with `go`, opens a `modal` with `open
458
460
  | Prop | Type | Description |
459
461
  |---|---|---|
460
462
  | `label` | text | Button text. Leave out for an icon-only button. |
461
- | `icon` | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` | Icon shown before the label. |
463
+ | `icon` | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` \| `minus` \| `arrow-up` \| `arrow-down` \| `chevron-up` \| `chevron-left` \| `external` \| `link` \| `refresh` \| `login` \| `logout` \| `grid` \| `list` \| `pause` \| `check-circle` \| `warning` \| `help` \| `eye` \| `eye-off` \| `unlock` \| `key` \| `thumbs-up` \| `smile` \| `flag` \| `zap` \| `phone` \| `inbox` \| `paperclip` \| `mic` \| `volume` \| `music` \| `wifi` \| `hash` \| `at` \| `rss` \| `file` \| `folder` \| `copy` \| `book` \| `briefcase` \| `printer` \| `code` \| `clock` \| `bag` \| `tag` \| `gift` \| `credit-card` \| `dollar` \| `truck` \| `globe` \| `map` \| `sun` \| `moon` \| `cloud` \| `facebook` \| `x` \| `instagram` \| `linkedin` \| `youtube` \| `github` \| `tiktok` \| `whatsapp` \| `telegram` \| `discord` \| `pinterest` \| `reddit` \| `snapchat` \| `slack` \| `spotify` \| `apple` \| `google` \| `microsoft` \| `dribbble` \| `twitch` | Icon shown before the label. |
462
464
  | `primary` | boolean | Heavier border: the main action on the screen. |
463
465
  | `disabled` | boolean | Grey out the button. |
464
466
  | `go` | string | Id of the screen to open when clicked. |
package/docs/spec.md CHANGED
@@ -190,6 +190,8 @@ screens:
190
190
 
191
191
  `icon` blocks, `button.icon`, nav items and tab-bar items use a built-in set of hand-drawn icons (`home`, `search`, `heart`, `comment`, `send`, `bell`, `user`, `camera`, …). The full list is at the end of the [block reference](blocks.md#icons). An unknown name is an error with a "did you mean" hint.
192
192
 
193
+ The set includes brand logos (`google`, `apple`, `github`, `facebook`, `x`, `linkedin`, …) for sign-in buttons, share bars and footers. Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.
194
+
193
195
  ## Quoting text with commas
194
196
 
195
197
  Inside `{ ... }` a comma ends the value, so YAML reads `{ type: text, text: Hello, world }` as text `Hello` plus a stray key `world`. Wrap such text in quotes: `text: "Hello, world"`. `validate` warns when it sees this.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openink",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Describe screens in YAML, get a clickable hand-drawn wireframe prototype (static HTML, PDF, PNG). Built on wired-elements and RoughJS.",
5
5
  "keywords": [
6
6
  "wireframe",
@@ -275,7 +275,80 @@
275
275
  "calendar",
276
276
  "filter",
277
277
  "info",
278
- "send"
278
+ "send",
279
+ "minus",
280
+ "arrow-up",
281
+ "arrow-down",
282
+ "chevron-up",
283
+ "chevron-left",
284
+ "external",
285
+ "link",
286
+ "refresh",
287
+ "login",
288
+ "logout",
289
+ "grid",
290
+ "list",
291
+ "pause",
292
+ "check-circle",
293
+ "warning",
294
+ "help",
295
+ "eye",
296
+ "eye-off",
297
+ "unlock",
298
+ "key",
299
+ "thumbs-up",
300
+ "smile",
301
+ "flag",
302
+ "zap",
303
+ "phone",
304
+ "inbox",
305
+ "paperclip",
306
+ "mic",
307
+ "volume",
308
+ "music",
309
+ "wifi",
310
+ "hash",
311
+ "at",
312
+ "rss",
313
+ "file",
314
+ "folder",
315
+ "copy",
316
+ "book",
317
+ "briefcase",
318
+ "printer",
319
+ "code",
320
+ "clock",
321
+ "bag",
322
+ "tag",
323
+ "gift",
324
+ "credit-card",
325
+ "dollar",
326
+ "truck",
327
+ "globe",
328
+ "map",
329
+ "sun",
330
+ "moon",
331
+ "cloud",
332
+ "facebook",
333
+ "x",
334
+ "instagram",
335
+ "linkedin",
336
+ "youtube",
337
+ "github",
338
+ "tiktok",
339
+ "whatsapp",
340
+ "telegram",
341
+ "discord",
342
+ "pinterest",
343
+ "reddit",
344
+ "snapchat",
345
+ "slack",
346
+ "spotify",
347
+ "apple",
348
+ "google",
349
+ "microsoft",
350
+ "dribbble",
351
+ "twitch"
279
352
  ]
280
353
  },
281
354
  "label": {
@@ -364,7 +437,7 @@
364
437
  },
365
438
  {
366
439
  "type": "object",
367
- "description": "Horizontal row that wraps on small screens.",
440
+ "description": "Horizontal row that wraps on small screens. Images, videos, maps and charts in a row are 4:3 thumbnails of their height.",
368
441
  "required": [
369
442
  "type"
370
443
  ],
@@ -1011,7 +1084,80 @@
1011
1084
  "calendar",
1012
1085
  "filter",
1013
1086
  "info",
1014
- "send"
1087
+ "send",
1088
+ "minus",
1089
+ "arrow-up",
1090
+ "arrow-down",
1091
+ "chevron-up",
1092
+ "chevron-left",
1093
+ "external",
1094
+ "link",
1095
+ "refresh",
1096
+ "login",
1097
+ "logout",
1098
+ "grid",
1099
+ "list",
1100
+ "pause",
1101
+ "check-circle",
1102
+ "warning",
1103
+ "help",
1104
+ "eye",
1105
+ "eye-off",
1106
+ "unlock",
1107
+ "key",
1108
+ "thumbs-up",
1109
+ "smile",
1110
+ "flag",
1111
+ "zap",
1112
+ "phone",
1113
+ "inbox",
1114
+ "paperclip",
1115
+ "mic",
1116
+ "volume",
1117
+ "music",
1118
+ "wifi",
1119
+ "hash",
1120
+ "at",
1121
+ "rss",
1122
+ "file",
1123
+ "folder",
1124
+ "copy",
1125
+ "book",
1126
+ "briefcase",
1127
+ "printer",
1128
+ "code",
1129
+ "clock",
1130
+ "bag",
1131
+ "tag",
1132
+ "gift",
1133
+ "credit-card",
1134
+ "dollar",
1135
+ "truck",
1136
+ "globe",
1137
+ "map",
1138
+ "sun",
1139
+ "moon",
1140
+ "cloud",
1141
+ "facebook",
1142
+ "x",
1143
+ "instagram",
1144
+ "linkedin",
1145
+ "youtube",
1146
+ "github",
1147
+ "tiktok",
1148
+ "whatsapp",
1149
+ "telegram",
1150
+ "discord",
1151
+ "pinterest",
1152
+ "reddit",
1153
+ "snapchat",
1154
+ "slack",
1155
+ "spotify",
1156
+ "apple",
1157
+ "google",
1158
+ "microsoft",
1159
+ "dribbble",
1160
+ "twitch"
1015
1161
  ],
1016
1162
  "description": "Icon name."
1017
1163
  },
@@ -1996,7 +2142,80 @@
1996
2142
  "calendar",
1997
2143
  "filter",
1998
2144
  "info",
1999
- "send"
2145
+ "send",
2146
+ "minus",
2147
+ "arrow-up",
2148
+ "arrow-down",
2149
+ "chevron-up",
2150
+ "chevron-left",
2151
+ "external",
2152
+ "link",
2153
+ "refresh",
2154
+ "login",
2155
+ "logout",
2156
+ "grid",
2157
+ "list",
2158
+ "pause",
2159
+ "check-circle",
2160
+ "warning",
2161
+ "help",
2162
+ "eye",
2163
+ "eye-off",
2164
+ "unlock",
2165
+ "key",
2166
+ "thumbs-up",
2167
+ "smile",
2168
+ "flag",
2169
+ "zap",
2170
+ "phone",
2171
+ "inbox",
2172
+ "paperclip",
2173
+ "mic",
2174
+ "volume",
2175
+ "music",
2176
+ "wifi",
2177
+ "hash",
2178
+ "at",
2179
+ "rss",
2180
+ "file",
2181
+ "folder",
2182
+ "copy",
2183
+ "book",
2184
+ "briefcase",
2185
+ "printer",
2186
+ "code",
2187
+ "clock",
2188
+ "bag",
2189
+ "tag",
2190
+ "gift",
2191
+ "credit-card",
2192
+ "dollar",
2193
+ "truck",
2194
+ "globe",
2195
+ "map",
2196
+ "sun",
2197
+ "moon",
2198
+ "cloud",
2199
+ "facebook",
2200
+ "x",
2201
+ "instagram",
2202
+ "linkedin",
2203
+ "youtube",
2204
+ "github",
2205
+ "tiktok",
2206
+ "whatsapp",
2207
+ "telegram",
2208
+ "discord",
2209
+ "pinterest",
2210
+ "reddit",
2211
+ "snapchat",
2212
+ "slack",
2213
+ "spotify",
2214
+ "apple",
2215
+ "google",
2216
+ "microsoft",
2217
+ "dribbble",
2218
+ "twitch"
2000
2219
  ],
2001
2220
  "description": "Icon shown before the label."
2002
2221
  },
@@ -2376,7 +2595,80 @@
2376
2595
  "calendar",
2377
2596
  "filter",
2378
2597
  "info",
2379
- "send"
2598
+ "send",
2599
+ "minus",
2600
+ "arrow-up",
2601
+ "arrow-down",
2602
+ "chevron-up",
2603
+ "chevron-left",
2604
+ "external",
2605
+ "link",
2606
+ "refresh",
2607
+ "login",
2608
+ "logout",
2609
+ "grid",
2610
+ "list",
2611
+ "pause",
2612
+ "check-circle",
2613
+ "warning",
2614
+ "help",
2615
+ "eye",
2616
+ "eye-off",
2617
+ "unlock",
2618
+ "key",
2619
+ "thumbs-up",
2620
+ "smile",
2621
+ "flag",
2622
+ "zap",
2623
+ "phone",
2624
+ "inbox",
2625
+ "paperclip",
2626
+ "mic",
2627
+ "volume",
2628
+ "music",
2629
+ "wifi",
2630
+ "hash",
2631
+ "at",
2632
+ "rss",
2633
+ "file",
2634
+ "folder",
2635
+ "copy",
2636
+ "book",
2637
+ "briefcase",
2638
+ "printer",
2639
+ "code",
2640
+ "clock",
2641
+ "bag",
2642
+ "tag",
2643
+ "gift",
2644
+ "credit-card",
2645
+ "dollar",
2646
+ "truck",
2647
+ "globe",
2648
+ "map",
2649
+ "sun",
2650
+ "moon",
2651
+ "cloud",
2652
+ "facebook",
2653
+ "x",
2654
+ "instagram",
2655
+ "linkedin",
2656
+ "youtube",
2657
+ "github",
2658
+ "tiktok",
2659
+ "whatsapp",
2660
+ "telegram",
2661
+ "discord",
2662
+ "pinterest",
2663
+ "reddit",
2664
+ "snapchat",
2665
+ "slack",
2666
+ "spotify",
2667
+ "apple",
2668
+ "google",
2669
+ "microsoft",
2670
+ "dribbble",
2671
+ "twitch"
2380
2672
  ]
2381
2673
  },
2382
2674
  "label": {
package/src/icons.js CHANGED
@@ -3,6 +3,9 @@
3
3
  // Each icon is a list of SVG path strings. `filled` icons can be solid when the `filled` attribute is set.
4
4
 
5
5
  const dot = (x, y, r = 1) => `M${x - r} ${y}a${r} ${r} 0 1 0 ${2 * r} 0a${r} ${r} 0 1 0 ${-2 * r} 0`;
6
+ // A rectangle with rounded corners.
7
+ const box = (x, y, w, h, r) =>
8
+ `M${x + r} ${y}h${w - 2 * r}a${r} ${r} 0 0 1 ${r} ${r}v${h - 2 * r}a${r} ${r} 0 0 1 ${-r} ${r}h${2 * r - w}a${r} ${r} 0 0 1 ${-r} ${-r}v${2 * r - h}a${r} ${r} 0 0 1 ${r} ${-r}z`;
6
9
 
7
10
  export const ICONS = {
8
11
  home: ["M3 11.5L12 3l9 8.5", "M5.5 10v10.5h13V10", "M10 20.5v-6h4v6"],
@@ -42,6 +45,113 @@ export const ICONS = {
42
45
  filter: ["M3 5h18l-7 8v6l-4-2v-4z"],
43
46
  info: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z", "M12 11v6", dot(12, 7.5, 0.6)],
44
47
  send: ["M3 11l18-8-8 18-2-8z"],
48
+
49
+ // arrows and controls
50
+ minus: ["M4 12h16"],
51
+ "arrow-up": ["M12 20V4M6 10l6-6 6 6"],
52
+ "arrow-down": ["M12 4v16M6 14l6 6 6-6"],
53
+ "chevron-up": ["M6 15l6-6 6 6"],
54
+ "chevron-left": ["M15 6l-6 6 6 6"],
55
+ external: ["M14 4h6v6", "M20 4l-9 9", "M18 14v6H4V6h6"],
56
+ link: ["M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1", "M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"],
57
+ refresh: ["M20 12a8 8 0 1 1-2.3-5.7", "M20 3.5V8h-4.5"],
58
+ login: ["M15 4h5v16h-5", "M10 8l4 4-4 4", "M14 12H3"],
59
+ logout: ["M9 4H4v16h5", "M16 8l4 4-4 4", "M20 12H9"],
60
+ grid: ["M4 4h7v7H4z", "M13 4h7v7h-7z", "M4 13h7v7H4z", "M13 13h7v7h-7z"],
61
+ list: ["M9 6h12M9 12h12M9 18h12", dot(4.5, 6), dot(4.5, 12), dot(4.5, 18)],
62
+ pause: ["M7 4h3v16H7z", "M14 4h3v16h-3z"],
63
+
64
+ // status
65
+ "check-circle": [dot(12, 12, 9), "M8 12.5l3 3 5-6"],
66
+ warning: ["M12 3L2 20h20z", "M12 10v4", dot(12, 17, 0.6)],
67
+ help: [dot(12, 12, 9), "M9.5 9a2.5 2.5 0 1 1 3.5 2.3c-.7.3-1 .9-1 1.7v.5", dot(12, 17, 0.6)],
68
+ eye: ["M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z", dot(12, 12, 3)],
69
+ "eye-off": ["M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z", dot(12, 12, 3), "M4 4l16 16"],
70
+ unlock: ["M6 11h12v10H6z", "M8.5 11V8a3.5 3.5 0 0 1 6.8-1.2"],
71
+ key: [dot(7.5, 16.5, 4), "M10.5 13.5L20 4", "M16.5 7.5l2.5 2.5", "M18.5 5.5l2 2"],
72
+ "thumbs-up": ["M7 10v11H3V10z", "M7 10l4-8a2.5 2.5 0 0 1 3 2.5V9h5.5a2 2 0 0 1 2 2.3l-1.4 8A2 2 0 0 1 18 21H7"],
73
+ smile: [dot(12, 12, 9), "M8 14.5a5 5 0 0 0 8 0", dot(9, 9.5, 0.6), dot(15, 9.5, 0.6)],
74
+ flag: ["M5 21V4", "M5 4h12l-2.5 4.5L17 13H5"],
75
+ zap: ["M13 2L4 14h7l-1 8 9-12h-7z"],
76
+
77
+ // communication and media
78
+ phone: ["M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2z"],
79
+ inbox: ["M3 13l3-8h12l3 8v6H3z", "M3 13h5l1 3h6l1-3h5"],
80
+ paperclip: ["M20 11l-8.5 8.5a5 5 0 0 1-7-7L13 4a3.5 3.5 0 0 1 5 5l-8.5 8.5a2 2 0 0 1-3-3L14 7"],
81
+ mic: [box(9, 2, 6, 12, 3), "M5 11a7 7 0 0 0 14 0", "M12 18v4M8 22h8"],
82
+ volume: ["M4 9h4l5-4v14l-5-4H4z", "M16 9a4 4 0 0 1 0 6", "M18.5 6.5a7.5 7.5 0 0 1 0 11"],
83
+ music: ["M9 18V5l12-2v13", dot(6, 18, 3), dot(18, 16, 3)],
84
+ wifi: ["M2 8.5a15 15 0 0 1 20 0", "M5 12a10 10 0 0 1 14 0", "M8.5 15.5a5 5 0 0 1 7 0", dot(12, 19)],
85
+ hash: ["M4 9h16M4 15h16", "M10 3L8 21M16 3l-2 18"],
86
+ at: [dot(12, 12, 4), "M16 8v5a2.5 2.5 0 0 0 5 0v-1a9 9 0 1 0-3.5 7.1"],
87
+ rss: ["M5 11a8 8 0 0 1 8 8", "M5 5a14 14 0 0 1 14 14", dot(6, 18, 1.2)],
88
+
89
+ // files and work
90
+ file: ["M5 2h9l5 5v15H5z", "M14 2v5h5"],
91
+ folder: ["M3 5h6l2 3h10v12H3z"],
92
+ copy: ["M9 9h11v11H9z", "M5 15H4V4h11v1"],
93
+ book: ["M4 4h6a2 2 0 0 1 2 2v14a2 2 0 0 0-2-2H4z", "M20 4h-6a2 2 0 0 0-2 2v14a2 2 0 0 1 2-2h6z"],
94
+ briefcase: ["M3 7h18v13H3z", "M9 7V4h6v3", "M3 13h18"],
95
+ printer: ["M6 9V3h12v6", "M6 17H3V9h18v8h-3", "M6 14h12v7H6z"],
96
+ code: ["M8 7l-5 5 5 5", "M16 7l5 5-5 5", "M14 4l-4 16"],
97
+ clock: [dot(12, 12, 9), "M12 7v5l3.5 2"],
98
+
99
+ // shopping and places
100
+ bag: ["M4 7h16l-1 14H5z", "M8.5 10V6a3.5 3.5 0 0 1 7 0v4"],
101
+ tag: ["M3 3h8l10 10-8 8L3 11z", dot(7.5, 7.5, 1.5)],
102
+ gift: ["M3 8h18v4H3z", "M5 12v9h14v-9", "M12 8v13", "M12 8C10 4 6.5 4.5 7.5 7c.5 1 2.5 1 4.5 1z", "M12 8c2-4 5.5-3.5 4.5-1-.5 1-2.5 1-4.5 1z"],
103
+ "credit-card": ["M2 6h20v13H2z", "M2 10h20", "M5 15h4"],
104
+ dollar: ["M12 2v20", "M17 6H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"],
105
+ truck: ["M2 5h12v11H2z", "M14 9h4l4 4v3h-8", dot(6, 18, 2), dot(18, 18, 2)],
106
+ globe: [dot(12, 12, 9), "M3 12h18", "M12 3a4.5 9 0 0 1 0 18a4.5 9 0 0 1 0-18"],
107
+ map: ["M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2z", "M9 4v14M15 6v14"],
108
+
109
+ // weather
110
+ sun: [dot(12, 12, 4), "M12 2v2M12 20v2M2 12h2M20 12h2", "M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"],
111
+ moon: ["M20 14.5A8.5 8.5 0 1 1 9.5 4a7 7 0 0 0 10.5 10.5z"],
112
+ cloud: ["M7 19a5 5 0 0 1-.5-10A6 6 0 0 1 18 9a5 5 0 0 1 0 10z"],
113
+
114
+ // brands: simplified outlines for "sign in with" buttons, share bars and footers. The names and logos are
115
+ // trademarks of their owners; these are wireframe sketches drawn for Open Ink, not the official artwork.
116
+ facebook: [dot(12, 12, 9), "M15.5 8H14a2 2 0 0 0-2 2v11", "M9.5 13h5"],
117
+ x: ["M4 4h4.5L20 20h-4.5z", "M20 4l-6.5 7.5M10.5 13.5L4 20"],
118
+ instagram: [box(3, 3, 18, 18, 5), dot(12, 12, 4), dot(17, 7, 0.7)],
119
+ linkedin: [box(3, 3, 18, 18, 2), "M7.5 10.5v6", dot(7.5, 7.5, 0.8), "M11 16.5v-6M11 13a2.5 2.5 0 0 1 5 0v3.5"],
120
+ youtube: [box(2, 5, 20, 14, 4), "M10 9l5 3-5 3z"],
121
+ github: [
122
+ "M8 21v-3.5c-3.5-.5-5-2.5-5-6 0-1.3.4-2.4 1.2-3.3-.3-1.2-.2-2.4.3-3.7 1.4 0 2.6.7 3.7 1.5a10 10 0 0 1 7.6 0c1.1-.8 2.3-1.5 3.7-1.5.5 1.3.6 2.5.3 3.7.8.9 1.2 2 1.2 3.3 0 3.5-1.5 5.5-5 6V21",
123
+ "M8 18.5c-2.5.5-3.5-1-4.5-2",
124
+ ],
125
+ tiktok: ["M14 3v12.5a3.5 3.5 0 1 1-3.5-3.5", "M14 3c.5 2.5 2.5 4.5 5 4.5"],
126
+ whatsapp: ["M4.6 16.3A8.5 8.5 0 1 1 7.8 19.4L3.5 20.5z", "M9 8.5c0 3.5 3 6.5 6.5 6.5l1-1.5-2-1-1 .8c-1-.5-2-1.5-2.5-2.5l.8-1-1-2z"],
127
+ telegram: [dot(12, 12, 9), "M6.5 12L17 7.5 15 17l-4.5-3.5z", "M10.5 13.5L14 10.5"],
128
+ discord: [
129
+ "M5 17c0-4 1-7.5 3-10.5 1.2-.5 2.5-.8 3-.8l.5 1h1l.5-1c.5 0 1.8.3 3 .8 2 3 3 6.5 3 10.5-1.3 1.2-2.8 1.8-4.2 2.2l-1-2c-1.3.3-3.3.3-4.6 0l-1 2C7.8 18.8 6.3 18.2 5 17z",
130
+ dot(9.5, 13, 1.2),
131
+ dot(14.5, 13, 1.2),
132
+ ],
133
+ pinterest: [dot(12, 12, 9), "M9 13c-.7-.8-1-1.7-1-2.8a4 4 0 0 1 8 .3c0 2.5-1.5 4.5-3.5 4.5-1 0-1.8-.6-1.6-1.6l.8-3.4", "M11.5 12l-2 9"],
134
+ reddit: [
135
+ "M3.5 14a8.5 5.5 0 1 0 17 0a8.5 5.5 0 1 0-17 0",
136
+ "M12 8.5l1.5-5 4 1",
137
+ dot(18.5, 4.5, 1.3),
138
+ dot(9, 13.5, 0.9),
139
+ dot(15, 13.5, 0.9),
140
+ "M9 16.5c1.8 1.2 4.2 1.2 6 0",
141
+ ],
142
+ snapchat: [
143
+ "M12 3c3 0 5 2.2 5 5v2.5l2-.5c.5 0 .8.6.3 1l-2.3 1.3c.6 2 2 3.3 4 3.8-.3.8-1.5 1-2.5 1.2l-.5 1.4-2-.2c-1 .3-2 1.5-4 1.5s-3-1.2-4-1.5l-2 .2-.5-1.4c-1-.2-2.2-.4-2.5-1.2 2-.5 3.4-1.8 4-3.8L4.7 11c-.5-.4-.2-1 .3-1l2 .5V8c0-2.8 2-5 5-5z",
144
+ ],
145
+ slack: [box(8.5, 2, 3, 9, 1.5), box(13, 8.5, 9, 3, 1.5), box(12.5, 13, 3, 9, 1.5), box(2, 12.5, 9, 3, 1.5)],
146
+ spotify: [dot(12, 12, 9), "M7 9.5c3.5-1 7.5-.7 10.5 1", "M7.5 12.8c3-.8 6-.5 8.5 1", "M8 15.8c2.5-.6 4.5-.4 6.5.7"],
147
+ apple: [
148
+ "M12 7.5c1-.6 2-.9 3-.9 2 0 3.5 1 4.3 2.4-1.7 1-2.6 2.6-2.3 4.6.2 1.5 1 2.6 2.3 3.2-.8 2.3-2.4 4.7-4.3 4.7-1 0-1.8-.6-3-.6s-2 .6-3 .6c-2.3 0-5-4.5-5-8.5 0-3.2 2-5.4 4.5-5.4 1.3 0 2.5.6 3.5.9z",
149
+ "M12 6.5c0-2 1.3-3.5 3.3-3.8 0 2-1.3 3.6-3.3 3.8z",
150
+ ],
151
+ google: ["M20 12h-8", "M20 12a8 8 0 1 1-2.3-5.7"],
152
+ microsoft: ["M3 3h8.5v8.5H3z", "M12.5 3H21v8.5h-8.5z", "M3 12.5h8.5V21H3z", "M12.5 12.5H21V21h-8.5z"],
153
+ dribbble: [dot(12, 12, 9), "M3.2 10.5c6 .5 11.5-1.5 14-5.5", "M8.5 3.7c3.5 4 6 10 7 16.5", "M5.5 18.5c2.5-4 7-6 15.3-4.2"],
154
+ twitch: ["M4 6l1.5-3H20v11l-4 4h-4l-3 3v-3H4z", "M11 7v4M15 7v4"],
45
155
  };
46
156
 
47
157
  export const ICON_NAMES = Object.keys(ICONS);
@@ -13,7 +13,7 @@ export default [
13
13
  {
14
14
  name: "row",
15
15
  group: "Layout",
16
- summary: "Horizontal row that wraps on small screens.",
16
+ summary: "Horizontal row that wraps on small screens. Images, videos, maps and charts in a row are 4:3 thumbnails of their height.",
17
17
  children: true,
18
18
  props: {
19
19
  between: { type: "boolean", doc: "Push the first and last child to opposite ends." },
@@ -12,7 +12,7 @@ class Chart extends HTMLElement {
12
12
  const kind = this.getAttribute("kind") || "line";
13
13
  const values = (this.getAttribute("values") || "").split(",").map((s) => s.trim()).filter(Boolean).map(Number).filter(Number.isFinite);
14
14
  const data = values.length ? values : SAMPLE[kind] || SAMPLE.line;
15
- this.style.cssText += `display:block;position:relative;height:${h}px`;
15
+ this.style.cssText += `display:block;position:relative;height:${h}px;--oi-w:${Math.round((h * 4) / 3)}px`;
16
16
 
17
17
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
18
18
  svg.style.cssText = "position:absolute;inset:0;width:100%;height:100%";
@@ -70,6 +70,7 @@ class Chart extends HTMLElement {
70
70
  }
71
71
  };
72
72
 
73
+ this.draw = draw; // print draws every screen at once, before a ResizeObserver would fire
73
74
  draw();
74
75
  new ResizeObserver(draw).observe(this);
75
76
  const label = this.getAttribute("label");
@@ -11,7 +11,7 @@ class Placeholder extends HTMLElement {
11
11
  const h = +this.getAttribute("h") || 120;
12
12
  // A round placeholder (avatar) is square and must have a width of its own to sit in a flex row.
13
13
  const width = this.hasAttribute("round") ? `width:${h}px;flex:none;` : "";
14
- this.style.cssText += `display:block;position:relative;height:${h}px;${width}`;
14
+ this.style.cssText += `display:block;position:relative;height:${h}px;--oi-w:${Math.round((h * 4) / 3)}px;${width}`;
15
15
 
16
16
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
17
17
  svg.style.cssText = "position:absolute;inset:0;width:100%;height:100%";
@@ -72,6 +72,7 @@ class Placeholder extends HTMLElement {
72
72
  }
73
73
  };
74
74
 
75
+ this.draw = draw; // print draws every screen at once, before a ResizeObserver would fire
75
76
  draw();
76
77
  new ResizeObserver(draw).observe(this);
77
78
 
@@ -24,11 +24,13 @@ export function preparePrint() {
24
24
  return { zoom: r.height > room ? Math.max(room / r.height, 0.3) : 1, width: r.width };
25
25
  });
26
26
 
27
+ // Draw everything now: the browser prints straight after this returns, before a ResizeObserver
28
+ // would draw the placeholders and charts on screens that were hidden.
27
29
  // Draw before zooming: wired-elements size their sketch from the zoomed box, and the zoom then
28
30
  // shrinks the sketch a second time. Keep the screen's unzoomed width so its layout (and so the
29
31
  // sketches) only scales, and record the zoomed box as the size last drawn, so a resize
30
32
  // (wired-card watches its own) does not redraw at that size.
31
- $$("*").forEach((el) => el.wiredRender?.(true));
33
+ $$("*").forEach((el) => (el.wiredRender ? el.wiredRender(true) : el.draw?.()));
32
34
  screens.forEach((s, i) => {
33
35
  const { zoom, width } = zooms[i];
34
36
  if (zoom === 1) return;
package/src/spec/docs.js CHANGED
@@ -45,6 +45,8 @@ export function blocksMarkdown() {
45
45
  "",
46
46
  ICON_NAMES.map((n) => `\`${n}\``).join(" · "),
47
47
  "",
48
+ "Brand names and logos are trademarks of their owners. Open Ink's brand icons are simplified sketches for wireframes only and do not imply endorsement. Use each brand's official assets in production.",
49
+ "",
48
50
  ];
49
51
  for (const g of groups) {
50
52
  out.push(`## ${g}`, "");
@@ -101,6 +101,8 @@ p { margin: 0; }
101
101
  .row { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
102
102
  .row.between { justify-content: space-between; }
103
103
  .row.center { justify-content: center; }
104
+ /* placeholders and charts have no content width, so in a row they are a 4:3 thumbnail (--oi-w is set from their height) */
105
+ .row > oi-placeholder:not([round]), .row > oi-chart { flex: none; width: var(--oi-w); max-width: 100%; }
104
106
  .grid { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: var(--gap); align-items: start; }
105
107
  @media (max-width: 720px) { html:not(.oi-print) .grid { grid-template-columns: 1fr; } }
106
108
  .card { display: block; padding: 1rem; background: var(--card-bg); }