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.
- package/.claude/skills/rikiki-debug/SKILL.md +55 -0
- package/.claude/skills/rikiki-deck/SKILL.md +81 -0
- package/.claude/skills/rikiki-theme/SKILL.md +70 -0
- package/README.md +45 -1
- package/bin/rikiki.mjs +35 -2
- package/dist/atoms/deck-code-highlighter.d.ts +6 -0
- package/dist/atoms/deck-code.d.ts +14 -0
- package/dist/click-stages.js +1 -1
- package/dist/deck-code-highlighter.js +1 -0
- package/dist/deck-code.js +2 -2
- package/dist/deck-presenter.js +50 -10
- package/dist/deck-root.js +5 -5
- package/dist/index.d.ts +3 -0
- package/dist/index.js +27 -27
- package/dist/plugins/click-stages.d.ts +5 -0
- package/dist/runtime/deck-root.d.ts +109 -1
- package/dist/shiki.js +1 -1
- package/dist/standalone.js +112 -72
- package/docs/llms/rikiki-reference.md +130 -18
- package/llms.txt +5 -3
- package/package.json +5 -2
- package/themes/rikiki.css +4 -4
- package/themes/siliceum.css +6 -4
|
@@ -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.
|
|
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;
|
package/dist/click-stages.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
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
|
|
2
|
-
`);for(;
|
|
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,"&").replace(/</g,"<").replace(/>/g,">"),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(/(<!--[\s\S]*?-->)/g,r=>e("cmt",r)),t=t.replace(/(<!doctype[^&]*>)/gi,r=>e("cmt",r)),t=t.replace(/("[^"]*"|'[^']*')/g,r=>e("str",r)),t=t.replace(/(<\/?)([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};
|
package/dist/deck-presenter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var
|
|
2
|
-
`),
|
|
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
|
-
|
|
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
|
|
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('${
|
|
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
|
-
|
|
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
|
|
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(
|
|
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};
|