@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.d.cts CHANGED
@@ -289,7 +289,7 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
289
289
  * auth-code mode leaves it to the RP backend.
290
290
  */
291
291
  declare const WIRE_VERSION = 1;
292
- declare const SDK_VERSION = "0.1.11";
292
+ declare const SDK_VERSION = "0.1.13";
293
293
  declare const SDK_NAME = "@zoreal/oauth2-js";
294
294
  declare const DEFAULT_ISSUER = "https://id.zoreal.com";
295
295
  /** Pending TTL is short. Poll gently; over-polling cancels the request. */
package/dist/index.d.ts CHANGED
@@ -289,7 +289,7 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
289
289
  * auth-code mode leaves it to the RP backend.
290
290
  */
291
291
  declare const WIRE_VERSION = 1;
292
- declare const SDK_VERSION = "0.1.11";
292
+ declare const SDK_VERSION = "0.1.13";
293
293
  declare const SDK_NAME = "@zoreal/oauth2-js";
294
294
  declare const DEFAULT_ISSUER = "https://id.zoreal.com";
295
295
  /** Pending TTL is short. Poll gently; over-polling cancels the request. */
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ function unsafeClaims(idToken) {
14
14
 
15
15
  // src/wire.ts
16
16
  var WIRE_VERSION = 1;
17
- var SDK_VERSION = "0.1.11";
17
+ var SDK_VERSION = "0.1.13";
18
18
  var SDK_NAME = "@zoreal/oauth2-js";
19
19
  var DEFAULT_ISSUER = "https://id.zoreal.com";
20
20
  var POLL_INTERVAL_MS = 2e3;
@@ -886,8 +886,20 @@ var LIGHT = `
886
886
  --zrl-accent-ink: #04698a;
887
887
  --zrl-urgent: #b4761a;
888
888
  --zrl-qr-bg: #ffffff;
889
+ --zrl-qr-filter: none;
890
+ --zrl-qr-spent-filter: blur(3px);
891
+ --zrl-qr-blend: normal;
889
892
  --zrl-shadow: 0 1px 2px rgba(16, 18, 27, 0.06), 0 20px 50px -12px rgba(16, 18, 27, 0.3);
890
893
  --zrl-ring: rgba(16, 18, 27, 0.07);
894
+ /* The light on the QR well's edge. Brand blue on both grounds, a lighter
895
+ tint at the head; only its strength is themed, see the dark block. */
896
+ --zrl-beam: #00b4d9;
897
+ --zrl-beam-head: #7fe0f4;
898
+ --zrl-beam-line: 2px;
899
+ --zrl-glow-core: 4px;
900
+ --zrl-glow-reach: 24px;
901
+ --zrl-glow-blur: 8px;
902
+ --zrl-glow-opacity: 0.6;
891
903
  `;
892
904
  var DARK = `
893
905
  --zrl-scrim: rgba(0, 0, 0, 0.62);
@@ -902,12 +914,24 @@ var DARK = `
902
914
  --zrl-accent-soft: #0d3b47;
903
915
  --zrl-accent-ink: #7fdcf0;
904
916
  --zrl-urgent: #e0a952;
905
- /* The QR well stays white in dark mode on purpose: a scanner needs the
906
- quiet-zone contrast, and an inverted QR fails on a good number of phone
907
- cameras. It reads as a deliberate light panel, not a theming miss. */
908
- --zrl-qr-bg: #ffffff;
917
+ /* The code is drawn light on the dark surface: the panel is transparent
918
+ and the image is inverted and screened, so only the modules and the
919
+ mark show. */
920
+ --zrl-qr-bg: transparent;
921
+ --zrl-qr-filter: invert(1);
922
+ --zrl-qr-spent-filter: invert(1) blur(3px);
923
+ --zrl-qr-blend: screen;
909
924
  --zrl-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 20px 50px -12px rgba(0, 0, 0, 0.65);
910
925
  --zrl-ring: rgba(255, 255, 255, 0.1);
926
+ /* A glow that reads on a white card disappears on a dark one: the light
927
+ here is brighter and wider, and its halo reaches further out. */
928
+ --zrl-beam: #22c8ec;
929
+ --zrl-beam-head: #c2f3fc;
930
+ --zrl-beam-line: 3px;
931
+ --zrl-glow-core: 6px;
932
+ --zrl-glow-reach: 32px;
933
+ --zrl-glow-blur: 10px;
934
+ --zrl-glow-opacity: 0.85;
911
935
  `;
912
936
  var CSS = `
913
937
  .${PREFIX}-root { ${LIGHT} }
@@ -967,115 +991,128 @@ var CSS = `
967
991
  color: var(--zrl-ink-soft);
968
992
  }
969
993
 
970
- /* The light around the well: a thin ring in the accent, one bright arc
971
- travelling round it, and a soft glow of the same colour bleeding a few
972
- pixels outward. It says the pairing is live without a word.
973
-
974
- It sits BELOW the well, which is why the well is wrapped instead of given a
975
- pseudo-element. Anything drawn inside the well paints over the white quiet
976
- zone the camera needs, and a negative z-index from inside the well lands
977
- wherever the card's stacking context happens to be that frame (the card's
978
- entrance animation makes and unmakes one). The wrapper is the well's old
979
- box exactly, same size, same margin, so nothing moves; the well keeps its
980
- own stacking order on top, and the ring cannot touch the QR. */
981
- .${PREFIX}-qr-halo {
982
- position: relative;
983
- isolation: isolate;
984
- width: 204px;
985
- height: 204px;
986
- margin: 20px auto 0;
987
- }
988
-
989
994
  .${PREFIX}-qr-well {
990
995
  position: relative;
991
- z-index: 1;
992
996
  display: grid;
993
997
  place-items: center;
994
998
  box-sizing: border-box;
995
999
  width: 204px;
996
1000
  height: 204px;
997
- margin: 0;
1001
+ margin: 20px auto 0;
998
1002
  padding: 12px;
999
1003
  border: 1px solid var(--zrl-line);
1000
- border-radius: 16px;
1004
+ border-radius: var(--zrl-radius);
1001
1005
  background: var(--zrl-qr-bg);
1006
+ /* The light on the edge takes its shape from here and its colour and
1007
+ strength from the theme tokens above. One lap in 4s on every tier. */
1008
+ --zrl-radius: 16px;
1009
+ --zrl-beam-time: 4s;
1002
1010
  }
1003
1011
 
1004
- /* The glow is the ring's own alpha, blurred and drawn beneath it, so it
1005
- follows the arc round and fades with it. It has to be a parent of the
1006
- masked band: a filter is applied before a mask, so a shadow cast from the
1007
- band itself would be cut off at the band's edge. Two shadows because one
1008
- 2px source blurred over 10px is too faint to read as light; the second
1009
- blurs the first. */
1010
- .${PREFIX}-qr-glow {
1012
+ /* The light on the well's edge: a short comet running along the border, with
1013
+ a soft glow outside it. Three overlays inside the well, each masked so the
1014
+ comet can only ever paint where its mask allows, and the white interior lies
1015
+ outside every mask: nothing here can reach the quiet zone a camera needs,
1016
+ whatever the comet is doing. The mask is the padding box cut out of the
1017
+ border box, a transparent layer clipped to the padding box intersected
1018
+ with a solid one clipped to the border box. The prefixed form is for Chrome
1019
+ before 120 and Safari before 15.4; the unprefixed one, declared after it,
1020
+ wins everywhere else.
1021
+
1022
+ qr-beam keeps a thin ring on the border line: the comet itself.
1023
+ qr-beam-glow is the glow: a wide band outside the well that blurs whatever
1024
+ is inside it, and inside it qr-beam-glow-band keeps a 3px ring with a
1025
+ second copy of the comet. The blur has to sit on the parent because a
1026
+ filter is applied before a mask: blurred on the band itself, the glow
1027
+ would be cut back to the band's own edge. On the parent it runs after the
1028
+ band has clipped the comet thin and before the parent's mask cuts away the
1029
+ inward half, which is what makes it fade outward and never over the QR.
1030
+ All three share one containing block, the well's padding box, so the two
1031
+ comets ride the same path; the spent badge is a later sibling and paints
1032
+ above them. */
1033
+ .${PREFIX}-qr-beam,
1034
+ .${PREFIX}-qr-beam-glow,
1035
+ .${PREFIX}-qr-beam-glow-band {
1011
1036
  position: absolute;
1012
- inset: -2px;
1037
+ inset: calc(0px - var(--zrl-beam-line));
1038
+ border: var(--zrl-beam-line) solid transparent;
1039
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-beam-line));
1013
1040
  pointer-events: none;
1014
- filter: drop-shadow(0 0 3px var(--zrl-accent)) drop-shadow(0 0 10px var(--zrl-accent));
1041
+ -webkit-mask: linear-gradient(transparent, transparent), linear-gradient(#fff, #fff);
1042
+ -webkit-mask-clip: padding-box, border-box;
1043
+ -webkit-mask-composite: source-in;
1044
+ mask: linear-gradient(transparent, transparent), linear-gradient(#fff, #fff);
1045
+ mask-clip: padding-box, border-box;
1046
+ mask-composite: intersect;
1015
1047
  }
1016
-
1017
- /* The band: a 2px ring just outside the well, cut with a mask that keeps the
1018
- padding and drops the content box. The gradient is on a pseudo-element
1019
- rather than on the band, because in the fallback it has to rotate as a
1020
- whole while the band stays put. The prefixed mask is for Chrome before 120;
1021
- the unprefixed one, declared after it, wins everywhere else. */
1022
- .${PREFIX}-qr-arc {
1023
- position: absolute;
1024
- inset: 0;
1025
- padding: 2px;
1026
- border-radius: 18px;
1027
- overflow: hidden;
1028
- -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
1029
- -webkit-mask-composite: xor;
1030
- mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
1031
- mask-composite: exclude;
1048
+ .${PREFIX}-qr-beam-glow {
1049
+ inset: calc(0px - var(--zrl-glow-reach));
1050
+ border-width: var(--zrl-glow-reach);
1051
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-reach));
1052
+ filter: blur(var(--zrl-glow-blur));
1053
+ opacity: var(--zrl-glow-opacity);
1054
+ will-change: filter;
1055
+ }
1056
+ .${PREFIX}-qr-beam-glow-band {
1057
+ inset: calc(0px - var(--zrl-glow-core));
1058
+ border-width: var(--zrl-glow-core);
1059
+ border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-core));
1032
1060
  }
1033
- .${PREFIX}-qr-arc::before,
1034
- .${PREFIX}-qr-arc::after {
1061
+
1062
+ /* At rest the edge holds a dim, even blue: a 1px line on the border and, from
1063
+ the glow band, a soft halo outside it. Hidden while the comet runs, so the
1064
+ border reads as the well's own line with a light passing over it; shown
1065
+ once the light has stopped. Every path below ends here, which is what
1066
+ makes them look the same at rest. */
1067
+ .${PREFIX}-qr-beam::before,
1068
+ .${PREFIX}-qr-beam-glow-band::before {
1035
1069
  content: '';
1036
1070
  position: absolute;
1071
+ inset: -50%;
1072
+ background: var(--zrl-beam);
1073
+ opacity: 0;
1037
1074
  transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
1038
1075
  }
1039
1076
 
1040
- /* The light. Mostly transparent, a tail that builds over a quarter turn, a
1041
- short full-strength head and a drop-off within a few degrees, so it reads
1042
- as travelling rather than blinking. One revolution every 3s, linear:
1043
- continuous motion has no ease. Oversized so its corners still cover the
1044
- band mid-rotation in the transform fallback; the property path shrinks it
1045
- back to the band. */
1046
- .${PREFIX}-qr-arc::before {
1077
+ /* The moving light: an oversized square carrying a conic sweep, rotated
1078
+ whole. A transform animation runs on the compositor, so the light keeps
1079
+ moving while the page is busy; animating the gradient angle instead
1080
+ repaints every frame on the main thread and stutters. */
1081
+ .${PREFIX}-qr-beam::after,
1082
+ .${PREFIX}-qr-beam-glow-band::after {
1083
+ content: '';
1084
+ position: absolute;
1047
1085
  inset: -50%;
1048
1086
  background: conic-gradient(
1049
- from var(--zrl-angle, 0deg),
1050
- transparent 0deg 245deg,
1051
- var(--zrl-accent) 332deg 346deg,
1087
+ from 0deg,
1088
+ transparent 0deg 220deg,
1089
+ var(--zrl-beam) 330deg,
1090
+ var(--zrl-beam-head) 348deg,
1052
1091
  transparent 356deg 360deg
1053
1092
  );
1054
- animation: ${PREFIX}-orbit 3s linear infinite;
1055
- }
1056
-
1057
- /* The rest state: the same ring, evenly lit. Faint under the moving light so
1058
- the ring reads as a ring; stronger once the light has stopped. */
1059
- .${PREFIX}-qr-arc::after {
1060
- inset: 0;
1061
- background: var(--zrl-accent);
1062
- opacity: 0.16;
1093
+ animation: ${PREFIX}-orbit var(--zrl-beam-time) linear infinite;
1094
+ will-change: transform;
1095
+ transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
1063
1096
  }
1064
1097
 
1065
- /* Spent: the light stops where it is and fades, and the ring settles to a
1066
- dim, even glow. Paused rather than removed, so it does not jump back to
1067
- its start on the way out. */
1068
- .${PREFIX}-qr-halo[data-spent="true"] .${PREFIX}-qr-arc::before {
1098
+ /* Spent: the light stops where it is and fades, and the edge settles to the
1099
+ dim glow. Paused rather than removed, so it does not jump back to its start
1100
+ on the way out. */
1101
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam::after,
1102
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam-glow-band::after {
1069
1103
  animation-play-state: paused;
1070
1104
  opacity: 0;
1071
1105
  }
1072
- .${PREFIX}-qr-halo[data-spent="true"] .${PREFIX}-qr-arc::after { opacity: 0.34; }
1106
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam::before { opacity: 0.55; }
1107
+ .${PREFIX}-qr-well[data-spent="true"] .${PREFIX}-qr-beam-glow-band::before { opacity: 0.7; }
1073
1108
 
1074
1109
  .${PREFIX}-qr {
1075
1110
  display: block;
1076
1111
  width: 100%;
1077
1112
  height: 100%;
1078
1113
  border-radius: 8px;
1114
+ filter: var(--zrl-qr-filter);
1115
+ mix-blend-mode: var(--zrl-qr-blend);
1079
1116
  transition: filter 300ms cubic-bezier(0.23, 1, 0.32, 1),
1080
1117
  opacity 300ms cubic-bezier(0.23, 1, 0.32, 1),
1081
1118
  transform 300ms cubic-bezier(0.23, 1, 0.32, 1);
@@ -1084,7 +1121,7 @@ var CSS = `
1084
1121
  /* Once the code is claimed the QR is spent. Blurring it out rather than
1085
1122
  swapping it keeps one object on screen through the state change, so the eye
1086
1123
  reads a transformation instead of two things trading places. */
1087
- .${PREFIX}-qr[data-spent="true"] { opacity: 0.2; filter: blur(3px); transform: scale(0.96); }
1124
+ .${PREFIX}-qr[data-spent="true"] { opacity: 0.2; filter: var(--zrl-qr-spent-filter); transform: scale(0.96); }
1088
1125
 
1089
1126
  .${PREFIX}-qr-overlay {
1090
1127
  position: absolute;
@@ -1213,32 +1250,7 @@ var CSS = `
1213
1250
  70%, 100% { transform: scale(2.6); opacity: 0 }
1214
1251
  }
1215
1252
 
1216
- /* Two ways to turn the light, one look.
1217
- Where the browser can register a typed custom property, the gradient's own
1218
- angle animates and the element never moves. Where it cannot (Firefox before
1219
- 128), an untyped custom property cannot interpolate and the light would
1220
- snap once a cycle, so the oversized pseudo-element rotates instead and the
1221
- band's mask keeps the ring in place. At rest the two are the same pixels:
1222
- same gradient, same centre, angle zero.
1223
- There is no direct query for @property support. transition-behavior
1224
- shipped after it in every engine (Chrome 117 vs 85, Firefox 129 vs 128,
1225
- Safari 17.4 vs 16.4), so it stands in: a yes is never wrong, and a no only
1226
- sends a capable browser down the fallback, which looks the same.
1227
- inherits: false so the angle stays on the element that animates it and
1228
- never reaches the host page or the well. */
1229
- @property --zrl-angle {
1230
- syntax: '<angle>';
1231
- inherits: false;
1232
- initial-value: 0deg;
1233
- }
1234
1253
  @keyframes ${PREFIX}-orbit { to { transform: rotate(360deg) } }
1235
- @keyframes ${PREFIX}-orbit-angle { to { --zrl-angle: 360deg } }
1236
- @supports (transition-behavior: allow-discrete) {
1237
- .${PREFIX}-qr-arc::before {
1238
- inset: 0;
1239
- animation-name: ${PREFIX}-orbit-angle;
1240
- }
1241
- }
1242
1254
 
1243
1255
  @media (prefers-reduced-motion: reduce) {
1244
1256
  .${PREFIX}-scrim,
@@ -1249,10 +1261,11 @@ var CSS = `
1249
1261
  .${PREFIX}-cancel,
1250
1262
  .${PREFIX}-close,
1251
1263
  .${PREFIX}-timer { transition: none }
1252
- /* No travelling light. The ring keeps a still, soft glow instead, a step
1253
- brighter than the spent state so pending and spent still read apart. */
1254
- .${PREFIX}-qr-arc::before { animation: none; opacity: 0 }
1255
- .${PREFIX}-qr-arc::after { opacity: 0.5 }
1264
+ /* No travelling light; the edge keeps its dim static glow instead. */
1265
+ .${PREFIX}-qr-beam::after,
1266
+ .${PREFIX}-qr-beam-glow-band::after { animation: none; opacity: 0 }
1267
+ .${PREFIX}-qr-beam::before { opacity: 0.55 }
1268
+ .${PREFIX}-qr-beam-glow-band::before { opacity: 0.7 }
1256
1269
  }
1257
1270
  `;
1258
1271
  function ensureStyles() {
@@ -1357,11 +1370,13 @@ function mountPairingModal(state, options) {
1357
1370
  const title = el("h2", cx("title"));
1358
1371
  title.id = titleId;
1359
1372
  const bodyText = el("p", cx("body-text"));
1360
- const halo = el("div", cx("qr-halo"));
1361
- const glow = el("span", cx("qr-glow"));
1362
- glow.setAttribute("aria-hidden", "true");
1363
- glow.appendChild(el("span", cx("qr-arc")));
1364
1373
  const well = el("div", cx("qr-well"));
1374
+ const glow = el("span", cx("qr-beam-glow"));
1375
+ glow.setAttribute("aria-hidden", "true");
1376
+ glow.appendChild(el("span", cx("qr-beam-glow-band")));
1377
+ const beam = el("span", cx("qr-beam"));
1378
+ beam.setAttribute("aria-hidden", "true");
1379
+ well.append(glow, beam);
1365
1380
  const qr = el("img", cx("qr"));
1366
1381
  qr.alt = t.qrAlt;
1367
1382
  qr.width = 180;
@@ -1372,14 +1387,13 @@ function mountPairingModal(state, options) {
1372
1387
  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"));
1373
1388
  overlay.appendChild(badge);
1374
1389
  well.append(qr, overlay);
1375
- halo.append(glow, well);
1376
1390
  const status = el("div", cx("status"));
1377
1391
  const dot = el("span", cx("dot"));
1378
1392
  dot.append(el("i"), el("i"));
1379
1393
  const statusLabel = el("span");
1380
1394
  status.append(dot, statusLabel);
1381
1395
  const timer = el("p", cx("timer"));
1382
- body.append(mark, title, bodyText, halo, status, timer);
1396
+ body.append(mark, title, bodyText, well, status, timer);
1383
1397
  const help = el("div", cx("help"));
1384
1398
  help.append(el("p", cx("help-title"), t.noIdTitle), el("p", cx("help-body"), t.noIdBody));
1385
1399
  const footer = el("div", cx("footer"));
@@ -1421,7 +1435,7 @@ function mountPairingModal(state, options) {
1421
1435
  bodyText.textContent = s.status === "enrolling" ? t.bodyEnrolling : settled ? t.bodyApprove : t.bodyScan;
1422
1436
  statusLabel.textContent = settled ? t.waitingApproval : t.waiting;
1423
1437
  qr.dataset.spent = String(settled);
1424
- halo.dataset.spent = String(settled);
1438
+ well.dataset.spent = String(settled);
1425
1439
  overlay.style.display = settled ? "" : "none";
1426
1440
  if (s.qrUrl) showFrame(s.qrUrl);
1427
1441
  };