@hypersocial/decks-cli 0.9.9 → 0.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ <!--
2
+ layout: cover-poster
3
+ summary: A poster cover: the picture across the top, edge to edge, and the title below it on the slide's ground. The picture takes whatever height the title leaves, so a long title shortens the picture instead of covering it.
4
+ use: the first slide when the picture is a scene or a landscape that crops well to a wide strip. Its .art is a placeholder check fails on: put in a wide picture of the real subject (gen-image / generate_image, 16:9 or wider) as <img class="art">; keep the subject in the middle band, the strip crops top and bottom. Never move the title up onto the picture. Without art the title fills the slide.
5
+ slot: bottom-right
6
+ presenter: stage
7
+ nopresenter: The title and lead run the slide's full width instead of stopping short of the corner.
8
+ cues: [wave] at the hello, [look: h1] when the narration says the title.
9
+ script: Hi, [wave] this is __TITLE__. [look: h1] One idea, in a few minutes.
10
+ -->
11
+ <style>
12
+ /* The picture is a flexible strip in the slide's flow; the text below it keeps its natural height. */
13
+ .l-cover-poster { gap: 0; }
14
+ /* A brand logo (applyBrand puts it first in the slide) sits on the picture's top-left, out of the flow, so the strip still starts at the top edge. */
15
+ .l-cover-poster:has(> .art) > .brand-logo { position: absolute; top: 28px; left: 72px; }
16
+ .l-cover-poster .art { display: block; flex: 1 1 0; min-height: 240px; width: auto; margin: -64px -72px 0; object-fit: cover; border-bottom: 4px solid var(--ink); }
17
+ .l-cover-poster div.art { position: relative; background: color-mix(in srgb, var(--ink) 6%, var(--paper)) repeating-linear-gradient(135deg, color-mix(in srgb, var(--ink) 10%, transparent) 0 2px, transparent 2px 22px); }
18
+ .l-cover-poster div.art::after { content: "Art goes here"; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 14px 26px; border: 3px dashed var(--ink); border-radius: 16px; background: var(--paper); color: var(--ink); font: 700 24px var(--mono); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; }
19
+ .l-cover-poster .content { flex: none; display: flex; flex-direction: column; gap: 18px; padding: 40px 400px 0 0; }
20
+ .l-cover-poster .tt { font-size: min(var(--t-display), 104px); line-height: .92; }
21
+ .l-cover-poster:not(:has(> .art)) .content { flex: 1; justify-content: center; padding-top: 0; }
22
+ @media (max-width: 650px) {
23
+ .l-cover-poster .art { flex: none; height: 196px; min-height: 0; margin: -28px -20px 0; border-bottom-width: 2px; }
24
+ .l-cover-poster div.art::after { padding: 8px 14px; border-width: 2px; font-size: 13px; }
25
+ .l-cover-poster .content { gap: 12px; padding: 18px 0 0; }
26
+ .l-cover-poster:has(> .art) > .brand-logo { top: 12px; left: 20px; }
27
+ .l-cover-poster .tt { font-size: var(--t-display); }
28
+ }
29
+ /* No presenter (:root.hd-no-presenter: no face and no captions, deck_guide look): the corner is free. Phones keep this layout's rules. */
30
+ @media (min-width: 651px) {
31
+ :root.hd-no-presenter .l-cover-poster .content { padding-right: 0; }
32
+ }
33
+ </style>
34
+ <section class="slide l-cover-poster" id="__ID__" data-chapter="Hello" data-note="Title slide: no facts.">
35
+ <div class="art" role="presentation"></div>
36
+ <div class="content">
37
+ <div class="kicker">__SLUG__</div>
38
+ <h1 class="tt" data-title-mobile="__TITLE__">__TITLE__</h1>
39
+ <p class="lead">One sentence on what this deck is about.</p>
40
+ </div>
41
+ <div class="narration-slot"></div>
42
+ </section>
@@ -0,0 +1,31 @@
1
+ <!--
2
+ layout: cover-type
3
+ summary: A type-only cover: the title, big, on the theme's own ground and texture, with the presenter large in the corner as the visual. No picture to fill.
4
+ use: the first slide when there is no picture that shows the real subject yet, or the deck is about an idea more than a thing. Swap the ground (on-ink, on-accent, on-tint) and keep one signature texture (bg-corner or bg-mark) at most; the title stays clear of it. Nothing to fill: it passes check as scaffolded.
5
+ slot: bottom-right
6
+ presenter: stage
7
+ nopresenter: The title widens into the corner the presenter held.
8
+ cues: [wave] at the hello, [look: h1] when the narration says the title.
9
+ script: Hi, [wave] this is __TITLE__. [look: h1] One idea, in a few minutes.
10
+ -->
11
+ <style>
12
+ .l-cover-type .content { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 30px; max-width: 980px; }
13
+ .l-cover-type .tt { font-size: calc(var(--t-display) * 1.12); line-height: .88; letter-spacing: -.05em; }
14
+ .l-cover-type .lead { max-width: 24em; }
15
+ @media (max-width: 650px) {
16
+ .l-cover-type .content { justify-content: flex-start; gap: 16px; padding-top: 24px; }
17
+ .l-cover-type .tt { font-size: calc(var(--t-display) * 1.08); }
18
+ }
19
+ /* No presenter (:root.hd-no-presenter: no face and no captions, deck_guide look): the corner is free. Phones keep this layout's rules. */
20
+ @media (min-width: 651px) {
21
+ :root.hd-no-presenter .l-cover-type .content { max-width: 1240px; }
22
+ }
23
+ </style>
24
+ <section class="slide on-ink l-cover-type bg-corner" id="__ID__" data-chapter="Hello" data-presenter-size="l" data-note="Title slide: no facts.">
25
+ <div class="content">
26
+ <div class="kicker">__SLUG__</div>
27
+ <h1 class="tt" data-title-mobile="__TITLE__">__TITLE__</h1>
28
+ <p class="lead">One sentence on what this deck is about.</p>
29
+ </div>
30
+ <div class="narration-slot"></div>
31
+ </section>
@@ -1,29 +1,44 @@
1
1
  <!--
