hanki-lanyard 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1912 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ var qrcode_react = require('qrcode.react');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ // src/LanyardCard.tsx
10
+
11
+ // src/defaults.ts
12
+ var DEFAULT_LANYARD_TEXT = "hanki.fun";
13
+ var DEFAULT_HANKI_BADGE = {
14
+ name: "Hanki",
15
+ role: "Platform",
16
+ roleType: "You're Invited",
17
+ eventCode: "HNKI",
18
+ date: "Join the waitlist",
19
+ location: "Online",
20
+ venue: "hanki.fun",
21
+ address: "Your link-in-bio badge studio",
22
+ website: "hanki.fun",
23
+ email: "hello@hanki.fun",
24
+ phone: "",
25
+ company: "Hanki",
26
+ tagline: "Create your badge. Share your links.",
27
+ logoUrl: "/hanki-logo.png",
28
+ lanyardText: DEFAULT_LANYARD_TEXT,
29
+ badgeLabel: "INVITE",
30
+ scanLabel: "VISIT",
31
+ emailLabel: "EMAIL",
32
+ phoneLabel: "PHONE",
33
+ addressLabel: "ADDRESS",
34
+ authHeaderEvent: "YOU'RE INVITED",
35
+ authHeaderVenue: "hanki.fun",
36
+ authHeaderDate: "Create your free account",
37
+ signInTitle: "SIGN IN",
38
+ signUpTitle: "SIGN UP",
39
+ signInSideLabel: "FRONT SIDE",
40
+ signUpSideLabel: "BACK SIDE",
41
+ signUpSubmitLabel: "JOIN HANKI \u2192",
42
+ signUpNameHint: "Shown on your Hanki badge.",
43
+ colors: {
44
+ cardBg: "#0a0a0a",
45
+ accentColor: "#ff2d85",
46
+ nameColor: "#ffffff",
47
+ roleColor: "#b8b8b8",
48
+ metaColor: "#888888",
49
+ footerBg: "#111111",
50
+ footerText: "#ffffff",
51
+ ringColor: "#666666"
52
+ }
53
+ };
54
+ function resolveLanyardText(data) {
55
+ const custom = data.lanyardText?.trim();
56
+ if (custom) return custom;
57
+ const site = data.website?.trim();
58
+ if (site) return site;
59
+ return DEFAULT_LANYARD_TEXT;
60
+ }
61
+
62
+ // src/auth.ts
63
+ var SIGNIN_STEPS_DEFAULT = [
64
+ {
65
+ id: "email",
66
+ question: "Welcome back \u2014 what email did you use?",
67
+ hint: "Sign in to your hanki.fun account.",
68
+ label: "EMAIL",
69
+ type: "email",
70
+ placeholder: "you@example.com",
71
+ autoComplete: "email"
72
+ },
73
+ {
74
+ id: "password",
75
+ question: "Enter your password",
76
+ label: "PASSWORD",
77
+ type: "password",
78
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
79
+ autoComplete: "current-password"
80
+ }
81
+ ];
82
+ var REGISTER_STEPS_DEFAULT = [
83
+ {
84
+ id: "name",
85
+ question: "What should we call you on Hanki?",
86
+ hint: "Shown on your Hanki badge.",
87
+ label: "FULL NAME",
88
+ type: "text",
89
+ placeholder: "Your name",
90
+ autoComplete: "name"
91
+ },
92
+ {
93
+ id: "email",
94
+ question: "What's your email?",
95
+ label: "EMAIL",
96
+ type: "email",
97
+ placeholder: "you@example.com",
98
+ autoComplete: "email"
99
+ },
100
+ {
101
+ id: "password",
102
+ question: "Choose a password",
103
+ hint: "At least 8 characters.",
104
+ label: "PASSWORD",
105
+ type: "password",
106
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
107
+ autoComplete: "new-password"
108
+ },
109
+ {
110
+ id: "confirm",
111
+ question: "Confirm your password",
112
+ label: "CONFIRM",
113
+ type: "password",
114
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
115
+ autoComplete: "new-password"
116
+ }
117
+ ];
118
+ function isValidEmail(value) {
119
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
120
+ }
121
+ function mergeBadgeStepCopy(base, data, overrides) {
122
+ const patch = overrides[base.id];
123
+ if (!patch) return base;
124
+ return {
125
+ ...base,
126
+ question: patch.question ?? base.question,
127
+ hint: patch.hint ?? base.hint
128
+ };
129
+ }
130
+ function getSignInSteps(data) {
131
+ return [
132
+ mergeBadgeStepCopy(SIGNIN_STEPS_DEFAULT[0], data, {
133
+ email: {
134
+ question: data.signInEmailQuestion,
135
+ hint: data.signInEmailHint
136
+ }
137
+ }),
138
+ mergeBadgeStepCopy(SIGNIN_STEPS_DEFAULT[1], data, {
139
+ password: { question: data.signInPasswordQuestion }
140
+ })
141
+ ];
142
+ }
143
+ function getRegisterSteps(data) {
144
+ return [
145
+ mergeBadgeStepCopy(REGISTER_STEPS_DEFAULT[0], data, {
146
+ name: {
147
+ question: data.signUpNameQuestion,
148
+ hint: data.signUpNameHint
149
+ }
150
+ }),
151
+ mergeBadgeStepCopy(REGISTER_STEPS_DEFAULT[1], data, {
152
+ email: { question: data.signUpEmailQuestion }
153
+ }),
154
+ mergeBadgeStepCopy(REGISTER_STEPS_DEFAULT[2], data, {
155
+ password: {
156
+ question: data.signUpPasswordQuestion,
157
+ hint: data.signUpPasswordHint
158
+ }
159
+ }),
160
+ mergeBadgeStepCopy(REGISTER_STEPS_DEFAULT[3], data, {
161
+ confirm: { question: data.signUpConfirmQuestion }
162
+ })
163
+ ];
164
+ }
165
+ function resolveAuthSteps(mode, data, auth) {
166
+ const flow = auth?.flows?.[mode];
167
+ if (flow?.steps?.length) return flow.steps;
168
+ if (mode === "signin" && auth?.signInSteps?.length) return auth.signInSteps;
169
+ if (mode === "signup" && auth?.signUpSteps?.length) return auth.signUpSteps;
170
+ return mode === "signin" ? getSignInSteps(data) : getRegisterSteps(data);
171
+ }
172
+ function defaultStepValidator(step, values, mode) {
173
+ const value = (values[step.id] ?? "").trim();
174
+ switch (step.id) {
175
+ case "name":
176
+ return value.length >= 2;
177
+ case "email":
178
+ return isValidEmail(value);
179
+ case "password":
180
+ return mode === "signin" ? value.length > 0 : value.length >= 8;
181
+ case "confirm":
182
+ return value.length >= 8 && value === (values.password ?? "");
183
+ default:
184
+ return value.length > 0;
185
+ }
186
+ }
187
+ function stepCanContinue(step, values, mode) {
188
+ if (step.validate) {
189
+ const result = step.validate(values[step.id] ?? "", values);
190
+ return result === true;
191
+ }
192
+ return defaultStepValidator(step, values, mode);
193
+ }
194
+ function emptyValuesForSteps(steps) {
195
+ return Object.fromEntries(steps.map((step) => [step.id, ""]));
196
+ }
197
+ function flowMeta(mode, data, auth) {
198
+ const flow = auth?.flows?.[mode];
199
+ const modeTitle = flow?.title ?? (mode === "signin" ? data.signInTitle ?? "SIGN IN" : data.signUpTitle ?? "SIGN UP");
200
+ const sideLabel = mode === "signin" ? flow?.sideLabel ?? data.signInSideLabel ?? "FRONT SIDE" : flow?.sideLabel ?? data.signUpSideLabel ?? "BACK SIDE";
201
+ const continueLabel = flow?.continueLabel ?? data.continueLabel ?? "CONTINUE \u2192";
202
+ const submitLabel = mode === "signin" ? flow?.submitLabel ?? data.signInSubmitLabel ?? "SIGN IN \u2192" : flow?.submitLabel ?? data.signUpSubmitLabel ?? "JOIN HANKI \u2192";
203
+ return { modeTitle, sideLabel, continueLabel, submitLabel };
204
+ }
205
+
206
+ // src/geometry.ts
207
+ var CARD_W = 260;
208
+ var CARD_H = 380;
209
+ var ANCHOR_Y = 40;
210
+ var STRAP_LENGTH = 280;
211
+ var ZOOM_AUTH_FOCUS_SCALE = 1.35;
212
+ var ZOOM_PERSPECTIVE = 900;
213
+ var ZOOM_MAX_Z = 700;
214
+ var MAX_ZOOM_SCALE = (ZOOM_PERSPECTIVE + ZOOM_MAX_Z) / ZOOM_PERSPECTIVE;
215
+ var COIL_STRENGTH = 0.72;
216
+ function coilFactorFromScale(scale) {
217
+ if (scale <= 1) return 0;
218
+ return Math.min(1, (scale - 1) / (MAX_ZOOM_SCALE - 1));
219
+ }
220
+ function effectiveStrapDrop(coil) {
221
+ return STRAP_LENGTH * (1 - coil * COIL_STRENGTH);
222
+ }
223
+ function effectiveCardBottomY(coil) {
224
+ return ANCHOR_Y + effectiveStrapDrop(coil) + CARD_H;
225
+ }
226
+ function effectiveCardCenterY(coil) {
227
+ return ANCHOR_Y + effectiveStrapDrop(coil) + CARD_H / 2;
228
+ }
229
+ var SPRING_STIFFNESS = 0.07;
230
+ var SPRING_DAMPING = 0.75;
231
+ var LABEL_START_OFFSET = 60;
232
+ var LABEL_SPACING = 130;
233
+ var LABEL_END_MARGIN = 20;
234
+ function cubicPoint(c, t) {
235
+ const mt = 1 - t;
236
+ return {
237
+ x: mt * mt * mt * c.p0.x + 3 * mt * mt * t * c.p1.x + 3 * mt * t * t * c.p2.x + t * t * t * c.p3.x,
238
+ y: mt * mt * mt * c.p0.y + 3 * mt * mt * t * c.p1.y + 3 * mt * t * t * c.p2.y + t * t * t * c.p3.y
239
+ };
240
+ }
241
+ function flattenCubics(cubics, stepsEach) {
242
+ const raw = [];
243
+ cubics.forEach((c, idx) => {
244
+ const start = idx === 0 ? 0 : 1;
245
+ for (let i = start; i <= stepsEach; i++) {
246
+ raw.push(cubicPoint(c, i / stepsEach));
247
+ }
248
+ });
249
+ return raw;
250
+ }
251
+ function addTangents(raw) {
252
+ return raw.map((p, i) => {
253
+ const prev = raw[Math.max(0, i - 1)];
254
+ const next = raw[Math.min(raw.length - 1, i + 1)];
255
+ const dx = next.x - prev.x;
256
+ const dy = next.y - prev.y;
257
+ const len = Math.hypot(dx, dy) || 1;
258
+ return { x: p.x, y: p.y, tx: dx / len, ty: dy / len };
259
+ });
260
+ }
261
+ function buildTenseStrapPoints(anchor, cardTopX, cardTopY, steps) {
262
+ const drop = Math.max(20, cardTopY - anchor.y);
263
+ const midY = anchor.y + drop * 0.5;
264
+ const raw = [];
265
+ for (let i = 0; i <= steps; i++) {
266
+ const t = i / steps;
267
+ const mt = 1 - t;
268
+ raw.push({
269
+ x: mt * mt * mt * anchor.x + 3 * mt * mt * t * anchor.x + 3 * mt * t * t * cardTopX + t * t * t * cardTopX,
270
+ y: mt * mt * mt * anchor.y + 3 * mt * mt * t * midY + 3 * mt * t * t * midY + t * t * t * cardTopY
271
+ });
272
+ }
273
+ return addTangents(raw);
274
+ }
275
+ function buildShoelaceLayers(anchor, cardTopX, cardTopY, coil, stepsEach) {
276
+ const ax = anchor.x;
277
+ const ay = anchor.y;
278
+ const spread = 16 + coil * 34;
279
+ const stackBase = ay + 8 + coil * 10;
280
+ const stackMid = stackBase + 20 + coil * 36;
281
+ const stackExit = stackMid + 14 + coil * 22;
282
+ const under = [
283
+ {
284
+ p0: { x: ax, y: ay },
285
+ p1: { x: ax - spread * 0.55, y: ay + 5 },
286
+ p2: { x: ax - spread * 0.92, y: stackBase + 6 },
287
+ p3: { x: ax - spread * 0.28, y: stackMid - 2 }
288
+ },
289
+ {
290
+ p0: { x: ax - spread * 0.28, y: stackMid - 2 },
291
+ p1: { x: ax - spread * 0.05, y: stackMid + 10 },
292
+ p2: { x: ax + spread * 0.42, y: stackExit - 6 },
293
+ p3: { x: ax + spread * 0.18, y: stackExit }
294
+ }
295
+ ];
296
+ const over = [
297
+ {
298
+ p0: { x: ax, y: ay },
299
+ p1: { x: ax + spread * 0.42, y: ay + 3 },
300
+ p2: { x: ax + spread * 1.02, y: stackBase + 8 },
301
+ p3: { x: ax + spread * 0.22, y: stackMid - 6 }
302
+ },
303
+ {
304
+ p0: { x: ax + spread * 0.22, y: stackMid - 6 },
305
+ p1: { x: ax - spread * 0.18, y: stackMid + 4 },
306
+ p2: { x: ax - spread * 0.42, y: stackMid + 16 },
307
+ p3: { x: ax - spread * 0.08, y: stackExit - 3 }
308
+ },
309
+ {
310
+ p0: { x: ax - spread * 0.08, y: stackExit - 3 },
311
+ p1: { x: ax + spread * 0.28, y: stackExit + 2 },
312
+ p2: { x: ax + spread * 0.12, y: stackExit + 8 },
313
+ p3: { x: ax + spread * 0.06, y: stackExit + 5 }
314
+ }
315
+ ];
316
+ const tail = [
317
+ {
318
+ p0: { x: ax + spread * 0.06, y: stackExit + 5 },
319
+ p1: { x: ax + spread * 0.08, y: stackExit + 28 },
320
+ p2: { x: cardTopX, y: cardTopY - 36 },
321
+ p3: { x: cardTopX, y: cardTopY }
322
+ }
323
+ ];
324
+ return [
325
+ { pts: addTangents(flattenCubics(under, stepsEach)), depth: "under" },
326
+ { pts: addTangents(flattenCubics(over, stepsEach)), depth: "over" },
327
+ { pts: addTangents(flattenCubics(tail, stepsEach)), depth: "over" }
328
+ ];
329
+ }
330
+ function concatStrapPaths(segments) {
331
+ const raw = [];
332
+ for (const seg of segments) {
333
+ for (const p of seg) {
334
+ if (raw.length > 0) {
335
+ const last = raw[raw.length - 1];
336
+ if (Math.hypot(p.x - last.x, p.y - last.y) < 0.5) continue;
337
+ }
338
+ raw.push({ x: p.x, y: p.y });
339
+ }
340
+ }
341
+ return addTangents(raw);
342
+ }
343
+ function buildStrapPoints(anchor, cardTopX, cardTopY, coil, steps) {
344
+ return buildTenseStrapPoints(anchor, cardTopX, cardTopY, steps);
345
+ }
346
+ var CRISP_TEXT = {
347
+ WebkitFontSmoothing: "antialiased",
348
+ MozOsxFontSmoothing: "grayscale",
349
+ textRendering: "optimizeLegibility"
350
+ };
351
+ function withAlpha(hex, alpha) {
352
+ const h = hex.replace("#", "");
353
+ if (h.length !== 6) return hex;
354
+ const r = parseInt(h.slice(0, 2), 16);
355
+ const g = parseInt(h.slice(2, 4), 16);
356
+ const b = parseInt(h.slice(4, 6), 16);
357
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
358
+ }
359
+ function contrastText(bg) {
360
+ const h = bg.replace("#", "");
361
+ if (h.length !== 6) return "#000";
362
+ const r = parseInt(h.slice(0, 2), 16);
363
+ const g = parseInt(h.slice(2, 4), 16);
364
+ const b = parseInt(h.slice(4, 6), 16);
365
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.55 ? "#000" : "#fff";
366
+ }
367
+ function Barcode({ color }) {
368
+ const bars = [3, 1, 2, 1, 3, 2, 1, 2, 1, 3, 1, 2, 2, 1, 1, 3, 2, 1, 2, 1, 3, 1, 2, 1, 3, 2, 1, 1, 2, 3, 1, 2, 1, 2, 3];
369
+ let x = 0;
370
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "200", height: "36", viewBox: "0 0 200 36", style: { display: "block" }, children: bars.map((w, i) => {
371
+ const rect = i % 2 === 0 ? /* @__PURE__ */ jsxRuntime.jsx("rect", { x, y: 0, width: w * 3.5, height: 36, fill: color, rx: 0.3 }, i) : null;
372
+ x += w * 3.5;
373
+ return rect;
374
+ }) });
375
+ }
376
+ function AuthModeToggleBar({
377
+ authMode,
378
+ signInTitle,
379
+ signUpTitle,
380
+ colors,
381
+ onChange
382
+ }) {
383
+ const c = colors;
384
+ const selectMode = (option, e) => {
385
+ e.preventDefault();
386
+ e.stopPropagation();
387
+ onChange(option);
388
+ };
389
+ return /* @__PURE__ */ jsxRuntime.jsx(
390
+ "div",
391
+ {
392
+ "data-auth-mode-toggle": true,
393
+ "data-no-drag": true,
394
+ "data-auth-interactive": true,
395
+ onPointerDown: (e) => e.stopPropagation(),
396
+ onPointerUp: (e) => e.stopPropagation(),
397
+ style: {
398
+ position: "absolute",
399
+ top: 22,
400
+ left: 8,
401
+ right: 8,
402
+ zIndex: 1,
403
+ display: "flex",
404
+ gap: 6,
405
+ padding: 5,
406
+ borderRadius: 10,
407
+ background: c.cardBg,
408
+ border: `1.5px solid ${withAlpha(c.accentColor, 0.32)}`,
409
+ boxShadow: "0 8px 24px rgba(0,0,0,0.42)",
410
+ pointerEvents: "auto",
411
+ touchAction: "manipulation",
412
+ cursor: "default",
413
+ isolation: "isolate"
414
+ },
415
+ children: ["signin", "signup"].map((option) => {
416
+ const active = authMode === option;
417
+ const label = option === "signin" ? signInTitle : signUpTitle;
418
+ return /* @__PURE__ */ jsxRuntime.jsx(
419
+ "button",
420
+ {
421
+ type: "button",
422
+ "data-auth-mode-toggle": true,
423
+ "data-no-drag": true,
424
+ onPointerDown: (e) => e.stopPropagation(),
425
+ onClick: (e) => selectMode(option, e),
426
+ style: {
427
+ flex: 1,
428
+ minHeight: 38,
429
+ cursor: "pointer",
430
+ border: active ? "none" : `1px solid ${withAlpha(c.accentColor, 0.22)}`,
431
+ borderRadius: 8,
432
+ padding: "10px 8px",
433
+ fontFamily: "var(--font-mono)",
434
+ fontSize: 9,
435
+ letterSpacing: "0.14em",
436
+ fontWeight: active ? 700 : 500,
437
+ lineHeight: 1,
438
+ background: active ? c.accentColor : withAlpha(c.accentColor, 0.07),
439
+ color: active ? contrastText(c.accentColor) : c.roleColor,
440
+ WebkitTapHighlightColor: "transparent"
441
+ },
442
+ children: label
443
+ },
444
+ option
445
+ );
446
+ })
447
+ }
448
+ );
449
+ }
450
+ function AuthFace({
451
+ mode,
452
+ data,
453
+ auth,
454
+ isFaceActive,
455
+ reserveToggleSpace
456
+ }) {
457
+ const customFace = auth?.renderAuthFace?.({
458
+ flowId: mode,
459
+ data,
460
+ isFaceActive,
461
+ reserveToggleSpace: !!reserveToggleSpace,
462
+ onComplete: (values2) => {
463
+ void auth?.onSubmit?.({
464
+ flowId: mode,
465
+ mode,
466
+ values: values2,
467
+ stepIndex: 0,
468
+ isComplete: true
469
+ });
470
+ }
471
+ });
472
+ if (customFace) {
473
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: customFace });
474
+ }
475
+ const c = data.colors;
476
+ const steps = resolveAuthSteps(mode, data, auth);
477
+ const { modeTitle, sideLabel, continueLabel, submitLabel } = flowMeta(mode, data, auth);
478
+ const headerEvent = data.authHeaderEvent ?? data.eventCode;
479
+ const headerVenue = data.authHeaderVenue ?? data.venue;
480
+ const headerDate = data.authHeaderDate ?? data.date;
481
+ const headerLogo = data.logoUrl ?? "/hanki-logo.png";
482
+ const [stepIndex, setStepIndex] = react.useState(0);
483
+ const [values, setValues] = react.useState(() => emptyValuesForSteps(steps));
484
+ const inputRef = react.useRef(null);
485
+ const currentStep = steps[stepIndex];
486
+ const isLastStep = stepIndex === steps.length - 1;
487
+ const canContinue = stepCanContinue(currentStep, values, mode);
488
+ const progress = (stepIndex + 1) / steps.length;
489
+ react.useEffect(() => {
490
+ setStepIndex(0);
491
+ setValues(emptyValuesForSteps(steps));
492
+ }, [mode, auth, data]);
493
+ react.useEffect(() => {
494
+ if (!isFaceActive) {
495
+ inputRef.current?.blur();
496
+ return;
497
+ }
498
+ const timer = window.setTimeout(() => inputRef.current?.focus({ preventScroll: true }), 120);
499
+ return () => window.clearTimeout(timer);
500
+ }, [stepIndex, mode, isFaceActive]);
501
+ const emitSubmit = (complete) => {
502
+ void auth?.onSubmit?.({
503
+ flowId: mode,
504
+ mode,
505
+ values,
506
+ stepIndex,
507
+ isComplete: complete
508
+ });
509
+ };
510
+ const handleContinue = () => {
511
+ if (!canContinue) return;
512
+ emitSubmit(isLastStep);
513
+ if (isLastStep) return;
514
+ setStepIndex((i) => i + 1);
515
+ };
516
+ const handleBack = () => {
517
+ if (stepIndex > 0) setStepIndex((i) => i - 1);
518
+ };
519
+ const handleFieldChange = (field, value) => {
520
+ setValues((prev) => ({ ...prev, [field]: value }));
521
+ };
522
+ const handleSubmit = (evt) => {
523
+ evt.preventDefault();
524
+ handleContinue();
525
+ };
526
+ return /* @__PURE__ */ jsxRuntime.jsxs(
527
+ "div",
528
+ {
529
+ "data-no-drag": true,
530
+ "data-auth-interactive": isFaceActive ? true : void 0,
531
+ "aria-hidden": !isFaceActive,
532
+ style: {
533
+ position: "absolute",
534
+ inset: 0,
535
+ borderRadius: 16,
536
+ overflow: "hidden",
537
+ pointerEvents: isFaceActive ? "auto" : "none",
538
+ visibility: isFaceActive ? "visible" : "hidden",
539
+ zIndex: 50,
540
+ background: c.cardBg,
541
+ touchAction: isFaceActive ? "auto" : "none",
542
+ userSelect: isFaceActive ? "auto" : "none",
543
+ cursor: isFaceActive ? "default" : "default",
544
+ ...CRISP_TEXT
545
+ },
546
+ onPointerEnter: () => {
547
+ if (isFaceActive) document.body.style.cursor = "default";
548
+ },
549
+ children: [
550
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", top: 0, left: 0, right: 0, height: 1, background: withAlpha(c.accentColor, 0.22), zIndex: 2 } }),
551
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
552
+ position: "absolute",
553
+ top: 14,
554
+ left: "50%",
555
+ transform: "translateX(-50%)",
556
+ width: 14,
557
+ height: 14,
558
+ borderRadius: "50%",
559
+ background: c.cardBg,
560
+ border: `1.5px solid ${c.ringColor}`,
561
+ zIndex: 11,
562
+ pointerEvents: "none"
563
+ } }),
564
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
565
+ position: "absolute",
566
+ inset: 0,
567
+ display: "flex",
568
+ flexDirection: "column",
569
+ paddingTop: reserveToggleSpace ? 68 : 30,
570
+ zIndex: 5
571
+ }, children: [
572
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
573
+ margin: "0 14px 6px",
574
+ padding: "6px 9px",
575
+ borderRadius: 8,
576
+ display: "flex",
577
+ alignItems: "center",
578
+ justifyContent: "space-between",
579
+ gap: 8,
580
+ background: mode === "signin" ? withAlpha(c.accentColor, 0.16) : withAlpha(c.accentColor, 0.08),
581
+ border: `1.5px solid ${mode === "signin" ? c.accentColor : withAlpha(c.accentColor, 0.28)}`
582
+ }, children: [
583
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
584
+ fontFamily: "var(--font-mono)",
585
+ fontSize: 10,
586
+ letterSpacing: "0.2em",
587
+ fontWeight: 700,
588
+ color: mode === "signin" ? c.accentColor : c.nameColor
589
+ }, children: modeTitle }),
590
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
591
+ fontFamily: "var(--font-mono)",
592
+ fontSize: 7,
593
+ letterSpacing: "0.16em",
594
+ color: c.roleColor,
595
+ whiteSpace: "nowrap"
596
+ }, children: sideLabel })
597
+ ] }),
598
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
599
+ display: "flex",
600
+ flexDirection: "column",
601
+ alignItems: "center",
602
+ gap: 4,
603
+ padding: "0 14px 8px"
604
+ }, children: [
605
+ /* @__PURE__ */ jsxRuntime.jsx(
606
+ "img",
607
+ {
608
+ src: headerLogo,
609
+ alt: "Hanki",
610
+ draggable: false,
611
+ style: { height: 22, width: "auto", maxWidth: 120, objectFit: "contain" }
612
+ }
613
+ ),
614
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 7, letterSpacing: "0.24em", color: c.metaColor }, children: headerEvent }),
615
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
616
+ fontFamily: "var(--font-sans)",
617
+ fontWeight: 700,
618
+ fontSize: 16,
619
+ color: c.accentColor,
620
+ letterSpacing: "-0.02em",
621
+ lineHeight: 1.1
622
+ }, children: headerVenue }),
623
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 7, color: c.roleColor, letterSpacing: "0.08em", textAlign: "center" }, children: headerDate })
624
+ ] }),
625
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: withAlpha(c.accentColor, 0.12), margin: "0 14px 6px" } }),
626
+ /* @__PURE__ */ jsxRuntime.jsxs(
627
+ "form",
628
+ {
629
+ "data-no-drag": true,
630
+ onSubmit: handleSubmit,
631
+ style: {
632
+ display: "flex",
633
+ flexDirection: "column",
634
+ padding: "0 14px",
635
+ touchAction: "auto"
636
+ },
637
+ children: [
638
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 3, borderRadius: 999, background: withAlpha(c.accentColor, 0.12), overflow: "hidden", marginBottom: 6 }, children: /* @__PURE__ */ jsxRuntime.jsx(
639
+ "div",
640
+ {
641
+ style: {
642
+ height: "100%",
643
+ width: `${progress * 100}%`,
644
+ borderRadius: 999,
645
+ background: c.accentColor,
646
+ transition: "width 0.35s ease"
647
+ }
648
+ }
649
+ ) }),
650
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontFamily: "var(--font-mono)", fontSize: 7, letterSpacing: "0.2em", color: c.roleColor, marginBottom: 6 }, children: [
651
+ modeTitle,
652
+ " \xB7 STEP ",
653
+ stepIndex + 1,
654
+ " OF ",
655
+ steps.length
656
+ ] }),
657
+ /* @__PURE__ */ jsxRuntime.jsxs(
658
+ "div",
659
+ {
660
+ style: {
661
+ display: "flex",
662
+ flexDirection: "column",
663
+ gap: 6,
664
+ animation: "authStepIn 0.32s ease"
665
+ },
666
+ children: [
667
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
668
+ fontFamily: "var(--font-sans)",
669
+ fontWeight: 700,
670
+ fontSize: 13,
671
+ lineHeight: 1.25,
672
+ color: c.nameColor,
673
+ letterSpacing: "-0.02em",
674
+ margin: 0
675
+ }, children: currentStep.question }),
676
+ currentStep.hint ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 7, color: c.roleColor, letterSpacing: "0.06em", margin: 0 }, children: currentStep.hint }) : null,
677
+ /* @__PURE__ */ jsxRuntime.jsx(
678
+ AuthField,
679
+ {
680
+ ref: inputRef,
681
+ label: currentStep.label,
682
+ type: currentStep.type ?? "text",
683
+ placeholder: currentStep.placeholder ?? "",
684
+ autoComplete: currentStep.autoComplete,
685
+ colors: c,
686
+ value: values[currentStep.id] ?? "",
687
+ onChange: (value) => handleFieldChange(currentStep.id, value)
688
+ }
689
+ )
690
+ ]
691
+ },
692
+ `${mode}-${currentStep.id}`
693
+ ),
694
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 10, paddingBottom: 6 }, children: [
695
+ stepIndex > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
696
+ "button",
697
+ {
698
+ type: "button",
699
+ "data-no-drag": true,
700
+ onClick: handleBack,
701
+ "aria-label": "Go back",
702
+ style: {
703
+ width: 32,
704
+ height: 32,
705
+ flexShrink: 0,
706
+ borderRadius: 999,
707
+ border: `1px solid ${withAlpha(c.accentColor, 0.25)}`,
708
+ background: "transparent",
709
+ color: c.metaColor,
710
+ cursor: "pointer",
711
+ fontFamily: "var(--font-mono)",
712
+ fontSize: 14,
713
+ lineHeight: 1
714
+ },
715
+ children: "\u2190"
716
+ }
717
+ ) : null,
718
+ /* @__PURE__ */ jsxRuntime.jsx(
719
+ "button",
720
+ {
721
+ type: "submit",
722
+ "data-no-drag": true,
723
+ disabled: !canContinue,
724
+ style: {
725
+ flex: 1,
726
+ height: 34,
727
+ borderRadius: 8,
728
+ background: canContinue ? c.accentColor : withAlpha(c.accentColor, 0.35),
729
+ color: contrastText(c.accentColor),
730
+ border: "none",
731
+ cursor: canContinue ? "pointer" : "not-allowed",
732
+ fontFamily: "var(--font-mono)",
733
+ fontSize: 10,
734
+ letterSpacing: "0.18em",
735
+ fontWeight: 600,
736
+ transition: "opacity 0.2s, background 0.2s",
737
+ opacity: canContinue ? 1 : 0.7
738
+ },
739
+ onMouseEnter: (e) => {
740
+ if (canContinue) e.currentTarget.style.opacity = "0.85";
741
+ },
742
+ onMouseLeave: (e) => {
743
+ if (canContinue) e.currentTarget.style.opacity = "1";
744
+ },
745
+ children: isLastStep ? submitLabel : continueLabel
746
+ }
747
+ )
748
+ ] })
749
+ ]
750
+ }
751
+ ),
752
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
753
+ height: 34,
754
+ flexShrink: 0,
755
+ marginTop: "auto",
756
+ display: "flex",
757
+ alignItems: "center",
758
+ justifyContent: "center",
759
+ background: c.footerBg,
760
+ borderTop: `1px solid ${withAlpha(c.accentColor, 0.12)}`
761
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: "0.16em", color: c.footerText }, children: data.website }) })
762
+ ] }),
763
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, borderRadius: 16, pointerEvents: "none", zIndex: 15, boxShadow: `inset 0 1px 0 ${withAlpha(c.accentColor, 0.12)}, inset 0 -1px 0 rgba(0,0,0,0.4)` } })
764
+ ]
765
+ }
766
+ );
767
+ }
768
+ var AuthField = function AuthField2({
769
+ label,
770
+ type,
771
+ placeholder,
772
+ autoComplete,
773
+ colors,
774
+ value,
775
+ onChange,
776
+ ref
777
+ }) {
778
+ const inputId = react.useId();
779
+ const borderDefault = withAlpha(colors.accentColor, 0.14);
780
+ const borderFocus = withAlpha(colors.accentColor, 0.45);
781
+ return /* @__PURE__ */ jsxRuntime.jsxs(
782
+ "div",
783
+ {
784
+ "data-no-drag": true,
785
+ style: { display: "flex", flexDirection: "column", gap: 6, position: "relative", zIndex: 20 },
786
+ children: [
787
+ /* @__PURE__ */ jsxRuntime.jsx(
788
+ "label",
789
+ {
790
+ htmlFor: inputId,
791
+ style: {
792
+ fontFamily: "var(--font-mono)",
793
+ fontSize: 8,
794
+ letterSpacing: "0.24em",
795
+ color: colors.metaColor,
796
+ cursor: "text",
797
+ paddingBottom: 2
798
+ },
799
+ children: label
800
+ }
801
+ ),
802
+ /* @__PURE__ */ jsxRuntime.jsx(
803
+ "input",
804
+ {
805
+ id: inputId,
806
+ ref,
807
+ type,
808
+ value,
809
+ onChange: (e) => onChange(e.target.value),
810
+ placeholder,
811
+ autoComplete,
812
+ "data-no-drag": true,
813
+ onPointerDown: (e) => e.stopPropagation(),
814
+ style: {
815
+ height: 42,
816
+ minHeight: 42,
817
+ padding: "0 12px",
818
+ borderRadius: 8,
819
+ background: withAlpha(colors.accentColor, 0.06),
820
+ border: `1px solid ${borderDefault}`,
821
+ color: colors.nameColor,
822
+ fontSize: 13,
823
+ fontFamily: "var(--font-mono)",
824
+ letterSpacing: "0.04em",
825
+ outline: "none",
826
+ transition: "border-color 0.2s",
827
+ cursor: "text",
828
+ touchAction: "manipulation",
829
+ userSelect: "text",
830
+ WebkitUserSelect: "text",
831
+ ...CRISP_TEXT
832
+ },
833
+ onFocus: (e) => e.target.style.borderColor = borderFocus,
834
+ onBlur: (e) => e.target.style.borderColor = borderDefault
835
+ }
836
+ )
837
+ ]
838
+ }
839
+ );
840
+ };
841
+ function CardFront({
842
+ data,
843
+ auth,
844
+ shinePct,
845
+ isFaceActive,
846
+ reserveToggleSpace
847
+ }) {
848
+ const c = data.colors;
849
+ return /* @__PURE__ */ jsxRuntime.jsx(
850
+ "div",
851
+ {
852
+ className: "absolute inset-0 rounded-2xl overflow-hidden",
853
+ style: {
854
+ backfaceVisibility: "hidden",
855
+ WebkitBackfaceVisibility: "hidden",
856
+ pointerEvents: isFaceActive ? "auto" : "none"
857
+ },
858
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full", style: { background: c.cardBg }, children: [
859
+ /* @__PURE__ */ jsxRuntime.jsxs(
860
+ "div",
861
+ {
862
+ style: {
863
+ position: "absolute",
864
+ inset: 0,
865
+ opacity: 0,
866
+ pointerEvents: "none"
867
+ },
868
+ children: [
869
+ /* @__PURE__ */ jsxRuntime.jsx(
870
+ "div",
871
+ {
872
+ className: "absolute inset-0 pointer-events-none rounded-2xl",
873
+ style: {
874
+ background: `radial-gradient(ellipse at ${shinePct.x}% ${shinePct.y}%, rgba(255,255,255,0.07) 0%, transparent 60%)`,
875
+ zIndex: 10
876
+ }
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute", style: { top: 14, left: "50%", transform: "translateX(-50%)", width: 14, height: 14, borderRadius: "50%", background: "#000", border: "1.5px solid #333", zIndex: 11 } }),
880
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-0 left-0 right-0 flex items-start justify-between px-4", style: { zIndex: 12, paddingTop: 34 }, children: [
881
+ data.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx(
882
+ "img",
883
+ {
884
+ src: data.logoUrl,
885
+ alt: "Logo",
886
+ style: { height: 13, maxWidth: 80, objectFit: "contain" },
887
+ draggable: false
888
+ }
889
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
890
+ "img",
891
+ {
892
+ src: "/hanki-logo.png",
893
+ alt: "Hanki",
894
+ style: { height: 13, maxWidth: 80, objectFit: "contain" },
895
+ draggable: false
896
+ }
897
+ ),
898
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-end", style: { gap: 2 }, children: [
899
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: "0.06em", color: c.metaColor, whiteSpace: "nowrap" }, children: data.date }),
900
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: "0.06em", color: c.metaColor, whiteSpace: "nowrap" }, children: data.location })
901
+ ] })
902
+ ] }),
903
+ /* @__PURE__ */ jsxRuntime.jsx(
904
+ "div",
905
+ {
906
+ className: "absolute pointer-events-none flex items-center justify-center",
907
+ style: {
908
+ top: 48,
909
+ left: 0,
910
+ right: 0,
911
+ zIndex: 3,
912
+ userSelect: "none"
913
+ },
914
+ children: data.shipImageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
915
+ "img",
916
+ {
917
+ src: data.shipImageUrl,
918
+ alt: "Ship Image",
919
+ style: {
920
+ width: "68%",
921
+ height: "auto"
922
+ },
923
+ draggable: false
924
+ }
925
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
926
+ "img",
927
+ {
928
+ src: data.logoUrl ?? "/hanki-logo.png",
929
+ alt: "Hanki",
930
+ style: {
931
+ width: "68%",
932
+ height: "auto",
933
+ objectFit: "contain"
934
+ },
935
+ draggable: false
936
+ }
937
+ )
938
+ }
939
+ ),
940
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute pointer-events-none", style: { top: 150, left: 24, zIndex: 6 }, children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "18", height: "22", viewBox: "0 0 22 26", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1 1L1 20.5L6 15.5L9.5 23L12 22L8.5 14.5L15.5 14.5L1 1Z", fill: c.metaColor, stroke: c.cardBg, strokeWidth: "1.5" }) }) }),
941
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute pointer-events-none", style: { top: 82, right: 18, width: 0, height: 0, borderLeft: "7px solid transparent", borderRight: "7px solid transparent", borderBottom: `12px solid ${c.metaColor}`, opacity: 0.4, zIndex: 6 } }),
942
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-0 left-0 right-0 flex flex-col justify-end", style: { zIndex: 7 }, children: [
943
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-5 pb-2", children: [
944
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 32, lineHeight: 1.05, color: c.nameColor, letterSpacing: "-0.02em" }, children: data.name }),
945
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.15em", color: c.roleColor, marginTop: 6 }, children: data.roleType.toUpperCase() })
946
+ ] }),
947
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "rgba(255,255,255,0.07)", margin: "10px 0" } }),
948
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-5 pb-3 flex items-end justify-between", children: [
949
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
950
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.12em", color: c.metaColor, lineHeight: 1.7 }, children: data.venue.toUpperCase() }),
951
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.12em", color: c.metaColor, opacity: 0.6 }, children: data.address.toUpperCase() })
952
+ ] }),
953
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "22", height: "19", viewBox: "0 0 1155 1000", fill: c.accentColor, style: { opacity: 0.9 }, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m577.3 0 577.4 1000H0z" }) })
954
+ ] }),
955
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", style: { background: c.footerBg, height: 36 }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.14em", color: c.footerText }, children: data.website }) })
956
+ ] })
957
+ ]
958
+ }
959
+ ),
960
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pointer-events-none rounded-2xl", style: { zIndex: 49, boxShadow: "inset 0 1px 0 rgba(255,255,255,0.08), inset 0 -1px 0 rgba(0,0,0,0.4)" } }),
961
+ /* @__PURE__ */ jsxRuntime.jsx(
962
+ AuthFace,
963
+ {
964
+ mode: "signin",
965
+ data,
966
+ auth,
967
+ isFaceActive,
968
+ reserveToggleSpace
969
+ }
970
+ )
971
+ ] })
972
+ }
973
+ );
974
+ }
975
+ function CardBack({
976
+ data,
977
+ auth,
978
+ shinePct,
979
+ isFaceActive,
980
+ reserveToggleSpace
981
+ }) {
982
+ const c = data.colors;
983
+ const qrValue = data.website ? data.website.startsWith("http") ? data.website : `https://${data.website}` : "https://example.com";
984
+ return /* @__PURE__ */ jsxRuntime.jsx(
985
+ "div",
986
+ {
987
+ className: "absolute inset-0 rounded-2xl overflow-hidden",
988
+ style: {
989
+ backfaceVisibility: "hidden",
990
+ WebkitBackfaceVisibility: "hidden",
991
+ transform: "rotateY(180deg)",
992
+ pointerEvents: isFaceActive ? "auto" : "none"
993
+ },
994
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full flex flex-col", style: { background: c.cardBg }, children: [
995
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", inset: 0, opacity: 0, pointerEvents: "none" }, children: [
996
+ /* @__PURE__ */ jsxRuntime.jsx(
997
+ "div",
998
+ {
999
+ className: "absolute inset-0 pointer-events-none rounded-2xl",
1000
+ style: {
1001
+ background: `radial-gradient(ellipse at ${100 - shinePct.x}% ${shinePct.y}%, rgba(255,255,255,0.07) 0%, transparent 60%)`,
1002
+ zIndex: 10
1003
+ }
1004
+ }
1005
+ ),
1006
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute", style: { top: 14, left: "50%", transform: "translateX(-50%)", width: 14, height: 14, borderRadius: "50%", background: "#000", border: "1.5px solid #333", zIndex: 11 } }),
1007
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 4, background: c.accentColor, flexShrink: 0 } }),
1008
+ /* @__PURE__ */ jsxRuntime.jsx(
1009
+ "div",
1010
+ {
1011
+ className: "absolute pointer-events-none select-none",
1012
+ style: { top: -20, right: -30, fontFamily: "var(--font-sans)", fontWeight: 900, fontSize: 200, lineHeight: 1, color: c.accentColor, opacity: 0.04, letterSpacing: "-0.05em", zIndex: 1 },
1013
+ children: data.eventCode
1014
+ }
1015
+ ),
1016
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col px-5", style: { zIndex: 5, gap: 7, flex: 1, paddingTop: 28 }, children: [
1017
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1018
+ data.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx(
1019
+ "img",
1020
+ {
1021
+ src: data.logoUrl,
1022
+ alt: "Logo",
1023
+ style: { height: 14, maxWidth: 80, objectFit: "contain" },
1024
+ draggable: false
1025
+ }
1026
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1027
+ "img",
1028
+ {
1029
+ src: "/hanki-logo.png",
1030
+ alt: "Hanki",
1031
+ style: { height: 14, maxWidth: 80, objectFit: "contain" },
1032
+ draggable: false
1033
+ }
1034
+ ),
1035
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: "0.2em", color: c.roleColor }, children: [
1036
+ data.eventCode,
1037
+ " / ",
1038
+ data.badgeLabel || "BADGE"
1039
+ ] })
1040
+ ] }),
1041
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1042
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontFamily: "var(--font-sans)", fontWeight: 800, fontSize: 17, color: c.nameColor, letterSpacing: "-0.02em", lineHeight: 1.1 }, children: data.company || "Company Name" }),
1043
+ data.tagline && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: "0.04em", color: c.metaColor, lineHeight: 1.4, opacity: 0.7, marginTop: 3 }, children: data.tagline })
1044
+ ] }),
1045
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: `${c.accentColor}30` } }),
1046
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start", style: { gap: 10 }, children: [
1047
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md overflow-hidden flex-shrink-0", style: { padding: 5, background: "#ffffff" }, children: /* @__PURE__ */ jsxRuntime.jsx(qrcode_react.QRCodeSVG, { value: qrValue, size: 78, bgColor: "#ffffff", fgColor: "#000000", level: "M" }) }),
1048
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col flex-1 min-w-0", style: { gap: 5, paddingTop: 1 }, children: [
1049
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1050
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 8, letterSpacing: "0.18em", color: c.metaColor, marginBottom: 2 }, children: data.scanLabel || "SCAN TO VISIT" }),
1051
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: "0.03em", color: c.accentColor, fontWeight: 600, lineHeight: 1.3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: data.website })
1052
+ ] }),
1053
+ data.email && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1054
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 8, letterSpacing: "0.15em", color: c.metaColor, marginBottom: 2 }, children: data.emailLabel || "EMAIL" }),
1055
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 8.5, color: c.roleColor, lineHeight: 1.3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: data.email })
1056
+ ] }),
1057
+ data.phone && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1058
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 8, letterSpacing: "0.15em", color: c.metaColor, marginBottom: 2 }, children: data.phoneLabel || "PHONE" }),
1059
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 8.5, color: c.roleColor, lineHeight: 1.3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: data.phone })
1060
+ ] })
1061
+ ] })
1062
+ ] }),
1063
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: `${c.accentColor}30` } }),
1064
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1065
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 8, letterSpacing: "0.18em", color: c.metaColor, marginBottom: 4 }, children: data.addressLabel ?? "ADDRESS" }),
1066
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", style: { gap: 8 }, children: [
1067
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
1068
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 9.5, color: c.roleColor, lineHeight: 1.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: data.venue }),
1069
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 9.5, color: c.roleColor, opacity: 0.7, lineHeight: 1.5, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: data.address })
1070
+ ] }),
1071
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 9.5, color: c.roleColor, opacity: 0.6, textAlign: "right", flexShrink: 0, whiteSpace: "nowrap" }, children: data.location })
1072
+ ] })
1073
+ ] }),
1074
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: `${c.accentColor}30` } }),
1075
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1076
+ /* @__PURE__ */ jsxRuntime.jsx(Barcode, { color: c.accentColor + "aa" }),
1077
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "var(--font-mono)", fontSize: 7, letterSpacing: "0.22em", color: c.metaColor, opacity: 0.4, marginTop: 3 }, children: "*" + data.name.replace(/\s/g, "").toUpperCase().slice(0, 12).padEnd(12, "0") + "*" })
1078
+ ] })
1079
+ ] }),
1080
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 mt-auto", style: { background: c.footerBg, height: 36, flexShrink: 0 }, children: [
1081
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.14em", color: c.footerText }, children: data.website }),
1082
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "14", viewBox: "0 0 1155 1000", fill: c.accentColor, style: { opacity: 0.8 }, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m577.3 0 577.4 1000H0z" }) })
1083
+ ] })
1084
+ ] }),
1085
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pointer-events-none rounded-2xl", style: { zIndex: 49, boxShadow: "inset 0 1px 0 rgba(255,255,255,0.08), inset 0 -1px 0 rgba(0,0,0,0.4)" } }),
1086
+ /* @__PURE__ */ jsxRuntime.jsx(
1087
+ AuthFace,
1088
+ {
1089
+ mode: "signup",
1090
+ data,
1091
+ auth,
1092
+ isFaceActive,
1093
+ reserveToggleSpace
1094
+ }
1095
+ )
1096
+ ] })
1097
+ }
1098
+ );
1099
+ }
1100
+ function LanyardCard({
1101
+ data,
1102
+ zoomScale = 1,
1103
+ authMode = "signin",
1104
+ onAuthModeChange,
1105
+ auth,
1106
+ className,
1107
+ style
1108
+ }) {
1109
+ const containerRef = react.useRef(null);
1110
+ const cardRef = react.useRef(null);
1111
+ const canvasRef = react.useRef(null);
1112
+ const coilRef = react.useRef(coilFactorFromScale(zoomScale));
1113
+ const pos = react.useRef({ x: 0, y: 0 });
1114
+ const vel = react.useRef({ x: 0, y: 0 });
1115
+ const tilt = react.useRef({ rx: 0, ry: 0 });
1116
+ const tiltTarget = react.useRef({ rx: 0, ry: 0 });
1117
+ const dragging = react.useRef(false);
1118
+ const dragOffset = react.useRef({ x: 0, y: 0 });
1119
+ const hasMounted = react.useRef(false);
1120
+ const rafId = react.useRef(0);
1121
+ const didDrag = react.useRef(false);
1122
+ const [shinePct, setShinePct] = react.useState({ x: 50, y: 50 });
1123
+ const [flipAngle, setFlipAngle] = react.useState(authMode === "signup" ? 180 : 0);
1124
+ const flipRef = react.useRef(authMode === "signup" ? 180 : 0);
1125
+ const flipTarget = react.useRef(authMode === "signup" ? 180 : 0);
1126
+ const flipRaf = react.useRef(0);
1127
+ const isSignup = authMode === "signup";
1128
+ const allowFlipOnClick = zoomScale < ZOOM_AUTH_FOCUS_SCALE;
1129
+ const lanyardTextRef = react.useRef(resolveLanyardText(data));
1130
+ lanyardTextRef.current = resolveLanyardText(data);
1131
+ const getAnchor = react.useCallback(() => {
1132
+ if (!containerRef.current) return { x: 0, y: ANCHOR_Y };
1133
+ return { x: containerRef.current.offsetWidth * 0.5, y: ANCHOR_Y };
1134
+ }, []);
1135
+ const drawRope = react.useCallback((colors, lanyardText) => {
1136
+ const canvas = canvasRef.current;
1137
+ if (!canvas || !containerRef.current) return;
1138
+ const ctx = canvas.getContext("2d");
1139
+ if (!ctx) return;
1140
+ const w = containerRef.current.offsetWidth;
1141
+ const h = containerRef.current.offsetHeight;
1142
+ const dpr = window.devicePixelRatio || 1;
1143
+ canvas.width = w * dpr;
1144
+ canvas.height = h * dpr;
1145
+ canvas.style.width = `${w}px`;
1146
+ canvas.style.height = `${h}px`;
1147
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1148
+ ctx.clearRect(0, 0, w, h);
1149
+ const anchor = getAnchor();
1150
+ const cardTopX = pos.current.x + CARD_W / 2;
1151
+ const cardTopY = pos.current.y - 18;
1152
+ const coil = coilRef.current;
1153
+ const steps = 80;
1154
+ const strapHalf = 15;
1155
+ const useShoelace = coil >= 0.04;
1156
+ const layers = useShoelace ? buildShoelaceLayers(anchor, cardTopX, cardTopY, coil, Math.round(steps / 3)) : null;
1157
+ const pts = layers ? layers[layers.length - 1].pts : buildStrapPoints(anchor, cardTopX, cardTopY, coil, steps);
1158
+ const drawStrapBody = (strapPts, depth) => {
1159
+ const fill = depth === "under" ? "#060606" : "#0a0a0a";
1160
+ const hi = depth === "under" ? 0.05 : 0.08;
1161
+ const lo = depth === "under" ? 0.28 : 0.4;
1162
+ ctx.save();
1163
+ ctx.beginPath();
1164
+ for (let i = 0; i < strapPts.length; i++) {
1165
+ const p = strapPts[i];
1166
+ const nx = -p.ty;
1167
+ const ny = p.tx;
1168
+ if (i === 0) ctx.moveTo(p.x + nx * strapHalf, p.y + ny * strapHalf);
1169
+ else ctx.lineTo(p.x + nx * strapHalf, p.y + ny * strapHalf);
1170
+ }
1171
+ for (let i = strapPts.length - 1; i >= 0; i--) {
1172
+ const p = strapPts[i];
1173
+ const nx = -p.ty;
1174
+ const ny = p.tx;
1175
+ ctx.lineTo(p.x - nx * strapHalf, p.y - ny * strapHalf);
1176
+ }
1177
+ ctx.closePath();
1178
+ ctx.fillStyle = "rgba(0,0,0,0.45)";
1179
+ ctx.filter = "blur(4px)";
1180
+ ctx.fill();
1181
+ ctx.filter = "none";
1182
+ ctx.fillStyle = fill;
1183
+ ctx.fill();
1184
+ const midPt = strapPts[Math.floor(strapPts.length / 2)];
1185
+ const grad = ctx.createLinearGradient(
1186
+ midPt.x - strapHalf,
1187
+ midPt.y,
1188
+ midPt.x + strapHalf,
1189
+ midPt.y
1190
+ );
1191
+ grad.addColorStop(0, `rgba(255,255,255,${hi})`);
1192
+ grad.addColorStop(0.4, "rgba(255,255,255,0)");
1193
+ grad.addColorStop(0.6, "rgba(0,0,0,0)");
1194
+ grad.addColorStop(1, `rgba(0,0,0,${lo})`);
1195
+ ctx.fillStyle = grad;
1196
+ ctx.fill();
1197
+ ctx.restore();
1198
+ ctx.save();
1199
+ const strokeEdge = (sign, color) => {
1200
+ ctx.beginPath();
1201
+ for (let i = 0; i < strapPts.length; i++) {
1202
+ const p = strapPts[i];
1203
+ const nx = -p.ty * sign;
1204
+ const ny = p.tx * sign;
1205
+ const x = p.x + nx * strapHalf;
1206
+ const y = p.y + ny * strapHalf;
1207
+ if (i === 0) ctx.moveTo(x, y);
1208
+ else ctx.lineTo(x, y);
1209
+ }
1210
+ ctx.strokeStyle = color;
1211
+ ctx.lineWidth = 1;
1212
+ ctx.stroke();
1213
+ };
1214
+ strokeEdge(1, depth === "under" ? "rgba(255,255,255,0.08)" : "rgba(255,255,255,0.18)");
1215
+ strokeEdge(-1, depth === "under" ? "rgba(0,0,0,0.75)" : "rgba(0,0,0,0.6)");
1216
+ ctx.restore();
1217
+ };
1218
+ const arcMetrics = (strapPts) => {
1219
+ const cumLen = [0];
1220
+ for (let i = 1; i < strapPts.length; i++) {
1221
+ const dx = strapPts[i].x - strapPts[i - 1].x;
1222
+ const dy = strapPts[i].y - strapPts[i - 1].y;
1223
+ cumLen.push(cumLen[i - 1] + Math.hypot(dx, dy));
1224
+ }
1225
+ const totalLen = cumLen[cumLen.length - 1];
1226
+ const sampleAt = (targetLen) => {
1227
+ const clamped = Math.max(0, Math.min(targetLen, totalLen));
1228
+ let i = 1;
1229
+ while (i < cumLen.length && cumLen[i] < clamped) i++;
1230
+ const i0 = Math.max(0, i - 1);
1231
+ const i1 = Math.min(strapPts.length - 1, i);
1232
+ const segLen = cumLen[i1] - cumLen[i0] || 1;
1233
+ const t = (clamped - cumLen[i0]) / segLen;
1234
+ const a = strapPts[i0];
1235
+ const b = strapPts[i1];
1236
+ const dx = b.x - a.x;
1237
+ const dy = b.y - a.y;
1238
+ const len = Math.hypot(dx, dy) || 1;
1239
+ return {
1240
+ x: a.x + dx * t,
1241
+ y: a.y + dy * t,
1242
+ tx: dx / len,
1243
+ ty: dy / len
1244
+ };
1245
+ };
1246
+ return { totalLen, sampleAt };
1247
+ };
1248
+ const drawStrapLabels = (strapPts) => {
1249
+ const { totalLen, sampleAt } = arcMetrics(strapPts);
1250
+ ctx.save();
1251
+ ctx.textAlign = "center";
1252
+ ctx.textBaseline = "middle";
1253
+ for (let d = LABEL_START_OFFSET; d < totalLen - LABEL_END_MARGIN; d += LABEL_SPACING) {
1254
+ const p = sampleAt(d);
1255
+ const angle = Math.atan2(p.ty, p.tx);
1256
+ ctx.save();
1257
+ ctx.translate(p.x, p.y);
1258
+ ctx.rotate(angle);
1259
+ ctx.fillStyle = "rgba(255,255,255,0.92)";
1260
+ ctx.font = '700 12px "Geist", system-ui, sans-serif';
1261
+ ctx.fillText(lanyardText || DEFAULT_LANYARD_TEXT, 0, 0);
1262
+ ctx.restore();
1263
+ }
1264
+ ctx.restore();
1265
+ };
1266
+ const labelPath = layers ? concatStrapPaths(layers.map((layer) => layer.pts)) : buildTenseStrapPoints(anchor, cardTopX, cardTopY, steps);
1267
+ if (layers) {
1268
+ for (const layer of layers) {
1269
+ drawStrapBody(layer.pts, layer.depth);
1270
+ }
1271
+ } else {
1272
+ drawStrapBody(pts, "over");
1273
+ }
1274
+ drawStrapLabels(labelPath);
1275
+ const clipTopY = cardTopY;
1276
+ const cardRingY = pos.current.y + 16;
1277
+ ctx.save();
1278
+ ctx.translate(cardTopX, clipTopY);
1279
+ ctx.save();
1280
+ ctx.fillStyle = "rgba(0,0,0,0.55)";
1281
+ ctx.filter = "blur(4px)";
1282
+ ctx.beginPath();
1283
+ ctx.roundRect(-10, 0, 20, 26, 4);
1284
+ ctx.fill();
1285
+ ctx.restore();
1286
+ const clipGrad = ctx.createLinearGradient(-10, 0, 10, 0);
1287
+ clipGrad.addColorStop(0, "#0d0d0d");
1288
+ clipGrad.addColorStop(0.5, "#2e2e2e");
1289
+ clipGrad.addColorStop(1, "#070707");
1290
+ ctx.fillStyle = clipGrad;
1291
+ ctx.beginPath();
1292
+ ctx.roundRect(-10, 0, 20, 26, 4);
1293
+ ctx.fill();
1294
+ ctx.fillStyle = "rgba(255,255,255,0.14)";
1295
+ ctx.beginPath();
1296
+ ctx.roundRect(-8, 3, 3, 20, 1.5);
1297
+ ctx.fill();
1298
+ ctx.strokeStyle = "rgba(0,0,0,0.7)";
1299
+ ctx.lineWidth = 0.7;
1300
+ ctx.beginPath();
1301
+ ctx.roundRect(-10, 0, 20, 26, 4);
1302
+ ctx.stroke();
1303
+ ctx.restore();
1304
+ ctx.save();
1305
+ ctx.strokeStyle = "#1a1a1a";
1306
+ ctx.lineWidth = 2.2;
1307
+ ctx.lineCap = "round";
1308
+ ctx.beginPath();
1309
+ ctx.moveTo(cardTopX, clipTopY + 26);
1310
+ ctx.lineTo(cardTopX, cardRingY + 2);
1311
+ ctx.stroke();
1312
+ ctx.strokeStyle = "rgba(255,255,255,0.18)";
1313
+ ctx.lineWidth = 0.8;
1314
+ ctx.beginPath();
1315
+ ctx.moveTo(cardTopX - 0.6, clipTopY + 26);
1316
+ ctx.lineTo(cardTopX - 0.6, cardRingY + 2);
1317
+ ctx.stroke();
1318
+ ctx.restore();
1319
+ }, [getAnchor]);
1320
+ const animate = react.useCallback(() => {
1321
+ if (!hasMounted.current) return;
1322
+ const card = cardRef.current;
1323
+ if (!card) return;
1324
+ if (!dragging.current) {
1325
+ const anchor = getAnchor();
1326
+ const coil = coilRef.current;
1327
+ const drop = effectiveStrapDrop(coil);
1328
+ const restX = anchor.x - CARD_W / 2;
1329
+ const restY = anchor.y + drop;
1330
+ vel.current.x += (restX - pos.current.x) * SPRING_STIFFNESS;
1331
+ vel.current.y += (restY - pos.current.y) * SPRING_STIFFNESS;
1332
+ vel.current.x *= SPRING_DAMPING;
1333
+ vel.current.y *= SPRING_DAMPING;
1334
+ pos.current.x += vel.current.x;
1335
+ pos.current.y += vel.current.y;
1336
+ }
1337
+ tilt.current.rx += (tiltTarget.current.rx - tilt.current.rx) * 0.1;
1338
+ tilt.current.ry += (tiltTarget.current.ry - tilt.current.ry) * 0.1;
1339
+ card.style.transform = `translate(${pos.current.x}px, ${pos.current.y}px) rotateX(${tilt.current.rx}deg) rotateY(${tilt.current.ry}deg)`;
1340
+ drawRope(
1341
+ card.__badgeColors || { accentColor: "#888", ringColor: "#888" },
1342
+ lanyardTextRef.current
1343
+ );
1344
+ rafId.current = requestAnimationFrame(animate);
1345
+ }, [getAnchor, drawRope]);
1346
+ const animateFlip = react.useCallback(() => {
1347
+ const diff = flipTarget.current - flipRef.current;
1348
+ flipRef.current += diff * 0.12;
1349
+ setFlipAngle(flipRef.current);
1350
+ if (Math.abs(diff) > 0.1) {
1351
+ flipRaf.current = requestAnimationFrame(animateFlip);
1352
+ } else {
1353
+ flipRef.current = flipTarget.current;
1354
+ setFlipAngle(flipTarget.current);
1355
+ }
1356
+ }, []);
1357
+ const triggerFlip = react.useCallback(() => {
1358
+ if (!allowFlipOnClick) return;
1359
+ onAuthModeChange?.(isSignup ? "signin" : "signup");
1360
+ }, [allowFlipOnClick, isSignup, onAuthModeChange]);
1361
+ react.useEffect(() => {
1362
+ const target = isSignup ? 180 : 0;
1363
+ if (Math.abs(flipTarget.current - target) < 0.1 && Math.abs(flipRef.current - target) < 0.1) return;
1364
+ flipTarget.current = target;
1365
+ cancelAnimationFrame(flipRaf.current);
1366
+ flipRaf.current = requestAnimationFrame(animateFlip);
1367
+ }, [isSignup, animateFlip]);
1368
+ react.useEffect(() => {
1369
+ coilRef.current = coilFactorFromScale(zoomScale);
1370
+ if (!containerRef.current) return;
1371
+ const drop = effectiveStrapDrop(coilRef.current);
1372
+ pos.current.y = ANCHOR_Y + drop;
1373
+ vel.current = { x: 0, y: 0 };
1374
+ }, [zoomScale]);
1375
+ react.useEffect(() => {
1376
+ if (!containerRef.current) return;
1377
+ const drop = effectiveStrapDrop(coilRef.current);
1378
+ pos.current = { x: containerRef.current.offsetWidth / 2 - CARD_W / 2, y: ANCHOR_Y + drop };
1379
+ hasMounted.current = true;
1380
+ rafId.current = requestAnimationFrame(animate);
1381
+ return () => {
1382
+ cancelAnimationFrame(rafId.current);
1383
+ cancelAnimationFrame(flipRaf.current);
1384
+ };
1385
+ }, [animate]);
1386
+ react.useEffect(() => {
1387
+ if (cardRef.current) {
1388
+ cardRef.current.__badgeColors = data.colors;
1389
+ }
1390
+ }, [data.colors]);
1391
+ const onPointerDown = react.useCallback((e) => {
1392
+ const target = e.target;
1393
+ if (target?.closest("[data-no-drag], [data-auth-interactive], [data-auth-mode-toggle]")) return;
1394
+ e.currentTarget.setPointerCapture(e.pointerId);
1395
+ dragging.current = true;
1396
+ didDrag.current = false;
1397
+ if (!containerRef.current) return;
1398
+ const visualScale = containerRef.current.getBoundingClientRect().width / containerRef.current.offsetWidth;
1399
+ const containerScreenLeft = containerRef.current.getBoundingClientRect().left;
1400
+ const containerScreenTop = containerRef.current.getBoundingClientRect().top;
1401
+ const lx = (e.clientX - containerScreenLeft) / visualScale;
1402
+ const ly = (e.clientY - containerScreenTop) / visualScale;
1403
+ dragOffset.current = {
1404
+ x: lx - pos.current.x,
1405
+ y: ly - pos.current.y
1406
+ };
1407
+ document.body.style.cursor = "grabbing";
1408
+ }, []);
1409
+ const onPointerMove = react.useCallback((e) => {
1410
+ if (!containerRef.current) return;
1411
+ const hitTarget = document.elementFromPoint(e.clientX, e.clientY);
1412
+ const overAuth = !!hitTarget?.closest("[data-auth-interactive], [data-auth-mode-toggle]");
1413
+ const bcrect = containerRef.current.getBoundingClientRect();
1414
+ const visualScale = bcrect.width / containerRef.current.offsetWidth;
1415
+ const lx = (e.clientX - bcrect.left) / visualScale;
1416
+ const ly = (e.clientY - bcrect.top) / visualScale;
1417
+ if (dragging.current) {
1418
+ const nx = lx - dragOffset.current.x;
1419
+ const ny = ly - dragOffset.current.y;
1420
+ const dx2 = nx - pos.current.x;
1421
+ const dy2 = ny - pos.current.y;
1422
+ if (Math.abs(dx2) + Math.abs(dy2) > 4) didDrag.current = true;
1423
+ pos.current.x = nx;
1424
+ pos.current.y = ny;
1425
+ vel.current = { x: 0, y: 0 };
1426
+ }
1427
+ if (overAuth) {
1428
+ tiltTarget.current = { rx: 0, ry: 0 };
1429
+ if (!dragging.current) {
1430
+ document.body.style.cursor = hitTarget?.closest("[data-auth-mode-toggle]") ? "pointer" : "default";
1431
+ }
1432
+ return;
1433
+ }
1434
+ if (!dragging.current) document.body.style.cursor = allowFlipOnClick ? "grab" : "default";
1435
+ const cx = pos.current.x + CARD_W / 2;
1436
+ const cy = pos.current.y + CARD_H / 2;
1437
+ const dx = (lx - cx) / (CARD_W / 2);
1438
+ const dy = (ly - cy) / (CARD_H / 2);
1439
+ if (dx * dx + dy * dy < 4) {
1440
+ tiltTarget.current.rx = -dy * 12;
1441
+ tiltTarget.current.ry = dx * 12;
1442
+ setShinePct({
1443
+ x: (lx - pos.current.x) / CARD_W * 100,
1444
+ y: (ly - pos.current.y) / CARD_H * 100
1445
+ });
1446
+ }
1447
+ }, [allowFlipOnClick]);
1448
+ const onPointerCancel = react.useCallback((e) => {
1449
+ e.currentTarget.releasePointerCapture(e.pointerId);
1450
+ dragging.current = false;
1451
+ didDrag.current = false;
1452
+ tiltTarget.current = { rx: 0, ry: 0 };
1453
+ document.body.style.cursor = "auto";
1454
+ }, []);
1455
+ const onPointerUp = react.useCallback((e) => {
1456
+ e.currentTarget.releasePointerCapture(e.pointerId);
1457
+ const wasDragging = didDrag.current;
1458
+ dragging.current = false;
1459
+ didDrag.current = false;
1460
+ tiltTarget.current = { rx: 0, ry: 0 };
1461
+ document.body.style.cursor = "auto";
1462
+ vel.current = { x: vel.current.x * 1.5, y: vel.current.y * 1.5 };
1463
+ if (!wasDragging && !e.target?.closest("[data-no-drag], [data-auth-interactive], [data-auth-mode-toggle]")) {
1464
+ triggerFlip();
1465
+ }
1466
+ }, [triggerFlip]);
1467
+ const showBackFace = flipAngle >= 90;
1468
+ const frontFaceActive = authMode === "signin" && !showBackFace;
1469
+ const backFaceActive = authMode === "signup" && showBackFace;
1470
+ const showModeToggle = !allowFlipOnClick && (frontFaceActive || backFaceActive);
1471
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1472
+ "div",
1473
+ {
1474
+ ref: containerRef,
1475
+ className: ["relative w-full h-full", className].filter(Boolean).join(" "),
1476
+ style: { perspective: "1200px", overflow: "visible", ...style },
1477
+ children: [
1478
+ /* @__PURE__ */ jsxRuntime.jsx(
1479
+ "canvas",
1480
+ {
1481
+ ref: canvasRef,
1482
+ className: "absolute inset-0 pointer-events-none",
1483
+ style: { zIndex: 1 }
1484
+ }
1485
+ ),
1486
+ /* @__PURE__ */ jsxRuntime.jsx(
1487
+ "div",
1488
+ {
1489
+ className: "absolute pointer-events-none",
1490
+ style: { left: "calc(50% - 2px)", top: ANCHOR_Y - 4, width: 4, height: 8, background: "#555", borderRadius: 2, zIndex: 2 }
1491
+ }
1492
+ ),
1493
+ /* @__PURE__ */ jsxRuntime.jsxs(
1494
+ "div",
1495
+ {
1496
+ ref: cardRef,
1497
+ className: "absolute select-none",
1498
+ style: {
1499
+ width: CARD_W,
1500
+ height: CARD_H,
1501
+ willChange: "transform",
1502
+ transformOrigin: "top center",
1503
+ transformStyle: "preserve-3d",
1504
+ zIndex: 3,
1505
+ cursor: allowFlipOnClick ? "grab" : "default",
1506
+ touchAction: "none"
1507
+ },
1508
+ onPointerDown,
1509
+ onPointerMove,
1510
+ onPointerUp,
1511
+ onPointerCancel,
1512
+ onPointerLeave: () => {
1513
+ if (!dragging.current) {
1514
+ tiltTarget.current = { rx: 0, ry: 0 };
1515
+ }
1516
+ },
1517
+ children: [
1518
+ /* @__PURE__ */ jsxRuntime.jsxs(
1519
+ "div",
1520
+ {
1521
+ style: {
1522
+ position: "relative",
1523
+ width: "100%",
1524
+ height: "100%",
1525
+ transformStyle: "preserve-3d",
1526
+ transform: `rotateY(${flipAngle}deg) translateZ(0.01px)`,
1527
+ boxShadow: "0 40px 100px rgba(0,0,0,0.8), inset 0 0 0 1px rgba(255,255,255,0.06)",
1528
+ borderRadius: 16,
1529
+ transition: "none",
1530
+ ...CRISP_TEXT
1531
+ },
1532
+ children: [
1533
+ /* @__PURE__ */ jsxRuntime.jsx(
1534
+ CardFront,
1535
+ {
1536
+ data,
1537
+ auth,
1538
+ shinePct,
1539
+ isFaceActive: frontFaceActive,
1540
+ reserveToggleSpace: showModeToggle
1541
+ }
1542
+ ),
1543
+ /* @__PURE__ */ jsxRuntime.jsx(
1544
+ CardBack,
1545
+ {
1546
+ data,
1547
+ auth,
1548
+ shinePct,
1549
+ isFaceActive: backFaceActive,
1550
+ reserveToggleSpace: showModeToggle
1551
+ }
1552
+ )
1553
+ ]
1554
+ }
1555
+ ),
1556
+ showModeToggle && onAuthModeChange ? /* @__PURE__ */ jsxRuntime.jsx(
1557
+ "div",
1558
+ {
1559
+ "data-auth-mode-toggle": true,
1560
+ "data-no-drag": true,
1561
+ style: {
1562
+ position: "absolute",
1563
+ inset: 0,
1564
+ zIndex: 200,
1565
+ pointerEvents: "none",
1566
+ transform: "translateZ(48px)",
1567
+ transformStyle: "preserve-3d",
1568
+ borderRadius: 16
1569
+ },
1570
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1571
+ AuthModeToggleBar,
1572
+ {
1573
+ authMode,
1574
+ signInTitle: data.signInTitle ?? "SIGN IN",
1575
+ signUpTitle: data.signUpTitle ?? "SIGN UP",
1576
+ colors: data.colors,
1577
+ onChange: onAuthModeChange
1578
+ }
1579
+ )
1580
+ }
1581
+ ) : null
1582
+ ]
1583
+ }
1584
+ )
1585
+ ]
1586
+ }
1587
+ );
1588
+ }
1589
+ var LanyardCard_default = LanyardCard;
1590
+ var BadgeCard = LanyardCard;
1591
+ var labelStyle = {
1592
+ fontFamily: "ui-monospace, monospace",
1593
+ fontSize: 11,
1594
+ letterSpacing: "0.18em",
1595
+ color: "rgba(255,255,255,0.45)",
1596
+ textTransform: "uppercase"
1597
+ };
1598
+ var inputStyle = {
1599
+ height: 32,
1600
+ fontSize: 12,
1601
+ background: "rgba(255,255,255,0.06)",
1602
+ border: "1px solid rgba(255,255,255,0.1)",
1603
+ color: "#fff",
1604
+ borderRadius: 8,
1605
+ padding: "0 10px",
1606
+ width: "100%"
1607
+ };
1608
+ function TextField({
1609
+ label,
1610
+ value,
1611
+ onChange,
1612
+ maxLength = 48
1613
+ }) {
1614
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
1615
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: labelStyle, children: label }),
1616
+ /* @__PURE__ */ jsxRuntime.jsx(
1617
+ "input",
1618
+ {
1619
+ value,
1620
+ maxLength,
1621
+ onChange: (e) => onChange(e.target.value),
1622
+ style: inputStyle
1623
+ }
1624
+ )
1625
+ ] });
1626
+ }
1627
+ function ColorField({
1628
+ label,
1629
+ value,
1630
+ onChange
1631
+ }) {
1632
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }, children: [
1633
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { ...labelStyle, minWidth: 96 }, children: label }),
1634
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, flex: 1 }, children: [
1635
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "color", value, onChange: (e) => onChange(e.target.value), style: { width: 28, height: 28, padding: 2, border: "none", background: "transparent" } }),
1636
+ /* @__PURE__ */ jsxRuntime.jsx("input", { value, maxLength: 7, onChange: (e) => onChange(e.target.value), style: { ...inputStyle, flex: 1 } })
1637
+ ] })
1638
+ ] });
1639
+ }
1640
+ function LanyardConfigPanel({ data, onChange }) {
1641
+ const set = (key, value) => onChange({ ...data, [key]: value });
1642
+ const setColor = (key, value) => onChange({ ...data, colors: { ...data.colors, [key]: value } });
1643
+ const section = (title, children) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1644
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: labelStyle, children: title }),
1645
+ children
1646
+ ] });
1647
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 20 }, children: [
1648
+ section("BADGE MEDIA", /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1649
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Logo URL", value: data.logoUrl ?? "", onChange: (v) => set("logoUrl", v), maxLength: 200 }),
1650
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Hero Image URL", value: data.shipImageUrl ?? "", onChange: (v) => set("shipImageUrl", v), maxLength: 200 }),
1651
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Lanyard Text", value: data.lanyardText ?? "", onChange: (v) => set("lanyardText", v), maxLength: 20 })
1652
+ ] })),
1653
+ section("TEXT \u2014 FRONT BADGE", /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1654
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Name", value: data.name, onChange: (v) => set("name", v) }),
1655
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Venue", value: data.venue, onChange: (v) => set("venue", v) }),
1656
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Website", value: data.website, onChange: (v) => set("website", v) })
1657
+ ] })),
1658
+ section("AUTH \u2014 HEADER", /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1659
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Event Label", value: data.authHeaderEvent ?? data.eventCode, onChange: (v) => set("authHeaderEvent", v) }),
1660
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Venue Label", value: data.authHeaderVenue ?? data.venue, onChange: (v) => set("authHeaderVenue", v) }),
1661
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Sign In Title", value: data.signInTitle ?? "SIGN IN", onChange: (v) => set("signInTitle", v), maxLength: 20 }),
1662
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Sign Up Title", value: data.signUpTitle ?? "SIGN UP", onChange: (v) => set("signUpTitle", v), maxLength: 20 })
1663
+ ] })),
1664
+ section("AUTH \u2014 SIGN IN STEPS", /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1665
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Email Question", value: data.signInEmailQuestion ?? "", onChange: (v) => set("signInEmailQuestion", v) }),
1666
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Password Question", value: data.signInPasswordQuestion ?? "", onChange: (v) => set("signInPasswordQuestion", v) })
1667
+ ] })),
1668
+ section("AUTH \u2014 SIGN UP STEPS", /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1669
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Name Question", value: data.signUpNameQuestion ?? "", onChange: (v) => set("signUpNameQuestion", v) }),
1670
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Email Question", value: data.signUpEmailQuestion ?? "", onChange: (v) => set("signUpEmailQuestion", v) }),
1671
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Password Question", value: data.signUpPasswordQuestion ?? "", onChange: (v) => set("signUpPasswordQuestion", v) })
1672
+ ] })),
1673
+ section("COLORS", /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1674
+ /* @__PURE__ */ jsxRuntime.jsx(ColorField, { label: "Card BG", value: data.colors.cardBg, onChange: (v) => setColor("cardBg", v) }),
1675
+ /* @__PURE__ */ jsxRuntime.jsx(ColorField, { label: "Accent", value: data.colors.accentColor, onChange: (v) => setColor("accentColor", v) }),
1676
+ /* @__PURE__ */ jsxRuntime.jsx(ColorField, { label: "Name", value: data.colors.nameColor, onChange: (v) => setColor("nameColor", v) })
1677
+ ] }))
1678
+ ] });
1679
+ }
1680
+ var CAM_Z_DEFAULT = 0;
1681
+ var CAM_Z_MIN = -400;
1682
+ var VIEWPORT_PAD_TOP = 52;
1683
+ var VIEWPORT_PAD_BOTTOM = 88;
1684
+ var camZToScale = (z) => (ZOOM_PERSPECTIVE + z) / ZOOM_PERSPECTIVE;
1685
+ var camZToPercent = (z) => Math.round(camZToScale(z) * 100);
1686
+ var snapCamZ = (z) => {
1687
+ const scale = Math.round(camZToScale(z) * 20) / 20;
1688
+ return scale * ZOOM_PERSPECTIVE - ZOOM_PERSPECTIVE;
1689
+ };
1690
+ function sceneTranslateForZoom(viewportH, scale) {
1691
+ const coil = coilFactorFromScale(scale);
1692
+ let ty = viewportH / 2 - effectiveCardCenterY(coil) * scale;
1693
+ const cardBottom = ty + effectiveCardBottomY(coil) * scale;
1694
+ if (cardBottom > viewportH - VIEWPORT_PAD_BOTTOM) {
1695
+ ty -= cardBottom - (viewportH - VIEWPORT_PAD_BOTTOM);
1696
+ }
1697
+ const anchorTop = ty + ANCHOR_Y * scale;
1698
+ if (anchorTop < VIEWPORT_PAD_TOP) {
1699
+ ty += VIEWPORT_PAD_TOP - anchorTop;
1700
+ }
1701
+ return ty;
1702
+ }
1703
+ var panelButtonStyle = {
1704
+ background: "rgba(255,255,255,0.06)",
1705
+ border: "1px solid rgba(255,255,255,0.1)",
1706
+ color: "rgba(255,255,255,0.7)",
1707
+ borderRadius: 8,
1708
+ padding: 8,
1709
+ cursor: "pointer"
1710
+ };
1711
+ function LanyardScene({
1712
+ data: controlledData,
1713
+ defaultData = DEFAULT_HANKI_BADGE,
1714
+ onDataChange,
1715
+ authMode: controlledAuthMode,
1716
+ onAuthModeChange,
1717
+ auth,
1718
+ showConfigPanel,
1719
+ features,
1720
+ headerLabel = "HANKI",
1721
+ background = "#070707",
1722
+ className,
1723
+ style,
1724
+ children
1725
+ }) {
1726
+ const [internalData, setInternalData] = react.useState(() => ({ ...defaultData }));
1727
+ const [internalAuthMode, setInternalAuthMode] = react.useState("signin");
1728
+ const [panelOpen, setPanelOpen] = react.useState(false);
1729
+ const [camZ, setCamZ] = react.useState(CAM_Z_DEFAULT);
1730
+ const [viewportH, setViewportH] = react.useState(900);
1731
+ const data = controlledData ?? internalData;
1732
+ const authMode = controlledAuthMode ?? internalAuthMode;
1733
+ const customizationEnabled = showConfigPanel ?? features?.customization ?? false;
1734
+ const zoomEnabled = features?.zoom !== false;
1735
+ const setData = react.useCallback((next) => {
1736
+ if (controlledData === void 0) setInternalData(next);
1737
+ onDataChange?.(next);
1738
+ }, [controlledData, onDataChange]);
1739
+ const setAuthMode = react.useCallback((mode) => {
1740
+ if (controlledAuthMode === void 0) setInternalAuthMode(mode);
1741
+ onAuthModeChange?.(mode);
1742
+ }, [controlledAuthMode, onAuthModeChange]);
1743
+ const camZRef = react.useRef(CAM_Z_DEFAULT);
1744
+ const sceneRef = react.useRef(null);
1745
+ const rafRef = react.useRef(0);
1746
+ const targetZRef = react.useRef(CAM_Z_DEFAULT);
1747
+ const displayZRef = react.useRef(CAM_Z_DEFAULT);
1748
+ react.useEffect(() => {
1749
+ const sync = () => setViewportH(window.innerHeight);
1750
+ sync();
1751
+ window.addEventListener("resize", sync);
1752
+ return () => window.removeEventListener("resize", sync);
1753
+ }, []);
1754
+ const applyCamera = react.useCallback((next) => {
1755
+ if (!zoomEnabled) return;
1756
+ const clamped = snapCamZ(Math.max(CAM_Z_MIN, Math.min(ZOOM_MAX_Z, next)));
1757
+ cancelAnimationFrame(rafRef.current);
1758
+ camZRef.current = clamped;
1759
+ displayZRef.current = clamped;
1760
+ targetZRef.current = clamped;
1761
+ setCamZ(clamped);
1762
+ }, [zoomEnabled]);
1763
+ const resetCamera = react.useCallback(() => {
1764
+ applyCamera(CAM_Z_DEFAULT);
1765
+ }, [applyCamera]);
1766
+ react.useEffect(() => {
1767
+ if (!zoomEnabled) return;
1768
+ const el = sceneRef.current;
1769
+ if (!el) return;
1770
+ const onWheel = (e) => {
1771
+ if (e.target?.closest("[data-no-drag]")) return;
1772
+ e.preventDefault();
1773
+ applyCamera(camZRef.current + -e.deltaY * 1.1);
1774
+ };
1775
+ el.addEventListener("wheel", onWheel, { passive: false });
1776
+ return () => el.removeEventListener("wheel", onWheel);
1777
+ }, [applyCamera, zoomEnabled]);
1778
+ const sceneScale = zoomEnabled ? camZToScale(camZ) : 1;
1779
+ const isAuthFocused = sceneScale >= ZOOM_AUTH_FOCUS_SCALE;
1780
+ const sceneTranslateY = react.useMemo(
1781
+ () => sceneTranslateForZoom(viewportH, sceneScale),
1782
+ [viewportH, sceneScale]
1783
+ );
1784
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1785
+ "main",
1786
+ {
1787
+ ref: sceneRef,
1788
+ className,
1789
+ style: { width: "100%", height: "100vh", overflow: "hidden", position: "relative", background, ...style },
1790
+ children: [
1791
+ /* @__PURE__ */ jsxRuntime.jsx(
1792
+ "div",
1793
+ {
1794
+ className: "absolute inset-0 pointer-events-none",
1795
+ style: {
1796
+ backgroundImage: "radial-gradient(circle, rgba(255,255,255,0.035) 1px, transparent 1px)",
1797
+ backgroundSize: "28px 28px"
1798
+ }
1799
+ }
1800
+ ),
1801
+ /* @__PURE__ */ jsxRuntime.jsx(
1802
+ "div",
1803
+ {
1804
+ className: "absolute inset-0 pointer-events-none",
1805
+ style: {
1806
+ background: "radial-gradient(ellipse 70% 80% at 50% 50%, transparent 0%, rgba(0,0,0,0.55) 100%)",
1807
+ zIndex: 10
1808
+ }
1809
+ }
1810
+ ),
1811
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
1812
+ "div",
1813
+ {
1814
+ className: "absolute inset-0",
1815
+ style: {
1816
+ transform: `translateY(${sceneTranslateY}px)`,
1817
+ zoom: sceneScale,
1818
+ WebkitFontSmoothing: "antialiased"
1819
+ },
1820
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1821
+ LanyardCard,
1822
+ {
1823
+ data,
1824
+ zoomScale: sceneScale,
1825
+ authMode,
1826
+ onAuthModeChange: setAuthMode,
1827
+ auth
1828
+ }
1829
+ )
1830
+ }
1831
+ ) }),
1832
+ headerLabel ? /* @__PURE__ */ jsxRuntime.jsx("header", { className: "absolute top-0 left-0 right-0 flex items-center justify-between px-4 sm:px-6 z-30 pointer-events-none pt-4", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "ui-monospace, monospace", fontSize: 12, letterSpacing: "0.2em", color: "rgba(255,255,255,0.25)" }, children: headerLabel }) }) : null,
1833
+ zoomEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-4 right-4 z-50 flex items-center gap-1", "data-no-drag": true, children: [
1834
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => applyCamera(camZRef.current - 90), style: panelButtonStyle, "aria-label": "Zoom out", children: "\u2212" }),
1835
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontFamily: "ui-monospace, monospace", fontSize: 12, color: "rgba(255,255,255,0.45)", minWidth: 36, textAlign: "center" }, children: [
1836
+ camZToPercent(camZ),
1837
+ "%"
1838
+ ] }),
1839
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => applyCamera(camZRef.current + 90), style: panelButtonStyle, "aria-label": "Zoom in", children: "+" }),
1840
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: resetCamera, style: panelButtonStyle, "aria-label": "Reset camera", children: "\u21BA" })
1841
+ ] }) : null,
1842
+ customizationEnabled ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-4 right-4 sm:bottom-6 sm:right-6 z-50 flex flex-col items-end gap-3", "data-no-drag": true, children: [
1843
+ /* @__PURE__ */ jsxRuntime.jsx(
1844
+ "button",
1845
+ {
1846
+ type: "button",
1847
+ onClick: () => setPanelOpen((v) => !v),
1848
+ style: {
1849
+ ...panelButtonStyle,
1850
+ background: panelOpen ? "rgba(255,255,255,0.12)" : panelButtonStyle.background,
1851
+ padding: "8px 16px",
1852
+ fontFamily: "ui-monospace, monospace",
1853
+ fontSize: 12,
1854
+ letterSpacing: "0.18em"
1855
+ },
1856
+ children: panelOpen ? "CLOSE" : "CUSTOMIZE"
1857
+ }
1858
+ ),
1859
+ panelOpen ? /* @__PURE__ */ jsxRuntime.jsx(
1860
+ "div",
1861
+ {
1862
+ style: {
1863
+ background: "rgba(14,14,14,0.97)",
1864
+ border: "1px solid rgba(255,255,255,0.08)",
1865
+ backdropFilter: "blur(20px)",
1866
+ borderRadius: 16,
1867
+ maxHeight: "calc(100dvh - 120px)",
1868
+ width: "min(320px, calc(100vw - 32px))",
1869
+ overflowY: "auto",
1870
+ padding: 16
1871
+ },
1872
+ children: /* @__PURE__ */ jsxRuntime.jsx(LanyardConfigPanel, { data, onChange: setData })
1873
+ }
1874
+ ) : null
1875
+ ] }) : null,
1876
+ children,
1877
+ /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "absolute left-0 right-0 bottom-6 z-20 pointer-events-none flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontFamily: "ui-monospace, monospace", fontSize: 12, letterSpacing: "0.22em", color: "rgba(255,255,255,0.15)" }, children: isAuthFocused ? "USE CARD TOGGLE TO SWITCH SIGN IN / SIGN UP \xB7 SCROLL TO ZOOM" : "DRAG \xB7 CLICK TO FLIP \xB7 SCROLL TO ZOOM" }) })
1878
+ ]
1879
+ }
1880
+ );
1881
+ }
1882
+
1883
+ exports.ANCHOR_Y = ANCHOR_Y;
1884
+ exports.BadgeCard = BadgeCard;
1885
+ exports.CARD_H = CARD_H;
1886
+ exports.CARD_W = CARD_W;
1887
+ exports.DEFAULT_HANKI_BADGE = DEFAULT_HANKI_BADGE;
1888
+ exports.DEFAULT_LANYARD_TEXT = DEFAULT_LANYARD_TEXT;
1889
+ exports.LanyardCard = LanyardCard;
1890
+ exports.LanyardConfigPanel = LanyardConfigPanel;
1891
+ exports.LanyardScene = LanyardScene;
1892
+ exports.MAX_ZOOM_SCALE = MAX_ZOOM_SCALE;
1893
+ exports.REGISTER_STEPS_DEFAULT = REGISTER_STEPS_DEFAULT;
1894
+ exports.SIGNIN_STEPS_DEFAULT = SIGNIN_STEPS_DEFAULT;
1895
+ exports.STRAP_LENGTH = STRAP_LENGTH;
1896
+ exports.ZOOM_AUTH_FOCUS_SCALE = ZOOM_AUTH_FOCUS_SCALE;
1897
+ exports.ZOOM_MAX_Z = ZOOM_MAX_Z;
1898
+ exports.ZOOM_PERSPECTIVE = ZOOM_PERSPECTIVE;
1899
+ exports.coilFactorFromScale = coilFactorFromScale;
1900
+ exports.default = LanyardCard_default;
1901
+ exports.effectiveCardBottomY = effectiveCardBottomY;
1902
+ exports.effectiveCardCenterY = effectiveCardCenterY;
1903
+ exports.effectiveStrapDrop = effectiveStrapDrop;
1904
+ exports.emptyValuesForSteps = emptyValuesForSteps;
1905
+ exports.flowMeta = flowMeta;
1906
+ exports.getRegisterSteps = getRegisterSteps;
1907
+ exports.getSignInSteps = getSignInSteps;
1908
+ exports.resolveAuthSteps = resolveAuthSteps;
1909
+ exports.resolveLanyardText = resolveLanyardText;
1910
+ exports.stepCanContinue = stepCanContinue;
1911
+ //# sourceMappingURL=index.cjs.map
1912
+ //# sourceMappingURL=index.cjs.map