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.
@@ -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: color-mix(in srgb, var(--card) 80%, transparent);
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: 15px;
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
- /* Faded back until it is wanted: on stage the slide is the point, and the
455
- toolbar keeps to the quiet strip along the bottom, between the arrows and
456
- the signature. */
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
- bottom: 14px;
634
+ top: 14px;
460
635
  left: 50%;
461
- transform: translateX(-50%);
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
- opacity: 0.4;
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 { opacity: 1; }
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 above the toolbar while a tool is armed — the
530
- toolbar sits on the bottom edge, so up is where the room is. */
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
- bottom: calc(100% + 9px);
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 { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
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 { display: block; width: 16px; height: 16px; }
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, the other keeps a readable
972
- strip — edit.js writes [data-focus] as the focus moves, and the swap
973
- animates through flex. The slide box starts big: notes are the side dish. */
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 { flex: 0 0 120px; }
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 above the toolbar like the pen's swatches. */
1496
+ /* The kind picker, floating below the toolbar like the pen's swatches. */
1167
1497
  #deck-pointer-kinds {
1168
1498
  position: absolute;
1169
- bottom: calc(100% + 9px);
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
- ## How those two slides work
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.