@zoreal/oauth2-js 0.1.11 → 0.1.13

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.13";
63
63
  var SDK_NAME = "@zoreal/oauth2-js";
64
64
  var DEFAULT_ISSUER = "https://id.zoreal.com";
65
65
  var POLL_INTERVAL_MS = 2e3;
@@ -931,8 +931,20 @@ var LIGHT = `
931
931
  --zrl-accent-ink: #04698a;
932
932
  --zrl-urgent: #b4761a;
933
933
  --zrl-qr-bg: #ffffff;
934
+ --zrl-qr-filter: none;
935
+ --zrl-qr-spent-filter: blur(3px);
936
+ --zrl-qr-blend: normal;
934
937
  --zrl-shadow: 0 1px 2px rgba(16, 18, 27, 0.06), 0 20px 50px -12px rgba(16, 18, 27, 0.3);
935
938
  --zrl-ring: rgba(16, 18, 27, 0.07);
939
+ /* The light on the QR well's edge. Brand blue on both grounds, a lighter
940
+ tint at the head; only its strength is themed, see the dark block. */
941
+ --zrl-beam: #00b4d9;
942
+ --zrl-beam-head: #7fe0f4;
943
+ --zrl-beam-line: 2px;
944
+ --zrl-glow-core: 4px;
945
+ --zrl-glow-reach: 24px;
946
+ --zrl-glow-blur: 8px;
947
+ --zrl-glow-opacity: 0.6;
936
948
  `;
937
949
  var DARK = `
938
950
  --zrl-scrim: rgba(0, 0, 0, 0.62);
@@ -947,12 +959,24 @@ var DARK = `
947
959
  --zrl-accent-soft: #0d3b47;
948
960
  --zrl-accent-ink: #7fdcf0;
949
961
  --zrl-urgent: #e0a952;
950
- /* The QR well stays white in dark mode on purpose: a scanner needs the
951
- quiet-zone contrast, and an inverted QR fails on a good number of phone
952
- cameras. It reads as a deliberate light panel, not a theming miss. */
953
- --zrl-qr-bg: #ffffff;
962
+ /* The code is drawn light on the dark surface: the panel is transparent
963
+ and the image is inverted and screened, so only the modules and the
964
+ mark show. */
965
+ --zrl-qr-bg: transparent;
966
+ --zrl-qr-filter: invert(1);
967
+ --zrl-qr-spent-filter: invert(1) blur(3px);
968
+ --zrl-qr-blend: screen;
954
969
  --zrl-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 20px 50px -12px rgba(0, 0, 0, 0.65);
955
970
  --zrl-ring: rgba(255, 255, 255, 0.1);
971
+ /* A glow that reads on a white card disappears on a dark one: the light
972
+ here is brighter and wider, and its halo reaches further out. */
973
+ --zrl-beam: #22c8ec;
974
+ --zrl-beam-head: #c2f3fc;
975
+ --zrl-beam-line: 3px;
976
+ --zrl-glow-core: 6px;
977
+ --zrl-glow-reach: 32px;
978
+ --zrl-glow-blur: 10px;
979
+ --zrl-glow-opacity: 0.85;
956
980
  `;
957
981
  var CSS = `
958
982
  .${PREFIX}-root { ${LIGHT} }
