@zoreal/oauth2-js 0.1.11 → 0.1.12

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.11";
62
+ var SDK_VERSION = "0.1.12";
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,109 +1012,121 @@ 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
-
1034
1015
  .${PREFIX}-qr-well {
1035
1016
  position: relative;
1036
- z-index: 1;
1037
1017
  display: grid;
1038
1018
  place-items: center;
1039
1019
  box-sizing: border-box;
1040
1020
  width: 204px;
1041
1021
  height: 204px;
1042
- margin: 0;
1022
+ margin: 20px auto 0;
1043
1023
  padding: 12px;
1044
1024
  border: 1px solid var(--zrl-line);
1045
- border-radius: 16px;
1025
+ border-radius: var(--zrl-radius);
1046
1026
  background: var(--zrl-qr-bg);
1027
+ /* The light on the edge takes its shape and colour from here. The colour
1028
+ is fixed, not themed: the brand blue on both grounds, a lighter tint of
1029
+ it at the head, no other hue. */
1030
+ --zrl-radius: 16px;
1031
+ --zrl-beam: #00b4d9;
1032
+ --zrl-beam-head: #7fe0f4;
1033
+ --zrl-beam-size: 100px;
1034
+ --zrl-beam-time: 4s;
1035
+ --zrl-glow-reach: 20px;
1036
+ --zrl-glow-core: 3px;
1047
1037
  }
1048
1038
 
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 {
1039
+ /* The light on the well's edge: a short comet running along the border, with
1040
+ a soft glow outside it. Three overlays inside the well, each masked so the
1041
+ comet can only ever paint where its mask allows, and the white interior lies
1042
+ outside every mask: nothing here can reach the quiet zone a camera needs,
1043
+ whatever the comet is doing. The mask is the padding box cut out of the
1044
+ border box, a transparent layer clipped to the padding box intersected
1045
+ with a solid one clipped to the border box. The prefixed form is for Chrome
1046
+ before 120 and Safari before 15.4; the unprefixed one, declared after it,
1047
+ wins everywhere else.
1048
+
1049
+ qr-beam keeps a 1px ring on the border line: the comet itself.
1050
+ qr-beam-glow is the glow: a wide band outside the well that blurs whatever
1051
+ is inside it, and inside it qr-beam-glow-band keeps a 3px ring with a
1052
+ second copy of the comet. The blur has to sit on the parent because a
1053
+ filter is applied before a mask: blurred on the band itself, the glow
1054
+ would be cut back to the band's own edge. On the parent it runs after the
1055
+ band has clipped the comet thin and before the parent's mask cuts away the
1056
+ inward half, which is what makes it fade outward and never over the QR.
1057
+ All three share one containing block, the well's padding box, so the two
1058
+ comets ride the same path; the spent badge is a later sibling and paints
1059
+ above them. */
1060
+ .${PREFIX}-qr-beam,
1061
+ .${PREFIX}-qr-beam-glow,
1062
+ .${PREFIX}-qr-beam-glow-band {
1056
1063
  position: absolute;
1057
- inset: -2px;
1064
+ inset: -1px;
1065
+ border: 1px solid transparent;
1066
+ border-radius: var(--zrl-radius);
1058
1067
  pointer-events: none;
1059
- filter: drop-shadow(0 0 3px var(--zrl-accent)) drop-shadow(0 0 10px var(--zrl-accent));
1068
+ -webkit-mask: linear-gradient(transparent, transparent), linear-gradient(#fff, #fff);
1069
+ -webkit-mask-clip: padding-box, border-box;
1070
+ -webkit-mask-composite: source-in;
1071
+ mask: linear-gradient(transparent, transparent), linear-gradient(#fff, #fff);
1072
+ mask-clip: padding-box, border-box;
1073
+ mask-composite: intersect;
1060
1074
  }
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;
1075
+ .${PREFIX}-qr-beam-glow {
1076
+ inset: calc(0px - var(--zrl-glow-reach));
1077
+ border-width: var(--zrl-glow-reach);
1078
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-reach));
1079
+ filter: blur(6px);
1080
+ opacity: 0.7;
1081
+ }
1082
+ .${PREFIX}-qr-beam-glow-band {
1083
+ inset: calc(0px - var(--zrl-glow-core));
1084
+ border-width: var(--zrl-glow-core);
1085
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-core));
1077
1086
  }
