fb-slides 0.5.0 → 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 +361 -31
- 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 +38 -1
package/runtime/theme.base.css
CHANGED
|
@@ -309,6 +309,18 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
309
309
|
height: 100%;
|
|
310
310
|
/* Full-bleed: this one is the demo, not a slide about the demo. */
|
|
311
311
|
margin: 0;
|
|
312
|
+
box-sizing: border-box;
|
|
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
|
+
the letterbox gap the slide already has, converted to slide space through
|
|
317
|
+
reveal's own scale. On tall windows the gap alone is enough and this
|
|
318
|
+
resolves to zero. */
|
|
319
|
+
padding-bottom: max(0px, calc(
|
|
320
|
+
(66px
|
|
321
|
+
- (var(--viewport-height, 100vh) - var(--slide-height, 720px) * var(--slide-scale, 1)) / 2)
|
|
322
|
+
/ var(--slide-scale, 1)
|
|
323
|
+
));
|
|
312
324
|
}
|
|
313
325
|
.reveal .slides section.demo-slide header {
|
|
314
326
|
display: flex;
|
|
@@ -342,6 +354,170 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
342
354
|
box-shadow: 0 18px 45px rgb(0 0 0 / 0.45);
|
|
343
355
|
}
|
|
344
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
|
+
|
|
345
521
|
/* --- chrome -------------------------------------------------------------- */
|
|
346
522
|
|
|
347
523
|
.reveal .progress { color: var(--accent); height: 3px; }
|
|
@@ -350,11 +526,10 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
350
526
|
.reveal-viewport { --r-controls-spacing: 30px; }
|
|
351
527
|
|
|
352
528
|
.reveal .slide-number {
|
|
353
|
-
background:
|
|
354
|
-
color: var(--muted);
|
|
529
|
+
background: none;
|
|
530
|
+
color: color-mix(in srgb, var(--muted) 40%, transparent);
|
|
355
531
|
font-family: var(--mono);
|
|
356
|
-
font-size:
|
|
357
|
-
border-radius: 6px;
|
|
532
|
+
font-size: 13px;
|
|
358
533
|
padding: 3px 8px;
|
|
359
534
|
/* Centred under the arrows: the pair spans 100px inwards from the spacing
|
|
360
535
|
edge, so its middle is spacing + 50px in from the left. */
|
|
@@ -451,14 +626,17 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
451
626
|
cursor: crosshair;
|
|
452
627
|
}
|
|
453
628
|
|
|
454
|
-
/*
|
|
455
|
-
|
|
456
|
-
|
|
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. */
|
|
457
632
|
#deck-tools {
|
|
458
633
|
position: fixed;
|
|
459
|
-
|
|
634
|
+
top: 14px;
|
|
460
635
|
left: 50%;
|
|
461
|
-
|
|
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));
|
|
462
640
|
z-index: 40;
|
|
463
641
|
display: flex;
|
|
464
642
|
gap: 4px;
|
|
@@ -467,12 +645,58 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
467
645
|
border: 1px solid var(--line);
|
|
468
646
|
border-radius: 999px;
|
|
469
647
|
backdrop-filter: blur(6px);
|
|
470
|
-
|
|
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;
|
|
471
681
|
transition: opacity 0.18s ease;
|
|
472
682
|
}
|
|
683
|
+
@keyframes deck-tools-hint {
|
|
684
|
+
0%, 100% { transform: translateX(-50%) translateY(0); }
|
|
685
|
+
50% { transform: translateX(-50%) translateY(3px); }
|
|
686
|
+
}
|
|
473
687
|
#deck-tools:hover,
|
|
474
688
|
#deck-tools:focus-within,
|
|
475
|
-
#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; }
|
|
476
700
|
|
|
477
701
|
.deck-tool {
|
|
478
702
|
position: relative;
|
|
@@ -526,11 +750,11 @@ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--tex
|
|
|
526
750
|
0 0 16px color-mix(in srgb, var(--pen, #ff8c1a) 55%, transparent);
|
|
527
751
|
}
|
|
528
752
|
|
|
529
|
-
/* The colours, floating
|
|
530
|
-
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. */
|
|
531
755
|
#deck-swatches {
|
|
532
756
|
position: absolute;
|
|
533
|
-
|
|
757
|
+
top: calc(100% + 9px);
|
|
534
758
|
left: 50%;
|
|
535
759
|
transform: translateX(-50%);
|
|
536
760
|
display: flex;
|
|
@@ -848,6 +1072,10 @@ body.outline-open #deck-outline { transform: none; }
|
|
|
848
1072
|
/* The outline's mirror image: it takes room from the deck's right edge, so the
|
|
849
1073
|
slide stays whole — and is the live preview — while its markdown is typed.
|
|
850
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; }
|
|
851
1079
|
body.edit-open > .reveal { width: calc(100% - var(--editor-w)); }
|
|
852
1080
|
/* Both panels out at once: the deck keeps to the strip between them. */
|
|
853
1081
|
body.outline-open.edit-open > .reveal { width: calc(100% - var(--outline-w) - var(--editor-w)); }
|
|
@@ -868,7 +1096,7 @@ body.edit-open #deck-sig { right: calc(var(--editor-w) + 14px); }
|
|
|
868
1096
|
border-left: 1px solid var(--line);
|
|
869
1097
|
backdrop-filter: blur(10px);
|
|
870
1098
|
transform: translateX(100%);
|
|
871
|
-
transition: transform 0.26s ease;
|
|
1099
|
+
transition: transform 0.26s ease, width 0.26s ease;
|
|
872
1100
|
}
|
|
873
1101
|
body.edit-open #deck-edit { transform: none; }
|
|
874
1102
|
|
|
@@ -879,12 +1107,6 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
879
1107
|
padding: 14px 10px 12px 16px;
|
|
880
1108
|
border-bottom: 1px solid var(--line);
|
|
881
1109
|
}
|
|
882
|
-
#deck-edit > header span {
|
|
883
|
-
font: 600 12px/1 var(--sans);
|
|
884
|
-
letter-spacing: 0.1em;
|
|
885
|
-
text-transform: uppercase;
|
|
886
|
-
color: var(--muted);
|
|
887
|
-
}
|
|
888
1110
|
.deck-edit-file {
|
|
889
1111
|
flex: 1;
|
|
890
1112
|
font: 500 12px/1 var(--mono);
|
|
@@ -894,7 +1116,8 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
894
1116
|
white-space: nowrap;
|
|
895
1117
|
}
|
|
896
1118
|
.deck-edit-close,
|
|
897
|
-
.deck-edit-nav
|
|
1119
|
+
.deck-edit-nav,
|
|
1120
|
+
.deck-edit-wide {
|
|
898
1121
|
display: grid;
|
|
899
1122
|
place-items: center;
|
|
900
1123
|
width: 28px;
|
|
@@ -907,16 +1130,23 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
907
1130
|
cursor: pointer;
|
|
908
1131
|
}
|
|
909
1132
|
.deck-edit-close:hover,
|
|
910
|
-
.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); }
|
|
911
1135
|
.deck-edit-close svg,
|
|
912
|
-
.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); }
|
|
913
1141
|
|
|
1142
|
+
/* The labels are also the handles that swap which box has the room. */
|
|
914
1143
|
.deck-edit-label {
|
|
915
1144
|
padding: 12px 16px 6px;
|
|
916
1145
|
font: 600 10px/1 var(--sans);
|
|
917
1146
|
letter-spacing: 0.12em;
|
|
918
1147
|
text-transform: uppercase;
|
|
919
1148
|
color: var(--muted);
|
|
1149
|
+
cursor: pointer;
|
|
920
1150
|
}
|
|
921
1151
|
|
|
922
1152
|
#deck-edit textarea {
|
|
@@ -968,16 +1198,24 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
968
1198
|
scrollbar-color: color-mix(in srgb, var(--accent) 45%, transparent) transparent;
|
|
969
1199
|
}
|
|
970
1200
|
}
|
|
971
|
-
/* Whichever box holds the cursor gets the room
|
|
972
|
-
|
|
973
|
-
|
|
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. */
|
|
974
1206
|
#deck-edit textarea {
|
|
975
1207
|
min-height: 0;
|
|
976
|
-
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;
|
|
977
1210
|
}
|
|
978
1211
|
#deck-edit-source { flex: 1 1 0; }
|
|
979
1212
|
#deck-edit-notes { flex: 0 0 120px; }
|
|
980
|
-
#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
|
+
}
|
|
981
1219
|
#deck-edit[data-focus='notes'] #deck-edit-notes { flex: 1 1 0; }
|
|
982
1220
|
|
|
983
1221
|
#deck-edit > footer {
|
|
@@ -1012,6 +1250,7 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
1012
1250
|
border: 1px solid var(--line);
|
|
1013
1251
|
border-radius: 999px;
|
|
1014
1252
|
font: 600 12px/1 var(--sans);
|
|
1253
|
+
white-space: nowrap;
|
|
1015
1254
|
color: var(--muted);
|
|
1016
1255
|
background: transparent;
|
|
1017
1256
|
cursor: pointer;
|
|
@@ -1060,6 +1299,97 @@ body.edit-open #deck-edit { transform: none; }
|
|
|
1060
1299
|
}
|
|
1061
1300
|
.deck-edit-confirm-delete:hover { background: #f2555a; }
|
|
1062
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
|
+
|
|
1063
1393
|
/* The open drawer's button wears the accent — it draws in nothing, like the
|
|
1064
1394
|
pointer's; the id outweighs the generic armed-tool rule. */
|
|
1065
1395
|
#tool-edit[aria-pressed='true'] {
|
|
@@ -1163,10 +1493,10 @@ html.deck-pointer-on * { cursor: none !important; }
|
|
|
1163
1493
|
0 0 16px color-mix(in srgb, var(--accent) 55%, transparent);
|
|
1164
1494
|
}
|
|
1165
1495
|
|
|
1166
|
-
/* The kind picker, floating
|
|
1496
|
+
/* The kind picker, floating below the toolbar like the pen's swatches. */
|
|
1167
1497
|
#deck-pointer-kinds {
|
|
1168
1498
|
position: absolute;
|
|
1169
|
-
|
|
1499
|
+
top: calc(100% + 9px);
|
|
1170
1500
|
left: 50%;
|
|
1171
1501
|
transform: translateX(-50%);
|
|
1172
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,15 +20,52 @@ section: Demos
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## An embed that shares the slide
|
|
24
|
+
|
|
25
|
+
<div class="frame">
|
|
26
|
+
<iframe src="demo/counter/"></iframe>
|
|
27
|
+
</div>
|
|
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
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## How those slides work
|
|
24
59
|
|
|
25
60
|
```markdown
|
|
26
61
|
<!-- demo: counter --> a folder in demo/
|
|
27
62
|
<!-- demo: http://…:4200/ --> anything else
|
|
63
|
+
<div class="frame">…</div> an embed that shares the slide
|
|
28
64
|
```
|
|
29
65
|
|
|
30
66
|
- the marker must be the first thing in the slide
|
|
31
67
|
- what follows it is the fallback for GitHub and Marp
|
|
32
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
|
|
33
70
|
|
|
34
71
|
Note: the iframe is reloaded every time you arrive on the slide, so a demo is always in its initial state.
|