@myandgo/signin 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/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @myandgo/signin
2
+
3
+ The MyAndGo fleet sign-in kit. **Portable** — inline styles (no Tailwind), no
4
+ router coupling, minimal peer deps (React only). Every product, including the
5
+ native apps, gets the identical sign-in experience.
6
+
7
+ ## What's in v0.1
8
+ - **`BrandWheel`** — the canonical ecosystem wheel. `mode="sweep"` (homepage hero,
9
+ slices call `onSelect`) or `mode="land"` (post-sign-in loader: sweeps then lands
10
+ on the product you're entering).
11
+ - **`BrandWheelLoader`** — the loader wrapper (`mode="land"` + caption).
12
+
13
+ _Coming in v0.2: `SignInCard` (Inspect-standard layout, per-product theming),
14
+ the "Last used" badge, and the "Trouble signing in?" helper._
15
+
16
+ ## Use
17
+
18
+ ```tsx
19
+ import { BrandWheel, BrandWheelLoader } from "@myandgo/signin";
20
+
21
+ // homepage hero — clickable slices
22
+ <BrandWheel mode="sweep" interactive showNames onSelect={(id) => router.push(`/products/${id}`)} />
23
+
24
+ // post-sign-in loader — lands on the product being entered
25
+ <BrandWheelLoader target="crmandgo" onLand={() => redirectToProduct()} />
26
+ ```
27
+
28
+ Consumers host the shield assets at `${logoBase}<id>-logo.png` (default `logoBase="/"`),
29
+ or pass a `products` array with explicit `logo` URLs. Respects `prefers-reduced-motion`.
30
+ Ships on the public `@myandgo` npm rail (OIDC tag → CI publish).
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ /**
3
+ * The canonical MyAndGo ecosystem wheel, portable (inline styles, no Tailwind,
4
+ * no router). Two modes:
5
+ * mode="sweep" — light sweeps clockwise forever; slices call `onSelect`.
6
+ * mode="land" — sweep then decelerate onto `target`, with a synced caption.
7
+ * Used by the post-sign-in loader.
8
+ * Reduced-motion aware. Consumers host the shield assets at `${logoBase}<id>-logo.png`.
9
+ */
10
+ export interface WheelProduct {
11
+ id: string;
12
+ name: string;
13
+ logo?: string;
14
+ colour: string;
15
+ }
16
+ export declare const WHEEL_PRODUCTS: WheelProduct[];
17
+ export interface BrandWheelProps {
18
+ size?: number;
19
+ mode?: "sweep" | "land";
20
+ target?: string;
21
+ onSelect?: (productId: string) => void;
22
+ showNames?: boolean;
23
+ caption?: boolean;
24
+ onLand?: () => void;
25
+ products?: WheelProduct[];
26
+ logoBase?: string;
27
+ }
28
+ export declare function BrandWheel({ size, mode, target, onSelect, showNames, caption, onLand, products, logoBase }: BrandWheelProps): React.JSX.Element;
29
+ export declare function BrandWheelLoader({ target, size, onLand, logoBase }: {
30
+ target: string;
31
+ size?: number;
32
+ onLand?: () => void;
33
+ logoBase?: string;
34
+ }): React.JSX.Element;
@@ -0,0 +1,112 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ export const WHEEL_PRODUCTS = [
4
+ { id: "signandgo", name: "SignAndGo", colour: "#FFCD00" },
5
+ { id: "shareandgo", name: "ShareAndGo", colour: "#3b52f6" },
6
+ { id: "bookandgo", name: "BookAndGo", colour: "#7ed956" },
7
+ { id: "crmandgo", name: "CRMandGo", colour: "#ff5757" },
8
+ { id: "inspectandgo", name: "InspectAndGo", colour: "#F97316" },
9
+ { id: "workandgo", name: "WorkAndGo", colour: "#1e3a5f" },
10
+ { id: "accountsandgo", name: "AccountsAndGo", colour: "#5CB8A5" },
11
+ { id: "brandandgo", name: "BrandAndGo", colour: "#1B4D3E" },
12
+ ];
13
+ const GAP = 1.8;
14
+ const SWEEP_SECS = 4.2;
15
+ const reduced = () => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
16
+ const readable = (hex) => (hex.toUpperCase() === "#FFCD00" ? "#b58a00" : hex);
17
+ const MYANDGO_LOGO = "/andgo-logo.png";
18
+ let injected = false;
19
+ function useKeyframes() {
20
+ useEffect(() => {
21
+ if (injected || typeof document === "undefined")
22
+ return;
23
+ injected = true;
24
+ const el = document.createElement("style");
25
+ el.textContent =
26
+ "@keyframes bwSweep{0%{fill-opacity:.10}5%{fill-opacity:.95}24%{fill-opacity:.10}100%{fill-opacity:.10}}" +
27
+ "@keyframes bwCoin{0%{transform:perspective(400px) rotateY(0)}15%{transform:perspective(400px) rotateY(360deg)}100%{transform:perspective(400px) rotateY(360deg)}}" +
28
+ "@media (prefers-reduced-motion: reduce){.bw-slice{animation:none!important;fill-opacity:.45!important}.bw-coin{animation:none!important}}";
29
+ document.head.appendChild(el);
30
+ }, []);
31
+ }
32
+ export function BrandWheel({ size = 380, mode = "sweep", target, onSelect, showNames = false, caption = false, onLand, products = WHEEL_PRODUCTS, logoBase = "/" }) {
33
+ useKeyframes();
34
+ const P = products;
35
+ const N = P.length;
36
+ const step = 360 / N;
37
+ const centre = size / 2;
38
+ const rIn = size * 0.185;
39
+ const rOut = size * 0.48;
40
+ const logoFor = (p) => p.logo ?? `${logoBase}${p.id}-logo.png`;
41
+ const polar = (r, deg) => {
42
+ const a = ((deg - 90) * Math.PI) / 180;
43
+ return [centre + r * Math.cos(a), centre + r * Math.sin(a)];
44
+ };
45
+ const slice = (a1, a2) => {
46
+ const [x1o, y1o] = polar(rOut, a1), [x2o, y2o] = polar(rOut, a2);
47
+ const [x2i, y2i] = polar(rIn, a2), [x1i, y1i] = polar(rIn, a1);
48
+ return `M ${x1o} ${y1o} A ${rOut} ${rOut} 0 0 1 ${x2o} ${y2o} L ${x2i} ${y2i} A ${rIn} ${rIn} 0 0 0 ${x1i} ${y1i} Z`;
49
+ };
50
+ const targetIdx = Math.max(0, P.findIndex((p) => p.id === target));
51
+ const [active, setActive] = useState(targetIdx);
52
+ const [landed, setLanded] = useState(mode !== "land");
53
+ const timer = useRef(undefined);
54
+ useEffect(() => {
55
+ if (mode !== "land")
56
+ return;
57
+ if (reduced()) {
58
+ setActive(targetIdx);
59
+ setLanded(true);
60
+ onLand?.();
61
+ return;
62
+ }
63
+ setLanded(false);
64
+ const seq = [];
65
+ for (let l = 0; l < 2; l++)
66
+ for (let i = 0; i < N; i++)
67
+ seq.push(i);
68
+ for (let i = 0; i <= targetIdx; i++)
69
+ seq.push(i);
70
+ let cancelled = false, s = 0;
71
+ const tick = () => {
72
+ if (cancelled)
73
+ return;
74
+ if (s >= seq.length) {
75
+ setLanded(true);
76
+ onLand?.();
77
+ return;
78
+ }
79
+ setActive(seq[s]);
80
+ const remaining = seq.length - s;
81
+ timer.current = window.setTimeout(tick, remaining < 9 ? 90 + (9 - remaining) * 55 : 85);
82
+ s++;
83
+ };
84
+ tick();
85
+ return () => { cancelled = true; if (timer.current)
86
+ window.clearTimeout(timer.current); };
87
+ }, [mode, target]); // eslint-disable-line react-hooks/exhaustive-deps
88
+ const landedProduct = P[targetIdx];
89
+ const coinBase = size * 0.095;
90
+ const box = { position: "relative", width: size, height: size, maxWidth: "100%" };
91
+ const abs = (x, y) => ({ position: "absolute", left: `${(x / size) * 100}%`, top: `${(y / size) * 100}%`, transform: "translate(-50%,-50%)" });
92
+ return (_jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center" }, children: [_jsxs("div", { style: box, children: [_jsxs("svg", { viewBox: `0 0 ${size} ${size}`, style: { height: "100%", width: "100%", overflow: "visible" }, children: [P.map((p, i) => {
93
+ const a1 = i * step + GAP / 2, a2 = (i + 1) * step - GAP / 2, d = slice(a1, a2);
94
+ if (mode === "land") {
95
+ const diff = (active - i + N) % N;
96
+ const op = landed ? (i === active ? 1 : 0.08) : diff === 0 ? 0.95 : diff === 1 ? 0.5 : diff === 2 ? 0.22 : 0.08;
97
+ return _jsx("path", { d: d, fill: p.colour, style: { fillOpacity: op, transition: "fill-opacity .18s ease" } }, p.id);
98
+ }
99
+ return (_jsxs("g", { role: onSelect ? "button" : undefined, "aria-label": onSelect ? `Open ${p.name}` : undefined, style: { cursor: onSelect ? "pointer" : undefined }, onClick: onSelect ? () => onSelect(p.id) : undefined, children: [_jsx("title", { children: p.name }), _jsx("path", { d: d, fill: p.colour, fillOpacity: 0.08 }), _jsx("path", { d: d, fill: p.colour, className: "bw-slice", style: { animation: `bwSweep ${SWEEP_SECS}s linear infinite`, animationDelay: `${(i / N) * SWEEP_SECS}s` } })] }, p.id));
100
+ }), _jsx("circle", { cx: centre, cy: centre, r: rIn - 2, fill: "white" })] }), P.map((p, i) => {
101
+ const mid = i * step + step / 2;
102
+ const [x, y] = polar((rIn + rOut) / 2, mid);
103
+ const isActive = mode === "land" && i === active;
104
+ return (_jsxs("div", { style: { ...abs(x, y), display: "flex", flexDirection: "column", alignItems: "center", cursor: onSelect ? "pointer" : undefined }, onClick: onSelect ? () => onSelect(p.id) : undefined, children: [_jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", width: coinBase, height: coinBase, borderRadius: "50%", background: "#fff", boxShadow: isActive ? `0 4px 14px -2px ${p.colour}99` : "0 1px 3px rgba(0,0,0,.08)", transform: isActive ? "scale(1.15)" : "scale(1)", transition: "transform .18s, box-shadow .18s" }, children: _jsx("img", { src: logoFor(p), alt: "", style: { width: size * 0.05, height: size * 0.05, objectFit: "contain" } }) }), showNames && _jsx("span", { style: { marginTop: 4, fontSize: 10, fontWeight: 600, lineHeight: 1.1, color: "#334155", textShadow: "0 1px 2px #fff, 0 0 3px #fff" }, children: p.name })] }, p.id));
105
+ }), _jsx("div", { style: { position: "absolute", left: "50%", top: "50%", transform: "translate(-50%,-50%)", pointerEvents: "none" }, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", width: (rIn - 4) * 2, height: (rIn - 4) * 2, borderRadius: "50%", background: "#fff" }, children: [_jsx("img", { src: mode === "land" && landed ? logoFor(landedProduct) : `${logoBase}andgo-logo.png`, alt: "MyAndGo", className: mode !== "land" || landed ? "bw-coin" : undefined, style: { width: size * 0.12, height: size * 0.12, objectFit: "contain", animation: mode !== "land" || landed ? "bwCoin 6s ease-in-out infinite" : undefined } }), mode !== "land" && _jsx("span", { style: { marginTop: 2, fontSize: 12, fontWeight: 700, letterSpacing: "-0.01em", color: "#0f172a" }, children: "MyAndGo" })] }) })] }), caption && (_jsx("div", { style: { marginTop: 24, height: 24, textAlign: "center" }, children: landed
106
+ ? _jsxs("p", { style: { fontSize: 16, color: "#64748b", margin: 0 }, children: ["Welcome to ", _jsx("span", { style: { fontWeight: 600, color: "#0f172a" }, children: landedProduct.name })] })
107
+ : _jsx("p", { style: { fontSize: 16, fontWeight: 600, letterSpacing: "-0.01em", margin: 0, color: readable(P[active].colour) }, children: P[active].name }) }))] }));
108
+ }
109
+ export function BrandWheelLoader({ target, size = 340, onLand, logoBase }) {
110
+ return _jsx(BrandWheel, { mode: "land", target: target, size: size, showNames: true, caption: true, onLand: onLand, logoBase: logoBase });
111
+ }
112
+ //# sourceMappingURL=BrandWheel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrandWheel.js","sourceRoot":"","sources":["../src/BrandWheel.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAiB,MAAM,OAAO,CAAC;AAa1E,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE;IACzD,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE;IAC3D,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE;IACzD,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE;IACvD,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE;IAC/D,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE;IACzD,EAAE,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE;IACjE,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE;CAC5D,CAAC;AAEF,MAAM,GAAG,GAAG,GAAG,CAAC;AAChB,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,kCAAkC,CAAC,CAAC,OAAO,CAAC;AACvH,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACtF,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAEvC,IAAI,QAAQ,GAAG,KAAK,CAAC;AACrB,SAAS,YAAY;IACnB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO;QACxD,QAAQ,GAAG,IAAI,CAAC;QAChB,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC3C,EAAE,CAAC,WAAW;YACZ,yGAAyG;gBACzG,mKAAmK;gBACnK,2IAA2I,CAAC;QAC9I,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAcD,MAAM,UAAU,UAAU,CAAC,EAAE,IAAI,GAAG,GAAG,EAAE,IAAI,GAAG,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,cAAc,EAAE,QAAQ,GAAG,GAAG,EAAmB;IACjL,YAAY,EAAE,CAAC;IACf,MAAM,CAAC,GAAG,QAAQ,CAAC;IACnB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnB,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC;IACrB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC;IACxB,MAAM,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACzB,MAAM,OAAO,GAAG,CAAC,CAAe,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,QAAQ,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC;IAE7E,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,GAAW,EAAoB,EAAE;QACzD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;QACvC,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAU,EAAE,EAAE;QACvC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/D,OAAO,KAAK,GAAG,IAAI,GAAG,MAAM,IAAI,IAAI,IAAI,UAAU,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC;IACvH,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC;IACnE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,MAAM,CAAqB,SAAS,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO;QAC5B,IAAI,OAAO,EAAE,EAAE,CAAC;YAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAAC,MAAM,EAAE,EAAE,CAAC;YAAC,OAAO;QAAC,CAAC;QAC7E,SAAS,CAAC,KAAK,CAAC,CAAC;QACjB,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;YAAE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,IAAI,SAAS,GAAG,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,IAAI,SAAS;gBAAE,OAAO;YACtB,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBAAC,MAAM,EAAE,EAAE,CAAC;gBAAC,OAAO;YAAC,CAAC;YAC7D,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAClB,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;YACjC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC,EAAE,CAAC;QACN,CAAC,CAAC;QACF,IAAI,EAAE,CAAC;QACP,OAAO,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO;YAAE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5F,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,kDAAkD;IAEtE,MAAM,aAAa,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,KAAK,CAAC;IAE9B,MAAM,GAAG,GAAkB,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACjG,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAiB,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,EAAE,SAAS,EAAE,sBAAsB,EAAE,CAAC,CAAC;IAE9K,OAAO,CACL,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,aAC5E,eAAK,KAAK,EAAE,GAAG,aACb,eAAK,OAAO,EAAE,OAAO,IAAI,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,aAC/F,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gCACd,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gCAChF,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;oCACpB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;oCAClC,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;oCAChH,OAAO,eAAiB,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,UAAU,EAAE,wBAAwB,EAAE,IAA5F,CAAC,CAAC,EAAE,CAA4F,CAAC;gCACrH,CAAC;gCACD,OAAO,CACL,aAAc,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,gBAAc,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,aACjN,0BAAQ,CAAC,CAAC,IAAI,GAAS,EACvB,eAAM,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,GAAI,EACjD,eAAM,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,WAAW,UAAU,mBAAmB,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,GAAG,EAAE,GAAI,KAHzJ,CAAC,CAAC,EAAE,CAIR,CACL,CAAC;4BACJ,CAAC,CAAC,EACF,iBAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,EAAC,OAAO,GAAG,IACvD,EAEL,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;wBACd,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;wBAChC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;wBAC5C,MAAM,QAAQ,GAAG,IAAI,KAAK,MAAM,IAAI,CAAC,KAAK,MAAM,CAAC;wBACjD,OAAO,CACL,eAEE,KAAK,EAAE,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,EACjI,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,aAEpD,cAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,2BAA2B,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,iCAAiC,EAAE,YAC/U,cAAK,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAC,EAAE,EAAC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,GAAI,GACrG,EACL,SAAS,IAAI,eAAM,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,8BAA8B,EAAE,YAAG,CAAC,CAAC,IAAI,GAAQ,KAPrK,CAAC,CAAC,EAAE,CAQL,CACP,CAAC;oBACJ,CAAC,CAAC,EAEF,cAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,EAAE,YACrH,eAAK,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,aAC5L,cAAK,GAAG,EAAE,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,gBAAgB,EAAE,GAAG,EAAC,SAAS,EAAC,SAAS,EAAE,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,SAAS,EAAE,GAAI,EACxU,IAAI,KAAK,MAAM,IAAI,eAAM,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,wBAAgB,IACxI,GACF,IACF,EAEL,OAAO,IAAI,CACV,cAAK,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,YAC3D,MAAM;oBACL,CAAC,CAAC,aAAG,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,4BAAa,eAAM,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,YAAG,aAAa,CAAC,IAAI,GAAQ,IAAI;oBACzJ,CAAC,CAAC,YAAG,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,YAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,GAAK,GACzI,CACP,IACG,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,GAAG,EAAE,MAAM,EAAE,QAAQ,EAA6E;IAClJ,OAAO,KAAC,UAAU,IAAC,IAAI,EAAC,MAAM,EAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,QAAC,OAAO,QAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAAC;AACtH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { BrandWheel, BrandWheelLoader, WHEEL_PRODUCTS } from "./BrandWheel.js";
2
+ export type { WheelProduct, BrandWheelProps } from "./BrandWheel.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // @myandgo/signin — the MyAndGo fleet sign-in kit. Portable (inline styles, no
2
+ // Tailwind or router coupling); theme per product. v0.1: the brand wheel + the
3
+ // post-auth loader. The sign-in card + badge + trouble-helper land in v0.2.
4
+ export { BrandWheel, BrandWheelLoader, WHEEL_PRODUCTS } from "./BrandWheel.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@myandgo/signin",
3
+ "version": "0.1.0",
4
+ "description": "MyAndGo fleet sign-in kit — the brand wheel + sign-in card, badge, trouble-helper and post-auth loader. Portable (inline styles, no Tailwind/router coupling); theme per product.",
5
+ "license": "MIT",
6
+ "repository": { "type": "git", "url": "git+https://github.com/andycarey1969/andgo-signin.git" },
7
+ "keywords": ["signin", "auth", "andgo", "myandgo", "brand-wheel"],
8
+ "publishConfig": { "access": "public" },
9
+ "type": "module",
10
+ "main": "dist/index.js",
11
+ "module": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": ["dist", "src"],
14
+ "scripts": { "build": "tsc -p tsconfig.json", "typecheck": "tsc --noEmit", "prepublishOnly": "npm run build" },
15
+ "peerDependencies": { "react": ">=18", "react-dom": ">=18" },
16
+ "devDependencies": { "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", "react": "^18.3.0", "react-dom": "^18.3.0", "typescript": "^5.6.0" }
17
+ }
@@ -0,0 +1,169 @@
1
+ import React, { useEffect, useRef, useState, CSSProperties } from "react";
2
+
3
+ /**
4
+ * The canonical MyAndGo ecosystem wheel, portable (inline styles, no Tailwind,
5
+ * no router). Two modes:
6
+ * mode="sweep" — light sweeps clockwise forever; slices call `onSelect`.
7
+ * mode="land" — sweep then decelerate onto `target`, with a synced caption.
8
+ * Used by the post-sign-in loader.
9
+ * Reduced-motion aware. Consumers host the shield assets at `${logoBase}<id>-logo.png`.
10
+ */
11
+
12
+ export interface WheelProduct { id: string; name: string; logo?: string; colour: string }
13
+
14
+ export const WHEEL_PRODUCTS: WheelProduct[] = [
15
+ { id: "signandgo", name: "SignAndGo", colour: "#FFCD00" },
16
+ { id: "shareandgo", name: "ShareAndGo", colour: "#3b52f6" },
17
+ { id: "bookandgo", name: "BookAndGo", colour: "#7ed956" },
18
+ { id: "crmandgo", name: "CRMandGo", colour: "#ff5757" },
19
+ { id: "inspectandgo", name: "InspectAndGo", colour: "#F97316" },
20
+ { id: "workandgo", name: "WorkAndGo", colour: "#1e3a5f" },
21
+ { id: "accountsandgo", name: "AccountsAndGo", colour: "#5CB8A5" },
22
+ { id: "brandandgo", name: "BrandAndGo", colour: "#1B4D3E" },
23
+ ];
24
+
25
+ const GAP = 1.8;
26
+ const SWEEP_SECS = 4.2;
27
+ const reduced = () => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
28
+ const readable = (hex: string) => (hex.toUpperCase() === "#FFCD00" ? "#b58a00" : hex);
29
+ const MYANDGO_LOGO = "/andgo-logo.png";
30
+
31
+ let injected = false;
32
+ function useKeyframes() {
33
+ useEffect(() => {
34
+ if (injected || typeof document === "undefined") return;
35
+ injected = true;
36
+ const el = document.createElement("style");
37
+ el.textContent =
38
+ "@keyframes bwSweep{0%{fill-opacity:.10}5%{fill-opacity:.95}24%{fill-opacity:.10}100%{fill-opacity:.10}}" +
39
+ "@keyframes bwCoin{0%{transform:perspective(400px) rotateY(0)}15%{transform:perspective(400px) rotateY(360deg)}100%{transform:perspective(400px) rotateY(360deg)}}" +
40
+ "@media (prefers-reduced-motion: reduce){.bw-slice{animation:none!important;fill-opacity:.45!important}.bw-coin{animation:none!important}}";
41
+ document.head.appendChild(el);
42
+ }, []);
43
+ }
44
+
45
+ export interface BrandWheelProps {
46
+ size?: number;
47
+ mode?: "sweep" | "land";
48
+ target?: string;
49
+ onSelect?: (productId: string) => void;
50
+ showNames?: boolean;
51
+ caption?: boolean;
52
+ onLand?: () => void;
53
+ products?: WheelProduct[];
54
+ logoBase?: string;
55
+ }
56
+
57
+ export function BrandWheel({ size = 380, mode = "sweep", target, onSelect, showNames = false, caption = false, onLand, products = WHEEL_PRODUCTS, logoBase = "/" }: BrandWheelProps) {
58
+ useKeyframes();
59
+ const P = products;
60
+ const N = P.length;
61
+ const step = 360 / N;
62
+ const centre = size / 2;
63
+ const rIn = size * 0.185;
64
+ const rOut = size * 0.48;
65
+ const logoFor = (p: WheelProduct) => p.logo ?? `${logoBase}${p.id}-logo.png`;
66
+
67
+ const polar = (r: number, deg: number): [number, number] => {
68
+ const a = ((deg - 90) * Math.PI) / 180;
69
+ return [centre + r * Math.cos(a), centre + r * Math.sin(a)];
70
+ };
71
+ const slice = (a1: number, a2: number) => {
72
+ const [x1o, y1o] = polar(rOut, a1), [x2o, y2o] = polar(rOut, a2);
73
+ const [x2i, y2i] = polar(rIn, a2), [x1i, y1i] = polar(rIn, a1);
74
+ return `M ${x1o} ${y1o} A ${rOut} ${rOut} 0 0 1 ${x2o} ${y2o} L ${x2i} ${y2i} A ${rIn} ${rIn} 0 0 0 ${x1i} ${y1i} Z`;
75
+ };
76
+
77
+ const targetIdx = Math.max(0, P.findIndex((p) => p.id === target));
78
+ const [active, setActive] = useState(targetIdx);
79
+ const [landed, setLanded] = useState(mode !== "land");
80
+ const timer = useRef<number | undefined>(undefined);
81
+
82
+ useEffect(() => {
83
+ if (mode !== "land") return;
84
+ if (reduced()) { setActive(targetIdx); setLanded(true); onLand?.(); return; }
85
+ setLanded(false);
86
+ const seq: number[] = [];
87
+ for (let l = 0; l < 2; l++) for (let i = 0; i < N; i++) seq.push(i);
88
+ for (let i = 0; i <= targetIdx; i++) seq.push(i);
89
+ let cancelled = false, s = 0;
90
+ const tick = () => {
91
+ if (cancelled) return;
92
+ if (s >= seq.length) { setLanded(true); onLand?.(); return; }
93
+ setActive(seq[s]);
94
+ const remaining = seq.length - s;
95
+ timer.current = window.setTimeout(tick, remaining < 9 ? 90 + (9 - remaining) * 55 : 85);
96
+ s++;
97
+ };
98
+ tick();
99
+ return () => { cancelled = true; if (timer.current) window.clearTimeout(timer.current); };
100
+ }, [mode, target]); // eslint-disable-line react-hooks/exhaustive-deps
101
+
102
+ const landedProduct = P[targetIdx];
103
+ const coinBase = size * 0.095;
104
+
105
+ const box: CSSProperties = { position: "relative", width: size, height: size, maxWidth: "100%" };
106
+ const abs = (x: number, y: number): CSSProperties => ({ position: "absolute", left: `${(x / size) * 100}%`, top: `${(y / size) * 100}%`, transform: "translate(-50%,-50%)" });
107
+
108
+ return (
109
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
110
+ <div style={box}>
111
+ <svg viewBox={`0 0 ${size} ${size}`} style={{ height: "100%", width: "100%", overflow: "visible" }}>
112
+ {P.map((p, i) => {
113
+ const a1 = i * step + GAP / 2, a2 = (i + 1) * step - GAP / 2, d = slice(a1, a2);
114
+ if (mode === "land") {
115
+ const diff = (active - i + N) % N;
116
+ const op = landed ? (i === active ? 1 : 0.08) : diff === 0 ? 0.95 : diff === 1 ? 0.5 : diff === 2 ? 0.22 : 0.08;
117
+ return <path key={p.id} d={d} fill={p.colour} style={{ fillOpacity: op, transition: "fill-opacity .18s ease" }} />;
118
+ }
119
+ return (
120
+ <g key={p.id} role={onSelect ? "button" : undefined} aria-label={onSelect ? `Open ${p.name}` : undefined} style={{ cursor: onSelect ? "pointer" : undefined }} onClick={onSelect ? () => onSelect(p.id) : undefined}>
121
+ <title>{p.name}</title>
122
+ <path d={d} fill={p.colour} fillOpacity={0.08} />
123
+ <path d={d} fill={p.colour} className="bw-slice" style={{ animation: `bwSweep ${SWEEP_SECS}s linear infinite`, animationDelay: `${(i / N) * SWEEP_SECS}s` }} />
124
+ </g>
125
+ );
126
+ })}
127
+ <circle cx={centre} cy={centre} r={rIn - 2} fill="white" />
128
+ </svg>
129
+
130
+ {P.map((p, i) => {
131
+ const mid = i * step + step / 2;
132
+ const [x, y] = polar((rIn + rOut) / 2, mid);
133
+ const isActive = mode === "land" && i === active;
134
+ return (
135
+ <div
136
+ key={p.id}
137
+ style={{ ...abs(x, y), display: "flex", flexDirection: "column", alignItems: "center", cursor: onSelect ? "pointer" : undefined }}
138
+ onClick={onSelect ? () => onSelect(p.id) : undefined}
139
+ >
140
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "center", width: coinBase, height: coinBase, borderRadius: "50%", background: "#fff", boxShadow: isActive ? `0 4px 14px -2px ${p.colour}99` : "0 1px 3px rgba(0,0,0,.08)", transform: isActive ? "scale(1.15)" : "scale(1)", transition: "transform .18s, box-shadow .18s" }}>
141
+ <img src={logoFor(p)} alt="" style={{ width: size * 0.05, height: size * 0.05, objectFit: "contain" }} />
142
+ </div>
143
+ {showNames && <span style={{ marginTop: 4, fontSize: 10, fontWeight: 600, lineHeight: 1.1, color: "#334155", textShadow: "0 1px 2px #fff, 0 0 3px #fff" }}>{p.name}</span>}
144
+ </div>
145
+ );
146
+ })}
147
+
148
+ <div style={{ position: "absolute", left: "50%", top: "50%", transform: "translate(-50%,-50%)", pointerEvents: "none" }}>
149
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", width: (rIn - 4) * 2, height: (rIn - 4) * 2, borderRadius: "50%", background: "#fff" }}>
150
+ <img src={mode === "land" && landed ? logoFor(landedProduct) : `${logoBase}andgo-logo.png`} alt="MyAndGo" className={mode !== "land" || landed ? "bw-coin" : undefined} style={{ width: size * 0.12, height: size * 0.12, objectFit: "contain", animation: mode !== "land" || landed ? "bwCoin 6s ease-in-out infinite" : undefined }} />
151
+ {mode !== "land" && <span style={{ marginTop: 2, fontSize: 12, fontWeight: 700, letterSpacing: "-0.01em", color: "#0f172a" }}>MyAndGo</span>}
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ {caption && (
157
+ <div style={{ marginTop: 24, height: 24, textAlign: "center" }}>
158
+ {landed
159
+ ? <p style={{ fontSize: 16, color: "#64748b", margin: 0 }}>Welcome to <span style={{ fontWeight: 600, color: "#0f172a" }}>{landedProduct.name}</span></p>
160
+ : <p style={{ fontSize: 16, fontWeight: 600, letterSpacing: "-0.01em", margin: 0, color: readable(P[active].colour) }}>{P[active].name}</p>}
161
+ </div>
162
+ )}
163
+ </div>
164
+ );
165
+ }
166
+
167
+ export function BrandWheelLoader({ target, size = 340, onLand, logoBase }: { target: string; size?: number; onLand?: () => void; logoBase?: string }) {
168
+ return <BrandWheel mode="land" target={target} size={size} showNames caption onLand={onLand} logoBase={logoBase} />;
169
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // @myandgo/signin — the MyAndGo fleet sign-in kit. Portable (inline styles, no
2
+ // Tailwind or router coupling); theme per product. v0.1: the brand wheel + the
3
+ // post-auth loader. The sign-in card + badge + trouble-helper land in v0.2.
4
+ export { BrandWheel, BrandWheelLoader, WHEEL_PRODUCTS } from "./BrandWheel.js";
5
+ export type { WheelProduct, BrandWheelProps } from "./BrandWheel.js";