@zoreal/oauth2-js 0.1.10 → 0.1.11

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.
package/dist/index.cjs CHANGED
@@ -59,7 +59,7 @@ function unsafeClaims(idToken) {
59
59
 
60
60
  // src/wire.ts
61
61
  var WIRE_VERSION = 1;
62
- var SDK_VERSION = "0.1.10";
62
+ var SDK_VERSION = "0.1.11";
63
63
  var SDK_NAME = "@zoreal/oauth2-js";
64
64
  var DEFAULT_ISSUER = "https://id.zoreal.com";
65
65
  var POLL_INTERVAL_MS = 2e3;
@@ -1012,20 +1012,110 @@ var CSS = `
1012
1012
  color: var(--zrl-ink-soft);
1013
1013
  }
1014
1014
 
1015
+ /* The light around the well: a thin ring in the accent, one bright arc
1016
+ travelling round it, and a soft glow of the same colour bleeding a few
1017
+ pixels outward. It says the pairing is live without a word.
1018
+
1019
+ It sits BELOW the well, which is why the well is wrapped instead of given a
1020
+ pseudo-element. Anything drawn inside the well paints over the white quiet
1021
+ zone the camera needs, and a negative z-index from inside the well lands
1022
+ wherever the card's stacking context happens to be that frame (the card's
1023
+ entrance animation makes and unmakes one). The wrapper is the well's old
1024
+ box exactly, same size, same margin, so nothing moves; the well keeps its
1025
+ own stacking order on top, and the ring cannot touch the QR. */
1026
+ .${PREFIX}-qr-halo {
1027
+ position: relative;
1028
+ isolation: isolate;
1029
+ width: 204px;
1030
+ height: 204px;
1031
+ margin: 20px auto 0;
1032
+ }
1033
+
1015
1034
  .${PREFIX}-qr-well {
1016
1035
  position: relative;
1036
+ z-index: 1;
1017
1037
  display: grid;
1018
1038
  place-items: center;
1019
1039
  box-sizing: border-box;
1020
1040
  width: 204px;
1021
1041
  height: 204px;
1022
- margin: 20px auto 0;
1042
+ margin: 0;
1023
1043
  padding: 12px;
1024
1044
  border: 1px solid var(--zrl-line);
1025
1045
  border-radius: 16px;
1026
1046
  background: var(--zrl-qr-bg);
1027
1047
  }
1028
1048
 
1049
+ /* The glow is the ring's own alpha, blurred and drawn beneath it, so it
1050
+ follows the arc round and fades with it. It has to be a parent of the
1051
+ masked band: a filter is applied before a mask, so a shadow cast from the
1052
+ band itself would be cut off at the band's edge. Two shadows because one
1053
+ 2px source blurred over 10px is too faint to read as light; the second
1054
+ blurs the first. */
1055
+ .${PREFIX}-qr-glow {
1056
+ position: absolute;
1057
+ inset: -2px;
1058
+ pointer-events: none;
1059
+ filter: drop-shadow(0 0 3px var(--zrl-accent)) drop-shadow(0 0 10px var(--zrl-accent));
1060
+ }
1061
+
1062
+ /* The band: a 2px ring just outside the well, cut with a mask that keeps the
1063
+ padding and drops the content box. The gradient is on a pseudo-element
1064
+ rather than on the band, because in the fallback it has to rotate as a
1065
+ whole while the band stays put. The prefixed mask is for Chrome before 120;
1066
+ the unprefixed one, declared after it, wins everywhere else. */
1067
+ .${PREFIX}-qr-arc {
1068
+ position: absolute;
1069
+ inset: 0;
1070
+ padding: 2px;
1071
+ border-radius: 18px;
1072
+ overflow: hidden;
1073
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
1074
+ -webkit-mask-composite: xor;
1075
+ mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
1076
+ mask-composite: exclude;
1077
+ }
1078
+ .${PREFIX}-qr-arc::before,
1079
+ .${PREFIX}-qr-arc::after {
1080
+ content: '';
1081
+ position: absolute;
1082
+ transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
1083
+ }
1084
+
1085
+ /* The light. Mostly transparent, a tail that builds over a quarter turn, a
1086
+ short full-strength head and a drop-off within a few degrees, so it reads
1087
+ as travelling rather than blinking. One revolution every 3s, linear:
1088
+ continuous motion has no ease. Oversized so its corners still cover the
1089
+ band mid-rotation in the transform fallback; the property path shrinks it
1090
+ back to the band. */
1091
+ .${PREFIX}-qr-arc::before {
1092
+ inset: -50%;
1093
+ background: conic-gradient(
1094
+ from var(--zrl-angle, 0deg),
1095
+ transparent 0deg 245deg,
1096
+ var(--zrl-accent) 332deg 346deg,
1097
+ transparent 356deg 360deg
1098
+ );
1099
+ animation: ${PREFIX}-orbit 3s linear infinite;
1100
+ }
1101
+
1102
+ /* The rest state: the same ring, evenly lit. Faint under the moving light so
1103
+ the ring reads as a ring; stronger once the light has stopped. */
1104
+ .${PREFIX}-qr-arc::after {
1105
+ inset: 0;
1106
+ background: var(--zrl-accent);
1107
+ opacity: 0.16;
1108
+ }
1109
+
1110
+ /* Spent: the light stops where it is and fades, and the ring settles to a
1111
+ dim, even glow. Paused rather than removed, so it does not jump back to
1112
+ its start on the way out. */
1113
+ .${PREFIX}-qr-halo[data-spent="true"] .${PREFIX}-qr-arc::before {
1114
+ animation-play-state: paused;
1115
+ opacity: 0;
1116
+ }
1117
+ .${PREFIX}-qr-halo[data-spent="true"] .${PREFIX}-qr-arc::after { opacity: 0.34; }
1118
+
1029
1119
  .${PREFIX}-qr {
1030
1120
  display: block;
1031
1121
  width: 100%;
@@ -1168,6 +1258,33 @@ var CSS = `
1168
1258
  70%, 100% { transform: scale(2.6); opacity: 0 }
1169
1259
  }
