Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions editable-html-slides/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 rossyao2022

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
116 changes: 116 additions & 0 deletions editable-html-slides/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
name: editable-html-slides
description: Add a self-contained, dependency-free in-browser WYSIWYG editor to any HTML slide deck so the deck stays editable after it is generated — click text to rewrite it, drop in images, drag/resize/delete objects, undo/redo, reorder slides via a filmstrip, autosave to localStorage, and export a clean standalone HTML. Use this skill WHENEVER the user wants an editable HTML presentation, wants to tweak or adjust slides in the browser instead of editing source, says the generated HTML cannot be edited / 可编辑的 HTML / 随手改 / 加点图片, asks to add inline or visual or WYSIWYG editing to a deck, or wants drag-and-drop slide reordering — even if they don't name this skill. Pairs especially well with html-ppt / reveal-style decks (preserves presenter view and speaker notes) but works on any deck using the .slide / .is-active convention. Needs no Node, npm, build step, or network.
license: MIT
author: rossyao2022
version: "1.0"
tags:
- presentation
- slides
- html
- editor
- wysiwyg
- frontend
---

# editable-html-slides

Turn a *static* HTML deck into one a non-developer can edit in the browser —
without rebuilding it, without a framework, and without touching the original
design. The whole editor is two small files (`editor.js` + `editor.css`) that
layer on top of an existing deck.

## Why this exists

AI coding agents are great at *generating* gorgeous HTML decks, but the output
is "read-only" to a normal user — to change a title or swap an image they'd have
to open the source. This skill closes that gap: it bolts a visual editor onto
the deck so the user can keep editing after handoff, while the original theme,
animations, and (for presenter decks) the speaker-notes / presenter view stay
exactly as they were.

It is deliberately **zero-dependency**: no Node, no npm, no bundler, no CDN. The
editor is plain ES5-ish JavaScript and CSS that run from `file://`.

## What the editor gives the end user

- **Inline text editing** — click any heading / paragraph / list item and type.
- **Add image** — pick a local file (embedded as a data URL, so the deck stays a
single file) or paste with ⌘/Ctrl+V.
- **Add text box** — free-floating text, double-click to edit.
- **Drag / resize / delete** added objects (corner handle; Delete key).
- **Undo / redo** — ⌘/Ctrl+Z and ⌘/Ctrl+Shift+Z (snapshot history).
- **Reorder slides** — a draggable thumbnail filmstrip (▦ 页序 / "Pages").
- **Autosave** to `localStorage` (survives reload) and **export** a clean
standalone HTML with all edits baked in and the editor code stripped out.
- Toggle the whole thing with the **✎ button** or the **E** key; **Esc** / ✓ to exit.

Everything is audience-facing-safe: in normal (non-edit) mode the deck looks and
behaves identically to before, so you can still present it.

## How to apply it (the normal workflow)

You usually have a deck folder like `my-talk/index.html`. Run the injector:

```bash
python3 scripts/inject.py /abs/path/to/my-talk/index.html
```

That copies `editor.css` + `editor.js` next to the deck and wires two tags in
(idempotently — safe to run twice). Then open `index.html` and press **E**.

If you'd rather wire it by hand, add these two lines to the deck:

```html
<!-- in <head>, after the deck's own stylesheets -->
<link rel="stylesheet" href="editor.css">

<!-- just before </body>, AFTER the deck's runtime/nav script -->
<script src="editor.js"></script>
```

