rshow 0.0.5 → 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.
@@ -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. */
@@ -228,6 +228,23 @@
228
228
  return anchor;
229
229
  }
230
230
 
231
+ // One cell of the theme's monospace font at the size the theme sets, measured
232
+ // on the page. A recording is fitted to the width only while its text stays at
233
+ // MIN_FONT; below that the frame keeps the page width and scrolls instead.
234
+ var MIN_FONT = 9;
235
+
236
+ function cellWidth(host) {
237
+ var probe = element('span', 'terminal-probe');
238
+ probe.style.cssText = 'position: absolute; top: 0; left: 0; visibility: hidden;' +
239
+ 'white-space: pre; font-family: ' + property(host, '--font-mono', 'monospace') +
240
+ '; font-size: ' + fontSize(host) + 'px';
241
+ probe.textContent = '0000000000';
242
+ (document.body || document.documentElement).appendChild(probe);
243
+ var width = probe.getBoundingClientRect().width / 10;
244
+ (document.body || document.documentElement).removeChild(probe);
245
+ return width > 0 ? width : 7.8;
246
+ }
247
+
231
248
  function build(host) {
232
249
  var src = text(host.getAttribute('data-src'));
233
250
  var title = text(host.getAttribute('data-title')) || (src === '' ? 'terminal' : fileName(src));
@@ -264,8 +281,9 @@
264
281
 
265
282
  // ----------------------------------------------------------------- ansi ---
266
283
 
267
- // A terminal window is as wide as its grid: the capture was taken at cols by
268
- // rows, so the frame is stretched to that width and no further.
284
+ // A terminal window is as wide as its grid, and never wider than the page: on a
285
+ // phone the frame keeps the width it is given and the grid scrolls inside the
286
+ // body, which is what terminal.css leaves overflow-x for.
269
287
  function hug(shell, body, term) {
270
288
  if (!term.element) {
271
289
  return;
@@ -276,7 +294,9 @@
276
294
  }
277
295
  var box = window.getComputedStyle(body);
278
296
  var side = (parseFloat(box.paddingLeft) || 0) + (parseFloat(box.paddingRight) || 0);
279
- shell.style.width = Math.ceil(width + side + 2) + 'px';
297
+ var wanted = Math.ceil(width + side + 2);
298
+ var room = shell.parentNode ? shell.parentNode.clientWidth : 0;
299
+ shell.style.width = (room > 0 && wanted > room ? room : wanted) + 'px';
280
300
  }
281
301
 
282
302
  function ansi(host, body, shell, src) {
@@ -339,7 +359,32 @@
339
359
 
340
360
  // ----------------------------------------------------------------- cast ---
341
361
 
362
+ // The grid a recording ran on: the figure says so, or the first line of the
363
+ // asciicast does, one JSON object with its width and height.
364
+ function castGrid(host, src, done) {
365
+ var cols = count(host.getAttribute('data-cols'), 0);
366
+ var rows = count(host.getAttribute('data-rows'), 0);
367
+ if (cols > 0 && rows > 0) {
368
+ done(cols, rows);
369
+ return;
370
+ }
371
+ window.fetch(src, { credentials: 'same-origin' }).then(function (reply) {
372
+ return reply.ok ? reply.text() : '';
373
+ }).then(function (cast) {
374
+ var head = {};
375
+ try {
376
+ head = JSON.parse(cast.split('\n')[0]) || {};
377
+ } catch (err) {
378
+ head = {};
379
+ }
380
+ done(count(head.width, 0), count(head.height, 0));
381
+ }, function () {
382
+ done(0, 0);
383
+ });
384
+ }
385
+
342
386
  function cast(host, body, src) {
387
+ var size = fontSize(host);
343
388
  var settings = {
344
389
  fit: 'width',
345
390
  controls: true,
@@ -348,19 +393,12 @@
348
393
  // rectangle: the prompt and the command being typed.
349
394
  poster: 'npt:0:02',
350
395
  terminalFontFamily: property(host, '--font-mono', 'monospace'),
351
- terminalFontSize: fontSize(host) + 'px',
396
+ terminalFontSize: size + 'px',
352
397
  terminalLineHeight: 1.4
353
398
  };
354
- var cols = count(host.getAttribute('data-cols'), 0);
355
- var rows = count(host.getAttribute('data-rows'), 0);
356
- if (cols > 0) {
357
- settings.cols = cols;
358
- }
359
- if (rows > 0) {
360
- settings.rows = rows;
361
- }
362
399
 
363
400
  var player = null;
401
+ var ready = false;
364
402
 
365
403
  function start(at) {
366
404
  var options = {};
@@ -378,11 +416,30 @@
378
416
  }
379
417
 
380
418
  loadPlayer().then(function () {
381
- start(0);
419
+ castGrid(host, src, function (cols, rows) {
420
+ if (cols > 0) {
421
+ settings.cols = cols;
422
+ }
423
+ if (rows > 0) {
424
+ settings.rows = rows;
425
+ }
426
+ // Fitting 100 columns into a phone asks for a 5px font. So the recording
427
+ // is fitted only while the fit keeps its text at MIN_FONT; below that
428
+ // fit is off at the theme's size and the body scrolls sideways.
429
+ if (cols > 0 && body.clientWidth > 0 &&
430
+ body.clientWidth / (cols * cellWidth(host)) * size < MIN_FONT) {
431
+ settings.fit = false;
432
+ }
433
+ ready = true;
434
+ start(0);
435
+ });
382
436
  // The player reads the palette once, when it mounts, so a mode change
383
437
  // means a new player, put back where the old one was.
384
438
  document.addEventListener('rshowmodechange', function () {
385
439
  var at = 0;
440
+ if (!ready) {
441
+ return;
442
+ }
386
443
  try {
387
444
  at = player && player.getCurrentTime ? player.getCurrentTime() : 0;
388
445
  } catch (err) {