@nano133/nanoterra 0.8.0 → 0.9.0

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.
Files changed (132) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +3 -0
  3. package/README.md +363 -25
  4. package/ai/SPEC.md +65 -3
  5. package/dist/admin/panel/dom.d.ts +2 -0
  6. package/dist/admin/panel/dom.js +6 -0
  7. package/dist/admin/panel/mount.d.ts +3 -0
  8. package/dist/admin/panel/mount.js +4 -2
  9. package/dist/emotes/panel/mount.d.ts +5 -0
  10. package/dist/emotes/panel/mount.js +7 -3
  11. package/dist/friends/panel/mount.d.ts +5 -0
  12. package/dist/friends/panel/mount.js +5 -2
  13. package/dist/options/panel/mount.d.ts +5 -0
  14. package/dist/options/panel/mount.js +5 -3
  15. package/dist/player-signin/adapters/d1.d.ts +10 -0
  16. package/dist/player-signin/adapters/d1.js +108 -0
  17. package/dist/player-signin/adapters/firestore.d.ts +45 -0
  18. package/dist/player-signin/adapters/firestore.js +168 -0
  19. package/dist/player-signin/conformance.d.ts +11 -0
  20. package/dist/player-signin/conformance.js +160 -0
  21. package/dist/player-signin/handler.d.ts +64 -0
  22. package/dist/player-signin/handler.js +364 -0
  23. package/dist/player-signin/host.d.ts +4 -0
  24. package/dist/player-signin/host.js +10 -0
  25. package/dist/player-signin/index.d.ts +9 -0
  26. package/dist/player-signin/index.js +15 -0
  27. package/dist/player-signin/memory.d.ts +15 -0
  28. package/dist/player-signin/memory.js +101 -0
  29. package/dist/player-signin/panel/index.d.ts +9 -0
  30. package/dist/player-signin/panel/index.js +7 -0
  31. package/dist/player-signin/panel/memo.d.ts +12 -0
  32. package/dist/player-signin/panel/memo.js +41 -0
  33. package/dist/player-signin/panel/mount.d.ts +21 -0
  34. package/dist/player-signin/panel/mount.js +105 -0
  35. package/dist/player-signin/panel/view.d.ts +45 -0
  36. package/dist/player-signin/panel/view.js +360 -0
  37. package/dist/player-signin/panel/words.d.ts +8 -0
  38. package/dist/player-signin/panel/words.js +26 -0
  39. package/dist/player-signin/scheme-check.d.ts +3 -0
  40. package/dist/player-signin/scheme-check.js +14 -0
  41. package/dist/player-signin/scheme.d.ts +13 -0
  42. package/dist/player-signin/scheme.js +28 -0
  43. package/dist/player-signin/types.d.ts +116 -0
  44. package/dist/player-signin/types.js +33 -0
  45. package/dist/shop/adapters/d1.d.ts +25 -0
  46. package/dist/shop/adapters/d1.js +200 -0
  47. package/dist/shop/adapters/firestore.d.ts +53 -0
  48. package/dist/shop/adapters/firestore.js +245 -0
  49. package/dist/shop/address.d.ts +14 -0
  50. package/dist/shop/address.js +104 -0
  51. package/dist/shop/amount.d.ts +26 -0
  52. package/dist/shop/amount.js +59 -0
  53. package/dist/shop/conformance.d.ts +13 -0
  54. package/dist/shop/conformance.js +203 -0
  55. package/dist/shop/handler.d.ts +76 -0
  56. package/dist/shop/handler.js +156 -0
  57. package/dist/shop/index.d.ts +19 -0
  58. package/dist/shop/index.js +21 -0
  59. package/dist/shop/match.d.ts +23 -0
  60. package/dist/shop/match.js +58 -0
  61. package/dist/shop/memory.d.ts +13 -0
  62. package/dist/shop/memory.js +106 -0
  63. package/dist/shop/nodes.d.ts +47 -0
  64. package/dist/shop/nodes.js +106 -0
  65. package/dist/shop/orders.d.ts +47 -0
  66. package/dist/shop/orders.js +82 -0
  67. package/dist/shop/panel/index.d.ts +7 -0
  68. package/dist/shop/panel/index.js +11 -0
  69. package/dist/shop/panel/looks.d.ts +30 -0
  70. package/dist/shop/panel/looks.js +61 -0
  71. package/dist/shop/panel/mount.d.ts +52 -0
  72. package/dist/shop/panel/mount.js +194 -0
  73. package/dist/shop/panel/pay.d.ts +24 -0
  74. package/dist/shop/panel/pay.js +115 -0
  75. package/dist/shop/panel/qr.d.ts +9 -0
  76. package/dist/shop/panel/qr.js +281 -0
  77. package/dist/shop/price.d.ts +23 -0
  78. package/dist/shop/price.js +43 -0
  79. package/dist/shop/scan.d.ts +24 -0
  80. package/dist/shop/scan.js +80 -0
  81. package/dist/shop/security.d.ts +10 -0
  82. package/dist/shop/security.js +56 -0
  83. package/dist/shop/store.d.ts +101 -0
  84. package/dist/shop/store.js +4 -0
  85. package/dist/shop/worker.d.ts +43 -0
  86. package/dist/shop/worker.js +76 -0
  87. package/package.json +21 -6
  88. package/src/admin/panel/dom.ts +6 -0
  89. package/src/admin/panel/mount.ts +7 -2
  90. package/src/emotes/panel/mount.ts +9 -2
  91. package/src/friends/panel/mount.ts +8 -2
  92. package/src/options/panel/mount.ts +8 -3
  93. package/src/player-signin/adapters/d1.sql +45 -0
  94. package/src/player-signin/adapters/d1.ts +120 -0
  95. package/src/player-signin/adapters/firestore.ts +194 -0
  96. package/src/player-signin/conformance.ts +184 -0
  97. package/src/player-signin/handler.ts +404 -0
  98. package/src/player-signin/host.ts +12 -0
  99. package/src/player-signin/index.ts +20 -0
  100. package/src/player-signin/memory.ts +94 -0
  101. package/src/player-signin/panel/index.ts +12 -0
  102. package/src/player-signin/panel/memo.ts +49 -0
  103. package/src/player-signin/panel/mount.ts +127 -0
  104. package/src/player-signin/panel/style.css +473 -0
  105. package/src/player-signin/panel/view.ts +428 -0
  106. package/src/player-signin/panel/words.ts +32 -0
  107. package/src/player-signin/scheme-check.ts +14 -0
  108. package/src/player-signin/scheme.ts +32 -0
  109. package/src/player-signin/types.ts +103 -0
  110. package/src/shop/adapters/d1.sql +52 -0
  111. package/src/shop/adapters/d1.ts +245 -0
  112. package/src/shop/adapters/firestore.ts +281 -0
  113. package/src/shop/address.ts +102 -0
  114. package/src/shop/amount.ts +66 -0
  115. package/src/shop/conformance.ts +232 -0
  116. package/src/shop/handler.ts +202 -0
  117. package/src/shop/index.ts +30 -0
  118. package/src/shop/match.ts +63 -0
  119. package/src/shop/memory.ts +102 -0
  120. package/src/shop/nodes.ts +115 -0
  121. package/src/shop/orders.ts +93 -0
  122. package/src/shop/panel/index.ts +15 -0
  123. package/src/shop/panel/looks.ts +106 -0
  124. package/src/shop/panel/mount.ts +241 -0
  125. package/src/shop/panel/pay.ts +171 -0
  126. package/src/shop/panel/qr.ts +256 -0
  127. package/src/shop/panel/style.css +534 -0
  128. package/src/shop/price.ts +52 -0
  129. package/src/shop/scan.ts +90 -0
  130. package/src/shop/security.ts +50 -0
  131. package/src/shop/store.ts +99 -0
  132. package/src/shop/worker.ts +107 -0
