@hypersocial/decks-cli 0.9.0 → 0.9.1
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/package.json +1 -1
- package/skills/hyperdecks/SKILL.md +17 -2
- package/skills/hyperdecks/chat.md +4 -0
- package/src/checkRules.mjs +7 -1
- package/src/commands.mjs +1 -1
- package/src/files.mjs +1 -0
- package/src/layouts.mjs +2 -2
- package/src/runtime.mjs +1 -1
- package/templates/layouts/_deck.html +30 -1
- package/templates/layouts/phones.html +54 -0
- package/templates/layouts/product.html +6 -2
package/package.json
CHANGED
|
@@ -460,7 +460,8 @@ suit it.
|
|
|
460
460
|
|---|---|---|
|
|
461
461
|
| `hero` | the opening, a section opener; the presenter is the visual, no `.art` unless it shows the real subject (check fails while the placeholder `<div class="art">` is in: swap it for an `<img class="art">` or delete it) | stage, bottom-right; `[wave]`, `[look: h1]` |
|
|
462
462
|
| `number` | one number with its source: proof, traction, a delta | stage; `[look: #<id>-figure]` as it is said |
|
|
463
|
-
| `product` | a screenshot in a browser frame (`.frame.
|
|
463
|
+
| `product` | a screenshot in a browser frame (`.frame.card` for a phone) with the point beside it | stage; `[look: #<id>-frame]`, `[reveal: #<id>-callout]` |
|
|
464
|
+
| `phones` | a phone app's flow: a fanned row of phone cards | stage, bottom-left; `[go: #<id>-p1]` … `p3`, `[go: slot]` |
|
|
464
465
|
| `quote` | a customer or user in their own words, quoted exactly | stage, bottom-left; `[look: #<id>-quote]` |
|
|
465
466
|
| `compare` | before/after, them/us, three parallel lines each | stage; `[look: #<id>-before]`, `[go: #<id>-after]` |
|
|
466
467
|
| `timeline` | four milestones: history, a roadmap, a process | stage; `[go: #<id>-m1]` … `m4`, `[go: slot]` |
|
|
@@ -469,11 +470,15 @@ suit it.
|
|
|
469
470
|
| `team` | up to four people, faces only with permission | stage; `[go: #<id>-p1]` … `p4`, `[go: slot]` |
|
|
470
471
|
| `closer` | the end: one line and one action (a command, a link) | stage, bottom-left; `[go: #<id>-cta]`, `[go: slot]`, `[wave]` |
|
|
471
472
|
|
|
473
|
+
**Phones and backgrounds.** One phone: `product`'s `.frame.card`; a flow of
|
|
474
|
+
two to four: `phones` (`--tilt`, `--shadow` on a card). A few slides at most:
|
|
475
|
+
`bg-dots` (never under text), a `.deco` of inked tiles.
|
|
476
|
+
|
|
472
477
|
Presenter style follows the deck (step 2, *Which presenter style*): `stage`
|
|
473
478
|
when the presenter carries the story, which is what the layouts are drawn for
|
|
474
479
|
(they keep the slot's corner clear); `corner` on a slide where a big
|
|
475
480
|
visual is the point and moves are not needed; `glass` on a dark or busy
|
|
476
|
-
picture.
|
|
481
|
+
picture. Three layouts (`quote`, `phones`, `closer`) sit left; put a `slot-left` on others
|
|
477
482
|
where there is room so slide changes move the presenter.
|
|
478
483
|
|
|
479
484
|
**Visuals: real first, generated second.**
|
|
@@ -793,6 +798,16 @@ folder) and `hyperdecks source push <slug> [dir]` (a folder's files back to the
|
|
|
793
798
|
draft), `hyperdecks asset add <slug> <file|url>` for an image, `hyperdecks guide
|
|
794
799
|
[topic]` for these sections, and `--hosted` on `deck new`, `check` and `release`.
|
|
795
800
|
|
|
801
|
+
## After you ship: say what cost you
|
|
802
|
+
|
|
803
|
+
When a deck is released, end your report with a short list of what cost you
|
|
804
|
+
time, ranked by cost: a rule you had to work around, a layout or style you
|
|
805
|
+
hand-wrote because the library had none, a check or tool that said the wrong
|
|
806
|
+
thing, something you couldn't find in this guide. One line each, concrete
|
|
807
|
+
(the file, the command, the message), no praise needed. Send it to whoever
|
|
808
|
+
asked for the deck. Hyperdecks turns these into roadmap cards; the Xbox
|
|
809
|
+
rebuild's list on 2026-11-05 produced four.
|
|
810
|
+
|
|
796
811
|
## Safety
|
|
797
812
|
|
|
798
813
|
- For read-only inspection, run commands directly and summarize the important
|
|
@@ -107,6 +107,10 @@ makes the hello deck; continue from step 5 below.
|
|
|
107
107
|
- A deck with no narration (a product tour, a silent walkthrough): put
|
|
108
108
|
`data-silent` on the player script and skip narrate_deck; check warns, release
|
|
109
109
|
goes through, and the player shows no Play button or clock.
|
|
110
|
+
- When the deck is released, tell the person what cost you time, ranked by
|
|
111
|
+
cost: a rule you worked around, a layout you hand-wrote, a tool or check that
|
|
112
|
+
said the wrong thing, something missing from `deck_guide`. One concrete line
|
|
113
|
+
each. Hyperdecks turns these into roadmap cards.
|
|
110
114
|
- The deck's own scripts run: slide controllers, `[custom: name]` cues,
|
|
111
115
|
pausing the narrator, tappable and interactive slides. `deck_guide
|
|
112
116
|
{ topic: "scripts" }` has the hooks and an example.
|
package/src/checkRules.mjs
CHANGED
|
@@ -511,6 +511,8 @@ export function phraseList(value) {
|
|
|
511
511
|
// own markers: the empty div (or the chart's labelled svg). A div that holds
|
|
512
512
|
// its own picture, or gets one from a url() in its style or a <style> rule
|
|
513
513
|
// naming its class, is the deck's art. A deck that wants none removes it.
|
|
514
|
+
// Each opening tag is read on its own (the inner text is a lookahead), so a
|
|
515
|
+
// stand-in nested in another of its tag, like a phone card's shot, is seen.
|
|
514
516
|
const PLACEHOLDERS = [
|
|
515
517
|
{
|
|
516
518
|
layout: "l-hero", tag: "div", cls: "art", what: "the hero's art", img: '<img class="art" src="…" alt="">, or delete the div for no art (the presenter is the visual)',
|
|
@@ -520,6 +522,10 @@ const PLACEHOLDERS = [
|
|
|
520
522
|
layout: "l-product", tag: "div", cls: "shot", what: "the product screenshot", img: '<img class="shot" src="…" alt="…">',
|
|
521
523
|
how: { hosted: "capture the real page with capture_url", cli: "capture the real page with `hyperdecks screenshot <url>`" },
|
|
522
524
|
},
|
|
525
|
+
{
|
|
526
|
+
layout: "l-phones", tag: "div", cls: "shot", what: "each phone screen", img: '<img class="shot" src="…" alt="…"> (or <video class="shot" src="…" autoplay muted loop playsinline>) in each card',
|
|
527
|
+
how: { hosted: "capture the app at phone size with capture_url (width 390, height 844), or upload a screen recording with upload_deck_asset", cli: "capture the app at phone size with `hyperdecks screenshot <url> --width 390 --height 844`, or add a screen recording with `hyperdecks asset add`" },
|
|
528
|
+
},
|
|
523
529
|
{
|
|
524
530
|
layout: "l-chart", tag: "svg", cls: "chart", label: /placeholder/i, what: "the chart (made-up numbers)", img: '<img class="chart" src="…" alt="…">',
|
|
525
531
|
how: { hosted: "draw the real data with render_chart", cli: "draw the real data with `hyperdecks chart <spec.json>`" },
|
|
@@ -531,7 +537,7 @@ export function placeholderErrors(html, { hosted = false } = {}) {
|
|
|
531
537
|
const styled = (cls) => [...css.matchAll(/([^{}]*)\{([^{}]*)\}/g)].some(([, sel, body]) => new RegExp(`\\.${cls}(?![\\w-])`).test(sel) && /url\(/i.test(body));
|
|
532
538
|
return slides(html).flatMap((s, i) => {
|
|
533
539
|
const layouts = classes(s.tag);
|
|
534
|
-
return PLACEHOLDERS.filter((p) => layouts.includes(p.layout) && !(p.tag === "div" && styled(p.cls)) && [...s.body.matchAll(new RegExp(`(<${p.tag}\\b[^>]*>)([\\s\\S]*?)</${p.tag}
|
|
540
|
+
return PLACEHOLDERS.filter((p) => layouts.includes(p.layout) && !(p.tag === "div" && styled(p.cls)) && [...s.body.matchAll(new RegExp(`(<${p.tag}\\b[^>]*>)(?=([\\s\\S]*?)</${p.tag}>)`, "gi"))].some(([, tag, inner]) =>
|
|
535
541
|
classes(tag).includes(p.cls) && (p.label ? p.label.test(attr(tag, "aria-label") ?? "") : !inner.trim() && !/url\(/i.test(attr(tag, "style") ?? ""))))
|
|
536
542
|
.map((p) => MESSAGES.placeholder(s.id || `${i + 1}`, p.what, `<${p.tag} class="${p.cls}">`, hosted ? p.how.hosted : p.how.cli, p.img));
|
|
537
543
|
});
|
package/src/commands.mjs
CHANGED
|
@@ -340,7 +340,7 @@ commands:
|
|
|
340
340
|
deck new <slug> "<title>" --narrated [--layouts hero,product,number,closer] [--presenter-style stage|bubble|glass|corner] [--no-brand] [--dir <path>]
|
|
341
341
|
scaffold decks/<slug>/: index.html (8 slides, or one per layout, player/v1), script.md, evidence.md,
|
|
342
342
|
in your saved brand's colours, fonts and logo (--no-brand: the template's look)
|
|
343
|
-
layouts: hero, number, product, quote, compare, timeline, features, chart, team, closer
|
|
343
|
+
layouts: hero, number, product, phones, quote, compare, timeline, features, chart, team, closer
|
|
344
344
|
check <slug|deck-dir> [--forbid "<phrase>,<phrase>"] [--max-duration <s>] [--layout] [--strict] [--fix] [--json]
|
|
345
345
|
the narrated-deck gate: slides vs manifest, timing, captions, clip URLs,
|
|
346
346
|
local refs, pinned player, mobile titles. data-silent on the player script: no narration
|
package/src/files.mjs
CHANGED
|
@@ -16,6 +16,7 @@ const FILES = {
|
|
|
16
16
|
"templates/layouts/hero.html": () => new URL("../templates/layouts/hero.html", import.meta.url),
|
|
17
17
|
"templates/layouts/number.html": () => new URL("../templates/layouts/number.html", import.meta.url),
|
|
18
18
|
"templates/layouts/product.html": () => new URL("../templates/layouts/product.html", import.meta.url),
|
|
19
|
+
"templates/layouts/phones.html": () => new URL("../templates/layouts/phones.html", import.meta.url),
|
|
19
20
|
"templates/layouts/quote.html": () => new URL("../templates/layouts/quote.html", import.meta.url),
|
|
20
21
|
"templates/layouts/team.html": () => new URL("../templates/layouts/team.html", import.meta.url),
|
|
21
22
|
"templates/layouts/timeline.html": () => new URL("../templates/layouts/timeline.html", import.meta.url),
|
package/src/layouts.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { packageText } from "./files.mjs";
|
|
|
5
5
|
|
|
6
6
|
export const NARRATED_FILES = { "index.html": "narrated-deck.html", "script.md": "script.md", "evidence.md": "evidence.md" };
|
|
7
7
|
// The library, in a sensible deck order; `--layouts` takes any order and repeats.
|
|
8
|
-
export const LAYOUTS = ["hero", "number", "product", "quote", "compare", "timeline", "features", "chart", "team", "closer"];
|
|
9
|
-
const LAYOUT_ALIASES = { "big-number": "number", "product-shot": "product", comparison: "compare", "three-up": "features" };
|
|
8
|
+
export const LAYOUTS = ["hero", "number", "product", "phones", "quote", "compare", "timeline", "features", "chart", "team", "closer"];
|
|
9
|
+
const LAYOUT_ALIASES = { "big-number": "number", "product-shot": "product", "phone-row": "phones", comparison: "compare", "three-up": "features" };
|
|
10
10
|
|
|
11
11
|
const escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
12
12
|
|
package/src/runtime.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { ConvexHttpClient } from "convex/browser";
|
|
9
9
|
import { makeFunctionReference } from "convex/server";
|
|
10
10
|
|
|
11
|
-
export const VERSION = "0.9.
|
|
11
|
+
export const VERSION = "0.9.1";
|
|
12
12
|
const UPLOAD_TIMEOUT_MS = 125_000;
|
|
13
13
|
|
|
14
14
|
export const PROD = {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
and the presenter, and plays the narration. Change the palette and fonts
|
|
52
52
|
here; every layout reads them. */
|
|
53
53
|
:root {
|
|
54
|
-
--ink: #0b0b0f; --paper: #fafaf7; --accent: #ffda1b; --accent-2: #27ceff; --card: #ffffff;
|
|
54
|
+
--ink: #0b0b0f; --paper: #fafaf7; --accent: #ffda1b; --accent-2: #27ceff; --accent-3: #ff9ec7; --card: #ffffff;
|
|
55
55
|
--font: "Space Grotesk", system-ui, sans-serif; --mono: "Space Mono", ui-monospace, monospace;
|
|
56
56
|
/* The type scale: one display size per slide at most, then title, sub (a title beside a visual), lead, body, label. */
|
|
57
57
|
--t-display: 128px; --t-title: 84px; --t-sub: 56px; --t-lead: 30px; --t-body: 24px; --t-label: 17px;
|
|
@@ -73,12 +73,41 @@
|
|
|
73
73
|
.slot-top .narration-slot { top: 96px; bottom: auto; }
|
|
74
74
|
.reveal { opacity: 0; transform: translateY(12px); transition: opacity .5s, transform .5s; }
|
|
75
75
|
.reveal.is-revealed { opacity: 1; transform: none; }
|
|
76
|
+
/* The phone card (product's .frame.card, the phones row): a frameless phone,
|
|
77
|
+
white with an ink edge, a hard offset shadow and a tilt. Change --tilt and
|
|
78
|
+
--shadow on the element (style="--tilt: 3deg; --shadow: var(--accent-2)");
|
|
79
|
+
put an <img class="shot"> or <video class="shot" autoplay muted loop
|
|
80
|
+
playsinline> in place of the placeholder div. */
|
|
81
|
+
.slide .frame.card { --tilt: -4deg; --shadow: var(--accent); position: relative; width: 320px; aspect-ratio: 9 / 19.5; padding: 9px; border: 3px solid var(--ink); border-radius: 46px; background: var(--card); box-shadow: 18px 18px 0 var(--shadow); transform: rotate(var(--tilt)); overflow: visible; }
|
|
82
|
+
.slide .frame.card > .shot { display: block; width: 100%; height: 100%; aspect-ratio: auto; border-radius: 36px; border: 2px solid color-mix(in srgb, var(--ink) 20%, transparent); object-fit: cover; }
|
|
83
|
+
.slide .frame.card > div.shot { background:
|
|
84
|
+
linear-gradient(color-mix(in srgb, var(--ink) 16%, transparent) 0 0) 50% 5% / 36% 2.2% no-repeat,
|
|
85
|
+
linear-gradient(var(--accent-2) 0 0) 50% 30% / 84% 40% no-repeat,
|
|
86
|
+
linear-gradient(color-mix(in srgb, var(--ink) 14%, transparent) 0 0) 16% 62% / 60% 2.4% no-repeat,
|
|
87
|
+
linear-gradient(color-mix(in srgb, var(--ink) 14%, transparent) 0 0) 14% 67% / 44% 2.4% no-repeat,
|
|
88
|
+
linear-gradient(var(--accent) 0 0) 50% 90% / 84% 6% no-repeat,
|
|
89
|
+
var(--paper); }
|
|
90
|
+
/* Backgrounds, optional and for a few slides a deck. bg-dots on the section:
|
|
91
|
+
a dot grid in the slide's text colour, faded in from the side away from the text
|
|
92
|
+
(--dots-fade is the fade's direction: 90deg puts the dots on the right,
|
|
93
|
+
270deg on the left). A <div class="deco" aria-hidden="true"> in the slide
|
|
94
|
+
holds small inked tiles, each a <span> placed by its inline left/top (and
|
|
95
|
+
background, rotate). Both sit behind the slide's content. */
|
|
96
|
+
.slide.bg-dots, .slide:has(> .deco) { isolation: isolate; }
|
|
97
|
+
.slide.bg-dots::before { content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; background: radial-gradient(color-mix(in srgb, currentColor 24%, transparent) 3px, transparent 3.6px) 0 0 / 36px 36px; -webkit-mask-image: linear-gradient(var(--dots-fade, 90deg), transparent 40%, #000 80%); mask-image: linear-gradient(var(--dots-fade, 90deg), transparent 40%, #000 80%); }
|
|
98
|
+
.slide > .deco { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
|
|
99
|
+
.slide > .deco > span { position: absolute; display: grid; place-items: center; width: 80px; height: 80px; border: 3px solid var(--ink); border-radius: 20px; background: var(--card); box-shadow: 8px 8px 0 var(--ink); color: var(--ink); font: 700 34px var(--font); }
|
|
76
100
|
/* Phones: the slide is the 374×736 card; the presenter is a small head in
|
|
77
101
|
the corner, so keep the bottom 170px clear. */
|
|
78
102
|
@media (max-width: 650px) {
|
|
79
103
|
:root { --t-display: 54px; --t-title: 38px; --t-sub: 28px; --t-lead: 17px; --t-body: 15px; --t-label: 12px; }
|
|
80
104
|
.slide { padding: 28px 20px 170px; gap: 14px; }
|
|
81
105
|
.narration-slot { right: 12px; left: 12px; bottom: 64px; }
|
|
106
|
+
.slide .frame.card { width: 160px; padding: 5px; border-width: 2px; border-radius: 24px; box-shadow: 8px 8px 0 var(--shadow); }
|
|
107
|
+
.slide .frame.card > .shot { border-radius: 19px; border-width: 1px; }
|
|
108
|
+
/* The text fills the card's width: the dots fade in towards the bottom, and the tiles stay off. */
|
|
109
|
+
.slide.bg-dots::before { background-size: 22px 22px; background-image: radial-gradient(color-mix(in srgb, currentColor 24%, transparent) 2px, transparent 2.5px); -webkit-mask-image: linear-gradient(180deg, transparent 45%, #000 85%); mask-image: linear-gradient(180deg, transparent 45%, #000 85%); }
|
|
110
|
+
.slide > .deco { display: none; }
|
|
82
111
|
}
|
|
83
112
|
</style>
|
|
84
113
|
<!-- LAYOUT STYLES -->
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
layout: phones
|
|
3
|
+
summary: A fanned row of two to four phone cards (the inner ones larger and in front, the outer ones tilted out), the point in the column beside it.
|
|
4
|
+
use: a phone app's flow or its key screens. Each card's .shot is a placeholder check fails on: put in a phone-size screenshot (`hyperdecks screenshot <url> --width 390 --height 844`, capture_url) as <img class="shot">, or a screen recording as <video class="shot" autoplay muted loop playsinline>. Two to four cards: the row spaces and tilts them (on phones only the first three show); change one card's --tilt or --shadow on it. One phone only: use product with .frame.card.
|
|
5
|
+
slot: bottom-left
|
|
6
|
+
presenter: stage
|
|
7
|
+
cues: [look: #__ID__-row] when the narration turns to the app; [go: #__ID__-p1] … [go: #__ID__-p3] to walk the screens, then [go: slot].
|
|
8
|
+
script: Here is the app on a phone. [look: #__ID__-row] You start here, [go: #__ID__-p1] you pick one, [go: #__ID__-p2] and it is done. [go: #__ID__-p3] [go: slot]
|
|
9
|
+
-->
|
|
10
|
+
<style>
|
|
11
|
+
.l-phones { display: grid; grid-template-columns: 500px 1fr; grid-template-areas: "text row"; column-gap: 40px; align-items: center; }
|
|
12
|
+
.l-phones .text { grid-area: text; align-self: start; display: flex; flex-direction: column; gap: 20px; }
|
|
13
|
+
.l-phones .tt { font-size: var(--t-sub); line-height: 1.02; }
|
|
14
|
+
.l-phones .lead { font-size: var(--t-body); }
|
|
15
|
+
/* The row: cards overlap a little; the first and last tilt out and sit lower,
|
|
16
|
+
the ones between are larger and in front. The card's look is shared (_deck.html). */
|
|
17
|
+
.l-phones .row { grid-area: row; display: flex; justify-content: center; align-items: center; padding: 0 0 40px; }
|
|
18
|
+
.l-phones .row > .frame.card { --tilt: 0deg; flex: none; width: 260px; margin: 0 -14px; }
|
|
19
|
+
.l-phones .row > .frame.card:first-child { --tilt: -7deg; --shadow: var(--accent-2); translate: 0 36px; }
|
|
20
|
+
.l-phones .row > .frame.card:last-child { --tilt: 7deg; --shadow: var(--accent-3); translate: 0 36px; }
|
|
21
|
+
.l-phones .row > .frame.card:not(:first-child, :last-child) { width: 300px; z-index: 1; }
|
|
22
|
+
.l-phones .row:has(> :nth-child(4)) > .frame.card { width: 220px; }
|
|
23
|
+
.l-phones .row:has(> :nth-child(4)) > .frame.card:not(:first-child, :last-child) { width: 250px; }
|
|
24
|
+
@media (max-width: 650px) {
|
|
25
|
+
.l-phones { display: flex; flex-direction: column; align-items: stretch; }
|
|
26
|
+
.l-phones .text { gap: 10px; }
|
|
27
|
+
.l-phones .row { padding: 6px 0 18px; }
|
|
28
|
+
.l-phones .row > .frame.card, .l-phones .row:has(> :nth-child(4)) > .frame.card { width: 108px; margin: 0 -6px; }
|
|
29
|
+
.l-phones .row > .frame.card:not(:first-child, :last-child), .l-phones .row:has(> :nth-child(4)) > .frame.card:not(:first-child, :last-child) { width: 128px; }
|
|
30
|
+
.l-phones .row > .frame.card:is(:first-child, :last-child) { translate: 0 16px; }
|
|
31
|
+
/* Four do not fit the phone card: the row keeps the first three, the third as its end. */
|
|
32
|
+
.l-phones .row > .frame.card:nth-child(n + 4) { display: none; }
|
|
33
|
+
.l-phones .row:has(> :nth-child(4)) > .frame.card:nth-child(3) { --tilt: 7deg; --shadow: var(--accent-3); width: 108px; translate: 0 16px; }
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
<section class="slide l-phones slot-left bg-dots" id="__ID__" data-chapter="On a phone" data-note="Phone screens: name the app build and the date they were captured.">
|
|
37
|
+
<div class="text">
|
|
38
|
+
<div class="kicker">On a phone</div>
|
|
39
|
+
<h2 class="tt" data-title-mobile="Three taps.">Three taps, start to finish.</h2>
|
|
40
|
+
<p class="lead">One line on what the screens show, in order.</p>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="row" id="__ID__-row">
|
|
43
|
+
<div class="frame card" id="__ID__-p1"><div class="shot" role="img" aria-label="Phone screen placeholder: the first step"></div></div>
|
|
44
|
+
<div class="frame card" id="__ID__-p2"><div class="shot" role="img" aria-label="Phone screen placeholder: the main screen"></div></div>
|
|
45
|
+
<div class="frame card" id="__ID__-p3"><div class="shot" role="img" aria-label="Phone screen placeholder: the result"></div></div>
|
|
46
|
+
</div>
|
|
47
|
+
<!-- Optional decorations, behind the content (a few slides a deck at most):
|
|
48
|
+
<div class="deco" aria-hidden="true">
|
|
49
|
+
<span style="left: 52%; top: 8%; background: var(--accent); rotate: -10deg;">★</span>
|
|
50
|
+
<span style="left: 92%; top: 42%; background: var(--accent-2); rotate: 12deg;">♪</span>
|
|
51
|
+
<span style="left: 48%; top: 78%; background: var(--accent-3); rotate: 8deg;">☺</span>
|
|
52
|
+
</div> -->
|
|
53
|
+
<div class="narration-slot"></div>
|
|
54
|
+
</section>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
layout: product
|
|
3
|
-
summary: A product screenshot in a browser frame (
|
|
4
|
-
use: a demo moment. Its .shot is a placeholder check fails on: put in `hyperdecks screenshot <url>` (capture_url) output as <img class="shot">; a callout chip names what to look at.
|
|
3
|
+
summary: A product screenshot in a browser frame (for a phone shot, .frame.card: the frameless phone card), the point in the column beside it.
|
|
4
|
+
use: a demo moment. Its .shot is a placeholder check fails on: put in `hyperdecks screenshot <url>` (capture_url) output as <img class="shot">; a callout chip names what to look at. For a phone app, make the frame `<div class="frame card" id="__ID__-frame">` holding only the shot (a phone screenshot or a `<video class="shot" autoplay muted loop playsinline>` clip) and the callout; tilt and shadow colour are `--tilt` and `--shadow` on it. `.frame.phone` (the black bezel) stays for older decks.
|
|
5
5
|
slot: bottom-right
|
|
6
6
|
presenter: stage
|
|
7
7
|
cues: [look: #__ID__-frame] when the narration turns to the product; [reveal: #__ID__-callout] for the detail.
|
|
@@ -27,6 +27,9 @@ script: This is the product. [look: #__ID__-frame] Look at this part. [reveal: #
|
|
|
27
27
|
linear-gradient(var(--accent-2) 0 0) 248px 176px / 300px 300px no-repeat,
|
|
28
28
|
linear-gradient(color-mix(in srgb, var(--ink) 8%, transparent) 0 0) 580px 176px / 308px 300px no-repeat,
|
|
29
29
|
linear-gradient(color-mix(in srgb, var(--ink) 6%, transparent) 0 0) 0 0 / 210px 100% no-repeat; }
|
|
30
|
+
/* The phone card's look is shared (_deck.html); here it sits centred in the frame column, without the browser bar. */
|
|
31
|
+
.l-product .frame.card { justify-self: center; }
|
|
32
|
+
.l-product .frame.card .bar { display: none; }
|
|
30
33
|
.l-product .frame.phone { width: 300px; justify-self: center; border-radius: 40px; padding: 12px; background: var(--ink); }
|
|
31
34
|
.l-product .frame.phone .bar { display: none; }
|
|
32
35
|
.l-product .frame.phone .shot { aspect-ratio: 9 / 19.5; border-radius: 28px; }
|
|
@@ -39,6 +42,7 @@ script: This is the product. [look: #__ID__-frame] Look at this part. [reveal: #
|
|
|
39
42
|
.l-product .bar i { width: 7px; height: 7px; }
|
|
40
43
|
.l-product .bar span { font-size: 9px; padding: 2px 8px; margin-left: 6px; }
|
|
41
44
|
.l-product div.shot { background-size: 30% 6%, 60% 2%, 50% 2%, 28% 40%, 28% 40%, 20% 100%; background-position: 26% 8%, 40% 18%, 36% 23%, 26% 70%, 62% 70%, 0 0; }
|
|
45
|
+
.l-product .frame.card { align-self: center; }
|
|
42
46
|
.l-product .callout { left: 10px; bottom: 10px; padding: 6px 12px; font-size: 12px; }
|
|
43
47
|
}
|
|
44
48
|
</style>
|