rshow 0.0.4 → 0.0.6

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.
@@ -14,9 +14,11 @@
14
14
  - two-col
15
15
  - quote
16
16
  - big-number
17
+ - your own layout
17
18
 
18
19
  One markup example per layout, all inside `<main class="deck">`.
19
20
  Every slide carries `slide` plus one layout class, and the layout class goes second.
21
+ The twelve are a starting set: a deck may add a layout of its own when none of them carries the claim.
20
22
  Nothing sits above a title: no kicker, no label, no number.
21
23
 
22
24
  A sparse slide (`title`, `section`, `statement`, `quote`, `big-number`, `bullets`) is centred a little above the middle of the 1280x720 canvas, so the air sits under the block instead of closing it in.
@@ -249,3 +251,51 @@ The unit belongs in the statement and the source goes under it.
249
251
  ```
250
252
 
251
253
  A number you cannot measure is `[PLACEHOLDER]` until it is real, and a number that stands for an example says so in the source line.
254
+
255
+ ## your own layout
256
+
257
+ A layout of your own takes a short class name and sits second on the slide, in the same slot as the twelve.
258
+ Pick the shape the claim needs, such as an annotated figure, a figure with a callout, a diagram built in HTML or SVG, a timeline, a before and after or a comparison grid.
259
+ Its CSS goes in one inline `<style>` block after the theme links, scoped to `.slide.<name>`, with colour, type and space from the tokens.
260
+ Here one figure carries three callouts, and they stack under it below 640px.
261
+
262
+ ```html
263
+ <style>
264
+ .slide.annotated .annotated-fig { position: relative; margin: 0; }
265
+ .slide.annotated .annotated-fig img { display: block; width: 100%; }
266
+ .slide.annotated .callout {
267
+ position: absolute;
268
+ max-width: 30%;
269
+ padding: var(--space-03) var(--space-04);
270
+ background: var(--layer-01);
271
+ border: 1px solid var(--border-strong);
272
+ color: var(--fg);
273
+ font-size: 18px;
274
+ line-height: 1.4;
275
+ }
276
+ .slide.annotated .callout.one { top: 6%; left: 3%; }
277
+ .slide.annotated .callout.two { top: 40%; right: 3%; }
278
+ .slide.annotated .callout.three { bottom: 6%; left: 30%; }
279
+ @media (max-width: 640px) {
280
+ .slide.annotated .callout {
281
+ position: static;
282
+ max-width: none;
283
+ margin: var(--space-03) 0 0;
284
+ padding: 0;
285
+ border: 0;
286
+ }
287
+ }
288
+ </style>
289
+
290
+ <section class="slide annotated">
291
+ <h2>The tail of the log is what a restart replays</h2>
292
+ <figure class="annotated-fig">
293
+ <img src="write-path.svg" alt="A log appended before the map is updated, with three stages marked.">
294
+ <p class="callout one">The append lands first.</p>
295
+ <p class="callout two">The map follows it.</p>
296
+ <p class="callout three">A crash replays the tail.</p>
297
+ <figcaption>A restart loses nothing after step 2.</figcaption>
298
+ </figure>
299
+ <aside class="notes"><p>Trace the three callouts in order.</p></aside>
300
+ </section>
301
+ ```
@@ -517,6 +517,323 @@ aside.notes {
517
517
  pointer-events: none;
518
518
  }
519
519
 
520
+ /* --------------------------------------------------------------- reading */
521
+
522
+ /* A phone held upright cannot show a 16:9 slide: 390px of width leaves a 219px
523
+ strip. So the deck stops presenting and reads. Every slide becomes a block
524
+ down the page, the width of the screen, one after another, with no scale in
525
+ it: a 1280px canvas shrunk to 390px sets the body text near 9px, which is a
526
+ picture of a slide and not a slide. The type takes sizes a phone reads, each
527
+ figure takes the width it is given, a wide table or a block of code scrolls
528
+ sideways inside itself, and every grid stacks to one column. The notes stay
529
+ hidden, every step is out, and a hairline rule with an "n / total" label in
530
+ the margin marks where one slide ends and the next begins.
531
+ deck.js turns this on for a viewport that is portrait and narrower than 700px,
532
+ and off everywhere else: a landscape phone and a desktop keep one slide at a
533
+ time. The button that goes back to presenting is deck.js's, and Esc comes back
534
+ here. It is a screen mode, so it sits in @media screen: a sheet of paper keeps
535
+ one slide per page, laid out on the canvas it was drawn for. */
536
+
537
+ @media screen {
538
+ html.is-reading,
539
+ body.is-reading {
540
+ height: auto;
541
+ overflow: visible;
542
+ background: var(--bg);
543
+ /* Reading sizes. The type scale has no 17px step and the canvas headings are
544
+ drawn for 1280px, so a phone slide names the size of its own body text and
545
+ of the one line a quote or a statement sets. Every other size below is a
546
+ token: 32px, 24px, 20px, 18px, 14px. */
547
+ --deck-read-body: 17px;
548
+ --deck-read-line: 22px;
549
+ }
550
+
551
+ /* The deck is the page now: no canvas, no transform, no clip. The bottom pad
552
+ clears the Present button, so the last line of the last slide stays under
553
+ the reader's thumb and not inside it. */
554
+ main.deck.is-reading {
555
+ position: static;
556
+ width: auto;
557
+ height: auto;
558
+ margin: 0;
559
+ padding: 0 0 80px;
560
+ overflow: visible;
561
+ transform: none !important;
562
+ background: var(--bg);
563
+ counter-reset: deck-slide;
564
+ }
565
+
566
+ /* One slide, one block: the width of the screen, a 16px gutter either side,
567
+ air above and below, and a rule where the previous slide ended. */
568
+ main.deck.is-reading > .slide,
569
+ main.deck.is-reading.is-ready > .slide {
570
+ position: relative;
571
+ inset: auto;
572
+ display: block;
573
+ width: auto;
574
+ height: auto;
575
+ min-height: 0;
576
+ margin: 0;
577
+ padding: var(--space-06) var(--space-05);
578
+ overflow: visible;
579
+ opacity: 1;
580
+ visibility: visible;
581
+ transform: none;
582
+ border-top: 1px solid var(--border-subtle);
583
+ counter-increment: deck-slide;
584
+ }
585
+
586
+ main.deck.is-reading > .slide:first-child {
587
+ border-top: 0;
588
+ }
589
+
590
+ /* Where this slide sits in the stack. It is a label, so sans, sentence case
591
+ and the muted colour of every other label: mono and caps are for code. */
592
+ main.deck.is-reading > .slide::before {
593
+ content: counter(deck-slide) " / " var(--deck-total, "");
594
+ display: block;
595
+ margin: 0 0 var(--space-05);
596
+ font-family: var(--font-sans);
597
+ font-size: var(--text-2xs);
598
+ font-weight: 400;
599
+ line-height: 1.33;
600
+ letter-spacing: 0;
601
+ text-transform: none;
602
+ color: var(--muted);
603
+ font-variant-numeric: tabular-nums;
604
+ }
605
+
606
+ /* ----------------------------------------------------- reading type --- */
607
+
608
+ /* The display steps belong to a 1280px slide: 76px of heading in 358px of
609
+ width is three words a line. In reading mode a heading is a heading. */
610
+ main.deck.is-reading .slide h1,
611
+ main.deck.is-reading .slide h2,
612
+ main.deck.is-reading .slide h3 {
613
+ margin: 0 0 var(--space-04);
614
+ max-width: none;
615
+ font-weight: 400;
616
+ line-height: var(--lh-heading);
617
+ letter-spacing: 0;
618
+ }
619
+
620
+ main.deck.is-reading .slide h1 {
621
+ font-size: var(--text-xl);
622
+ }
623
+
624
+ main.deck.is-reading .slide h2 {
625
+ font-size: var(--text-md);
626
+ }
627
+
628
+ main.deck.is-reading .slide h3 {
629
+ font-size: var(--text-base);
630
+ font-weight: 600;
631
+ }
632
+
633
+ /* Running text: 17px on a 1.45 leading reads on a phone without a pinch, and
634
+ the measure is the screen, so nothing caps it short of the gutter. */
635
+ main.deck.is-reading .slide p,
636
+ main.deck.is-reading .slide li,
637
+ main.deck.is-reading .slide li > p,
638
+ main.deck.is-reading .slide .col p,
639
+ main.deck.is-reading .slide .col li {
640
+ max-width: none;
641
+ font-size: var(--deck-read-body);
642
+ line-height: var(--lh-body);
643
+ }
644
+
645
+ main.deck.is-reading .slide ul,
646
+ main.deck.is-reading .slide ol {
647
+ padding-left: var(--space-06);
648
+ }
649
+
650
+ main.deck.is-reading .slide li {
651
+ margin-bottom: var(--space-03);
652
+ }
653
+
654
+ main.deck.is-reading .slide li:last-child {
655
+ margin-bottom: 0;
656
+ }
657
+
658
+ main.deck.is-reading .slide.title .subtitle {
659
+ margin-top: var(--space-04);
660
+ max-width: none;
661
+ font-size: var(--text-sm);
662
+ line-height: 1.4;
663
+ }
664
+
665
+ /* The line under a title, a figure or a table: a caption, so small and muted,
666
+ and never wider than the slide. */
667
+ main.deck.is-reading .slide .muted,
668
+ main.deck.is-reading .slide figcaption,
669
+ main.deck.is-reading .slide cite,
670
+ main.deck.is-reading .slide caption,
671
+ main.deck.is-reading .slide .byline,
672
+ main.deck.is-reading .slide .source,
673
+ main.deck.is-reading .slide li > p {
674
+ max-width: none;
675
+ font-size: var(--text-2xs);
676
+ line-height: 1.45;
677
+ }
678
+
679
+ main.deck.is-reading .slide.section h2 {
680
+ margin-bottom: 0;
681
+ font-size: var(--text-md);
682
+ font-weight: 400;
683
+ }
684
+
685
+ main.deck.is-reading .slide.statement p,
686
+ main.deck.is-reading .slide.quote blockquote {
687
+ max-width: none;
688
+ font-size: var(--deck-read-line);
689
+ font-weight: 400;
690
+ line-height: 1.4;
691
+ }
692
+
693
+ main.deck.is-reading .slide.big-number .value {
694
+ font-size: var(--text-2xl);
695
+ line-height: var(--lh-flat);
696
+ }
697
+
698
+ main.deck.is-reading .slide.big-number .caption {
699
+ max-width: none;
700
+ font-size: var(--text-sm);
701
+ line-height: 1.4;
702
+ }
703
+
704
+ /* A statement or a divider carries one short line, so it keeps a little
705
+ height and sits its text in the middle of it instead of at the top. */
706
+ main.deck.is-reading > .slide.statement,
707
+ main.deck.is-reading > .slide.section {
708
+ display: flex;
709
+ flex-direction: column;
710
+ justify-content: center;
711
+ min-height: 40vh;
712
+ }
713
+
714
+ /* ------------------------------------------------ reading containers -- */
715
+
716
+ /* Anything that holds a picture takes the width it is given, keeps its own
717
+ shape, and never reaches past the gutter. */
718
+ main.deck.is-reading .slide figure {
719
+ display: block;
720
+ margin: 0;
721
+ min-height: 0;
722
+ }
723
+
724
+ main.deck.is-reading .slide img,
725
+ main.deck.is-reading .slide svg,
726
+ main.deck.is-reading .slide video,
727
+ main.deck.is-reading .slide canvas {
728
+ display: block;
729
+ max-width: 100%;
730
+ height: auto;
731
+ }
732
+
733
+ /* A table keeps its columns and a code block keeps its lines: each one
734
+ scrolls sideways inside itself rather than widening the page. */
735
+ main.deck.is-reading .slide table {
736
+ display: block;
737
+ width: 100%;
738
+ max-width: 100%;
739
+ overflow-x: auto;
740
+ font-size: 15px;
741
+ line-height: 1.4;
742
+ }
743
+
744
+ main.deck.is-reading .slide th,
745
+ main.deck.is-reading .slide td {
746
+ padding: var(--space-03) var(--space-04);
747
+ }
748
+
749
+ main.deck.is-reading .slide pre {
750
+ max-width: 100%;
751
+ max-height: none;
752
+ padding: var(--space-05);
753
+ overflow-x: auto;
754
+ overflow-y: hidden;
755
+ font-size: 14px;
756
+ line-height: var(--lh-code);
757
+ white-space: pre;
758
+ word-break: normal;
759
+ tab-size: 2;
760
+ }
761
+
762
+ /* Two columns and two figures side by side are a landscape idea. On a phone
763
+ every grid is one column, in the order the deck wrote it. */
764
+ main.deck.is-reading > .slide.two-col,
765
+ main.deck.is-reading > .slide.three-col,
766
+ main.deck.is-reading > .slide .pair,
767
+ main.deck.is-reading > .slide .col,
768
+ main.deck.is-reading > .slide .cols,
769
+ main.deck.is-reading > .slide .grid,
770
+ main.deck.is-reading > .slide .gallery {
771
+ display: block;
772
+ grid-template-columns: none;
773
+ gap: 0;
774
+ min-height: 0;
775
+ }
776
+
777
+ main.deck.is-reading .slide.two-col > h2 {
778
+ grid-column: auto;
779
+ margin-bottom: var(--space-05);
780
+ }
781
+
782
+ main.deck.is-reading .slide .pair > figure + figure,
783
+ main.deck.is-reading .slide .col + .col {
784
+ margin-top: var(--space-06);
785
+ }
786
+
787
+ /* --------------------------------------------------- reading chrome --- */
788
+
789
+ /* A reader scrolls. A keypress it cannot give, so nothing waits for one, and
790
+ the notes and the counter are a presenter's furniture, not a reader's. */
791
+ main.deck.is-reading .step {
792
+ opacity: 1;
793
+ }
794
+
795
+ main.deck.is-reading aside.notes,
796
+ main.deck.is-reading .deck-hud {
797
+ display: none;
798
+ }
799
+ }
800
+
801
+ /* One slide at a time, for a room or for a phone turned back to presenting. */
802
+ .deck-present {
803
+ position: fixed;
804
+ z-index: 20;
805
+ right: max(16px, env(safe-area-inset-right, 16px));
806
+ bottom: max(16px, env(safe-area-inset-bottom, 16px));
807
+ min-width: 44px;
808
+ min-height: 44px;
809
+ padding: 0 14px;
810
+ font-family: var(--font-sans);
811
+ font-size: var(--text-2xs);
812
+ line-height: 1;
813
+ letter-spacing: 0;
814
+ color: var(--fg);
815
+ background: var(--bg);
816
+ border: 1px solid var(--border-subtle);
817
+ border-radius: var(--radius);
818
+ cursor: pointer;
819
+ -webkit-appearance: none;
820
+ appearance: none;
821
+ }
822
+
823
+ .deck-present[hidden] {
824
+ display: none;
825
+ }
826
+
827
+ /* A phone in present mode keeps the canvas on the window rather than in the
828
+ document: a 1280px box in the page widens a phone's layout viewport and
829
+ pushes the slide off the screen. */
830
+ @media (max-width: 699px) {
831
+ main.deck {
832
+ position: fixed;
833
+ inset: 0;
834
+ }
835
+ }
836
+
520
837
  @media (prefers-reduced-motion: reduce) {
521
838
  .step {
522
839
  transition: none;
@@ -574,9 +891,16 @@ aside.notes {
574
891
  }
575
892
 
576
893
  .deck-hud,
577
- aside.notes {
894
+ aside.notes,
895
+ .deck-present {
578
896
  display: none !important;
579
897
  }
898
+
899
+ /* Reading mode is a screen mode, so a page printed from a phone is the deck
900
+ on its canvas again: one slide, one sheet. Nothing here has to undo it. */
901
+ main.deck.is-reading {
902
+ overflow: visible;
903
+ }
580
904
  }
581
905
 
582
906
  /* Exact 16:9 sheet for one slide per page. */
@@ -309,6 +309,9 @@
309
309
  writeHash();
310
310
  syncNotes();
311
311
  syncPresenter();
312
+ if (reading) {
313
+ scrollToCurrent();
314
+ }
312
315
  }
313
316
 
314
317
  function go(n) {
@@ -619,6 +622,14 @@
619
622
  cycleMode();
620
623
  event.preventDefault();
621
624
  break;
625
+ case 'Escape':
626
+ // Out of one slide at a time, back to the stack the phone reads.
627
+ if (reading || forced !== null) {
628
+ forced = null;
629
+ setReading(wantsReading());
630
+ event.preventDefault();
631
+ }
632
+ break;
622
633
  default:
623
634
  break;
624
635
  }
@@ -673,6 +684,147 @@
673
684
  }
674
685
  }, { passive: true });
675
686
 
687
+ // ------------------------------------------------------------- reading --
688
+
689
+ // A phone held upright has no room for a 1280 x 720 canvas: the slide arrives
690
+ // as a 390 x 219 strip. deck.css stacks every slide down the page as a block
691
+ // at the width of the screen and reflows its type, so there is no scale to
692
+ // work out here; this is the part that decides when, and the small fixed
693
+ // button and Esc are how a reader switches.
694
+ var READING = window.matchMedia
695
+ ? window.matchMedia('(max-width: 699px) and (orientation: portrait)')
696
+ : null;
697
+ var reading = false;
698
+ var forced = null; // 'read', 'present', or null for whatever the device is
699
+ var toggleButton = null;
700
+
701
+ function wantsReading() {
702
+ if (forced === 'read') {
703
+ return true;
704
+ }
705
+ if (forced === 'present') {
706
+ return false;
707
+ }
708
+ return READING !== null && READING.matches;
709
+ }
710
+
711
+ function scrollToCurrent() {
712
+ if (!slides[current]) {
713
+ return;
714
+ }
715
+ var box = slides[current].getBoundingClientRect();
716
+ var top = (window.pageYOffset || document.documentElement.scrollTop || 0) + box.top;
717
+ window.scrollTo(0, top);
718
+ }
719
+
720
+ // Scrolling the stack is navigating it: the slide at the top of the window is
721
+ // the current one, so the counter and the URL follow the reader.
722
+ function followScroll() {
723
+ if (!reading) {
724
+ return;
725
+ }
726
+ var top = window.pageYOffset || document.documentElement.scrollTop || 0;
727
+ var at = 0;
728
+ var k;
729
+ var box;
730
+ for (k = 0; k < slides.length; k += 1) {
731
+ box = slides[k].getBoundingClientRect();
732
+ if (box.top <= 4) {
733
+ at = k;
734
+ }
735
+ }
736
+ if (top + window.innerHeight >= document.documentElement.scrollHeight - 4) {
737
+ at = slides.length - 1;
738
+ }
739
+ if (at === current) {
740
+ return;
741
+ }
742
+ current = at;
743
+ syncHud();
744
+ writeHash();
745
+ syncNotes();
746
+ syncPresenter();
747
+ }
748
+
749
+ function syncToggle() {
750
+ if (!toggleButton) {
751
+ return;
752
+ }
753
+ toggleButton.hidden = !reading && forced !== 'present';
754
+ toggleButton.textContent = reading ? 'Present' : 'Read';
755
+ toggleButton.setAttribute('aria-label', reading
756
+ ? 'Present: one slide at a time, Esc comes back to reading'
757
+ : 'Read: every slide down the page, Esc comes back to presenting');
758
+ }
759
+
760
+ function setReading(on) {
761
+ reading = on;
762
+ deck.classList.toggle('is-reading', on);
763
+ document.documentElement.classList.toggle('is-reading', on);
764
+ document.body.classList.toggle('is-reading', on);
765
+ syncToggle();
766
+ if (on) {
767
+ var k;
768
+ // The reader sees the whole deck, so no step waits for a key.
769
+ for (k = 0; k < slides.length; k += 1) {
770
+ revealed[k] = stepsOf(k).length;
771
+ }
772
+ render();
773
+ return;
774
+ }
775
+ fit();
776
+ render();
777
+ }
778
+
779
+ function flip(which) {
780
+ forced = which;
781
+ setReading(wantsReading());
782
+ }
783
+
784
+ function mountToggle() {
785
+ toggleButton = document.createElement('button');
786
+ toggleButton.type = 'button';
787
+ toggleButton.className = 'deck-present';
788
+ toggleButton.addEventListener('click', function () {
789
+ flip(reading ? 'present' : 'read');
790
+ });
791
+ document.body.appendChild(toggleButton);
792
+ syncToggle();
793
+ }
794
+
795
+ if (READING !== null) {
796
+ if (READING.addEventListener) {
797
+ READING.addEventListener('change', function () {
798
+ if (forced === null) {
799
+ setReading(wantsReading());
800
+ }
801
+ });
802
+ } else if (READING.addListener) {
803
+ READING.addListener(function () {
804
+ if (forced === null) {
805
+ setReading(wantsReading());
806
+ }
807
+ });
808
+ }
809
+ }
810
+
811
+ var followQueued = false;
812
+ window.addEventListener('scroll', function () {
813
+ if (!reading || followQueued) {
814
+ return;
815
+ }
816
+ followQueued = true;
817
+ var run = function () {
818
+ followQueued = false;
819
+ followScroll();
820
+ };
821
+ if (window.requestAnimationFrame) {
822
+ window.requestAnimationFrame(run);
823
+ } else {
824
+ run();
825
+ }
826
+ }, { passive: true });
827
+
676
828
  var resizeQueued = false;
677
829
  function onResize() {
678
830
  if (resizeQueued) {
@@ -681,7 +833,15 @@
681
833
  resizeQueued = true;
682
834
  var run = function () {
683
835
  resizeQueued = false;
684
- fit();
836
+ // A turn of the phone crosses the reading query: follow it, then lay the
837
+ // deck out for the mode it landed in.
838
+ if (forced === null && wantsReading() !== reading) {
839
+ setReading(wantsReading());
840
+ return;
841
+ }
842
+ if (!reading) {
843
+ fit();
844
+ }
685
845
  };
686
846
  if (window.requestAnimationFrame) {
687
847
  window.requestAnimationFrame(run);
@@ -715,6 +875,11 @@
715
875
  deck.appendChild(hud);
716
876
  }
717
877
  hud.setAttribute('aria-hidden', 'true');
878
+ // Reading mode numbers each slide "n / total", and CSS cannot count the
879
+ // deck, so the count is a property the stack reads. content takes a string,
880
+ // so the value carries its quotes and the label prints 3 and not a bare
881
+ // number the counter cannot join.
882
+ deck.style.setProperty('--deck-total', '"' + slides.length + '"');
718
883
 
719
884
  var match = /^#(\d+)$/.exec(window.location.hash);
720
885
  current = match ? clamp(parseInt(match[1], 10) - 1) : 0;
@@ -723,6 +888,9 @@
723
888
 
724
889
  fit();
725
890
  render();
891
+
892
+ mountToggle();
893
+ setReading(wantsReading());
726
894
  }
727
895
 
728
896
  window.rshowDeck = {
@@ -409,9 +409,11 @@
409
409
  <p>Every layout the theme ships, one frame each. A frame is lazy loaded, so
410
410
  the page stays light on a phone, and the link under it opens the same example
411
411
  full size. The deck, the page, the report, the project page, the poster and
412
- the dashboard are fixed templates with a documented markup. The mockups, the
413
- sketches, the terminals and the tuis are guided kits: they give an agent a
414
- frame, a floor and a set of rules, and the drawing is the work.</p>
412
+ the dashboard are documented templates: the markup is the starting point, and
413
+ a page may add its own layouts and classes on top, with colours from the
414
+ tokens. The mockups, the sketches, the terminals and the tuis are guided kits:
415
+ they give an agent a frame, a floor and a set of rules, and the drawing is the
416
+ work.</p>
415
417
 
416
418
  <div class="layouts">
417
419
  <article class="layout">