rikiki-deck 0.5.0 → 0.6.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,55 @@
1
+ ---
2
+ name: rikiki-debug
3
+ description: Use when a rikiki deck renders or behaves wrong — slides unstyled or tiny, letterbox bands clash, content overflows or won't reflow, click-stages/reveals don't fire, navigation dead, livereload silent, or a bundled single-file deck breaks. Triggers on "rikiki not rendering", "deck broken", "slides unstyled", "reveals don't work", "debug a deck".
4
+ ---
5
+
6
+ # Debugging a rikiki deck
7
+
8
+ Work from the symptom. Most breakage is load order, a wrong selector/token, or
9
+ the rendering model — not the engine. `docs/llms/rikiki-reference.md` is the
10
+ source of truth for tags, attributes, and tokens.
11
+
12
+ ## First checks (do these before anything)
13
+
14
+ - **Console + network.** A `Failed to load resource` on `dist/index.js` or the
15
+ theme means a wrong relative path — the deck never upgrades. Fix the two paths
16
+ in the `<head>` first.
17
+ - **Load order.** Theme `<link>` must come **before** `dist/index.js`. Reversed,
18
+ components upgrade with no tokens and render unstyled.
19
+ - **Upgrade.** In the console, `customElements.get('deck-root')` must be defined
20
+ and `document.querySelector('deck-root > [active]')` must match one slide.
21
+
22
+ ## Symptom → cause
23
+
24
+ | Symptom | Likely cause |
25
+ |---|---|
26
+ | Whole deck unstyled / browser-default fonts | theme `<link>` missing or after `index.js`; or a non-rikiki page consuming theme CSS without the engine |
27
+ | Slides tiny / letterboxed on mobile | working as designed — zoom-to-fit canvas (1920×1080) scaled to fit. Want reflow? add `fluid` on `<deck-root>` |
28
+ | Letterbox bands clash with a slide | slide background isn't opaque or uses a gradient/image; bands match only an opaque `background-color` |
29
+ | Content overflows the slide | authored past the logical canvas; use `cqw/cqh` and `--rik-*` sizing, not fixed px |
30
+ | Content missing or in the wrong place / a title/lead doesn't show | wrong or missing `slot=` name — each layout names its slots (e.g. `deck-split` uses `left`/`right` or `a`/`b`/`c`, not arbitrary names). Check the layout's slots in the reference |
31
+ | Arrows don't jump between chapters / `↑↓` does nothing | 2D navigation is opt-in: add `nav="2d"` on `<deck-root>` (needs `<deck-section>` chapters). Without it arrows are linear — by design |
32
+ | One slide's `<style>` leaks deck-wide | the `<style>` lacks `scoped` — without it a light-DOM `<style>` is a global stylesheet |
33
+ | Reveals / click-stages don't fire | `installClickStages()` not called, or wrong attribute (`data-click`, `data-anim=…` — check the reference's exact values). On a deck created **dynamically after** `installClickStages()`, register per instance: `deckRoot.use(clickStagesPlugin())` |
34
+ | `customElements.define` "already used" crash from a plugin | a plugin (or custom code) did a **value** import from a per-component dist file (`dist/deck-code.js`, `dist/deck-root.js`) which re-bundles + re-defines the element. Import types with `import type`, and reach shared state via `customElements.get(...)` or the re-exports from `dist/index.js` |
35
+ | Steps don't advance | missing `steps="N"` + `[data-step-block]`, or `deck-code[step-groups]` JSON malformed |
36
+ | Navigation dead | `mouse-nav="none"`, focus trapped in an input, or an overlay (`?`/`O`) open |
37
+ | Zoom does nothing / "ça zoom pas" | Slide zoom is on by default in the fixed canvas: Ctrl/⌘+wheel, pinch, or `+`/`-`/`0` magnify the slide (drag/wheel to pan, any nav resets). If it does nothing: the deck is in `fluid` mode (no fixed layout to magnify · use the fixed canvas), `no-zoom` is set, or an overlay (`?`/`O`) is open. For reflowing bigger text instead of magnification, use `fluid` + `cqw/cqh` |
38
+ | Embedded deck breaks the host page | older build — 0.5.0+ scopes globals to full-page decks; rebuild/upgrade |
39
+ | Livereload silent | `?live` missing from the URL, or the static server doesn't see file changes |
40
+ | Bundled single-file deck unstyled | `bundle.mjs` resolves a plain relative ref against the deck's own dir; the `rikiki/(dist\|themes\|tokens.css)` convention is what triggers the package-root fallback. A deck pointing outside its dir (`../../dist/index.js`) won't inline — repoint at `rikiki/…`-style paths. See reference §9 |
41
+
42
+ ## Isolate
43
+
44
+ Reproduce against a known-good fixture (`examples/rikiki-tour/`,
45
+ `rikiki/starter.html`). If the fixture works and your deck doesn't, the deck
46
+ markup is the fault — diff its `<head>` and slide tags against the reference.
47
+ For rendering regressions in the engine itself, the Playwright render net
48
+ (`e2e/`) is the fast reproduction harness.
49
+
50
+ ## Rules
51
+
52
+ - Don't patch a symptom with hardcoded px or `!important` — find the wrong
53
+ token, path, or attribute.
54
+ - Only trust tags/attributes/tokens listed in the reference; a silent no-op is
55
+ usually an invented name.
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: rikiki-deck
3
+ description: Use when creating or editing a rikiki presentation deck (HTML decks using <deck-root> and deck-* Web Components), assembling multi-file decks, adding click-stage animations, enabling livereload, or bundling a deck to a single file. Triggers on "rikiki deck", "create a slide deck", "rikiki slides", "presentation deck".
4
+ ---
5
+
6
+ # Authoring rikiki decks
7
+
8
+ Rikiki is a Lit Web Components presentation framework. Decks are plain HTML: a
9
+ theme stylesheet + `dist/index.js`, then a `<deck-root>` wrapping `deck-*` slide
10
+ elements. No build step is required to author or run a deck.
11
+
12
+ ## Before you start
13
+
14
+ Read the full component + attribute reference first:
15
+ `docs/llms/rikiki-reference.md` (in the rikiki repo). It is the source of truth
16
+ for every tag, attribute, slot, and design token. Do not invent tags,
17
+ attributes, or tokens — use only what the reference lists.
18
+
19
+ ## Workflow
20
+
21
+ 1. **Skeleton** — start from the head + `<deck-root>` shape in `starter.html`
22
+ (theme `<link>` first, then `<script type="module" src="./dist/index.js">`).
23
+ 2. **Pick layouts per slide** — one focal idea each. `deck-cover` to open,
24
+ `deck-section` for chapters, `deck-feature` / `deck-split` /
25
+ `deck-feature-cards` for content, `deck-takeaway` to close. Respect named
26
+ `slot=` attributes (`title`, `lead`, `left`/`right`, `a`/`b`/`c`).
27
+ 3. **Prose & code** — put Markdown inside `<deck-md>`; code inside
28
+ `<deck-code lang="…">`. The built-in highlighter only understands
29
+ `js`/`ts`/`json`/`html`/`xml`/`svg`/`css`/`scss`/`less`. Any other `lang`
30
+ (`python`, `rust`, `bash`, `go`, `sql`, …) is silently colored as JS — no
31
+ error — so it looks fine but is wrong. If the deck uses any language outside
32
+ that set, install Shiki **after** `dist/index.js`:
33
+ `import { installShiki } from './dist/shiki.js'; await installShiki({ theme: 'one-dark-pro', langs: ['ts','rust','bash'] });`
34
+ (list every language the deck uses in `langs`). See the reference's Shiki
35
+ section.
36
+ 4. **Reveals** — for stepped builds use `steps="N"` + `[data-step-block]` or
37
+ `deck-code[step-groups]`; for per-element reveals install the click-stages
38
+ plugin (`import { installClickStages } from './dist/click-stages.js';
39
+ installClickStages();`) and annotate elements with `data-click`,
40
+ `data-click="N"`, `data-click-hide`, and
41
+ `data-anim="fade|slide-up|slide-down|slide-left|slide-right|scale|blur|flip-up|draw"`.
42
+ Fine-tune with `data-anim-duration` / `data-anim-delay` (ms) and
43
+ `data-anim-ease="out|spring|in-out|cubic-bezier(…)"`. One-click
44
+ choreographies: `data-click-auto="800"` (chains after the previous stage,
45
+ no click), `data-click-stagger="80"` (container children cascade on one
46
+ click), `data-click-children` (one click per child). Magic move:
47
+ `data-morph="key"` pairs an element across steps or consecutive slides
48
+ (explicit steps for same-click swaps: `data-click-hide="1"` +
49
+ `data-click="1"`). Mouse navigation is on by default (click/wheel/
50
+ chevrons/buttons 4-5) · disable with `mouse-nav="none"` or pick a subset
51
+ like `mouse-nav="wheel arrows"` on `<deck-root>`.
52
+ 5. **Speaker notes** — add `<deck-notes>` inside a slide; press `P` to present.
53
+ 6. **Livereload (authoring)** — add `?live` to the deck URL (e.g.
54
+ `…/deck.html?live`) so `index.js` lazy-loads the poller and auto-reloads on
55
+ file changes. Never ship it in a presented or bundled deck.
56
+ 7. **Multi-file decks** — split slides into `parts/*.html` / `*.md`, list them in
57
+ `deck.config.js`, run `npm run deck decks/<name>/deck.config.js` (or
58
+ `node build/vite-deck.mjs <config>`).
59
+ 8. **Share** — `node bundle.mjs <deck>.html` produces one self-contained file
60
+ (`--no-fonts` strips Google Fonts). Note: `bundle.mjs` only rewrites
61
+ `rikiki/(dist|themes|tokens.css)`-style references, not plain relative paths
62
+ like `../../dist/index.js` — see the reference's multi-deck caveat.
63
+
64
+ ## Verify
65
+
66
+ Serve with `python3 -m http.server` and open the deck; click through every slide
67
+ and every step. Confirm slides are styled and reveals fire in order.
68
+
69
+ ## Rules
70
+
71
+ - Never nest `<deck-root>`.
72
+ - **Asset paths are relative to your deck file** — adjust the theme `<link>`,
73
+ the `dist/index.js` script, and any `click-stages.js` import together. Next to
74
+ `starter.html` it's `./tokens.css` / `./dist/…`; a deck under `examples/<name>/`
75
+ uses `../../rikiki/tokens.css` / `../../rikiki/dist/…`; an npm consumer points
76
+ at their `node_modules/rikiki-deck/…` (or an import map).
77
+ - Load theme CSS before `dist/index.js`.
78
+ - Use semantic `--rik-*` tokens for any color/spacing override, at `:root` (or
79
+ component `--deck-*-…` tokens on one host). Do not hardcode colors.
80
+ - Keep one idea per slide; move detail into `<deck-notes>`.
81
+ - Only use tags, attributes, and tokens listed in `docs/llms/rikiki-reference.md`.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: rikiki-theme
3
+ description: Use when creating, customizing, or debugging a rikiki theme — defining a new color/typography look, overriding design tokens, porting an existing brand into a deck, or fixing a theme where colors/fonts don't apply. Triggers on "rikiki theme", "new theme", "custom theme", "theme tokens", "rebrand a deck".
4
+ ---
5
+
6
+ # Authoring rikiki themes
7
+
8
+ A rikiki theme is one CSS file. Most of it is `--rik-*` custom properties at
9
+ `:root`, but **the rules below `:root` are load-bearing** — the reset,
10
+ `::selection`, the `html/body` binding, and slotted-element styles
11
+ (`deck-cover > h1`, `.lead`, `table.dense`, …) paint the page and slide content.
12
+ Components read only the **semantic** tokens, so re-theming never touches
13
+ component code — you swap one `<link>`.
14
+
15
+ `docs/llms/rikiki-reference.md` lists every semantic token. It is the source of
16
+ truth — do not invent token names.
17
+
18
+ ## Start from a copy
19
+
20
+ **Copy the entire `themes/siliceum.css` (the cleanest example) — not just its
21
+ `:root`** — and change values. A `:root`-only theme renders bodies and slide
22
+ titles unstyled because the painting rules sit below `:root`. Keep the layering:
23
+
24
+ 1. **Palette (private).** `--rik-palette-*` raw brand colors (`paper-50`,
25
+ `ink-900`, `accent-500`…). Only this layer holds hex values. Nothing outside
26
+ the theme reads these.
27
+ 2. **Semantic (public).** The names components consume, mapped onto the palette.
28
+ These names match `themes/rikiki.css` **1:1** — keep every one, change only
29
+ the value. The full set is large; the families below are representative, not
30
+ exhaustive — copy the whole `:root` from `themes/rikiki.css` and re-point
31
+ values rather than hand-listing: `--rik-surface-*` (page/raised/inverse),
32
+ `--rik-text-*` (default/inverse/`--faint`), `--rik-accent` (+`--soft`),
33
+ `--rik-status-*` (success/danger/warn/info, each `bg`/`border`/`text`),
34
+ `--rik-interactive-*`, `--rik-border-*`, `--rik-link-*`, `--rik-focus-*`,
35
+ `--rik-selection-*`, `--rik-decor-*`, `--rik-elevation-*`, `--rik-code-*`
36
+ (syntax surface), `--rik-font-*`, `--rik-font-size-*`, `--rik-space-*`,
37
+ `--rik-radius-*`, `--rik-icon-*`, `--rik-opacity-*`, `--rik-motion-*`,
38
+ `--rik-z-*`.
39
+
40
+ Keep the `@media (prefers-reduced-motion: reduce)` block too — it zeroes the
41
+ `--rik-motion-*` durations and neutralizes the spring ease.
42
+
43
+ ## Fonts
44
+
45
+ Declare `@font-face` (or import a `*-fonts.css`, like `siliceum-fonts.css`) and
46
+ point `--rik-font-sans` / `--rik-font-mono` / `--rik-font-display` at them. The
47
+ default theme pulls Unbounded + Inter + Space Mono from Google Fonts; self-host
48
+ for offline decks.
49
+
50
+ ## Rules
51
+
52
+ - Define the **full** semantic set. A missing token falls back to nothing and
53
+ breaks a component silently — diff your `:root` against `themes/rikiki.css`
54
+ (e.g. `comm -23` of the two token lists must be empty).
55
+ - Put hex only in the palette layer; semantic tokens reference it via `var(...)`.
56
+ Two sanctioned exceptions, as in both shipped themes: `--rik-code__*` (the
57
+ syntax surface) holds raw hex, and alpha tints use `rgba(...)` literals.
58
+ - For a **dark** theme, keep the inverse surfaces (`--rik-palette-night-*`)
59
+ *darker* than the dark page so cover/section/takeaway stay a distinct layer.
60
+ - Don't restyle components in the theme. Per-component tweaks are `--deck-*-…`
61
+ tokens set on that host, not in the theme file.
62
+ - Keep `--rik-*` lowercase; match WCAG contrast (the default theme documents the
63
+ link-contrast caveat inline — read it before lowering contrast).
64
+
65
+ ## Verify
66
+
67
+ Load a deck (e.g. `examples/rikiki-tour/`) with your theme `<link>`. Click
68
+ through covers, sections, callouts, code, and a `deck-takeaway` (it uses
69
+ `--rik-surface-inverse` + `--rik-accent`). Every surface, text tone, accent and
70
+ status color must be intentional — no browser-default black or unstyled blocks.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  A tiny **Lit Web Components** framework for technical presentations. Drop a folder anywhere, open `index.html`, give the talk. No build step on the consumer side · the framework itself is built from TypeScript, but the output is plain ES modules you import directly.
8
8
 
