@appshoteditor/shot-dsl 0.4.0 → 0.5.1
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 +105 -23
- package/package.json +5 -2
- package/src/compose.ts +1057 -134
- 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,61 @@ 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
|
+
The requested crop is automatically padded, on whichever sides have room inside the screenshot,
|
|
178
|
+
before it's magnified, so the rounded corners of the card don't clip content sitting at the
|
|
179
|
+
requested crop's own corners (e.g. a selection handle); a side with no room (flush against the
|
|
180
|
+
screenshot edge) can still clip there. It may cover its own source slice (the pop-out), but
|
|
181
|
+
it hides at most `CALLOUT_MAX_FOCUS_COVER` (35%) of the REST of the focus band: it is centred on its
|
|
182
|
+
source when that allows, else moved up/down or out to a side edge (breaking out of the device), else
|
|
183
|
+
made smaller; if nothing works it is skipped (`callout-skipped`). `report.screens[i].callout.focusCover`
|
|
184
|
+
is the share hidden. Never on zoom cards or a straddling device.
|
|
185
|
+
- **Mascots** (`plan.art` + `screen.mascot` / `style.hero.mascot`): an image layer (uploaded src,
|
|
186
|
+
`flipX` to face into the canvas when `faces` is given, shadow). Anchors `headline` / `device-top` /
|
|
187
|
+
`device-side` / `seam` are tried in order with fallbacks at 100/85/70% size; a placement must clear
|
|
188
|
+
every text line, the badge, the subheadline, the callout, every other mascot on the screen
|
|
189
|
+
(including a neighbour's seam half) and the focus band's core (its outer 10% W may be overlapped).
|
|
190
|
+
`seam` puts one mascot across a panorama seam, emitted on both screens (±W). Otherwise
|
|
191
|
+
`mascot-skipped`.
|
|
192
|
+
- **Badges** keep their label at WCAG ≥ 4.5 on the pill: a solid (tonal accent) pill is toned
|
|
193
|
+
toward black or white until it does; a translucent one gets fainter.
|
|
194
|
+
- **Shadows** (`style.shadows`, default true), tinted with the brand hue: device layers get
|
|
195
|
+
`fabricData.deviceShadow = { color, blur, offsetX, offsetY }` in CANVAS units — the editor casts it
|
|
196
|
+
from the frame's screenshot (a Fabric shadow on the frame PNG would fall into its transparent screen
|
|
197
|
+
hole); frameless / zoom / callouts / mascots get a Fabric `shadow` in object units.
|
|
198
|
+
`validateTemplate` checks `deviceShadow` (device layers only; colour ≤ 64 chars; blur 0…canvasWidth;
|
|
199
|
+
offsets ±canvasWidth).
|
|
200
|
+
- **Panorama motif** (`decoration: "honeycomb" | "wave"`): the motif is laid out across the whole span,
|
|
201
|
+
but each screen emits only ITS part (the hexagons / wave segments touching it plus a 2-stroke
|
|
202
|
+
overdraw) as one locked `Path` in its own coordinates, rounded to 0.1 unit; both neighbours emit
|
|
203
|
+
the same points around a seam, so it runs on continuously. (0.5.0-pre emitted the whole N·W path on
|
|
204
|
+
every screen — O(N²) bytes.)
|
|
205
|
+
- **Handoff size.** `MAX_HANDOFF_BYTES` (256 KiB, the server's cap) and `handoffBytes(template)` (UTF-8
|
|
206
|
+
size of the `{ template }` request body). The skill's `lint` / `compose` / `publish` / `variants`
|
|
207
|
+
error over the cap and warn over 80%. A 30-screen phone + iPad + Mac honeycomb panorama is ≈ 166 KB.
|
|
208
|
+
|
|
140
209
|
`report.warnings` (`ComposeWarning`) flags:
|
|
141
210
|
|
|
142
211
|
- a headline over 5 words, or estimated at more than 2 lines at the set size;
|
|
@@ -147,31 +216,41 @@ Output shapes:
|
|
|
147
216
|
- skipped straddles, and more than one straddle per set;
|
|
148
217
|
- tilts reduced to keep the side margins;
|
|
149
218
|
- zoom crops that can't show their whole focus band;
|
|
150
|
-
- long badges
|
|
219
|
+
- long badges;
|
|
220
|
+
- 0.5.0: a stranded last headline word (`headline-orphan`), a screen no colour can bring to AA
|
|
221
|
+
(`contrast-low`), a callout that can't be magnified or placed without hiding the focus band
|
|
222
|
+
(`callout-skipped`), no room for a mascot (`mascot-skipped`).
|
|
223
|
+
|
|
224
|
+
Backward compatibility (0.5.0): a 0.4.0 plan still composes, but screen 1 is now the hero (1.25×
|
|
225
|
+
headline, `deep` bleed, its own device pose) and devices get shadows — `style.hero: false` /
|
|
226
|
+
`style.shadows: false` restore the 0.4.0 look. Balanced line breaks can change where a headline
|
|
227
|
+
wraps, and the new warnings can make `--strict` fail for a plan that passed before.
|
|
151
228
|
|
|
152
229
|
Warnings never block composing.
|
|
153
230
|
|
|
154
|
-
`makeVariants(plan)` returns three distinct
|
|
155
|
-
candidates. Each is named `<name> — A
|
|
231
|
+
`makeVariants(plan)` returns three distinct ART DIRECTIONS, intended as Product Page Optimization
|
|
232
|
+
test candidates. Each is named `<name> — A Brand Classic` / `— B Clean Frameless` / `— C Story Panorama`:
|
|
156
233
|
|
|
157
|
-
- **A:** device frames
|
|
158
|
-
|
|
159
|
-
- **
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
234
|
+
- **A Brand Classic:** device frames on the `vivid` tonal palette, hero with the mascot (first `art`)
|
|
235
|
+
+ badge, `callouts: "auto"` (plus explicit ones), rhythm every 4 (`text-bottom`).
|
|
236
|
+
- **B Clean Frameless:** `light` tonal palette, full-width frameless screenshots with a `deep` bleed
|
|
237
|
+
(a zoom card only for an explicit `crop` — B1), callouts, hero at 1.3× with the mascot.
|
|
238
|
+
- **C Story Panorama:** `deep` tonal palette; spans = the input's `panorama.spans`, else triples and
|
|
239
|
+
pairs (`panoramaRuns`); `decoration` = the input's (not `orbs`), else `wave`; a mascot crossing the
|
|
240
|
+
first seam of every span but the hero's; the hero tilted (the input's `style.tilt`, or 8°); bleed
|
|
241
|
+
`deep`; a straddle only if the input asks.
|
|
163
242
|
|
|
164
243
|
**Kept from the input** in every concept:
|
|
165
244
|
- copy (headline / subheadline / badge) and `headlineColor` / `subheadlineColor`;
|
|
166
|
-
- `background`, `deviceId`, `screenshot`, `focus`, `crop`;
|
|
245
|
+
- `background`, `deviceId`, `screenshot`, `focus`, `crop`, `callout`, `mascot`, `art`;
|
|
167
246
|
- canvas size;
|
|
168
|
-
- `style.palette
|
|
247
|
+
- the brand colours (`style.palette.colors`; else the first screen's background colour), `style.font`,
|
|
248
|
+
and `style.bleed: none`.
|
|
169
249
|
|
|
170
250
|
**Overridden:**
|
|
171
|
-
- every `layout` becomes `text-top
|
|
172
|
-
share a layout;
|
|
251
|
+
- every `layout` becomes `text-top` (the hero and rhythm accents differ on purpose);
|
|
173
252
|
- per-screen `presentation` and `tilt` are dropped;
|
|
174
|
-
-
|
|
253
|
+
- palette mode → `tonal` with the concept's tone; hero, rhythm, callouts, panorama per concept.
|
|
175
254
|
|
|
176
255
|
## Layer order
|
|
177
256
|
|
|
@@ -236,7 +315,10 @@ and the editor converts it on import.
|
|
|
236
315
|
> Consumers: a template in the 0.3.0 shape needs an editor that understands `fabricData.screenshot`
|
|
237
316
|
> (appshoteditor.com with the matching importer). 0.4.0 adds no new layer shapes that the 0.3.0 validator
|
|
238
317
|
> or importer would reject: frameless/zoom are ordinary image layers, and panorama and badges are
|
|
239
|
-
> ordinary shape and text layers.
|
|
318
|
+
> ordinary shape and text layers. 0.5.0 likewise: callouts and mascots are image layers with
|
|
319
|
+
> uploaded-asset srcs, motifs are `Path` shape layers — all accepted by the 0.4.0 validator (and by
|
|
320
|
+
> 0.5.0's, which additionally checks the shape of `deviceShadow`). Only the device shadow needs a
|
|
321
|
+
> 0.5.0-aware editor to render; older editors ignore it.
|
|
240
322
|
|
|
241
323
|
Zero runtime dependencies. The `schemaVersion` is the compatibility contract between producers and the editor.
|
|
242
324
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appshoteditor/shot-dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "App Shot Editor layout DSL + device-frame geometry — 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",
|
|
@@ -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": [
|