to render the all-in-one
component against a real session
Theming lives in src/netflixAppearance.ts as pure data so it can be
lifted directly into a customer app.
Co-Authored-By: Claude Opus 4.7
---
bun.lock | 19 +
packages/netflix-demo/README.md | 49 ++
packages/netflix-demo/index.html | 18 +
packages/netflix-demo/package.json | 24 +
packages/netflix-demo/src/App.tsx | 474 ++++++++++++++++++
packages/netflix-demo/src/globals.css | 49 ++
packages/netflix-demo/src/main.tsx | 12 +
.../netflix-demo/src/netflixAppearance.ts | 183 +++++++
packages/netflix-demo/tsconfig.json | 17 +
packages/netflix-demo/vite.config.ts | 10 +
10 files changed, 855 insertions(+)
create mode 100644 packages/netflix-demo/README.md
create mode 100644 packages/netflix-demo/index.html
create mode 100644 packages/netflix-demo/package.json
create mode 100644 packages/netflix-demo/src/App.tsx
create mode 100644 packages/netflix-demo/src/globals.css
create mode 100644 packages/netflix-demo/src/main.tsx
create mode 100644 packages/netflix-demo/src/netflixAppearance.ts
create mode 100644 packages/netflix-demo/tsconfig.json
create mode 100644 packages/netflix-demo/vite.config.ts
diff --git a/bun.lock b/bun.lock
index 6778b3b..f0378ec 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "managed-auth-react-workspace",
@@ -44,6 +45,22 @@
"react-dom": ">=18",
},
},
+ "packages/netflix-demo": {
+ "name": "@onkernel/managed-auth-react-netflix-demo",
+ "version": "0.0.0",
+ "dependencies": {
+ "@onkernel/managed-auth-react": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ },
+ "devDependencies": {
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@vitejs/plugin-react": "^4.3.0",
+ "typescript": "^5.6.0",
+ "vite": "^5.4.0",
+ },
+ },
},
"packages": {
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
@@ -150,6 +167,8 @@
"@onkernel/managed-auth-react-demo": ["@onkernel/managed-auth-react-demo@workspace:packages/demo"],
+ "@onkernel/managed-auth-react-netflix-demo": ["@onkernel/managed-auth-react-netflix-demo@workspace:packages/netflix-demo"],
+
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.2", "", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="],
diff --git a/packages/netflix-demo/README.md b/packages/netflix-demo/README.md
new file mode 100644
index 0000000..6e0fc90
--- /dev/null
+++ b/packages/netflix-demo/README.md
@@ -0,0 +1,49 @@
+# `@onkernel/managed-auth-react-netflix-demo`
+
+Netflix-skinned demo of [`@onkernel/managed-auth-react`](../managed-auth-react). Drops ` ` onto a Netflix-style backdrop (red bloom, black gradient, Bebas-Neue wordmark) so you can see the customization API in action against a recognizable brand.
+
+## Run it
+
+From the repo root:
+
+```bash
+bun install
+bun run --filter @onkernel/managed-auth-react-netflix-demo dev
+```
+
+Opens at `http://localhost:5174`.
+
+## Two modes
+
+### Preview mode (default)
+
+The page loads in preview mode — no backend required. A state picker below the auth card lets you scrub through every UI step (`prime`, `discovering`, `awaiting_input`, `success`, `error`, etc.) so you can audit the Netflix theming against every state.
+
+### Live mode
+
+Pass a real session ID and handoff code via URL params and the page renders the all-in-one `` component instead:
+
+```
+http://localhost:5174/?session=&code=
+```
+
+Generate those on your backend with the Kernel SDK:
+
+```ts
+import Kernel from "@onkernel/sdk";
+
+const kernel = new Kernel({ apiKey: process.env.KERNEL_API_KEY });
+
+const connection = await kernel.auth.connections.create({
+ domain: "netflix.com",
+ profile_name: "demo-user",
+});
+const { id, handoff_code } = await kernel.auth.connections.login(connection.id);
+
+// Open the demo with these in the URL:
+console.log(`http://localhost:5174/?session=${id}&code=${handoff_code}`);
+```
+
+## Where the theming lives
+
+`src/netflixAppearance.ts` — exports `netflixAppearance` and `netflixLocalization`. Pure data; no component logic. Lift it into your own app to reproduce the look.
diff --git a/packages/netflix-demo/index.html b/packages/netflix-demo/index.html
new file mode 100644
index 0000000..d0b2934
--- /dev/null
+++ b/packages/netflix-demo/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Kernel Managed Auth — Netflix demo
+
+
+
+
+
+
+
+
+
diff --git a/packages/netflix-demo/package.json b/packages/netflix-demo/package.json
new file mode 100644
index 0000000..4ce5181
--- /dev/null
+++ b/packages/netflix-demo/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@onkernel/managed-auth-react-netflix-demo",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@onkernel/managed-auth-react": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@vitejs/plugin-react": "^4.3.0",
+ "typescript": "^5.6.0",
+ "vite": "^5.4.0"
+ }
+}
diff --git a/packages/netflix-demo/src/App.tsx b/packages/netflix-demo/src/App.tsx
new file mode 100644
index 0000000..e4779c3
--- /dev/null
+++ b/packages/netflix-demo/src/App.tsx
@@ -0,0 +1,474 @@
+import { useMemo, useState } from "react";
+import {
+ AppearanceProvider,
+ ExternalActionWaiting,
+ KernelManagedAuth,
+ LoadingState,
+ LocalizationProvider,
+ Shell,
+ StepError,
+ StepExpired,
+ StepPrime,
+ StepSuccess,
+ UnifiedAuthForm,
+ type DiscoveredField,
+ type MFAOption,
+ type SignInOption,
+ type SSOButton,
+} from "@onkernel/managed-auth-react";
+import "@onkernel/managed-auth-react/styles.css";
+import { netflixAppearance, netflixLocalization } from "./netflixAppearance";
+
+const TARGET_DOMAIN = "netflix.com";
+
+type Step =
+ | "prime"
+ | "discovering"
+ | "awaiting_input"
+ | "awaiting_input_sso"
+ | "awaiting_input_mfa"
+ | "awaiting_external_action"
+ | "submitting"
+ | "success"
+ | "expired"
+ | "error";
+
+const allSteps: { id: Step; label: string }[] = [
+ { id: "prime", label: "Prime / consent" },
+ { id: "discovering", label: "Discovering" },
+ { id: "awaiting_input", label: "Awaiting input" },
+ { id: "awaiting_input_sso", label: "+ SSO" },
+ { id: "awaiting_input_mfa", label: "MFA select" },
+ { id: "awaiting_external_action", label: "External action" },
+ { id: "submitting", label: "Submitting" },
+ { id: "success", label: "Success" },
+ { id: "expired", label: "Expired" },
+ { id: "error", label: "Error" },
+];
+
+const mockFields: DiscoveredField[] = [
+ {
+ name: "email",
+ label: "Email or phone number",
+ type: "email",
+ required: true,
+ placeholder: "Email or phone number",
+ },
+ {
+ name: "password",
+ label: "Password",
+ type: "password",
+ required: true,
+ placeholder: "Password",
+ },
+];
+
+const mockSSO: SSOButton[] = [
+ { provider: "google", label: "Continue with Google", selector: "" },
+ { provider: "facebook", label: "Continue with Facebook", selector: "" },
+];
+
+const mockMFA: MFAOption[] = [
+ { type: "sms", target: "•••• 8421" },
+ { type: "email", target: "n••••@netflix.com" },
+ { type: "totp", description: "Use your authenticator app" },
+];
+
+const mockSignInOptions: SignInOption[] = [
+ { id: "personal", label: "Personal", description: "you@example.com" },
+ { id: "kids", label: "Kids", description: "Profile for younger viewers" },
+];
+
+function renderStep(step: Step) {
+ switch (step) {
+ case "prime":
+ return {}} />;
+ case "discovering":
+ return (
+
+ );
+ case "awaiting_input":
+ return (
+ {}}
+ onSSOClick={() => {}}
+ onMFASelect={() => {}}
+ onSignInOptionSelect={() => {}}
+ />
+ );
+ case "awaiting_input_sso":
+ return (
+ {}}
+ onSSOClick={() => {}}
+ onMFASelect={() => {}}
+ onSignInOptionSelect={() => {}}
+ />
+ );
+ case "awaiting_input_mfa":
+ return (
+ {}}
+ onSSOClick={() => {}}
+ onMFASelect={() => {}}
+ onSignInOptionSelect={() => {}}
+ />
+ );
+ case "awaiting_external_action":
+ return (
+
+ );
+ case "submitting":
+ return (
+
+ );
+ case "success":
+ return ;
+ case "expired":
+ return ;
+ case "error":
+ return (
+
+ );
+ }
+}
+
+interface LiveCredentials {
+ sessionId: string;
+ handoffCode: string;
+}
+
+function readLiveCredentialsFromUrl(): LiveCredentials | null {
+ if (typeof window === "undefined") return null;
+ const params = new URLSearchParams(window.location.search);
+ const sessionId = params.get("session") ?? params.get("sessionId");
+ const handoffCode = params.get("code") ?? params.get("handoffCode");
+ if (!sessionId || !handoffCode) return null;
+ return { sessionId, handoffCode };
+}
+
+export function App() {
+ const live = useMemo(readLiveCredentialsFromUrl, []);
+ const [step, setStep] = useState("prime");
+
+ return (
+
+
+
+
+
+
+ {!live && }
+
+
+ );
+}
+
+function AuthCard({
+ live,
+ step,
+}: {
+ live: LiveCredentials | null;
+ step: Step;
+}) {
+ if (live) {
+ return (
+ {
+ // eslint-disable-next-line no-console
+ console.log("[netflix-demo] success", { profileName, domain });
+ }}
+ onError={({ code, message }) => {
+ // eslint-disable-next-line no-console
+ console.error("[netflix-demo] error", code, message);
+ }}
+ />
+ );
+ }
+
+ return (
+
+
+ {renderStep(step)}
+
+
+ );
+}
+
+function BackdropPoster() {
+ return (
+ <>
+ {/* The Netflix-y backdrop: dark base + crimson radial bloom + bottom
+ black gradient that fades into the form area. Three layered
+ fixed-position divs so the auth card always reads as if it's on a
+ movie poster. */}
+
+
+
+ >
+ );
+}
+
+function Header() {
+ return (
+
+ NETFLIX
+
+
+ );
+}
+
+function Hero({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+function StatePicker({
+ step,
+ onChange,
+}: {
+ step: Step;
+ onChange: (s: Step) => void;
+}) {
+ return (
+
+ );
+}
+
+function DemoFooter({ live }: { live: LiveCredentials | null }) {
+ return (
+
+ );
+}
+
+const pageStyles: Record = {
+ page: {
+ position: "relative",
+ minHeight: "100vh",
+ color: "#fff",
+ overflowX: "hidden",
+ paddingBottom: 80,
+ },
+ backdropBase: {
+ position: "fixed",
+ inset: 0,
+ background:
+ "linear-gradient(180deg, #000 0%, #0a0a0a 40%, #000 100%)",
+ zIndex: -3,
+ },
+ backdropBloom: {
+ position: "fixed",
+ inset: 0,
+ background:
+ "radial-gradient(60% 50% at 50% 30%, rgba(229, 9, 20, 0.35) 0%, rgba(229, 9, 20, 0.08) 35%, transparent 70%)",
+ zIndex: -2,
+ },
+ backdropGradient: {
+ position: "fixed",
+ inset: 0,
+ background:
+ "linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.0) 25%, rgba(0,0,0,0.0) 60%, rgba(0,0,0,0.85) 100%)",
+ zIndex: -1,
+ pointerEvents: "none",
+ },
+ header: {
+ position: "relative",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ padding: "24px 56px",
+ zIndex: 2,
+ },
+ brand: {
+ fontFamily: "'Bebas Neue', 'Inter', sans-serif",
+ fontSize: 36,
+ fontWeight: 900,
+ letterSpacing: "0.04em",
+ color: "#e50914",
+ textShadow: "0 1px 2px rgba(0,0,0,0.6)",
+ },
+ langButton: {
+ background: "rgba(0, 0, 0, 0.5)",
+ color: "#fff",
+ border: "1px solid rgba(255, 255, 255, 0.4)",
+ padding: "6px 14px",
+ fontSize: 13,
+ fontWeight: 500,
+ borderRadius: 2,
+ cursor: "pointer",
+ },
+ heroWrap: {
+ position: "relative",
+ zIndex: 1,
+ display: "flex",
+ justifyContent: "center",
+ padding: "40px 16px 24px",
+ },
+ cardSlot: {
+ width: "100%",
+ maxWidth: 480,
+ },
+ footer: {
+ position: "relative",
+ zIndex: 1,
+ marginTop: 40,
+ padding: "0 24px",
+ },
+ footerInner: {
+ maxWidth: 720,
+ margin: "0 auto",
+ fontSize: 12,
+ lineHeight: 1.6,
+ color: "#808080",
+ textAlign: "center",
+ display: "flex",
+ flexDirection: "column",
+ gap: 6,
+ },
+ footerLine: {},
+ footerLink: { color: "#b3b3b3", textDecoration: "underline" },
+ footerCode: {
+ fontFamily: "ui-monospace, monospace",
+ fontSize: 11,
+ color: "#b3b3b3",
+ },
+};
+
+const pickerStyles: Record = {
+ panel: {
+ position: "relative",
+ zIndex: 1,
+ maxWidth: 720,
+ margin: "32px auto 0",
+ padding: "16px 20px",
+ background: "rgba(0, 0, 0, 0.55)",
+ border: "1px solid rgba(255, 255, 255, 0.08)",
+ borderRadius: 6,
+ },
+ header: {
+ display: "flex",
+ alignItems: "baseline",
+ justifyContent: "space-between",
+ gap: 12,
+ marginBottom: 12,
+ flexWrap: "wrap",
+ },
+ headerLabel: {
+ fontSize: 13,
+ fontWeight: 600,
+ textTransform: "uppercase",
+ letterSpacing: "0.08em",
+ color: "#fff",
+ },
+ headerHint: { fontSize: 12, color: "#808080" },
+ code: {
+ fontFamily: "ui-monospace, monospace",
+ background: "rgba(255, 255, 255, 0.06)",
+ padding: "1px 5px",
+ borderRadius: 3,
+ color: "#b3b3b3",
+ },
+ chips: {
+ display: "flex",
+ flexWrap: "wrap",
+ gap: 6,
+ },
+ chip: {
+ background: "rgba(255, 255, 255, 0.06)",
+ color: "#b3b3b3",
+ border: "1px solid rgba(255, 255, 255, 0.1)",
+ padding: "6px 12px",
+ fontSize: 12,
+ fontWeight: 500,
+ borderRadius: 4,
+ cursor: "pointer",
+ },
+ chipActive: {
+ background: "#e50914",
+ color: "#fff",
+ border: "1px solid #e50914",
+ padding: "6px 12px",
+ fontSize: 12,
+ fontWeight: 600,
+ borderRadius: 4,
+ cursor: "pointer",
+ },
+};
diff --git a/packages/netflix-demo/src/globals.css b/packages/netflix-demo/src/globals.css
new file mode 100644
index 0000000..b870dbd
--- /dev/null
+++ b/packages/netflix-demo/src/globals.css
@@ -0,0 +1,49 @@
+/*
+ * Demo chrome only — these tokens are scoped to the Netflix-style page
+ * surrounding the auth component. The auth component itself is themed via
+ * the `--kma-*` tokens emitted by `appearance.variables` (see netflixAppearance
+ * in App.tsx).
+ */
+:root {
+ --nflx-red: #e50914;
+ --nflx-red-hover: #f6121d;
+ --nflx-red-deep: #b1060f;
+ --nflx-black: #000000;
+ --nflx-near-black: #141414;
+ --nflx-charcoal: #181818;
+ --nflx-gray: #2f2f2f;
+ --nflx-text: #ffffff;
+ --nflx-text-muted: #b3b3b3;
+ --nflx-text-dim: #808080;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ margin: 0;
+ padding: 0;
+ min-height: 100vh;
+ background: var(--nflx-black);
+ color: var(--nflx-text);
+ font-family:
+ "Inter",
+ "Netflix Sans",
+ ui-sans-serif,
+ system-ui,
+ -apple-system,
+ BlinkMacSystemFont,
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+}
+
+a {
+ color: inherit;
+}
+
+button {
+ font-family: inherit;
+}
diff --git a/packages/netflix-demo/src/main.tsx b/packages/netflix-demo/src/main.tsx
new file mode 100644
index 0000000..78014d4
--- /dev/null
+++ b/packages/netflix-demo/src/main.tsx
@@ -0,0 +1,12 @@
+import React from "react";
+import { createRoot } from "react-dom/client";
+import { App } from "./App";
+import "./globals.css";
+
+const container = document.getElementById("root");
+if (!container) throw new Error("#root not found");
+createRoot(container).render(
+
+
+ ,
+);
diff --git a/packages/netflix-demo/src/netflixAppearance.ts b/packages/netflix-demo/src/netflixAppearance.ts
new file mode 100644
index 0000000..27b76d6
--- /dev/null
+++ b/packages/netflix-demo/src/netflixAppearance.ts
@@ -0,0 +1,183 @@
+import type { Appearance, Localization } from "@onkernel/managed-auth-react";
+
+/**
+ * Netflix-style appearance for ` `.
+ *
+ * Pulls from Netflix's public sign-in page: black surface, charcoal card,
+ * white type, signature red primary CTA (#e50914), tight 4px radius, and
+ * the floating-label input pattern via a thicker dark gray fill.
+ *
+ * `kernelLogoColor: "white"` keeps the Powered-by row legible on the
+ * near-black card.
+ */
+export const netflixAppearance: Appearance = {
+ theme: "dark",
+ variables: {
+ colorBackground: "transparent",
+ colorCard: "rgba(0, 0, 0, 0.75)",
+ colorCardForeground: "#ffffff",
+ colorForeground: "#ffffff",
+ colorMuted: "#2f2f2f",
+ colorMutedForeground: "#b3b3b3",
+ colorBorder: "rgba(255, 255, 255, 0.08)",
+ colorInput: "#333333",
+ colorInputForeground: "#ffffff",
+ colorPrimary: "#e50914",
+ colorPrimaryForeground: "#ffffff",
+ colorRing: "#e50914",
+ colorSuccess: "#46d369",
+ colorSuccessForeground: "#000000",
+ colorDanger: "#e87c03",
+ colorDangerForeground: "#000000",
+ fontFamily:
+ "'Inter', 'Netflix Sans', ui-sans-serif, system-ui, sans-serif",
+ fontWeightNormal: 400,
+ fontWeightMedium: 500,
+ fontWeightSemibold: 700,
+ borderRadius: "4px",
+ borderRadiusSm: "2px",
+ borderRadiusLg: "6px",
+ },
+ elements: {
+ card: {
+ style: {
+ background: "rgba(0, 0, 0, 0.75)",
+ border: "none",
+ boxShadow: "none",
+ },
+ },
+ title: {
+ style: {
+ fontSize: 32,
+ fontWeight: 700,
+ letterSpacing: "-0.01em",
+ marginBottom: 28,
+ },
+ },
+ subtitle: {
+ style: { color: "#b3b3b3" },
+ },
+ description: {
+ style: { color: "#b3b3b3" },
+ },
+ label: {
+ style: { color: "#b3b3b3", fontWeight: 500 },
+ },
+ input: {
+ style: {
+ background: "#333333",
+ border: "none",
+ color: "#ffffff",
+ height: 50,
+ fontSize: 16,
+ padding: "16px 20px 0",
+ borderRadius: 4,
+ "::placeholder": { color: "#8c8c8c", opacity: 1 },
+ ":focus-visible": {
+ outline: "none",
+ background: "#454545",
+ boxShadow: "inset 0 -2px 0 #e50914",
+ },
+ ":hover": { background: "#454545" },
+ },
+ },
+ buttonPrimary: {
+ style: {
+ background: "#e50914",
+ color: "#ffffff",
+ fontSize: 16,
+ fontWeight: 700,
+ height: 48,
+ textTransform: "none",
+ letterSpacing: "0.01em",
+ borderRadius: 4,
+ ":hover": {
+ background: "#f6121d",
+ opacity: 1,
+ },
+ ":active": { background: "#b1060f" },
+ ":disabled": { background: "rgba(229, 9, 20, 0.5)", opacity: 1 },
+ },
+ },
+ buttonSecondary: {
+ style: {
+ background: "rgba(255, 255, 255, 0.08)",
+ color: "#ffffff",
+ border: "none",
+ ":hover": {
+ background: "rgba(255, 255, 255, 0.16)",
+ opacity: 1,
+ },
+ },
+ },
+ ssoButton: {
+ style: {
+ background: "rgba(255, 255, 255, 0.08)",
+ color: "#ffffff",
+ border: "none",
+ height: 48,
+ fontWeight: 500,
+ ":hover": {
+ background: "rgba(255, 255, 255, 0.16)",
+ opacity: 1,
+ },
+ },
+ },
+ mfaOption: {
+ style: {
+ background: "rgba(255, 255, 255, 0.04)",
+ border: "1px solid rgba(255, 255, 255, 0.1)",
+ ":hover": {
+ background: "rgba(255, 255, 255, 0.08)",
+ opacity: 1,
+ },
+ },
+ },
+ signInOption: {
+ style: {
+ background: "rgba(255, 255, 255, 0.04)",
+ border: "1px solid rgba(255, 255, 255, 0.1)",
+ ":hover": {
+ background: "rgba(255, 255, 255, 0.08)",
+ opacity: 1,
+ },
+ },
+ },
+ divider: { style: { color: "#808080" } },
+ dividerLine: { style: { borderColor: "rgba(255, 255, 255, 0.1)" } },
+ dividerText: { style: { color: "#808080", fontSize: 13 } },
+ securityCard: {
+ style: {
+ background: "rgba(255, 255, 255, 0.04)",
+ border: "1px solid rgba(255, 255, 255, 0.08)",
+ },
+ },
+ securityText: { style: { color: "#b3b3b3", fontSize: 13 } },
+ legalText: { style: { color: "#808080", fontSize: 13 } },
+ legalLink: { style: { color: "#0071eb" } },
+ errorBanner: {
+ style: {
+ background: "#e87c03",
+ color: "#000000",
+ borderRadius: 4,
+ padding: "10px 20px",
+ },
+ },
+ successIcon: { style: { color: "#46d369" } },
+ poweredBy: { style: { color: "#808080" } },
+ },
+ layout: {
+ poweredByKernel: true,
+ kernelLogoColor: "white",
+ showSecurityCard: true,
+ },
+};
+
+export const netflixLocalization: Localization = {
+ loginTitle: () => "Sign In",
+ primeContinueButton: "Sign In",
+ submitButton: "Sign In",
+ submittingButton: "Signing in…",
+ ssoButtonLabel: (provider) => `Continue with ${provider}`,
+ orDivider: "OR",
+};
diff --git a/packages/netflix-demo/tsconfig.json b/packages/netflix-demo/tsconfig.json
new file mode 100644
index 0000000..42e0521
--- /dev/null
+++ b/packages/netflix-demo/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true
+ },
+ "include": ["src"]
+}
diff --git a/packages/netflix-demo/vite.config.ts b/packages/netflix-demo/vite.config.ts
new file mode 100644
index 0000000..2f32457
--- /dev/null
+++ b/packages/netflix-demo/vite.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 5174,
+ open: true,
+ },
+});