9
- This documentation tracks rikiki v0.5.0.
9
+ This documentation tracks rikiki v0.6.0.
10
10
 
11
11
  ## TL;DR
12
12
 
@@ -16,6 +16,21 @@ cp starter.html my-deck.html
16
16
 
17
17
  Edit `my-deck.html`. Each slide is a custom element. Markdown is available anywhere via `<deck-md>`. Navigate with `←` / `→` (or click, or the scroll wheel), `O` for the overview grid. Decks are linear by default; add `nav="2d"` on `<deck-root>` for chapter/slide grid navigation.
18
18
 
19
+ ## For LLMs / coding assistants
20
+
21
+ If you point a coding assistant (Claude Code, Copilot, …) at this package, give it
22
+ the machine-oriented docs — they are the single source of truth and ship with the
23
+ npm package:
24
+
25
+ - **[`llms.txt`](./llms.txt)** — concise capability map and entry points (the
26
+ [llms.txt convention](https://llmstxt.org)).
27
+ - **[`docs/llms/rikiki-reference.md`](./docs/llms/rikiki-reference.md)** — every
28
+ tag, attribute, slot, design token, plugin, and recipe in one file. Have the
29
+ assistant read this first; tell it not to invent tags or tokens outside it.
30
+ - **Claude Code skills** — `rikiki-deck`, `rikiki-theme`, `rikiki-debug` ship in
31
+ `.claude/skills/` (see [Claude Code skills](#claude-code-skills) to install
32
+ them); they teach an assistant the authoring/theming/debugging workflows.
33
+
19
34
  ## Layout
20
35
 
21
36
  ```
@@ -172,6 +187,16 @@ reflows like a web page:
172
187
  <deck-root fluid> <!-- fills its box, reflows, no letterbox -->
173
188
  ```
174
189
 
190
+ A single slide can also escape the canvas by carrying its own `fluid` attribute
191
+ · that slide gets the real viewport (handy for an embedded live demo) while the
192
+ rest of the deck stays on the fixed canvas:
193
+
194
+ ```html
195
+ <deck-feature fluid>
196
+ <iframe src="playground.html" style="position:fixed; inset:0; border:0;"></iframe>
197
+ </deck-feature>
198
+ ```
199
+
175
200
  Both modes are embed-safe · a `<deck-root>` placed inside a larger page scales
176
201
  to (or fills) its own container and never touches the host page's scroll or
177
202
  typography.
@@ -221,6 +246,25 @@ npm run typecheck # tsc --noEmit
221
246
 
222
247
  `dist/` is versioned · consumers don't run a build.
223
248
 
249
+ ## Claude Code skills
250
+
251
+ The package ships three Claude Code skills so an assistant authoring your deck
252
+ knows the framework: `rikiki-deck` (build a deck), `rikiki-theme` (theming), and
253
+ `rikiki-debug` (diagnose a deck). After `npm install rikiki-deck`, copy them into
254
+ your project (or `~/.claude/skills` for all projects):
255
+
256
+ ```sh
257
+ # project-local · available in this repo only
258
+ mkdir -p .claude/skills
259
+ cp -r node_modules/rikiki-deck/.claude/skills/* .claude/skills/
260
+
261
+ # or global · available in every project
262
+ cp -r node_modules/rikiki-deck/.claude/skills/* ~/.claude/skills/
263
+ ```
264
+
265
+ Claude Code discovers them automatically on the next session. Re-run the copy
266
+ after `npm update rikiki-deck` to pick up skill changes.
267
+
224
268
  ## Reveals & animations
225
269
 
226
270
  Per-element click-through builds are an opt-in plugin (`installClickStages()` from
package/bin/rikiki.mjs CHANGED
@@ -5,14 +5,16 @@
5
5
  // rikiki init --standalone [name.html] [--title "…"] [--theme rikiki|siliceum]
6
6
  // [--with-mermaid] [--with-shiki] [--no-fonts]
7
7
  // rikiki bundle <deck.html> [out.html|-] [--no-fonts]
8
+ // rikiki skills [--dir <path>] [--force]
8
9
  //
9
10
  // `init --standalone` generates a self-contained, share-anywhere deck with no
10
11
  // external links. `bundle` folds an existing deck into the same single file.
11
- // Both use the rolldown-powered inliner in lib/inline.mjs.
12
+ // Both use the rolldown-powered inliner in lib/inline.mjs. `skills` installs the
13
+ // bundled Claude Code skills into a project so the agent auto-discovers them.
12
14
  // ════════════════════════════════════════════════════════════════
13
15
 
14
16
  import { parseArgs } from 'node:util';
15
- import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
17
+ import { readFileSync, writeFileSync, existsSync, statSync, cpSync, mkdirSync } from 'node:fs';
16
18
  import { resolve, dirname, basename, join } from 'node:path';
17
19
  import { fileURLToPath } from 'node:url';
18
20
  import { inlineDeck } from './lib/inline.mjs';
@@ -24,6 +26,7 @@ const HELP = `rikiki · self-contained slide decks
24
26
 
25
27
  rikiki init --standalone [name.html] [options] generate a new single-file deck
26
28
  rikiki bundle <deck.html> [out.html|-] [options] fold an existing deck into one file
29
+ rikiki skills [--dir <path>] [--force] install the Claude Code skills into a project
27
30
 
28
31
  Options:
29
32
  --title "…" deck title (init)
@@ -138,10 +141,40 @@ async function cmdBundle(argv) {
138
141
  if (outputPath !== '-') warnExternal(inlined);
139
142
  }
140
143
 
144
+ // Consumer-facing skills shipped in the npm tarball. `rikiki-component` and
145
+ // `bump-version` stay repo-only (they need the TS sources / release scripts a
146
+ // package consumer doesn't have).
147
+ const DISTRIBUTED_SKILLS = ['rikiki-deck', 'rikiki-theme', 'rikiki-debug'];
148
+
149
+ function cmdSkills(argv) {
150
+ const { values } = parseArgs({
151
+ args: argv,
152
+ options: { dir: { type: 'string' }, force: { type: 'boolean', default: false } },
153
+ });
154
+ const targetRoot = resolve(process.cwd(), values.dir || '.claude/skills');
155
+ let copied = 0;
156
+ for (const name of DISTRIBUTED_SKILLS) {
157
+ const src = join(PKG_ROOT, '.claude', 'skills', name);
158
+ if (!existsSync(src)) continue; // not in this install (e.g. running from a trimmed tarball)
159
+ const dest = join(targetRoot, name);
160
+ if (existsSync(dest) && !values.force) {
161
+ console.error(`rikiki skills · ${name} already exists · use --force to overwrite`);
162
+ continue;
163
+ }
164
+ mkdirSync(dirname(dest), { recursive: true });
165
+ cpSync(src, dest, { recursive: true });
166
+ console.error(`rikiki skills · installed ${name} → ${join(values.dir || '.claude/skills', name)}`);
167
+ copied++;
168
+ }
169
+ if (copied) console.error(`rikiki skills · ${copied} skill(s) installed · restart Claude Code to pick them up`);
170
+ else console.error('rikiki skills · nothing installed');
171
+ }
172
+
141
173
  const [cmd, ...rest] = process.argv.slice(2);
142
174
  try {
143
175
  if (cmd === 'init') await cmdInit(rest);
144
176
  else if (cmd === 'bundle') await cmdBundle(rest);
177
+ else if (cmd === 'skills') cmdSkills(rest);
145
178
  else if (!cmd || cmd === '-h' || cmd === '--help' || cmd === 'help') { console.log(HELP); }
146
179
  else { console.error('rikiki · unknown command: ' + cmd + '\n\n' + HELP); process.exit(1); }
147
180
  } catch (e) {
@@ -0,0 +1,6 @@
1
+ import type { DeckCodeHighlighter } from './deck-code.js';
2
+ export type { DeckCodeHighlighter };
3
+ /** Register (or, with null, remove) a custom highlighter shared by every
4
+ * `<deck-code>`. The function returns the block's inner HTML, or null to fall
5
+ * back to the built-in regex highlighter. Re-highlights existing instances. */
6
+ export declare function setDeckCodeHighlighter(fn: DeckCodeHighlighter | null): void;
@@ -1,6 +1,17 @@
1
1
  import { LitElement } from 'lit';
2
2
  export type DeckCodeLang = 'js' | 'ts' | 'json' | 'html' | 'xml' | 'svg' | 'css' | 'scss' | 'less';
3
+ /** A custom highlighter for every <deck-code> · returns the block's inner HTML,
4
+ * or null to fall back to the built-in regex highlighter. */
5
+ export type DeckCodeHighlighter = (code: string, lang: string) => string | null;
3
6
  export declare class DeckCode extends LitElement {
7
+ /** Shared highlighter override · the opt-in Shiki plugin sets this on the
8
+ * registered class (via customElements.get) so plugin and component share the
9
+ * one class, not separate flat-dist bundles with their own module state. Null
10
+ * keeps the built-in regex highlighter. */
11
+ static highlighter: DeckCodeHighlighter | null;
12
+ /** Re-highlight every <deck-code> on the page · called after the shared
13
+ * highlighter changes so a deck already on screen picks it up. */
14
+ static rehighlightAll(): void;
4
15
  static styles: import("lit").CSSResult;
5
16
  lang: string;
6
17
  hero: boolean;
@@ -9,6 +20,9 @@ export declare class DeckCode extends LitElement {
9
20
  private _html;
10
21
  private _groups;
11
22
  connectedCallback(): void;
23
+ /** Re-run highlighting and request a render · used after the shared
24
+ * highlighter changes (see setDeckCodeHighlighter). */
25
+ rehighlight(): void;
12
26
  private _highlight;
13
27
  /** Public API · called by deck-root when stepping through code groups. */
14
28
  applyStep(n: number): void;
@@ -1 +1 @@
1
- var D="data-click",v="data-click-hide",$="data-click-auto",S="data-click-stagger",Z="data-click-children";function J(t){return!t.hasAttribute(S)&&(t.parentElement?.hasAttribute(S)??!1)}function C(t){return x()?0:parseInt(t.getAttribute("data-anim-delay")??"",10)||0}var P=new WeakSet;function Q(t){t.querySelectorAll(`[${Z}]`).forEach(e=>{P.has(e)||(P.add(e),Array.from(e.children).forEach(n=>{let r=n;r.hasAttribute(D)||r.setAttribute(D,"");for(let s of["data-anim","data-anim-duration","data-anim-delay","data-anim-ease"]){let a=e.getAttribute(s);a&&!r.hasAttribute(s)&&r.setAttribute(s,a)}}))})}function tt(t){return z(t).reduce((e,n)=>Math.max(e,n.step),0)}function z(t){Q(t);let e=[],n=0,r=0;return t.querySelectorAll(`[${D}], [${v}], [${$}], [${S}]`).forEach(s=>{if(s.hasAttribute(S)){let m=parseInt(s.getAttribute(S)??"",10),b=Number.isFinite(m)?Math.max(0,m):80,G=++n;r=0,Array.from(s.children).forEach((A,o)=>{let i=A;e.push({el:i,step:G,hide:i.hasAttribute(v),delay:o*b+C(i),scheduled:!0})});return}if(J(s))return;if(s.hasAttribute($)){r+=parseInt(s.getAttribute($)??"",10)||0,e.push({el:s,step:n,hide:!1,delay:r+C(s),scheduled:!0});return}let a=s.hasAttribute(v),u=s.getAttribute(a?v:D)??"",c=parseInt(u,10),p=Number.isFinite(c)&&c>0,d=p?c:++n;p?n=Math.max(n,c):r=0,e.push({el:s,step:d,hide:a,delay:0})}),e}var O="data-morph",y=!1;function T(t){let e=new Map;return t.querySelectorAll(`[${O}]`).forEach(n=>{let r=n.getAttribute(O);r&&e.set(r,[...e.get(r)??[],n])}),e}function N(t){let e=getComputedStyle(t);return e.display==="none"||e.visibility==="hidden"?!1:(t.style.opacity!==""?t.style.opacity:e.opacity)!=="0"&&t.getClientRects().length>0}function et(t,e){let n=T(e);return Array.from(T(t).keys()).filter(r=>n.has(r))}var nt=t=>t.replace(/[^a-zA-Z0-9_-]/g,"_");function _(t,e){t.forEach((n,r)=>{let s=!1;n.forEach(a=>{let c=(e?.get(a)??N(a))&&!s;c&&(s=!0),a.style.viewTransitionName=c?`rk-morph-${nt(r)}`:"none"})})}function j(t,e){return t.find(n=>e?.get(n)??N(n))}function F(t){let e=new Map;return t.forEach((n,r)=>{let s=j(n);s&&e.set(r,s.getBoundingClientRect())}),e}var X=360,rt="cubic-bezier(0.22, 1, 0.3, 1)";function W(t,e,n){t.forEach((r,s)=>{let a=e.get(s),u=j(r,n);if(!a||!u)return;let c=u.getBoundingClientRect();if(!c.width||!c.height||!a.width||!a.height)return;let p=a.left-c.left,d=a.top-c.top,m=a.width/c.width,b=a.height/c.height;!p&&!d&&m===1&&b===1||u.animate([{transformOrigin:"top left",transform:`translate(${p}px, ${d}px) scale(${m}, ${b})`},{transformOrigin:"top left",transform:"none"}],{duration:X,easing:rt})})}var q=new WeakSet,ot={out:"cubic-bezier(0.22, 1, 0.36, 1)",spring:"cubic-bezier(0.5, 1.8, 0.3, 1)","in-out":"cubic-bezier(0.45, 0, 0.55, 1)"};function x(){return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches??!1}function st(t){if(x())return{dur:1,delay:0,ease:"linear"};let e=parseInt(t.getAttribute("data-anim-duration")??"",10)||320,n=parseInt(t.getAttribute("data-anim-delay")??"",10)||0,r=t.getAttribute("data-anim-ease")??"out";return{dur:e,delay:n,ease:ot[r]??r}}var it="path, line, polyline, polygon, circle, ellipse, rect";function B(t){return t instanceof SVGGeometryElement?[t]:Array.from(t.querySelectorAll(it))}function at(t,e){B(t).forEach(n=>{let r=n.getTotalLength?.()??0;r&&(n.style.strokeDasharray=String(r),n.style.transition=e)})}function ct(t,e){B(t).forEach(n=>{let r=n.getTotalLength?.()??0;r&&(n.style.strokeDashoffset=e?"0":String(r))}),t.style.pointerEvents=e?"":"none"}function lt(t,e,n=!1){if(q.has(t))return;q.add(t);let{dur:r,delay:s,ease:a}=st(t),u=n?0:s,c=t.getAttribute("data-anim"),p=c==="blur"?["opacity","transform","filter"]:c==="draw"?["stroke-dashoffset"]:["opacity","transform"],d=p.map(m=>`${m} ${r}ms ${a} ${u}ms`).join(", ");c==="draw"?at(t,d):(t.style.transition=d,t.style.willChange=p.join(", ")),R(t,e)}function ut(t){switch(t.getAttribute("data-anim")){case"slide-up":return"translateY(16px)";case"slide-down":return"translateY(-16px)";case"slide-left":return"translateX(16px)";case"slide-right":return"translateX(-16px)";case"scale":return"scale(0.92)";case"flip-up":return"perspective(600px) rotateX(35deg)";default:return"none"}}function R(t,e){if(t.getAttribute("data-anim")==="draw"){ct(t,e);return}t.style.opacity=e?"1":"0",t.style.transform=e?"none":ut(t),t.getAttribute("data-anim")==="blur"&&(t.style.filter=e?"none":"blur(12px)"),t.style.pointerEvents=e?"":"none"}function dt(){let t=customElements.get("deck-root");if(!t){console.warn("[rikiki/click-stages] <deck-root> is not defined yet \xB7 import rikiki first");return}let e=t.prototype;if(e._clickStagesInstalled)return;e._clickStagesInstalled=!0;let n=e._maxSteps;e._maxSteps=function(){let o=n.call(this),i=this.slides?.[this.current],l=i?tt(i):0;return Math.max(o,l)};let r=new WeakMap;function s(o){let i=r.get(o);i!==void 0&&(window.clearTimeout(i),r.delete(o))}function a(o,i,l){s(o),r.set(o,window.setTimeout(()=>{r.delete(o),R(o,i)},l))}let u=null;function c(o,i,l){u?u.push({el:o,target:i,delay:l}):a(o,i,l)}function p(){u=[]}function d(){let o=u??[];u=null,o.forEach(({el:i,target:l,delay:f})=>{a(i,l,f)})}function m(o){s(o),u&&(u=u.filter(i=>i.el!==o))}let b=new WeakMap,G=e._applyStep;e._applyStep=function(){G.call(this);let o=this.slides?.[this.current];if(!o)return;let i=b.get(this),l=i&&i.slide===this.current?i.step:-1;b.set(this,{slide:this.current,step:this.step});let f=z(o),E=()=>f.forEach(({el:g,step:M,hide:L,delay:H,scheduled:K})=>{lt(g,L,K),m(g);let w=this.step>=M,V=L?!w:w,Y=w&&this.step===M&&l<M,U=w&&M===0&&l===-1;H>0&&(Y||U)?(R(g,L),c(g,V,H)):R(g,V)}),I=document.startViewTransition?.bind(document),h=l!==-1&&l!==this.step&&!y&&!x()?T(o):null;if(h&&h.size>0){let g=new Map(f.map(({el:M,step:L,hide:H})=>[M,H?this.step<L:this.step>=L]));if(I){_(h),y=!0,p();try{I(()=>{E(),_(h,g)}).finished.finally(()=>{y=!1,d()})}catch{y=!1,E(),d()}}else{let M=F(h);E(),W(h,M,g)}}else E()};let A=e._goTo;e._goTo=function(o){let i=this.slides??[],l=i[this.current],f=i[Math.max(0,Math.min(i.length-1,o))],E=document.startViewTransition?.bind(document);if((l&&f&&l!==f?et(l,f):[]).length===0||y||x()){A.call(this,o);return}let k=this;if(!E){let h=F(T(l));k.__rkMorphActive=!0,A.call(this,o),W(T(f),h),window.setTimeout(()=>{k.__rkMorphActive=!1},X+40);return}_(T(l)),y=!0,k.__rkMorphActive=!0,p();try{E(()=>{A.call(this,o),_(T(f))}).finished.finally(()=>{y=!1,k.__rkMorphActive=!1,d()})}catch{y=!1,k.__rkMorphActive=!1,A.call(this,o),d()}},document.querySelectorAll("deck-root").forEach(o=>{o._applyStep?.()})}export{dt as installClickStages};
1
+ var _="data-click",v="data-click-hide",V="data-click-auto",w="data-click-stagger",Y="data-click-children";function U(t){return!t.hasAttribute(w)&&(t.parentElement?.hasAttribute(w)??!1)}function C(t){return D()?0:parseInt(t.getAttribute("data-anim-delay")??"",10)||0}var I=new WeakSet;function Z(t){t.querySelectorAll(`[${Y}]`).forEach(e=>{I.has(e)||(I.add(e),Array.from(e.children).forEach(n=>{let r=n;r.hasAttribute(_)||r.setAttribute(_,"");for(let o of["data-anim","data-anim-duration","data-anim-delay","data-anim-ease"]){let u=e.getAttribute(o);u&&!r.hasAttribute(o)&&r.setAttribute(o,u)}}))})}function J(t){return q(t).reduce((e,n)=>Math.max(e,n.step),0)}function q(t){Z(t);let e=[],n=0,r=0;return t.querySelectorAll(`[${_}], [${v}], [${V}], [${w}]`).forEach(o=>{if(o.hasAttribute(w)){let i=parseInt(o.getAttribute(w)??"",10),s=Number.isFinite(i)?Math.max(0,i):80,c=++n;r=0,Array.from(o.children).forEach((l,d)=>{let f=l;e.push({el:f,step:c,hide:f.hasAttribute(v),delay:d*s+C(f),scheduled:!0})});return}if(U(o))return;if(o.hasAttribute(V)){r+=parseInt(o.getAttribute(V)??"",10)||0,e.push({el:o,step:n,hide:!1,delay:r+C(o),scheduled:!0});return}let u=o.hasAttribute(v),p=o.getAttribute(u?v:_)??"",a=parseInt(p,10),m=Number.isFinite(a)&&a>0,h=m?a:++n;m?n=Math.max(n,a):r=0,e.push({el:o,step:h,hide:u,delay:0})}),e}var P="data-morph",y=!1;function T(t){let e=new Map;return t.querySelectorAll(`[${P}]`).forEach(n=>{let r=n.getAttribute(P);r&&e.set(r,[...e.get(r)??[],n])}),e}function z(t){let e=getComputedStyle(t);return e.display==="none"||e.visibility==="hidden"?!1:(t.style.opacity!==""?t.style.opacity:e.opacity)!=="0"&&t.getClientRects().length>0}function Q(t,e){let n=T(e);return Array.from(T(t).keys()).filter(r=>n.has(r))}var tt=t=>t.replace(/[^a-zA-Z0-9_-]/g,"_");function S(t,e){t.forEach((n,r)=>{let o=!1;n.forEach(u=>{let a=(e?.get(u)??z(u))&&!o;a&&(o=!0),u.style.viewTransitionName=a?`rk-morph-${tt(r)}`:"none"})})}function j(t,e){return t.find(n=>e?.get(n)??z(n))}function O(t){let e=new Map;return t.forEach((n,r)=>{let o=j(n);o&&e.set(r,o.getBoundingClientRect())}),e}var x=360,et="cubic-bezier(0.22, 1, 0.3, 1)";function F(t,e,n){t.forEach((r,o)=>{let u=e.get(o),p=j(r,n);if(!u||!p)return;let a=p.getBoundingClientRect();if(!a.width||!a.height||!u.width||!u.height)return;let m=u.left-a.left,h=u.top-a.top,i=u.width/a.width,s=u.height/a.height;!m&&!h&&i===1&&s===1||p.animate([{transformOrigin:"top left",transform:`translate(${m}px, ${h}px) scale(${i}, ${s})`},{transformOrigin:"top left",transform:"none"}],{duration:x,easing:et})})}var W=new WeakSet,nt={out:"cubic-bezier(0.22, 1, 0.36, 1)",spring:"cubic-bezier(0.5, 1.8, 0.3, 1)","in-out":"cubic-bezier(0.45, 0, 0.55, 1)"};function D(){return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches??!1}function rt(t){if(D())return{dur:1,delay:0,ease:"linear"};let e=parseInt(t.getAttribute("data-anim-duration")??"",10)||320,n=parseInt(t.getAttribute("data-anim-delay")??"",10)||0,r=t.getAttribute("data-anim-ease")??"out";return{dur:e,delay:n,ease:nt[r]??r}}var ot="path, line, polyline, polygon, circle, ellipse, rect";function N(t){return t instanceof SVGGeometryElement?[t]:Array.from(t.querySelectorAll(ot))}function it(t,e){N(t).forEach(n=>{let r=n.getTotalLength?.()??0;r&&(n.style.strokeDasharray=String(r),n.style.transition=e)})}function st(t,e){N(t).forEach(n=>{let r=n.getTotalLength?.()??0;r&&(n.style.strokeDashoffset=e?"0":String(r))}),t.style.pointerEvents=e?"":"none"}function at(t,e,n=!1){if(W.has(t))return;W.add(t);let{dur:r,delay:o,ease:u}=rt(t),p=n?0:o,a=t.getAttribute("data-anim"),m=a==="blur"?["opacity","transform","filter"]:a==="draw"?["stroke-dashoffset"]:["opacity","transform"],h=m.map(i=>`${i} ${r}ms ${u} ${p}ms`).join(", ");a==="draw"?it(t,h):(t.style.transition=h,t.style.willChange=m.join(", ")),R(t,e)}function ct(t){switch(t.getAttribute("data-anim")){case"slide-up":return"translateY(16px)";case"slide-down":return"translateY(-16px)";case"slide-left":return"translateX(16px)";case"slide-right":return"translateX(-16px)";case"scale":return"scale(0.92)";case"flip-up":return"perspective(600px) rotateX(35deg)";default:return"none"}}function R(t,e){if(t.getAttribute("data-anim")==="draw"){st(t,e);return}t.style.opacity=e?"1":"0",t.style.transform=e?"none":ct(t),t.getAttribute("data-anim")==="blur"&&(t.style.filter=e?"none":"blur(12px)"),t.style.pointerEvents=e?"":"none"}function ut(){let t=new WeakMap,e=new Set;function n(i){let s=t.get(i);s!==void 0&&(window.clearTimeout(s),e.delete(s),t.delete(i))}function r(i,s,c){n(i);let l=window.setTimeout(()=>{e.delete(l),t.delete(i),R(i,s)},c);e.add(l),t.set(i,l)}let o=null;function u(i,s,c){o?o.push({el:i,target:s,delay:c}):r(i,s,c)}function p(){o=[]}function a(){let i=o??[];o=null,i.forEach(({el:s,target:c,delay:l})=>{r(s,c,l)})}function m(i){n(i),o&&(o=o.filter(s=>s.el!==i))}let h=new WeakMap;return{name:"click-stages",setup(){return()=>{e.forEach(i=>{window.clearTimeout(i)}),e.clear()}},steps(i){return J(i)},applyStep(i,s,c){let l=h.get(c.host),d=l&&l.slide===c.current?l.step:-1;h.set(c.host,{slide:c.current,step:c.step});let f=q(s),b=()=>f.forEach(({el:E,step:M,hide:L,delay:H,scheduled:X})=>{at(E,L,X),m(E);let k=c.step>=M,$=L?!k:k,B=k&&c.step===M&&d<M,K=k&&M===0&&d===-1;H>0&&(B||K)?(R(E,L),u(E,$,H)):R(E,$)}),G=document.startViewTransition?.bind(document),g=d!==-1&&d!==c.step&&!y&&!D()?T(s):null;if(g&&g.size>0){let E=new Map(f.map(({el:M,step:L,hide:H})=>[M,H?c.step<L:c.step>=L]));if(G){S(g),y=!0,p();try{G(()=>{b(),S(g,E)}).finished.finally(()=>{y=!1,a()})}catch{y=!1,b(),a()}}else{let M=O(g);b(),F(g,M,E)}}else b()},navigate(i,s,c){let l=s.slides,d=l[s.current],f=l[Math.max(0,Math.min(l.length-1,i))],b=document.startViewTransition?.bind(document);if((d&&f&&d!==f?Q(d,f):[]).length===0||y||D())return!1;let A=s.host;if(!b){let g=O(T(d));return A.__rkMorphActive=!0,c(),F(T(f),g),window.setTimeout(()=>{A.__rkMorphActive=!1},x+40),!0}S(T(d)),y=!0,A.__rkMorphActive=!0,p();try{b(()=>{c(),S(T(f))}).finished.finally(()=>{y=!1,A.__rkMorphActive=!1,a()})}catch{y=!1,A.__rkMorphActive=!1,c(),a()}return!0}}}function lt(){let t=ut();document.querySelectorAll("deck-root").forEach(e=>{e.use?.(t)})}export{ut as clickStagesPlugin,lt as installClickStages};
@@ -0,0 +1 @@
1
+ function t(i){let e=customElements.get("deck-code");if(!e){console.warn("[rikiki/deck-code] <deck-code> is not defined yet \xB7 import rikiki first");return}e.highlighter=i,e.rehighlightAll()}export{t as setDeckCodeHighlighter};
package/dist/deck-code.js CHANGED
@@ -1,2 +1,2 @@
1
- var m=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var l=(d,n,e,s)=>{for(var r=s>1?void 0:s?h(n,e):n,a=d.length-1,i;a>=0;a--)(i=d[a])&&(r=(s?i(n,e,r):i(r))||r);return s&&r&&m(n,e,r),r};import{LitElement as v,html as u,css as y}from"./vendor/lit.js";import{customElement as f,property as g,state as k}from"./vendor/lit.js";function _(d,n){let e=d.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),s=[],r=(t,c)=>{let p=`P${s.length}E`;return s.push(`<span class="${t}">${c}</span>`),p};return n==="html"||n==="xml"||n==="svg"?(e=e.replace(/(&lt;!--[\s\S]*?--&gt;)/g,t=>r("cmt",t)),e=e.replace(/(&lt;!doctype[^&]*&gt;)/gi,t=>r("cmt",t)),e=e.replace(/("[^"]*"|'[^']*')/g,t=>r("str",t)),e=e.replace(/(&lt;\/?)([a-zA-Z][a-zA-Z0-9:-]*)/g,(t,c,p)=>c+r("kw",p)),e=e.replace(/\b([a-zA-Z][a-zA-Z0-9-]*)(?==)/g,t=>r("prop",t))):n==="css"||n==="scss"||n==="less"?(e=e.replace(/(\/\*[\s\S]*?\*\/)/g,t=>r("cmt",t)),e=e.replace(/("[^"]*"|'[^']*')/g,t=>r("str",t)),e=e.replace(/([a-zA-Z-]+)(?=\s*:)/g,t=>r("prop",t)),e=e.replace(/(#[0-9a-fA-F]{3,8})\b/g,t=>r("num",t)),e=e.replace(/\b(\d+(?:\.\d+)?)(px|rem|em|%|vh|vw|vmin|vmax|s|ms|deg)?/g,(t,c,p)=>r("num",c+(p??"")))):(e=e.replace(/(\/\/[^\n]*)/g,t=>r("cmt",t)),e=e.replace(/(['"`])((?:\\.|(?!\1)[^\\])*)\1/g,t=>r("str",t)),e=e.replace(/\b(const|let|var|function|return|if|else|for|while|class|extends|new|export|import|from|as|await|async|of|in|typeof|instanceof|true|false|null|undefined)\b/g,t=>r("kw",t)),e=e.replace(/\b(\d+(?:\.\d+)?)\b/g,t=>r("num",t))),e=e.replace(/P(\d+)E/g,(t,c)=>s[+c]??""),e}var o=class extends v{constructor(){super(...arguments);this.lang="";this.hero=!1;this.nested=!1;this._html="";this._groups=null}connectedCallback(){super.connectedCallback(),this._highlight();try{this._groups=JSON.parse(this.getAttribute("step-groups")??"null")}catch{this._groups=null}}_highlight(){let s=(this.textContent??"").split(`
2
- `);for(;s.length&&!s[0].trim();)s.shift();for(;s.length&&!s[s.length-1].trim();)s.pop();let r=s.filter(i=>i.trim().length>0).reduce((i,t)=>Math.min(i,t.match(/^ */)?.[0].length??0),1/0),a=r===1/0?s:s.map(i=>i.slice(r));this._html=a.map((i,t)=>'<span class="line" data-line="'+(t+1)+'">'+_(i||" ",this.lang)+"</span>").join("")}applyStep(e){if(!this._groups)return;let s=this.shadowRoot?.querySelectorAll(".line");if(s)if(e===0)s.forEach(r=>{r.classList.remove("dim","lit")});else{let r=this._groups[Math.min(e-1,this._groups.length-1)]??[];s.forEach(a=>{let i=parseInt(a.dataset.line??"0",10);a.classList.toggle("lit",r.includes(i)),a.classList.toggle("dim",!r.includes(i))})}}render(){return u`<pre><code .innerHTML="${this._html}"></code></pre>`}};o.styles=y`:host{display:block;background:var(--deck-code-bg,var(--rik-code__bg));border:1px solid var(--deck-code-border,var(--rik-code__border));border-radius:var(--deck-code-radius,var(--rik-radius-md));padding:var(--deck-code-padding-y,var(--rik-space-3)) var(--deck-code-padding-x,var(--rik-space-4));font-family:var(--rik-font-mono);font-size:var(--rik-font-size-mono);line-height:1.7;color:var(--deck-code-text,var(--rik-code__text));box-shadow:var(--rik-elevation-2);overflow:auto;white-space:pre}:host([hero]){display:flex;align-items:safe center;padding:var(--deck-code-padding-y,var(--rik-space-4)) var(--deck-code-padding-x,var(--rik-space-5))}:host([nested]){box-shadow:none;border-radius:var(--rik-radius-sm);padding:var(--deck-code-padding-y,var(--rik-space-2)) var(--deck-code-padding-x,var(--rik-space-3))}pre{margin:0;font:inherit;color:inherit}code{display:block;width:100%;font:inherit;color:inherit}.line{transition:opacity 0.25s ease;display:block}.line.dim{opacity:0.25}.line.lit{opacity:1}.kw{color:var(--deck-code-syntax-kw,var(--rik-code__syntax-keyword))}.fn{color:var(--deck-code-syntax-fn,var(--rik-code__syntax-function))}.str{color:var(--deck-code-syntax-str,var(--rik-code__syntax-string))}.num{color:var(--deck-code-syntax-num,var(--rik-code__syntax-number))}.cmt{color:var(--deck-code-syntax-cmt,var(--rik-code__syntax-comment));font-style:italic}.ty{color:var(--deck-code-syntax-ty,var(--rik-code__syntax-type))}.prop{color:var(--deck-code-syntax-prop,var(--rik-code__syntax-property))}`,l([g({type:String})],o.prototype,"lang",2),l([g({type:Boolean,reflect:!0})],o.prototype,"hero",2),l([g({type:Boolean,reflect:!0})],o.prototype,"nested",2),l([g({type:String,attribute:"step-groups"})],o.prototype,"stepGroups",2),l([k()],o.prototype,"_html",2),o=l([f("deck-code")],o);export{o as DeckCode};
1
+ var h=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var c=(g,n,t,i)=>{for(var e=i>1?void 0:i?m(n,t):n,a=g.length-1,o;a>=0;a--)(o=g[a])&&(e=(i?o(n,t,e):o(e))||e);return i&&e&&h(n,t,e),e};import{LitElement as u,html as v,css as y}from"./vendor/lit.js";import{customElement as f,property as p,state as k}from"./vendor/lit.js";function x(g,n){let t=g.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),i=[],e=(r,l)=>{let d=`P${i.length}E`;return i.push(`<span class="${r}">${l}</span>`),d};return n==="html"||n==="xml"||n==="svg"?(t=t.replace(/(&lt;!--[\s\S]*?--&gt;)/g,r=>e("cmt",r)),t=t.replace(/(&lt;!doctype[^&]*&gt;)/gi,r=>e("cmt",r)),t=t.replace(/("[^"]*"|'[^']*')/g,r=>e("str",r)),t=t.replace(/(&lt;\/?)([a-zA-Z][a-zA-Z0-9:-]*)/g,(r,l,d)=>l+e("kw",d)),t=t.replace(/\b([a-zA-Z][a-zA-Z0-9-]*)(?==)/g,r=>e("prop",r))):n==="css"||n==="scss"||n==="less"?(t=t.replace(/(\/\*[\s\S]*?\*\/)/g,r=>e("cmt",r)),t=t.replace(/("[^"]*"|'[^']*')/g,r=>e("str",r)),t=t.replace(/([a-zA-Z-]+)(?=\s*:)/g,r=>e("prop",r)),t=t.replace(/(#[0-9a-fA-F]{3,8})\b/g,r=>e("num",r)),t=t.replace(/\b(\d+(?:\.\d+)?)(px|rem|em|%|vh|vw|vmin|vmax|s|ms|deg)?/g,(r,l,d)=>e("num",l+(d??"")))):(t=t.replace(/(\/\/[^\n]*)/g,r=>e("cmt",r)),t=t.replace(/(['"`])((?:\\.|(?!\1)[^\\])*)\1/g,r=>e("str",r)),t=t.replace(/\b(const|let|var|function|return|if|else|for|while|class|extends|new|export|import|from|as|await|async|of|in|typeof|instanceof|true|false|null|undefined)\b/g,r=>e("kw",r)),t=t.replace(/\b(\d+(?:\.\d+)?)\b/g,r=>e("num",r))),t=t.replace(/P(\d+)E/g,(r,l)=>i[+l]??""),t}var s=class extends u{constructor(){super(...arguments);this.lang="";this.hero=!1;this.nested=!1;this._html="";this._groups=null}static rehighlightAll(){document.querySelectorAll("deck-code").forEach(t=>{t.rehighlight()})}connectedCallback(){super.connectedCallback(),this._highlight();try{this._groups=JSON.parse(this.getAttribute("step-groups")??"null")}catch{this._groups=null}}rehighlight(){this._highlight(),this.requestUpdate()}_highlight(){let t=s.highlighter?.(this.textContent??"",this.lang||"txt");if(t!=null){this._html=t;return}let e=(this.textContent??"").split(`
2
+ `);for(;e.length&&!e[0].trim();)e.shift();for(;e.length&&!e[e.length-1].trim();)e.pop();let a=e.filter(r=>r.trim().length>0).reduce((r,l)=>Math.min(r,l.match(/^ */)?.[0].length??0),1/0),o=a===1/0?e:e.map(r=>r.slice(a));this._html=o.map((r,l)=>'<span class="line" data-line="'+(l+1)+'">'+x(r||" ",this.lang)+"</span>").join("")}applyStep(t){if(!this._groups)return;let i=this.shadowRoot?.querySelectorAll(".line");if(i)if(t===0)i.forEach(e=>{e.classList.remove("dim","lit")});else{let e=this._groups[Math.min(t-1,this._groups.length-1)]??[];i.forEach(a=>{let o=parseInt(a.dataset.line??"0",10);a.classList.toggle("lit",e.includes(o)),a.classList.toggle("dim",!e.includes(o))})}}render(){return v`<pre><code .innerHTML="${this._html}"></code></pre>`}};s.highlighter=null,s.styles=y`:host{display:block;background:var(--deck-code-bg,var(--rik-code__bg));border:1px solid var(--deck-code-border,var(--rik-code__border));border-radius:var(--deck-code-radius,var(--rik-radius-md));padding:var(--deck-code-padding-y,var(--rik-space-3)) var(--deck-code-padding-x,var(--rik-space-4));font-family:var(--rik-font-mono);font-size:var(--rik-font-size-mono);line-height:1.7;color:var(--deck-code-text,var(--rik-code__text));box-shadow:var(--rik-elevation-2);overflow:auto;white-space:pre}:host([hero]){display:flex;align-items:safe center;padding:var(--deck-code-padding-y,var(--rik-space-4)) var(--deck-code-padding-x,var(--rik-space-5))}:host([nested]){box-shadow:none;border-radius:var(--rik-radius-sm);padding:var(--deck-code-padding-y,var(--rik-space-2)) var(--deck-code-padding-x,var(--rik-space-3))}pre{margin:0;font:inherit;color:inherit}code{display:block;width:100%;font:inherit;color:inherit}.line{transition:opacity 0.25s ease;display:block}.line.dim{opacity:0.25}.line.lit{opacity:1}.kw{color:var(--deck-code-syntax-kw,var(--rik-code__syntax-keyword))}.fn{color:var(--deck-code-syntax-fn,var(--rik-code__syntax-function))}.str{color:var(--deck-code-syntax-str,var(--rik-code__syntax-string))}.num{color:var(--deck-code-syntax-num,var(--rik-code__syntax-number))}.cmt{color:var(--deck-code-syntax-cmt,var(--rik-code__syntax-comment));font-style:italic}.ty{color:var(--deck-code-syntax-ty,var(--rik-code__syntax-type))}.prop{color:var(--deck-code-syntax-prop,var(--rik-code__syntax-property))}`,c([p({type:String})],s.prototype,"lang",2),c([p({type:Boolean,reflect:!0})],s.prototype,"hero",2),c([p({type:Boolean,reflect:!0})],s.prototype,"nested",2),c([p({type:String,attribute:"step-groups"})],s.prototype,"stepGroups",2),c([k()],s.prototype,"_html",2),s=c([f("deck-code")],s);export{s as DeckCode};
@@ -1,5 +1,5 @@
1
- var c="rik-presenter",g=new URL("./index.js",import.meta.url).href,t=null,l=null,o=null;function p(e){let r=Array.from(e.children).filter(i=>i.tagName.toLowerCase().startsWith("deck-")),s=r.findIndex(i=>i.hasAttribute("active")),a=r[s]??null,n=r[s+1]??null,m=(a?.querySelector("deck-notes")?.textContent??"").trim(),u=document.querySelector('link[rel="stylesheet"][href*="rikiki"], link[rel="stylesheet"][href*="tokens"], link[rel="stylesheet"][href*="theme"]')?.href??"",f=Array.from(document.querySelectorAll("style")).map(i=>i.textContent??"").join(`
2
- `),h=document.querySelector('script[type="module"][data-rikiki-bundle]')?.textContent??"";return{current:s+1,total:r.length,slideHtml:a?.outerHTML??"",nextHtml:n?.outerHTML??null,notes:m,themeHref:u,inlineStyles:f,bundleHref:g,bundleInline:h}}function d(e){l&&l.postMessage({type:"state",state:p(e)})}var y=e=>`<!doctype html>
1
+ var H="rik-presenter",R=new URL("./index.js",import.meta.url).href,l=null,p=null,u=null,f=null,h=!1;async function I(){if(f)return f;let e=window.getScreenDetails;if(!e)return null;try{return f=await e.call(window),f}catch{return null}}var g=1280,y=720;function C(e){return{left:Math.round(e.availLeft+(e.availWidth-g)/2),top:Math.round(e.availTop+(e.availHeight-y)/2)}}function M(e){let{left:n,top:r}=C(e);return`popup=yes,width=${g},height=${y},left=${n},top=${r}`}function T(e,n){let{left:r,top:t}=C(n);e.resizeTo(g,y),e.moveTo(r,t)}function S(e,n){e.requestFullscreen?.({screen:n}).then(()=>{if(!u?.has(e)){document.exitFullscreen?.().catch(()=>{});return}h=!0}).catch(()=>{})}function P(){document.fullscreenElement||(h=!1)}function B(){document.removeEventListener("fullscreenchange",P),h&&document.fullscreenElement&&document.exitFullscreen?.().catch(()=>{}),h=!1}function v(e){l?.close(),l=null,p?.close(),p=null,u?.delete(e),e.presenterActive=!1,B()}function D(e){let n=Array.from(e.children).filter(i=>i.tagName.toLowerCase().startsWith("deck-")),r=n.findIndex(i=>i.hasAttribute("active")),t=n[r]??null,d=n[r+1]??null,b=(t?.querySelector("deck-notes")?.textContent??"").trim(),o=document.querySelector('link[rel="stylesheet"][href*="rikiki"], link[rel="stylesheet"][href*="tokens"], link[rel="stylesheet"][href*="theme"]')?.href??"",a=Array.from(document.querySelectorAll("style")).map(i=>i.textContent??"").join(`
2
+ `),m=document.querySelector('script[type="module"][data-rikiki-bundle]')?.textContent??"";return{current:r+1,total:n.length,slideHtml:t?.outerHTML??"",nextHtml:d?.outerHTML??null,notes:b,themeHref:o,inlineStyles:a,bundleHref:R,bundleInline:m}}function E(e){p&&p.postMessage({type:"state",state:D(e)})}var W=e=>`<!doctype html>
3
3
  <html lang="en">
4
4
  <head>
5
5
  <meta charset="utf-8">
@@ -36,7 +36,17 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
36
36
  background: #161c2e;
37
37
  }
38
38
  .panel .body { flex: 1; min-height: 0; padding: 16px; overflow: hidden; border-radius: 8px; }
39
- .panel iframe { width: 100%; height: 100%; border: 0; background: #0f1422; display: block; }
39
+ /* Preview panes center a 16:9 box so the thumbnail matches the projection
40
+ geometry regardless of the pane/window shape (issue #5) \xB7 the size
41
+ container lets the iframe size against the pane in cq units. */
42
+ #current .body, #next .body { display: grid; place-items: center; container-type: size; }
43
+ .panel iframe { border: 0; background: #0f1422; display: block; }
44
+ #current-frame, #next-frame {
45
+ aspect-ratio: 16 / 9;
46
+ width: min(100cqw, calc(100cqh * 16 / 9));
47
+ height: auto;
48
+ max-width: 100%;
49
+ }
40
50
  #notes { font-size: 17px; line-height: 1.6; white-space: pre-wrap; padding: 20px; overflow: auto; color: #e8e4f0; }
41
51
  #notes:empty::before { content: 'No notes for this slide.'; color: rgba(232,228,240,0.4); font-style: italic; }
42
52
  #footer {
@@ -60,6 +70,8 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
60
70
  }
61
71
  button:hover { background: rgba(255,255,255,0.06); }
62
72
  .ghost { color: rgba(232,228,240,0.4); }
73
+ .opt { display: inline-flex; align-items: center; gap: 6px; font: 600 13px/1 var(--rik-font-mono, monospace); color: rgba(232,228,240,0.7); cursor: pointer; user-select: none; }
74
+ .opt input { accent-color: var(--rik-accent, #8fd14f); cursor: pointer; }
63
75
  </style>
64
76
  </head>
65
77
  <body>
@@ -79,11 +91,14 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
79
91
  <div id="footer">
80
92
  <div><span id="timer">00:00</span> <button id="timer-toggle">Pause</button> <button id="timer-reset">Reset</button></div>
81
93
  <div id="counter">${e.current} / ${e.total}</div>
82
- <div><span class="ghost">P to close</span></div>
94
+ <div>
95
+ <label class="opt"><input type="checkbox" id="opt-advance" checked> Advance on click</label>
96
+ <span class="ghost">\xB7 P to close</span>
97
+ </div>
83
98
  </div>
84
99
  </div>
85
100
  <script>
86
- const channel = new BroadcastChannel('${c}');
101
+ const channel = new BroadcastChannel('${H}');
87
102
  const current = document.getElementById('current-frame');
88
103
  const next = document.getElementById('next-frame');
89
104
  const notes = document.getElementById('notes');
@@ -113,7 +128,12 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
113
128
  };
114
129
  resetBtn.onclick = () => { startedAt = Date.now(); elapsed = 0; running = true; toggleBtn.textContent = 'Pause'; };
115
130
 
116
- function wrapFrame(slideHtml) {
131
+ // Presentation options \xB7 push each change to the live deck over the channel.
132
+ document.getElementById('opt-advance').addEventListener('change', (e) => {
133
+ channel.postMessage({ type: 'config', advanceOnClick: e.target.checked });
134
+ });
135
+
136
+ function wrapFrame(slideHtml, forward) {
117
137
  const themeHref = ${JSON.stringify(e.themeHref)};
118
138
  const inlineStyles = ${JSON.stringify(e.inlineStyles)};
119
139
  const bundleHref = ${JSON.stringify(e.bundleHref)};
@@ -143,6 +163,17 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
143
163
  // Mark the cloned slide [active] so its real component CSS applies
144
164
  // (:host([active]){display:flex}) instead of forcing display via !important.
145
165
  const activeSlide = slideHtml.replace(/^(\\s*<deck-[a-z-]+)/i, '$1 active');
166
+ // Only the "Current" pane is a control surface \xB7 it captures key/click/wheel
167
+ // and posts them to this popup window, which relays them onto the channel so
168
+ // the live deck acts on them. Coords are normalised 0..1 over the iframe.
169
+ const forwarder = forward
170
+ ? '<scr' + 'ipt>(function(){' +
171
+ 'var post=function(o){o.source="rikiki-presenter-input";parent.postMessage(o,"*");};' +
172
+ 'addEventListener("keydown",function(e){var t=e.target;if(t&&t.matches&&t.matches("input,textarea,button"))return;post({type:"key",key:e.key,shift:!!e.shiftKey});});' +
173
+ 'addEventListener("click",function(e){post({type:"click",x:e.clientX/innerWidth,y:e.clientY/innerHeight,shift:!!e.shiftKey});});' +
174
+ 'addEventListener("wheel",function(e){if(e.ctrlKey||e.metaKey)return;post({type:"wheel",x:e.clientX/innerWidth,y:e.clientY/innerHeight,dx:e.deltaX,dy:e.deltaY});},{passive:true});' +
175
+ '})();<' + '/scr' + 'ipt>'
176
+ : '';
146
177
  // The deck always letterboxes into its logical canvas, so the slide keeps
147
178
  // its 16:9 proportions regardless of the pane's shape \xB7 just drop the
148
179
  // hint / nav-arrow chrome for a clean, correctly-shaped thumbnail.
@@ -150,7 +181,7 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
150
181
  bundleTag +
151
182
  '<style>html,body{margin:0;padding:0;height:100%;overflow:hidden;background:#0f1422}' +
152
183
  'deck-root{position:absolute;inset:0}</style>' +
153
- '</head><body><deck-root no-hint no-arrows>' + activeSlide + '</deck-root></body></html>';
184
+ '</head><body><deck-root no-hint no-arrows no-counter preview>' + activeSlide + '</deck-root>' + forwarder + '</body></html>';
154
185
  }
155
186
 
156
187
  channel.onmessage = (e) => {
@@ -158,8 +189,8 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
158
189
  const s = e.data.state;
159
190
  counter.textContent = s.current + ' / ' + s.total;
160
191
  notes.textContent = s.notes;
161
- if (s.slideHtml) current.srcdoc = wrapFrame(s.slideHtml);
162
- if (s.nextHtml) next.srcdoc = wrapFrame(s.nextHtml);
192
+ if (s.slideHtml) current.srcdoc = wrapFrame(s.slideHtml, true);
193
+ if (s.nextHtml) next.srcdoc = wrapFrame(s.nextHtml, false);
163
194
  else next.srcdoc = '<!doctype html><html><body style="background:#0f1422;color:rgba(232,228,240,0.4);display:flex;align-items:center;justify-content:center;font-family:system-ui">End of deck</body></html>';
164
195
  };
165
196
 
@@ -173,8 +204,17 @@ ${e.themeHref?`<link rel="stylesheet" href="${e.themeHref}">`:""}
173
204
  channel.postMessage({ type: 'key', key: e.key, shift: e.shiftKey });
174
205
  });
175
206
 
207
+ // Relay input captured inside the "Current" preview iframe (key/click/wheel)
208
+ // onto the channel \xB7 the iframe is a separate browsing context so its events
209
+ // never reach this window directly \xB7 it postMessages them here instead.
210
+ window.addEventListener('message', (e) => {
211
+ const d = e.data;
212
+ if (!d || d.source !== 'rikiki-presenter-input') return;
213
+ channel.postMessage({ type: d.type, key: d.key, shift: d.shift, x: d.x, y: d.y, dx: d.dx, dy: d.dy });
214
+ });
215
+
176
216
  // Tell main window we're alive
177
217
  channel.postMessage({ type: 'hello' });
178
218
  <\/script>
179
219
  </body>
180
- </html>`;function x(e){if(o=o??new WeakSet,o.has(e)){t?.close(),t=null;return}o.add(e),l=new BroadcastChannel(c),e.addEventListener("slide-change",()=>d(e)),l.addEventListener("message",a=>{let n=a.data;n?.type==="key"&&n.key&&window.dispatchEvent(new KeyboardEvent("keydown",{key:n.key,shiftKey:!!n.shift,bubbles:!0})),n?.type==="hello"&&d(e)});let r=p(e);if(t=window.open("","rikiki-presenter","width=1280,height=800,popup=yes"),!t){console.warn("[rikiki/presenter] popup was blocked \xB7 allow popups for this site"),o.delete(e);return}t.document.open(),t.document.write(y(r)),t.document.close();let s=setInterval(()=>{t?.closed&&(clearInterval(s),o?.delete(e),t=null)},1e3)}export{x as installPresenter};
220
+ </html>`;function F(e,n){let r=document.elementFromPoint(e,n);for(;r?.shadowRoot;){let t=r.shadowRoot.elementFromPoint(e,n);if(!t||t===r)break;r=t}return r}function $(e,n,r){for(let t=e;t;t=t.parentElement){let d=getComputedStyle(t);if(r!==0&&t.scrollHeight>t.clientHeight&&/auto|scroll/.test(d.overflowY)||n!==0&&t.scrollWidth>t.clientWidth&&/auto|scroll/.test(d.overflowX))return t}return null}function N(e,n,r){let t=e.getBoundingClientRect();return{x:t.left+n*t.width,y:t.top+r*t.height}}function L(e,n){let{x:r,y:t}=N(e,n.x??.5,n.y??.5);return{x:r,y:t,target:F(r,t)??e}}function A(e){let n=(e??"all").trim();return n==="none"?"none":n===""||n==="all"?"wheel arrows aux":n.split(/\s+/).filter(r=>r!=="click").join(" ")||"none"}function O(e){if(u=u??new WeakSet,u.has(e)){v(e);return}u.add(e);let n=e.mouseNav;p=new BroadcastChannel(H),e.addEventListener("slide-change",()=>E(e)),p.addEventListener("message",s=>{let o=s.data;if(o?.type==="key"&&o.key)window.dispatchEvent(new KeyboardEvent("keydown",{key:o.key,shiftKey:!!o.shift,bubbles:!0}));else if(o?.type==="click"){let{x:a,y:m,target:i}=L(e,o),c={bubbles:!0,composed:!0,cancelable:!0,clientX:a,clientY:m,view:window,shiftKey:!!o.shift};i.dispatchEvent(new PointerEvent("pointerdown",{...c,pointerId:1,isPrimary:!0})),i.dispatchEvent(new PointerEvent("pointerup",{...c,pointerId:1,isPrimary:!0})),i.dispatchEvent(new MouseEvent("click",c))}else if(o?.type==="wheel"){let{x:a,y:m,target:i}=L(e,o),c=o.dx??0,k=o.dy??0,x=$(i,c,k);x?x.scrollBy({left:c,top:k}):i.dispatchEvent(new WheelEvent("wheel",{bubbles:!0,composed:!0,cancelable:!0,clientX:a,clientY:m,deltaX:c,deltaY:k,view:window}))}else o?.type==="config"&&typeof o.advanceOnClick=="boolean"?e.mouseNav=o.advanceOnClick?n:A(n):o?.type==="hello"&&E(e)});let r=f,t=r?.screens.find(s=>s!==r.currentScreen)??null;t?S(e,t):I().then(s=>{if(!s)return;let o=s.screens.find(a=>a!==s.currentScreen);o&&S(e,o),l&&s.currentScreen&&T(l,s.currentScreen)});let d=D(e),w=r?.currentScreen?M(r.currentScreen):`popup=yes,width=${g},height=${y}`;if(l=window.open("","rikiki-presenter",w),!l){console.warn("[rikiki/presenter] popup was blocked \xB7 allow popups for this site"),v(e);return}l.document.open(),l.document.write(W(d)),l.document.close(),e.presenterActive=!0,document.addEventListener("fullscreenchange",P);let b=setInterval(()=>{l?.closed&&(clearInterval(b),v(e))},1e3)}export{O as installPresenter};