2
2
  layout: hero
3
- summary: The opening. One display-size line, a visual on the right, the presenter's hello.
4
- use: the first slide; a section opener. Its .art is a placeholder check fails on: put in a picture of the real subject (gen-image / generate_image) as <img class="art">, or delete the div and let the presenter be the visual.
3
+ summary: The opening, as a split cover: the title on the slide's own ground on the left, the picture in a clean frame top-right, the presenter's hello. Text never sits on the picture.
4
+ use: the first slide (the default cover); a section opener. Its .art is a placeholder check fails on: put in a picture of the real subject (gen-image / generate_image, landscape or near-square: the frame is about 7:6) as <img class="art">, or delete the div and the title takes the full frame with the presenter as the visual. Never lay the title over the picture or fade the picture behind it. Other covers: cover-band (the picture full bleed, the title on a solid panel), cover-poster (the picture on top, the title below) and cover-type (type only).
5
5
  slot: bottom-right
6
6
  presenter: stage
7
- nopresenter: With art (the placeholder div counts), unchanged: the art already fills the right. Delete the art and the line re-centres in the full frame.
7
+ nopresenter: With art, the picture grows to the full height of its column. Delete the art and the line re-centres in the full frame.
8
8
  cues: [wave] at the hello, [look: h1] when the narration says the title.
9
9
  script: Hi, [wave] this is __TITLE__. [look: h1] One idea, in a few minutes.
10
10
  -->
11
11
  <style>
12
- .l-hero { overflow: hidden; }
13
- .l-hero .content { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 28px; max-width: 980px; position: relative; z-index: 1; }
12
+ /* The title and the picture are two columns: whatever the title's length, it never reaches the picture. */
13
+ .l-hero .content { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 28px; max-width: 980px; }
14
14
  .l-hero .tt { font-size: var(--t-display); line-height: .9; }
