sliderpro-agentic-skills-etch 0.1.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.
@@ -0,0 +1,492 @@
1
+ ---
2
+ icon: robot
3
+ ---
4
+
5
+ # AI Skills Reference
6
+
7
+ Everything an agent needs to build and configure **Slider Pro for Etch** through the Etch AI
8
+ Connector. Read this file in full at session start. Its companion,
9
+ `slider-pro-skills-reference.md`, is lookup-only: Grep into it, never read it whole.
10
+
11
+ ### When to consult the reference file
12
+
13
+ | You need | Section to Grep |
14
+ | --- | --- |
15
+ | A prop's exact key, path, attribute or default | `## 1. Prop reference` |
16
+ | A CSS variable name for arrows, dots, progress or the lightbox | `## 2. CSS variables` |
17
+ | Sync Custom Element details, multiple selectors, the overlap caveat | `## 3. Sync Custom Element` |
18
+ | Custom Options or the JavaScript API | `## 4. Escape hatches` |
19
+ | Whether an existing build is a premade template | `## 5. Recognising an existing setup` |
20
+ | Site-wide plugin settings | `## 6. Admin settings` |
21
+
22
+ Everything else lives here.
23
+
24
+ ***
25
+
26
+ ## START HERE: mandatory workflow
27
+
28
+ Do these in order. Skipping a step is how sessions go wrong.
29
+
30
+ ### 0. Skill file first
31
+
32
+ Read this file before touching the connector. If the user asks for something and you have not read
33
+ it this session, read it now.
34
+
35
+ ### 1. Connect and preflight
36
+
37
+ ```bash
38
+ npx @digital-gravy/etch-connector serve # once; leave running
39
+ npx @digital-gravy/etch-connector tabs # confirm exactly one tab for this site
40
+ ```
41
+
42
+ The tab name printed as `+ tab "..."` is the `-t` value for every `eval`. Use **one connected tab
43
+ per site**. Never two tabs on the same site.
44
+
45
+ Then confirm the plugin is actually there before planning anything:
46
+
47
+ ```js
48
+ const comps = etch.components.list();
49
+ return comps.filter(c => c.name.startsWith('DWC Slider') || c.name === 'DWC Slide')
50
+ .map(c => c.name + ' = ' + c.id);
51
+ ```
52
+
53
+ If that returns fewer than seven, stop and tell the user the components are not installed or are
54
+ out of date. Do not improvise replacements.
55
+
56
+ ### 2. Scope clarification and user confirmation gate
57
+
58
+ Before building, state back in one short paragraph: what you will create, where it will go, and
59
+ what you will change on anything that already exists. Wait for confirmation. Never restructure an
60
+ existing page as a side effect of adding a slider.
61
+
62
+ ### 3. Resolve component IDs by name, every session
63
+
64
+ **Component IDs are site-specific. Never hardcode them.** Resolve all seven by name at the start of
65
+ every session and keep the map for the rest of it.
66
+
67
+ ```js
68
+ const byName = {};
69
+ for (const c of etch.components.list()) byName[c.name] = c.id;
70
+ // byName['DWC Slider Wrapper'], byName['DWC Slider'], byName['DWC Slide'], ...
71
+ ```
72
+
73
+ ### 4. The native-first gate
74
+
75
+ **Slider Pro is props and CSS. It is almost never JavaScript.**
76
+
77
+ Before you write a single line of script, you must be able to say which of these two cases you are
78
+ in. If neither applies, you are about to do something the plugin already does:
79
+
80
+ 1. **A per-element index or distance beyond plus or minus 1.** The plugin gives you exactly three
81
+ state classes: `is-active`, `is-prev`, `is-next`. That is distance 0 and plus or minus 1.
82
+ A design needing "the third card back sits lower and darker", a z-index ladder, or hit-testing
83
+ past the immediate neighbours cannot be expressed from those classes, because nothing in the
84
+ DOM says how far a given element is from the active one.
85
+ 2. **An interpolable custom property.** `CSS.registerProperty({ name: '--x', syntax: '<number>' })`
86
+ so a number can animate. A plain custom property does not interpolate.
87
+
88
+ Everything else is a prop or a CSS rule. In particular, **do not write script for**: per-slide
89
+ transforms, tilts, scaling, fading or perspective (the `slides.*` group does all of it), responsive
90
+ behaviour (the shorthand does it), autoplay, looping, marquees, syncing elements to a slider, or
91
+ turning a carousel into a grid.
92
+
93
+ ### 5. Declare before you script
94
+
95
+ State the method before writing the code: which blocks you will touch, which props you will set,
96
+ and, if you are writing script, which of the two cases above applies. One sentence each.
97
+
98
+ ***
99
+
100
+ ## Connector contract
101
+
102
+ ### Running scripts
103
+
104
+ ```bash
105
+ npx @digital-gravy/etch-connector eval "return etch.blocks.getTree().length" -t "<tab>"
106
+ npx @digital-gravy/etch-connector eval -f ./script.js -t "<tab>" --timeout 30000
107
+ ```
108
+
109
+ Anything longer than one expression goes in a file with `-f`. Inline multi-line quoting breaks in
110
+ every shell eventually.
111
+
112
+ ### Safe mode
113
+
114
+ Scripts see `etch` and standard JavaScript built-ins. **`window`, `document`, all browser globals,
115
+ network and storage are blocked.**
116
+
117
+ ```
118
+ ReferenceError: "window" is not available in safe mode
119
+ ```
120
+
121
+ This matters constantly here: you cannot query the DOM to check what a slider rendered. Use
122
+ `etch.blocks.*` to inspect the document, and the CDP commands below to see the result.
123
+
124
+ ### The `etch` API surface
125
+
126
+ ```js
127
+ Object.keys(etch)
128
+ // ["blocks", "loops", "styles", "stylesheets", "components",
129
+ // "navigation", "fields", "ui", "history",
130
+ // "saveAsync", "connectAs", "apiVersion", "version"]
131
+ ```
132
+
133
+ The ones this work uses:
134
+
135
+ ```js
136
+ etch.blocks.getTree() // whole document
137
+ etch.blocks.getJson(blockId) // one block + subtree
138
+ etch.blocks.find({ type, class, attribute }) // ids of matching blocks
139
+ etch.blocks.getAttribute(blockId, key)
140
+ etch.blocks.setAttribute(blockId, key, value)
141
+ etch.blocks.create(json, parentId?, index?) // returns new id
142
+ etch.components.list() // [{ id, name }, ...]
143
+ etch.components.getJson(id) // .properties and .blocks
144
+ etch.styles.list() // [{ id, selector, type, collection, css }]
145
+ await etch.saveAsync()
146
+ ```
147
+
148
+ ### Component instances are the unit of work
149
+
150
+ A Slider Pro build is a tree of `etch/component` blocks, each carrying `ref` (the component id) and
151
+ a flat `attributes` map of prop values:
152
+
153
+ ```json
154
+ {
155
+ "blockName": "etch/component",
156
+ "attrs": {
157
+ "metadata": { "name": "- Flow" },
158
+ "ref": 6455,
159
+ "attributes": { "wrapperHeight": "350px", "spaceBetweenSliders": "50px" }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Group props and the one-extra-brace rule
165
+
166
+ Most Slider props live in groups (`layout`, `motion`, `slides`, `autoplay`, ...). A group is stored
167
+ as JSON wrapped in **one extra layer of braces**:
168
+
169
+ ```js
170
+ function getGroup(blockId, key) {
171
+ const raw = etch.blocks.getAttribute(blockId, key);
172
+ return raw ? JSON.parse(raw.slice(1, -1)) : {}; // strip one { and one }
173
+ }
174
+
175
+ function setGroup(blockId, key, obj) {
176
+ etch.blocks.setAttribute(blockId, key, '{' + JSON.stringify(obj) + '}');
177
+ }
178
+ ```
179
+
180
+ Read, modify, write back. Never assemble a group from scratch unless you intend to drop every
181
+ value already in it.
182
+
183
+ **Booleans inside groups are strings**, not JS booleans:
184
+
185
+ ```js
186
+ motion.loop = '{true}'; // correct
187
+ motion.loop = true; // wrong, silently useless
188
+ ```
189
+
190
+ **`setAttribute` validates the key** against the component's registered properties. You cannot set
191
+ an arbitrary `data-*` on a component instance. If a key is rejected, you have the wrong key: check
192
+ the prop reference.
193
+
194
+ ### Visual verification
195
+
196
+ You cannot see the page from inside safe mode. After any visual change:
197
+
198
+ ```bash
199
+ npx @digital-gravy/etch-connector shot -t "<tab>" -s ".my-slider" -o ./out.png
200
+ npx @digital-gravy/etch-connector html ".my-slider" -t "<tab>"
201
+ npx @digital-gravy/etch-connector computed ".my-slider .splide__slide" -t "<tab>" --props transform,opacity
202
+ ```
203
+
204
+ Then actually look at the screenshot. A slider that mounted is not the same as a slider that looks
205
+ right.
206
+
207
+ ### Saving
208
+
209
+ ```js
210
+ await etch.saveAsync();
211
+ ```
212
+
213
+ Save once at the end of a coherent change, not after every attribute. Use `--timeout 30000` or
214
+ more for scripts that save.
215
+
216
+ ***
217
+
218
+ ## The structural contract
219
+
220
+ Get this right and most things work by themselves.
221
+
222
+ ```
223
+ DWC Slider Wrapper [data-slider-wrapper]
224
+ DWC Slider [data-slider-role="main"] .splide
225
+ DWC Slide .splide__slide
226
+ DWC Slide
227
+ DWC Slider Nav Button (arrows)
228
+ DWC Slider Pagination (dots)
229
+ DWC Slider Progress (bar / circular / counter)
230
+ DWC Slider Play-Pause
231
+ ```
232
+
233
+ Four rules follow from it:
234
+
235
+ 1. **Controls find their own slider.** Every control searches the Slider first, then the Wrapper.
236
+ Put arrows above the track and a counter below it and both connect. There are no IDs to wire.
237
+ 2. **A Wrapper can hold more than one Slider.** Set `sliderSetup.sliderRole` to `main` on one and
238
+ `thumbnails` on the other and they sync automatically. No Sync Group needed.
239
+ 3. **Across separate Wrappers**, a main and thumbnail pair needs a matching
240
+ `sliderSetup.mainThumbnailSyncGroup` on both.
241
+ 4. **A Wrapper with `sliderlessSync.customElement` set and no Slider inside** switches into Sync
242
+ Without Slider. That combination alone is the trigger.
243
+
244
+ ***
245
+
246
+ ## Decision tree
247
+
248
+ **Build from scratch:** components and props first, then CSS.
249
+
250
+ Reach for a premade template only when the user names one ("use Deck Featured"). Then paste its
251
+ JSON and customise, and read `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` or `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` for
252
+ its knobs.
253
+
254
+ **If the user points at an existing slider**, first check whether it is a premade template. Grep
255
+ `## 5. Recognising an existing setup` in the reference for the signature classes, and if it matches,
256
+ configure it the way its documentation describes instead of inventing new CSS over the top.
257
+
258
+ ***
259
+
260
+ ## Design recipes
261
+
262
+ Each of these is a shipped template reduced to the settings that produce it.
263
+
264
+ ### Cover-flow carousel (Slider Flow): no script, no CSS
265
+
266
+ The whole effect is the `slides.*` group, which applies transforms to inactive slides and separate
267
+ `-Active` values to the current one.
268
+
269
+ ```js
270
+ setGroup(sliderId, 'layout', { slidesPerPage: '3 lg:1', gapBetweenSlides: '20px',
271
+ sliderEdgeOffset: 'lg:20% md:18%' });
272
+ setGroup(sliderId, 'motion', { focus: 'center', loop: '{true}', speed: '800',
273
+ updateOnMove: '{true}' });
274
+ setGroup(sliderId, 'slides', { perspective: '950px', opacity: '0.3', scale: '0.9',
275
+ borderRadius: '1rem',
276
+ transition: 'transform 0.5s ease, opacity 0.5s ease' });
277
+ setGroup(sliderId, 'dimensions', { sliderHeight: '350px md:300px sm:180px' });
278
+ ```
279
+
280
+ `motion.focus: 'center'` is what puts the active slide in the middle. The neighbours are dimmed and
281
+ shrunk purely by `slides.opacity` and `slides.scale`, which apply to inactive slides only.
282
+
283
+ For an angled flip, add `slides.rotateY` and turn on `slides.flipNextRotateY` so the slide on the
284
+ other side mirrors the angle instead of repeating it. Same for `translateX` with
285
+ `flipNextTranslateX`.
286
+
287
+ ### Logo marquee (Slider Marquee): no script
288
+
289
+ Two Sliders in one Wrapper, running in opposite directions.
290
+
291
+ ```js
292
+ // both rows
293
+ setGroup(sliderId, 'dimensions', { slideAutoWidth: '{true}' }); // slides size to content
294
+ setGroup(sliderId, 'motion', { loop: '{true}' });
295
+ setGroup(sliderId, 'autoscroll', { infiniteScroll: '{true}', scrollSpeed: '1',
296
+ pauseOnHover: '{false}', pauseOnFocus: '{false}' });
297
+ setGroup(sliderId, 'layout', { gapBetweenSlides: '2.4rem' });
298
+
299
+ // second row only
300
+ setGroup(secondId, 'sliderSetup', { slldeDirection: 'rtl' }); // note the spelling
301
+ ```
302
+
303
+ Edge Fade goes on the **Wrapper** (`props.edgeFade`), not the sliders, so both rows fade as one
304
+ unit. `props.pauseSlidersOnHover` on the Wrapper pauses both rows together.
305
+
306
+ Infinite Scroll needs the Auto-Scroll extension enabled in admin settings, which is the default.
307
+
308
+ ### Synced elements (Zeon, Chronos, Team)
309
+
310
+ The pattern behind the full-bleed hero, the timeline and the staff showcase is one prop.
311
+
312
+ ```js
313
+ setGroup(sliderId, 'sliderSetup', { syncCustomElement: '.timeline-node',
314
+ syncCustomElementNav: '{true}' });
315
+ ```
316
+
317
+ The plugin then moves `is-active`, `is-prev` and `is-next` across those elements as the slider
318
+ moves, and with nav on, clicking one jumps the slider to it. **You write the CSS for the three
319
+ states.** The elements do not need to be inside the slider and do not need to match the slide
320
+ count.
321
+
322
+ Grep `## 3. Sync Custom Element` in the reference for multiple selectors and the overlap caveat.
323
+
324
+ ### Carousel on mobile, grid on desktop
325
+
326
+ ```js
327
+ setGroup(sliderId, 'layout', { layoutMode: 'static md:slider', gridColumns: '3 sm:1' });
328
+ ```
329
+
330
+ Above `md` it is a plain CSS grid; at `md` and below it is a real carousel. The slider is built and
331
+ torn down live as the viewport crosses the breakpoint. **Controls are hidden in static mode**,
332
+ because there is no live slider to drive.
333
+
334
+ ### Card decks with no slider (Deck, Fall): the one place script is allowed
335
+
336
+ Structure: a Wrapper with `sliderlessSync.customElement` pointing at your cards, no Slider inside,
337
+ cards nested three deep as `card > lift > content` so the 3-D survives.
338
+
339
+ ```js
340
+ setGroup(wrapperId, 'sliderlessSync', {
341
+ customElement: '.deck-card', customElementNav: '{true}',
342
+ loop: '{true}', autoplay: '{true}', autoplayInterval: '4000'
343
+ });
344
+ ```
345
+
346
+ CSS handles the front card and its two neighbours from `is-active` / `is-prev` / `is-next`. What
347
+ CSS cannot do is know that a given card is *three* back, so the rows behind, the z-index ladder and
348
+ the click targets need a script. That is case 1 of the native-first gate. The shipped script does
349
+ exactly three things:
350
+
351
+ ```js
352
+ // 1. make a custom property interpolable, so a blur can animate (case 2)
353
+ CSS.registerProperty({ name: '--stack-focus', syntax: '<number>',
354
+ inherits: true, initialValue: '0' });
355
+
356
+ // 2. per card: distance from the active one, capped at the deepest tier the CSS defines
357
+ var rank = Math.abs(distanceFromActive);
358
+ card.setAttribute('data-tier', Math.min(rank, MAX_TIERS));
359
+
360
+ // 3. hit-testing: stack order by rank, and clicks off beyond the clickable rings
361
+ card.style.zIndex = String(total - rank);
362
+ lift.style.pointerEvents = rank <= NAV_RINGS ? 'auto' : 'none';
363
+ ```
364
+
365
+ Step 3 is not optional. Stacked cards are coplanar, so the browser hit-tests them by DOM order and
366
+ a click near the front card can land on one three positions away.
367
+
368
+ Card counts for a looping deck: three are always visible (front plus two), and a loop needs a
369
+ hidden slot at each end, so **five is the minimum** and nine gives the full three-row fan. Fall
370
+ never loops, so it has no minimum.
371
+
372
+ ***
373
+
374
+ ## Script library
375
+
376
+ Include these at the top of any script.
377
+
378
+ ```js
379
+ function comps() {
380
+ const m = {};
381
+ for (const c of etch.components.list()) m[c.name] = c.id;
382
+ return m;
383
+ }
384
+
385
+ function getGroup(id, key) {
386
+ const raw = etch.blocks.getAttribute(id, key);
387
+ return raw ? JSON.parse(raw.slice(1, -1)) : {};
388
+ }
389
+
390
+ function setGroup(id, key, patch) {
391
+ const next = Object.assign(getGroup(id, key), patch);
392
+ etch.blocks.setAttribute(id, key, '{' + JSON.stringify(next) + '}');
393
+ }
394
+
395
+ function component(ref, attributes, children) {
396
+ return {
397
+ blockName: 'etch/component',
398
+ attrs: { ref: ref, attributes: attributes || {} },
399
+ innerBlocks: children || []
400
+ };
401
+ }
402
+
403
+ function findByRef(nodes, ref, out) {
404
+ out = out || [];
405
+ for (const n of nodes || []) {
406
+ if (n.componentId === ref || (n.attrs && n.attrs.ref === ref)) out.push(n);
407
+ findByRef(n.children || n.innerBlocks || [], ref, out);
408
+ }
409
+ return out;
410
+ }
411
+ ```
412
+
413
+ ### Build a wrapper with N slides
414
+
415
+ ```js
416
+ const C = comps();
417
+ const slides = [];
418
+ for (let i = 0; i < 5; i++) slides.push(component(C['DWC Slide'], {}));
419
+
420
+ const id = etch.blocks.create(
421
+ component(C['DWC Slider Wrapper'], {}, [
422
+ component(C['DWC Slider'], {}, slides),
423
+ component(C['DWC Slider Nav Button'], {}),
424
+ component(C['DWC Slider Pagination'], {})
425
+ ])
426
+ );
427
+ return id;
428
+ ```
429
+
430
+ Build **one** first, screenshot it, then scale to the full count. Do not generate twenty slides
431
+ before you have seen one render.
432
+
433
+ ### Read the current state of a slider
434
+
435
+ ```js
436
+ const C = comps();
437
+ const slider = findByRef(etch.blocks.getTree(), C['DWC Slider'])[0];
438
+ const out = {};
439
+ for (const k of ['sliderSetup', 'layout', 'dimensions', 'motion', 'autoplay',
440
+ 'navigation', 'slides', 'autoscroll', 'lightbox', 'breakpoints']) {
441
+ out[k] = getGroup(slider.id, k);
442
+ }
443
+ return JSON.stringify(out, null, 1);
444
+ ```
445
+
446
+ Do this before changing anything on a slider you did not build.
447
+
448
+ ***
449
+
450
+ ## Rules and gotchas
451
+
452
+ Each of these is a real failure, not a style preference.
453
+
454
+ **The responsive shorthand is desktop-first.** `3 md:2 sm:1` means 3 on the widest screens, 2 at
455
+ `md` and below, 1 at `sm` and below. The bare value is the **largest** screen. Breakpoints are
456
+ max-widths (default SM 640, MD 1024, LG 1120). Writing it mobile-first is the single most common
457
+ mistake, and `ltr sm:ttb` does the opposite of what it looks like.
458
+
459
+ **Never set a prop to its default.** It adds noise to the markup and hides real intent.
460
+
461
+ **A nested inner slider must be Slide or Fade, never Loop or Infinite Scroll.** A looping slider
462
+ makes hidden copies of its slides, and copying a slider that contains a slider breaks both. To wrap
463
+ around, use Fade, or Slide with Rewind.
464
+
465
+ **Do not put `overflow`, `opacity`, `filter`, `clip-path`, `mask`, `mix-blend-mode`, `isolation` or
466
+ `contain` on a 3-D stack, its cards, or the lifts.** Any one of them flattens the 3-D into a single
467
+ pile. If you need to clip or tint, add a separate wrapper outside the stack and put it there.
468
+
469
+ **Static layout mode hides the controls**, because there is no live slider to drive. Do not add
470
+ arrows to a slider that is static at every breakpoint.
471
+
472
+ **Slide Auto Width disables Slides Per Page.** Each slide takes its content's width, so the slides
473
+ need their own sizing.
474
+
475
+ **A vertical slider needs a height.** `slldeDirection: 'ttb'` cannot derive height from width, so
476
+ set `dimensions.sliderHeight` or `aspectRatio`. Both take the shorthand.
477
+
478
+ **Splide is a hard dependency for anything with a track.** Card stacks keep working without it,
479
+ sliders and the lightbox do not. If the site has When to Load Slider Assets on "Never", say so
480
+ rather than debugging a slider that will never start.
481
+
482
+ **Do not touch the plugin's own stylesheet.** Style through the Slider Class and CSS variables.
483
+
484
+ ### Do not
485
+
486
+ * Hardcode component IDs.
487
+ * Set an arbitrary `data-*` on a component instance. `setAttribute` will reject it.
488
+ * Write script for anything the `slides.*` group, the shorthand, or Sync Custom Element already
489
+ does.
490
+ * Rebuild a premade template's CSS from scratch when the user is asking you to adjust one.
491
+ * Save after every attribute. Batch the change, then save once.
492
+ * Report success without a screenshot.