Loading order matters: `editor.js` must come **after** any deck runtime
(e.g. html-ppt's `runtime.js`) so it can see the rendered slides. Also copy
`assets/editor.css` and `assets/editor.js` into the deck folder.

## Requirements the deck must meet

The editor targets the common reveal/html-ppt slide convention:

- Each slide is an element with class **`.slide`**; the visible one has
**`.is-active`** (the editor edits whichever slide is active).
- Ideally slides live inside a **`.deck`** container. (The editor scopes to
`.deck .slide` and ignores any `.overview` clone grid — see the gotcha below.)
- Speaker notes, if any, are in **`<aside class="notes">`** — the editor never
turns these into editable on-slide text.

If a deck doesn't use these conventions, point the user to
`references/runtime-notes.md` for how to adapt the two selectors.

## When NOT to reach for this

- The user wants an editable **PowerPoint (.pptx)** for Office users → that's a
different tool (e.g. a pptx skill / Claude for PowerPoint), not this.
- The user wants to *generate* a brand-new deck from scratch → generate it first
(e.g. with an html-ppt / frontend-slides skill), then apply this skill to make
it editable.
- The deck has no `.slide` structure and you can't adapt the selectors.

## Files in this skill

- `assets/editor.js` — the editor (no deps). Safe to read; ~500 lines.
- `assets/editor.css` — editor chrome (toolbar, filmstrip, handles, toast).
- `scripts/inject.py` — idempotent injector + asset copier.
- `examples/demo.html` — a tiny self-contained deck (with a 30-line minimal
runtime) so you can see the editor work with no other dependencies. Open it and
press E.
- `references/runtime-notes.md` — internals & adaptation: the data model
(slot-fixed content-swap reorder), the `.overview` double-count gotcha, key
handling vs the deck runtime, and how to retarget the selectors.

## Verifying it works

Open the deck with `?edtest=1` in a headless browser; the editor runs a self-test
(add image + text box + move + undo/redo + reorder + export) and writes the
result to `document.body[data-edtest]` as `PASS …` or `FAIL …`. `examples/demo.html`
is the quickest thing to try.
110 changes: 110 additions & 0 deletions editable-html-slides/assets/editor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* editor.css — in-browser WYSIWYG layer for claude-edit-skill
* Adds: inline text editing, add image / text box, drag / resize / delete,
* localStorage persistence, export. Only visible when edit mode is on. */

/* ---- floating launcher (always visible) ---- */
#ed-launch{
position:fixed; right:18px; bottom:16px; z-index:9000;
font-family:var(--font-sans,sans-serif); font-size:13px; font-weight:600;
color:#fff; background:var(--accent,#d97757);
border:none; border-radius:999px; padding:10px 16px;
box-shadow:0 8px 24px rgba(0,0,0,.4); cursor:pointer;
display:flex; align-items:center; gap:7px;
transition:transform .15s ease, filter .15s ease;
}
#ed-launch:hover{ transform:translateY(-1px); filter:brightness(1.06); }

/* ---- toolbar (only in edit mode) ---- */
#ed-bar{
position:fixed; top:14px; left:50%; transform:translateX(-50%);
z-index:9000; display:none; gap:8px; align-items:center;
padding:8px 10px; border-radius:12px;
background:rgba(28,25,23,.92); backdrop-filter:blur(10px);
border:1px solid var(--border-strong,rgba(217,119,87,.32));
box-shadow:0 14px 40px rgba(0,0,0,.5);
font-family:var(--font-sans,sans-serif);
}
body.ed-on #ed-bar{ display:flex; }
#ed-bar button{
font-size:13px; font-weight:600; color:var(--text-1,#f5efe9);
background:var(--surface-2,#332d29); border:1px solid var(--border,rgba(217,119,87,.16));
border-radius:8px; padding:7px 11px; cursor:pointer; white-space:nowrap;
transition:background .12s ease;
}
#ed-bar button:hover{ background:var(--accent,#d97757); color:#fff; }
#ed-bar button.primary{ background:var(--accent,#d97757); color:#fff; border-color:transparent; }
#ed-bar button:disabled{ opacity:.38; cursor:default; }
#ed-bar button:disabled:hover{ background:var(--surface-2,#332d29); color:var(--text-1,#f5efe9); }
#ed-bar .ed-sep{ width:1px; height:22px; background:var(--border,rgba(217,119,87,.22)); margin:0 2px; }
#ed-bar .ed-page{ font-family:var(--font-mono,monospace); font-size:12px; color:var(--text-3,#9a8b7d); padding:0 4px; }

/* ---- filmstrip (reorder by drag) ---- */
#ed-film{
position:fixed; left:0; right:0; bottom:0; z-index:8999; display:none;
gap:10px; align-items:stretch; padding:12px 14px; overflow-x:auto;
background:rgba(22,19,16,.96); backdrop-filter:blur(10px);
border-top:1px solid var(--border-strong,rgba(217,119,87,.32));
}
#ed-film .ed-chip{
flex:0 0 auto; width:150px; min-height:62px; cursor:grab;
display:flex; flex-direction:column; gap:6px; padding:10px 12px;
background:var(--surface,#262220); border:1px solid var(--border,rgba(217,119,87,.18));
border-radius:10px; font-family:var(--font-sans,sans-serif); user-select:none;
transition:border-color .12s, transform .12s;
}
#ed-film .ed-chip:hover{ border-color:var(--accent,#d97757); }
#ed-film .ed-chip.cur{ border-color:var(--accent,#d97757); box-shadow:0 0 0 1px var(--accent,#d97757) inset; }
#ed-film .ed-chip.dragging{ opacity:.45; }
#ed-film .ed-chip.over{ transform:translateY(-3px); border-color:var(--accent-2,#e8a87c); border-style:dashed; }
#ed-film .ed-chip .ci{ font-family:var(--font-mono,monospace); font-size:11px; color:var(--accent,#d97757); font-weight:700; }
#ed-film .ed-chip .ct{ font-size:13px; color:var(--text-1,#f5efe9); line-height:1.35; }
body.ed-on #ed-launch{ bottom:74px; } /* lift launcher above an open filmstrip */