@@ -1012,115 +1036,128 @@ var CSS = `
1012
1036
  color: var(--zrl-ink-soft);
1013
1037
  }
1014
1038
 
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
1039
  .${PREFIX}-qr-well {
1035
1040
  position: relative;
1036
- z-index: 1;
1037
1041
  display: grid;
1038
1042
  place-items: center;
1039
1043
  box-sizing: border-box;
1040
1044
  width: 204px;
1041
1045
  height: 204px;
1042
- margin: 0;
1046
+ margin: 20px auto 0;
1043
1047
  padding: 12px;
1044
1048
  border: 1px solid var(--zrl-line);
1045
- border-radius: 16px;
1049
+ border-radius: var(--zrl-radius);
1046
1050
  background: var(--zrl-qr-bg);
1051
+ /* The light on the edge takes its shape from here and its colour and
1052
+ strength from the theme tokens above. One lap in 4s on every tier. */
1053
+ --zrl-radius: 16px;
1054
+ --zrl-beam-time: 4s;
1047
1055
  }
1048
1056
 
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 {
1057
+ /* The light on the well's edge: a short comet running along the border, with
1058
+ a soft glow outside it. Three overlays inside the well, each masked so the
1059
+ comet can only ever paint where its mask allows, and the white interior lies
1060
+ outside every mask: nothing here can reach the quiet zone a camera needs,
1061
+ whatever the comet is doing. The mask is the padding box cut out of the
1062
+ border box, a transparent layer clipped to the padding box intersected
1063
+ with a solid one clipped to the border box. The prefixed form is for Chrome
1064
+ before 120 and Safari before 15.4; the unprefixed one, declared after it,
1065
+ wins everywhere else.
1066
+
1067
+ qr-beam keeps a thin ring on the border line: the comet itself.
1068
+ qr-beam-glow is the glow: a wide band outside the well that blurs whatever
1069
+ is inside it, and inside it qr-beam-glow-band keeps a 3px ring with a
1070
+ second copy of the comet. The blur has to sit on the parent because a
1071
+ filter is applied before a mask: blurred on the band itself, the glow
1072
+ would be cut back to the band's own edge. On the parent it runs after the
1073
+ band has clipped the comet thin and before the parent's mask cuts away the
1074
+ inward half, which is what makes it fade outward and never over the QR.
1075
+ All three share one containing block, the well's padding box, so the two
1076
+ comets ride the same path; the spent badge is a later sibling and paints
1077
+ above them. */
1078
+ .${PREFIX}-qr-beam,
1079
+ .${PREFIX}-qr-beam-glow,
1080
+ .${PREFIX}-qr-beam-glow-band {
1056
1081
  position: absolute;
1057
- inset: -2px;
1082
+ inset: calc(0px - var(--zrl-beam-line));
1083
+ border: var(--zrl-beam-line) solid transparent;
1084
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-beam-line));
1058
1085
  pointer-events: none;
1059
- filter: drop-shadow(0 0 3px var(--zrl-accent)) drop-shadow(0 0 10px var(--zrl-accent));
1086
+ -webkit-mask: linear-gradient(transparent, transparent), linear-gradient(#fff, #fff);
1087
+ -webkit-mask-clip: padding-box, border-box;
1088
+ -webkit-mask-composite: source-in;
1089
+ mask: linear-gradient(transparent, transparent), linear-gradient(#fff, #fff);
1090
+ mask-clip: padding-box, border-box;
1091
+ mask-composite: intersect;
1060
1092
  }
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;
1093
+ .${PREFIX}-qr-beam-glow {
1094
+ inset: calc(0px - var(--zrl-glow-reach));
1095
+ border-width: var(--zrl-glow-reach);
1096
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-reach));
1097
+ filter: blur(var(--zrl-glow-blur));
1098
+ opacity: var(--zrl-glow-opacity);
1099
+ will-change: filter;
1100
+ }
1101
+ .${PREFIX}-qr-beam-glow-band {
1102
+ inset: calc(0px - var(--zrl-glow-core));
1103
+ border-width: var(--zrl-glow-core);
1104
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-core));
1077
1105
  }
1078
- .${PREFIX}-qr-arc::before,
1079
- .${PREFIX}-qr-arc::after {
1106
+
1107
+ /* At rest the edge holds a dim, even blue: a 1px line on the border and, from
1108
+ the glow band, a soft halo outside it. Hidden while the comet runs, so the
1109
+ border reads as the well's own line with a light passing over it; shown
1110
+ once the light has stopped. Every path below ends here, which is what
1111
+ makes them look the same at rest. */
1112
+ .${PREFIX}-qr-beam::before,
1113
+ .${PREFIX}-qr-beam-glow-band::before {
1080
1114
  content: '';
1081
1115
  position: absolute;
1116
+ inset: -50%;
1117
+ background: var(--zrl-beam);
1118
+ opacity: 0;
1082
1119
  transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
1083
1120
  }
1084
1121
 
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 {
1122
+ /* The moving light: an oversized square carrying a conic sweep, rotated
1123
+ whole. A transform animation runs on the compositor, so the light keeps
1124
+ moving while the page is busy; animating the gradient angle instead
1125
+ repaints every frame on the main thread and stutters. */
1126
+ .${PREFIX}-qr-beam::after,
1127
+ .${PREFIX}-qr-beam-glow-band::after {
1128
+ content: '';
1129
+ position: absolute;
1092
1130
  inset: -50%;
1093
1131
  background: conic-gradient(
1094
- from var(--zrl-angle, 0deg),
1095
- transparent 0deg 245deg,
1096
- var(--zrl-accent) 332deg 346deg,
1132
+ from 0deg,
1133
+ transparent 0deg 220deg,
1134
+ var(--zrl-beam) 330deg,
1135
+ var(--zrl-beam-head) 348deg,
1097
1136
  transparent 356deg 360deg
1098
1137
  );
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;
1138
+ animation: ${PREFIX}-orbit var(--zrl-beam-time) linear infinite;
1139
+ will-change: transform;
1140
+ transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
1108
1141
  }
1109
1142
 
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 {
1143
+ /* Spent: the light stops where it is and fades, and the edge settles to the
1144
+ dim glow. Paused rather than removed, so it does not jump back to its start
1145
+ on the way out. */
1146
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam::after,
1147
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam-glow-band::after {
1114
1148
  animation-play-state: paused;
1115
1149
  opacity: 0;
1116
1150
  }
1117
- .${PREFIX}-qr-halo[data-spent="true"] .${PREFIX}-qr-arc::after { opacity: 0.34; }
1151
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam::before { opacity: 0.55; }
1152
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam-glow-band::before { opacity: 0.7; }
1118
1153
 
1119
1154
  .${PREFIX}-qr {
1120
1155
  display: block;
1121
1156
  width: 100%;
1122
1157
  height: 100%;
1123
1158
  border-radius: 8px;
1159
+ filter: var(--zrl-qr-filter);
1160
+ mix-blend-mode: var(--zrl-qr-blend);
1124
1161
  transition: filter 300ms cubic-bezier(0.23, 1, 0.32, 1),
1125
1162
  opacity 300ms cubic-bezier(0.23, 1, 0.32, 1),
1126
1163
  transform 300ms cubic-bezier(0.23, 1, 0.32, 1);
@@ -1129,7 +1166,7 @@ var CSS = `
1129
1166
  /* Once the code is claimed the QR is spent. Blurring it out rather than
1130
1167
  swapping it keeps one object on screen through the state change, so the eye
1131
1168
  reads a transformation instead of two things trading places. */