1170
1260
 
1261
+ /* Two ways to turn the light, one look.
1262
+ Where the browser can register a typed custom property, the gradient's own
1263
+ angle animates and the element never moves. Where it cannot (Firefox before
1264
+ 128), an untyped custom property cannot interpolate and the light would
1265
+ snap once a cycle, so the oversized pseudo-element rotates instead and the
1266
+ band's mask keeps the ring in place. At rest the two are the same pixels:
1267
+ same gradient, same centre, angle zero.
1268
+ There is no direct query for @property support. transition-behavior
1269
+ shipped after it in every engine (Chrome 117 vs 85, Firefox 129 vs 128,
1270
+ Safari 17.4 vs 16.4), so it stands in: a yes is never wrong, and a no only
1271
+ sends a capable browser down the fallback, which looks the same.
1272
+ inherits: false so the angle stays on the element that animates it and
1273
+ never reaches the host page or the well. */
1274
+ @property --zrl-angle {
1275
+ syntax: '<angle>';
1276
+ inherits: false;
1277
+ initial-value: 0deg;
1278
+ }
1279
+ @keyframes ${PREFIX}-orbit { to { transform: rotate(360deg) } }
1280
+ @keyframes ${PREFIX}-orbit-angle { to { --zrl-angle: 360deg } }
1281
+ @supports (transition-behavior: allow-discrete) {
1282
+ .${PREFIX}-qr-arc::before {
1283
+ inset: 0;
1284
+ animation-name: ${PREFIX}-orbit-angle;
1285
+ }
1286
+ }
1287
+
1171
1288
  @media (prefers-reduced-motion: reduce) {
1172
1289
  .${PREFIX}-scrim,
1173
1290
  .${PREFIX}-card,
@@ -1177,6 +1294,10 @@ var CSS = `
1177
1294
  .${PREFIX}-cancel,
1178
1295
  .${PREFIX}-close,
1179
1296
  .${PREFIX}-timer { transition: none }
1297
+ /* No travelling light. The ring keeps a still, soft glow instead, a step
1298
+ brighter than the spent state so pending and spent still read apart. */
1299
+ .${PREFIX}-qr-arc::before { animation: none; opacity: 0 }
1300
+ .${PREFIX}-qr-arc::after { opacity: 0.5 }
1180
1301
  }
1181
1302
  `;
1182
1303
  function ensureStyles() {
@@ -1281,6 +1402,10 @@ function mountPairingModal(state, options) {
1281
1402
  const title = el("h2", cx("title"));
1282
1403
  title.id = titleId;
1283
1404
  const bodyText = el("p", cx("body-text"));
1405
+ const halo = el("div", cx("qr-halo"));
1406
+ const glow = el("span", cx("qr-glow"));
1407
+ glow.setAttribute("aria-hidden", "true");
1408
+ glow.appendChild(el("span", cx("qr-arc")));
1284
1409
  const well = el("div", cx("qr-well"));
1285
1410
  const qr = el("img", cx("qr"));
1286
1411
  qr.alt = t.qrAlt;
@@ -1292,13 +1417,14 @@ function mountPairingModal(state, options) {
1292
1417
  badge.appendChild(strokeIcon(24, ["M8.5 2h7a2.5 2.5 0 0 1 2.5 2.5v15a2.5 2.5 0 0 1-2.5 2.5h-7A2.5 2.5 0 0 1 6 19.5v-15A2.5 2.5 0 0 1 8.5 2Z", "M11 18.5h2"], "1.8"));
1293
1418
  overlay.appendChild(badge);
1294
1419
  well.append(qr, overlay);
1420
+ halo.append(glow, well);
1295
1421
  const status = el("div", cx("status"));
1296
1422
  const dot = el("span", cx("dot"));
1297
1423
  dot.append(el("i"), el("i"));
1298
1424
  const statusLabel = el("span");
1299
1425
  status.append(dot, statusLabel);
1300
1426
  const timer = el("p", cx("timer"));
1301
- body.append(mark, title, bodyText, well, status, timer);
1427
+ body.append(mark, title, bodyText, halo, status, timer);
1302
1428
  const help = el("div", cx("help"));
1303
1429
  help.append(el("p", cx("help-title"), t.noIdTitle), el("p", cx("help-body"), t.noIdBody));
1304
1430
  const footer = el("div", cx("footer"));
@@ -1340,6 +1466,7 @@ function mountPairingModal(state, options) {
1340
1466
  bodyText.textContent = s.status === "enrolling" ? t.bodyEnrolling : settled ? t.bodyApprove : t.bodyScan;
1341
1467
  statusLabel.textContent = settled ? t.waitingApproval : t.waiting;
1342
1468
  qr.dataset.spent = String(settled);
1469
+ halo.dataset.spent = String(settled);
1343
1470
  overlay.style.display = settled ? "" : "none";
1344
1471
  if (s.qrUrl) showFrame(s.qrUrl);
1345
1472
  };