contour-deck 0.2.1 → 0.2.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.
@@ -6,6 +6,8 @@ export interface RemoteLabels {
6
6
  remoteQr: string;
7
7
  remoteHint: string;
8
8
  notesQr: string;
9
+ /** Names the switch between the two QR codes. */
10
+ qrMode: string;
9
11
  notesHint: string;
10
12
  noRelay: string;
11
13
  relayConnecting: string;
@@ -4,6 +4,7 @@ export const defaultRemoteLabels = {
4
4
  remoteQr: "手機遙控",
5
5
  remoteHint: "掃描後手機會顯示確認碼,在這裡按允許才能控制。",
6
6
  notesQr: "講稿",
7
+ qrMode: "QR 碼用途",
7
8
  notesHint: "手機自己翻頁的講稿,不需要連線。",
8
9
  noRelay: "這個頁面沒有設定中繼服務,手機無法遙控;講稿與講者視窗仍可使用。",
9
10
  relayConnecting: "中繼連線中",
@@ -23,9 +23,13 @@ export function createPanel(options) {
23
23
  /** Requests and the paired phone: the part that changes. */
24
24
  const live = el.appendChild(h("div", "ct-rp-live", "", { "aria-live": "polite" }));
25
25
  live.style.display = "contents";
26
+ // One QR at a time: the phone remote or the notes, chosen with a switch above it.
27
+ const modes = el.appendChild(h("div", "ct-rp-modes", "", { role: "group", "aria-label": labels.qrMode }));
26
28
  const codes = el.appendChild(h("div", "ct-rp-qrs"));
27
- function tile(title, hint, link) {
28
- const figure = codes.appendChild(h("figure", "ct-rp-qr"));
29
+ const tiles = {};
30
+ function tile(mode, title, hint, link) {
31
+ const figure = codes.appendChild(h("figure", "ct-rp-qr", "", { "data-ct-qr": mode }));
32
+ tiles[mode] = { figure, switch: modes.appendChild(button(`qr:${mode}`, title)) };
29
33
  const image = h("div", "ct-rp-img");
30
34
  image.hidden = true;
31
35
  figure.append(image, h("h3", "", title), h("p", "", hint), h("a", "ct-rp-url", link, { href: link, target: "_blank", rel: "noopener" }));
@@ -41,10 +45,19 @@ export function createPanel(options) {
41
45
  .catch(() => { });
42
46
  }
43
47
  if (options.remoteLink)
44
- tile(labels.remoteQr, labels.remoteHint, options.remoteLink);
48
+ tile("remote", labels.remoteQr, labels.remoteHint, options.remoteLink);
45
49
  else
46
50
  codes.before(h("p", "", labels.noRelay));
47
- tile(labels.notesQr, labels.notesHint, options.notesLink);
51
+ tile("notes", labels.notesQr, labels.notesHint, options.notesLink);
52
+ // Without a relay there is only the notes QR, and nothing to switch.
53
+ modes.hidden = Object.keys(tiles).length < 2;
54
+ function showQr(mode) {
55
+ for (const [name, { figure, switch: control }] of Object.entries(tiles)) {
56
+ figure.hidden = name !== mode;
57
+ control.setAttribute("aria-pressed", String(name === mode));
58
+ }
59
+ }
60
+ showQr(options.remoteLink ? "remote" : "notes");
48
61
  const tools = el.appendChild(h("div", "ct-rp-row"));
49
62
  const time = h("b", "ct-rp-time", "0:00");
50
63
  const timer = h("span", "ct-rp-row");
@@ -63,6 +76,9 @@ export function createPanel(options) {
63
76
  };
64
77
  el.addEventListener("click", event => {
65
78
  const control = event.target.closest("[data-ct-rp]");
79
+ const action = control?.dataset.ctRp ?? "";
80
+ if (action.startsWith("qr:"))
81
+ return showQr(action.slice(3));
66
82
  if (control)
67
83
  actions[control.dataset.ctRp ?? ""]?.(control.dataset.ctId ?? "");
68
84
  });
@@ -26,8 +26,10 @@ const css = `
26
26
  .ct-rp-code { font: 800 1.9rem/1 var(--ct-mono); letter-spacing: .14em; margin-right: auto; }
27
27
  .ct-rp-row { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
28
28
  .ct-rp-row > :last-child { margin-left: auto; }
29
- .ct-rp-qrs { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; }
30
- .ct-rp-qr { margin: 0; display: flex; flex-direction: column; gap: .35rem; min-width: 0; }
29
+ .ct-rp-modes { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; }
30
+ .ct-rp-modes[hidden], .ct-rp-qr[hidden] { display: none; }
31
+ .ct-rp-qrs { display: grid; justify-items: center; }
32
+ .ct-rp-qr { margin: 0; display: flex; flex-direction: column; gap: .35rem; min-width: 0; width: min(100%, 15rem); }
31
33
  .ct-rp-img { aspect-ratio: 1; background: #fff; border: 1px solid var(--ct-border); }
32
34
  .ct-rp-img svg { display: block; width: 100%; height: 100%; }
33
35
  .ct-rp-url { font: .62rem/1.35 var(--ct-mono); color: var(--ct-muted); overflow-wrap: anywhere; }
@@ -40,6 +42,8 @@ const css = `
40
42
  .ct-pv { position: relative; overflow: hidden; aspect-ratio: var(--ct-ratio, 16 / 9); background: var(--ct-bg); border: 1px solid var(--ct-border); }
41
43
  .ct-app .ct-pv > .ct-stage { inset: auto; left: 0; top: 0; width: 1280px; height: calc(1280px / var(--ct-ratio, 16 / 9)); transform: scale(var(--ct-k, .25)); transform-origin: 0 0; border: 0; z-index: 0; visibility: visible; pointer-events: none; }
42
44
  .ct-pvw { height: 100%; overflow: hidden; }
45
+ /* The copy shows the page as it stands, revealed: a host's entrance motion would hold it at its first frame (often transparent), since the copy is never the current page. */
46
+ .ct-pv.ct-pv > .ct-stage *, .ct-pv.ct-pv > .ct-stage *::before, .ct-pv.ct-pv > .ct-stage *::after { animation: none !important; transition: none !important; }
43
47
 
44
48
  /* ---- the phone ---- */
45
49
  .ct-app.ct-rmt { display: flex; flex-direction: column; grid-template-rows: none; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); font-size: 1rem; -webkit-user-select: none; user-select: none; touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contour-deck",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Outline-first slide decks on real DOM: blocks, slides, pages.",
5
5
  "keywords": [
6
6
  "slides",