1132
- .${PREFIX}-qr[data-spent="true"] { opacity: 0.2; filter: blur(3px); transform: scale(0.96); }
1169
+ .${PREFIX}-qr[data-spent="true"] { opacity: 0.2; filter: var(--zrl-qr-spent-filter); transform: scale(0.96); }
1133
1170
 
1134
1171
  .${PREFIX}-qr-overlay {
1135
1172
  position: absolute;
@@ -1258,32 +1295,7 @@ var CSS = `
1258
1295
  70%, 100% { transform: scale(2.6); opacity: 0 }
1259
1296
  }
1260
1297
 
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
1298
  @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
1299
 
1288
1300
  @media (prefers-reduced-motion: reduce) {
1289
1301
  .${PREFIX}-scrim,
@@ -1294,10 +1306,11 @@ var CSS = `
1294
1306
  .${PREFIX}-cancel,
1295
1307
  .${PREFIX}-close,
1296
1308
  .${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 }
1309
+ /* No travelling light; the edge keeps its dim static glow instead. */
1310
+ .${PREFIX}-qr-beam::after,
1311
+ .${PREFIX}-qr-beam-glow-band::after { animation: none; opacity: 0 }
1312
+ .${PREFIX}-qr-beam::before { opacity: 0.55 }
1313
+ .${PREFIX}-qr-beam-glow-band::before { opacity: 0.7 }
1301
1314
  }
1302
1315
  `;
1303
1316
  function ensureStyles() {
@@ -1402,11 +1415,13 @@ function mountPairingModal(state, options) {
1402
1415
  const title = el("h2", cx("title"));
1403
1416
  title.id = titleId;
1404
1417
  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
1418
  const well = el("div", cx("qr-well"));
1419
+ const glow = el("span", cx("qr-beam-glow"));
1420
+ glow.setAttribute("aria-hidden", "true");
1421
+ glow.appendChild(el("span", cx("qr-beam-glow-band")));
1422
+ const beam = el("span", cx("qr-beam"));
1423
+ beam.setAttribute("aria-hidden", "true");
1424
+ well.append(glow, beam);
1410
1425
  const qr = el("img", cx("qr"));
1411
1426
  qr.alt = t.qrAlt;
1412
1427
  qr.width = 180;
@@ -1417,14 +1432,13 @@ function mountPairingModal(state, options) {
1417
1432
  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
1433
  overlay.appendChild(badge);
1419
1434
  well.append(qr, overlay);
1420
- halo.append(glow, well);
1421
1435
  const status = el("div", cx("status"));
1422
1436
  const dot = el("span", cx("dot"));
1423
1437
  dot.append(el("i"), el("i"));
1424
1438
  const statusLabel = el("span");
1425
1439
  status.append(dot, statusLabel);
1426
1440
  const timer = el("p", cx("timer"));
1427
- body.append(mark, title, bodyText, halo, status, timer);
1441
+ body.append(mark, title, bodyText, well, status, timer);
1428
1442
  const help = el("div", cx("help"));
1429
1443
  help.append(el("p", cx("help-title"), t.noIdTitle), el("p", cx("help-body"), t.noIdBody));
1430
1444
  const footer = el("div", cx("footer"));
@@ -1466,7 +1480,7 @@ function mountPairingModal(state, options) {
1466
1480
  bodyText.textContent = s.status === "enrolling" ? t.bodyEnrolling : settled ? t.bodyApprove : t.bodyScan;
1467
1481
  statusLabel.textContent = settled ? t.waitingApproval : t.waiting;
1468
1482
  qr.dataset.spent = String(settled);
1469
- halo.dataset.spent = String(settled);
1483
+ well.dataset.spent = String(settled);
1470
1484
  overlay.style.display = settled ? "" : "none";
1471
1485
  if (s.qrUrl) showFrame(s.qrUrl);
1472
1486
  };