@@ -0,0 +1,127 @@
1
+ // The sign-in sheet of a game: `mountPlayerSignin(element, options)`. It draws no key of its own: the game's account key, the shop's "sign in
2
+ // first" and anything else call `open()`, which resolves with the player once they are signed in, or null when the sheet closes. Plain DOM; a
3
+ // modal sheet with a scrim: from the bottom on a phone held upright, in the middle on a wide or sideways screen. Escape, the close key and a press
4
+ // on the scrim close it. Every key is 44 px or more.
5
+
6
+ import { h, releaseFocus } from "../../admin/panel/dom.js";
7
+ import { icon } from "../../admin/panel/icons.js";
8
+ import { signInView, type PlayerSigninViewOptions, type SignedIn } from "./view.js";
9
+
10
+ export type PlayerSigninMountOptions = PlayerSigninViewOptions & {
11
+ /** The sheet's title. Default "Sign in". */
12
+ title?: string;
13
+ /** How long "Signed in as …" shows before the sheet closes, in ms. Default 900. */
14
+ closeAfterMs?: number;
15
+ /**
16
+ * Where the keyboard focus goes when the sheet closes. "game" (the default): out of the sheet, to the page, so the game's keys reach the game. "back":
17
+ * to the element that had it when the sheet opened (a page with keys of its own).
18
+ */
19
+ focusAfterClose?: "game" | "back";
20
+ };
21
+
22
+ export type MountedPlayerSignin = {
23
+ /** Opens the sheet. Resolves with the player when they sign in, or null when the sheet closes first. A second call while it is open gets the same promise. */
24
+ open(): Promise<SignedIn | null>;
25
+ close(): void;
26
+ readonly isOpen: boolean;
27
+ /** Removes the sheet and stops every wait. */
28
+ destroy(): void;
29
+ };
30
+
31
+ let mounts = 0;
32
+
33
+ export function mountPlayerSignin(root: HTMLElement, options: PlayerSigninMountOptions): MountedPlayerSignin {
34
+ const n = ++mounts;
35
+ let isOpen = false;
36
+ let pending: { promise: Promise<SignedIn | null>; resolve: (p: SignedIn | null) => void } | null = null;
37
+ let before: Element | null = null;
38
+ let closing: ReturnType<typeof setTimeout> | undefined;
39
+
40
+ const settle = (p: SignedIn | null) => {
41
+ const was = pending;
42
+ pending = null;
43
+ was?.resolve(p);
44
+ };
45
+
46
+ const view = signInView({
47
+ ...options,
48
+ onSignedIn(player) {
49
+ options.onSignedIn?.(player);
50
+ settle(player);
51
+ closing = setTimeout(close, Math.max(0, options.closeAfterMs ?? 900));
52
+ },
53
+ });
54
+
55
+ const titleEl = h("h2", { class: "n133s-title", id: `n133s-title-${n}` }, options.title ?? "Sign in");
56
+ const closeKey = h("button", { class: "n133s-close", type: "button", "aria-label": "Close" }, icon("close", 20));
57
+ const scrim = h("div", { class: "n133s-scrim" });
58
+ const panel = h("section", { class: "n133s-panel", role: "dialog", "aria-modal": "true", "aria-labelledby": `n133s-title-${n}` }, h("header", { class: "n133s-head" }, titleEl, closeKey), h("div", { class: "n133s-body" }, view.node));
59
+ const wrap = h("div", { class: "n133-signin n133s-sheet", "data-open": "false" }, scrim, panel);
60
+ wrap.hidden = true;
61
+ root.append(wrap);
62
+
63
+ function close() {
64
+ if (closing) clearTimeout(closing);
65
+ closing = undefined;
66
+ if (!isOpen) return;
67
+ isOpen = false;
68
+ view.stop();
69
+ wrap.dataset.open = "false";
70
+ wrap.hidden = true;
71
+ settle(null);
72
+ if (options.focusAfterClose === "back" && before instanceof HTMLElement) before.focus();
73
+ else releaseFocus(wrap);
74
+ }
75
+
76
+ function open(): Promise<SignedIn | null> {
77
+ if (isOpen && pending) return pending.promise;
78
+ if (closing) clearTimeout(closing);
79
+ let resolve!: (p: SignedIn | null) => void;
80
+ const promise = new Promise<SignedIn | null>((r) => (resolve = r));
81
+ pending = { promise, resolve };
82
+ before = document.activeElement;
83
+ isOpen = true;
84
+ view.reset();
85
+ wrap.hidden = false;
86
+ wrap.dataset.open = "true";
87
+ (panel.querySelector<HTMLElement>(".n133s-area button") ?? closeKey).focus({ preventScroll: true });
88
+ return promise;
89
+ }
90
+
91
+ closeKey.addEventListener("click", close);
92
+ scrim.addEventListener("click", close);
93
+ wrap.addEventListener("keydown", (e) => {
94
+ if (e.key === "Escape") {
95
+ e.preventDefault();
96
+ e.stopPropagation();
97
+ close();
98
+ return;
99
+ }
100
+ if (e.key !== "Tab") return;
101
+ // The focus stays in the sheet while it is open.
102
+ const keys = Array.from(panel.querySelectorAll<HTMLElement>("button:not([disabled]), a[href], [tabindex]:not([tabindex='-1'])")).filter((el) => !el.closest("[hidden]"));
103
+ if (!keys.length) return;
104
+ const first = keys[0];
105
+ const last = keys[keys.length - 1];
106
+ if (e.shiftKey && document.activeElement === first) {
107
+ e.preventDefault();
108
+ last.focus();
109
+ } else if (!e.shiftKey && document.activeElement === last) {
110
+ e.preventDefault();
111
+ first.focus();
112
+ }
113
+ });
114
+
115
+ return {
116
+ open,
117
+ close,
118
+ get isOpen() {
119
+ return isOpen;
120
+ },
121
+ destroy() {
122
+ close();
123
+ view.stop();
124
+ wrap.remove();
125
+ },
126
+ };
127
+ }
@@ -0,0 +1,473 @@
1
+ /* The player's sign-in sheet (mountPlayerSignin in ./mount.ts) and its content (signInView in ./view.ts). Every rule is under .n133-signin, so nothing
2
+ leaks into the game's page.
3
+ Phone first: every key is 44 px or more and its words 16 px. On a phone held upright the sheet comes from the bottom; on a wide or sideways screen it
4
+ is a card in the middle. A scrim covers the game while it is open.
5
+ Light and dark: the sheet follows data-n133-theme="light" | "dark" on the page's <html> when the page sets it, else the device's prefers-color-scheme.
6
+ To theme it, set the --n133-* custom properties on the element that holds the sheet (or a parent): --n133-ink, --n133-ink-soft, --n133-paper,
7
+ --n133-ground, --n133-line, --n133-gem, --n133-gem-dark, --n133-on-gem, --n133-radius, --n133-font and --n133-signin-z (the sheet's z-index, default
8
+ 1200: above the options and friends panels). Those set the LIGHT theme; the DARK theme reads the twins with "dark-" in the name. */
9
+
10
+ .n133-signin {
11
+ --font: var(--n133-font, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
12
+ --radius: var(--n133-radius, 18px);
13
+ --ink: var(--n133-ink, #16202e);
14
+ --ink-soft: var(--n133-ink-soft, #4a5568);
15
+ --paper: var(--n133-paper, #ffffff);
16
+ --ground: var(--n133-ground, #eef2f7);
17
+ --line: var(--n133-line, #d3dce7);
18
+ --gem: var(--n133-gem, #20c9a6);
19
+ --gem-dark: var(--n133-gem-dark, #0e8a72);
20
+ --on-gem: var(--n133-on-gem, #ffffff);
21
+ --warn: #b4232f;
22
+ --shade: 16 24 40;
23
+ color: var(--ink);
24
+ color-scheme: light;
25
+ font: 16px/1.45 var(--font);
26
+ -webkit-text-size-adjust: 100%;
27
+ -webkit-tap-highlight-color: transparent;
28
+ }
29
+
30
+ /* Dark: the page says so (data-n133-theme), or the device does and the page does not say light. The two blocks are the same: CSS cannot share them. */
31
+ :root[data-n133-theme="dark"] .n133-signin {
32
+ --ink: var(--n133-dark-ink, #e7edf5);
33
+ --ink-soft: var(--n133-dark-ink-soft, #a9b5c5);
34
+ --paper: var(--n133-dark-paper, #171f2b);
35
+ --ground: var(--n133-dark-ground, #0f151e);
36
+ --line: var(--n133-dark-line, #34415a);
37
+ --gem: var(--n133-dark-gem, #20c9a6);
38
+ --gem-dark: var(--n133-dark-gem-dark, #3fd8b6);
39
+ --on-gem: var(--n133-dark-on-gem, #062b23);
40
+ --warn: #ff8a93;
41
+ --shade: 0 0 0;
42
+ color-scheme: dark;
43
+ }
44
+
45
+ @media (prefers-color-scheme: dark) {
46
+ :root:not([data-n133-theme="light"]) .n133-signin {
47
+ --ink: var(--n133-dark-ink, #e7edf5);
48
+ --ink-soft: var(--n133-dark-ink-soft, #a9b5c5);
49
+ --paper: var(--n133-dark-paper, #171f2b);
50
+ --ground: var(--n133-dark-ground, #0f151e);
51
+ --line: var(--n133-dark-line, #34415a);
52
+ --gem: var(--n133-dark-gem, #20c9a6);
53
+ --gem-dark: var(--n133-dark-gem-dark, #3fd8b6);
54
+ --on-gem: var(--n133-dark-on-gem, #062b23);
55
+ --warn: #ff8a93;
56
+ --shade: 0 0 0;
57
+ color-scheme: dark;
58
+ }
59
+ }
60
+
61
+ .n133-signin *,
62
+ .n133-signin *::before,
63
+ .n133-signin *::after {
64
+ box-sizing: border-box;
65
+ }
66
+
67
+ .n133-signin [hidden] {
68
+ display: none !important;
69
+ }
70
+
71
+ /* ---- the sheet ---- */
72
+
73
+ .n133-signin.n133s-sheet {
74
+ position: fixed;
75
+ z-index: var(--n133-signin-z, 1200);
76
+ inset: 0;
77
+ display: flex;
78
+ align-items: flex-end;
79
+ justify-content: center;
80
+ }
81
+
82
+ .n133-signin .n133s-scrim {
83
+ position: absolute;
84
+ inset: 0;
85
+ background: rgb(var(--shade) / 0.45);
86
+ }
87
+
88
+ .n133-signin .n133s-panel {
89
+ position: relative;
90
+ display: flex;
91
+ flex-direction: column;
92
+ width: 100%;
93
+ max-height: 92vh;
94
+ max-height: 92dvh;
95
+ border: 1px solid var(--line);
96
+ border-radius: var(--radius) var(--radius) 0 0;
97
+ background: var(--paper);
98
+ box-shadow: 0 -8px 30px rgb(var(--shade) / 0.22);
99
+ padding-bottom: env(safe-area-inset-bottom);
100
+ }
101
+
102
+ @media (min-width: 640px), (orientation: landscape) and (max-height: 520px) {
103
+ .n133-signin.n133s-sheet {
104
+ align-items: center;
105
+ padding: 12px;
106
+ }
107
+ .n133-signin .n133s-panel {
108
+ width: min(440px, 100%);
109
+ max-height: calc(100vh - 24px);
110
+ max-height: calc(100dvh - 24px);
111
+ border-radius: var(--radius);
112
+ box-shadow: 0 16px 48px rgb(var(--shade) / 0.28);
113
+ }
114
+ }
115
+
116
+ .n133-signin .n133s-head {
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ gap: 12px;
121
+ padding: 10px 10px 4px 20px;
122
+ }
123
+
124
+ .n133-signin .n133s-title {
125
+ margin: 0;
126
+ font: 750 20px/1.2 var(--font);
127
+ }
128
+
129
+ .n133-signin .n133s-close {
130
+ display: inline-grid;
131
+ place-items: center;
132
+ width: 44px;
133
+ height: 44px;
134
+ border: 0;
135
+ border-radius: 50%;
136
+ background: transparent;
137
+ color: var(--ink-soft);
138
+ cursor: pointer;
139
+ }
140
+
141
+ .n133-signin .n133s-close:hover {
142
+ background: var(--ground);
143
+ color: var(--ink);
144
+ }
145
+
146
+ .n133-signin .n133s-body {
147
+ overflow-y: auto;
148
+ overscroll-behavior: contain;
149
+ padding: 4px 20px 20px;
150
+ }
151
+
152
+ /* ---- the content ---- */
153
+
154
+ .n133-signin .n133s-area {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 12px;
158
+ }
159
+
160
+ .n133-signin .n133s-lead,
161
+ .n133-signin .n133s-text {
162
+ margin: 0;
163
+ }
164
+
165
+ .n133-signin .n133s-lead,
166
+ .n133-signin .n133s-fine,
167
+ .n133-signin .n133s-or {
168
+ color: var(--ink-soft);
169
+ }
170
+
171
+ .n133-signin .n133s-fine {
172
+ margin: 0;
173
+ font-size: 14px;
174
+ }
175
+
176
+ .n133-signin .n133s-or {
177
+ margin: 6px 0 -4px;
178
+ font: 650 14px/1.3 var(--font);
179
+ }
180
+
181
+ .n133-signin .n133s-step {
182
+ margin: 0;
183
+ font: 750 18px/1.25 var(--font);
184
+ outline: none;
185
+ }
186
+
187
+ .n133-signin .n133s-fault {
188
+ padding: 12px 14px;
189
+ border-left: 4px solid var(--warn);
190
+ border-radius: 10px;
191
+ background: var(--ground);
192
+ }
193
+
194
+ /* The passkey's big key: first, the whole row. */
195
+ .n133-signin .n133s-pk {
196
+ position: relative;
197
+ display: flex;
198
+ align-items: center;
199
+ gap: 14px;
200
+ width: 100%;
201
+ min-height: 72px;
202
+ padding: 12px 16px;
203
+ border: 0;
204
+ border-radius: 16px;
205
+ background: var(--gem-dark);
206
+ color: var(--on-gem);
207
+ font: inherit;
208
+ text-align: left;
209
+ cursor: pointer;
210
+ touch-action: manipulation;
211
+ }
212
+
213
+ :root[data-n133-theme="dark"] .n133-signin .n133s-pk {
214
+ background: var(--gem);
215
+ }
216
+
217
+ @media (prefers-color-scheme: dark) {
218
+ :root:not([data-n133-theme="light"]) .n133-signin .n133s-pk {
219
+ background: var(--gem);
220
+ }
221
+ }
222
+
223
+ .n133-signin .n133s-pk.is-off {
224
+ border: 1px dashed var(--line);
225
+ background: var(--paper);
226
+ color: var(--ink-soft);
227
+ }
228
+
229
+ .n133-signin .n133s-pk-mark {
230
+ display: inline-grid;
231
+ flex: none;
232
+ place-items: center;
233
+ width: 44px;
234
+ height: 44px;
235
+ border-radius: 12px;
236
+ background: rgb(255 255 255 / 0.18);
237
+ }
238
+
239
+ .n133-signin .n133s-pk.is-off .n133s-pk-mark {
240
+ background: var(--ground);
241
+ }
242
+
243
+ .n133-signin .n133s-pk-words {
244
+ display: flex;
245
+ flex-direction: column;
246
+ gap: 2px;
247
+ }
248
+
249
+ .n133-signin .n133s-pk-words strong {
250
+ font: 750 18px/1.2 var(--font);
251
+ }
252
+
253
+ .n133-signin .n133s-pk-words small {
254
+ font-size: 14px;
255
+ opacity: 0.92;
256
+ }
257
+
258
+ .n133-signin .n133s-tiles {
259
+ display: grid;
260
+ grid-template-columns: repeat(3, minmax(0, 1fr));
261
+ gap: 10px;
262
+ }
263
+
264
+ .n133-signin .n133s-tile {
265
+ position: relative;
266
+ display: flex;
267
+ flex-direction: column;
268
+ align-items: center;
269
+ justify-content: center;
270
+ gap: 6px;
271
+ min-height: 92px;
272
+ padding: 12px 6px;
273
+ border: 1px solid var(--line);
274
+ border-radius: 16px;
275
+ background: var(--paper);
276
+ color: var(--ink);
277
+ font: inherit;
278
+ cursor: pointer;
279
+ touch-action: manipulation;
280
+ }
281
+
282
+ .n133-signin .n133s-tile:hover,
283
+ .n133-signin .n133s-pk.is-off:hover {
284
+ border-color: var(--gem-dark);
285
+ }
286
+
287
+ .n133-signin .n133s-mark {
288
+ display: inline-grid;
289
+ place-items: center;
290
+ width: 40px;
291
+ height: 40px;
292
+ border-radius: 12px;
293
+ background: var(--ground);
294
+ color: var(--ink);
295
+ font: 800 15px/1 var(--font);
296
+ }
297
+
298
+ .n133-signin .n133s-name {
299
+ font: 650 15px/1.2 var(--font);
300
+ text-align: center;
301
+ }
302
+
303
+ .n133-signin .n133s-last {
304
+ position: absolute;
305
+ top: 6px;
306
+ right: 8px;
307
+ padding: 1px 7px;
308
+ border-radius: 999px;
309
+ background: var(--ground);
310
+ color: var(--ink-soft);
311
+ font: 700 11px/18px var(--font);
312
+ }
313
+
314
+ .n133-signin .n133s-pk .n133s-last {
315
+ background: rgb(255 255 255 / 0.22);
316
+ color: inherit;
317
+ }
318
+
319
+ .n133-signin .n133s-keys {
320
+ display: flex;
321
+ flex-direction: column;
322
+ gap: 10px;
323
+ }
324
+
325
+ .n133-signin .n133s-btn {
326
+ display: inline-flex;
327
+ align-items: center;
328
+ justify-content: center;
329
+ width: 100%;
330
+ min-height: 48px;
331
+ padding: 10px 16px;
332
+ border: 1px solid var(--line);
333
+ border-radius: 12px;
334
+ background: var(--paper);
335
+ color: var(--ink);
336
+ font: 700 16px/1.2 var(--font);
337
+ text-align: center;
338
+ text-decoration: none;
339
+ cursor: pointer;
340
+ touch-action: manipulation;
341
+ }
342
+
343
+ .n133-signin .n133s-primary {
344
+ border-color: var(--gem-dark);
345
+ background: var(--gem-dark);
346
+ color: var(--on-gem);
347
+ }
348
+
349
+ :root[data-n133-theme="dark"] .n133-signin .n133s-primary {
350
+ border-color: var(--gem);
351
+ background: var(--gem);
352
+ }
353
+
354
+ @media (prefers-color-scheme: dark) {
355
+ :root:not([data-n133-theme="light"]) .n133-signin .n133s-primary {
356
+ border-color: var(--gem);
357
+ background: var(--gem);
358
+ }
359
+ }
360
+
361
+ .n133-signin .n133s-quiet {
362
+ border-color: transparent;
363
+ background: transparent;
364
+ color: var(--ink-soft);
365
+ }
366
+
367
+ .n133-signin .n133s-btn:disabled,
368
+ .n133-signin .n133s-tile:disabled,
369
+ .n133-signin .n133s-pk:disabled {
370
+ cursor: progress;
371
+ opacity: 0.55;
372
+ }
373
+
374
+ .n133-signin .n133s-back {
375
+ display: inline-flex;
376
+ align-self: flex-start;
377
+ align-items: center;
378
+ gap: 6px;
379
+ min-height: 44px;
380
+ margin: 0 0 -4px -10px;
381
+ padding: 0 10px;
382
+ border: 0;
383
+ border-radius: 10px;
384
+ background: transparent;
385
+ color: var(--ink-soft);
386
+ font: 650 15px/1 var(--font);
387
+ cursor: pointer;
388
+ }
389
+
390
+ .n133-signin .n133s-back:hover {
391
+ background: var(--ground);
392
+ color: var(--ink);
393
+ }
394
+
395
+ .n133-signin a:not(.n133s-btn) {
396
+ color: var(--gem-dark);
397
+ font-weight: 650;
398
+ text-underline-offset: 2px;
399
+ }
400
+
401
+ .n133-signin .n133s-qr {
402
+ display: grid;
403
+ place-items: center;
404
+ padding: 12px;
405
+ border-radius: 14px;
406
+ background: #ffffff;
407
+ }
408
+
409
+ .n133-signin .n133s-signcode {
410
+ font: 800 18px/1 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
411
+ letter-spacing: 0.04em;
412
+ }
413
+
414
+ .n133-signin .n133s-codebox {
415
+ display: flex;
416
+ flex-direction: column;
417
+ gap: 10px;
418
+ padding: 12px;
419
+ border: 1px solid var(--line);
420
+ border-radius: 12px;
421
+ background: var(--ground);
422
+ }
423
+
424
+ .n133-signin .n133s-code {
425
+ margin: 0;
426
+ font: 650 16px/1.5 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
427
+ letter-spacing: 0.06em;
428
+ white-space: pre;
429
+ overflow-wrap: anywhere;
430
+ user-select: all;
431
+ }
432
+
433
+ .n133-signin .n133s-done {
434
+ display: flex;
435
+ align-items: center;
436
+ gap: 10px;
437
+ margin: 8px 0;
438
+ font: 700 17px/1.3 var(--font);
439
+ color: var(--gem-dark);
440
+ }
441
+
442
+ .n133-signin .n133s-status {
443
+ min-height: 1.4em;
444
+ margin: 12px 0 0;
445
+ color: var(--ink-soft);
446
+ font-size: 15px;
447
+ }
448
+
449
+ .n133-signin .n133s-status:empty {
450
+ margin: 0;
451
+ }
452
+
453
+ .n133-signin .n133s-check:empty {
454
+ display: none;
455
+ }
456
+
457
+ .n133-signin :focus-visible {
458
+ outline: 3px solid var(--gem);
459
+ outline-offset: 2px;
460
+ }
461
+
462
+ @media (prefers-reduced-motion: no-preference) {
463
+ .n133-signin.n133s-sheet[data-open="true"] .n133s-panel {
464
+ animation: n133s-rise 160ms ease-out;
465
+ }
466
+ }
467
+
468
+ @keyframes n133s-rise {
469
+ from {
470
+ transform: translateY(24px);
471
+ opacity: 0;
472
+ }
473
+ }