15
- /* Swap the div for an <img class="art"> of your file (gen-image, wide), or delete it for no art. */
16
- .l-hero .art { position: absolute; top: 0; right: 0; width: 820px; height: 100%; object-fit: cover; -webkit-mask-image: linear-gradient(to right, transparent, #000 32%); mask-image: linear-gradient(to right, transparent, #000 32%); }
15
+ /* With art: the title on the left on the slide's ground; the picture framed top-right, the presenter's corner below it. */
16
+ .l-hero:has(> .art) { display: grid; grid-template-columns: minmax(0, 1fr) 560px; grid-template-rows: minmax(0, 1fr); column-gap: 72px; }
17
+ .l-hero:has(> .art) .content { grid-column: 1; grid-row: 1; max-width: none; }
18
+ .l-hero:has(> .art) .tt { font-size: min(var(--t-display), 112px); }
19
+ /* A brand logo (applyBrand puts it first in the slide) heads the title column; the picture spans both rows. */
20
+ .l-hero:has(> .art):has(> .brand-logo) { grid-template-rows: auto minmax(0, 1fr); }
21
+ .l-hero:has(> .art) > .brand-logo { grid-column: 1; grid-row: 1; }
22
+ .l-hero:has(> .art):has(> .brand-logo) .content { grid-row: 2; }
23
+ .l-hero:has(> .art):has(> .brand-logo) .art { grid-row: 1 / -1; }
24
+ /* Swap the div for an <img class="art"> of your file (gen-image, landscape or square), or delete it for no art. */
25
+ .l-hero .art { grid-column: 2; grid-row: 1; align-self: start; display: block; width: 100%; height: 480px; margin-top: 8px; border: 4px solid var(--ink); border-radius: 28px; object-fit: cover; box-shadow: 14px 14px 0 var(--ink); }
26
+ .on-ink.l-hero .art { border-color: var(--paper); box-shadow: 14px 14px 0 var(--accent); }
17
27
  /* Until then a labelled, hatched stand-in, so it reads as a slot to fill; check fails while it is here. */
18
- .l-hero div.art { background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--ink) 10%, transparent) 0 2px, transparent 2px 22px); }
19
- .l-hero div.art::after { content: "Art goes here"; position: absolute; left: 64%; top: 40%; transform: translate(-50%, -50%); padding: 14px 26px; border: 3px dashed var(--ink); border-radius: 16px; background: var(--paper); color: var(--ink); font: 700 24px var(--mono); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; }
28
+ .l-hero div.art { position: relative; background: var(--paper) repeating-linear-gradient(135deg, color-mix(in srgb, var(--ink) 10%, transparent) 0 2px, transparent 2px 22px); }
29
+ .l-hero div.art::after { content: "Art goes here"; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 14px 26px; border: 3px dashed var(--ink); border-radius: 16px; background: var(--paper); color: var(--ink); font: 700 24px var(--mono); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; }
20
30
  @media (max-width: 650px) {
21
- .l-hero .content { justify-content: flex-start; gap: 16px; padding-top: 200px; }
22
- .l-hero .art { width: 100%; height: 230px; -webkit-mask-image: linear-gradient(to bottom, #000 60%, transparent); mask-image: linear-gradient(to bottom, #000 60%, transparent); }
23
- .l-hero div.art::after { left: 50%; top: 45%; padding: 8px 14px; border-width: 2px; font-size: 13px; }
31
+ /* Phones: the picture above, the title below it on the ground. */
32
+ .l-hero:has(> .art) { display: flex; }
33
+ .l-hero:has(> .art) .tt { font-size: var(--t-display); }
34
+ .l-hero .content { flex: none; justify-content: flex-start; gap: 16px; }
35
+ .l-hero .art { flex: none; height: 176px; margin-top: 0; border-width: 2px; border-radius: 16px; box-shadow: 6px 6px 0 var(--ink); }
36
+ .on-ink.l-hero .art { box-shadow: 6px 6px 0 var(--accent); }
37
+ .l-hero div.art::after { padding: 8px 14px; border-width: 2px; font-size: 13px; }
24
38
  }
25
39
  /* No presenter (:root.hd-no-presenter: no face and no captions, deck_guide look): the corner is free. Phones keep this layout's rules. */
26
40
  @media (min-width: 651px) {
41
+ :root.hd-no-presenter .l-hero:has(> .art) .art { align-self: stretch; height: auto; margin-top: 0; }
27
42
  :root.hd-no-presenter .l-hero:not(:has(> .art)) .content { align-self: center; align-items: center; text-align: center; max-width: 1200px; }
28
43
  }
29
44
  </style>
@@ -1,6 +1,6 @@
1
1
  <!--
2
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.
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. The row stands on a dot plate (bg-stage).
4
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
5
  slot: bottom-left
6
6
  presenter: stage
@@ -18,7 +18,7 @@ script: Here is the app on a phone. [look: #__ID__-row] You start here, [go: #__
18
18
  .l-phones .row { grid-area: row; display: flex; justify-content: center; align-items: center; padding: 0 0 40px; }
19
19
  .l-phones .row > .frame.card { --tilt: 0deg; flex: none; width: 260px; margin: 0 -14px; }
20
20
  .l-phones .row > .frame.card:first-child { --tilt: -7deg; --shadow: var(--accent-2); translate: 0 36px; }
21
- .l-phones .row > .frame.card:last-child { --tilt: 7deg; --shadow: var(--accent-3); translate: 0 36px; }
21
+ .l-phones .row > .frame.card:last-child { --tilt: 7deg; --shadow: var(--accent-3, var(--accent-2)); translate: 0 36px; }
22
22
  .l-phones .row > .frame.card:not(:first-child, :last-child) { width: 300px; z-index: 1; }
23
23
  .l-phones .row:has(> :nth-child(4)) > .frame.card { width: 220px; }
24
24
  .l-phones .row:has(> :nth-child(4)) > .frame.card:not(:first-child, :last-child) { width: 250px; }
@@ -31,14 +31,14 @@ script: Here is the app on a phone. [look: #__ID__-row] You start here, [go: #__
31
31
  .l-phones .row > .frame.card:is(:first-child, :last-child) { translate: 0 16px; }
32
32
  /* Four do not fit the phone card: the row keeps the first three, the third as its end. */
33
33
  .l-phones .row > .frame.card:nth-child(n + 4) { display: none; }
34
- .l-phones .row:has(> :nth-child(4)) > .frame.card:nth-child(3) { --tilt: 7deg; --shadow: var(--accent-3); width: 108px; translate: 0 16px; }
34
+ .l-phones .row:has(> :nth-child(4)) > .frame.card:nth-child(3) { --tilt: 7deg; --shadow: var(--accent-3, var(--accent-2)); width: 108px; translate: 0 16px; }
35
35
  }
36
36
  /* No presenter (:root.hd-no-presenter: no face and no captions, deck_guide look): the corner is free. Phones keep this layout's rules. */
37
37
  @media (min-width: 651px) {
38
38
  :root.hd-no-presenter .l-phones .text { align-self: center; }
39
39
  }
40
40
  </style>
41
- <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.">
41
+ <section class="slide l-phones slot-left bg-stage" id="__ID__" data-chapter="On a phone" data-note="Phone screens: name the app build and the date they were captured.">
42
42
  <div class="text">
43
43
  <div class="kicker">On a phone</div>
44
44
  <h2 class="tt" data-title-mobile="Three taps.">Three taps, start to finish.</h2>
@@ -53,7 +53,7 @@ script: Here is the app on a phone. [look: #__ID__-row] You start here, [go: #__
53
53
  <div class="deco" aria-hidden="true">
54
54
  <span style="left: 52%; top: 8%; background: var(--accent); rotate: -10deg;">★</span>
55
55
  <span style="left: 92%; top: 42%; background: var(--accent-2); rotate: 12deg;">♪</span>
56
- <span style="left: 48%; top: 78%; background: var(--accent-3); rotate: 8deg;">☺</span>
56
+ <span style="left: 48%; top: 78%; background: var(--accent-3, var(--accent-2)); rotate: 8deg;">☺</span>
57
57
  </div> -->
58
58
  <div class="narration-slot"></div>
59
59
  </section>
@@ -1,6 +1,6 @@
1
1
  <!--
2
2
  layout: split
3
- summary: Image plus copy: the copy on the left (a sticker for the logo or icon, kicker, title, one line, three ticks), a real picture on the right in a phone card, on speed lines.
3
+ summary: Image plus copy: the copy on the left (a sticker for the logo or icon, kicker, title, one line, three ticks), a real picture on the right in a phone card (the picture is the background; add bg-pool to light it).
4
4
  use: the workhorse for a product, a feature or an app: most slides of a good deck are this one. The phone's .shot is a placeholder check fails on: put in a phone screenshot (capture_url at 390×844) or a screen recording (upload_deck_asset) as <img class="shot"> or <video class="shot" autoplay muted loop playsinline>. For a desktop app, replace the card with <div class="frame laptop" id="__ID__-frame"><img class="shot" src="…" alt="…"></div>; for a 16:9 clip or photo, <img class="shot wide" src="…" alt="…" id="__ID__-frame">. Swap the sticker's icon for the product's logo (<span class="sticker photo"><img src="…" alt=""></span>) or one icon from the deck's set.
5
5
  slot: bottom-left
6
6
  presenter: stage
@@ -9,13 +9,13 @@ cues: [look: #__ID__-frame] when the narration turns to the picture; [go: #__ID_
9
9
  script: Here it is, doing the job. [look: #__ID__-frame] It starts with [go: #__ID__-t1] the first thing. Then [go: #__ID__-t2] the second. And [go: #__ID__-t3] the third. [go: slot]
10
10
  -->
11
11
  <style>
12
- .l-split { display: grid; grid-template-columns: 680px 1fr; column-gap: 64px; align-items: center; }
12
+ .l-split { display: grid; grid-template-columns: 680px minmax(0, 1fr); column-gap: 64px; align-items: center; }
13
13
  .l-split .text { align-self: start; display: flex; flex-direction: column; gap: 22px; padding-top: 24px; }
14
14
  .l-split .head { display: flex; align-items: center; gap: 22px; }
15
15
  .l-split .tt { font-size: var(--t-sub); line-height: 1.02; }
16
16
  .l-split .lead { font-size: var(--t-body); }
17
17
  .l-split .ticks { margin-top: 6px; }
18
- .l-split .media { display: grid; place-items: center; height: 100%; }
18
+ .l-split .media { display: grid; grid-template-columns: minmax(0, 1fr); place-items: center; height: 100%; min-width: 0; }
19
19
  .l-split .frame.card { width: 300px; --tilt: 4deg; }
20
20
  .l-split .shot.wide { display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: 18px; object-fit: cover; box-shadow: 14px 14px 0 var(--accent); }
21
21
  .on-accent.l-split .shot.wide { box-shadow: 14px 14px 0 var(--ink); }
@@ -32,7 +32,7 @@ script: Here it is, doing the job. [look: #__ID__-frame] It starts with [go: #__
32
32
  :root.hd-no-presenter .l-split .text { align-self: center; padding-top: 0; }
33
33
  }
34
34
  </style>
35
- <section class="slide l-split slot-left bg-lines" id="__ID__" data-chapter="The product" data-note="Picture: name the screen or clip and the date it was captured.">
35
+ <section class="slide l-split slot-left" id="__ID__" data-chapter="The product" data-note="Picture: name the screen or clip and the date it was captured.">
36
36
  <div class="text">
37
37
  <div class="head">
38
38
  <span class="sticker" id="__ID__-logo"><svg class="lucide-smartphone" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg></span>
@@ -7,7 +7,8 @@
7
7
  <!-- Loading state (keep verbatim; `hyperdecks check` requires it). Until the
8
8
  player marks body[data-hd-ready] the page shows only the slide card, at its
9
9
  final size, in the deck's --hd-ground (its first slide's colour) on the
10
- page's brand colour, with a quiet loader in --hd-ground-ink; then the
10
+ page's brand colour, with the opening card over it (the title and the
11
+ presenter's still), or in a frame quiet dots in --hd-ground-ink; then the
11
12
  slides fade in. If the player never arrives, the slides show after 10 s
12
13
  rather than never. Put data-layout="overlay" on <html> for the overlay. -->
13
14
  <link rel="modulepreload" href="https://hyperdecks.ai/player/v1/player.js">
@@ -21,7 +22,7 @@
21
22
  /* The card as the player draws it (player.css, framed): 16:9, scaled to fit
22
23
  between the 64px masthead and the 56px dock, 24px in from the sides. */
23
24
  body::before { inset: 64px 24px 56px; margin: auto; width: min(100vw - 48px, (100dvh - 120px) * 16 / 9); height: min(100dvh - 120px, (100vw - 48px) * 9 / 16); border-radius: calc(min(100vw - 48px, (100dvh - 120px) * 16 / 9) * .01125); background: var(--hd-ground, #fff); box-shadow: 0 0 0 1px color-mix(in srgb, var(--hd-ground-ink, #0b0b0f) 16%, transparent); }
24
- body::after { left: 50%; top: 50%; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 50%; color: var(--hd-ground-ink, #0b0b0f); background: currentColor; box-shadow: -24px 0 currentColor, 24px 0 currentColor; opacity: 0; animation: hd-dots .7s ease-in-out .6s infinite alternate; }
25
+ [data-hd-frame] body::after { left: 50%; top: 50%; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 50%; color: var(--hd-ground-ink, #0b0b0f); background: currentColor; box-shadow: -24px 0 currentColor, 24px 0 currentColor; opacity: 0; animation: hd-dots .7s ease-in-out .6s infinite alternate; }
25
26
  body[data-hd-ready]::before, body[data-hd-ready]::after { opacity: 0; animation: none; }
26
27
  /* The overlay (embedded, ?thumb, or data-layout="overlay"): the stage fills the window. */
27
28
  :is([data-hd-frame], [data-layout="overlay"]) body::before { inset: 0; width: min(100vw, 100dvh * 16 / 9); height: min(100dvh, 100vw * 9 / 16); border-radius: calc(min(100vw, 100dvh * 16 / 9) * .01125); box-shadow: none; }
@@ -41,7 +42,75 @@
41
42
  @keyframes hd-in { from { opacity: 0; } }
42
43
  @keyframes hd-dots { from { opacity: .15; } to { opacity: .6; } }
43
44
  @keyframes hd-fail-open { to { visibility: visible; overflow: auto; } }
45
+ #hd-open{position:fixed;inset:0;z-index:2147483000;pointer-events:none;visibility:visible!important;animation:none!important;opacity:0;font-family:var(--hd-ui-font,"Space Grotesk",system-ui,sans-serif);color:#0b0b0f}
46
+ #hd-open.is-shown{opacity:1;transition:opacity .2s ease-out}
47
+ #hd-open.is-gone{opacity:0;transition:opacity .45s ease-out}
48
+ #hd-open .hdo-slide{position:absolute;inset:64px 24px 56px;margin:auto;width:min(100vw - 48px,(100dvh - 120px)*16/9);height:min(100dvh - 120px,(100vw - 48px)*9/16);border-radius:calc(min(100vw - 48px,(100dvh - 120px)*16/9)*.01125);overflow:hidden;container-type:inline-size;background:var(--hd-ground,#fff)}
49
+ #hd-open .hdo-slide img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;filter:blur(2.2cqi) saturate(1.15);transform:scale(1.12);animation:hdo-breathe 2.4s ease-in-out infinite}
50
+ #hd-open .hdo-slide::after{content:"";position:absolute;inset:0;background:linear-gradient(100deg,transparent 30%,rgba(255,255,255,.3) 50%,transparent 70%);background-size:250% 100%;animation:hdo-shim 1.8s ease-in-out infinite;mix-blend-mode:soft-light}
51
+ #hd-open.is-done .hdo-slide img{animation:hdo-deblur .5s cubic-bezier(.2,.8,.2,1) .5s forwards}
52
+ #hd-open.is-done .hdo-slide::after{opacity:0;transition:opacity .3s}
53
+ .hdo-card{position:absolute;left:50%;top:50%;translate:-50% -50%;width:min(86vw,420px);display:grid;justify-items:center;gap:.6em;padding:1.3em 1.5em 1.2em;border-radius:22px;background:rgba(255,255,255,.74);-webkit-backdrop-filter:blur(14px) saturate(1.3);backdrop-filter:blur(14px) saturate(1.3);box-shadow:4px 4px 0 #0b0b0f;border:1.5px solid #0b0b0f;text-align:center;box-sizing:border-box}
54
+ #hd-open.is-done .hdo-card{opacity:0;transform:scale(.96);transition:opacity .25s .5s,transform .25s .5s}
55
+ .hdo-buddy{position:relative;width:92px;height:96px;margin-top:-62px;display:grid;justify-items:center}
56
+ .hdo-hop{width:86px;height:80px;transform-origin:50% 100%;animation:hdo-hop 1.15s cubic-bezier(.3,0,.4,1) infinite}
57
+ .hdo-look{width:100%;height:100%;animation:hdo-look 4.6s ease-in-out infinite}
58
+ .hdo-buddy img{width:100%;height:100%;object-fit:contain;display:block;filter:drop-shadow(0 3px 0 rgba(11,11,15,.18))}
59
+ .hdo-shadow{position:absolute;bottom:4px;width:46px;height:8px;border-radius:50%;background:rgba(11,11,15,.22);animation:hdo-shadow 1.15s cubic-bezier(.3,0,.4,1) infinite}
60
+ .hdo-think{position:absolute;right:-30px;top:-6px;display:flex;gap:3px;padding:5px 8px;border-radius:14px;background:#fff;border:1.5px solid #0b0b0f;box-shadow:2px 2px 0 #0b0b0f}
61
+ .hdo-think::after{content:"";position:absolute;left:4px;bottom:-7px;width:7px;height:7px;border-radius:50%;background:#fff;border:1.5px solid #0b0b0f}
62
+ .hdo-think i{width:5px;height:5px;border-radius:50%;background:#0b0b0f;animation:hdo-tdot 1s ease-in-out infinite}
63
+ .hdo-think i:nth-child(2){animation-delay:.15s}.hdo-think i:nth-child(3){animation-delay:.3s}
64
+ .hdo-spark{position:absolute;left:50%;top:40%;width:8px;height:8px;margin:-4px;opacity:0}
65
+ .hdo-spark::before{content:"";position:absolute;inset:0;background:#0b0b0f;clip-path:polygon(50% 0,62% 38%,100% 50%,62% 62%,50% 100%,38% 62%,0 50%,38% 38%)}
66
+ #hd-open.is-done .hdo-hop{animation:hdo-yay .55s cubic-bezier(.2,1.4,.4,1) forwards}
67
+ #hd-open.is-done :is(.hdo-look,.hdo-shadow){animation:none}
68
+ #hd-open.is-done .hdo-think{opacity:0;transition:opacity .15s}
69
+ #hd-open.is-done .hdo-spark{animation:hdo-spark .6s ease-out forwards}
70
+ .hdo-title{font-weight:700;font-size:18px;line-height:1.2;text-wrap:balance;overflow-wrap:anywhere}
71
+ .hdo-by{font:12.5px/1.3 var(--hd-ui-mono,"JetBrains Mono",ui-monospace,monospace);color:#3d3b35}
72
+ .hdo-bar{width:100%;height:5px;border-radius:999px;background:rgba(11,11,15,.12);overflow:hidden}
73
+ .hdo-bar i{display:block;height:100%;width:8%;background:#0b0b0f;border-radius:999px;transition:width .35s ease-out}
74
+ .hdo-step{font:12px/1.3 var(--hd-ui-mono,"JetBrains Mono",ui-monospace,monospace);color:#55534b;min-height:1.3em}
75
+ @keyframes hdo-breathe{50%{filter:blur(2.6cqi) saturate(1.25) brightness(1.05)}}
76
+ @keyframes hdo-shim{0%{background-position:120% 0}100%{background-position:-120% 0}}
77
+ @keyframes hdo-deblur{to{filter:blur(0);transform:none}}
78
+ @keyframes hdo-hop{0%,100%{transform:translateY(0) scale(1.06,.92)}12%{transform:translateY(0) scale(.95,1.06)}45%{transform:translateY(-14px) scale(1,1)}70%{transform:translateY(0) scale(1.04,.95)}82%{transform:translateY(0) scale(1,1)}}
79
+ @keyframes hdo-shadow{0%,100%{transform:scale(1.05);opacity:.9}45%{transform:scale(.62);opacity:.45}70%{transform:scale(1);opacity:.85}}
80
+ @keyframes hdo-look{0%,18%{transform:rotate(0)}24%,38%{transform:rotate(-9deg) translateX(-2px)}44%,58%{transform:rotate(8deg) translateX(2px)}64%,100%{transform:rotate(0)}}
81
+ @keyframes hdo-tdot{0%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-2px)}}
82
+ @keyframes hdo-yay{0%{transform:translateY(0) scale(1.08,.9)}40%{transform:translateY(-26px) scale(.94,1.08) rotate(-6deg)}100%{transform:translateY(-6px) scale(1) rotate(0)}}
83
+ @keyframes hdo-spark{0%{opacity:0;transform:translate(0,0) scale(.4)}30%{opacity:1}100%{opacity:0;transform:translate(var(--x),var(--y)) scale(1.2) rotate(90deg)}}
84
+ :is([data-hd-frame],[data-layout="overlay"]) #hd-open .hdo-slide{inset:0;width:min(100vw,100dvh*16/9);height:min(100dvh,100vw*9/16)}
85
+ @media (max-width:650px){#hd-open .hdo-slide{inset:52px 8px calc(56px + env(safe-area-inset-bottom));width:auto;height:auto;border-radius:14px}.hdo-card{width:min(84vw,320px);padding:1em 1.1em .95em}.hdo-title{font-size:15.5px}.hdo-buddy{transform:scale(.85);margin-top:-58px}}
86
+ @media (prefers-reduced-motion:reduce){#hd-open *,#hd-open *::before,#hd-open *::after{animation:none!important;transition:none!important}#hd-open .hdo-slide::after{display:none}#hd-open.is-done .hdo-card{opacity:1;transform:none}}
87
+ @media print{#hd-open{display:none!important}}
44
88
  </style>
89
+ <script data-hd-opening>(function(){var d=document;
90
+ if(self!==top||/[?&](thumb|export)\b/.test(location.search))return;
91
+ function go(){if(d.readyState==="loading")return;d.removeEventListener("readystatechange",go);
92
+ var b=d.body,p=d.querySelector('script[src*="/player/v"][src*="/player.js"]');
93
+ if(!b||!p||d.getElementById("hd-open")||b.hasAttribute("data-hd-ready")||d.documentElement.hasAttribute("data-hyperdecks-thumb"))return;
94
+ var o=d.createElement("div"),img=p.getAttribute("data-presenter")!=="none"&&p.getAttribute("data-presenter-image");
95
+ o.id="hd-open";o.setAttribute("aria-hidden","true");
96
+ o.innerHTML='<div class="hdo-slide"></div><div class="hdo-card">'+(img?"<div class=\"hdo-buddy\"><div class=\"hdo-hop\"><div class=\"hdo-look\"><img src=\"\" alt=\"\"></div></div><div class=\"hdo-shadow\"></div><div class=\"hdo-think\"><i></i><i></i><i></i></div><span class=\"hdo-spark\" style=\"--x:-40px;--y:-30px\"></span><span class=\"hdo-spark\" style=\"--x:42px;--y:-34px\"></span><span class=\"hdo-spark\" style=\"--x:-30px;--y:10px\"></span><span class=\"hdo-spark\" style=\"--x:36px;--y:6px\"></span><span class=\"hdo-spark\" style=\"--x:0px;--y:-46px\"></span></div>":'')+'<div class="hdo-title"></div><div class="hdo-bar"><i></i></div><div class="hdo-step"></div></div>';
97
+ if(img){var i=o.querySelector(".hdo-buddy img");i.addEventListener("error",function(){var y=o.querySelector(".hdo-buddy");if(y)y.remove()});i.src=img}
98
+ o.querySelector(".hdo-title").textContent=(d.title||"").replace(/\s+/g," ").trim();
99
+ b.insertBefore(o,b.firstChild);(function(o){var b=document.body,d=document.documentElement;if(!o||!b)return;
100
+ if(self!==top||/[?&](thumb|export)\b/.test(location.search)||d.hasAttribute("data-hyperdecks-thumb")){o.remove();return}
101
+ var S={"page":[10,"Opening the deck…"],"player":[38,"Loading the player…"],"presenter":[62,"Waking up {name}…"],"narration":[84,"Getting the narration…"]},bar=o.querySelector(".hdo-bar i"),step=o.querySelector(".hdo-step"),who=o.getAttribute("data-name")||"the presenter",at=0,shown=false,done=false,mo;
102
+ var rm=window.matchMedia&&matchMedia("(prefers-reduced-motion: reduce)").matches;
103
+ function boot(){var s=S[b.getAttribute("data-hd-boot")];if(!s||s[0]<=at)return;at=s[0];bar.style.width=s[0]+"%";step.textContent=s[1].split("{name}").join(who)}
104
+ function finish(){if(done)return;done=true;if(mo)mo.disconnect();clearTimeout(t1);clearTimeout(t2);
105
+ if(!shown){o.remove();return}bar.style.width="100%";step.textContent="Ready";
106
+ o.classList.add("is-done");setTimeout(function(){o.classList.add("is-gone")},rm?0:500);setTimeout(function(){o.remove()},rm?500:1000)}
107
+ function check(){if(b.hasAttribute("data-hd-ready"))finish();else boot()}
108
+ var t1=setTimeout(function(){if(!done){shown=true;o.classList.add("is-shown")}},150);
109
+ var t2=setTimeout(finish,10000);
110
+ step.textContent=S.page[1];
111
+ if(window.MutationObserver){mo=new MutationObserver(check);mo.observe(b,{attributes:true,attributeFilter:["data-hd-ready","data-hd-boot"]})}
112
+ check()})(o)}
113
+ d.addEventListener("readystatechange",go);go()})();</script>
45
114
  <link rel="preconnect" href="https://fonts.googleapis.com">
46
115
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
47
116
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=Space+Mono:wght@400;700&display=swap">
@@ -54,7 +123,7 @@
54
123
  .slide { display: flex; flex-direction: column; gap: 28px; padding: 64px 72px 56px; background: var(--paper); }
55
124
  .slide.accent { background: var(--accent); }
56
125
  .kicker { font: 18px var(--mono); letter-spacing: .16em; text-transform: uppercase; }
57
- .tt { font-family: var(--display, var(--font)); font-size: 104px; line-height: .95; letter-spacing: -.045em; margin: 0; text-wrap: balance; }
126
+ .tt { font-family: var(--display, var(--font)); font-weight: var(--display-weight, 700); font-synthesis: none; font-size: 104px; line-height: .95; letter-spacing: var(--display-tracking, -.045em); margin: 0; text-wrap: balance; }
58
127
  .lead { font-size: 32px; line-height: 1.3; max-width: 30em; margin: 0; }
59
128
  .content { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 28px; padding-right: 500px; }
60
129
  /* The presenter glides to each slide's slot; slots alternate sides so the
@@ -156,6 +225,8 @@
156
225
  <!-- `hyperdecks narrate decks/__SLUG__` fills data-manifest with the manifest
157
226
  URL it writes. data-presenter: head (live Minimoji, needs
158
227
  data-avatar), static (data-presenter-image) or none (captions only).
228
+ deck new, deck narrator and narrate write the narrator's face here
229
+ (data-presenter-from="narrator", so release follows a later narrator).
159
230
  data-presenter-style: stage (large head, lower-third captions), bubble,
160
231
  glass (frosted panel) or corner (small head, subtitles). -->
161
232
  <script type="module" src="https://hyperdecks.ai/player/v1/player.js"