fb-slides 0.5.1 → 0.6.2
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/README.md +74 -0
- package/lib/config.mjs +4 -0
- package/lib/render.mjs +1 -0
- package/package.json +1 -1
- package/runtime/annotate.js +11 -1
- package/runtime/deck.js +52 -0
- package/runtime/edit.js +76 -14
- package/runtime/pointer.js +9 -1
- package/runtime/snippets.js +313 -0
- package/runtime/spotlight.js +9 -1
- package/runtime/theme.base.css +349 -30
- package/templates/starter/assets/author/photo-1.svg +11 -0
- package/templates/starter/assets/author/photo-2.svg +11 -0
- package/templates/starter/decks/01-intro.md +22 -0
- package/templates/starter/decks/02-demos.md +33 -6
- package/templates/starter/theme.css +0 -11
package/runtime/theme.base.css
CHANGED
|
@@ -310,9 +310,9 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
310
310
|
/* Full-bleed: this one is the demo, not a slide about the demo. */
|
|
311
311
|
margin: 0;
|
|
312
312
|
box-sizing: border-box;
|
|
313
|
-
/* The
|
|
314
|
-
|
|
315
|
-
66px of viewport clears
|
|
313
|
+
/* The arrows and signature live in fixed viewport space along the bottom,
|
|
314
|
+
where a full-height slide runs under them. Reserve that strip:
|
|
315
|
+
66px of viewport clears them (bottom 14px + 44px of chrome + air), minus
|
|
316
316
|
the letterbox gap the slide already has, converted to slide space through
|
|
317
317
|
reveal's own scale. On tall windows the gap alone is enough and this
|
|
318
318
|
resolves to zero. */
|
|
@@ -354,6 +354,170 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
354
354
|
box-shadow: 0 18px 45px rgb(0 0 0 / 0.45);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
+
/* --- the slide vocabulary ------------------------------------------------ */
|
|
358
|
+
/* The classes a slide's Markdown can reach for, so the common layouts are a
|
|
359
|
+
word rather than a paragraph of inline styles. Every one of them is layout
|
|
360
|
+
only — no content, no colours of its own — and every number it fixes is a
|
|
361
|
+
token, so a project retunes it from its own theme.css instead of copying
|
|
362
|
+
this CSS. The edit drawer's `+` inserts the markup for each (snippets.js). */
|
|
363
|
+
|
|
364
|
+
/* Images and clips are centred, and lose the white card reveal's core CSS
|
|
365
|
+
gives every image: a screenshot on a dark slide wants no frame around it. */
|
|
366
|
+
.reveal .slides section img {
|
|
367
|
+
display: block;
|
|
368
|
+
max-width: 100%;
|
|
369
|
+
margin: 0.4em auto;
|
|
370
|
+
background: none;
|
|
371
|
+
border: 0;
|
|
372
|
+
border-radius: 10px;
|
|
373
|
+
}
|
|
374
|
+
.reveal .slides section video {
|
|
375
|
+
display: block;
|
|
376
|
+
max-width: 100%;
|
|
377
|
+
margin: 0.4em auto;
|
|
378
|
+
border-radius: 10px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* Two columns. `min-width: 0` on the children is the whole point: a flex item
|
|
382
|
+
is min-content wide by default, and a <pre> refuses to shrink — one code
|
|
383
|
+
block and the pair walks off the slide. */
|
|
384
|
+
.reveal .slides .cols {
|
|
385
|
+
display: flex;
|
|
386
|
+
gap: var(--cols-gap, 2rem);
|
|
387
|
+
align-items: var(--cols-align, flex-start);
|
|
388
|
+
}
|
|
389
|
+
/* Any direct child is a column; `style="flex: 1.4"` on one still tips the
|
|
390
|
+
balance, because the shorthand here sets the same property it does. */
|
|
391
|
+
.reveal .slides .cols > * { flex: 1 1 0; min-width: 0; }
|
|
392
|
+
|
|
393
|
+
/* An embedded page that is not the whole slide. The height is in px, not vh:
|
|
394
|
+
the slide is a fixed 1280x720 box reveal scales as a whole, so a viewport
|
|
395
|
+
unit here would size against the window instead of against the slide. */
|
|
396
|
+
.reveal .slides .frame { display: flex; justify-content: center; }
|
|
397
|
+
.reveal .slides .frame iframe {
|
|
398
|
+
width: var(--frame-w, min(860px, 100%));
|
|
399
|
+
height: var(--frame-h, 500px);
|
|
400
|
+
border: 0;
|
|
401
|
+
border-radius: 10px;
|
|
402
|
+
/* Most embeds assume a white page under them and draw nothing of their own. */
|
|
403
|
+
background: var(--frame-bg, #fff);
|
|
404
|
+
box-shadow: 0 18px 40px rgb(0 0 0 / 0.35);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* A line under a figure, or an aside that should not carry the same weight as
|
|
408
|
+
the sentence above it. */
|
|
409
|
+
.reveal .slides .caption {
|
|
410
|
+
margin: 0.4em 0 0;
|
|
411
|
+
font-size: 0.8em;
|
|
412
|
+
line-height: 1.5;
|
|
413
|
+
color: var(--muted);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* A bordered block: the point a slide keeps coming back to. */
|
|
417
|
+
.reveal .slides .box {
|
|
418
|
+
padding: 0.7em 1.05em;
|
|
419
|
+
border: 1px solid var(--box-line, var(--line));
|
|
420
|
+
border-radius: 10px;
|
|
421
|
+
background: color-mix(in srgb, var(--card) 55%, transparent);
|
|
422
|
+
}
|
|
423
|
+
.reveal .slides .box > :last-child { margin-bottom: 0; }
|
|
424
|
+
|
|
425
|
+
/* --- the author slide ---------------------------------------------------- */
|
|
426
|
+
/* `<!-- .slide: class="author-slide" -->` turns a slide into the speaker page:
|
|
427
|
+
photos filling the left edge to edge, the bio centred on the right. Who you
|
|
428
|
+
are stays in the Markdown — this only lays it out. */
|
|
429
|
+
|
|
430
|
+
.reveal .slides section.author-slide {
|
|
431
|
+
/* Reveal keeps a safe band around the 1280x720 slide box (`margin: 0.055`).
|
|
432
|
+
The photo bleeds through it with negative margins — `.reveal` is
|
|
433
|
+
`overflow: hidden`, so whatever spills past the viewport is clipped.
|
|
434
|
+
Sized with slack for 16:9 up to ~2:1 screens; overshooting only costs a
|
|
435
|
+
slightly tighter crop, never a visible seam. */
|
|
436
|
+
--author-bleed-y: 130px;
|
|
437
|
+
--author-bleed-x: 150px;
|
|
438
|
+
--author-photo: 40%;
|
|
439
|
+
--author-gap: 3rem;
|
|
440
|
+
/* Carousel timing: how long a photo holds, and how long the cross-fade is.
|
|
441
|
+
deck.js reads both to write the keyframes for this many photos. */
|
|
442
|
+
--author-hold: 3.5s;
|
|
443
|
+
--author-fade: 1.2s;
|
|
444
|
+
--author-slot: calc(var(--author-hold) + var(--author-fade));
|
|
445
|
+
/* One knob for the whole slide: every size below is in `em`, so bumping this
|
|
446
|
+
scales the title, the bullets and the muted lines together. */
|
|
447
|
+
font-size: 1.25em;
|
|
448
|
+
|
|
449
|
+
display: flex !important;
|
|
450
|
+
flex-direction: row;
|
|
451
|
+
align-items: stretch;
|
|
452
|
+
height: 100%;
|
|
453
|
+
margin: 0;
|
|
454
|
+
padding: 0;
|
|
455
|
+
gap: var(--author-gap);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.reveal .slides section.author-slide .author-photo {
|
|
459
|
+
flex: 0 0 calc(var(--author-photo) + var(--author-bleed-x));
|
|
460
|
+
margin: calc(var(--author-bleed-y) * -1) 0 calc(var(--author-bleed-y) * -1)
|
|
461
|
+
calc(var(--author-bleed-x) * -1);
|
|
462
|
+
position: relative;
|
|
463
|
+
overflow: hidden;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* The photos stack on top of each other and cross-fade in DOM order: each one
|
|
467
|
+
starts a slot after the one before, and its fade-in lands exactly on that
|
|
468
|
+
one's fade-out. `--i` and the keyframes come from deck.js, which knows how
|
|
469
|
+
many there are; the first photo is visible from the start, so a single one —
|
|
470
|
+
or a deck where that script never ran — still shows a face.
|
|
471
|
+
|
|
472
|
+
`.present` is reveal's class for the slide on screen: the animation is only
|
|
473
|
+
attached while the slide is being shown, so every visit starts the sequence
|
|
474
|
+
from the first photo instead of dropping in mid-rotation. */
|
|
475
|
+
.reveal .slides section.author-slide .author-photo img {
|
|
476
|
+
position: absolute;
|
|
477
|
+
inset: 0;
|
|
478
|
+
width: 100%;
|
|
479
|
+
height: 100%;
|
|
480
|
+
object-fit: cover;
|
|
481
|
+
object-position: var(--author-crop, 50% 35%);
|
|
482
|
+
display: block;
|
|
483
|
+
margin: 0;
|
|
484
|
+
border: 0;
|
|
485
|
+
border-radius: 0;
|
|
486
|
+
box-shadow: none;
|
|
487
|
+
opacity: 0;
|
|
488
|
+
}
|
|
489
|
+
.reveal .slides section.author-slide .author-photo img:first-child { opacity: 1; }
|
|
490
|
+
.reveal .slides section.present.author-slide .author-photo img {
|
|
491
|
+
animation-name: var(--author-anim, none);
|
|
492
|
+
animation-duration: calc(var(--author-n, 1) * var(--author-slot));
|
|
493
|
+
animation-delay: calc(var(--i, 0) * var(--author-slot));
|
|
494
|
+
animation-iteration-count: infinite;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.reveal .slides section.author-slide .author-bio {
|
|
498
|
+
flex: 1 1 auto;
|
|
499
|
+
display: flex;
|
|
500
|
+
flex-direction: column;
|
|
501
|
+
justify-content: center;
|
|
502
|
+
padding-right: 2rem;
|
|
503
|
+
min-width: 0;
|
|
504
|
+
}
|
|
505
|
+
.reveal .slides section.author-slide .author-bio > :last-child { margin-bottom: 0; }
|
|
506
|
+
.reveal .slides section.author-slide h1 { margin: 0 0 1.4rem; }
|
|
507
|
+
.reveal .slides section.author-slide ul { margin: 0; font-size: 0.8em; line-height: 1.75; }
|
|
508
|
+
/* The quiet lines under the bio — what you work with, where to find you. */
|
|
509
|
+
.reveal .slides section.author-slide .author-meta {
|
|
510
|
+
margin: 1.4rem 0 0;
|
|
511
|
+
color: var(--muted);
|
|
512
|
+
font-size: 0.58em;
|
|
513
|
+
line-height: 1.7;
|
|
514
|
+
}
|
|
515
|
+
.reveal .slides section.author-slide .author-meta + .author-meta { margin-top: 0.7rem; }
|
|
516
|
+
|
|
517
|
+
/* The corner badge names the deck a slide belongs to. The speaker page is
|
|
518
|
+
full-bleed and belongs to itself — the badge would sit on the photo. */
|
|
519
|
+
.reveal:has(section.present.author-slide) + #deck-badge { display: none; }
|
|
520
|
+
|
|
357
521
|
/* --- chrome -------------------------------------------------------------- */
|
|
358
522
|
|
|
359
523
|
.reveal .progress { color: var(--accent); height: 3px; }
|
|
@@ -462,14 +626,17 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
462
626
|
cursor: crosshair;
|
|
463
627
|
}
|
|
464
628
|
|
|
465
|
-
/*
|
|
466
|
-
|
|
467
|
-
|
|
629
|
+
/* Hidden until it is wanted: on stage the slide is the point, so the toolbar
|
|
630
|
+
sleeps just above the top edge of the screen. Parking the mouse along that
|
|
631
|
+
edge — or arming a tool, or tabbing into it — slides it back down. */
|
|
468
632
|
#deck-tools {
|
|
469
633
|
position: fixed;
|
|
470
|
-
|
|
634
|
+
top: 14px;
|
|
471
635
|
left: 50%;
|
|
472
|
-
|
|
636
|
+
/* Fully clear of the viewport, with a couple of pixels to spare for the
|
|
637
|
+
border. NOT hidden with opacity: that would take the ::before arrow —
|
|
638
|
+
the only piece meant to stay visible — down with it. */
|
|
639
|
+
transform: translateX(-50%) translateY(calc(-100% - 18px));
|
|
473
640
|
z-index: 40;
|
|
474
641
|
display: flex;
|
|
475
642
|
gap: 4px;
|
|
@@ -478,12 +645,58 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
478
645
|
border: 1px solid var(--line);
|
|
479
646
|
border-radius: 999px;
|
|
480
647
|
backdrop-filter: blur(6px);
|
|
481
|
-
|
|
648
|
+
transition: transform 0.22s ease;
|
|
649
|
+
}
|
|
650
|
+
/* The wake strip: one invisible hover surface reaching from the very top of
|
|
651
|
+
the screen, across the chip, to a short stretch below it. While the toolbar
|
|
652
|
+
is away, its lower reach pokes into the viewport as the thing to hover; on
|
|
653
|
+
the way down it keeps the reveal alive over every pixel the mouse crosses,
|
|
654
|
+
the 14px gap above the chip included, so the bar never bounces. */
|
|
655
|
+
#deck-tools::before {
|
|
656
|
+
content: '';
|
|
657
|
+
position: absolute;
|
|
658
|
+
top: -14px;
|
|
659
|
+
bottom: -26px;
|
|
660
|
+
left: -60px;
|
|
661
|
+
right: -60px;
|
|
662
|
+
cursor: pointer;
|
|
663
|
+
}
|
|
664
|
+
/* The hint: a chevron breathing just under the top edge while the toolbar is
|
|
665
|
+
away — the one clue that a menu lives up here. Drawn through a mask so it
|
|
666
|
+
stays crisp at any size and wears whatever the theme's muted colour is. */
|
|
667
|
+
#deck-tools::after {
|
|
668
|
+
content: '';
|
|
669
|
+
position: absolute;
|
|
670
|
+
top: calc(100% + 7px);
|
|
671
|
+
left: 50%;
|
|
672
|
+
width: 20px;
|
|
673
|
+
height: 11px;
|
|
674
|
+
transform: translateX(-50%);
|
|
675
|
+
background: var(--muted);
|
|
676
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 11'%3E%3Cpath d='M2 2l8 7 8-7' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
677
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 11'%3E%3Cpath d='M2 2l8 7 8-7' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
678
|
+
opacity: 0.8;
|
|
679
|
+
pointer-events: none;
|
|
680
|
+
animation: deck-tools-hint 2.6s ease-in-out infinite;
|
|
482
681
|
transition: opacity 0.18s ease;
|
|
483
682
|
}
|
|
683
|
+
@keyframes deck-tools-hint {
|
|
684
|
+
0%, 100% { transform: translateX(-50%) translateY(0); }
|
|
685
|
+
50% { transform: translateX(-50%) translateY(3px); }
|
|
686
|
+
}
|
|
484
687
|
#deck-tools:hover,
|
|
485
688
|
#deck-tools:focus-within,
|
|
486
|
-
#deck-tools.is-armed {
|
|
689
|
+
#deck-tools.is-armed {
|
|
690
|
+
transform: translateX(-50%) translateY(0);
|
|
691
|
+
}
|
|
692
|
+
/* Once the toolbar is out, the chevron has done its job — the strip stays as
|
|
693
|
+
a silent hover target. */
|
|
694
|
+
#deck-tools:hover::before,
|
|
695
|
+
#deck-tools:focus-within::before,
|
|
696
|
+
#deck-tools.is-armed::before { cursor: default; }
|
|
697
|
+
#deck-tools:hover::after,
|
|
698
|
+
#deck-tools:focus-within::after,
|
|
699
|
+
#deck-tools.is-armed::after { opacity: 0; }
|
|
487
700
|
|
|
488
701
|
.deck-tool {
|
|
489
702
|
position: relative;
|
|
@@ -537,11 +750,11 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
537
750
|
0 0 16px color-mix(in srgb, var(--pen, #ff8c1a) 55%, transparent);
|
|
538
751
|
}
|
|
539
752
|
|
|
540
|
-
/* The colours, floating
|
|
541
|
-
toolbar
|
|
753
|
+
/* The colours, floating below the toolbar while a tool is armed — the
|
|
754
|
+
toolbar hangs from the top edge, so down is where the room is. */
|
|
542
755
|
#deck-swatches {
|
|
543
756
|
position: absolute;
|
|
544
|
-
|
|
757
|
+
top: calc(100% + 9px);
|
|
545
758
|
left: 50%;
|
|
546
759
|
transform: translateX(-50%);
|
|
547
760
|
display: flex;
|
|
@@ -859,6 +1072,10 @@ body.outline-open #deck-outline { transform: none; }
|
|
|
859
1072
|
/* The outline's mirror image: it takes room from the deck's right edge, so the
|
|
860
1073
|
slide stays whole — and is the live preview — while its markdown is typed.
|
|
861
1074
|
Only the dev server ever shows it; a built deck never builds this chrome. */
|
|
1075
|
+
/* The header's chevron trades the drawer's width for the deck's: half the page
|
|
1076
|
+
for a slide that is mostly markup. Everything that steps aside for the
|
|
1077
|
+
drawer reads the same token, so saying it once here moves all of it. */
|
|
1078
|
+
body.edit-wide { --editor-w: 50vw; }
|
|
862
1079
|
body.edit-open > .reveal { width: calc(100% - var(--editor-w)); }
|
|
863
1080
|
/* Both panels out at once: the deck keeps to the strip between them. */
|
|
864
1081
|
body.outline-open.edit-open > .reveal { width: calc(100% - var(--outline-w) - var(--editor-w)); }
|
|
@@ -879,7 +1096,7 @@ body.edit-open #deck-sig { right: calc(var(--editor-w) + 14px); }
|
|
|
879
1096
|
border-left: 1px solid var(--line);
|
|
880
1097
|
backdrop-filter: blur(10px);
|
|
881
1098
|
transform: translateX(100%);
|
|
882
|
-
transition: transform 0.26s ease;
|
|
1099
|
+
transition: transform 0.26s ease, width 0.26s ease;
|
|
883
1100
|
}
|
|
884
1101
|
body.edit-open #deck-edit { transform: none; }
|
|
885
1102
|
|
|
@@ -890,12 +1107,6 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
890
1107
|
padding: 14px 10px 12px 16px;
|
|
891
1108
|
border-bottom: 1px solid var(--line);
|
|
892
1109
|
}
|
|
893
|
-
#deck-edit > header span {
|
|
894
|
-
font: 600 12px/1 var(--sans);
|
|
895
|
-
letter-spacing: 0.1em;
|
|
896
|
-
text-transform: uppercase;
|
|
897
|
-
color: var(--muted);
|
|
898
|
-
}
|
|
899
1110
|
.deck-edit-file {
|
|
900
1111
|
flex: 1;
|
|
901
1112
|
font: 500 12px/1 var(--mono);
|
|
@@ -905,7 +1116,8 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
905
1116
|
white-space: nowrap;
|
|
906
1117
|
}
|
|
907
1118
|
.deck-edit-close,
|
|
908
|
-
.deck-edit-nav
|
|
1119
|
+
.deck-edit-nav,
|
|
1120
|
+
.deck-edit-wide {
|
|
909
1121
|
display: grid;
|
|
910
1122
|
place-items: center;
|
|
911
1123
|
width: 28px;
|
|
@@ -918,16 +1130,23 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
918
1130
|
cursor: pointer;
|
|
919
1131
|
}
|
|
920
1132
|
.deck-edit-close:hover,
|
|
921
|
-
.deck-edit-nav:hover
|
|
1133
|
+
.deck-edit-nav:hover,
|
|
1134
|
+
.deck-edit-wide:hover { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
|
|
922
1135
|
.deck-edit-close svg,
|
|
923
|
-
.deck-edit-nav svg
|
|
1136
|
+
.deck-edit-nav svg,
|
|
1137
|
+
.deck-edit-wide svg { display: block; width: 16px; height: 16px; }
|
|
1138
|
+
/* Widened, the chevron says so on its own — it is the one control in the
|
|
1139
|
+
header that is holding a state. */
|
|
1140
|
+
.deck-edit-wide[aria-pressed='true'] { color: var(--accent); }
|
|
924
1141
|
|
|
1142
|
+
/* The labels are also the handles that swap which box has the room. */
|
|
925
1143
|
.deck-edit-label {
|
|
926
1144
|
padding: 12px 16px 6px;
|
|
927
1145
|
font: 600 10px/1 var(--sans);
|
|
928
1146
|
letter-spacing: 0.12em;
|
|
929
1147
|
text-transform: uppercase;
|
|
930
1148
|
color: var(--muted);
|
|
1149
|
+
cursor: pointer;
|
|
931
1150
|
}
|
|
932
1151
|
|
|
933
1152
|
#deck-edit textarea {
|
|
@@ -979,16 +1198,24 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
979
1198
|
scrollbar-color: color-mix(in srgb, var(--accent) 45%, transparent) transparent;
|
|
980
1199
|
}
|
|
981
1200
|
}
|
|
982
|
-
/* Whichever box holds the cursor gets the room
|
|
983
|
-
|
|
984
|
-
|
|
1201
|
+
/* Whichever box holds the cursor gets the room — edit.js writes [data-focus]
|
|
1202
|
+
as the focus moves, and the swap animates through flex. The slide box starts
|
|
1203
|
+
big and leaves the notes a readable strip; the notes, focused, take the
|
|
1204
|
+
whole drawer and the slide box folds away entirely — its label stays as the
|
|
1205
|
+
handle that brings it back. */
|
|
985
1206
|
#deck-edit textarea {
|
|
986
1207
|
min-height: 0;
|
|
987
|
-
transition: flex-grow 0.25s ease, flex-basis 0.25s ease
|
|
1208
|
+
transition: flex-grow 0.25s ease, flex-basis 0.25s ease,
|
|
1209
|
+
padding 0.25s ease, border-width 0.25s ease;
|
|
988
1210
|
}
|
|
989
1211
|
#deck-edit-source { flex: 1 1 0; }
|
|
990
1212
|
#deck-edit-notes { flex: 0 0 120px; }
|
|
991
|
-
#deck-edit[data-focus='notes'] #deck-edit-source {
|
|
1213
|
+
#deck-edit[data-focus='notes'] #deck-edit-source {
|
|
1214
|
+
flex: 0 0 0px;
|
|
1215
|
+
padding-block: 0;
|
|
1216
|
+
border-width: 0;
|
|
1217
|
+
overflow: hidden;
|
|
1218
|
+
}
|
|
992
1219
|
#deck-edit[data-focus='notes'] #deck-edit-notes { flex: 1 1 0; }
|
|
993
1220
|
|
|
994
1221
|
#deck-edit > footer {
|
|
@@ -1023,6 +1250,7 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
1023
1250
|
border: 1px solid var(--line);
|
|
1024
1251
|
border-radius: 999px;
|
|
1025
1252
|
font: 600 12px/1 var(--sans);
|
|
1253
|
+
white-space: nowrap;
|
|
1026
1254
|
color: var(--muted);
|
|
1027
1255
|
background: transparent;
|
|
1028
1256
|
cursor: pointer;
|
|
@@ -1071,6 +1299,97 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
1071
1299
|
}
|
|
1072
1300
|
.deck-edit-confirm-delete:hover { background: #f2555a; }
|
|
1073
1301
|
|
|
1302
|
+
/* The label line over the slide box carries the block picker's `+`. */
|
|
1303
|
+
.deck-edit-row {
|
|
1304
|
+
display: flex;
|
|
1305
|
+
align-items: center;
|
|
1306
|
+
justify-content: space-between;
|
|
1307
|
+
padding-right: 12px;
|
|
1308
|
+
}
|
|
1309
|
+
.deck-edit-snip {
|
|
1310
|
+
display: grid;
|
|
1311
|
+
place-items: center;
|
|
1312
|
+
width: 24px;
|
|
1313
|
+
height: 24px;
|
|
1314
|
+
padding: 0;
|
|
1315
|
+
border: 1px solid var(--line);
|
|
1316
|
+
border-radius: 999px;
|
|
1317
|
+
background: transparent;
|
|
1318
|
+
color: var(--muted);
|
|
1319
|
+
cursor: pointer;
|
|
1320
|
+
}
|
|
1321
|
+
.deck-edit-snip:hover {
|
|
1322
|
+
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1323
|
+
color: var(--text);
|
|
1324
|
+
}
|
|
1325
|
+
.deck-edit-snip svg { display: block; width: 14px; height: 14px; }
|
|
1326
|
+
|
|
1327
|
+
/* The block picker (snippets.js) — over the drawer, like the delete question,
|
|
1328
|
+
so the deck keeps the stage while a block is chosen. */
|
|
1329
|
+
.deck-snip {
|
|
1330
|
+
position: absolute;
|
|
1331
|
+
inset: 0;
|
|
1332
|
+
z-index: 2;
|
|
1333
|
+
display: flex;
|
|
1334
|
+
flex-direction: column;
|
|
1335
|
+
gap: 10px;
|
|
1336
|
+
padding: 14px 12px;
|
|
1337
|
+
background: color-mix(in srgb, var(--bg) 80%, transparent);
|
|
1338
|
+
backdrop-filter: blur(6px);
|
|
1339
|
+
}
|
|
1340
|
+
.deck-snip[hidden] { display: none; }
|
|
1341
|
+
|
|
1342
|
+
.deck-snip-filter {
|
|
1343
|
+
flex: 0 0 auto;
|
|
1344
|
+
padding: 9px 12px;
|
|
1345
|
+
font: 400 13px/1.4 var(--sans);
|
|
1346
|
+
color: var(--text);
|
|
1347
|
+
background: color-mix(in srgb, var(--card) 80%, transparent);
|
|
1348
|
+
border: 1px solid var(--line);
|
|
1349
|
+
border-radius: 8px;
|
|
1350
|
+
}
|
|
1351
|
+
.deck-snip-filter:focus {
|
|
1352
|
+
outline: none;
|
|
1353
|
+
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.deck-snip-list {
|
|
1357
|
+
flex: 1 1 auto;
|
|
1358
|
+
margin: 0;
|
|
1359
|
+
padding: 0;
|
|
1360
|
+
list-style: none;
|
|
1361
|
+
overflow-y: auto;
|
|
1362
|
+
}
|
|
1363
|
+
.deck-snip-list li {
|
|
1364
|
+
display: flex;
|
|
1365
|
+
flex-direction: column;
|
|
1366
|
+
gap: 2px;
|
|
1367
|
+
padding: 7px 10px;
|
|
1368
|
+
border-radius: 8px;
|
|
1369
|
+
cursor: pointer;
|
|
1370
|
+
}
|
|
1371
|
+
.deck-snip-list li[hidden] { display: none; }
|
|
1372
|
+
.deck-snip-list li span { font: 600 13px/1.3 var(--sans); color: var(--text); }
|
|
1373
|
+
.deck-snip-list li small { font: 400 11.5px/1.45 var(--sans); color: var(--muted); }
|
|
1374
|
+
/* Keyboard and pointer land on the same row: the arrows move this class, and
|
|
1375
|
+
the pointer moving over an entry takes it. */
|
|
1376
|
+
.deck-snip-list li.is-current { background: color-mix(in srgb, var(--accent) 18%, transparent); }
|
|
1377
|
+
|
|
1378
|
+
/* The hairline the outline list and the drawer's boxes wear. */
|
|
1379
|
+
.deck-snip-list::-webkit-scrollbar { width: 5px; }
|
|
1380
|
+
.deck-snip-list::-webkit-scrollbar-track { background: transparent; }
|
|
1381
|
+
.deck-snip-list::-webkit-scrollbar-thumb {
|
|
1382
|
+
border-radius: 999px;
|
|
1383
|
+
background: var(--outline-bar);
|
|
1384
|
+
}
|
|
1385
|
+
.deck-snip-list::-webkit-scrollbar-thumb:hover { background: var(--outline-bar-hover); }
|
|
1386
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
1387
|
+
.deck-snip-list {
|
|
1388
|
+
scrollbar-width: thin;
|
|
1389
|
+
scrollbar-color: var(--outline-bar) transparent;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1074
1393
|
/* The open drawer's button wears the accent — it draws in nothing, like the
|
|
1075
1394
|
pointer's; the id outweighs the generic armed-tool rule. */
|
|
1076
1395
|
#tool-edit[aria-pressed='true'] {
|
|
@@ -1174,10 +1493,10 @@ html.deck-pointer-on * { cursor: none !important; }
|
|
|
1174
1493
|
0 0 16px color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1175
1494
|
}
|
|
1176
1495
|
|
|
1177
|
-
/* The kind picker, floating
|
|
1496
|
+
/* The kind picker, floating below the toolbar like the pen's swatches. */
|
|
1178
1497
|
#deck-pointer-kinds {
|
|
1179
1498
|
position: absolute;
|
|
1180
|
-
|
|
1499
|
+
top: calc(100% + 9px);
|
|
1181
1500
|
left: 50%;
|
|
1182
1501
|
transform: translateX(-50%);
|
|
1183
1502
|
display: flex;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 800" width="600" height="800">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g1" x1="0" y1="0" x2="0.6" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#1f2937" />
|
|
5
|
+
<stop offset="1" stop-color="#4b5563" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="600" height="800" fill="url(#g1)" />
|
|
9
|
+
<text x="300" y="400" text-anchor="middle" font-family="ui-sans-serif, system-ui, sans-serif"
|
|
10
|
+
font-size="34" fill="#e8ebf0" fill-opacity="0.55">your photo 1</text>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 800" width="600" height="800">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g2" x1="0" y1="0" x2="0.6" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#1e3a5f" />
|
|
5
|
+
<stop offset="1" stop-color="#2f6f8f" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="600" height="800" fill="url(#g2)" />
|
|
9
|
+
<text x="300" y="400" text-anchor="middle" font-family="ui-sans-serif, system-ui, sans-serif"
|
|
10
|
+
font-size="34" fill="#e8ebf0" fill-opacity="0.55">your photo 2</text>
|
|
11
|
+
</svg>
|
|
@@ -10,6 +10,28 @@ The subtitle goes here.
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
<!-- .slide: class="author-slide" -->
|
|
14
|
+
|
|
15
|
+
<div class="author-photo">
|
|
16
|
+
<img src="assets/author/photo-1.svg" alt="" />
|
|
17
|
+
<img src="assets/author/photo-2.svg" alt="" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="author-bio">
|
|
21
|
+
<h1>Your name</h1>
|
|
22
|
+
<ul>
|
|
23
|
+
<li>What you do</li>
|
|
24
|
+
<li>What you are <strong>known for</strong></li>
|
|
25
|
+
<li>Where people can find you</li>
|
|
26
|
+
</ul>
|
|
27
|
+
<p class="author-meta">The stack · you · work · with</p>
|
|
28
|
+
<p class="author-meta">❤️ Life outside work — <strong>yoursite.dev</strong></p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
Note: thirty seconds, no more. Swap the two placeholders in assets/author/ for your own photos — add as many as you like, they cross-fade in the order they are written.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
13
35
|
## A slide
|
|
14
36
|
|
|
15
37
|
A slide is whatever sits between two `---` lines.
|
|
@@ -20,25 +20,52 @@ section: Demos
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
## An embed that shares the slide
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<div class="mockup-frame">
|
|
28
|
-
<iframe src="https://stage.mokup.dev/embed/95ff8eab-3c34-4eaf-9f53-e263ee1c6062" width="800" height="500" style="border:0;" allowfullscreen></iframe>
|
|
25
|
+
<div class="frame">
|
|
26
|
+
<iframe src="demo/counter/"></iframe>
|
|
29
27
|
</div>
|
|
30
28
|
|
|
29
|
+
Not every demo wants the whole slide. `.frame` sizes one so the heading stays.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## The mechanism, in one picture
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
sequenceDiagram
|
|
37
|
+
autonumber
|
|
38
|
+
participant U as User
|
|
39
|
+
participant A as Your App
|
|
40
|
+
participant M as Model
|
|
41
|
+
participant S as Your Services
|
|
42
|
+
|
|
43
|
+
U->>A: "how are the running shoes doing?"
|
|
44
|
+
A->>M: prompt + tool list + component catalog
|
|
45
|
+
M->>A: tool call: getSales('running shoes')
|
|
46
|
+
A->>S: getSales('running shoes')
|
|
47
|
+
S-->>A: data
|
|
48
|
+
A->>M: tool result
|
|
49
|
+
M-->>A: UI description (JSON)
|
|
50
|
+
A->>A: render with YOUR components
|
|
51
|
+
A-->>U: a chart, product cards, a form
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Note what never happens: the model never touches the DOM, the network, or your state.
|
|
55
|
+
|
|
31
56
|
---
|
|
32
57
|
|
|
33
|
-
## How those
|
|
58
|
+
## How those slides work
|
|
34
59
|
|
|
35
60
|
```markdown
|
|
36
61
|
<!-- demo: counter --> a folder in demo/
|
|
37
62
|
<!-- demo: http://…:4200/ --> anything else
|
|
63
|
+
<div class="frame">…</div> an embed that shares the slide
|
|
38
64
|
```
|
|
39
65
|
|
|
40
66
|
- the marker must be the first thing in the slide
|
|
41
67
|
- what follows it is the fallback for GitHub and Marp
|
|
42
68
|
- `demo/angular-hello` is started by `servers:` in `slides.config.js`
|
|
69
|
+
- the `+` in the edit drawer writes all three, and `/` opens the same list
|
|
43
70
|
|
|
44
71
|
Note: the iframe is reloaded every time you arrive on the slide, so a demo is always in its initial state.
|
|
@@ -2,17 +2,6 @@
|
|
|
2
2
|
Redefining a token here reskins the whole deck. Delete this file if the
|
|
3
3
|
default dark theme is fine as it is. */
|
|
4
4
|
|
|
5
|
-
.mockup-frame {
|
|
6
|
-
display: flex;
|
|
7
|
-
justify-content: center;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.mockup-frame iframe {
|
|
11
|
-
max-width: 100%;
|
|
12
|
-
border-radius: 8px;
|
|
13
|
-
background: #fff;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
5
|
/*
|
|
17
6
|
:root {
|
|
18
7
|
--bg: #0e1116;
|