@appshoteditor/shot-dsl 0.4.0 → 0.5.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/README.md +102 -23
- package/package.json +6 -3
- package/src/compose.ts +987 -132
- package/src/decor.ts +137 -0
- package/src/index.ts +3 -0
- package/src/palette.ts +186 -0
- package/src/typography.ts +200 -0
- package/src/validate.ts +46 -2
- package/src/variants.ts +140 -43
package/README.md
CHANGED
|
@@ -25,6 +25,9 @@ composer — so a layout composed by the skill renders identically in the editor
|
|
|
25
25
|
- **Set layout system (0.4.0)** — `solveVertical` / `NO_TANGENT` (the no-tangent rule), `focusReach`,
|
|
26
26
|
`rotatedBox` (`layout-system.ts`); `makeVariants(plan)` (three concept plans, `variants.ts`); small
|
|
27
27
|
colour helpers (`color.ts`).
|
|
28
|
+
- **Art direction (0.5.0)** — `breakLines` (balanced line breaks, `typography.ts`), tonal palettes +
|
|
29
|
+
`harmonize` (one WCAG-AA text colour per set, `palette.ts`), panorama motifs (`decor.ts`), and in
|
|
30
|
+
`composeSet`: the hero, accent rhythm, callouts, mascots and shadows (see "Art direction" below).
|
|
28
31
|
|
|
29
32
|
```ts
|
|
30
33
|
import { composeTemplate, validateTemplate } from '@appshoteditor/shot-dsl';
|
|
@@ -95,19 +98,30 @@ Plan additions (all optional; a 0.3.0 plan composes and validates unchanged):
|
|
|
95
98
|
| `style.presentation` | plan | `device` (default) · `frameless` (rounded screenshot + shadow) · `zoom` (magnified crop of `crop`, else the centre of the `focus` band) |
|
|
96
99
|
| `style.tilt`, `style.tiltScreens` | plan | Degrees (clockwise) applied only to the listed screen indices. A tilted subject prefers a bleed under `auto`/`deep`; the tilt is reduced (`tilt-reduced`) if it can't keep the side margins. |
|
|
97
100
|
| `style.bleed` | plan | `auto` · `none` · `deep` (see above) |
|
|
98
|
-
| `style.palette` | plan | `{ mode: "family" \| "sequence", colors: [hex…] }`. Background for screens that omit one. |
|
|
99
|
-
| `style.panorama` | plan | `{ spans
|
|
101
|
+
| `style.palette` | plan | `{ mode: "tonal" \| "family" \| "sequence", colors: [hex…], tone? }`. Background for screens that omit one; ONE text colour per set (see Art direction). |
|
|
102
|
+
| `style.panorama` | plan | `{ spans?: [[0,1], …], straddle?: true \| [screen…], decoration?: "orbs" \| "honeycomb" \| "wave" \| "none" }`. Adjacent screens share one continuous background; `straddle` picks the span-start screens whose device crosses the seam (only by ≥ 18% of it). |
|
|
103
|
+
| `style.hero`, `style.rhythm`, `style.callouts`, `style.shadows` | plan | 0.5.0 — see Art direction. |
|
|
104
|
+
| `art` | plan | 0.5.0 `[{ id, url, width, height, faces? }]` brand art (uploaded-asset URLs only). |
|
|
105
|
+
| `callout`, `mascot` | screen | 0.5.0 — a magnified crop of the selling element · brand art placement. |
|
|
100
106
|
| `style.font` | plan | One of `COMPOSE_FONTS` (the editor's font list). |
|
|
101
107
|
| `focus`, `crop`, `presentation`, `tilt`, `badge` | screen | Focus band · zoom crop `{x,y,w,h}` · per-screen overrides · a social-proof pill above the headline |
|
|
102
108
|
|
|
103
109
|
Output shapes:
|
|
104
110
|
|
|
105
|
-
- **
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
- **Text colour (0.5.0: ONE per set).** An explicit `headlineColor` / `subheadlineColor` always
|
|
112
|
+
wins. Otherwise EVERY screen takes part in choosing one set colour: `harmonize` tries a dark and a
|
|
113
|
+
light candidate (a deep / pale tint of the brand hue for tonal palettes, else `#111827` / white),
|
|
114
|
+
sampling every screen on a 5×7 grid. Palette / span backgrounds may be re-toned (moved away from
|
|
115
|
+
the text in small steps until the worst sample passes WCAG 4.5); explicit backgrounds are sampled
|
|
116
|
+
as they are and never recoloured. The candidate needing the least re-toning wins (the other only
|
|
117
|
+
if it also passes on the explicit backgrounds for ≤ 0.15 more). If the set colour still can't reach
|
|
118
|
+
4.5 on an explicit background, ONLY that screen gets its own best colour (a panorama span whose
|
|
119
|
+
fill is explicit counts as ONE unit: one colour for the whole span, so the scene stays consistent) —
|
|
120
|
+
`report.screens[i].textColorSource === "screen"` (else `"set"`, or `"plan"` for an explicit
|
|
121
|
+
`headlineColor`). An explicit-only set keeps white whenever white passes (0.4.0). The headline and
|
|
122
|
+
subheadline are also checked densely right behind their boxes; a screen that no colour can bring to
|
|
123
|
+
4.5 warns `contrast-low` — never silent. The subheadline's brand tint is used only when it clears
|
|
124
|
+
4.6 (a margin, so it never lands at 4.49).
|
|
111
125
|
- **Frameless / zoom** is a plain `type: 'image'` layer:
|
|
112
126
|
- Its `src` is the uploaded screenshot, with `crossOrigin: 'anonymous'`.
|
|
113
127
|
- It has a rounded `clipPath` Rect in the image's local space, `imageCornerRadius` (canvas units,
|
|
@@ -137,6 +151,58 @@ Output shapes:
|
|
|
137
151
|
- **Badge** is a rounded `Rect` pill plus a `Textbox` (`templateKey: 'badge'`). They sit in a badge row
|
|
138
152
|
above the headline, reserved set-wide, so they never collide with the text or the device.
|
|
139
153
|
|
|
154
|
+
### Art direction (0.5.0)
|
|
155
|
+
|
|
156
|
+
- **Line breaks.** Headlines and subheadlines are set in `breakLines` lines. An explicit `\n` is ALWAYS
|
|
157
|
+
a break — lines the author broke are never merged, on any device or role. Only a line (explicit or
|
|
158
|
+
not) wider than the box is broken further: into the greedy line count, re-balanced (no single word
|
|
159
|
+
on the last line, breaks after punctuation preferred, never after "the"/"to"/…; past 40 tokens it
|
|
160
|
+
breaks greedily, so absurd copy fails fast). CJK / kana / Hangul count ~1 em per glyph and may
|
|
161
|
+
break between glyphs. The breaks are emitted as `\n` in the Textbox. A single word wider than the
|
|
162
|
+
box throws (Fabric can't wrap inside a word — it would widen the box past the margins).
|
|
163
|
+
Subheadline: 0.5× the headline, weight 600, full opacity, tinted 18% toward the brand hue when
|
|
164
|
+
that still passes AA. Text box 0.88 W.
|
|
165
|
+
- **Tonal palette.** `{ mode: "tonal", colors: [base, accent?], tone: "light" | "vivid" | "deep" }`:
|
|
166
|
+
screen i gets tonal step i % 4 of the base hue as a vertical gradient (the hero step 0). The accent
|
|
167
|
+
fills badge pills. Tonal panorama spans run one ramp through their members' tones.
|
|
168
|
+
- **Hero** (`style.hero`, default ON for screen 1; `false` = off): headline 1.25× (stepped down in
|
|
169
|
+
0.05s — to 1× if needed — so it never has more lines than at the set size; `scale` 1–1.4), its own group with bleed `deep`, optional `layout`, `tilt`,
|
|
170
|
+
`badge`, `mascot`. `report.screens[i].role` is `hero`.
|
|
171
|
+
- **Rhythm** (`style.rhythm: { every, treatment }`): screens where `i % every === every − 1` are
|
|
172
|
+
`accent`s — `text-bottom` layout, or `callout` (a forced callout + deep bleed). All `set`-role
|
|
173
|
+
screens still share one group, type size, text area, scale and baseline.
|
|
174
|
+
- **Callouts** (`screen.callout` crop, or `style.callouts: "auto"` from a focus band ≤ 50% tall): an
|
|
175
|
+
image layer with the SAME uploaded `src` + `cropX`/`cropY`, rounded, shadowed, magnified 1.6–2.2×
|
|
176
|
+
(down to 1.25× to fit), inside 3.5% side margins, between the text block and a 4% far-edge margin.
|
|
177
|
+
It may cover its own source slice (the pop-out), but it hides at most `CALLOUT_MAX_FOCUS_COVER`
|
|
178
|
+
(35%) of the REST of the focus band: it is centred on its source when that allows, else moved
|
|
179
|
+
up/down or out to a side edge (breaking out of the device), else made smaller; if nothing works it
|
|
180
|
+
is skipped (`callout-skipped`). `report.screens[i].callout.focusCover` is the share hidden. Never on
|
|
181
|
+
zoom cards or a straddling device.
|
|
182
|
+
- **Mascots** (`plan.art` + `screen.mascot` / `style.hero.mascot`): an image layer (uploaded src,
|
|
183
|
+
`flipX` to face into the canvas when `faces` is given, shadow). Anchors `headline` / `device-top` /
|
|
184
|
+
`device-side` / `seam` are tried in order with fallbacks at 100/85/70% size; a placement must clear
|
|
185
|
+
every text line, the badge, the subheadline, the callout, every other mascot on the screen
|
|
186
|
+
(including a neighbour's seam half) and the focus band's core (its outer 10% W may be overlapped).
|
|
187
|
+
`seam` puts one mascot across a panorama seam, emitted on both screens (±W). Otherwise
|
|
188
|
+
`mascot-skipped`.
|
|
189
|
+
- **Badges** keep their label at WCAG ≥ 4.5 on the pill: a solid (tonal accent) pill is toned
|
|
190
|
+
toward black or white until it does; a translucent one gets fainter.
|
|
191
|
+
- **Shadows** (`style.shadows`, default true), tinted with the brand hue: device layers get
|
|
192
|
+
`fabricData.deviceShadow = { color, blur, offsetX, offsetY }` in CANVAS units — the editor casts it
|
|
193
|
+
from the frame's screenshot (a Fabric shadow on the frame PNG would fall into its transparent screen
|
|
194
|
+
hole); frameless / zoom / callouts / mascots get a Fabric `shadow` in object units.
|
|
195
|
+
`validateTemplate` checks `deviceShadow` (device layers only; colour ≤ 64 chars; blur 0…canvasWidth;
|
|
196
|
+
offsets ±canvasWidth).
|
|
197
|
+
- **Panorama motif** (`decoration: "honeycomb" | "wave"`): the motif is laid out across the whole span,
|
|
198
|
+
but each screen emits only ITS part (the hexagons / wave segments touching it plus a 2-stroke
|
|
199
|
+
overdraw) as one locked `Path` in its own coordinates, rounded to 0.1 unit; both neighbours emit
|
|
200
|
+
the same points around a seam, so it runs on continuously. (0.5.0-pre emitted the whole N·W path on
|
|
201
|
+
every screen — O(N²) bytes.)
|
|
202
|
+
- **Handoff size.** `MAX_HANDOFF_BYTES` (256 KiB, the server's cap) and `handoffBytes(template)` (UTF-8
|
|
203
|
+
size of the `{ template }` request body). The skill's `lint` / `compose` / `publish` / `variants`
|
|
204
|
+
error over the cap and warn over 80%. A 30-screen phone + iPad + Mac honeycomb panorama is ≈ 166 KB.
|
|
205
|
+
|
|
140
206
|
`report.warnings` (`ComposeWarning`) flags:
|
|
141
207
|
|
|
142
208
|
- a headline over 5 words, or estimated at more than 2 lines at the set size;
|
|
@@ -147,31 +213,41 @@ Output shapes:
|
|
|
147
213
|
- skipped straddles, and more than one straddle per set;
|
|
148
214
|
- tilts reduced to keep the side margins;
|
|
149
215
|
- zoom crops that can't show their whole focus band;
|
|
150
|
-
- long badges
|
|
216
|
+
- long badges;
|
|
217
|
+
- 0.5.0: a stranded last headline word (`headline-orphan`), a screen no colour can bring to AA
|
|
218
|
+
(`contrast-low`), a callout that can't be magnified or placed without hiding the focus band
|
|
219
|
+
(`callout-skipped`), no room for a mascot (`mascot-skipped`).
|
|
220
|
+
|
|
221
|
+
Backward compatibility (0.5.0): a 0.4.0 plan still composes, but screen 1 is now the hero (1.25×
|
|
222
|
+
headline, `deep` bleed, its own device pose) and devices get shadows — `style.hero: false` /
|
|
223
|
+
`style.shadows: false` restore the 0.4.0 look. Balanced line breaks can change where a headline
|
|
224
|
+
wraps, and the new warnings can make `--strict` fail for a plan that passed before.
|
|
151
225
|
|
|
152
226
|
Warnings never block composing.
|
|
153
227
|
|
|
154
|
-
`makeVariants(plan)` returns three distinct
|
|
155
|
-
candidates. Each is named `<name> — A
|
|
228
|
+
`makeVariants(plan)` returns three distinct ART DIRECTIONS, intended as Product Page Optimization
|
|
229
|
+
test candidates. Each is named `<name> — A Brand Classic` / `— B Clean Frameless` / `— C Story Panorama`:
|
|
156
230
|
|
|
157
|
-
- **A:** device frames
|
|
158
|
-
|
|
159
|
-
- **
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
231
|
+
- **A Brand Classic:** device frames on the `vivid` tonal palette, hero with the mascot (first `art`)
|
|
232
|
+
+ badge, `callouts: "auto"` (plus explicit ones), rhythm every 4 (`text-bottom`).
|
|
233
|
+
- **B Clean Frameless:** `light` tonal palette, full-width frameless screenshots with a `deep` bleed
|
|
234
|
+
(a zoom card only for an explicit `crop` — B1), callouts, hero at 1.3× with the mascot.
|
|
235
|
+
- **C Story Panorama:** `deep` tonal palette; spans = the input's `panorama.spans`, else triples and
|
|
236
|
+
pairs (`panoramaRuns`); `decoration` = the input's (not `orbs`), else `wave`; a mascot crossing the
|
|
237
|
+
first seam of every span but the hero's; the hero tilted (the input's `style.tilt`, or 8°); bleed
|
|
238
|
+
`deep`; a straddle only if the input asks.
|
|
163
239
|
|
|
164
240
|
**Kept from the input** in every concept:
|
|
165
241
|
- copy (headline / subheadline / badge) and `headlineColor` / `subheadlineColor`;
|
|
166
|
-
- `background`, `deviceId`, `screenshot`, `focus`, `crop`;
|
|
242
|
+
- `background`, `deviceId`, `screenshot`, `focus`, `crop`, `callout`, `mascot`, `art`;
|
|
167
243
|
- canvas size;
|
|
168
|
-
- `style.palette
|
|
244
|
+
- the brand colours (`style.palette.colors`; else the first screen's background colour), `style.font`,
|
|
245
|
+
and `style.bleed: none`.
|
|
169
246
|
|
|
170
247
|
**Overridden:**
|
|
171
|
-
- every `layout` becomes `text-top
|
|
172
|
-
share a layout;
|
|
248
|
+
- every `layout` becomes `text-top` (the hero and rhythm accents differ on purpose);
|
|
173
249
|
- per-screen `presentation` and `tilt` are dropped;
|
|
174
|
-
-
|
|
250
|
+
- palette mode → `tonal` with the concept's tone; hero, rhythm, callouts, panorama per concept.
|
|
175
251
|
|
|
176
252
|
## Layer order
|
|
177
253
|
|
|
@@ -236,7 +312,10 @@ and the editor converts it on import.
|
|
|
236
312
|
> Consumers: a template in the 0.3.0 shape needs an editor that understands `fabricData.screenshot`
|
|
237
313
|
> (appshoteditor.com with the matching importer). 0.4.0 adds no new layer shapes that the 0.3.0 validator
|
|
238
314
|
> or importer would reject: frameless/zoom are ordinary image layers, and panorama and badges are
|
|
239
|
-
> ordinary shape and text layers.
|
|
315
|
+
> ordinary shape and text layers. 0.5.0 likewise: callouts and mascots are image layers with
|
|
316
|
+
> uploaded-asset srcs, motifs are `Path` shape layers — all accepted by the 0.4.0 validator (and by
|
|
317
|
+
> 0.5.0's, which additionally checks the shape of `deviceShadow`). Only the device shadow needs a
|
|
318
|
+
> 0.5.0-aware editor to render; older editors ignore it.
|
|
240
319
|
|
|
241
320
|
Zero runtime dependencies. The `schemaVersion` is the compatibility contract between producers and the editor.
|
|
242
321
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appshoteditor/shot-dsl",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "App Shot Editor layout DSL + device-frame geometry
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "App Shot Editor layout DSL + device-frame geometry \u2014 framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./src/index.ts",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"src/device-frames.ts",
|
|
24
24
|
"src/layout-system.ts",
|
|
25
25
|
"src/color.ts",
|
|
26
|
-
"src/variants.ts"
|
|
26
|
+
"src/variants.ts",
|
|
27
|
+
"src/typography.ts",
|
|
28
|
+
"src/palette.ts",
|
|
29
|
+
"src/decor.ts"
|
|
27
30
|
],
|
|
28
31
|
"sideEffects": false,
|
|
29
32
|
"keywords": [
|