chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:48 +08:00
commit d2d8db40d8
223 changed files with 74420 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
.DS_Store
TEMPLATE_SPEC.md
AUTHORING_PROMPT.md
previews/
Demo recordings/
# Local reference / generation artifacts
chinese-font-guide.html
template-to-design-md.prompt.md
AUTHORING_PROMPT copy.md
.tools/
decks/
docs/
gallery-showcase*.mp4
mockups/
quiver-output/
scripts/shot-slides.mjs
showcase.mp4
+210
View File
@@ -0,0 +1,210 @@
# Agent Instructions
You are an agent working with the **beautiful-html-templates** library. Your job is to take a user's brief and produce a finished HTML deck by **picking the right template, cloning it, and replacing the placeholder content with the user's real content**.
This document is your operating manual. Read it once at the start of any deck-building task.
---
## 1. The full workflow
For every deck-building request, follow this exact sequence. Do **not** skip the clarifying step or the preview step.
### Step 1 — Ask the user about occasion and mood
Before reading any files, ask the user:
> "Two quick questions before I pick a template:
> 1. **What's the occasion?** (e.g. founder pitch, research synthesis, brand manifesto, classroom kickoff, etc.)
> 2. **What mood / vibe do you want?** (e.g. confident & punchy, quiet & literary, warm & playful, dark & moody, etc.)"
Wait for the user's answer. Do not pick yet. Even if the brief seems obvious, ask — the user's *taste* often surprises in ways no inferred brief can capture.
### Step 2 — Read `index.json` and pick 3 candidates
Read `index.json` at the repo root. Match the user's stated occasion + mood against each template's `mood`, `tone`, `best_for`, and `formality`. **Pick three templates** whose tones genuinely fit. The three should be *different enough from each other* that the user has a real choice — e.g. don't pick three editorial templates if the brief is editorial; pick one editorial, one warmer alternative, and one wildcard that re-interprets the brief.
### Step 3 — Build a title-slide preview of each candidate
For each of the 3 candidates:
1. Read the template's `template.html` to learn its visual system.
2. Take the **first slide only** (the cover / title slide of that template).
3. Replace the placeholder content with **the user's actual deck topic / title / subtitle / author / date** — i.e., make this preview real, not generic.
4. Save the preview as a standalone HTML file in a temp folder, e.g. `previews/01-<slug>.html`. Keep all sibling assets (`styles.css`, `deck-stage.js`, etc.) the template needs so the preview opens correctly.
These three preview files should be self-contained — opening any of them shows that template's title slide, populated with the user's real content.
### Step 4 — Open all 3 previews in the browser, send paths to user
Open each of the 3 preview files in the browser using `open <path>` (macOS). Then send the user a message like:
> "Three options to compare:
>
> 1. **<Template A>** — <one-line tone description>
> `/path/to/previews/01-template-a.html`
> 2. **<Template B>** — <one-line tone description>
> `/path/to/previews/02-template-b.html`
> 3. **<Template C>** — <one-line tone description>
> `/path/to/previews/03-template-c.html`
>
> Which one feels right?"
Wait for the user to pick.
### Step 5 — Build the full deck in the chosen template
Once the user picks:
1. Clone the chosen template's full folder into the user's project workspace.
2. Adapt every slide per the rules in §3 (preserve / replace / extend).
3. If the user's deck needs more slides than the template's demo holds, duplicate existing layouts to fit; if it needs fewer, drop slides from the bottom. Update page-number labels.
4. **If a slide needs a layout the template doesn't have, design it from scratch using the template's design system** — same fonts, same color palette, same decorative vocabulary, same spacing rhythm, same component grammar. Do not bail back to the user; do not pick a different template; do not import a new visual language. The new slide should look like a natural extension of the template, not a graft. (See §5 — designing missing layouts.)
### Step 6 — Open the final deck in the browser, send the file path
Open the finished deck with `open <path>`. Send the user a message like:
> "Done. Your deck is at `/path/to/deck/template.html` — opened it in your browser.
>
> [One line about what you did and any caveats.]"
This applies to **every artifact you produce** — preview files, intermediate iterations, final deck. Always open it, always send the path.
---
## 2. What's in `index.json`
```jsonc
{
"schema_version": 1,
"template_count": 28,
"templates": [
{
"slug": "neo-grid-bold",
"name": "Neo-Grid Bold",
"tagline": "Editorial neo-brutalism with a single neon yellow accent on off-white paper.",
"mood": ["confident", "punchy", "editorial", "modern"],
"occasion": ["product launch", "design review", "founder pitch", ...],
"tone": ["bold", "minimal", "design-led", "graphic"],
"formality": "medium",
"density": "high",
"scheme": "light",
"best_for": "Anything that should feel confident and editorial-graphic ...",
"avoid_for": "Contexts that need to feel quiet, traditional, or warm ...",
"slide_count": 12
},
...
]
}
```
Field definitions:
| field | how to use it |
|---|---|
| `mood` | emotional adjectives. Match against the user's *feeling* keywords. |
| `occasion` | example use cases. Useful as soft signal, not a hard filter. |
| `tone` | voice / personality. Match against descriptors like "playful", "serious", "literary". |
| `formality` | `low` / `medium-low` / `medium` / `medium-high` / `high`. Sanity-check against the user's audience. |
| `density` | how much content per slide the template can hold. Match against the user's content volume. |
| `scheme` | `light` / `dark` / `mixed`. Hard signal if the user explicitly wants light or dark. |
| `best_for` | the **feeling** + example contexts. Lead with this when narrating your pick. |
| `avoid_for` | tone *clashes* — soft warning, not a veto. |
| `slide_count` | size of the demo deck. Hint for how many layouts the template demonstrates. |
You only need a deeper read of a template's actual HTML/CSS once you've shortlisted it.
---
## 3. How to adapt a chosen template
Once you've cloned the template's folder into the user's workspace, follow these rules.
### Always preserve (these ARE the design system)
- **Fonts.** Whatever the template imports from Google Fonts or declares in `font-family` — keep it. Never substitute.
- **Color palette.** All `:root` CSS variables and color values. Never recolor.
- **Layout grid.** The grid columns, the absolute positioning, the flex hierarchies. Don't restructure.
- **Slide-level CSS classes** (e.g. `.s-toc`, `.slide--quote`, `.layout-cover`). These carry the visual identity.
- **Decorative elements** — corner brackets, paper grain, geometric shapes, illustrated SVGs, hand-drawn doodles. They are part of the system, not optional ornament.
- **The navigation runtime** — whether it's `deck-stage.js`, an inline keyboard handler, scroll-snap, or nothing. Whatever the template uses, keep using.
### Always replace (this is the user's content)
- **Headlines** — `<h1>`, `<h2>`, `<h3>`, etc.
- **Body copy** — `<p>`, list items, captions.
- **Numbers and stats** — placeholder values like `47%`, `2.4M`, `+142%`.
- **Names, dates, attributions** — author names, citation lines, "May 2026" placeholders.
- **Section labels and chrome text** — the `[Topic]`, `[Year]`, `[Studio]`, etc. tokens that templates use as authored placeholders.
- **Image placeholders** — replace `<div class="img-placeholder">Image Placeholder</div>` or `<img src="placeholder.jpg">` with the user's real image, **at the same dimensions**.
### Adding more slides (existing layout)
If the user has more content than the template's demo deck holds, duplicate an existing slide of the most appropriate layout and replace its content. Update page-number labels (`NN / TT`).
### Removing slides
Drop slides from the bottom if the user doesn't need them. Update page-number labels.
---
## 4. Tone-first matching
Templates have **tones**, not industries. The `best_for` and `avoid_for` fields describe how a template *feels*, not what industry it belongs to.
This means: **a confident editorial deck can carry a tech talk** if the user wants to feel design-led. **A playful pastel deck can carry a finance review** if the user is intentionally rejecting the formal-finance look. The user's taste wins.
When matching:
- **Lead with `mood` + `tone` + `best_for`.** Match the *feeling* the user wants.
- **Treat `avoid_for` as a soft warning, not a hard rule.** If the user has explicitly asked for the look that a template's `avoid_for` flags against, the user wins.
- **Use `formality` and `density` to sanity-check** — a low-formality template for a senior board presentation is probably the wrong call regardless of tone overlap, and you should flag that to the user.
- **Don't over-fit on `occasion`.** That field is example contexts, not the canon list.
- **In Step 1, ask about *tone*, not *industry*.** Good question: "Should this feel polished and authoritative, or warm and design-led?" Bad question: "Is this for finance or tech?"
---
## 5. Designing missing layouts (extending a template)
Some user briefs require a slide layout the chosen template doesn't include — e.g., a comparison table when the template only has process-flow and stat-grid layouts. In that case **design the missing slide using the template's existing design system**. Do not bail. Do not pick a different template. Do not import a new visual language.
The rules:
- **Same fonts.** Use the same `font-family` declarations the template uses for h1 / h2 / body / mono. Same weights, same letter-spacing, same line-heights.
- **Same color palette.** Use the existing `:root` CSS variables. If the new layout needs a "warning" or "highlight" color and one isn't in the palette, pick the closest existing accent rather than introducing a new hex.
- **Same decorative vocabulary.** If the template uses corner brackets, paper grain overlays, hand-drawn doodles, geometric shapes, etc. — your new slide should use the same vocabulary. A bare slide with no decoration in a template full of ornament will look broken.
- **Same spacing rhythm.** If the template uses `padding: 64px` on its slides, your new slide does too. If it uses an 8-column grid with 24px gutter, so do you.
- **Same component grammar.** If the template's stat cards use a specific structure (large number → label → description → mono caption), reuse that structure for new stat-like elements rather than inventing a different one.
- **Same chrome.** Top label / bottom page-number / corner mark — match whatever the rest of the deck shows.
- **Same navigation behavior.** A new slide must integrate with the template's existing nav (deck-stage / inline keyboard / scroll-snap). Add it as a sibling section / div in the same way existing slides are structured.
A good test: open your new slide between two existing slides in the deck. If it visibly *belongs* — same fonts, same colors, same decorations, same rhythm — you've succeeded. If it looks like a different template grafted on, you've failed; redo it.
---
## 6. Common pitfalls
- **Don't skip Step 1.** Even if the brief is detailed, ask about occasion and mood explicitly. The user's stated *taste* almost always reveals something the brief alone didn't.
- **Don't skip the previews in Step 4.** A title slide is the fastest, cheapest way for the user to feel which template is right. Don't talk them through tradeoffs in prose; show them.
- **Don't substitute fonts.** "Inter is similar enough" — no, the typography is the design system. If the original Cormorant Garamond doesn't load, fix the import, don't replace the family.
- **Don't recolor.** Even small accent shifts break the palette's harmony.
- **Don't combine layouts from different templates.** Each template is a closed visual system. Mixing slide A from raw-grid with slide B from neo-grid-bold will look amateur. (This is *different* from §5: extending one template is fine; mashing two templates is not.)
- **Don't strip "extra" decoration thinking it's noise.** Corner brackets, paper grain, SVG ornaments — they are part of the identity.
- **Don't try to "modernize" old templates** — they're working as designed. If a template feels dated, pick a different template, don't edit the existing one.
- **Don't forget to open the file in the browser.** Every preview, every iteration, every final deck. Use `open <path>`.
---
## 7. Output contract
After **every artifact** you produce — title-slide previews, intermediate iterations, the final deck — do both of these:
1. **Open the file in the browser.** Use `open /absolute/path/to/file.html`. Don't just announce it; actually open it.
2. **Send the user the absolute file path** in your message, on its own line, formatted as a path so it's clickable.
For the final deck, also include:
- A one-line note about which template you picked and why (the *tone match*).
- Any caveats (e.g., "I designed slides 4 and 7 from scratch using the template's design system since you needed a comparison table and a 4-column timeline that the demo deck didn't include").
Do not narrate every step you took. The user wants the artifact + the path + a one-line rationale, not a transcript.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Zara Zhang
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.
+361
View File
@@ -0,0 +1,361 @@
# beautiful-html-templates
A library of reusable HTML slide templates designed so that any coding agent can pick the right one and produce a beautiful deck on the user's behalf, automatically.
Agents using the library should read [`AGENTS.md`](./AGENTS.md). It's the operating manual: how to read `index.json`, match the user's brief to a template, clone it, and adapt the content.
## Get started
Copy this to your coding agent:
```
Clone https://github.com/zarazhangrui/beautiful-html-templates and follow the instructions in AGENTS.md to build me a beautiful HTML slide deck.
```
## Gallery
All 34 templates. Three slides per template (cover · mid-deck · later) to give a sense of how each visual system handles different layouts. Click any template name to open its folder on GitHub — the HTML, metadata, and any sibling assets are all there.
### [Soft Editorial](./templates/soft-editorial/)
<p>
<img src="./screenshots/soft-editorial-4.png" width="32.5%" alt="Soft Editorial — slide 4" />
<img src="./screenshots/soft-editorial-6.png" width="32.5%" alt="Soft Editorial — slide 6" />
<img src="./screenshots/soft-editorial-10.png" width="32.5%" alt="Soft Editorial — slide 10" />
</p>
> Cormorant Garamond serif on warm paper with sage, blush, and lemon accents.
### [Editorial Forest](./templates/editorial-forest/)
<p>
<img src="./screenshots/editorial-forest-1.png" width="32.5%" alt="Editorial Forest — slide 1" />
<img src="./screenshots/editorial-forest-2.png" width="32.5%" alt="Editorial Forest — slide 2" />
<img src="./screenshots/editorial-forest-5.png" width="32.5%" alt="Editorial Forest — slide 5" />
</p>
> Forest green, dusty pink, and warm cream in Source Serif 4 — quiet, intentional quarterly-review aesthetic.
### [Pin & Paper](./templates/pin-and-paper/)
<p>
<img src="./screenshots/pin-and-paper-1.png" width="32.5%" alt="Pin & Paper — slide 1" />
<img src="./screenshots/pin-and-paper-11.png" width="32.5%" alt="Pin & Paper — slide 11" />
<img src="./screenshots/pin-and-paper-3.png" width="32.5%" alt="Pin & Paper — slide 3" />
</p>
> Yellow paper with safety-pin illustrations, ink-blue handwritten Caveat, paper-grain texture.
### [Sakura Chroma](./templates/sakura-chroma/)
<p>
<img src="./screenshots/sakura-chroma-1.png" width="32.5%" alt="Sakura Chroma — slide 1" />
<img src="./screenshots/sakura-chroma-3.png" width="32.5%" alt="Sakura Chroma — slide 3" />
<img src="./screenshots/sakura-chroma-4.png" width="32.5%" alt="Sakura Chroma — slide 4" />
</p>
> Vintage Japanese cassette-package aesthetic: cream paper, diagonal rainbow ribbons, condensed bold type, JIS-style spec checkboxes.
### [Stencil & Tablet](./templates/stencil-tablet/)
<p>
<img src="./screenshots/stencil-tablet-1.png" width="32.5%" alt="Stencil & Tablet — slide 1" />
<img src="./screenshots/stencil-tablet-3.png" width="32.5%" alt="Stencil & Tablet — slide 3" />
<img src="./screenshots/stencil-tablet-8.png" width="32.5%" alt="Stencil & Tablet — slide 8" />
</p>
> Bone paper with stencil-cut headlines and a six-color earth palette: archaeology meets brand.
### [Cobalt Grid](./templates/cobalt-grid/)
<p>
<img src="./screenshots/cobalt-grid-1.png" width="32.5%" alt="Cobalt Grid — slide 1" />
<img src="./screenshots/cobalt-grid-3.png" width="32.5%" alt="Cobalt Grid — slide 3" />
<img src="./screenshots/cobalt-grid-5.png" width="32.5%" alt="Cobalt Grid — slide 5" />
</p>
> Electric cobalt italic serifs on a graph-paper canvas, anchored by stair-stepped pixel-glitch decorations and slim hairline rules.
### [Vellum](./templates/vellum/)
<p>
<img src="./screenshots/vellum-1.png" width="32.5%" alt="Vellum — slide 1" />
<img src="./screenshots/vellum-4.png" width="32.5%" alt="Vellum — slide 4" />
<img src="./screenshots/vellum-8.png" width="32.5%" alt="Vellum — slide 8" />
</p>
> Deep navy canvas with warm-yellow italic Cormorant serifs and a single dusty teal accent. A quiet, scholarly aesthetic.
### [Emerald Editorial](./templates/emerald-editorial/)
<p>
<img src="./screenshots/emerald-editorial-1.png" width="32.5%" alt="Emerald Editorial — slide 1" />
<img src="./screenshots/emerald-editorial-3.png" width="32.5%" alt="Emerald Editorial — slide 3" />
<img src="./screenshots/emerald-editorial-6.png" width="32.5%" alt="Emerald Editorial — slide 6" />
</p>
> Magazine-cover business deck: emerald + navy + paper with double-rule masthead ornaments and a heavy Bodoni-style display serif.
### [Neo-Grid Bold](./templates/neo-grid-bold/)
<p>
<img src="./screenshots/neo-grid-bold-1.png" width="32.5%" alt="Neo-Grid Bold — slide 1" />
<img src="./screenshots/neo-grid-bold-3.png" width="32.5%" alt="Neo-Grid Bold — slide 3" />
<img src="./screenshots/neo-grid-bold-8.png" width="32.5%" alt="Neo-Grid Bold — slide 8" />
</p>
> Editorial neo-brutalism with a single neon yellow accent on off-white paper.
### [Editorial Tri-Tone](./templates/editorial-tri-tone/)
<p>
<img src="./screenshots/editorial-tri-tone-1.png" width="32.5%" alt="Editorial Tri-Tone — slide 1" />
<img src="./screenshots/editorial-tri-tone-4.png" width="32.5%" alt="Editorial Tri-Tone — slide 4" />
<img src="./screenshots/editorial-tri-tone-3.png" width="32.5%" alt="Editorial Tri-Tone — slide 3" />
</p>
> Three-color editorial system: dusty pink, mustard cream, and deep burgundy, set in Bricolage + Instrument Serif.
### [Creative Mode](./templates/creative-mode/)
<p>
<img src="./screenshots/creative-mode-1.png" width="32.5%" alt="Creative Mode — slide 1" />
<img src="./screenshots/creative-mode-4.png" width="32.5%" alt="Creative Mode — slide 4" />
<img src="./screenshots/creative-mode-6.png" width="32.5%" alt="Creative Mode — slide 6" />
</p>
> Cream paper canvas with confident multi-color (green, pink, orange, yellow) accents and Archivo Black display.
### [Monochrome](./templates/monochrome/)
<p>
<img src="./screenshots/monochrome-1.png" width="32.5%" alt="Monochrome — slide 1" />
<img src="./screenshots/monochrome-4.png" width="32.5%" alt="Monochrome — slide 4" />
<img src="./screenshots/monochrome-12.png" width="32.5%" alt="Monochrome — slide 12" />
</p>
> Ivory ledger paper with all-black type; Lora serif headlines, Jost body, no color at all.
### [People's Platform (Block & Bold)](./templates/peoples-platform/)
<p>
<img src="./screenshots/peoples-platform-1.png" width="32.5%" alt="People's Platform (Block & Bold) — slide 1" />
<img src="./screenshots/peoples-platform-4.png" width="32.5%" alt="People's Platform (Block & Bold) — slide 4" />
<img src="./screenshots/peoples-platform-8.png" width="32.5%" alt="People's Platform (Block & Bold) — slide 8" />
</p>
> Activist poster energy: blue, orange, red on cream, with Alfa Slab + Caveat Brush.
### [Pink Script — After Hours](./templates/pink-script/)
<p>
<img src="./screenshots/pink-script-1.png" width="32.5%" alt="Pink Script — After Hours — slide 1" />
<img src="./screenshots/pink-script-4.png" width="32.5%" alt="Pink Script — After Hours — slide 4" />
<img src="./screenshots/pink-script-8.png" width="32.5%" alt="Pink Script — After Hours — slide 8" />
</p>
> Black canvas, hot pink accent, pearl-cream paper, Instrument Serif headlines: late-night editorial luxury.
### [8-Bit Orbit](./templates/8-bit-orbit/)
<p>
<img src="./screenshots/8-bit-orbit-1.png" width="32.5%" alt="8-Bit Orbit — slide 1" />
<img src="./screenshots/8-bit-orbit-6.png" width="32.5%" alt="8-Bit Orbit — slide 6" />
<img src="./screenshots/8-bit-orbit-5.png" width="32.5%" alt="8-Bit Orbit — slide 5" />
</p>
> Pixel-art neon arcade aesthetic on a deep navy void.
### [BlockFrame](./templates/block-frame/)
<p>
<img src="./screenshots/block-frame-1.png" width="32.5%" alt="BlockFrame — slide 1" />
<img src="./screenshots/block-frame-4.png" width="32.5%" alt="BlockFrame — slide 4" />
<img src="./screenshots/block-frame-8.png" width="32.5%" alt="BlockFrame — slide 8" />
</p>
> Neobrutalist deck with pastel-neon color blocks and chunky black borders.
### [Blue Professional](./templates/blue-professional/)
<p>
<img src="./screenshots/blue-professional-1.png" width="32.5%" alt="Blue Professional — slide 1" />
<img src="./screenshots/blue-professional-6.png" width="32.5%" alt="Blue Professional — slide 6" />
<img src="./screenshots/blue-professional-8.png" width="32.5%" alt="Blue Professional — slide 8" />
</p>
> Cream paper background with electric cobalt blue accents; clean modern professional.
### [Bold Poster](./templates/bold-poster/)
<p>
<img src="./screenshots/bold-poster-1.png" width="32.5%" alt="Bold Poster — slide 1" />
<img src="./screenshots/bold-poster-4.png" width="32.5%" alt="Bold Poster — slide 4" />
<img src="./screenshots/bold-poster-8.png" width="32.5%" alt="Bold Poster — slide 8" />
</p>
> Editorial poster aesthetic with massive Shrikhand display and a single fire-engine red accent.
### [Broadside](./templates/broadside/)
<p>
<img src="./screenshots/broadside-1.png" width="32.5%" alt="Broadside — slide 1" />
<img src="./screenshots/broadside-4.png" width="32.5%" alt="Broadside — slide 4" />
<img src="./screenshots/broadside-13.png" width="32.5%" alt="Broadside — slide 13" />
</p>
> Dark editorial canvas with a single fire orange accent and bilingual Latin/Chinese type stack.
### [Capsule](./templates/capsule/)
<p>
<img src="./screenshots/capsule-1.png" width="32.5%" alt="Capsule — slide 1" />
<img src="./screenshots/capsule-4.png" width="32.5%" alt="Capsule — slide 4" />
<img src="./screenshots/capsule-8.png" width="32.5%" alt="Capsule — slide 8" />
</p>
> Modular pill-shaped cards on warm bone with a full pastel-pop palette.
### [Cartesian](./templates/cartesian/)
<p>
<img src="./screenshots/cartesian-1.png" width="32.5%" alt="Cartesian — slide 1" />
<img src="./screenshots/cartesian-4.png" width="32.5%" alt="Cartesian — slide 4" />
<img src="./screenshots/cartesian-8.png" width="32.5%" alt="Cartesian — slide 8" />
</p>
> Quiet warm-neutral palette with classical Playfair serifs; tasteful and unhurried.
### [Coral](./templates/coral/)
<p>
<img src="./screenshots/coral-1.png" width="32.5%" alt="Coral — slide 1" />
<img src="./screenshots/coral-4.png" width="32.5%" alt="Coral — slide 4" />
<img src="./screenshots/coral-8.png" width="32.5%" alt="Coral — slide 8" />
</p>
> Cream and coral on near-black, set in oversized Bebas Neue.
### [Daisy Days](./templates/daisy-days/)
<p>
<img src="./screenshots/daisy-days-1.png" width="32.5%" alt="Daisy Days — slide 1" />
<img src="./screenshots/daisy-days-4.png" width="32.5%" alt="Daisy Days — slide 4" />
<img src="./screenshots/daisy-days-8.png" width="32.5%" alt="Daisy Days — slide 8" />
</p>
> Cheerful pastel deck with hand-drawn daisies, stars, and rainbows. Friendly, soft, and warm.
### [Grove](./templates/grove/)
<p>
<img src="./screenshots/grove-1.png" width="32.5%" alt="Grove — slide 1" />
<img src="./screenshots/grove-4.png" width="32.5%" alt="Grove — slide 4" />
<img src="./screenshots/grove-8.png" width="32.5%" alt="Grove — slide 8" />
</p>
> Forest-green canvas with cream type, classical Playfair serifs, and a single rust accent.
### [Mat](./templates/mat/)
<p>
<img src="./screenshots/mat-1.png" width="32.5%" alt="Mat — slide 1" />
<img src="./screenshots/mat-4.png" width="32.5%" alt="Mat — slide 4" />
<img src="./screenshots/mat-8.png" width="32.5%" alt="Mat — slide 8" />
</p>
> Dark sage canvas with bone paper and burnt-orange accent; mid-century modern with wood undertones.
### [Playful](./templates/playful/)
<p>
<img src="./screenshots/playful-1.png" width="32.5%" alt="Playful — slide 1" />
<img src="./screenshots/playful-6.png" width="32.5%" alt="Playful — slide 6" />
<img src="./screenshots/playful-8.png" width="32.5%" alt="Playful — slide 8" />
</p>
> Sun-warm peach background with Syne display: a friendly indie launch deck.
### [Raw Grid](./templates/raw-grid/)
<p>
<img src="./screenshots/raw-grid-1.png" width="32.5%" alt="Raw Grid — slide 1" />
<img src="./screenshots/raw-grid-4.png" width="32.5%" alt="Raw Grid — slide 4" />
<img src="./screenshots/raw-grid-8.png" width="32.5%" alt="Raw Grid — slide 8" />
</p>
> Neo-brutalist deck with thick borders, offset shadows, and a pink/sage/ink palette.
### [Retro Windows](./templates/retro-windows/)
<p>
<img src="./screenshots/retro-windows-1.png" width="32.5%" alt="Retro Windows — slide 1" />
<img src="./screenshots/retro-windows-4.png" width="32.5%" alt="Retro Windows — slide 4" />
<img src="./screenshots/retro-windows-8.png" width="32.5%" alt="Retro Windows — slide 8" />
</p>
> Windows 95 chrome: gray title bars, MS Sans Serif, pixel typography, full nostalgia.
### [Retro Zine](./templates/retro-zine/)
<p>
<img src="./screenshots/retro-zine-1.png" width="32.5%" alt="Retro Zine — slide 1" />
<img src="./screenshots/retro-zine-4.png" width="32.5%" alt="Retro Zine — slide 4" />
<img src="./screenshots/retro-zine-8.png" width="32.5%" alt="Retro Zine — slide 8" />
</p>
> Beige paper with green accent and Bebas Neue + Caveat: a riso-printed zine in HTML form.
### [Scatterbrain](./templates/scatterbrain/)
<p>
<img src="./screenshots/scatterbrain-1.png" width="32.5%" alt="Scatterbrain — slide 1" />
<img src="./screenshots/scatterbrain-4.png" width="32.5%" alt="Scatterbrain — slide 4" />
<img src="./screenshots/scatterbrain-8.png" width="32.5%" alt="Scatterbrain — slide 8" />
</p>
> Post-it inspired: pastel sticky notes, Caveat handwriting, Shrikhand and Zilla Slab type stack.
### [Signal](./templates/signal/)
<p>
<img src="./screenshots/signal-1.png" width="32.5%" alt="Signal — slide 1" />
<img src="./screenshots/signal-18.png" width="32.5%" alt="Signal — slide 18" />
<img src="./screenshots/signal-8.png" width="32.5%" alt="Signal — slide 8" />
</p>
> Deep navy canvas with bone paper and a single muted-gold accent; institutional with quiet weight.
### [Studio](./templates/studio/)
<p>
<img src="./screenshots/studio-1.png" width="32.5%" alt="Studio — slide 1" />
<img src="./screenshots/studio-4.png" width="32.5%" alt="Studio — slide 4" />
<img src="./screenshots/studio-8.png" width="32.5%" alt="Studio — slide 8" />
</p>
> Black canvas with electric-yellow type; high-voltage design studio aesthetic.
### [Biennale Yellow](./templates/biennale-yellow/)
<p>
<img src="./screenshots/biennale-yellow-1.png" width="32.5%" alt="Biennale Yellow — slide 1" />
<img src="./screenshots/biennale-yellow-5.png" width="32.5%" alt="Biennale Yellow — slide 5" />
<img src="./screenshots/biennale-yellow-8.png" width="32.5%" alt="Biennale Yellow — slide 8" />
</p>
> Solar yellow on warm parchment with deep indigo serif and atmospheric sun-glow gradients. Dutch-editorial poster energy.
### [Long Table](./templates/long-table/)
<p>
<img src="./screenshots/long-table-1.png" width="32.5%" alt="Long Table — slide 1" />
<img src="./screenshots/long-table-3.png" width="32.5%" alt="Long Table — slide 3" />
<img src="./screenshots/long-table-7.png" width="32.5%" alt="Long Table — slide 7" />
</p>
> Warm cream and rust-red supper-club aesthetic with bold uppercase grotesk headlines, italic Fraunces, and pill-shaped outlined buttons.
## License
[MIT](./LICENSE) — free to use, modify, and distribute.
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`zarazhangrui/beautiful-html-templates`
- 原始仓库:https://github.com/zarazhangrui/beautiful-html-templates
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+1072
View File
File diff suppressed because it is too large Load Diff
+619
View File
@@ -0,0 +1,619 @@
/**
* <deck-stage> — reusable web component for HTML decks.
*
* Handles:
* (a) speaker notes — reads <script type="application/json" id="speaker-notes">
* and posts {slideIndexChanged: N} to the parent window on nav.
* (b) keyboard navigation — ←/→, PgUp/PgDn, Space, Home/End, number keys.
* (c) press R to reset to slide 0 (with a tasteful keyboard hint).
* (d) bottom-center overlay showing slide count + hints, fades out on idle.
* (e) auto-scaling — inner canvas is a fixed design size (default 1920×1080)
* scaled with `transform: scale()` to fit the viewport, letterboxed.
* Set the `noscale` attribute to render at authored size (1:1) — the
* PPTX exporter sets this so its DOM capture sees unscaled geometry.
* (f) print — `@media print` lays every slide out as its own page at the
* design size, so the browser's Print → Save as PDF produces a clean
* one-page-per-slide PDF with no extra setup.
*
* Slides are HIDDEN, not unmounted. Non-active slides stay in the DOM with
* `visibility: hidden` + `opacity: 0`, so their state (videos, iframes,
* form inputs, React trees) is preserved across navigation.
*
* Lifecycle event — the component dispatches a `slidechange` CustomEvent on
* itself whenever the active slide changes (including the initial mount).
* The event bubbles and composes out of shadow DOM, so you can listen on
* the <deck-stage> element or on document:
*
* document.querySelector('deck-stage').addEventListener('slidechange', (e) => {
* e.detail.index // new 0-based index
* e.detail.previousIndex // previous index, or -1 on init
* e.detail.total // total slide count
* e.detail.slide // the new active slide element
* e.detail.previousSlide // the prior slide element, or null on init
* e.detail.reason // 'init' | 'keyboard' | 'click' | 'tap' | 'api'
* });
*
* Persistence: none at the deck level. The host app keeps the current slide
* in its own URL (?slide=) and re-delivers it via location.hash on load, so a
* bare load with no hash always starts at slide 1.
*
* Usage:
* <deck-stage width="1920" height="1080">
* <section data-label="Title">...</section>
* <section data-label="Agenda">...</section>
* </deck-stage>
*
* Slides are the direct element children of <deck-stage>. Each slide is
* automatically tagged with:
* - data-screen-label="NN Label" (1-indexed, for comment flow)
* - data-om-validate="no_overflowing_text,no_overlapping_text,slide_sized_text"
*/
(() => {
const DESIGN_W_DEFAULT = 1920;
const DESIGN_H_DEFAULT = 1080;
const OVERLAY_HIDE_MS = 1800;
const VALIDATE_ATTR = 'no_overflowing_text,no_overlapping_text,slide_sized_text';
const pad2 = (n) => String(n).padStart(2, '0');
const stylesheet = `
:host {
position: fixed;
inset: 0;
display: block;
background: #000;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
overflow: hidden;
}
.stage {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
.canvas {
position: relative;
transform-origin: center center;
flex-shrink: 0;
background: #fff;
will-change: transform;
}
/* Slides live in light DOM (via <slot>) so authored CSS still applies.
We absolutely position each slotted child to stack them. */
::slotted(*) {
position: absolute !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
box-sizing: border-box !important;
overflow: hidden;
opacity: 0;
pointer-events: none;
visibility: hidden;
}
::slotted([data-deck-active]) {
opacity: 1;
pointer-events: auto;
visibility: visible;
}
/* Tap zones for mobile — back/forward thirds like Stories.
Transparent, no visible UI, don't block the overlay. */
.tapzones {
position: fixed;
inset: 0;
display: flex;
z-index: 2147482000;
pointer-events: none;
}
.tapzone {
flex: 1;
pointer-events: auto;
-webkit-tap-highlight-color: transparent;
}
/* Only activate tap zones on coarse pointers (touch devices). */
@media (hover: hover) and (pointer: fine) {
.tapzones { display: none; }
}
.overlay {
position: fixed;
left: 50%;
bottom: 22px;
transform: translate(-50%, 6px) scale(0.92);
filter: blur(6px);
display: flex;
align-items: center;
gap: 4px;
padding: 4px;
background: #000;
color: #fff;
border-radius: 999px;
font-size: 12px;
font-feature-settings: "tnum" 1;
letter-spacing: 0.01em;
opacity: 0;
pointer-events: none;
transition: opacity 260ms ease, transform 260ms cubic-bezier(.2,.8,.2,1), filter 260ms ease;
transform-origin: center bottom;
z-index: 2147483000;
user-select: none;
}
.overlay[data-visible] {
opacity: 1;
pointer-events: auto;
transform: translate(-50%, 0) scale(1);
filter: blur(0);
}
.btn {
appearance: none;
-webkit-appearance: none;
background: transparent;
border: 0;
margin: 0;
padding: 0;
color: inherit;
font: inherit;
cursor: default;
display: inline-flex;
align-items: center;
justify-content: center;
height: 28px;
min-width: 28px;
border-radius: 999px;
color: rgba(255,255,255,0.72);
transition: background 140ms ease, color 140ms ease;
-webkit-tap-highlight-color: transparent;
}
.btn:hover { background: rgba(255,255,255,0.12); color: #fff; }
.btn:active { background: rgba(255,255,255,0.18); }
.btn:focus { outline: none; }
.btn:focus-visible { outline: none; }
.btn::-moz-focus-inner { border: 0; }
.btn svg { width: 14px; height: 14px; display: block; }
.btn.reset {
font-size: 11px;
font-weight: 500;
letter-spacing: 0.02em;
padding: 0 10px 0 12px;
gap: 6px;
color: rgba(255,255,255,0.72);
}
.btn.reset .kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 16px;
height: 16px;
padding: 0 4px;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
font-size: 10px;
line-height: 1;
color: rgba(255,255,255,0.88);
background: rgba(255,255,255,0.12);
border-radius: 4px;
}
.count {
font-variant-numeric: tabular-nums;
color: #fff;
font-weight: 500;
padding: 0 8px;
min-width: 42px;
text-align: center;
font-size: 12px;
}
.count .sep { color: rgba(255,255,255,0.45); margin: 0 3px; font-weight: 400; }
.count .total { color: rgba(255,255,255,0.55); }
.divider {
width: 1px;
height: 14px;
background: rgba(255,255,255,0.18);
margin: 0 2px;
}
/* ── Print: one page per slide, no chrome ────────────────────────────
The screen layout stacks every slide at inset:0 inside a scaled
canvas; for print we want them in document flow at the authored
design size so the browser paginates one slide per sheet. The
@page size is set from the width/height attributes via the inline
<style id="deck-stage-print-page"> that connectedCallback injects
into <head> (the @page at-rule has no effect inside shadow DOM). */
@media print {
:host {
position: static;
inset: auto;
background: none;
overflow: visible;
color: inherit;
}
.stage { position: static; display: block; }
.canvas {
transform: none !important;
width: auto !important;
height: auto !important;
background: none;
will-change: auto;
}
::slotted(*) {
position: relative !important;
inset: auto !important;
width: var(--deck-design-w) !important;
height: var(--deck-design-h) !important;
box-sizing: border-box !important;
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto;
break-after: page;
page-break-after: always;
break-inside: avoid;
overflow: hidden;
}
::slotted(*:last-child) {
break-after: auto;
page-break-after: auto;
}
.overlay, .tapzones { display: none !important; }
}
`;
class DeckStage extends HTMLElement {
static get observedAttributes() { return ['width', 'height', 'noscale']; }
constructor() {
super();
this._root = this.attachShadow({ mode: 'open' });
this._index = 0;
this._slides = [];
this._notes = [];
this._hideTimer = null;
this._mouseIdleTimer = null;
this._onKey = this._onKey.bind(this);
this._onResize = this._onResize.bind(this);
this._onSlotChange = this._onSlotChange.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
this._onTapBack = this._onTapBack.bind(this);
this._onTapForward = this._onTapForward.bind(this);
}
get designWidth() {
return parseInt(this.getAttribute('width'), 10) || DESIGN_W_DEFAULT;
}
get designHeight() {
return parseInt(this.getAttribute('height'), 10) || DESIGN_H_DEFAULT;
}
connectedCallback() {
this._render();
this._loadNotes();
this._syncPrintPageRule();
window.addEventListener('keydown', this._onKey);
window.addEventListener('resize', this._onResize);
window.addEventListener('mousemove', this._onMouseMove, { passive: true });
// Initial collection + layout happens via slotchange, which fires on mount.
}
disconnectedCallback() {
window.removeEventListener('keydown', this._onKey);
window.removeEventListener('resize', this._onResize);
window.removeEventListener('mousemove', this._onMouseMove);
if (this._hideTimer) clearTimeout(this._hideTimer);
if (this._mouseIdleTimer) clearTimeout(this._mouseIdleTimer);
}
attributeChangedCallback() {
if (this._canvas) {
this._canvas.style.width = this.designWidth + 'px';
this._canvas.style.height = this.designHeight + 'px';
this._canvas.style.setProperty('--deck-design-w', this.designWidth + 'px');
this._canvas.style.setProperty('--deck-design-h', this.designHeight + 'px');
this._fit();
this._syncPrintPageRule();
}
}
_render() {
const style = document.createElement('style');
style.textContent = stylesheet;
const stage = document.createElement('div');
stage.className = 'stage';
const canvas = document.createElement('div');
canvas.className = 'canvas';
canvas.style.width = this.designWidth + 'px';
canvas.style.height = this.designHeight + 'px';
canvas.style.setProperty('--deck-design-w', this.designWidth + 'px');
canvas.style.setProperty('--deck-design-h', this.designHeight + 'px');
const slot = document.createElement('slot');
slot.addEventListener('slotchange', this._onSlotChange);
canvas.appendChild(slot);
stage.appendChild(canvas);
// Tap zones (mobile): left third = back, right third = forward.
const tapzones = document.createElement('div');
tapzones.className = 'tapzones export-hidden';
tapzones.setAttribute('aria-hidden', 'true');
tapzones.setAttribute('data-noncommentable', '');
const tzBack = document.createElement('div');
tzBack.className = 'tapzone tapzone--back';
const tzMid = document.createElement('div');
tzMid.className = 'tapzone tapzone--mid';
tzMid.style.pointerEvents = 'none';
const tzFwd = document.createElement('div');
tzFwd.className = 'tapzone tapzone--fwd';
tzBack.addEventListener('click', this._onTapBack);
tzFwd.addEventListener('click', this._onTapForward);
tapzones.append(tzBack, tzMid, tzFwd);
// Overlay: compact, solid black, with clickable controls.
const overlay = document.createElement('div');
overlay.className = 'overlay export-hidden';
overlay.setAttribute('role', 'toolbar');
overlay.setAttribute('aria-label', 'Deck controls');
overlay.setAttribute('data-noncommentable', '');
overlay.innerHTML = `
<button class="btn prev" type="button" aria-label="Previous slide" title="Previous (←)">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 3L5 8l5 5"/></svg>
</button>
<span class="count" aria-live="polite"><span class="current">1</span><span class="sep">/</span><span class="total">1</span></span>
<button class="btn next" type="button" aria-label="Next slide" title="Next (→)">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
</button>
<span class="divider"></span>
<button class="btn reset" type="button" aria-label="Reset to first slide" title="Reset (R)">Reset<span class="kbd">R</span></button>
`;
overlay.querySelector('.prev').addEventListener('click', () => this._go(this._index - 1, 'click'));
overlay.querySelector('.next').addEventListener('click', () => this._go(this._index + 1, 'click'));
overlay.querySelector('.reset').addEventListener('click', () => this._go(0, 'click'));
this._root.append(style, stage, tapzones, overlay);
this._canvas = canvas;
this._slot = slot;
this._overlay = overlay;
this._countEl = overlay.querySelector('.current');
this._totalEl = overlay.querySelector('.total');
}
/** @page must live in the document stylesheet — it's a no-op inside
* shadow DOM. Inject/update a single <head> style tag so the print
* sheet matches the design size and Save-as-PDF yields one slide per
* page with no margins. */
_syncPrintPageRule() {
const id = 'deck-stage-print-page';
let tag = document.getElementById(id);
if (!tag) {
tag = document.createElement('style');
tag.id = id;
document.head.appendChild(tag);
}
tag.textContent =
'@page { size: ' + this.designWidth + 'px ' + this.designHeight + 'px; margin: 0; } ' +
'@media print { html, body { margin: 0 !important; padding: 0 !important; background: none !important; overflow: visible !important; height: auto !important; } ' +
'* { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }';
}
_onSlotChange() {
this._collectSlides();
this._restoreIndex();
this._applyIndex({ showOverlay: false, broadcast: true, reason: 'init' });
this._fit();
}
_collectSlides() {
const assigned = this._slot.assignedElements({ flatten: true });
this._slides = assigned.filter((el) => {
// Skip template/style/script nodes even if someone slots them.
const tag = el.tagName;
return tag !== 'TEMPLATE' && tag !== 'SCRIPT' && tag !== 'STYLE';
});
this._slides.forEach((slide, i) => {
const n = i + 1;
// Determine a label for comment flow: prefer explicit data-label,
// then an existing data-screen-label, then first heading, else "Slide".
let label = slide.getAttribute('data-label');
if (!label) {
const existing = slide.getAttribute('data-screen-label');
if (existing) {
// Strip any leading number the author may have included.
label = existing.replace(/^\s*\d+\s*/, '').trim() || existing;
}
}
if (!label) {
const h = slide.querySelector('h1, h2, h3, [data-title]');
if (h) label = (h.textContent || '').trim().slice(0, 40);
}
if (!label) label = 'Slide';
slide.setAttribute('data-screen-label', `${pad2(n)} ${label}`);
// Validation attribute for comment flow / auto-checks.
if (!slide.hasAttribute('data-om-validate')) {
slide.setAttribute('data-om-validate', VALIDATE_ATTR);
}
slide.setAttribute('data-deck-slide', String(i));
});
if (this._totalEl) this._totalEl.textContent = String(this._slides.length || 1);
if (this._index >= this._slides.length) this._index = Math.max(0, this._slides.length - 1);
}
_loadNotes() {
const tag = document.getElementById('speaker-notes');
if (!tag) { this._notes = []; return; }
try {
const parsed = JSON.parse(tag.textContent || '[]');
if (Array.isArray(parsed)) this._notes = parsed;
} catch (e) {
console.warn('[deck-stage] Failed to parse #speaker-notes JSON:', e);
this._notes = [];
}
}
_restoreIndex() {
// The host's ?slide= param is delivered as a #<int> hash (1-indexed) on
// the iframe src. No hash → slide 1; the deck itself keeps no position
// state across loads.
const h = (location.hash || '').match(/^#(\d+)$/);
if (h) {
const n = parseInt(h[1], 10) - 1;
if (n >= 0 && n < this._slides.length) this._index = n;
}
}
_applyIndex({ showOverlay = true, broadcast = true, reason = 'init' } = {}) {
if (!this._slides.length) return;
const prev = this._prevIndex == null ? -1 : this._prevIndex;
const curr = this._index;
// Keep the iframe's own hash in sync so an in-iframe location.reload()
// (reload banner path in viewer-handle.ts) lands on the current slide,
// not the stale deep-link hash from initial load.
try { history.replaceState(null, '', '#' + (curr + 1)); } catch (e) {}
this._slides.forEach((s, i) => {
if (i === curr) s.setAttribute('data-deck-active', '');
else s.removeAttribute('data-deck-active');
});
if (this._countEl) this._countEl.textContent = String(curr + 1);
if (broadcast) {
// (1) Legacy: host-window postMessage for speaker-notes renderers.
try { window.postMessage({ slideIndexChanged: curr }, '*'); } catch (e) {}
// (2) In-page CustomEvent on the <deck-stage> element itself.
// Bubbles and composes out of shadow DOM so slide code can listen:
// document.querySelector('deck-stage').addEventListener('slidechange', e => {
// e.detail.index, e.detail.previousIndex, e.detail.total, e.detail.slide, e.detail.reason
// });
const detail = {
index: curr,
previousIndex: prev,
total: this._slides.length,
slide: this._slides[curr] || null,
previousSlide: prev >= 0 ? (this._slides[prev] || null) : null,
reason: reason, // 'init' | 'keyboard' | 'click' | 'tap' | 'api'
};
this.dispatchEvent(new CustomEvent('slidechange', {
detail,
bubbles: true,
composed: true,
}));
}
this._prevIndex = curr;
if (showOverlay) this._flashOverlay();
}
_flashOverlay() {
if (!this._overlay) return;
this._overlay.setAttribute('data-visible', '');
if (this._hideTimer) clearTimeout(this._hideTimer);
this._hideTimer = setTimeout(() => {
this._overlay.removeAttribute('data-visible');
}, OVERLAY_HIDE_MS);
}
_fit() {
if (!this._canvas) return;
// PPTX export sets noscale so the DOM capture sees authored-size
// geometry — the scaled canvas is in shadow DOM, so the exporter's
// resetTransformSelector can't reach .canvas.style.transform directly.
if (this.hasAttribute('noscale')) {
this._canvas.style.transform = 'none';
return;
}
const vw = window.innerWidth;
const vh = window.innerHeight;
const s = Math.min(vw / this.designWidth, vh / this.designHeight);
this._canvas.style.transform = `scale(${s})`;
}
_onResize() { this._fit(); }
_onMouseMove() {
// Keep overlay visible while mouse moves; hide after idle.
this._flashOverlay();
}
_onTapBack(e) {
e.preventDefault();
this._go(this._index - 1, 'tap');
}
_onTapForward(e) {
e.preventDefault();
this._go(this._index + 1, 'tap');
}
_onKey(e) {
// Ignore when the user is typing.
const t = e.target;
if (t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))) return;
if (e.metaKey || e.ctrlKey || e.altKey) return;
const key = e.key;
let handled = true;
if (key === 'ArrowRight' || key === 'PageDown' || key === ' ' || key === 'Spacebar') {
this._go(this._index + 1, 'keyboard');
} else if (key === 'ArrowLeft' || key === 'PageUp') {
this._go(this._index - 1, 'keyboard');
} else if (key === 'Home') {
this._go(0, 'keyboard');
} else if (key === 'End') {
this._go(this._slides.length - 1, 'keyboard');
} else if (key === 'r' || key === 'R') {
this._go(0, 'keyboard');
} else if (/^[0-9]$/.test(key)) {
// 1..9 jump to that slide; 0 jumps to 10.
const n = key === '0' ? 9 : parseInt(key, 10) - 1;
if (n < this._slides.length) this._go(n, 'keyboard');
} else {
handled = false;
}
if (handled) {
e.preventDefault();
this._flashOverlay();
}
}
_go(i, reason = 'api') {
if (!this._slides.length) return;
const clamped = Math.max(0, Math.min(this._slides.length - 1, i));
if (clamped === this._index) {
this._flashOverlay();
return;
}
this._index = clamped;
this._applyIndex({ showOverlay: true, broadcast: true, reason });
}
// Public API ------------------------------------------------------------
/** Current slide index (0-based). */
get index() { return this._index; }
/** Total slide count. */
get length() { return this._slides.length; }
/** Programmatically navigate. */
goTo(i) { this._go(i, 'api'); }
next() { this._go(this._index + 1, 'api'); }
prev() { this._go(this._index - 1, 'api'); }
reset() { this._go(0, 'api'); }
}
if (!customElements.get('deck-stage')) {
customElements.define('deck-stage', DeckStage);
}
})();
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Some files were not shown because too many files have changed in this diff Show More