@epicdm/flowstate-auth-ui 1.0.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.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared CSS for all Epic FlowState auth pages.
3
+ *
4
+ * Uses CSS custom properties for light/dark mode.
5
+ * Automatic: follows prefers-color-scheme.
6
+ * Manual: [data-theme="dark"] or [data-theme="light"] on <html> overrides.
7
+ */
8
+ declare const BRAND_CSS = "\n/* --- Reset --- */\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n\n/* --- Theme variables --- */\n:root{\n --fs-brand:#00c3ff;\n --fs-brand-light:#4dd8ff;\n --fs-brand-dark:#0088cc;\n --fs-bg:#f8fafc;\n --fs-card-bg:#fff;\n --fs-card-border:1px solid #e2e8f0;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.08);\n --fs-text:#0f172a;\n --fs-text-secondary:#475569;\n --fs-muted:#94a3b8;\n --fs-border:#e2e8f0;\n --fs-input-bg:#fff;\n --fs-input-border:#cbd5e1;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#f1f5f9;\n --fs-footer-border:#e2e8f0;\n --fs-error:#dc2626;\n --fs-error-bg:#fef2f2;\n --fs-success:#16a34a;\n --fs-success-bg:#f0fdf4;\n --fs-logo-text:#0f172a;\n --fs-header-bg:rgba(255,255,255,.8);\n}\n@media(prefers-color-scheme:dark){\n :root:not([data-theme=\"light\"]){\n --fs-bg:#0f172a;\n --fs-card-bg:#1e293b;\n --fs-card-border:1px solid #334155;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.3);\n --fs-text:#e2e8f0;\n --fs-text-secondary:#94a3b8;\n --fs-muted:#64748b;\n --fs-border:#334155;\n --fs-input-bg:#0f172a;\n --fs-input-border:#475569;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#020617;\n --fs-footer-border:#1e293b;\n --fs-error:#ef4444;\n --fs-error-bg:rgba(239,68,68,.12);\n --fs-success:#22c55e;\n --fs-success-bg:rgba(0,195,255,.12);\n --fs-logo-text:#e2e8f0;\n --fs-header-bg:rgba(15,23,42,.8);\n }\n}\n[data-theme=\"dark\"]{\n --fs-bg:#0f172a;\n --fs-card-bg:#1e293b;\n --fs-card-border:1px solid #334155;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.3);\n --fs-text:#e2e8f0;\n --fs-text-secondary:#94a3b8;\n --fs-muted:#64748b;\n --fs-border:#334155;\n --fs-input-bg:#0f172a;\n --fs-input-border:#475569;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#020617;\n --fs-footer-border:#1e293b;\n --fs-error:#ef4444;\n --fs-error-bg:rgba(239,68,68,.12);\n --fs-success:#22c55e;\n --fs-success-bg:rgba(0,195,255,.12);\n --fs-logo-text:#e2e8f0;\n --fs-header-bg:rgba(15,23,42,.8);\n}\n[data-theme=\"light\"]{\n --fs-bg:#f8fafc;\n --fs-card-bg:#fff;\n --fs-card-border:1px solid #e2e8f0;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.08);\n --fs-text:#0f172a;\n --fs-text-secondary:#475569;\n --fs-muted:#94a3b8;\n --fs-border:#e2e8f0;\n --fs-input-bg:#fff;\n --fs-input-border:#cbd5e1;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#f1f5f9;\n --fs-footer-border:#e2e8f0;\n --fs-error:#dc2626;\n --fs-error-bg:#fef2f2;\n --fs-success:#16a34a;\n --fs-success-bg:#f0fdf4;\n --fs-logo-text:#0f172a;\n --fs-header-bg:rgba(255,255,255,.8);\n}\n\n/* --- Base --- */\nbody{\n font-family:system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif;\n background:var(--fs-bg);\n color:var(--fs-text);\n min-height:100vh;\n display:flex;\n flex-direction:column;\n transition:background .2s,color .2s;\n}\na{color:var(--fs-muted);text-decoration:none;transition:color .15s}\na:hover{color:var(--fs-text)}\n\n/* --- Header --- */\n.fs-header{\n position:sticky;top:0;z-index:50;\n display:flex;align-items:center;justify-content:space-between;\n padding:16px 32px;\n background:var(--fs-header-bg);\n backdrop-filter:blur(8px);\n -webkit-backdrop-filter:blur(8px);\n}\n.fs-header-left{display:flex;align-items:center;gap:32px}\n.fs-header-left svg{color:var(--fs-logo-text)}\n.fs-header-right{display:flex;align-items:center;gap:16px}\n.fs-nav-link{font-size:14px;color:var(--fs-muted);transition:color .15s}\n.fs-nav-link:hover{color:var(--fs-text)}\n\n/* --- Theme toggle --- */\n.fs-theme-toggle{\n background:none;border:1px solid var(--fs-border);\n border-radius:8px;padding:6px 8px;cursor:pointer;\n color:var(--fs-muted);display:flex;align-items:center;gap:4px;\n transition:color .15s,border-color .15s;\n font-size:12px;\n}\n.fs-theme-toggle:hover{color:var(--fs-text);border-color:var(--fs-muted)}\n.fs-theme-toggle .sun{display:none}\n.fs-theme-toggle .moon{display:inline-flex}\n[data-theme=\"dark\"] .fs-theme-toggle .sun{display:inline-flex}\n[data-theme=\"dark\"] .fs-theme-toggle .moon{display:none}\n@media(prefers-color-scheme:dark){\n :root:not([data-theme=\"light\"]) .fs-theme-toggle .sun{display:inline-flex}\n :root:not([data-theme=\"light\"]) .fs-theme-toggle .moon{display:none}\n}\n[data-theme=\"light\"] .fs-theme-toggle .sun{display:none}\n[data-theme=\"light\"] .fs-theme-toggle .moon{display:inline-flex}\n\n/* --- Main content --- */\n.fs-main{flex:1;display:flex;align-items:center;justify-content:center;padding:24px 16px}\n\n/* --- Card --- */\n.fs-card{\n width:100%;max-width:448px;padding:32px;\n background:var(--fs-card-bg);\n border:var(--fs-card-border);\n border-radius:12px;\n box-shadow:var(--fs-card-shadow);\n}\n.fs-card h1{font-size:24px;font-weight:700;color:var(--fs-text);margin-bottom:8px;text-align:center}\n.fs-card .fs-subtitle{font-size:14px;color:var(--fs-text-secondary);text-align:center;margin-bottom:24px;line-height:1.6}\n.fs-card p{font-size:14px;color:var(--fs-text-secondary);line-height:1.6}\n\n/* --- Card status icons --- */\n.fs-status-icon{\n margin:0 auto 24px;width:64px;height:64px;border-radius:50%;\n display:flex;align-items:center;justify-content:center;\n}\n.fs-status-icon.success{background:var(--fs-success-bg)}\n.fs-status-icon.error{background:var(--fs-error-bg)}\n.fs-status-icon svg{width:32px;height:32px}\n.fs-card .fs-action{margin-top:24px;font-size:13px;color:var(--fs-muted);text-align:center}\n\n/* --- Client info badge --- */\n.fs-client-info{\n background:var(--fs-bg);border:1px solid var(--fs-border);\n border-radius:8px;padding:12px;margin-bottom:24px;text-align:center;\n font-size:14px;color:var(--fs-text-secondary);\n}\n.fs-client-info strong{color:var(--fs-brand);font-weight:600}\n\n/* --- Forms --- */\n.fs-form{display:flex;flex-direction:column;gap:16px}\n.fs-form label{font-weight:500;color:var(--fs-text);font-size:14px}\n.fs-form input[type=\"email\"],\n.fs-form input[type=\"text\"]{\n width:100%;padding:12px 16px;\n background:var(--fs-input-bg);\n border:2px solid var(--fs-input-border);\n border-radius:8px;font-size:16px;\n color:var(--fs-text);\n transition:border-color .2s;\n}\n.fs-form input::placeholder{color:var(--fs-muted)}\n.fs-form input:focus{outline:none;border-color:var(--fs-input-focus)}\n.fs-btn{\n background:var(--fs-brand);color:#fff;border:none;\n padding:14px;border-radius:8px;font-size:16px;font-weight:600;\n cursor:pointer;transition:background .2s;width:100%;\n}\n.fs-btn:hover{background:var(--fs-brand-dark)}\n.fs-btn:disabled{opacity:.5;cursor:not-allowed}\n.fs-error-text{color:var(--fs-error);font-size:14px;text-align:center}\n.fs-success-text{color:var(--fs-success);font-size:14px;text-align:center}\n.fs-back-link{text-align:center;margin-top:16px}\n.fs-back-link a{color:var(--fs-brand);font-size:14px}\n.fs-back-link a:hover{color:var(--fs-brand-light)}\n\n/* --- Footer --- */\n.fs-footer{background:var(--fs-footer-bg);padding:48px 32px;transition:background .2s}\n.fs-footer-grid{\n max-width:1280px;margin:0 auto;\n display:grid;grid-template-columns:repeat(4,1fr);gap:32px;\n}\n@media(max-width:768px){.fs-footer-grid{grid-template-columns:repeat(2,1fr)}}\n@media(max-width:480px){.fs-footer-grid{grid-template-columns:1fr}}\n.fs-footer-col h3{\n font-size:12px;font-weight:600;text-transform:uppercase;\n letter-spacing:.05em;color:var(--fs-text);margin-bottom:16px;\n}\n.fs-footer-col ul{list-style:none}\n.fs-footer-col li{margin-bottom:12px}\n.fs-footer-col a{font-size:14px;color:var(--fs-muted)}\n.fs-footer-bottom{\n max-width:1280px;margin:0 auto;\n display:flex;align-items:center;justify-content:space-between;\n border-top:1px solid var(--fs-footer-border);\n padding-top:32px;margin-top:48px;\n}\n.fs-footer-bottom-left{display:flex;align-items:center;gap:12px}\n.fs-footer-bottom p{font-size:14px;color:var(--fs-muted)}\n";
9
+
10
+ /**
11
+ * Epic FlowState brand SVG assets for self-contained HTML pages.
12
+ * All SVGs are inline-safe (no external references).
13
+ */
14
+ /** Diamond icon mark (36x36) */
15
+ declare const ICON_SVG = "<svg aria-hidden=\"true\" viewBox=\"0 0 36 36\" fill=\"none\" style=\"height:36px;width:36px\"><g fill=\"none\" stroke-linejoin=\"round\" stroke-width=\"3\"><path d=\"M10.308 5L18 17.5 10.308 30 2.615 17.5 10.308 5z\" stroke=\"var(--fs-brand,#00c3ff)\"/><path d=\"M18 17.5L10.308 5h15.144l7.933 12.5M18 17.5h15.385L25.452 30H10.308L18 17.5z\" stroke=\"var(--fs-brand-dark,#0088cc)\"/></g></svg>";
16
+ /** Full wordmark logo. Fill adapts via currentColor for light/dark. */
17
+ declare const LOGO_SVG = "<svg aria-label=\"Epic FlowState\" viewBox=\"0 0 4000 594.516\" style=\"height:36px;width:auto\"><g id=\"icon\"><path d=\"M 0,0 -90.883,157.413 H 64.324 c 16.382,0 31.646,-8.812 39.835,-23 L 181.764,0 Z\" fill=\"#4dd8ff\" transform=\"matrix(1.333,0,0,-1.333,349.797,271.737)\"/><path d=\"M 0,0 90.883,157.413 H 272.647 L 195.044,22.999 C 186.853,8.812 171.589,0 155.206,0 Z\" fill=\"#0088cc\" transform=\"matrix(1.333,0,0,-1.333,228.62,532.663)\"/><path d=\"M 0,0 C -8.189,14.188 -8.189,31.815 0.002,46.002 L 77.605,180.416 168.488,23.001 77.605,-134.413 Z\" fill=\"#00c3ff\" transform=\"matrix(1.333,0,0,-1.333,80.942,327.926)\"/></g><g id=\"text\" fill=\"currentColor\"><path d=\"M 0,0 H -116.758 V -28.172 H -83.377 0 c 28.481,0 49.92,-10.106 49.92,-43.792 0,-33.991 -20.212,-44.404 -50.224,-44.71 h -83.073 -6.534 c -14.828,0 -26.847,-12.02 -26.847,-26.847 v -71.149 h 33.381 v 69.823 h 82.769 c 45.932,0 84.213,22.66 84.213,72.883 C 83.605,-23.273 48.998,0 0,0\" transform=\"matrix(1.333,0,0,-1.333,1657.659,148.441)\"/><path d=\"M 0,0 H 95.263 V -28.785 H 60.661 -0.613 c -44.711,0 -72.883,-35.218 -72.883,-80.233 0,-45.018 28.785,-77.479 72.883,-77.479 h 61.274 34.602 v -28.479 H 0 c -63.697,0 -106.573,46.241 -106.877,107.488 C -106.877,-46.241 -63.393,0 0,0\" transform=\"matrix(1.333,0,0,-1.333,2341.873,148.032)\"/><path d=\"M 0,0 H -79.658 V -28.785 H 151.629 V 0 Z\" transform=\"matrix(1.333,0,0,-1.333,1085.02,396.287)\"/><path d=\"m 0,0 v -27.868 h 79.658 83.134 V 0 H 79.658 Z\" transform=\"matrix(1.333,0,0,-1.333,978.809,273.382)\"/><path d=\"m 734.107,305.464 h 231.287 v 28.785 H 734.107 Z\" transform=\"matrix(1.333,0,0,-1.333,0,594.516)\"/><path d=\"m 1475.135,119.887 h 33.381 V 334.25 h -33.381 z\" transform=\"matrix(1.333,0,0,-1.333,0,594.516)\"/><rect x=\"2650\" y=\"140\" width=\"8\" height=\"320\" opacity=\"0.3\"/><text x=\"2850\" y=\"405\" font-family=\"system-ui,-apple-system,sans-serif\" font-size=\"285\" font-weight=\"300\">Flowstate</text></g></svg>";
18
+ /** GitHub octocat icon (scales to parent size via width/height styles) */
19
+ declare const GITHUB_SVG = "<svg aria-hidden=\"true\" viewBox=\"0 0 16 16\" style=\"height:20px;width:20px;fill:var(--fs-muted,#94a3b8)\"><path d=\"M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z\"/></svg>";
20
+ /** Sun icon for light-mode toggle */
21
+ declare const SUN_SVG = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"width:18px;height:18px\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/><line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/><line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/><line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/><line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/><line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/><line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/></svg>";
22
+ /** Moon icon for dark-mode toggle */
23
+ declare const MOON_SVG = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"width:18px;height:18px\"><path d=\"M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z\"/></svg>";
24
+
25
+ interface PageShellOptions {
26
+ /** Document <title> */
27
+ title: string;
28
+ /** HTML content placed inside <main> .fs-card */
29
+ body: string;
30
+ /** Extra CSS appended after BRAND_CSS */
31
+ extraCss?: string;
32
+ /** Extra JS appended before </body> */
33
+ extraJs?: string;
34
+ /** Hide the theme toggle button (default: false) */
35
+ hideThemeToggle?: boolean;
36
+ /** Hide the full footer, show only copyright (default: false) */
37
+ compactFooter?: boolean;
38
+ }
39
+ /**
40
+ * Renders a full self-contained HTML page with Epic FlowState branding.
41
+ *
42
+ * Includes header (logo + nav), centered card, footer, and light/dark toggle.
43
+ * All CSS is inline, all SVGs are inline. No external dependencies.
44
+ */
45
+ declare function pageShell(options: PageShellOptions): string;
46
+ declare function escapeHtml(str: string): string;
47
+
48
+ /**
49
+ * OAuth callback success page (for CLI loopback server).
50
+ */
51
+ declare function callbackSuccessPage(): string;
52
+ /**
53
+ * OAuth callback error page (for CLI loopback server).
54
+ */
55
+ declare function callbackErrorPage(message: string): string;
56
+ /**
57
+ * OAuth authorization / login page (for auth server).
58
+ *
59
+ * Two-step flow: email entry, then verification code.
60
+ * Params are embedded as JSON to prevent XSS.
61
+ */
62
+ declare function authorizePage(params: {
63
+ clientId: string;
64
+ clientName: string;
65
+ redirectUri: string;
66
+ codeChallenge: string;
67
+ codeChallengeMethod: string;
68
+ scope: string;
69
+ state?: string | undefined;
70
+ }): string;
71
+
72
+ export { BRAND_CSS, GITHUB_SVG, ICON_SVG, LOGO_SVG, MOON_SVG, type PageShellOptions, SUN_SVG, authorizePage, callbackErrorPage, callbackSuccessPage, escapeHtml, pageShell };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared CSS for all Epic FlowState auth pages.
3
+ *
4
+ * Uses CSS custom properties for light/dark mode.
5
+ * Automatic: follows prefers-color-scheme.
6
+ * Manual: [data-theme="dark"] or [data-theme="light"] on <html> overrides.
7
+ */
8
+ declare const BRAND_CSS = "\n/* --- Reset --- */\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n\n/* --- Theme variables --- */\n:root{\n --fs-brand:#00c3ff;\n --fs-brand-light:#4dd8ff;\n --fs-brand-dark:#0088cc;\n --fs-bg:#f8fafc;\n --fs-card-bg:#fff;\n --fs-card-border:1px solid #e2e8f0;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.08);\n --fs-text:#0f172a;\n --fs-text-secondary:#475569;\n --fs-muted:#94a3b8;\n --fs-border:#e2e8f0;\n --fs-input-bg:#fff;\n --fs-input-border:#cbd5e1;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#f1f5f9;\n --fs-footer-border:#e2e8f0;\n --fs-error:#dc2626;\n --fs-error-bg:#fef2f2;\n --fs-success:#16a34a;\n --fs-success-bg:#f0fdf4;\n --fs-logo-text:#0f172a;\n --fs-header-bg:rgba(255,255,255,.8);\n}\n@media(prefers-color-scheme:dark){\n :root:not([data-theme=\"light\"]){\n --fs-bg:#0f172a;\n --fs-card-bg:#1e293b;\n --fs-card-border:1px solid #334155;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.3);\n --fs-text:#e2e8f0;\n --fs-text-secondary:#94a3b8;\n --fs-muted:#64748b;\n --fs-border:#334155;\n --fs-input-bg:#0f172a;\n --fs-input-border:#475569;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#020617;\n --fs-footer-border:#1e293b;\n --fs-error:#ef4444;\n --fs-error-bg:rgba(239,68,68,.12);\n --fs-success:#22c55e;\n --fs-success-bg:rgba(0,195,255,.12);\n --fs-logo-text:#e2e8f0;\n --fs-header-bg:rgba(15,23,42,.8);\n }\n}\n[data-theme=\"dark\"]{\n --fs-bg:#0f172a;\n --fs-card-bg:#1e293b;\n --fs-card-border:1px solid #334155;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.3);\n --fs-text:#e2e8f0;\n --fs-text-secondary:#94a3b8;\n --fs-muted:#64748b;\n --fs-border:#334155;\n --fs-input-bg:#0f172a;\n --fs-input-border:#475569;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#020617;\n --fs-footer-border:#1e293b;\n --fs-error:#ef4444;\n --fs-error-bg:rgba(239,68,68,.12);\n --fs-success:#22c55e;\n --fs-success-bg:rgba(0,195,255,.12);\n --fs-logo-text:#e2e8f0;\n --fs-header-bg:rgba(15,23,42,.8);\n}\n[data-theme=\"light\"]{\n --fs-bg:#f8fafc;\n --fs-card-bg:#fff;\n --fs-card-border:1px solid #e2e8f0;\n --fs-card-shadow:0 10px 15px -3px rgba(0,0,0,.08);\n --fs-text:#0f172a;\n --fs-text-secondary:#475569;\n --fs-muted:#94a3b8;\n --fs-border:#e2e8f0;\n --fs-input-bg:#fff;\n --fs-input-border:#cbd5e1;\n --fs-input-focus:#00c3ff;\n --fs-footer-bg:#f1f5f9;\n --fs-footer-border:#e2e8f0;\n --fs-error:#dc2626;\n --fs-error-bg:#fef2f2;\n --fs-success:#16a34a;\n --fs-success-bg:#f0fdf4;\n --fs-logo-text:#0f172a;\n --fs-header-bg:rgba(255,255,255,.8);\n}\n\n/* --- Base --- */\nbody{\n font-family:system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif;\n background:var(--fs-bg);\n color:var(--fs-text);\n min-height:100vh;\n display:flex;\n flex-direction:column;\n transition:background .2s,color .2s;\n}\na{color:var(--fs-muted);text-decoration:none;transition:color .15s}\na:hover{color:var(--fs-text)}\n\n/* --- Header --- */\n.fs-header{\n position:sticky;top:0;z-index:50;\n display:flex;align-items:center;justify-content:space-between;\n padding:16px 32px;\n background:var(--fs-header-bg);\n backdrop-filter:blur(8px);\n -webkit-backdrop-filter:blur(8px);\n}\n.fs-header-left{display:flex;align-items:center;gap:32px}\n.fs-header-left svg{color:var(--fs-logo-text)}\n.fs-header-right{display:flex;align-items:center;gap:16px}\n.fs-nav-link{font-size:14px;color:var(--fs-muted);transition:color .15s}\n.fs-nav-link:hover{color:var(--fs-text)}\n\n/* --- Theme toggle --- */\n.fs-theme-toggle{\n background:none;border:1px solid var(--fs-border);\n border-radius:8px;padding:6px 8px;cursor:pointer;\n color:var(--fs-muted);display:flex;align-items:center;gap:4px;\n transition:color .15s,border-color .15s;\n font-size:12px;\n}\n.fs-theme-toggle:hover{color:var(--fs-text);border-color:var(--fs-muted)}\n.fs-theme-toggle .sun{display:none}\n.fs-theme-toggle .moon{display:inline-flex}\n[data-theme=\"dark\"] .fs-theme-toggle .sun{display:inline-flex}\n[data-theme=\"dark\"] .fs-theme-toggle .moon{display:none}\n@media(prefers-color-scheme:dark){\n :root:not([data-theme=\"light\"]) .fs-theme-toggle .sun{display:inline-flex}\n :root:not([data-theme=\"light\"]) .fs-theme-toggle .moon{display:none}\n}\n[data-theme=\"light\"] .fs-theme-toggle .sun{display:none}\n[data-theme=\"light\"] .fs-theme-toggle .moon{display:inline-flex}\n\n/* --- Main content --- */\n.fs-main{flex:1;display:flex;align-items:center;justify-content:center;padding:24px 16px}\n\n/* --- Card --- */\n.fs-card{\n width:100%;max-width:448px;padding:32px;\n background:var(--fs-card-bg);\n border:var(--fs-card-border);\n border-radius:12px;\n box-shadow:var(--fs-card-shadow);\n}\n.fs-card h1{font-size:24px;font-weight:700;color:var(--fs-text);margin-bottom:8px;text-align:center}\n.fs-card .fs-subtitle{font-size:14px;color:var(--fs-text-secondary);text-align:center;margin-bottom:24px;line-height:1.6}\n.fs-card p{font-size:14px;color:var(--fs-text-secondary);line-height:1.6}\n\n/* --- Card status icons --- */\n.fs-status-icon{\n margin:0 auto 24px;width:64px;height:64px;border-radius:50%;\n display:flex;align-items:center;justify-content:center;\n}\n.fs-status-icon.success{background:var(--fs-success-bg)}\n.fs-status-icon.error{background:var(--fs-error-bg)}\n.fs-status-icon svg{width:32px;height:32px}\n.fs-card .fs-action{margin-top:24px;font-size:13px;color:var(--fs-muted);text-align:center}\n\n/* --- Client info badge --- */\n.fs-client-info{\n background:var(--fs-bg);border:1px solid var(--fs-border);\n border-radius:8px;padding:12px;margin-bottom:24px;text-align:center;\n font-size:14px;color:var(--fs-text-secondary);\n}\n.fs-client-info strong{color:var(--fs-brand);font-weight:600}\n\n/* --- Forms --- */\n.fs-form{display:flex;flex-direction:column;gap:16px}\n.fs-form label{font-weight:500;color:var(--fs-text);font-size:14px}\n.fs-form input[type=\"email\"],\n.fs-form input[type=\"text\"]{\n width:100%;padding:12px 16px;\n background:var(--fs-input-bg);\n border:2px solid var(--fs-input-border);\n border-radius:8px;font-size:16px;\n color:var(--fs-text);\n transition:border-color .2s;\n}\n.fs-form input::placeholder{color:var(--fs-muted)}\n.fs-form input:focus{outline:none;border-color:var(--fs-input-focus)}\n.fs-btn{\n background:var(--fs-brand);color:#fff;border:none;\n padding:14px;border-radius:8px;font-size:16px;font-weight:600;\n cursor:pointer;transition:background .2s;width:100%;\n}\n.fs-btn:hover{background:var(--fs-brand-dark)}\n.fs-btn:disabled{opacity:.5;cursor:not-allowed}\n.fs-error-text{color:var(--fs-error);font-size:14px;text-align:center}\n.fs-success-text{color:var(--fs-success);font-size:14px;text-align:center}\n.fs-back-link{text-align:center;margin-top:16px}\n.fs-back-link a{color:var(--fs-brand);font-size:14px}\n.fs-back-link a:hover{color:var(--fs-brand-light)}\n\n/* --- Footer --- */\n.fs-footer{background:var(--fs-footer-bg);padding:48px 32px;transition:background .2s}\n.fs-footer-grid{\n max-width:1280px;margin:0 auto;\n display:grid;grid-template-columns:repeat(4,1fr);gap:32px;\n}\n@media(max-width:768px){.fs-footer-grid{grid-template-columns:repeat(2,1fr)}}\n@media(max-width:480px){.fs-footer-grid{grid-template-columns:1fr}}\n.fs-footer-col h3{\n font-size:12px;font-weight:600;text-transform:uppercase;\n letter-spacing:.05em;color:var(--fs-text);margin-bottom:16px;\n}\n.fs-footer-col ul{list-style:none}\n.fs-footer-col li{margin-bottom:12px}\n.fs-footer-col a{font-size:14px;color:var(--fs-muted)}\n.fs-footer-bottom{\n max-width:1280px;margin:0 auto;\n display:flex;align-items:center;justify-content:space-between;\n border-top:1px solid var(--fs-footer-border);\n padding-top:32px;margin-top:48px;\n}\n.fs-footer-bottom-left{display:flex;align-items:center;gap:12px}\n.fs-footer-bottom p{font-size:14px;color:var(--fs-muted)}\n";
9
+
10
+ /**
11
+ * Epic FlowState brand SVG assets for self-contained HTML pages.
12
+ * All SVGs are inline-safe (no external references).
13
+ */
14
+ /** Diamond icon mark (36x36) */
15
+ declare const ICON_SVG = "<svg aria-hidden=\"true\" viewBox=\"0 0 36 36\" fill=\"none\" style=\"height:36px;width:36px\"><g fill=\"none\" stroke-linejoin=\"round\" stroke-width=\"3\"><path d=\"M10.308 5L18 17.5 10.308 30 2.615 17.5 10.308 5z\" stroke=\"var(--fs-brand,#00c3ff)\"/><path d=\"M18 17.5L10.308 5h15.144l7.933 12.5M18 17.5h15.385L25.452 30H10.308L18 17.5z\" stroke=\"var(--fs-brand-dark,#0088cc)\"/></g></svg>";
16
+ /** Full wordmark logo. Fill adapts via currentColor for light/dark. */
17
+ declare const LOGO_SVG = "<svg aria-label=\"Epic FlowState\" viewBox=\"0 0 4000 594.516\" style=\"height:36px;width:auto\"><g id=\"icon\"><path d=\"M 0,0 -90.883,157.413 H 64.324 c 16.382,0 31.646,-8.812 39.835,-23 L 181.764,0 Z\" fill=\"#4dd8ff\" transform=\"matrix(1.333,0,0,-1.333,349.797,271.737)\"/><path d=\"M 0,0 90.883,157.413 H 272.647 L 195.044,22.999 C 186.853,8.812 171.589,0 155.206,0 Z\" fill=\"#0088cc\" transform=\"matrix(1.333,0,0,-1.333,228.62,532.663)\"/><path d=\"M 0,0 C -8.189,14.188 -8.189,31.815 0.002,46.002 L 77.605,180.416 168.488,23.001 77.605,-134.413 Z\" fill=\"#00c3ff\" transform=\"matrix(1.333,0,0,-1.333,80.942,327.926)\"/></g><g id=\"text\" fill=\"currentColor\"><path d=\"M 0,0 H -116.758 V -28.172 H -83.377 0 c 28.481,0 49.92,-10.106 49.92,-43.792 0,-33.991 -20.212,-44.404 -50.224,-44.71 h -83.073 -6.534 c -14.828,0 -26.847,-12.02 -26.847,-26.847 v -71.149 h 33.381 v 69.823 h 82.769 c 45.932,0 84.213,22.66 84.213,72.883 C 83.605,-23.273 48.998,0 0,0\" transform=\"matrix(1.333,0,0,-1.333,1657.659,148.441)\"/><path d=\"M 0,0 H 95.263 V -28.785 H 60.661 -0.613 c -44.711,0 -72.883,-35.218 -72.883,-80.233 0,-45.018 28.785,-77.479 72.883,-77.479 h 61.274 34.602 v -28.479 H 0 c -63.697,0 -106.573,46.241 -106.877,107.488 C -106.877,-46.241 -63.393,0 0,0\" transform=\"matrix(1.333,0,0,-1.333,2341.873,148.032)\"/><path d=\"M 0,0 H -79.658 V -28.785 H 151.629 V 0 Z\" transform=\"matrix(1.333,0,0,-1.333,1085.02,396.287)\"/><path d=\"m 0,0 v -27.868 h 79.658 83.134 V 0 H 79.658 Z\" transform=\"matrix(1.333,0,0,-1.333,978.809,273.382)\"/><path d=\"m 734.107,305.464 h 231.287 v 28.785 H 734.107 Z\" transform=\"matrix(1.333,0,0,-1.333,0,594.516)\"/><path d=\"m 1475.135,119.887 h 33.381 V 334.25 h -33.381 z\" transform=\"matrix(1.333,0,0,-1.333,0,594.516)\"/><rect x=\"2650\" y=\"140\" width=\"8\" height=\"320\" opacity=\"0.3\"/><text x=\"2850\" y=\"405\" font-family=\"system-ui,-apple-system,sans-serif\" font-size=\"285\" font-weight=\"300\">Flowstate</text></g></svg>";
18
+ /** GitHub octocat icon (scales to parent size via width/height styles) */
19
+ declare const GITHUB_SVG = "<svg aria-hidden=\"true\" viewBox=\"0 0 16 16\" style=\"height:20px;width:20px;fill:var(--fs-muted,#94a3b8)\"><path d=\"M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z\"/></svg>";
20
+ /** Sun icon for light-mode toggle */
21
+ declare const SUN_SVG = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"width:18px;height:18px\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/><line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/><line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/><line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/><line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/><line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/><line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/></svg>";
22
+ /** Moon icon for dark-mode toggle */
23
+ declare const MOON_SVG = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"width:18px;height:18px\"><path d=\"M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z\"/></svg>";
24
+
25
+ interface PageShellOptions {
26
+ /** Document <title> */
27
+ title: string;
28
+ /** HTML content placed inside <main> .fs-card */
29
+ body: string;
30
+ /** Extra CSS appended after BRAND_CSS */
31
+ extraCss?: string;
32
+ /** Extra JS appended before </body> */
33
+ extraJs?: string;
34
+ /** Hide the theme toggle button (default: false) */
35
+ hideThemeToggle?: boolean;
36
+ /** Hide the full footer, show only copyright (default: false) */
37
+ compactFooter?: boolean;
38
+ }
39
+ /**
40
+ * Renders a full self-contained HTML page with Epic FlowState branding.
41
+ *
42
+ * Includes header (logo + nav), centered card, footer, and light/dark toggle.
43
+ * All CSS is inline, all SVGs are inline. No external dependencies.
44
+ */
45
+ declare function pageShell(options: PageShellOptions): string;
46
+ declare function escapeHtml(str: string): string;
47
+
48
+ /**
49
+ * OAuth callback success page (for CLI loopback server).
50
+ */
51
+ declare function callbackSuccessPage(): string;
52
+ /**
53
+ * OAuth callback error page (for CLI loopback server).
54
+ */
55
+ declare function callbackErrorPage(message: string): string;
56
+ /**
57
+ * OAuth authorization / login page (for auth server).
58
+ *
59
+ * Two-step flow: email entry, then verification code.
60
+ * Params are embedded as JSON to prevent XSS.
61
+ */
62
+ declare function authorizePage(params: {
63
+ clientId: string;
64
+ clientName: string;
65
+ redirectUri: string;
66
+ codeChallenge: string;
67
+ codeChallengeMethod: string;
68
+ scope: string;
69
+ state?: string | undefined;
70
+ }): string;
71
+
72
+ export { BRAND_CSS, GITHUB_SVG, ICON_SVG, LOGO_SVG, MOON_SVG, type PageShellOptions, SUN_SVG, authorizePage, callbackErrorPage, callbackSuccessPage, escapeHtml, pageShell };