fb-slides 0.5.1 → 0.6.3
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/CHANGELOG.md +118 -0
- package/README.md +99 -1
- package/lib/config.mjs +4 -0
- package/lib/render.mjs +1 -0
- package/package.json +2 -1
- package/runtime/annotate.js +11 -1
- package/runtime/deck.js +117 -1
- package/runtime/edit.js +76 -14
- package/runtime/outline.js +24 -5
- package/runtime/pointer.js +9 -1
- package/runtime/snippets.js +319 -0
- package/runtime/spotlight.js +9 -1
- package/runtime/theme.base.css +397 -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 +44 -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;
|
|
@@ -766,6 +979,24 @@ body.outline-open #deck-outline { transform: none; }
|
|
|
766
979
|
opacity: 0.55;
|
|
767
980
|
}
|
|
768
981
|
|
|
982
|
+
/* A disabled slide — `<!-- disabled -->` in its source. It is listed, because
|
|
983
|
+
the navigator is how it is reached at all, but it reads as set aside: the
|
|
984
|
+
talk walks past it and it holds no number. Still a button, still hovers,
|
|
985
|
+
still opens — clicking one is how you look at it. */
|
|
986
|
+
.deck-outline-item.is-off .deck-outline-title { opacity: 0.4; }
|
|
987
|
+
.deck-outline-item.is-off .deck-outline-thumb { opacity: 0.32; }
|
|
988
|
+
.deck-outline-item.is-off:hover .deck-outline-title,
|
|
989
|
+
.deck-outline-item.is-off:hover .deck-outline-thumb { opacity: 0.75; }
|
|
990
|
+
.deck-outline-n.is-off {
|
|
991
|
+
padding: 2px 4px;
|
|
992
|
+
border: 1px solid var(--line);
|
|
993
|
+
border-radius: 4px;
|
|
994
|
+
font: 600 8.5px/1 var(--sans);
|
|
995
|
+
letter-spacing: 0.1em;
|
|
996
|
+
text-transform: uppercase;
|
|
997
|
+
opacity: 1;
|
|
998
|
+
}
|
|
999
|
+
|
|
769
1000
|
/* --- the outline's preview mode ------------------------------------------ */
|
|
770
1001
|
|
|
771
1002
|
/* Same rows, but each one is the slide itself. Depth indents carry over from
|
|
@@ -859,6 +1090,10 @@ body.outline-open #deck-outline { transform: none; }
|
|
|
859
1090
|
/* The outline's mirror image: it takes room from the deck's right edge, so the
|
|
860
1091
|
slide stays whole — and is the live preview — while its markdown is typed.
|
|
861
1092
|
Only the dev server ever shows it; a built deck never builds this chrome. */
|
|
1093
|
+
/* The header's chevron trades the drawer's width for the deck's: half the page
|
|
1094
|
+
for a slide that is mostly markup. Everything that steps aside for the
|
|
1095
|
+
drawer reads the same token, so saying it once here moves all of it. */
|
|
1096
|
+
body.edit-wide { --editor-w: 50vw; }
|
|
862
1097
|
body.edit-open > .reveal { width: calc(100% - var(--editor-w)); }
|
|
863
1098
|
/* Both panels out at once: the deck keeps to the strip between them. */
|
|
864
1099
|
body.outline-open.edit-open > .reveal { width: calc(100% - var(--outline-w) - var(--editor-w)); }
|
|
@@ -879,7 +1114,7 @@ body.edit-open #deck-sig { right: calc(var(--editor-w) + 14px); }
|
|
|
879
1114
|
border-left: 1px solid var(--line);
|
|
880
1115
|
backdrop-filter: blur(10px);
|
|
881
1116
|
transform: translateX(100%);
|
|
882
|
-
transition: transform 0.26s ease;
|
|
1117
|
+
transition: transform 0.26s ease, width 0.26s ease;
|
|
883
1118
|
}
|
|
884
1119
|
body.edit-open #deck-edit { transform: none; }
|
|
885
1120
|
|
|
@@ -890,12 +1125,6 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
890
1125
|
padding: 14px 10px 12px 16px;
|
|
891
1126
|
border-bottom: 1px solid var(--line);
|
|
892
1127
|
}
|
|
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
1128
|
.deck-edit-file {
|
|
900
1129
|
flex: 1;
|
|
901
1130
|
font: 500 12px/1 var(--mono);
|
|
@@ -905,7 +1134,8 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
905
1134
|
white-space: nowrap;
|
|
906
1135
|
}
|
|
907
1136
|
.deck-edit-close,
|
|
908
|
-
.deck-edit-nav
|
|
1137
|
+
.deck-edit-nav,
|
|
1138
|
+
.deck-edit-wide {
|
|
909
1139
|
display: grid;
|
|
910
1140
|
place-items: center;
|
|
911
1141
|
width: 28px;
|
|
@@ -918,16 +1148,23 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
918
1148
|
cursor: pointer;
|
|
919
1149
|
}
|
|
920
1150
|
.deck-edit-close:hover,
|
|
921
|
-
.deck-edit-nav:hover
|
|
1151
|
+
.deck-edit-nav:hover,
|
|
1152
|
+
.deck-edit-wide:hover { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
|
|
922
1153
|
.deck-edit-close svg,
|
|
923
|
-
.deck-edit-nav svg
|
|
1154
|
+
.deck-edit-nav svg,
|
|
1155
|
+
.deck-edit-wide svg { display: block; width: 16px; height: 16px; }
|
|
1156
|
+
/* Widened, the chevron says so on its own — it is the one control in the
|
|
1157
|
+
header that is holding a state. */
|
|
1158
|
+
.deck-edit-wide[aria-pressed='true'] { color: var(--accent); }
|
|
924
1159
|
|
|
1160
|
+
/* The labels are also the handles that swap which box has the room. */
|
|
925
1161
|
.deck-edit-label {
|
|
926
1162
|
padding: 12px 16px 6px;
|
|
927
1163
|
font: 600 10px/1 var(--sans);
|
|
928
1164
|
letter-spacing: 0.12em;
|
|
929
1165
|
text-transform: uppercase;
|
|
930
1166
|
color: var(--muted);
|
|
1167
|
+
cursor: pointer;
|
|
931
1168
|
}
|
|
932
1169
|
|
|
933
1170
|
#deck-edit textarea {
|
|
@@ -979,16 +1216,24 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
979
1216
|
scrollbar-color: color-mix(in srgb, var(--accent) 45%, transparent) transparent;
|
|
980
1217
|
}
|
|
981
1218
|
}
|
|
982
|
-
/* Whichever box holds the cursor gets the room
|
|
983
|
-
|
|
984
|
-
|
|
1219
|
+
/* Whichever box holds the cursor gets the room — edit.js writes [data-focus]
|
|
1220
|
+
as the focus moves, and the swap animates through flex. The slide box starts
|
|
1221
|
+
big and leaves the notes a readable strip; the notes, focused, take the
|
|
1222
|
+
whole drawer and the slide box folds away entirely — its label stays as the
|
|
1223
|
+
handle that brings it back. */
|
|
985
1224
|
#deck-edit textarea {
|
|
986
1225
|
min-height: 0;
|
|
987
|
-
transition: flex-grow 0.25s ease, flex-basis 0.25s ease
|
|
1226
|
+
transition: flex-grow 0.25s ease, flex-basis 0.25s ease,
|
|
1227
|
+
padding 0.25s ease, border-width 0.25s ease;
|
|
988
1228
|
}
|
|
989
1229
|
#deck-edit-source { flex: 1 1 0; }
|
|
990
1230
|
#deck-edit-notes { flex: 0 0 120px; }
|
|
991
|
-
#deck-edit[data-focus='notes'] #deck-edit-source {
|
|
1231
|
+
#deck-edit[data-focus='notes'] #deck-edit-source {
|
|
1232
|
+
flex: 0 0 0px;
|
|
1233
|
+
padding-block: 0;
|
|
1234
|
+
border-width: 0;
|
|
1235
|
+
overflow: hidden;
|
|
1236
|
+
}
|
|
992
1237
|
#deck-edit[data-focus='notes'] #deck-edit-notes { flex: 1 1 0; }
|
|
993
1238
|
|
|
994
1239
|
#deck-edit > footer {
|
|
@@ -1023,6 +1268,7 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
1023
1268
|
border: 1px solid var(--line);
|
|
1024
1269
|
border-radius: 999px;
|
|
1025
1270
|
font: 600 12px/1 var(--sans);
|
|
1271
|
+
white-space: nowrap;
|
|
1026
1272
|
color: var(--muted);
|
|
1027
1273
|
background: transparent;
|
|
1028
1274
|
cursor: pointer;
|
|
@@ -1071,6 +1317,97 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
1071
1317
|
}
|
|
1072
1318
|
.deck-edit-confirm-delete:hover { background: #f2555a; }
|
|
1073
1319
|
|
|
1320
|
+
/* The label line over the slide box carries the block picker's `+`. */
|
|
1321
|
+
.deck-edit-row {
|
|
1322
|
+
display: flex;
|
|
1323
|
+
align-items: center;
|
|
1324
|
+
justify-content: space-between;
|
|
1325
|
+
padding-right: 12px;
|
|
1326
|
+
}
|
|
1327
|
+
.deck-edit-snip {
|
|
1328
|
+
display: grid;
|
|
1329
|
+
place-items: center;
|
|
1330
|
+
width: 24px;
|
|
1331
|
+
height: 24px;
|
|
1332
|
+
padding: 0;
|
|
1333
|
+
border: 1px solid var(--line);
|
|
1334
|
+
border-radius: 999px;
|
|
1335
|
+
background: transparent;
|
|
1336
|
+
color: var(--muted);
|
|
1337
|
+
cursor: pointer;
|
|
1338
|
+
}
|
|
1339
|
+
.deck-edit-snip:hover {
|
|
1340
|
+
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1341
|
+
color: var(--text);
|
|
1342
|
+
}
|
|
1343
|
+
.deck-edit-snip svg { display: block; width: 14px; height: 14px; }
|
|
1344
|
+
|
|
1345
|
+
/* The block picker (snippets.js) — over the drawer, like the delete question,
|
|
1346
|
+
so the deck keeps the stage while a block is chosen. */
|
|
1347
|
+
.deck-snip {
|
|
1348
|
+
position: absolute;
|
|
1349
|
+
inset: 0;
|
|
1350
|
+
z-index: 2;
|
|
1351
|
+
display: flex;
|
|
1352
|
+
flex-direction: column;
|
|
1353
|
+
gap: 10px;
|
|
1354
|
+
padding: 14px 12px;
|
|
1355
|
+
background: color-mix(in srgb, var(--bg) 80%, transparent);
|
|
1356
|
+
backdrop-filter: blur(6px);
|
|
1357
|
+
}
|
|
1358
|
+
.deck-snip[hidden] { display: none; }
|
|
1359
|
+
|
|
1360
|
+
.deck-snip-filter {
|
|
1361
|
+
flex: 0 0 auto;
|
|
1362
|
+
padding: 9px 12px;
|
|
1363
|
+
font: 400 13px/1.4 var(--sans);
|
|
1364
|
+
color: var(--text);
|
|
1365
|
+
background: color-mix(in srgb, var(--card) 80%, transparent);
|
|
1366
|
+
border: 1px solid var(--line);
|
|
1367
|
+
border-radius: 8px;
|
|
1368
|
+
}
|
|
1369
|
+
.deck-snip-filter:focus {
|
|
1370
|
+
outline: none;
|
|
1371
|
+
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
.deck-snip-list {
|
|
1375
|
+
flex: 1 1 auto;
|
|
1376
|
+
margin: 0;
|
|
1377
|
+
padding: 0;
|
|
1378
|
+
list-style: none;
|
|
1379
|
+
overflow-y: auto;
|
|
1380
|
+
}
|
|
1381
|
+
.deck-snip-list li {
|
|
1382
|
+
display: flex;
|
|
1383
|
+
flex-direction: column;
|
|
1384
|
+
gap: 2px;
|
|
1385
|
+
padding: 7px 10px;
|
|
1386
|
+
border-radius: 8px;
|
|
1387
|
+
cursor: pointer;
|
|
1388
|
+
}
|
|
1389
|
+
.deck-snip-list li[hidden] { display: none; }
|
|
1390
|
+
.deck-snip-list li span { font: 600 13px/1.3 var(--sans); color: var(--text); }
|
|
1391
|
+
.deck-snip-list li small { font: 400 11.5px/1.45 var(--sans); color: var(--muted); }
|
|
1392
|
+
/* Keyboard and pointer land on the same row: the arrows move this class, and
|
|
1393
|
+
the pointer moving over an entry takes it. */
|
|
1394
|
+
.deck-snip-list li.is-current { background: color-mix(in srgb, var(--accent) 18%, transparent); }
|
|
1395
|
+
|
|
1396
|
+
/* The hairline the outline list and the drawer's boxes wear. */
|
|
1397
|
+
.deck-snip-list::-webkit-scrollbar { width: 5px; }
|
|
1398
|
+
.deck-snip-list::-webkit-scrollbar-track { background: transparent; }
|
|
1399
|
+
.deck-snip-list::-webkit-scrollbar-thumb {
|
|
1400
|
+
border-radius: 999px;
|
|
1401
|
+
background: var(--outline-bar);
|
|
1402
|
+
}
|
|
1403
|
+
.deck-snip-list::-webkit-scrollbar-thumb:hover { background: var(--outline-bar-hover); }
|
|
1404
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
1405
|
+
.deck-snip-list {
|
|
1406
|
+
scrollbar-width: thin;
|
|
1407
|
+
scrollbar-color: var(--outline-bar) transparent;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1074
1411
|
/* The open drawer's button wears the accent — it draws in nothing, like the
|
|
1075
1412
|
pointer's; the id outweighs the generic armed-tool rule. */
|
|
1076
1413
|
#tool-edit[aria-pressed='true'] {
|
|
@@ -1081,6 +1418,36 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
1081
1418
|
0 0 16px color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1082
1419
|
}
|
|
1083
1420
|
|
|
1421
|
+
/* A disabled slide in reveal's own overview, where the grid is the other way
|
|
1422
|
+
of picking one by hand. Dimmed, not hidden: it is still there to click. */
|
|
1423
|
+
/* `filter`, not `opacity`: reveal pins every slide in the overview to
|
|
1424
|
+
`opacity: 1 !important`, and this says the same thing without the shouting
|
|
1425
|
+
match. */
|
|
1426
|
+
.reveal.overview .slides section.slide-off { filter: grayscale(1) opacity(0.35); }
|
|
1427
|
+
.reveal.overview .slides section.slide-off:hover { filter: grayscale(0.4) opacity(0.8); }
|
|
1428
|
+
|
|
1429
|
+
/* Looking at one, the count and the bar stand down — there is no number to be
|
|
1430
|
+
at, this slide is not in the talk — and the word takes the number's place,
|
|
1431
|
+
which is the shortest way to say why it went. */
|
|
1432
|
+
.reveal:has(.slides section.slide-off.present) .slide-number,
|
|
1433
|
+
.reveal:has(.slides section.slide-off.present) .progress { visibility: hidden; }
|
|
1434
|
+
.reveal:has(.slides section.slide-off.present)::after {
|
|
1435
|
+
content: 'off';
|
|
1436
|
+
position: absolute;
|
|
1437
|
+
z-index: 4;
|
|
1438
|
+
left: calc(var(--r-controls-spacing, 12px) + 50px);
|
|
1439
|
+
bottom: 12px;
|
|
1440
|
+
transform: translateX(-50%);
|
|
1441
|
+
padding: 3px 6px;
|
|
1442
|
+
border: 1px solid var(--line);
|
|
1443
|
+
border-radius: 5px;
|
|
1444
|
+
font: 600 9px/1 var(--sans);
|
|
1445
|
+
letter-spacing: 0.12em;
|
|
1446
|
+
text-transform: uppercase;
|
|
1447
|
+
color: var(--muted);
|
|
1448
|
+
pointer-events: none;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1084
1451
|
/* --- the pointer (pointer.js) -------------------------------------------- */
|
|
1085
1452
|
|
|
1086
1453
|
/* The real cursor goes away everywhere while the pointer is armed — the
|
|
@@ -1174,10 +1541,10 @@ html.deck-pointer-on * { cursor: none !important; }
|
|
|
1174
1541
|
0 0 16px color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1175
1542
|
}
|
|
1176
1543
|
|
|
1177
|
-
/* The kind picker, floating
|
|
1544
|
+
/* The kind picker, floating below the toolbar like the pen's swatches. */
|
|
1178
1545
|
#deck-pointer-kinds {
|
|
1179
1546
|
position: absolute;
|
|
1180
|
-
|
|
1547
|
+
top: calc(100% + 9px);
|
|
1181
1548
|
left: 50%;
|
|
1182
1549
|
transform: translateX(-50%);
|
|
1183
1550
|
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,63 @@ 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
|
+
<!-- disabled -->
|
|
34
|
+
|
|
35
|
+
## A slide kept out of the talk
|
|
36
|
+
|
|
37
|
+
`<!-- disabled -->` on its own line: still in the file, still in the navigator —
|
|
38
|
+
marked **off** there — but the arrows walk past it and it is not counted.
|
|
39
|
+
|
|
40
|
+
Click it in the navigator (`V`) to look at it anyway.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## The mechanism, in one picture
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
sequenceDiagram
|
|
48
|
+
autonumber
|
|
49
|
+
participant U as User
|
|
50
|
+
participant A as Your App
|
|
51
|
+
participant M as Model
|
|
52
|
+
participant S as Your Services
|
|
53
|
+
|
|
54
|
+
U->>A: "how are the running shoes doing?"
|
|
55
|
+
A->>M: prompt + tool list + component catalog
|
|
56
|
+
M->>A: tool call: getSales('running shoes')
|
|
57
|
+
A->>S: getSales('running shoes')
|
|
58
|
+
S-->>A: data
|
|
59
|
+
A->>M: tool result
|
|
60
|
+
M-->>A: UI description (JSON)
|
|
61
|
+
A->>A: render with YOUR components
|
|
62
|
+
A-->>U: a chart, product cards, a form
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Note what never happens: the model never touches the DOM, the network, or your state.
|
|
66
|
+
|
|
31
67
|
---
|
|
32
68
|
|
|
33
|
-
## How those
|
|
69
|
+
## How those slides work
|
|
34
70
|
|
|
35
71
|
```markdown
|
|
36
72
|
<!-- demo: counter --> a folder in demo/
|
|
37
73
|
<!-- demo: http://…:4200/ --> anything else
|
|
74
|
+
<div class="frame">…</div> an embed that shares the slide
|
|
38
75
|
```
|
|
39
76
|
|
|
40
77
|
- the marker must be the first thing in the slide
|
|
41
78
|
- what follows it is the fallback for GitHub and Marp
|
|
42
79
|
- `demo/angular-hello` is started by `servers:` in `slides.config.js`
|
|
80
|
+
- the `+` in the edit drawer writes all three, and `/` opens the same list
|
|
43
81
|
|
|
44
82
|
Note: the iframe is reloaded every time you arrive on the slide, so a demo is always in its initial state.
|