1078
- .${PREFIX}-qr-arc::before,
1079
- .${PREFIX}-qr-arc::after {
1087
+
1088
+ /* At rest the edge holds a dim, even blue: a 1px line on the border and, from
1089
+ the glow band, a soft halo outside it. Hidden while the comet runs, so the
1090
+ border reads as the well's own line with a light passing over it; shown
1091
+ once the light has stopped. Every path below ends here, which is what
1092
+ makes them look the same at rest. */
1093
+ .${PREFIX}-qr-beam::before,
1094
+ .${PREFIX}-qr-beam-glow-band::before {
1080
1095
  content: '';
1081
1096
  position: absolute;
1097
+ inset: -50%;
1098
+ background: var(--zrl-beam);
1099
+ opacity: 0;
1082
1100
  transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
1083
1101
  }
1084
1102
 
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 {
1103
+ /* The moving light. Declared bottom-up: the transform fallback here, and each
1104
+ @supports block further down replaces it where the browser allows. */
1105
+ .${PREFIX}-qr-beam::after,
1106
+ .${PREFIX}-qr-beam-glow-band::after {
1107
+ content: '';
1108
+ position: absolute;
1092
1109
  inset: -50%;
1093
1110
  background: conic-gradient(
1094
1111
  from var(--zrl-angle, 0deg),
1095
1112
  transparent 0deg 245deg,
1096
- var(--zrl-accent) 332deg 346deg,
1113
+ var(--zrl-beam) 332deg 346deg,
1097
1114
  transparent 356deg 360deg
1098
1115
  );
1099
1116
  animation: ${PREFIX}-orbit 3s linear infinite;
1117
+ transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
1100
1118
  }
1101
1119
 
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 {
1120
+ /* Spent: the light stops where it is and fades, and the edge settles to the
1121
+ dim glow. Paused rather than removed, so it does not jump back to its start
1122
+ on the way out. */
1123
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam::after,
1124
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam-glow-band::after {
1114
1125
  animation-play-state: paused;
1115
1126
  opacity: 0;
1116
1127
  }
1117
- .${PREFIX}-qr-halo[data-spent="true"] .${PREFIX}-qr-arc::after { opacity: 0.34; }
1128
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam::before { opacity: 0.55; }
1129
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam-glow-band::before { opacity: 0.7; }
1118
1130
 
1119
1131
  .${PREFIX}-qr {
1120
1132
  display: block;
@@ -1258,17 +1270,16 @@ var CSS = `
1258
1270
  70%, 100% { transform: scale(2.6); opacity: 0 }
1259
1271
  }
1260
1272
 
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.
1273
+ /* Three ways to move the light, one look at rest.
1274
+ Where offset-path takes a rect() (Chrome 116, Firefox 122, Safari 18) the
1275
+ comet rides the edge. Before that, a conic gradient turns inside the same
1276
+ masks instead: on a registered custom property where @property exists, and
1277
+ by rotating an oversized square where it does not, since an untyped custom
1278
+ property cannot interpolate and the light would snap once a cycle.
1268
1279
  There is no direct query for @property support. transition-behavior
1269
1280
  shipped after it in every engine (Chrome 117 vs 85, Firefox 129 vs 128,
1270
1281
  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.
1282
+ sends a capable browser down the transform tier, which looks the same.
1272
1283
  inherits: false so the angle stays on the element that animates it and
1273
1284
  never reaches the host page or the well. */
1274
1285
  @property --zrl-angle {
@@ -1278,12 +1289,34 @@ var CSS = `
1278
1289
  }
1279
1290
  @keyframes ${PREFIX}-orbit { to { transform: rotate(360deg) } }
1280
1291
  @keyframes ${PREFIX}-orbit-angle { to { --zrl-angle: 360deg } }
1292
+ @keyframes ${PREFIX}-beam { to { offset-distance: 100% } }
1281
1293
  @supports (transition-behavior: allow-discrete) {
1282
- .${PREFIX}-qr-arc::before {
1283
- inset: 0;
1294
+ .${PREFIX}-qr-beam::after {
1295
+ inset: -1px;
1296
+ animation-name: ${PREFIX}-orbit-angle;
1297
+ }
1298
+ .${PREFIX}-qr-beam-glow-band::after {
1299
+ inset: calc(0px - var(--zrl-glow-core));
1284
1300
  animation-name: ${PREFIX}-orbit-angle;
1285
1301
  }
1286
1302
  }
1303
+ /* The comet: a square that rides the well's edge on a rounded-rect path, its
1304
+ anchor a little ahead of centre, turned to face the way it travels, and
1305
+ masked down to what its overlay allows. The gradient runs against the
1306
+ direction of travel: a light head, the blue body, nothing behind. One lap
1307
+ in 4s, linear: continuous motion has no ease. */
1308
+ @supports (offset-path: rect(0 auto auto 0)) {
1309
+ .${PREFIX}-qr-beam::after,
1310
+ .${PREFIX}-qr-beam-glow-band::after {
1311
+ inset: auto;
1312
+ width: var(--zrl-beam-size);
1313
+ height: var(--zrl-beam-size);
1314
+ background: linear-gradient(to left, var(--zrl-beam-head), var(--zrl-beam), transparent);
1315
+ offset-path: rect(0 auto auto 0 round calc(var(--zrl-radius) - 1px));
1316
+ offset-anchor: 60% 50%;
1317
+ animation: ${PREFIX}-beam var(--zrl-beam-time) linear infinite;
1318
+ }
1319
+ }
1287
1320
 
1288
1321
  @media (prefers-reduced-motion: reduce) {
1289
1322
  .${PREFIX}-scrim,
@@ -1294,10 +1327,11 @@ var CSS = `
1294
1327
  .${PREFIX}-cancel,
1295
1328
  .${PREFIX}-close,
1296
1329
  .${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 }
1330
+ /* No travelling light; the edge keeps its dim static glow instead. */
1331
+ .${PREFIX}-qr-beam::after,
1332
+ .${PREFIX}-qr-beam-glow-band::after { animation: none; opacity: 0 }
1333
+ .${PREFIX}-qr-beam::before { opacity: 0.55 }
1334
+ .${PREFIX}-qr-beam-glow-band::before { opacity: 0.7 }
1301
1335
  }
1302
1336
  `;
1303
1337
  function ensureStyles() {
@@ -1402,11 +1436,13 @@ function mountPairingModal(state, options) {
1402
1436
  const title = el("h2", cx("title"));
1403
1437
  title.id = titleId;
1404
1438
  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")));
1409
1439
  const well = el("div", cx("qr-well"));
1440
+ const glow = el("span", cx("qr-beam-glow"));
1441
+ glow.setAttribute("aria-hidden", "true");
1442
+ glow.appendChild(el("span", cx("qr-beam-glow-band")));
1443
+ const beam = el("span", cx("qr-beam"));
1444
+ beam.setAttribute("aria-hidden", "true");
1445
+ well.append(glow, beam);
1410
1446
  const qr = el("img", cx("qr"));
1411
1447
  qr.alt = t.qrAlt;
1412
1448
  qr.width = 180;
@@ -1417,14 +1453,13 @@ function mountPairingModal(state, options) {
1417
1453
  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"));
1418
1454
  overlay.appendChild(badge);
1419
1455
  well.append(qr, overlay);
1420
- halo.append(glow, well);
1421
1456
  const status = el("div", cx("status"));
1422
1457
  const dot = el("span", cx("dot"));
1423
1458
  dot.append(el("i"), el("i"));
1424
1459
  const statusLabel = el("span");
1425
1460
  status.append(dot, statusLabel);
1426
1461
  const timer = el("p", cx("timer"));
1427
- body.append(mark, title, bodyText, halo, status, timer);
1462
+ body.append(mark, title, bodyText, well, status, timer);
1428
1463
  const help = el("div", cx("help"));
1429
1464
  help.append(el("p", cx("help-title"), t.noIdTitle), el("p", cx("help-body"), t.noIdBody));
1430
1465
  const footer = el("div", cx("footer"));
@@ -1466,7 +1501,7 @@ function mountPairingModal(state, options) {
1466
1501
  bodyText.textContent = s.status === "enrolling" ? t.bodyEnrolling : settled ? t.bodyApprove : t.bodyScan;
1467
1502
  statusLabel.textContent = settled ? t.waitingApproval : t.waiting;
1468
1503
  qr.dataset.spent = String(settled);
1469
- halo.dataset.spent = String(settled);
1504
+ well.dataset.spent = String(settled);
1470
1505
  overlay.style.display = settled ? "" : "none";
1471
1506
  if (s.qrUrl) showFrame(s.qrUrl);
1472
1507
  };