/* ---- editable text affordance ---- */
body.ed-on [data-ed-text]{
outline:1px dashed rgba(217,119,87,.45); outline-offset:3px;
border-radius:3px; cursor:text;
}
body.ed-on [data-ed-text]:hover{ outline-color:var(--accent,#d97757); }
body.ed-on [data-ed-text]:focus{
outline:2px solid var(--accent,#d97757); background:rgba(217,119,87,.07);
}

/* ---- added objects (images / text boxes) ---- */
.ed-obj{ position:absolute; z-index:5; cursor:grab; }
.ed-obj img{ display:block; width:100%; height:auto; border-radius:8px; pointer-events:none; }
.ed-obj.ed-textbox{
min-width:80px; padding:8px 12px; color:var(--text-1,#f5efe9);
font-family:var(--font-sans,sans-serif); font-size:22px; line-height:1.4;
}
body.ed-on .ed-obj{ outline:1px solid rgba(217,119,87,.4); }
body.ed-on .ed-obj.sel{ outline:2px solid var(--accent,#d97757); cursor:grab; }
body:not(.ed-on) .ed-obj{ cursor:default; }

/* resize handle + delete button on selected obj (edit mode only) */
.ed-handle{
position:absolute; right:-7px; bottom:-7px; width:14px; height:14px;
background:var(--accent,#d97757); border:2px solid #fff; border-radius:50%;
cursor:nwse-resize; display:none; z-index:6;
}
.ed-del{
position:absolute; right:-10px; top:-12px; width:22px; height:22px;
background:#e06c5e; color:#fff; border:none; border-radius:50%;
font-size:13px; line-height:1; cursor:pointer; display:none; z-index:6;
}
body.ed-on .ed-obj.sel .ed-handle,
body.ed-on .ed-obj.sel .ed-del{ display:block; }

/* toast */
#ed-toast{
position:fixed; bottom:70px; right:18px; z-index:9001;
background:rgba(28,25,23,.95); color:var(--text-1,#f5efe9);
border:1px solid var(--accent,#d97757); border-radius:10px;
padding:10px 16px; font-family:var(--font-sans,sans-serif); font-size:13px;
opacity:0; transform:translateY(8px); transition:opacity .2s, transform .2s;
pointer-events:none;
}
#ed-toast.show{ opacity:1; transform:translateY(0); }

/* hide all editor chrome in presenter-preview iframes */
body.is-preview #ed-launch, body.is-preview #ed-bar{ display:none !important; }
Loading
Loading