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.
@@ -279,6 +279,74 @@ body {
279
279
  will-change: transform;
280
280
  }
281
281
 
282
+ /* The key list and the small button that toggles it. Both sit in the free
283
+ bottom left corner of the stage, over the canvas and never in its way. */
284
+
285
+ .mk-keys-toggle {
286
+ position: absolute;
287
+ left: 12px;
288
+ bottom: 12px;
289
+ z-index: 4;
290
+ min-height: 28px;
291
+ padding: 0 8px;
292
+ font: inherit;
293
+ font-size: 12px;
294
+ line-height: 1;
295
+ color: var(--muted, #525252);
296
+ background: var(--bg, #ffffff);
297
+ border: 1px solid var(--border-subtle, #e0e0e0);
298
+ border-radius: 0;
299
+ cursor: pointer;
300
+ opacity: 0.75;
301
+ -webkit-appearance: none;
302
+ appearance: none;
303
+ }
304
+
305
+ .mk-keys-toggle:hover,
306
+ .mk-keys-toggle[aria-expanded="true"] {
307
+ opacity: 1;
308
+ color: var(--fg, #161616);
309
+ border-color: var(--border-strong, #8d8d8d);
310
+ }
311
+
312
+ .mk-keys {
313
+ position: absolute;
314
+ left: 12px;
315
+ bottom: 48px;
316
+ z-index: 5;
317
+ width: 340px;
318
+ max-width: calc(100% - 24px);
319
+ padding: 12px 14px;
320
+ background: var(--bg, #ffffff);
321
+ border: 1px solid var(--border-subtle, #e0e0e0);
322
+ box-shadow: 0 2px 6px var(--border-subtle, #e0e0e0);
323
+ }
324
+
325
+ .mk-keys[hidden] {
326
+ display: none;
327
+ }
328
+
329
+ .mk-keys-list {
330
+ display: grid;
331
+ grid-template-columns: auto minmax(0, 1fr);
332
+ gap: 6px 16px;
333
+ margin: 0;
334
+ font-size: 13px;
335
+ line-height: 1.35;
336
+ }
337
+
338
+ .mk-keys-list dt {
339
+ font-family: var(--font-mono, ui-monospace, monospace);
340
+ font-size: 12px;
341
+ color: var(--fg, #161616);
342
+ white-space: nowrap;
343
+ }
344
+
345
+ .mk-keys-list dd {
346
+ margin: 0;
347
+ color: var(--muted, #525252);
348
+ }
349
+
282
350
  .mk-row {
283
351
  display: flex;
284
352
  align-items: flex-start;
@@ -307,6 +375,18 @@ body {
307
375
  --mk-h: 1112px;
308
376
  }
309
377
 
378
+ /* The board the keys picked. It is a choice in the canvas and not a second
379
+ frame around the screen a reader is already looking at. */
380
+ .artboard.is-selected {
381
+ outline: 2px solid var(--accent, #0f62fe);
382
+ outline-offset: 6px;
383
+ }
384
+
385
+ .mockup[data-view="focus"] .artboard.is-selected,
386
+ .mockup[data-view="play"] .artboard.is-selected {
387
+ outline: none;
388
+ }
389
+
310
390
  .mk-head {
311
391
  display: flex;
312
392
  align-items: center;
@@ -968,13 +1048,24 @@ body {
968
1048
  }
969
1049
 
970
1050
  @media (max-width: 520px) {
1051
+ /* A phone cannot hold the whole toolbar on one line: the groups wrap onto a
1052
+ second row, and a group that still does not fit shrinks to the row and
1053
+ scrolls inside itself, so no control ever sits past the right edge. */
971
1054
  .mk-toolbar {
972
- flex-wrap: nowrap;
973
1055
  gap: 2px 6px;
974
1056
  padding: 6px 10px;
975
1057
  overflow-x: auto;
976
1058
  }
977
1059
 
1060
+ .mk-toolbar > * {
1061
+ flex: 0 1 auto;
1062
+ min-width: 0;
1063
+ }
1064
+
1065
+ .mk-group {
1066
+ overflow-x: auto;
1067
+ }
1068
+
978
1069
  .mk-field-text {
979
1070
  display: none;
980
1071
  }
@@ -982,6 +1073,10 @@ body {
982
1073
  .mk-name {
983
1074
  font-size: 13px;
984
1075
  }
1076
+
1077
+ .mk-keys-toggle {
1078
+ min-height: 32px;
1079
+ }
985
1080
  }
986
1081
 
987
1082
  /* ---------------------------------------------------------------- views --- */
@@ -996,7 +1091,9 @@ body {
996
1091
  display: none;
997
1092
  }
998
1093
 
999
- .mockup[data-view="play"] .mk-side {
1094
+ .mockup[data-view="play"] .mk-side,
1095
+ .mockup[data-view="play"] .mk-keys,
1096
+ .mockup[data-view="play"] .mk-keys-toggle {
1000
1097
  display: none;
1001
1098
  }
1002
1099
  }
@@ -1029,10 +1126,16 @@ body {
1029
1126
  .mk-overlay,
1030
1127
  .mk-statebar,
1031
1128
  .mk-hint,
1129
+ .mk-keys,
1130
+ .mk-keys-toggle,
1032
1131
  .rshow-mode-toggle {
1033
1132
  display: none;
1034
1133
  }
1035
1134
 
1135
+ .artboard.is-selected {
1136
+ outline: none;
1137
+ }
1138
+
1036
1139
  .mk-stage {
1037
1140
  position: static;
1038
1141
  overflow: visible;
@@ -55,6 +55,13 @@
55
55
  * link holds a screen. Escape goes back to the canvas. ?skin= and ?view= and
56
56
  * ?screen= set the first frame, which is what a screenshot wants.
57
57
  *
58
+ * Keys
59
+ *
60
+ * w a s d and the arrows pan the canvas, + - 0 and z zoom it, [ ] and Tab
61
+ * pick a screen, Enter opens it and Escape goes back to the canvas. 1 to 9
62
+ * pick a state of the screen the keys are on, and ? lists every one of them.
63
+ * A field keeps its own keys and a chord with meta, ctrl or alt is never ours.
64
+ *
58
65
  * Comments are read and written at data-store, default ../_data/mockup-comments
59
66
  * relative to the page, and the store holds {value: [{id, artboard, text, at}]}.
60
67
  * The server records who wrote it: this file never sends an author.
@@ -464,7 +471,7 @@
464
471
  modeHost.appendChild(modeButton);
465
472
  toolbar.appendChild(modeHost);
466
473
 
467
- var hints = el('span', 'mk-hint', 'Drag to pan, Ctrl and wheel to zoom, Enter on a title to focus');
474
+ var hints = el('span', 'mk-hint', 'Drag to pan, Ctrl and wheel to zoom, ? for the keys');
468
475
  toolbar.appendChild(hints);
469
476
 
470
477
  // The bar that names the screen in focus and in play.
@@ -529,6 +536,54 @@
529
536
  main.appendChild(stage);
530
537
  main.appendChild(side);
531
538
 
539
+ // ---------------------------------------------------------------- keys ---
540
+
541
+ var KEYS = [
542
+ ['w a s d', 'Pan the canvas'],
543
+ ['arrow keys', 'Pan the canvas, or step between screens'],
544
+ ['Shift and a key', 'Pan a quarter of the stage'],
545
+ ['+ -', 'Zoom in and out'],
546
+ ['0', 'Fit every screen'],
547
+ ['z', 'Zoom to 100%'],
548
+ ['[ ] or Tab', 'Previous and next screen'],
549
+ ['Enter', 'Open the selected screen'],
550
+ ['1 to 9', 'Pick a state of that screen'],
551
+ ['Esc', 'Back to the canvas, or close this list'],
552
+ ['?', 'Show or hide this list']
553
+ ];
554
+
555
+ var keysOpen = false;
556
+
557
+ var keysPanel = el('div', 'mk-keys');
558
+ keysPanel.setAttribute('role', 'dialog');
559
+ keysPanel.setAttribute('aria-label', 'Keys');
560
+ keysPanel.hidden = true;
561
+ var keysList = el('dl', 'mk-keys-list');
562
+ KEYS.forEach(function (row) {
563
+ keysList.appendChild(el('dt', null, row[0]));
564
+ keysList.appendChild(el('dd', null, row[1]));
565
+ });
566
+ keysPanel.appendChild(keysList);
567
+ stage.appendChild(keysPanel);
568
+
569
+ // The corner button says what it is, so a reader who never guesses ? still
570
+ // finds the list. The pointerdown guard leaves the buttons of the stage alone.
571
+ var keysButton = el('button', 'mk-keys-toggle', '? keys');
572
+ keysButton.type = 'button';
573
+ keysButton.setAttribute('aria-label', 'Keys');
574
+ keysButton.setAttribute('aria-expanded', 'false');
575
+ keysButton.setAttribute('title', 'Every key (?)');
576
+ keysButton.addEventListener('click', function () {
577
+ setKeys(!keysOpen);
578
+ });
579
+ stage.appendChild(keysButton);
580
+
581
+ function setKeys(open) {
582
+ keysOpen = open === true;
583
+ keysPanel.hidden = !keysOpen;
584
+ keysButton.setAttribute('aria-expanded', keysOpen ? 'true' : 'false');
585
+ }
586
+
532
587
  // Rows: wide screens and phones pack side by side, everything stays in the
533
588
  // same visual order as the markup.
534
589
  function layoutRows() {
@@ -605,6 +660,7 @@
605
660
  }
606
661
 
607
662
  function fitAll() {
663
+ stopAnimation();
608
664
  var box = stageBox();
609
665
  var w = world.offsetWidth;
610
666
  var h = world.offsetHeight;
@@ -616,24 +672,33 @@
616
672
  applyTransform(true);
617
673
  }
618
674
 
619
- function clamp() {
675
+ function clampPoint(x, y) {
620
676
  var box = stageBox();
621
677
  var w = world.offsetWidth * zoom;
622
678
  var h = world.offsetHeight * zoom;
623
679
  var pad = 80;
680
+ var at = { x: x, y: y };
624
681
  if (w <= box.width) {
625
- tx = (box.width - w) / 2;
682
+ at.x = (box.width - w) / 2;
626
683
  } else {
627
- tx = Math.min(pad, Math.max(box.width - w - pad, tx));
684
+ at.x = Math.min(pad, Math.max(box.width - w - pad, at.x));
628
685
  }
629
686
  if (h <= box.height) {
630
- ty = (box.height - h) / 2;
687
+ at.y = (box.height - h) / 2;
631
688
  } else {
632
- ty = Math.min(pad, Math.max(box.height - h - pad, ty));
689
+ at.y = Math.min(pad, Math.max(box.height - h - pad, at.y));
633
690
  }
691
+ return at;
692
+ }
693
+
694
+ function clamp() {
695
+ var at = clampPoint(tx, ty);
696
+ tx = at.x;
697
+ ty = at.y;
634
698
  }
635
699
 
636
700
  function zoomTo(next, clientX, clientY) {
701
+ stopAnimation();
637
702
  var box = stageBox();
638
703
  var sx = clientX === undefined ? box.width / 2 : clientX - box.left;
639
704
  var sy = clientY === undefined ? box.height / 2 : clientY - box.top;
@@ -670,6 +735,144 @@
670
735
  zoomTo(0.1);
671
736
  }
672
737
 
738
+ // ------------------------------------------------------------ selection --
739
+
740
+ var selectedId = null;
741
+ var panAnimation = null;
742
+
743
+ function markSelected() {
744
+ for (var b = 0; b < boards.length; b += 1) {
745
+ boards[b].el.classList.toggle('is-selected', boards[b].id === selectedId);
746
+ }
747
+ }
748
+
749
+ function selectedBoard() {
750
+ return view === 'canvas' ? boardById(selectedId) : current();
751
+ }
752
+
753
+ function pickState(index) {
754
+ var board = selectedBoard();
755
+ if (!board) {
756
+ return false;
757
+ }
758
+ var names = [''].concat(board.states);
759
+ if (index < 0 || index >= names.length) {
760
+ return false;
761
+ }
762
+ setState(board, names[index]);
763
+ return true;
764
+ }
765
+
766
+ // Where a board sits in the stage, measured with the canvas transform out of
767
+ // the way, so a selection lands on it at whatever the zoom is.
768
+ function boardAt(board) {
769
+ var box = stageBox();
770
+ var saved = world.style.transform;
771
+ world.style.transform = 'translate(0px,0px) scale(1)';
772
+ var rect = board.el.getBoundingClientRect();
773
+ world.style.transform = saved;
774
+ return { x: rect.left - box.left, y: rect.top - box.top, w: rect.width, h: rect.height };
775
+ }
776
+
777
+ function stopAnimation() {
778
+ if (panAnimation !== null) {
779
+ if (typeof window.cancelAnimationFrame === 'function') {
780
+ window.cancelAnimationFrame(panAnimation.frame);
781
+ }
782
+ window.clearTimeout(panAnimation.timer);
783
+ panAnimation = null;
784
+ }
785
+ }
786
+
787
+ function animateTo(x, y) {
788
+ stopAnimation();
789
+ var run = { frame: null, timer: null };
790
+ panAnimation = run;
791
+
792
+ function finish() {
793
+ if (panAnimation !== run) {
794
+ return;
795
+ }
796
+ panAnimation = null;
797
+ tx = x;
798
+ ty = y;
799
+ applyTransform(true);
800
+ }
801
+
802
+ if (typeof window.requestAnimationFrame !== 'function') {
803
+ finish();
804
+ return;
805
+ }
806
+ var fromX = tx;
807
+ var fromY = ty;
808
+ var started = null;
809
+
810
+ function frame(now) {
811
+ if (panAnimation !== run) {
812
+ return;
813
+ }
814
+ var at = typeof now === 'number' ? now : 0;
815
+ if (started === null) {
816
+ started = at;
817
+ }
818
+ var t = Math.min(1, (at - started) / 220);
819
+ var eased = 1 - (1 - t) * (1 - t) * (1 - t);
820
+ tx = fromX + (x - fromX) * eased;
821
+ ty = fromY + (y - fromY) * eased;
822
+ applyTransform(true);
823
+ if (t < 1) {
824
+ run.frame = window.requestAnimationFrame(frame);
825
+ return;
826
+ }
827
+ finish();
828
+ }
829
+
830
+ run.frame = window.requestAnimationFrame(frame);
831
+ // A window that draws no frames, such as the headless one behind a
832
+ // screenshot, still lands on the screen the keys picked.
833
+ run.timer = window.setTimeout(finish, 320);
834
+ }
835
+
836
+ function centreOn(board, animate) {
837
+ var at = boardAt(board);
838
+ var box = stageBox();
839
+ var to = clampPoint(
840
+ (box.width - at.w * zoom) / 2 - at.x * zoom,
841
+ (box.height - at.h * zoom) / 2 - at.y * zoom
842
+ );
843
+ if (animate !== true) {
844
+ tx = to.x;
845
+ ty = to.y;
846
+ applyTransform(true);
847
+ return;
848
+ }
849
+ animateTo(to.x, to.y);
850
+ }
851
+
852
+ function selectBoard(index) {
853
+ var count = boards.length;
854
+ if (count === 0) {
855
+ return;
856
+ }
857
+ var at = ((index % count) + count) % count;
858
+ selectedId = boards[at].id;
859
+ markSelected();
860
+ if (view === 'canvas') {
861
+ centreOn(boards[at], true);
862
+ }
863
+ }
864
+
865
+ function stepSelection(delta) {
866
+ if (boards.length === 0) {
867
+ return;
868
+ }
869
+ var at = selectedId === null ? -1 : boardIndex(selectedId);
870
+ if (at < 0) {
871
+ at = delta < 0 ? boards.length : -1;
872
+ }
873
+ selectBoard(at + delta);
874
+ }
875
+
673
876
  function markCurrent() {
674
877
  for (var b = 0; b < boards.length; b += 1) {
675
878
  boards[b].el.classList.toggle('is-current', boards[b].id === currentId);
@@ -677,6 +880,7 @@
677
880
  }
678
881
 
679
882
  function layoutCurrent() {
883
+ stopAnimation();
680
884
  var board = current();
681
885
  if (!board) {
682
886
  return;
@@ -744,6 +948,8 @@
744
948
  canvasState = { zoom: zoom, tx: tx, ty: ty };
745
949
  }
746
950
  currentId = id;
951
+ selectedId = id;
952
+ markSelected();
747
953
  setView('focus');
748
954
  writeHash(id);
749
955
  }
@@ -756,13 +962,21 @@
756
962
  canvasState = { zoom: zoom, tx: tx, ty: ty };
757
963
  }
758
964
  currentId = id;
965
+ selectedId = id;
966
+ markSelected();
759
967
  setView('play');
760
968
  writeHash(id);
761
969
  }
762
970
 
763
971
  function backToCanvas() {
972
+ if (view !== 'canvas' && currentId !== null) {
973
+ // The screen the reader was on stays the selected one, so the keys can
974
+ // open it again or step from it.
975
+ selectedId = currentId;
976
+ }
764
977
  currentId = null;
765
978
  setView('canvas');
979
+ markSelected();
766
980
  writeHash('');
767
981
  zoom = canvasState.zoom;
768
982
  tx = canvasState.tx;
@@ -1105,6 +1319,17 @@
1105
1319
  var flashTimer = null;
1106
1320
  var suppressHash = false;
1107
1321
 
1322
+ // A control placed over the canvas owns its own pointer and its own keys.
1323
+ var CONTROLS =
1324
+ 'button, a[href], input, select, textarea, label, summary, [role="button"], [tabindex]:not([tabindex="-1"])';
1325
+
1326
+ function control(node) {
1327
+ if (!node || !node.closest) {
1328
+ return null;
1329
+ }
1330
+ return node.closest(CONTROLS);
1331
+ }
1332
+
1108
1333
  toolbar.addEventListener('click', function (event) {
1109
1334
  var button = event.target.closest ? event.target.closest('[data-mk]') : null;
1110
1335
  if (!button) {
@@ -1198,6 +1423,7 @@
1198
1423
  });
1199
1424
 
1200
1425
  function startPan(event) {
1426
+ stopAnimation();
1201
1427
  dragging = { x: event.clientX, y: event.clientY, tx: tx, ty: ty };
1202
1428
  stage.classList.add('is-panning');
1203
1429
  if (stage.setPointerCapture) {
@@ -1213,6 +1439,12 @@
1213
1439
  if (event.button !== 0) {
1214
1440
  return;
1215
1441
  }
1442
+ // A control above the screen keeps its own click: capturing the pointer
1443
+ // here would retarget that click to the stage.
1444
+ var taken = control(event.target);
1445
+ if (!spaceDown && taken !== null && taken !== stage) {
1446
+ return;
1447
+ }
1216
1448
  var inScreen = event.target.closest ? event.target.closest('.mk-screen') : null;
1217
1449
  if (!spaceDown && inScreen) {
1218
1450
  return;
@@ -1254,6 +1486,7 @@
1254
1486
  return;
1255
1487
  }
1256
1488
  event.preventDefault();
1489
+ stopAnimation();
1257
1490
  if (event.ctrlKey || event.metaKey) {
1258
1491
  zoomTo(zoom * (event.deltaY < 0 ? 1.12 : 1 / 1.12), event.clientX, event.clientY);
1259
1492
  return;
@@ -1274,6 +1507,32 @@
1274
1507
  return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || node.isContentEditable === true;
1275
1508
  }
1276
1509
 
1510
+ // Tab and Enter belong to the page while a reader is in the toolbar, the
1511
+ // panel or a screen: only the stage itself takes them over.
1512
+ function fromStage(node) {
1513
+ return node === document.body || node === document.documentElement || node === stage;
1514
+ }
1515
+
1516
+ function panSize(event) {
1517
+ if (event.shiftKey === true) {
1518
+ var box = stageBox();
1519
+ return { x: Math.round(box.width / 4), y: Math.round(box.height / 4) };
1520
+ }
1521
+ return { x: 64, y: 64 };
1522
+ }
1523
+
1524
+ // A screen taller than the stage scrolls under it. One that fits has nowhere
1525
+ // to go, so up and down leave it alone.
1526
+ function panTall(direction, event) {
1527
+ var board = current();
1528
+ var box = stageBox();
1529
+ if (!board || board.el.offsetHeight * zoom <= box.height) {
1530
+ return;
1531
+ }
1532
+ ty -= direction * panSize(event).y;
1533
+ applyTransform(false);
1534
+ }
1535
+
1277
1536
  document.addEventListener('keydown', function (event) {
1278
1537
  if (event.metaKey || event.ctrlKey || event.altKey || typing(event.target)) {
1279
1538
  return;
@@ -1286,6 +1545,29 @@
1286
1545
  }
1287
1546
  return;
1288
1547
  }
1548
+ if (key === '?') {
1549
+ event.preventDefault();
1550
+ setKeys(!keysOpen);
1551
+ return;
1552
+ }
1553
+ if (key === 'Escape') {
1554
+ if (keysOpen) {
1555
+ event.preventDefault();
1556
+ setKeys(false);
1557
+ return;
1558
+ }
1559
+ if (view !== 'canvas') {
1560
+ event.preventDefault();
1561
+ backToCanvas();
1562
+ }
1563
+ return;
1564
+ }
1565
+ if (key >= '1' && key <= '9' && key.length === 1) {
1566
+ if (pickState(Number(key) - 1)) {
1567
+ event.preventDefault();
1568
+ }
1569
+ return;
1570
+ }
1289
1571
  if (key === '+' || key === '=') {
1290
1572
  event.preventDefault();
1291
1573
  zoomStep(1);
@@ -1303,33 +1585,67 @@
1303
1585
  }
1304
1586
  return;
1305
1587
  }
1306
- if (key === 'Escape') {
1307
- if (view !== 'canvas') {
1588
+ if (key === 'z' || key === 'Z') {
1589
+ if (view === 'canvas') {
1308
1590
  event.preventDefault();
1309
- backToCanvas();
1591
+ zoomTo(1);
1310
1592
  }
1311
1593
  return;
1312
1594
  }
1313
- if (key === 'ArrowRight' || key === 'ArrowLeft') {
1314
- var delta = key === 'ArrowRight' ? 1 : -1;
1595
+ if (key === ']' || key === '[') {
1596
+ event.preventDefault();
1597
+ var order = key === ']' ? 1 : -1;
1315
1598
  if (view === 'canvas') {
1316
- tx -= delta * 64;
1317
- clamp();
1318
- applyTransform(true);
1599
+ stepSelection(order);
1319
1600
  } else {
1601
+ step(order);
1602
+ }
1603
+ return;
1604
+ }
1605
+ if (key === 'Tab') {
1606
+ if (view === 'canvas' && fromStage(event.target) && control(event.target) === null) {
1320
1607
  event.preventDefault();
1321
- step(delta);
1608
+ stepSelection(event.shiftKey === true ? -1 : 1);
1322
1609
  }
1323
1610
  return;
1324
1611
  }
1325
- if (key === 'ArrowDown' || key === 'ArrowUp') {
1326
- if (view !== 'canvas') {
1612
+ if (key === 'Enter') {
1613
+ if (view !== 'canvas' || selectedId === null || control(event.target) !== null) {
1327
1614
  return;
1328
1615
  }
1329
- ty -= (key === 'ArrowDown' ? 1 : -1) * 64;
1616
+ event.preventDefault();
1617
+ focus(selectedId);
1618
+ return;
1619
+ }
1620
+ var dx = 0;
1621
+ var dy = 0;
1622
+ if (key === 'ArrowRight' || key === 'd' || key === 'D') {
1623
+ dx = 1;
1624
+ } else if (key === 'ArrowLeft' || key === 'a' || key === 'A') {
1625
+ dx = -1;
1626
+ } else if (key === 'ArrowDown' || key === 's' || key === 'S') {
1627
+ dy = 1;
1628
+ } else if (key === 'ArrowUp' || key === 'w' || key === 'W') {
1629
+ dy = -1;
1630
+ }
1631
+ if (dx === 0 && dy === 0) {
1632
+ return;
1633
+ }
1634
+ event.preventDefault();
1635
+ if (view === 'canvas') {
1636
+ var stepSize = panSize(event);
1637
+ stopAnimation();
1638
+ tx -= dx * stepSize.x;
1639
+ ty -= dy * stepSize.y;
1330
1640
  clamp();
1331
1641
  applyTransform(true);
1642
+ return;
1643
+ }
1644
+ if (dx !== 0) {
1645
+ step(dx);
1646
+ return;
1332
1647
  }
1648
+ panTall(dy, event);
1333
1649
  });
1334
1650
 
1335
1651
  document.addEventListener('keyup', function (event) {
@@ -1412,6 +1728,12 @@
1412
1728
 
1413
1729
  var requested = query.get('screen') || window.location.hash.replace(/^#/, '');
1414
1730
  var startView = query.get('view');
1731
+ // A phone shows the whole canvas at 12%, which nobody can read. When the stage
1732
+ // is narrower than 700px and the URL asks for no view and no screen, the first
1733
+ // screen opens in focus view, fitted to the width by layoutCurrent. ?view=play,
1734
+ // ?view=canvas and #screen still say what they said.
1735
+ var narrow = stage.getBoundingClientRect().width < 700;
1736
+ var asked = startView !== null && startView !== '';
1415
1737
  main.setAttribute('data-view', 'canvas');
1416
1738
  fitAll();
1417
1739
  if (boards.length > 0) {
@@ -1425,6 +1747,8 @@
1425
1747
  play(boards[0].id);
1426
1748
  } else if (startView === 'focus') {
1427
1749
  focus(boards[0].id);
1750
+ } else if (narrow && !asked) {
1751
+ focus(boards[0].id);
1428
1752
  }
1429
1753
  }
1430
1754
 
@@ -241,6 +241,14 @@
241
241
  tab-size: 8;
242
242
  }
243
243
 
244
+ /* A recording the width cannot hold keeps the grid it was taken on and scrolls
245
+ here, rather than being fitted down to a font nobody can read. The player
246
+ clips its own box, so the scroll has to be said again one class deeper. */
247
+ .terminal .terminal-body .ap-player {
248
+ overflow-x: auto;
249
+ overscroll-behavior-x: contain;
250
+ }
251
+
244
252
  /* asciinema-player declares a dark palette of its own on .ap-player, which
245
253
  would win over anything inherited from the figure. Hand it the figure's
246
254
  palette instead, so a recording and a capture of the same program agree. */