{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "app-sign-in",
  "title": "Sign in",
  "description": "Sign in is a production-ready blocks component from BJORK UI, packaged for shadcn registries.",
  "dependencies": [
    "clsx",
    "framer-motion",
    "next-themes",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "components/bjork-ui/blocks/app-sign-in.tsx",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type FormEvent,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type ReactNode,\n} from \"react\";\nimport { motion } from \"framer-motion\";\nimport {\n  BlockButton,\n  BlockField,\n  IconButton,\n  InitialsAvatar,\n  blockRoot,\n  focusRing,\n  useAnnouncer,\n  useAppBlockTheme,\n  useBlockReducedMotion,\n  useBlockToast,\n  type AppBlockTheme,\n} from \"@/components/bjork-ui/blocks/app-block-kit\";\nimport { cn } from \"@/lib/utils\";\n\nexport type AppSignInMode = \"sign-in\" | \"sign-up\";\nexport type AppSignInProvider = \"google\" | \"github\" | \"apple\";\n\nexport interface AppSignInPayload {\n  mode: AppSignInMode;\n  email: string;\n  password: string;\n  /** Only sent when creating an account. */\n  name?: string;\n  remember: boolean;\n}\n\nexport interface AppSignInBrand {\n  name: string;\n  /** One line under the heading. */\n  tagline: string;\n  supportHref?: string;\n  termsHref?: string;\n  privacyHref?: string;\n}\n\nexport interface AppSignInTestimonial {\n  quote: string;\n  author: string;\n  role: string;\n}\n\nexport interface AppSignInProps {\n  theme?: AppBlockTheme;\n  /** Controlled mode. */\n  mode?: AppSignInMode;\n  defaultMode?: AppSignInMode;\n  onModeChange?: (mode: AppSignInMode) => void;\n  brand?: AppSignInBrand;\n  testimonial?: AppSignInTestimonial;\n  providers?: AppSignInProvider[];\n  /** Throw an Error to show its message as the form error. Without it the block runs a demo backend. */\n  onSubmit?: (payload: AppSignInPayload) => Promise<void> | void;\n  onSso?: (provider: AppSignInProvider) => Promise<void> | void;\n  /** Sends the link. Throw to show an error. */\n  onMagicLink?: (email: string) => Promise<void> | void;\n  /** Checks the 6-digit code from the email. Throw to reject it. */\n  onVerifyCode?: (email: string, code: string) => Promise<void> | void;\n  /** Called from the success screen's primary button. */\n  onContinue?: () => void;\n  /** Shows the brand panel at wide container widths. Default true. */\n  showPanel?: boolean;\n  /** Seconds before a new link can be requested. Default 30. */\n  resendSeconds?: number;\n  className?: string;\n}\n\nexport const APP_SIGN_IN_BRAND: AppSignInBrand = {\n  name: \"Kestrel\",\n  tagline: \"Incident timelines, on-call and status pages for teams that ship daily.\",\n  supportHref: \"#support\",\n  termsHref: \"#terms\",\n  privacyHref: \"#privacy\",\n};\n\nexport const APP_SIGN_IN_TESTIMONIAL: AppSignInTestimonial = {\n  quote:\n    \"We used to rebuild every incident from Slack scrollback. Now the timeline writes itself, and our postmortems ship the same afternoon.\",\n  author: \"Maren Okafor\",\n  role: \"Staff SRE, Halden Freight\",\n};\n\nexport const APP_SIGN_IN_PROVIDERS: AppSignInProvider[] = [\"google\", \"github\", \"apple\"];\n\ntype View = \"form\" | \"magic\" | \"success\";\ntype FieldName = \"name\" | \"email\" | \"password\";\ntype Errors = Partial<Record<FieldName, string>>;\n\nconst PROVIDER_LABEL: Record<AppSignInProvider, string> = {\n  google: \"Google\",\n  github: \"GitHub\",\n  apple: \"Apple\",\n};\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/;\n\nconst PASSWORD_RULES: { id: string; label: string; test: (value: string) => boolean }[] = [\n  { id: \"length\", label: \"At least 10 characters\", test: (v) => v.length >= 10 },\n  { id: \"case\", label: \"Upper and lower case\", test: (v) => /[a-z]/.test(v) && /[A-Z]/.test(v) },\n  { id: \"number\", label: \"A number\", test: (v) => /\\d/.test(v) },\n  { id: \"symbol\", label: \"A symbol\", test: (v) => /[^A-Za-z0-9]/.test(v) },\n];\n\nconst STRENGTH = [\n  { label: \"Too weak\", color: \"var(--blk-error)\" },\n  { label: \"Weak\", color: \"var(--blk-error)\" },\n  { label: \"Fair\", color: \"var(--blk-warning)\" },\n  { label: \"Good\", color: \"var(--bjork-accent)\" },\n  { label: \"Strong\", color: \"var(--blk-success)\" },\n] as const;\n\nfunction validate(mode: AppSignInMode, values: { name: string; email: string; password: string }): Errors {\n  const errors: Errors = {};\n  if (mode === \"sign-up\" && values.name.trim().length < 2) errors.name = \"Enter your full name.\";\n  if (!values.email.trim()) errors.email = \"Enter your work email.\";\n  else if (!EMAIL_RE.test(values.email.trim())) errors.email = \"That doesn't look like an email address.\";\n  if (!values.password) errors.password = mode === \"sign-in\" ? \"Enter your password.\" : \"Choose a password.\";\n  else if (mode === \"sign-up\" && PASSWORD_RULES.some((rule) => !rule.test(values.password)))\n    errors.password = \"Your password needs every item on the list below.\";\n  return errors;\n}\n\nconst wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms));\n\n// Demo backend: a few addresses and passwords trigger the error states.\nasync function demoSubmit(payload: AppSignInPayload) {\n  await wait(900);\n  const email = payload.email.toLowerCase();\n  if (email.endsWith(\"@blocked.test\"))\n    throw new Error(\"This account is locked after too many attempts. Use a sign-in link, or try again in 15 minutes.\");\n  if (payload.mode === \"sign-up\" && email.endsWith(\"@taken.test\"))\n    throw new Error(\"An account with this email already exists.\");\n  if (payload.mode === \"sign-in\" && payload.password === \"wrong\")\n    throw new Error(\"That email and password don't match. Check for typos, or reset your password.\");\n}\n\nfunction firstName(name: string, email: string) {\n  const fromName = name.trim().split(/\\s+/)[0];\n  if (fromName) return fromName;\n  const local = email.split(\"@\")[0] ?? \"\";\n  return local ? local.charAt(0).toUpperCase() + local.slice(1).split(/[._-]/)[0] : \"there\";\n}\n\n/**\n * A complete sign-in and sign-up page: SSO, email and password with live validation, a passwordless link with\n * a 6-digit code fallback, and loading, error and success states. Lays out as a split screen when the container\n * is wide and a single column when it is narrow.\n */\nexport function AppSignIn({\n  theme = \"auto\",\n  mode: modeProp,\n  defaultMode = \"sign-in\",\n  onModeChange,\n  brand = APP_SIGN_IN_BRAND,\n  testimonial = APP_SIGN_IN_TESTIMONIAL,\n  providers = APP_SIGN_IN_PROVIDERS,\n  onSubmit,\n  onSso,\n  onMagicLink,\n  onVerifyCode,\n  onContinue,\n  showPanel = true,\n  resendSeconds = 30,\n  className,\n}: AppSignInProps) {\n  const { style } = useAppBlockTheme(theme);\n  const reduce = useBlockReducedMotion();\n  const { announce, region } = useAnnouncer();\n  const toast = useBlockToast();\n  const uid = useId();\n\n  const [innerMode, setInnerMode] = useState<AppSignInMode>(defaultMode);\n  const mode = modeProp ?? innerMode;\n  const [view, setView] = useState<View>(\"form\");\n  const [values, setValues] = useState({ name: \"\", email: \"\", password: \"\" });\n  const [touched, setTouched] = useState<Partial<Record<FieldName, boolean>>>({});\n  const [submitted, setSubmitted] = useState(false);\n  const [formError, setFormError] = useState<string | null>(null);\n  const [pending, setPending] = useState<null | \"submit\" | \"magic\" | AppSignInProvider>(null);\n  const [remember, setRemember] = useState(true);\n  const [showPassword, setShowPassword] = useState(false);\n  const [capsLock, setCapsLock] = useState(false);\n  const [passwordFocused, setPasswordFocused] = useState(false);\n  const [successName, setSuccessName] = useState(\"\");\n\n  const headingRef = useRef<HTMLHeadingElement>(null);\n  const nameRef = useRef<HTMLInputElement>(null);\n  const emailRef = useRef<HTMLInputElement>(null);\n  const passwordRef = useRef<HTMLInputElement>(null);\n  const bannerRef = useRef<HTMLDivElement>(null);\n  const moveFocusToHeading = useRef(false);\n\n  // After a view change, the new heading takes focus so screen readers land on the new context.\n  useLayoutEffect(() => {\n    if (!moveFocusToHeading.current) return;\n    moveFocusToHeading.current = false;\n    headingRef.current?.focus({ preventScroll: true });\n  }, [view]);\n\n  const goTo = useCallback((next: View) => {\n    moveFocusToHeading.current = true;\n    setView(next);\n  }, []);\n\n  const setMode = (next: AppSignInMode) => {\n    if (next === mode) return;\n    if (modeProp === undefined) setInnerMode(next);\n    onModeChange?.(next);\n    setFormError(null);\n    setSubmitted(false);\n    setTouched({});\n    announce(next === \"sign-in\" ? \"Sign in form\" : \"Create account form\");\n  };\n\n  const errors = validate(mode, values);\n  const visibleError = (field: FieldName) => (submitted || touched[field] ? errors[field] ?? null : null);\n\n  const update = (field: FieldName) => (event: React.ChangeEvent<HTMLInputElement>) => {\n    setValues((prev) => ({ ...prev, [field]: event.target.value }));\n    if (formError) setFormError(null);\n  };\n  const blur = (field: FieldName) => () => {\n    if (values[field]) setTouched((prev) => ({ ...prev, [field]: true }));\n  };\n\n  const busy = pending !== null;\n\n  const handleSubmit = async (event: FormEvent) => {\n    event.preventDefault();\n    if (busy) return;\n    setSubmitted(true);\n    const order: FieldName[] = mode === \"sign-up\" ? [\"name\", \"email\", \"password\"] : [\"email\", \"password\"];\n    const firstInvalid = order.find((field) => errors[field]);\n    if (firstInvalid) {\n      ({ name: nameRef, email: emailRef, password: passwordRef })[firstInvalid].current?.focus();\n      const count = order.filter((field) => errors[field]).length;\n      announce(`${count} ${count === 1 ? \"field needs\" : \"fields need\"} attention. ${errors[firstInvalid]}`);\n      return;\n    }\n    setFormError(null);\n    setPending(\"submit\");\n    const payload: AppSignInPayload = {\n      mode,\n      email: values.email.trim(),\n      password: values.password,\n      remember,\n      ...(mode === \"sign-up\" ? { name: values.name.trim() } : {}),\n    };\n    try {\n      await (onSubmit ? onSubmit(payload) : demoSubmit(payload));\n      setSuccessName(firstName(values.name, values.email));\n      goTo(\"success\");\n    } catch (error) {\n      const message = error instanceof Error ? error.message : \"Something went wrong. Try again.\";\n      setFormError(message);\n      announce(message);\n      window.requestAnimationFrame(() => bannerRef.current?.focus({ preventScroll: false }));\n    } finally {\n      setPending(null);\n    }\n  };\n\n  const handleSso = async (provider: AppSignInProvider) => {\n    if (busy) return;\n    setPending(provider);\n    setFormError(null);\n    try {\n      await (onSso ? onSso(provider) : wait(1100));\n      setSuccessName(values.name.trim().split(/\\s+/)[0] || \"Ada\");\n      goTo(\"success\");\n    } catch (error) {\n      const message = error instanceof Error ? error.message : `${PROVIDER_LABEL[provider]} sign-in was cancelled.`;\n      setFormError(message);\n      announce(message);\n    } finally {\n      setPending(null);\n    }\n  };\n\n  const handleMagic = async () => {\n    if (busy) return;\n    setTouched((prev) => ({ ...prev, email: true }));\n    if (errors.email) {\n      emailRef.current?.focus();\n      announce(errors.email);\n      return;\n    }\n    setPending(\"magic\");\n    setFormError(null);\n    try {\n      await (onMagicLink ? onMagicLink(values.email.trim()) : wait(800));\n      goTo(\"magic\");\n    } catch (error) {\n      const message = error instanceof Error ? error.message : \"We couldn't send the link. Try again.\";\n      setFormError(message);\n      announce(message);\n    } finally {\n      setPending(null);\n    }\n  };\n\n  const handleForgot = () => {\n    setTouched((prev) => ({ ...prev, email: true }));\n    if (errors.email) {\n      emailRef.current?.focus();\n      announce(`To reset your password, ${errors.email.charAt(0).toLowerCase()}${errors.email.slice(1)}`);\n      return;\n    }\n    toast.show(`Reset link sent to ${values.email.trim()}`, \"success\");\n  };\n\n  const reset = () => {\n    setValues({ name: \"\", email: \"\", password: \"\" });\n    setTouched({});\n    setSubmitted(false);\n    setFormError(null);\n    setShowPassword(false);\n    goTo(\"form\");\n  };\n\n  const checkCaps = (event: ReactKeyboardEvent<HTMLInputElement>) => {\n    setCapsLock(event.getModifierState?.(\"CapsLock\") ?? false);\n  };\n\n  const passed = PASSWORD_RULES.filter((rule) => rule.test(values.password)).length;\n  const strength = values.password ? STRENGTH[passed] : null;\n  const isSignUp = mode === \"sign-up\";\n  const headingId = `${uid}-heading`;\n  const tabIds = { \"sign-in\": `${uid}-tab-in`, \"sign-up\": `${uid}-tab-up` } as const;\n  const panelId = `${uid}-panel`;\n\n  const onTabKey = (event: ReactKeyboardEvent<HTMLButtonElement>) => {\n    if (![\"ArrowLeft\", \"ArrowRight\", \"Home\", \"End\"].includes(event.key)) return;\n    event.preventDefault();\n    const next: AppSignInMode =\n      event.key === \"Home\" ? \"sign-in\" : event.key === \"End\" ? \"sign-up\" : mode === \"sign-in\" ? \"sign-up\" : \"sign-in\";\n    setMode(next);\n    document.getElementById(tabIds[next])?.focus();\n  };\n\n  const viewMotion = reduce\n    ? { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.12 } }\n    : {\n        initial: { opacity: 0, y: 6, filter: \"blur(3px)\" },\n        animate: { opacity: 1, y: 0, filter: \"blur(0px)\" },\n        transition: { duration: 0.24, ease: [0.23, 1, 0.32, 1] as const },\n      };\n\n  return (\n    <div className={cn(blockRoot, \"h-full\", className)} style={style}>\n      <div className={cn(\"grid h-full min-h-0 w-full\", showPanel && \"@4xl:grid-cols-[minmax(0,1fr)_minmax(0,1.08fr)]\")}>\n        <main className=\"flex min-h-0 min-w-0 flex-col overflow-y-auto\" aria-labelledby={headingId}>\n          <header className=\"flex items-center justify-between gap-4 px-5 pt-5 @md:px-8 @md:pt-7\">\n            <a\n              href=\"#\"\n              onClick={(event) => event.preventDefault()}\n              className={cn(\"flex items-center gap-2.5 rounded-[8px]\", focusRing)}\n              aria-label={`${brand.name} home`}\n            >\n              <BrandMark />\n              <span className=\"text-[15px] font-semibold tracking-[-0.02em]\">{brand.name}</span>\n            </a>\n            {brand.supportHref && (\n              <a\n                href={brand.supportHref}\n                className={cn(\n                  \"rounded-[6px] text-[12.5px] text-[color:var(--bjork-text-muted)] transition-colors hover:text-[color:var(--bjork-text)]\",\n                  focusRing,\n                )}\n              >\n                Need help?\n              </a>\n            )}\n          </header>\n\n          <div className=\"flex flex-1 items-center justify-center px-5 py-8 @md:px-8 @md:py-10\">\n            <div className=\"w-full max-w-[384px]\">\n              {view === \"form\" && (\n                <motion.div key={`form-${mode}`} {...viewMotion}>\n                  <div\n                    role=\"tablist\"\n                    aria-label=\"Account\"\n                    className=\"relative mb-7 grid grid-cols-2 rounded-[11px] border border-[color:var(--bjork-border)] bg-[var(--bjork-field-inset)] p-[3px] shadow-[var(--bjork-shadow-inset)]\"\n                  >\n                    <span\n                      aria-hidden=\"true\"\n                      className={cn(\n                        \"absolute inset-y-[3px] left-[3px] w-[calc(50%-3px)] rounded-[8px] border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]\",\n                        !reduce && \"transition-transform duration-[240ms] ease-[cubic-bezier(0.23,1,0.32,1)]\",\n                        isSignUp ? \"translate-x-full\" : \"translate-x-0\",\n                      )}\n                    />\n                    {([\"sign-in\", \"sign-up\"] as const).map((tab) => (\n                      <button\n                        key={tab}\n                        id={tabIds[tab]}\n                        type=\"button\"\n                        role=\"tab\"\n                        aria-selected={mode === tab}\n                        aria-controls={panelId}\n                        tabIndex={mode === tab ? 0 : -1}\n                        onClick={() => setMode(tab)}\n                        onKeyDown={onTabKey}\n                        className={cn(\n                          \"relative z-10 h-8 cursor-pointer rounded-[8px] text-[13px] font-medium transition-colors duration-150\",\n                          focusRing,\n                          mode === tab\n                            ? \"text-[color:var(--bjork-text)]\"\n                            : \"text-[color:var(--bjork-text-muted)] hover:text-[color:var(--bjork-text-medium)]\",\n                        )}\n                      >\n                        {tab === \"sign-in\" ? \"Sign in\" : \"Create account\"}\n                      </button>\n                    ))}\n                  </div>\n\n                  <h1\n                    id={headingId}\n                    ref={headingRef}\n                    tabIndex={-1}\n                    className=\"font-bjork-display text-[28px] font-semibold leading-[1.1] tracking-[-0.035em] outline-none @md:text-[32px]\"\n                  >\n                    {isSignUp ? `Start with ${brand.name}` : \"Welcome back\"}\n                  </h1>\n                  <p className=\"mt-2 text-[13.5px] leading-[1.5] text-[color:var(--bjork-text-muted)]\">\n                    {isSignUp ? \"Free for up to 5 responders. No card required.\" : brand.tagline}\n                  </p>\n\n                  <div className=\"mt-7 grid grid-cols-3 gap-2\">\n                    {providers.map((provider) => (\n                      <BlockButton\n                        key={provider}\n                        variant=\"secondary\"\n                        size=\"lg\"\n                        onClick={() => handleSso(provider)}\n                        disabled={busy && pending !== provider}\n                        aria-busy={pending === provider || undefined}\n                        aria-label={`${isSignUp ? \"Sign up\" : \"Sign in\"} with ${PROVIDER_LABEL[provider]}`}\n                        className=\"h-11 min-w-0 px-2\"\n                      >\n                        {pending === provider ? <Spinner /> : <ProviderMark provider={provider} />}\n                        <span className=\"hidden truncate @sm:inline\">{PROVIDER_LABEL[provider]}</span>\n                      </BlockButton>\n                    ))}\n                  </div>\n\n                  <div className=\"my-6 flex items-center gap-3\" role=\"presentation\">\n                    <span className=\"h-px flex-1 bg-[var(--bjork-border)]\" />\n                    <span className=\"font-mono text-[10.5px] uppercase tracking-[0.12em] text-[color:var(--bjork-text-soft)]\">\n                      or with email\n                    </span>\n                    <span className=\"h-px flex-1 bg-[var(--bjork-border)]\" />\n                  </div>\n\n                  <form\n                    id={panelId}\n                    role=\"tabpanel\"\n                    aria-labelledby={tabIds[mode]}\n                    noValidate\n                    onSubmit={handleSubmit}\n                    aria-busy={pending === \"submit\" || undefined}\n                    className=\"flex flex-col gap-4\"\n                  >\n                    {formError && (\n                      <div\n                        ref={bannerRef}\n                        tabIndex={-1}\n                        role=\"alert\"\n                        className=\"flex gap-3 rounded-[12px] border border-[color:color-mix(in_oklab,var(--blk-error)_28%,transparent)] bg-[var(--blk-error-soft)] px-3.5 py-3 text-[12.5px] leading-[1.5] text-[color:var(--bjork-text-strong)] outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--blk-error)]\"\n                      >\n                        <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"mt-[2px] size-3.5 shrink-0 text-[color:var(--blk-error)]\">\n                          <path d=\"M8 1.75 15 14H1L8 1.75Z\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinejoin=\"round\" />\n                          <path d=\"M8 6.25v3.5M8 11.6v.1\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n                        </svg>\n                        <div className=\"min-w-0\">\n                          <p>{formError}</p>\n                          {formError.startsWith(\"An account with this email\") && (\n                            <button\n                              type=\"button\"\n                              onClick={() => setMode(\"sign-in\")}\n                              className={cn(\"mt-1 cursor-pointer rounded-[4px] font-medium text-[color:var(--blk-accent-ink)] underline-offset-2 hover:underline\", focusRing)}\n                            >\n                              Sign in instead\n                            </button>\n                          )}\n                        </div>\n                      </div>\n                    )}\n\n                    {isSignUp && (\n                      <BlockField\n                        ref={nameRef}\n                        label=\"Full name\"\n                        name=\"name\"\n                        autoComplete=\"name\"\n                        placeholder=\"Ada Lindqvist\"\n                        value={values.name}\n                        onChange={update(\"name\")}\n                        onBlur={blur(\"name\")}\n                        readOnly={busy}\n                        error={visibleError(\"name\")}\n                      />\n                    )}\n\n                    <BlockField\n                      ref={emailRef}\n                      label=\"Work email\"\n                      name=\"email\"\n                      type=\"email\"\n                      inputMode=\"email\"\n                      autoComplete=\"email\"\n                      spellCheck={false}\n                      placeholder=\"ada@halden.co\"\n                      value={values.email}\n                      onChange={update(\"email\")}\n                      onBlur={blur(\"email\")}\n                      readOnly={busy}\n                      error={visibleError(\"email\")}\n                    />\n\n                    <div className=\"flex flex-col gap-2\">\n                      <BlockField\n                        ref={passwordRef}\n                        label=\"Password\"\n                        name=\"password\"\n                        type={showPassword ? \"text\" : \"password\"}\n                        autoComplete={isSignUp ? \"new-password\" : \"current-password\"}\n                        placeholder={isSignUp ? \"10+ characters\" : \"Your password\"}\n                        value={values.password}\n                        onChange={update(\"password\")}\n                        onBlur={() => {\n                          blur(\"password\")();\n                          setPasswordFocused(false);\n                          setCapsLock(false);\n                        }}\n                        onFocus={() => setPasswordFocused(true)}\n                        onKeyDown={checkCaps}\n                        onKeyUp={checkCaps}\n                        readOnly={busy}\n                        error={visibleError(\"password\")}\n                        hint={\n                          capsLock && passwordFocused ? (\n                            <span className=\"flex items-center gap-1.5 text-[color:var(--blk-warning)]\">\n                              <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"size-3 shrink-0\">\n                                <path d=\"M8 2.5 13.5 8H10.5v3h-5V8H2.5L8 2.5ZM5.5 13.5h5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinejoin=\"round\" strokeLinecap=\"round\" />\n                              </svg>\n                              Caps Lock is on\n                            </span>\n                          ) : undefined\n                        }\n                        labelAside={\n                          !isSignUp ? (\n                            <button\n                              type=\"button\"\n                              onClick={handleForgot}\n                              className={cn(\n                                \"cursor-pointer rounded-[4px] text-[12px] text-[color:var(--bjork-text-muted)] transition-colors hover:text-[color:var(--blk-accent-ink)]\",\n                                focusRing,\n                              )}\n                            >\n                              Forgot password?\n                            </button>\n                          ) : undefined\n                        }\n                        trailing={\n                          <IconButton\n                            size=\"sm\"\n                            label={showPassword ? \"Hide password\" : \"Show password\"}\n                            pressed={showPassword}\n                            onClick={() => setShowPassword((v) => !v)}\n                            className=\"aria-pressed:text-[color:var(--bjork-text)]\"\n                          >\n                            <EyeIcon open={showPassword} />\n                          </IconButton>\n                        }\n                      />\n\n                      {isSignUp && (values.password || passwordFocused) && (\n                        <PasswordMeter value={values.password} passed={passed} strength={strength} reduce={reduce} />\n                      )}\n                    </div>\n\n                    {!isSignUp && <RememberMe checked={remember} onChange={setRemember} />}\n\n                    <BlockButton\n                      type=\"submit\"\n                      variant=\"primary\"\n                      size=\"lg\"\n                      className=\"mt-1 w-full\"\n                      disabled={busy && pending !== \"submit\"}\n                      aria-disabled={pending === \"submit\" || undefined}\n                    >\n                      {pending === \"submit\" ? (\n                        <>\n                          <Spinner />\n                          {isSignUp ? \"Creating account\" : \"Signing in\"}\n                        </>\n                      ) : isSignUp ? (\n                        \"Create account\"\n                      ) : (\n                        \"Sign in\"\n                      )}\n                    </BlockButton>\n\n                    {!isSignUp && (\n                      <BlockButton\n                        variant=\"ghost\"\n                        size=\"md\"\n                        onClick={handleMagic}\n                        disabled={busy && pending !== \"magic\"}\n                        className=\"w-full text-[color:var(--bjork-text-medium)]\"\n                      >\n                        {pending === \"magic\" ? <Spinner /> : <MailIcon />}\n                        Email me a sign-in link instead\n                      </BlockButton>\n                    )}\n\n                    {isSignUp && (\n                      <p className=\"text-center text-[11.5px] leading-[1.55] text-[color:var(--bjork-text-soft)]\">\n                        By creating an account you agree to the{\" \"}\n                        <a href={brand.termsHref ?? \"#\"} className={cn(\"rounded-[3px] text-[color:var(--bjork-text-medium)] underline decoration-[color:var(--bjork-border-strong)] underline-offset-2 hover:text-[color:var(--bjork-text)]\", focusRing)}>\n                          Terms\n                        </a>{\" \"}\n                        and{\" \"}\n                        <a href={brand.privacyHref ?? \"#\"} className={cn(\"rounded-[3px] text-[color:var(--bjork-text-medium)] underline decoration-[color:var(--bjork-border-strong)] underline-offset-2 hover:text-[color:var(--bjork-text)]\", focusRing)}>\n                          Privacy Policy\n                        </a>\n                        .\n                      </p>\n                    )}\n                  </form>\n                </motion.div>\n              )}\n\n              {view === \"magic\" && (\n                <motion.div key=\"magic\" {...viewMotion}>\n                  <MagicLinkView\n                    headingId={headingId}\n                    headingRef={headingRef}\n                    email={values.email.trim()}\n                    resendSeconds={resendSeconds}\n                    onBack={() => goTo(\"form\")}\n                    onResend={async () => {\n                      await (onMagicLink ? onMagicLink(values.email.trim()) : wait(500));\n                      toast.show(`New link sent to ${values.email.trim()}`, \"success\");\n                    }}\n                    onVerify={async (code) => {\n                      if (onVerifyCode) await onVerifyCode(values.email.trim(), code);\n                      else {\n                        await wait(700);\n                        if (code === \"000000\") throw new Error(\"That code has expired. Request a new link.\");\n                      }\n                      setSuccessName(firstName(values.name, values.email));\n                      goTo(\"success\");\n                    }}\n                    announce={announce}\n                  />\n                </motion.div>\n              )}\n\n              {view === \"success\" && (\n                <motion.div key=\"success\" {...viewMotion}>\n                  <SuccessView\n                    headingId={headingId}\n                    headingRef={headingRef}\n                    name={successName}\n                    email={values.email.trim()}\n                    created={isSignUp}\n                    brand={brand.name}\n                    reduce={reduce}\n                    onContinue={onContinue ?? (() => toast.show(\"Opening your workspace\"))}\n                    onSignOut={reset}\n                  />\n                </motion.div>\n              )}\n            </div>\n          </div>\n\n          <footer className=\"flex flex-wrap items-center justify-between gap-x-4 gap-y-1 px-5 pb-5 text-[11.5px] text-[color:var(--bjork-text-soft)] @md:px-8 @md:pb-7\">\n            <span className=\"tabular-nums\">© 2026 {brand.name} Labs</span>\n            <span className=\"flex items-center gap-1.5\">\n              <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"size-3\">\n                <rect x=\"3\" y=\"7\" width=\"10\" height=\"7\" rx=\"1.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" />\n                <path d=\"M5.5 7V5a2.5 2.5 0 0 1 5 0v2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" />\n              </svg>\n              SOC 2 Type II · SSO with SAML on Business\n            </span>\n          </footer>\n        </main>\n\n        {showPanel && <BrandPanel brand={brand} testimonial={testimonial} reduce={reduce} />}\n      </div>\n      {region}\n      {toast.node}\n    </div>\n  );\n}\n\nfunction PasswordMeter({\n  value,\n  passed,\n  strength,\n  reduce,\n}: {\n  value: string;\n  passed: number;\n  strength: (typeof STRENGTH)[number] | null;\n  reduce: boolean;\n}) {\n  return (\n    <div className=\"rounded-[11px] border border-[color:var(--bjork-border)] bg-[var(--bjork-field-muted)] px-3 py-2.5\">\n      <div className=\"flex items-center gap-3\">\n        <div className=\"grid flex-1 grid-cols-4 gap-1\" aria-hidden=\"true\">\n          {[0, 1, 2, 3].map((i) => (\n            <span key={i} className=\"h-1 overflow-hidden rounded-full bg-[var(--bjork-border-strong)]\">\n              <span\n                className={cn(\n                  \"block h-full origin-left rounded-full\",\n                  !reduce && \"transition-[transform,background-color] duration-200 ease-out\",\n                )}\n                style={{\n                  background: strength?.color ?? \"transparent\",\n                  transform: `scaleX(${value && i < passed ? 1 : 0})`,\n                }}\n              />\n            </span>\n          ))}\n        </div>\n        <span className=\"w-[52px] text-right text-[11.5px] font-medium text-[color:var(--bjork-text-medium)]\" aria-live=\"polite\">\n          {strength ? strength.label : \"\"}\n        </span>\n      </div>\n      <ul className=\"mt-2.5 grid grid-cols-1 gap-x-3 gap-y-1 @md:grid-cols-2\" aria-label=\"Password requirements\">\n        {PASSWORD_RULES.map((rule) => {\n          const ok = rule.test(value);\n          return (\n            <li\n              key={rule.id}\n              className={cn(\n                \"flex items-center gap-1.5 text-[11.5px] transition-colors duration-150\",\n                ok ? \"text-[color:var(--bjork-text-medium)]\" : \"text-[color:var(--bjork-text-soft)]\",\n              )}\n            >\n              <svg viewBox=\"0 0 12 12\" aria-hidden=\"true\" className=\"size-3 shrink-0\">\n                {ok ? (\n                  <path d=\"M2.5 6.2 5 8.5l4.5-5\" fill=\"none\" stroke=\"var(--blk-success)\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n                ) : (\n                  <circle cx=\"6\" cy=\"6\" r=\"1.6\" fill=\"currentColor\" />\n                )}\n              </svg>\n              {rule.label}\n              <span className=\"sr-only\">{ok ? \", met\" : \", not met\"}</span>\n            </li>\n          );\n        })}\n      </ul>\n    </div>\n  );\n}\n\nfunction RememberMe({ checked, onChange }: { checked: boolean; onChange: (value: boolean) => void }) {\n  return (\n    <label className=\"group flex w-fit cursor-pointer select-none items-center gap-2.5 text-[12.5px] text-[color:var(--bjork-text-medium)]\">\n      <input type=\"checkbox\" className=\"peer sr-only\" checked={checked} onChange={(event) => onChange(event.target.checked)} />\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"grid size-[18px] place-items-center rounded-[6px] border transition-[background-color,border-color] duration-150\",\n          \"peer-focus-visible:ring-2 peer-focus-visible:ring-[color:var(--bjork-accent)] peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-[color:var(--bjork-ring-offset)]\",\n          checked\n            ? \"border-transparent bg-[var(--blk-accent-fill)] text-[color:var(--blk-accent-fill-ink)]\"\n            : \"border-[color:var(--bjork-border-strong)] bg-[var(--bjork-field)] group-hover:border-[color:var(--bjork-text-faint)]\",\n        )}\n      >\n        <svg viewBox=\"0 0 12 12\" className={cn(\"size-3 transition-opacity duration-150\", checked ? \"opacity-100\" : \"opacity-0\")}>\n          <path d=\"M2.5 6.2 5 8.5l4.5-5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n      </span>\n      Keep me signed in for 30 days\n    </label>\n  );\n}\n\nconst CODE_LENGTH = 6;\n\nfunction MagicLinkView({\n  headingId,\n  headingRef,\n  email,\n  resendSeconds,\n  onBack,\n  onResend,\n  onVerify,\n  announce,\n}: {\n  headingId: string;\n  headingRef: React.RefObject<HTMLHeadingElement | null>;\n  email: string;\n  resendSeconds: number;\n  onBack: () => void;\n  onResend: () => Promise<void>;\n  onVerify: (code: string) => Promise<void>;\n  announce: (message: string) => void;\n}) {\n  const [code, setCode] = useState(\"\");\n  const [error, setError] = useState<string | null>(null);\n  const [verifying, setVerifying] = useState(false);\n  const [resending, setResending] = useState(false);\n  const [remaining, setRemaining] = useState(resendSeconds);\n  const [codeFocused, setCodeFocused] = useState(false);\n  const codeId = useId();\n\n  useEffect(() => {\n    if (remaining <= 0) return;\n    const id = window.setTimeout(() => setRemaining((s) => s - 1), 1000);\n    return () => window.clearTimeout(id);\n  }, [remaining]);\n\n  const verify = async (value: string) => {\n    if (value.length !== CODE_LENGTH || verifying) return;\n    setVerifying(true);\n    setError(null);\n    try {\n      await onVerify(value);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"That code didn't work.\";\n      setError(message);\n      announce(message);\n      setCode(\"\");\n    } finally {\n      setVerifying(false);\n    }\n  };\n\n  const resend = async () => {\n    setResending(true);\n    try {\n      await onResend();\n      setRemaining(resendSeconds);\n      setError(null);\n    } finally {\n      setResending(false);\n    }\n  };\n\n  const mm = Math.floor(remaining / 60);\n  const ss = String(remaining % 60).padStart(2, \"0\");\n\n  return (\n    <div>\n      <button\n        type=\"button\"\n        onClick={onBack}\n        className={cn(\n          \"-ml-1 mb-8 inline-flex cursor-pointer items-center gap-1.5 rounded-[7px] px-1 py-0.5 text-[12.5px] text-[color:var(--bjork-text-muted)] transition-colors hover:text-[color:var(--bjork-text)]\",\n          focusRing,\n        )}\n      >\n        <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"size-3.5\">\n          <path d=\"M10 3.5 5.5 8l4.5 4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n        Use a different email\n      </button>\n\n      <EnvelopeArt />\n\n      <h1\n        id={headingId}\n        ref={headingRef}\n        tabIndex={-1}\n        className=\"mt-6 font-bjork-display text-[28px] font-semibold leading-[1.1] tracking-[-0.035em] outline-none @md:text-[32px]\"\n      >\n        Check your inbox\n      </h1>\n      <p className=\"mt-2 text-[13.5px] leading-[1.55] text-[color:var(--bjork-text-muted)]\">\n        We sent a sign-in link to{\" \"}\n        <span className=\"font-medium text-[color:var(--bjork-text)] [overflow-wrap:anywhere]\">{email}</span>. It expires in 15 minutes.\n        Or enter the 6-digit code from the same email.\n      </p>\n\n      <form\n        className=\"mt-7\"\n        noValidate\n        onSubmit={(event) => {\n          event.preventDefault();\n          void verify(code);\n        }}\n      >\n        <label htmlFor={codeId} className=\"text-[12.5px] font-medium text-[color:var(--bjork-text-medium)]\">\n          Verification code\n        </label>\n        <div className=\"relative mt-1.5\">\n          <input\n            id={codeId}\n            value={code}\n            onChange={(event) => {\n              const next = event.target.value.replace(/\\D/g, \"\").slice(0, CODE_LENGTH);\n              setCode(next);\n              setError(null);\n              if (next.length === CODE_LENGTH) void verify(next);\n            }}\n            onFocus={() => setCodeFocused(true)}\n            onBlur={() => setCodeFocused(false)}\n            inputMode=\"numeric\"\n            autoComplete=\"one-time-code\"\n            pattern=\"\\d{6}\"\n            maxLength={CODE_LENGTH}\n            readOnly={verifying}\n            aria-invalid={error ? true : undefined}\n            aria-describedby={error ? `${codeId}-error` : `${codeId}-hint`}\n            className=\"absolute inset-0 z-10 w-full cursor-text bg-transparent font-mono text-transparent caret-transparent opacity-0 outline-none selection:bg-transparent\"\n          />\n          <div className=\"grid grid-cols-6 gap-2\" aria-hidden=\"true\">\n            {Array.from({ length: CODE_LENGTH }).map((_, i) => {\n              const char = code[i];\n              const active = codeFocused && (i === code.length || (i === CODE_LENGTH - 1 && code.length === CODE_LENGTH));\n              return (\n                <span\n                  key={i}\n                  className={cn(\n                    \"relative grid h-12 place-items-center rounded-[11px] border bg-[var(--bjork-field)] font-mono text-[20px] font-medium tabular-nums shadow-[var(--bjork-shadow-soft)] transition-[border-color,box-shadow] duration-150\",\n                    error\n                      ? \"border-[color:var(--blk-error)]\"\n                      : active\n                        ? \"border-[color:var(--bjork-accent)] ring-[3px] ring-[color:var(--bjork-accent-soft)]\"\n                        : \"border-[color:var(--bjork-border)]\",\n                    i === 2 && \"mr-1.5\",\n                    i === 3 && \"ml-1.5\",\n                  )}\n                >\n                  {char ?? \"\"}\n                  {active && !char && (\n                    <span className=\"absolute h-5 w-px animate-pulse bg-[var(--bjork-accent)] motion-reduce:animate-none\" />\n                  )}\n                </span>\n              );\n            })}\n          </div>\n        </div>\n        {error ? (\n          <p id={`${codeId}-error`} role=\"alert\" className=\"mt-2 text-[12px] text-[color:var(--blk-error)]\">\n            {error}\n          </p>\n        ) : (\n          <p id={`${codeId}-hint`} className=\"mt-2 text-[12px] text-[color:var(--bjork-text-soft)]\">\n            Paste it in, we&apos;ll check it as soon as all six digits are there.\n          </p>\n        )}\n\n        <BlockButton type=\"submit\" variant=\"primary\" size=\"lg\" className=\"mt-5 w-full\" disabled={code.length !== CODE_LENGTH && !verifying}>\n          {verifying ? (\n            <>\n              <Spinner /> Verifying\n            </>\n          ) : (\n            \"Verify and sign in\"\n          )}\n        </BlockButton>\n      </form>\n\n      <div className=\"mt-5 flex items-center justify-between gap-3 border-t border-[color:var(--bjork-border)] pt-4 text-[12.5px]\">\n        <span className=\"text-[color:var(--bjork-text-soft)]\">Nothing yet? Check spam.</span>\n        <button\n          type=\"button\"\n          onClick={resend}\n          disabled={remaining > 0 || resending}\n          className={cn(\n            \"cursor-pointer rounded-[6px] font-medium tabular-nums text-[color:var(--blk-accent-ink)] transition-colors hover:underline disabled:cursor-default disabled:text-[color:var(--bjork-text-muted)] disabled:no-underline\",\n            focusRing,\n          )}\n        >\n          {resending ? \"Sending…\" : remaining > 0 ? `Resend in ${mm}:${ss}` : \"Resend link\"}\n        </button>\n      </div>\n    </div>\n  );\n}\n\nfunction SuccessView({\n  headingId,\n  headingRef,\n  name,\n  email,\n  created,\n  brand,\n  reduce,\n  onContinue,\n  onSignOut,\n}: {\n  headingId: string;\n  headingRef: React.RefObject<HTMLHeadingElement | null>;\n  name: string;\n  email: string;\n  created: boolean;\n  brand: string;\n  reduce: boolean;\n  onContinue: () => void;\n  onSignOut: () => void;\n}) {\n  return (\n    <div className=\"flex flex-col items-start\">\n      <span className=\"relative grid size-14 place-items-center rounded-[16px] border border-[color:color-mix(in_oklab,var(--blk-success)_30%,transparent)] bg-[var(--blk-success-soft)]\">\n        <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" className=\"size-6\">\n          <motion.path\n            d=\"M5.5 12.5 10 17l8.5-9.5\"\n            fill=\"none\"\n            stroke=\"var(--blk-success)\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            initial={reduce ? false : { pathLength: 0 }}\n            animate={{ pathLength: 1 }}\n            transition={{ duration: 0.36, delay: 0.08, ease: [0.23, 1, 0.32, 1] }}\n          />\n        </svg>\n      </span>\n      <h1\n        id={headingId}\n        ref={headingRef}\n        tabIndex={-1}\n        className=\"mt-6 font-bjork-display text-[28px] font-semibold leading-[1.1] tracking-[-0.035em] outline-none @md:text-[32px]\"\n      >\n        {created ? `You're in, ${name}` : `Welcome back, ${name}`}\n      </h1>\n      <p className=\"mt-2 text-[13.5px] leading-[1.55] text-[color:var(--bjork-text-muted)]\">\n        {created\n          ? `Your ${brand} workspace is ready. We'll walk you through connecting your first service.`\n          : \"You're signed in. Pick up where you left off.\"}\n      </p>\n      <div className=\"mt-6 flex w-full items-center gap-3 rounded-[12px] border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] px-3 py-2.5 shadow-[var(--bjork-shadow-soft)]\">\n        <InitialsAvatar name={name === \"there\" ? \"Kestrel User\" : name} size={30} />\n        <div className=\"min-w-0 flex-1\">\n          <p className=\"truncate text-[13px] font-medium\">{name}</p>\n          <p className=\"truncate text-[12px] text-[color:var(--bjork-text-muted)]\">{email || \"ada@halden.co\"}</p>\n        </div>\n        <span className=\"rounded-full bg-[var(--blk-success-soft)] px-2 py-0.5 text-[11px] font-medium text-[color:var(--blk-success)]\">\n          Verified\n        </span>\n      </div>\n      <BlockButton variant=\"primary\" size=\"lg\" className=\"mt-6 w-full\" onClick={onContinue}>\n        {created ? \"Set up workspace\" : \"Continue to dashboard\"}\n        <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n          <path d=\"M3.5 8h9M8.5 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n      </BlockButton>\n      <BlockButton variant=\"ghost\" size=\"md\" className=\"mt-2 w-full\" onClick={onSignOut}>\n        Not you? Sign out\n      </BlockButton>\n    </div>\n  );\n}\n\nfunction BrandPanel({\n  brand,\n  testimonial,\n  reduce,\n}: {\n  brand: AppSignInBrand;\n  testimonial: AppSignInTestimonial;\n  reduce: boolean;\n}) {\n  return (\n    <aside aria-label={`About ${brand.name}`} className=\"hidden min-h-0 p-3 @4xl:flex\">\n      <div className=\"relative flex w-full flex-col justify-between overflow-hidden rounded-[22px] border border-[color:var(--bjork-border)] bg-[var(--bjork-panel)] p-9 shadow-[var(--bjork-shadow-panel)] @6xl:p-11\">\n        {/* Dot grid, faded toward the edges, and a warm glow behind the product card. */}\n        <div\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0 opacity-70\"\n          style={{\n            backgroundImage: \"radial-gradient(var(--bjork-border-strong) 1px, transparent 1.2px)\",\n            backgroundSize: \"18px 18px\",\n            maskImage: \"radial-gradient(ellipse 80% 70% at 60% 45%, #000 20%, transparent 80%)\",\n            WebkitMaskImage: \"radial-gradient(ellipse 80% 70% at 60% 45%, #000 20%, transparent 80%)\",\n          }}\n        />\n        <div\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute -right-24 top-16 size-[420px] rounded-full opacity-80 blur-3xl\"\n          style={{ background: \"radial-gradient(circle, var(--bjork-accent-muted), transparent 65%)\" }}\n        />\n\n        <div className=\"relative flex items-center gap-2 self-start rounded-full border border-[color:var(--bjork-border)] bg-[var(--bjork-surface-muted)] py-1 pl-2 pr-3 text-[11.5px] text-[color:var(--bjork-text-medium)] backdrop-blur\">\n          <span className=\"relative grid size-2 place-items-center\">\n            {!reduce && <span className=\"absolute size-2 animate-ping rounded-full bg-[var(--blk-success)] opacity-40\" />}\n            <span className=\"size-1.5 rounded-full bg-[var(--blk-success)]\" />\n          </span>\n          All systems normal\n          <span className=\"text-[color:var(--bjork-text-faint)]\">·</span>\n          <span className=\"tabular-nums\">99.98% uptime this quarter</span>\n        </div>\n\n        <ProductPreview reduce={reduce} />\n\n        <figure className=\"relative max-w-[460px]\">\n          <svg viewBox=\"0 0 24 18\" aria-hidden=\"true\" className=\"mb-4 h-4 w-5 text-[color:var(--bjork-accent)]\">\n            <path\n              d=\"M0 18V10.5C0 4.6 3.1 1.1 9.2 0l1 2.6C6.6 3.6 5 5.7 4.9 8.6H9.6V18H0Zm14.4 0V10.5C14.4 4.6 17.5 1.1 23.6 0l1 2.6c-3.6 1-5.2 3.1-5.3 6h4.7V18h-9.6Z\"\n              fill=\"currentColor\"\n            />\n          </svg>\n          <blockquote className=\"font-bjork-display text-[20px] font-medium leading-[1.35] tracking-[-0.02em] text-[color:var(--bjork-text-strong)] @6xl:text-[22px]\">\n            {testimonial.quote}\n          </blockquote>\n          <figcaption className=\"mt-5 flex items-center gap-3\">\n            <InitialsAvatar name={testimonial.author} size={34} />\n            <span className=\"flex flex-col\">\n              <span className=\"text-[13px] font-medium\">{testimonial.author}</span>\n              <span className=\"text-[12px] text-[color:var(--bjork-text-muted)]\">{testimonial.role}</span>\n            </span>\n          </figcaption>\n        </figure>\n      </div>\n    </aside>\n  );\n}\n\n// p95 latency over 14 days, ms. The dip after day 8 is the rollout the testimonial is about.\nconst LATENCY = [238, 244, 231, 252, 247, 259, 241, 236, 214, 198, 191, 186, 179, 182];\nconst LATENCY_PREV = [242, 236, 248, 240, 255, 246, 251, 244, 249, 238, 246, 241, 250, 243];\n\nfunction ProductPreview({ reduce }: { reduce: boolean }) {\n  const w = 300;\n  const h = 92;\n  const min = 160;\n  const max = 270;\n  const x = (i: number) => (i / (LATENCY.length - 1)) * w;\n  const y = (v: number) => h - ((v - min) / (max - min)) * h;\n  const line = LATENCY.map((v, i) => `${i ? \"L\" : \"M\"}${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(\"\");\n  const prev = LATENCY_PREV.map((v, i) => `${i ? \"L\" : \"M\"}${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(\"\");\n  const area = `${line}L${w},${h}L0,${h}Z`;\n  const lastX = x(LATENCY.length - 1);\n  const lastY = y(LATENCY[LATENCY.length - 1]);\n\n  const timeline = [\n    { time: \"14:02\", label: \"Alert: checkout p95 > 400 ms\", tone: \"var(--blk-error)\" },\n    { time: \"14:04\", label: \"Maren acknowledged · paged payments\", tone: \"var(--blk-warning)\" },\n    { time: \"14:19\", label: \"Rolled back api@4.18.2\", tone: \"var(--bjork-accent)\" },\n    { time: \"14:26\", label: \"Resolved · postmortem drafted\", tone: \"var(--blk-success)\" },\n  ];\n\n  return (\n    <div aria-hidden=\"true\" className=\"relative my-8 flex flex-col items-start\">\n      <div className=\"w-[min(100%,380px)] rounded-[16px] border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] p-4 shadow-[var(--bjork-shadow-menu)]\">\n        <div className=\"flex items-start justify-between gap-3\">\n          <div>\n            <p className=\"font-mono text-[10px] uppercase tracking-[0.12em] text-[color:var(--bjork-text-soft)]\">checkout · p95 latency</p>\n            <p className=\"mt-1 flex items-baseline gap-2\">\n              <span className=\"text-[26px] font-semibold tabular-nums tracking-[-0.03em]\">182</span>\n              <span className=\"text-[12px] text-[color:var(--bjork-text-muted)]\">ms</span>\n              <span className=\"ml-1 rounded-full bg-[var(--blk-success-soft)] px-1.5 py-0.5 text-[11px] font-medium tabular-nums text-[color:var(--blk-success)]\">\n                −24%\n              </span>\n            </p>\n          </div>\n          <div className=\"flex items-center gap-3 pt-0.5 text-[11px] text-[color:var(--bjork-text-muted)]\">\n            <span className=\"flex items-center gap-1.5\">\n              <span className=\"h-[2px] w-3 rounded-full bg-[var(--bjork-accent)]\" />\n              14d\n            </span>\n            <span className=\"flex items-center gap-1.5\">\n              <span className=\"h-0 w-3 border-t border-dashed border-[color:var(--blk-chart-ghost)]\" />\n              prior\n            </span>\n          </div>\n        </div>\n        <svg viewBox={`-4 -6 ${w + 8} ${h + 12}`} className=\"mt-3 h-[96px] w-full overflow-visible\">\n          <defs>\n            <linearGradient id=\"app-sign-in-area\" x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\">\n              <stop offset=\"0%\" stopColor=\"var(--bjork-accent)\" stopOpacity=\"0.22\" />\n              <stop offset=\"100%\" stopColor=\"var(--bjork-accent)\" stopOpacity=\"0\" />\n            </linearGradient>\n          </defs>\n          {[0.25, 0.5, 0.75].map((t) => (\n            <line key={t} x1=\"0\" x2={w} y1={h * t} y2={h * t} stroke=\"var(--bjork-border)\" strokeWidth=\"1\" />\n          ))}\n          <path d={prev} fill=\"none\" stroke=\"var(--blk-chart-ghost)\" strokeWidth=\"1.25\" strokeDasharray=\"3 3\" />\n          <path d={area} fill=\"url(#app-sign-in-area)\" />\n          <motion.path\n            d={line}\n            fill=\"none\"\n            stroke=\"var(--bjork-accent)\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            initial={reduce ? false : { pathLength: 0 }}\n            animate={{ pathLength: 1 }}\n            transition={{ duration: 1.1, ease: [0.23, 1, 0.32, 1], delay: 0.15 }}\n          />\n          <circle cx={lastX} cy={lastY} r=\"4\" fill=\"var(--bjork-surface)\" stroke=\"var(--bjork-accent)\" strokeWidth=\"2\" />\n        </svg>\n      </div>\n\n      <div className=\"-mt-5 ml-[18%] w-[min(82%,340px)] rounded-[14px] border border-[color:var(--bjork-border)] bg-[var(--bjork-menu)] p-3.5 shadow-[var(--bjork-shadow-menu)] backdrop-blur-xl\">\n        <div className=\"mb-2.5 flex items-center justify-between\">\n          <p className=\"text-[12px] font-medium\">INC-2291 · Checkout latency</p>\n          <span className=\"rounded-full bg-[var(--blk-success-soft)] px-1.5 py-0.5 text-[11px] font-medium text-[color:var(--blk-success)]\">\n            Resolved\n          </span>\n        </div>\n        <ol className=\"relative\">\n          {timeline.map((entry, i) => (\n            <li key={entry.time} className=\"relative flex items-center gap-2.5 py-[5px] text-[11.5px]\">\n              {i < timeline.length - 1 && (\n                <span className=\"absolute left-[3.5px] top-[17px] h-[calc(100%-10px)] w-px bg-[var(--bjork-border-strong)]\" />\n              )}\n              <span className=\"relative size-2 shrink-0 rounded-full\" style={{ background: entry.tone }} />\n              <span className=\"w-[38px] shrink-0 font-mono text-[11px] tabular-nums text-[color:var(--bjork-text-soft)]\">{entry.time}</span>\n              <span className=\"truncate text-[color:var(--bjork-text-medium)]\">{entry.label}</span>\n            </li>\n          ))}\n        </ol>\n      </div>\n    </div>\n  );\n}\n\nfunction BrandMark() {\n  return (\n    <span className=\"grid size-7 place-items-center rounded-[8px] bg-[var(--blk-accent-fill)] shadow-[inset_0_1px_0_rgba(255,255,255,0.25),0_1px_2px_rgba(0,0,0,0.2)]\">\n      <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"size-4 text-[color:var(--blk-accent-fill-ink)]\">\n        <path d=\"M2.5 11.5 8 3l2.2 3.6L13.5 5 10 12.5 7.6 9.4 2.5 11.5Z\" fill=\"currentColor\" />\n      </svg>\n    </span>\n  );\n}\n\nfunction ProviderMark({ provider }: { provider: AppSignInProvider }) {\n  if (provider === \"google\") {\n    return (\n      <svg viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n        <path d=\"M17.6 9.2c0-.6-.1-1.2-.2-1.8H9v3.4h4.8a4.1 4.1 0 0 1-1.8 2.7v2.2h2.9c1.7-1.6 2.7-3.9 2.7-6.5Z\" fill=\"#4285F4\" />\n        <path d=\"M9 18c2.4 0 4.5-.8 5.9-2.2L12 13.6c-.8.5-1.8.9-3 .9-2.3 0-4.3-1.6-5-3.7H1v2.3A9 9 0 0 0 9 18Z\" fill=\"#34A853\" />\n        <path d=\"M4 10.8a5.4 5.4 0 0 1 0-3.5V5H1a9 9 0 0 0 0 8.1l3-2.3Z\" fill=\"#FBBC05\" />\n        <path d=\"M9 3.6c1.3 0 2.5.5 3.4 1.3L15 2.4A9 9 0 0 0 1 5l3 2.3c.7-2.1 2.7-3.7 5-3.7Z\" fill=\"#EA4335\" />\n      </svg>\n    );\n  }\n  if (provider === \"github\") {\n    return (\n      <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"text-[color:var(--bjork-text)]\">\n        <path\n          fill=\"currentColor\"\n          d=\"M8 .2a8 8 0 0 0-2.5 15.6c.4 0 .5-.2.5-.4v-1.5c-2.2.5-2.7-1-2.7-1-.4-.9-.9-1.2-.9-1.2-.7-.5.1-.5.1-.5.8.1 1.2.8 1.2.8.7 1.3 1.9.9 2.3.7.1-.5.3-.9.5-1.1-1.8-.2-3.6-.9-3.6-4 0-.9.3-1.6.8-2.1-.1-.2-.4-1 .1-2.1 0 0 .7-.2 2.2.8a7.6 7.6 0 0 1 4 0c1.5-1 2.2-.8 2.2-.8.4 1.1.2 1.9.1 2.1.5.6.8 1.3.8 2.1 0 3.1-1.9 3.8-3.6 4 .3.3.6.8.6 1.5v2.2c0 .2.1.5.6.4A8 8 0 0 0 8 .2Z\"\n        />\n      </svg>\n    );\n  }\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"text-[color:var(--bjork-text)]\">\n      <path\n        fill=\"currentColor\"\n        d=\"M11.2 8.5c0-1.6 1.3-2.4 1.4-2.4-.8-1.1-2-1.3-2.4-1.3-1-.1-2 .6-2.5.6s-1.3-.6-2.2-.6C4.4 4.8 3.3 5.5 2.7 6.6c-1.2 2.1-.3 5.2.9 6.9.6.8 1.2 1.7 2.1 1.7.8 0 1.2-.5 2.2-.5s1.3.5 2.2.5 1.5-.8 2-1.7c.6-.9.9-1.8.9-1.9 0 0-1.8-.7-1.8-3.1ZM9.6 3.6c.5-.6.8-1.3.7-2.1-.7 0-1.5.5-2 1.1-.4.5-.8 1.3-.7 2 .8.1 1.5-.4 2-1Z\"\n      />\n    </svg>\n  );\n}\n\nfunction EnvelopeArt() {\n  return (\n    <span aria-hidden=\"true\" className=\"relative grid size-14 place-items-center rounded-[16px] border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-surface)]\">\n      <svg viewBox=\"0 0 28 28\" className=\"size-7\">\n        <rect x=\"4\" y=\"7\" width=\"20\" height=\"14\" rx=\"3\" fill=\"none\" stroke=\"var(--bjork-text-medium)\" strokeWidth=\"1.5\" />\n        <path d=\"m5 8.5 9 6.5 9-6.5\" fill=\"none\" stroke=\"var(--bjork-text-medium)\" strokeWidth=\"1.5\" strokeLinejoin=\"round\" />\n      </svg>\n      <span className=\"absolute -right-1 -top-1 grid size-4 place-items-center rounded-full bg-[var(--blk-accent-fill)] text-[11px] font-semibold text-[color:var(--blk-accent-fill-ink)] ring-2 ring-[color:var(--bjork-bg)]\">\n        1\n      </span>\n    </span>\n  );\n}\n\nfunction MailIcon() {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n      <rect x=\"2\" y=\"3.5\" width=\"12\" height=\"9\" rx=\"2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" />\n      <path d=\"m2.8 4.6 5.2 3.8 5.2-3.8\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" strokeLinejoin=\"round\" />\n    </svg>\n  );\n}\n\nfunction EyeIcon({ open }: { open: boolean }) {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n      <path d=\"M1.5 8S4 3.5 8 3.5 14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8Z\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" strokeLinejoin=\"round\" />\n      <circle cx=\"8\" cy=\"8\" r=\"2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" />\n      {!open && <path d=\"M2.5 2.5 13.5 13.5\" stroke=\"currentColor\" strokeWidth=\"1.3\" strokeLinecap=\"round\" />}\n    </svg>\n  );\n}\n\nfunction Spinner(): ReactNode {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"animate-spin motion-reduce:animate-[spin_1.6s_linear_infinite]\">\n      <circle cx=\"8\" cy=\"8\" r=\"6\" fill=\"none\" stroke=\"currentColor\" strokeOpacity=\"0.25\" strokeWidth=\"1.8\" />\n      <path d=\"M14 8a6 6 0 0 0-6-6\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" />\n    </svg>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/blocks/app-sign-in.tsx"
    },
    {
      "path": "components/bjork-ui/blocks/app-block-kit.tsx",
      "content": "\"use client\";\n\nimport {\n  forwardRef,\n  useCallback,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type ButtonHTMLAttributes,\n  type CSSProperties,\n  type InputHTMLAttributes,\n  type ReactNode,\n  type RefObject,\n} from \"react\";\nimport { useReducedMotion } from \"framer-motion\";\nimport { useBjorkTone } from \"@/components/bjork-ui/_core/tone\";\nimport type { BjorkTone } from \"@/components/bjork-ui/_core/palette\";\nimport { cn } from \"@/lib/utils\";\n\n// Shared plumbing for the application blocks (app-*). Every block is a full layout that sizes itself to its\n// container, not the viewport, so it behaves the same pasted into a page, a dialog or a docs preview.\n\nexport type AppBlockTheme = \"light\" | \"dark\" | \"auto\";\n\n// The library tokens, so a forced theme works even when the page itself is in the other mode.\nconst TOKENS: Record<BjorkTone, Record<string, string>> = {\n  light: {\n    \"--bjork-bg\": \"#f7f3ea\",\n    \"--bjork-surface\": \"#fffcf6\",\n    \"--bjork-surface-muted\": \"rgba(250, 246, 237, 0.94)\",\n    \"--bjork-surface-hover\": \"#f5efe3\",\n    \"--bjork-surface-active\": \"#efe7d8\",\n    \"--bjork-panel\": \"#f8f2e7\",\n    \"--bjork-menu\": \"rgba(255, 252, 246, 0.98)\",\n    \"--bjork-field\": \"rgba(255, 252, 246, 0.96)\",\n    \"--bjork-field-muted\": \"rgba(248, 242, 231, 0.82)\",\n    \"--bjork-field-inset\": \"rgba(239, 231, 216, 0.72)\",\n    \"--bjork-error-bg\": \"#fff0e8\",\n    \"--bjork-text\": \"#171717\",\n    \"--bjork-text-strong\": \"rgba(23, 23, 23, 0.9)\",\n    \"--bjork-text-medium\": \"rgba(23, 23, 23, 0.72)\",\n    \"--bjork-text-muted\": \"rgba(23, 23, 23, 0.56)\",\n    \"--bjork-text-soft\": \"rgba(23, 23, 23, 0.46)\",\n    \"--bjork-text-faint\": \"rgba(23, 23, 23, 0.22)\",\n    \"--bjork-inverted-text\": \"#fff8f0\",\n    \"--bjork-border\": \"#eee6db\",\n    \"--bjork-border-muted\": \"#f5ede2\",\n    \"--bjork-border-strong\": \"#e1d7c8\",\n    \"--bjork-thumb\": \"#a49b8e\",\n    \"--bjork-track\": \"#ded6ca\",\n    \"--bjork-accent\": \"#ec7d43\",\n    \"--bjork-accent-hover\": \"#f0935f\",\n    \"--bjork-accent-soft\": \"rgba(236, 125, 67, 0.1)\",\n    \"--bjork-accent-muted\": \"rgba(236, 125, 67, 0.2)\",\n    \"--bjork-accent-foreground\": \"#3f2112\",\n    \"--bjork-shadow-surface\":\n      \"inset 0 7px 14px rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.92), inset 1px 0 0 rgba(88, 72, 49, 0.026), inset -1px 0 0 rgba(255, 255, 255, 0.68), 0 14px 22px -9px rgba(66, 52, 33, 0.11)\",\n    \"--bjork-shadow-soft\": \"inset 0 1px 0 rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.86)\",\n    \"--bjork-shadow-panel\":\n      \"inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.64), 0 18px 38px -30px rgba(66, 52, 33, 0.2)\",\n    \"--bjork-shadow-menu\":\n      \"inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 0 12px 24px rgba(88, 72, 49, 0.022), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.62), 0 22px 44px -26px rgba(66, 52, 33, 0.22)\",\n    \"--bjork-shadow-inset\": \"inset 0 1px 10px rgba(88, 72, 49, 0.12), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.72)\",\n    \"--bjork-ring-offset\": \"#f7f3ea\",\n  },\n  dark: {\n    \"--bjork-bg\": \"#111111\",\n    \"--bjork-surface\": \"#121212\",\n    \"--bjork-surface-muted\": \"rgba(22, 22, 22, 0.92)\",\n    \"--bjork-surface-hover\": \"#181818\",\n    \"--bjork-surface-active\": \"#202020\",\n    \"--bjork-panel\": \"#0d0d0d\",\n    \"--bjork-menu\": \"rgba(18, 18, 18, 0.98)\",\n    \"--bjork-field\": \"rgba(18, 18, 18, 0.95)\",\n    \"--bjork-field-muted\": \"rgba(18, 18, 18, 0.55)\",\n    \"--bjork-field-inset\": \"rgba(9, 9, 9, 0.75)\",\n    \"--bjork-error-bg\": \"#130d0a\",\n    \"--bjork-text\": \"#ededed\",\n    \"--bjork-text-strong\": \"rgba(237, 237, 237, 0.9)\",\n    \"--bjork-text-medium\": \"rgba(237, 237, 237, 0.72)\",\n    \"--bjork-text-muted\": \"rgba(237, 237, 237, 0.6)\",\n    \"--bjork-text-soft\": \"rgba(237, 237, 237, 0.4)\",\n    \"--bjork-text-faint\": \"rgba(237, 237, 237, 0.22)\",\n    \"--bjork-inverted-text\": \"#080808\",\n    \"--bjork-border\": \"#232323\",\n    \"--bjork-border-muted\": \"#1c1c1c\",\n    \"--bjork-border-strong\": \"#343434\",\n    \"--bjork-thumb\": \"#626262\",\n    \"--bjork-track\": \"#090909\",\n    \"--bjork-accent\": \"#ec5c13\",\n    \"--bjork-accent-hover\": \"#f07832\",\n    \"--bjork-accent-soft\": \"rgba(236, 92, 19, 0.12)\",\n    \"--bjork-accent-muted\": \"rgba(236, 92, 19, 0.24)\",\n    \"--bjork-accent-foreground\": \"#fff2ea\",\n    \"--bjork-shadow-surface\":\n      \"inset 0 7px 14px rgba(255, 255, 255, 0.03), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.06), 0 14px 20px -6px rgba(0, 0, 0, 0.45)\",\n    \"--bjork-shadow-soft\": \"inset 0 1px 0 rgba(255, 255, 255, 0.045)\",\n    \"--bjork-shadow-panel\": \"inset 0 1px 0 rgba(255, 255, 255, 0.035), 0 18px 36px -28px rgba(0, 0, 0, 0.9)\",\n    \"--bjork-shadow-menu\":\n      \"inset 0 1px 0 rgba(255, 255, 255, 0.055), inset 0 12px 24px rgba(255, 255, 255, 0.018), inset 0 -18px 26px rgba(0, 0, 0, 0.24), 0 22px 42px -22px rgba(0, 0, 0, 0.9)\",\n    \"--bjork-shadow-inset\": \"inset 0 1px 10px rgba(0, 0, 0, 0.45)\",\n    \"--bjork-ring-offset\": \"#080808\",\n  },\n};\n\n// Block-only tokens the global sheet does not carry: status inks, a readable accent ink and the scrim.\nconst EXTRAS: Record<BjorkTone, Record<string, string>> = {\n  light: {\n    \"--blk-accent-ink\": \"#b4531f\",\n    \"--blk-accent-fill\": \"#e0692c\",\n    \"--blk-accent-fill-ink\": \"#fffaf5\",\n    \"--blk-success\": \"#1f8a55\",\n    \"--blk-success-soft\": \"rgba(31, 138, 85, 0.1)\",\n    \"--blk-warning\": \"#9a6200\",\n    \"--blk-warning-soft\": \"rgba(168, 107, 0, 0.1)\",\n    \"--blk-error\": \"#c8361f\",\n    \"--blk-error-soft\": \"rgba(200, 54, 31, 0.08)\",\n    \"--blk-scrim\": \"rgba(40, 32, 22, 0.28)\",\n    \"--blk-chart-ghost\": \"rgba(23, 23, 23, 0.28)\",\n  },\n  dark: {\n    \"--blk-accent-ink\": \"#f07832\",\n    \"--blk-accent-fill\": \"#c94f12\",\n    \"--blk-accent-fill-ink\": \"#fff2ea\",\n    \"--blk-success\": \"#4cc38a\",\n    \"--blk-success-soft\": \"rgba(76, 195, 138, 0.12)\",\n    \"--blk-warning\": \"#f2b544\",\n    \"--blk-warning-soft\": \"rgba(242, 181, 68, 0.12)\",\n    \"--blk-error\": \"#ff6a5c\",\n    \"--blk-error-soft\": \"rgba(255, 92, 77, 0.1)\",\n    \"--blk-scrim\": \"rgba(0, 0, 0, 0.55)\",\n    \"--blk-chart-ghost\": \"rgba(237, 237, 237, 0.3)\",\n  },\n};\n\nconst subscribeNoop = () => () => {};\n\n/** True after hydration. Server and first client render agree on false. */\nexport function useHydrated() {\n  return useSyncExternalStore(\n    subscribeNoop,\n    () => true,\n    () => false,\n  );\n}\n\n/** Reduced motion, read only after hydration so the first render matches the server. */\nexport function useBlockReducedMotion() {\n  const hydrated = useHydrated();\n  const reduced = useReducedMotion();\n  return hydrated && reduced === true;\n}\n\n/**\n * Resolves a block's theme to inline custom properties. \"auto\" leaves the library tokens to the page\n * (no flash on load) and only adds the block extras for the resolved tone; \"light\" and \"dark\" pin every token.\n */\nexport function useAppBlockTheme(theme: AppBlockTheme = \"auto\") {\n  const forced = theme === \"auto\" ? undefined : theme;\n  const tone = useBjorkTone(forced);\n  const style = {\n    ...(forced ? TOKENS[forced] : {}),\n    ...EXTRAS[tone],\n    colorScheme: tone,\n  } as CSSProperties;\n  return { tone, style };\n}\n\n/** Width of an element, tracked with ResizeObserver. 0 until measured. */\nexport function useElementWidth(ref: RefObject<HTMLElement | null>) {\n  const [width, setWidth] = useState(0);\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    setWidth(el.getBoundingClientRect().width);\n    if (typeof ResizeObserver === \"undefined\") return;\n    const ro = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [ref]);\n  return width;\n}\n\nexport const focusRing =\n  \"outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--bjork-accent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--bjork-ring-offset)]\";\n\nexport const blockRoot =\n  \"@container relative isolate flex min-h-0 w-full overflow-hidden bg-[var(--bjork-bg)] font-bjork-alpha text-[color:var(--bjork-text)] antialiased selection:bg-[color:var(--bjork-accent-muted)]\";\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\" | \"outline\";\ntype ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nconst BUTTON_VARIANTS: Record<ButtonVariant, string> = {\n  primary:\n    \"bg-[var(--blk-accent-fill)] text-[color:var(--blk-accent-fill-ink)] shadow-[inset_0_1px_0_rgba(255,255,255,0.22),0_1px_2px_rgba(0,0,0,0.18)] hover:brightness-[1.08]\",\n  secondary:\n    \"border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)] hover:bg-[var(--bjork-surface-hover)]\",\n  outline:\n    \"border border-[color:var(--bjork-border-strong)] bg-transparent text-[color:var(--bjork-text)] hover:bg-[var(--bjork-surface-hover)]\",\n  ghost:\n    \"bg-transparent text-[color:var(--bjork-text-medium)] hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text)]\",\n  danger:\n    \"bg-[var(--blk-error)] text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] hover:brightness-[1.08]\",\n};\n\nconst BUTTON_SIZES: Record<ButtonSize, string> = {\n  sm: \"h-8 gap-1.5 rounded-[9px] px-2.5 text-[12.5px]\",\n  md: \"h-9 gap-2 rounded-[10px] px-3.5 text-[13px]\",\n  lg: \"h-11 gap-2 rounded-[12px] px-4 text-[14px]\",\n};\n\nexport interface BlockButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n  variant?: ButtonVariant;\n  size?: ButtonSize;\n}\n\nexport const BlockButton = forwardRef<HTMLButtonElement, BlockButtonProps>(function BlockButton(\n  { variant = \"secondary\", size = \"md\", className, type = \"button\", ...props },\n  ref,\n) {\n  return (\n    <button\n      ref={ref}\n      type={type}\n      className={cn(\n        \"inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap font-medium tracking-[-0.01em] transition-[background-color,color,filter,transform,border-color] duration-150 ease-out active:scale-[0.97] disabled:pointer-events-none disabled:opacity-40 motion-reduce:transition-none motion-reduce:active:scale-100 [&_svg]:size-4 [&_svg]:shrink-0\",\n        focusRing,\n        BUTTON_VARIANTS[variant],\n        BUTTON_SIZES[size],\n        className,\n      )}\n      {...props}\n    />\n  );\n});\n\nexport interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n  /** Accessible name. Also used as the native tooltip. */\n  label: string;\n  size?: \"sm\" | \"md\";\n  pressed?: boolean;\n}\n\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n  { label, size = \"md\", pressed, className, type = \"button\", children, ...props },\n  ref,\n) {\n  return (\n    <button\n      ref={ref}\n      type={type}\n      aria-label={label}\n      title={label}\n      aria-pressed={pressed}\n      className={cn(\n        \"inline-grid shrink-0 cursor-pointer place-items-center rounded-[9px] text-[color:var(--bjork-text-muted)] transition-[background-color,color,transform] duration-150 ease-out hover:bg-[var(--bjork-surface-hover)] hover:text-[color:var(--bjork-text)] active:scale-[0.94] disabled:pointer-events-none disabled:opacity-40 motion-reduce:transition-none motion-reduce:active:scale-100 aria-pressed:text-[color:var(--blk-accent-ink)] [&_svg]:shrink-0\",\n        size === \"sm\" ? \"size-7 [&_svg]:size-[15px]\" : \"size-9 [&_svg]:size-[17px]\",\n        focusRing,\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n});\n\nexport function Kbd({ children, className }: { children: ReactNode; className?: string }) {\n  return (\n    <kbd\n      className={cn(\n        \"inline-flex h-5 min-w-5 items-center justify-center rounded-[5px] border border-[color:var(--bjork-border-strong)] bg-[var(--bjork-surface)] px-1 font-mono text-[11px] font-medium leading-none text-[color:var(--bjork-text-muted)] shadow-[0_1px_0_var(--bjork-border-strong)]\",\n        className,\n      )}\n    >\n      {children}\n    </kbd>\n  );\n}\n\nexport const inputClass =\n  \"h-10 w-full min-w-0 rounded-[10px] border border-[color:var(--bjork-border)] bg-[var(--bjork-field)] px-3 text-[13.5px] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)] outline-none transition-[border-color,box-shadow,background-color] duration-150 placeholder:text-[color:var(--bjork-text-soft)] hover:border-[color:var(--bjork-border-strong)] focus-visible:border-[color:var(--bjork-accent)] focus-visible:ring-[3px] focus-visible:ring-[color:var(--bjork-accent-soft)] aria-[invalid=true]:border-[color:var(--blk-error)] aria-[invalid=true]:focus-visible:ring-[color:var(--blk-error-soft)] disabled:cursor-not-allowed disabled:opacity-50\";\n\nexport interface BlockFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"children\"> {\n  label: string;\n  hint?: ReactNode;\n  error?: string | null;\n  /** Rendered at the right of the label row, e.g. a \"Forgot?\" link. */\n  labelAside?: ReactNode;\n  /** Rendered inside the field on the right, e.g. a show/hide toggle. */\n  trailing?: ReactNode;\n  className?: string;\n  inputClassName?: string;\n}\n\n/** Label, input, hint and error wired together with ids. The error replaces the hint and is announced. */\nexport const BlockField = forwardRef<HTMLInputElement, BlockFieldProps>(function BlockField(\n  { label, hint, error, labelAside, trailing, className, inputClassName, id, ...props },\n  ref,\n) {\n  const autoId = useId();\n  const inputId = id ?? autoId;\n  const noteId = `${inputId}-note`;\n  const hasNote = Boolean(error) || hint !== undefined;\n  return (\n    <div className={cn(\"flex min-w-0 flex-col gap-1.5\", className)}>\n      <div className=\"flex items-baseline justify-between gap-3\">\n        <label htmlFor={inputId} className=\"text-[12.5px] font-medium text-[color:var(--bjork-text-medium)]\">\n          {label}\n        </label>\n        {labelAside}\n      </div>\n      <div className=\"relative\">\n        <input\n          ref={ref}\n          id={inputId}\n          aria-invalid={error ? true : undefined}\n          aria-describedby={hasNote ? noteId : undefined}\n          className={cn(inputClass, trailing ? \"pr-10\" : undefined, inputClassName)}\n          {...props}\n        />\n        {trailing && <div className=\"absolute inset-y-0 right-1 flex items-center\">{trailing}</div>}\n      </div>\n      {hasNote && (\n        <p\n          id={noteId}\n          role={error ? \"alert\" : undefined}\n          className={cn(\n            \"flex items-start gap-1.5 text-[12px] leading-[1.45]\",\n            error ? \"text-[color:var(--blk-error)]\" : \"text-[color:var(--bjork-text-soft)]\",\n          )}\n        >\n          {error ? (\n            <>\n              <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className=\"mt-[2px] size-3 shrink-0\">\n                <circle cx=\"8\" cy=\"8\" r=\"6.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n                <path d=\"M8 4.75v3.75M8 10.9v.1\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" />\n              </svg>\n              {error}\n            </>\n          ) : (\n            hint\n          )}\n        </p>\n      )}\n    </div>\n  );\n});\n\n/** Initials avatar on a quiet tinted disc. Hue comes from the name so a person keeps their colour. */\nexport function InitialsAvatar({\n  name,\n  size = 28,\n  className,\n}: {\n  name: string;\n  size?: number;\n  className?: string;\n}) {\n  const initials = name\n    .split(/\\s+/)\n    .filter(Boolean)\n    .slice(0, 2)\n    .map((part) => part[0]?.toUpperCase())\n    .join(\"\");\n  let hash = 0;\n  for (let i = 0; i < name.length; i += 1) hash = (hash * 31 + name.charCodeAt(i)) >>> 0;\n  const hue = [22, 38, 150, 200, 260, 330][hash % 6];\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(\n        \"inline-grid shrink-0 place-items-center rounded-full font-semibold tracking-[-0.02em] text-[color:var(--bjork-text)]\",\n        className,\n      )}\n      style={{\n        width: size,\n        height: size,\n        fontSize: Math.max(11, Math.round(size * 0.38)),\n        background: `color-mix(in oklab, hsl(${hue} 70% 55%) 22%, var(--bjork-surface-active))`,\n        boxShadow: `inset 0 0 0 1px color-mix(in oklab, hsl(${hue} 70% 55%) 24%, transparent)`,\n      }}\n    >\n      {initials}\n    </span>\n  );\n}\n\nconst FOCUSABLE =\n  'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Keeps focus inside `ref` while `active`, closes on Escape, and hands focus back to whatever had it\n * before. Used by the in-block drawers and dialogs, which are positioned inside the block rather than portalled,\n * so a forced theme reaches them.\n */\nexport function useFocusTrap(ref: RefObject<HTMLElement | null>, active: boolean, onEscape: () => void) {\n  const escapeRef = useRef(onEscape);\n  useLayoutEffect(() => {\n    escapeRef.current = onEscape;\n  });\n  useEffect(() => {\n    if (!active) return;\n    const node = ref.current;\n    if (!node) return;\n    const previous = document.activeElement as HTMLElement | null;\n    const first = node.querySelector<HTMLElement>(\"[data-autofocus]\") ?? node.querySelector<HTMLElement>(FOCUSABLE);\n    (first ?? node).focus({ preventScroll: true });\n    const onKey = (event: KeyboardEvent) => {\n      if (event.key === \"Escape\") {\n        event.stopPropagation();\n        escapeRef.current();\n        return;\n      }\n      if (event.key !== \"Tab\") return;\n      const items = Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n        (el) => el.offsetParent !== null || el === document.activeElement,\n      );\n      if (items.length === 0) return;\n      const head = items[0];\n      const tail = items[items.length - 1];\n      if (event.shiftKey && document.activeElement === head) {\n        event.preventDefault();\n        tail.focus();\n      } else if (!event.shiftKey && document.activeElement === tail) {\n        event.preventDefault();\n        head.focus();\n      }\n    };\n    node.addEventListener(\"keydown\", onKey);\n    return () => {\n      node.removeEventListener(\"keydown\", onKey);\n      if (previous && document.contains(previous)) previous.focus({ preventScroll: true });\n    };\n  }, [active, ref]);\n}\n\nexport interface BlockDrawerProps {\n  open: boolean;\n  onClose: () => void;\n  side?: \"left\" | \"right\";\n  /** Accessible name of the dialog. */\n  label: string;\n  children: ReactNode;\n  className?: string;\n}\n\n/**\n * A modal drawer anchored to the block, not the viewport. Under reduced motion it fades instead of sliding.\n * The block's own content should be marked `inert` while it is open.\n */\nexport function BlockDrawer({ open, onClose, side = \"left\", label, children, className }: BlockDrawerProps) {\n  const panelRef = useRef<HTMLDivElement>(null);\n  const reduce = useBlockReducedMotion();\n  useFocusTrap(panelRef, open, onClose);\n  const hidden = side === \"left\" ? \"-translate-x-full\" : \"translate-x-full\";\n  return (\n    <div className={cn(\"absolute inset-0 z-40\", open ? \"pointer-events-auto\" : \"pointer-events-none\")} aria-hidden={!open}>\n      <div\n        className={cn(\n          \"absolute inset-0 bg-[var(--blk-scrim)] transition-opacity duration-200 ease-out\",\n          open ? \"opacity-100\" : \"opacity-0\",\n        )}\n        onClick={onClose}\n      />\n      <div\n        ref={panelRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-label={label}\n        tabIndex={-1}\n        inert={!open}\n        className={cn(\n          \"absolute inset-y-0 flex w-[min(300px,86%)] flex-col bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-menu)] outline-none\",\n          side === \"left\" ? \"left-0 border-r\" : \"right-0 border-l\",\n          \"border-[color:var(--bjork-border)]\",\n          reduce\n            ? cn(\"transition-opacity duration-150\", open ? \"opacity-100\" : \"opacity-0\")\n            : cn(\n                \"transition-transform duration-[280ms] ease-[cubic-bezier(0.32,0.72,0,1)]\",\n                open ? \"translate-x-0\" : hidden,\n              ),\n          className,\n        )}\n      >\n        {children}\n      </div>\n    </div>\n  );\n}\n\n/** Polite status text for screen readers; changes are announced once. */\nexport function useAnnouncer() {\n  const [message, setMessage] = useState(\"\");\n  const announce = useCallback((next: string) => {\n    // Clearing first makes a repeated message announce again.\n    setMessage(\"\");\n    window.requestAnimationFrame(() => setMessage(next));\n  }, []);\n  const region = (\n    <span role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n      {message}\n    </span>\n  );\n  return { announce, region };\n}\n\n/** A small toast pinned to the bottom of the block. Purely visual; pair it with `useAnnouncer`. */\nexport function BlockToast({ message, tone = \"neutral\" }: { message: string | null; tone?: \"neutral\" | \"success\" | \"error\" }) {\n  const reduce = useBlockReducedMotion();\n  const dot =\n    tone === \"success\" ? \"var(--blk-success)\" : tone === \"error\" ? \"var(--blk-error)\" : \"var(--bjork-accent)\";\n  return (\n    <div className=\"pointer-events-none absolute inset-x-0 bottom-4 z-50 flex justify-center px-4\" aria-hidden=\"true\">\n      <div\n        className={cn(\n          \"flex max-w-full items-center gap-2.5 rounded-full border border-[color:var(--bjork-border)] bg-[var(--bjork-menu)] py-2 pl-3 pr-4 text-[12.5px] font-medium text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-menu)] backdrop-blur-xl\",\n          reduce ? \"transition-opacity duration-150\" : \"transition-[opacity,transform] duration-200 ease-out\",\n          message ? \"translate-y-0 opacity-100\" : \"translate-y-2 opacity-0\",\n        )}\n      >\n        <span className=\"size-1.5 shrink-0 rounded-full\" style={{ background: dot }} />\n        <span className=\"truncate\">{message}</span>\n      </div>\n    </div>\n  );\n}\n\n/** Shows a toast for `ms` and announces it. */\nexport function useBlockToast(ms = 2600) {\n  const { announce, region } = useAnnouncer();\n  const [toast, setToast] = useState<{ message: string; tone: \"neutral\" | \"success\" | \"error\" } | null>(null);\n  const timer = useRef<number | undefined>(undefined);\n  const show = useCallback(\n    (message: string, tone: \"neutral\" | \"success\" | \"error\" = \"neutral\") => {\n      window.clearTimeout(timer.current);\n      setToast({ message, tone });\n      announce(message);\n      timer.current = window.setTimeout(() => setToast(null), ms);\n    },\n    [announce, ms],\n  );\n  useEffect(() => () => window.clearTimeout(timer.current), []);\n  const node = (\n    <>\n      <BlockToast message={toast?.message ?? null} tone={toast?.tone} />\n      {region}\n    </>\n  );\n  return { show, node };\n}\n\nexport interface BlockDialogProps {\n  open: boolean;\n  onClose: () => void;\n  /** Visible title; also the dialog's accessible name. */\n  title: string;\n  description?: ReactNode;\n  children?: ReactNode;\n  /** Buttons for the footer, right-aligned. */\n  footer?: ReactNode;\n  tone?: \"neutral\" | \"danger\";\n  className?: string;\n}\n\n/**\n * A modal dialog positioned inside the block (not portalled), so a forced theme reaches it. Traps focus, closes\n * on Escape or a scrim click, and returns focus to the trigger. Mark the block's own content `inert` while open.\n */\nexport function BlockDialog({ open, onClose, title, description, children, footer, tone = \"neutral\", className }: BlockDialogProps) {\n  const panelRef = useRef<HTMLDivElement>(null);\n  const reduce = useBlockReducedMotion();\n  const titleId = useId();\n  const descId = useId();\n  useFocusTrap(panelRef, open, onClose);\n  return (\n    <div\n      className={cn(\"absolute inset-0 z-50 grid place-items-center p-4\", open ? \"pointer-events-auto\" : \"pointer-events-none\")}\n      aria-hidden={!open}\n    >\n      <div\n        className={cn(\n          \"absolute inset-0 bg-[var(--blk-scrim)] transition-opacity duration-200 ease-out\",\n          open ? \"opacity-100\" : \"opacity-0\",\n        )}\n        onClick={onClose}\n      />\n      <div\n        ref={panelRef}\n        role={tone === \"danger\" ? \"alertdialog\" : \"dialog\"}\n        aria-modal=\"true\"\n        aria-labelledby={titleId}\n        aria-describedby={description ? descId : undefined}\n        tabIndex={-1}\n        inert={!open}\n        className={cn(\n          \"relative flex max-h-full w-full max-w-[440px] flex-col overflow-hidden rounded-[18px] border border-[color:var(--bjork-border)] bg-[var(--bjork-surface)] shadow-[var(--bjork-shadow-menu)] outline-none\",\n          reduce\n            ? cn(\"transition-opacity duration-150\", open ? \"opacity-100\" : \"opacity-0\")\n            : cn(\n                \"transition-[opacity,transform] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]\",\n                open ? \"scale-100 opacity-100\" : \"scale-[0.97] opacity-0\",\n              ),\n          className,\n        )}\n      >\n        <div className=\"overflow-y-auto px-5 pb-4 pt-5\">\n          <h2 id={titleId} className=\"text-[16px] font-semibold tracking-[-0.015em]\">\n            {title}\n          </h2>\n          {description && (\n            <div id={descId} className=\"mt-1.5 text-[13px] leading-[1.55] text-[color:var(--bjork-text-muted)]\">\n              {description}\n            </div>\n          )}\n          {children && <div className=\"mt-4\">{children}</div>}\n        </div>\n        {footer && (\n          <div className=\"flex flex-wrap items-center justify-end gap-2 border-t border-[color:var(--bjork-border)] bg-[var(--bjork-panel)] px-5 py-3\">\n            {footer}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport interface BlockSwitchProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n  checked: boolean;\n  onCheckedChange: (checked: boolean) => void;\n  /** Accessible name when there is no visible label wired with aria-labelledby. */\n  label?: string;\n}\n\n/** A role=\"switch\" toggle. Space and Enter flip it; it reads as on/off to assistive tech. */\nexport const BlockSwitch = forwardRef<HTMLButtonElement, BlockSwitchProps>(function BlockSwitch(\n  { checked, onCheckedChange, label, className, disabled, ...props },\n  ref,\n) {\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      role=\"switch\"\n      aria-checked={checked}\n      aria-label={label}\n      disabled={disabled}\n      onClick={() => onCheckedChange(!checked)}\n      className={cn(\n        \"relative inline-flex h-[22px] w-[38px] shrink-0 cursor-pointer items-center rounded-full border transition-[background-color,border-color] duration-200 ease-out disabled:cursor-not-allowed disabled:opacity-45 motion-reduce:transition-none\",\n        checked\n          ? \"border-transparent bg-[var(--blk-accent-fill)]\"\n          : \"border-[color:var(--bjork-border-strong)] bg-[var(--bjork-field-inset)] shadow-[var(--bjork-shadow-inset)]\",\n        focusRing,\n        className,\n      )}\n      {...props}\n    >\n      <span\n        aria-hidden=\"true\"\n        className={cn(\n          \"absolute left-[2px] size-4 rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.3)] transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\",\n          checked ? \"translate-x-4\" : \"translate-x-0\",\n        )}\n      />\n    </button>\n  );\n});\n\nexport function BlockSpinner({ className }: { className?: string }) {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" className={cn(\"animate-spin motion-reduce:animate-[spin_1.6s_linear_infinite]\", className)}>\n      <circle cx=\"8\" cy=\"8\" r=\"6\" fill=\"none\" stroke=\"currentColor\" strokeOpacity=\"0.25\" strokeWidth=\"1.8\" />\n      <path d=\"M14 8a6 6 0 0 0-6-6\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" />\n    </svg>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/blocks/app-block-kit.tsx"
    },
    {
      "path": "components/bjork-ui/_core/tone.ts",
      "content": "\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport type { BjorkTone } from \"./palette\";\n\nconst subscribeNoop = () => () => {};\nconst getMounted = () => true;\nconst getServerMounted = () => false;\n\n/**\n * Resolves the tone for a component. An explicit `tone` always wins.\n * Otherwise it follows next-themes' resolved theme, but only after mount,\n * so the server render and first client render agree on dark.\n */\nexport function useBjorkTone(tone?: BjorkTone): BjorkTone {\n  const { resolvedTheme } = useTheme();\n  const mounted = useSyncExternalStore(subscribeNoop, getMounted, getServerMounted);\n  if (tone) return tone;\n  if (!mounted) return \"dark\";\n  return resolvedTheme === \"light\" ? \"light\" : \"dark\";\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/tone.ts"
    },
    {
      "path": "components/bjork-ui/_core/palette.ts",
      "content": "export type BjorkTone = \"dark\" | \"light\";\n\n// Canvas, WebGL and SVG attribute colours. DOM code uses the CSS tokens with these as fallbacks.\nexport const BJORK_PALETTE = {\n  dark: {\n    bg: \"#050505\", stage: \"#111111\", surface: \"#121212\", raised: \"#161616\", active: \"#202020\",\n    border: \"#232323\", borderStrong: \"#343434\",\n    text: \"#ededed\", textMedium: \"rgba(237,237,237,0.72)\", textMuted: \"rgba(237,237,237,0.6)\",\n    textSoft: \"rgba(237,237,237,0.5)\", textFaint: \"rgba(237,237,237,0.22)\", hair: \"rgba(237,237,237,0.12)\",\n    accent: \"#ec5c13\", accentInk: \"#ec5c13\", accentFill: \"#b84a12\", accentSoft: \"rgba(236,92,19,0.12)\", accentMuted: \"rgba(236,92,19,0.24)\",\n    accentFg: \"#fff2ea\", success: \"#4cc38a\", warning: \"#f2b544\", error: \"#ff5c4d\",\n  },\n  light: {\n    bg: \"#f7f5ef\", stage: \"#f7f5ef\", surface: \"#fffcf6\", raised: \"#f5efe3\", active: \"#efe7d8\",\n    border: \"#eee6db\", borderStrong: \"#e1d7c8\",\n    text: \"#171717\", textMedium: \"rgba(23,23,23,0.78)\", textMuted: \"rgba(23,23,23,0.68)\",\n    textSoft: \"rgba(23,23,23,0.62)\", textFaint: \"rgba(23,23,23,0.22)\", hair: \"rgba(23,23,23,0.10)\",\n    accent: \"#ec7d43\", accentInk: \"#b4531f\", accentFill: \"#ec7d43\", accentSoft: \"rgba(236,125,67,0.10)\", accentMuted: \"rgba(236,125,67,0.20)\",\n    accentFg: \"#3f2112\", success: \"#1f8a55\", warning: \"#a86b00\", error: \"#c8361f\",\n  },\n} as const;\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/_core/palette.ts"
    },
    {
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n",
      "type": "registry:lib",
      "target": "@lib/utils.ts"
    }
  ],
  "cssVars": {
    "light": {
      "bjork-bg": "#f7f3ea",
      "bjork-surface": "#fffcf6",
      "bjork-surface-muted": "rgba(250, 246, 237, 0.94)",
      "bjork-surface-hover": "#f5efe3",
      "bjork-surface-active": "#efe7d8",
      "bjork-panel": "#f8f2e7",
      "bjork-menu": "rgba(255, 252, 246, 0.98)",
      "bjork-field": "rgba(255, 252, 246, 0.96)",
      "bjork-field-muted": "rgba(248, 242, 231, 0.82)",
      "bjork-field-inset": "rgba(239, 231, 216, 0.72)",
      "bjork-error-bg": "#fff0e8",
      "bjork-text": "#171717",
      "bjork-text-strong": "rgba(23, 23, 23, 0.9)",
      "bjork-text-medium": "rgba(23, 23, 23, 0.78)",
      "bjork-text-muted": "rgba(23, 23, 23, 0.68)",
      "bjork-text-soft": "rgba(23, 23, 23, 0.62)",
      "bjork-text-faint": "rgba(23, 23, 23, 0.22)",
      "bjork-inverted-text": "#fff8f0",
      "bjork-border": "#eee6db",
      "bjork-border-muted": "#f5ede2",
      "bjork-border-strong": "#e1d7c8",
      "bjork-thumb": "#a49b8e",
      "bjork-track": "#ded6ca",
      "bjork-accent": "#ec7d43",
      "bjork-accent-hover": "#f0935f",
      "bjork-accent-soft": "rgba(236, 125, 67, 0.1)",
      "bjork-accent-muted": "rgba(236, 125, 67, 0.2)",
      "bjork-accent-badge": "rgba(236, 125, 67, 0.16)",
      "bjork-accent-badge-foreground": "#8d421d",
      "bjork-accent-foreground": "#3f2112",
      "bjork-accent-fill": "#ec7d43",
      "bjork-accent-fill-hover": "#f0935f",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.92), inset 1px 0 0 rgba(88, 72, 49, 0.026), inset -1px 0 0 rgba(255, 255, 255, 0.68), 0 14px 22px -9px rgba(66, 52, 33, 0.11)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.86)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.64), 0 18px 38px -30px rgba(66, 52, 33, 0.2)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 0 12px 24px rgba(88, 72, 49, 0.022), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.62), 0 22px 44px -26px rgba(66, 52, 33, 0.22)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(88, 72, 49, 0.12), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.72)",
      "bjork-ring-offset": "#f7f3ea"
    },
    "dark": {
      "bjork-bg": "#111111",
      "bjork-surface": "#121212",
      "bjork-surface-muted": "rgba(22, 22, 22, 0.92)",
      "bjork-surface-hover": "#161616",
      "bjork-surface-active": "#202020",
      "bjork-panel": "#0d0d0d",
      "bjork-menu": "rgba(18, 18, 18, 0.98)",
      "bjork-field": "rgba(18, 18, 18, 0.95)",
      "bjork-field-muted": "rgba(18, 18, 18, 0.55)",
      "bjork-field-inset": "rgba(9, 9, 9, 0.75)",
      "bjork-error-bg": "#130d0a",
      "bjork-text": "#ededed",
      "bjork-text-strong": "rgba(237, 237, 237, 0.9)",
      "bjork-text-medium": "rgba(237, 237, 237, 0.72)",
      "bjork-text-muted": "rgba(237, 237, 237, 0.6)",
      "bjork-text-soft": "rgba(237, 237, 237, 0.5)",
      "bjork-text-faint": "rgba(237, 237, 237, 0.22)",
      "bjork-inverted-text": "#080808",
      "bjork-border": "#232323",
      "bjork-border-muted": "#1c1c1c",
      "bjork-border-strong": "#343434",
      "bjork-thumb": "#626262",
      "bjork-track": "#090909",
      "bjork-accent": "#ec5c13",
      "bjork-accent-hover": "#f07832",
      "bjork-accent-soft": "rgba(236, 92, 19, 0.12)",
      "bjork-accent-muted": "rgba(236, 92, 19, 0.24)",
      "bjork-accent-badge": "#b84a12",
      "bjork-accent-badge-foreground": "#fff2ea",
      "bjork-accent-foreground": "#fff2ea",
      "bjork-accent-fill": "#b84a12",
      "bjork-accent-fill-hover": "#a84a14",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(255, 255, 255, 0.03), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.06), 0 14px 20px -6px rgba(0, 0, 0, 0.45)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(255, 255, 255, 0.045)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(255, 255, 255, 0.035), 0 18px 36px -28px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(255, 255, 255, 0.055), inset 0 12px 24px rgba(255, 255, 255, 0.018), inset 0 -18px 26px rgba(0, 0, 0, 0.24), 0 22px 42px -22px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(0, 0, 0, 0.45)",
      "bjork-ring-offset": "#080808"
    }
  },
  "css": {
    ".bjork-range": {
      "box-shadow": "inset 0 1px 1px rgba(255, 255, 255, 0.04), 0 0 14px rgba(189, 69, 20, 0.06)"
    },
    ".bjork-range::-webkit-slider-thumb": {
      "appearance": "none",
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-range::-moz-range-thumb": {
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-layered-button-accent, .bjork-layered-button-raised": {
      "position": "relative",
      "isolation": "isolate",
      "appearance": "none",
      "overflow": "hidden",
      "border": "0",
      "border-radius": "13px",
      "box-shadow": "inset 0 1px 0 #ffffff38, 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a",
      "transition": "filter 150ms ease-out, box-shadow 150ms ease-out"
    },
    ".bjork-layered-button-accent::after, .bjork-layered-button-raised::after": {
      "position": "absolute",
      "inset": "0",
      "pointer-events": "none",
      "content": "\"\"",
      "border": "1.5px solid transparent",
      "border-radius": "inherit",
      "background": "linear-gradient(#ffffffb8, #0000003d 41% 75%, #ffffff47) border-box",
      "mix-blend-mode": "overlay",
      "-webkit-mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "-webkit-mask-clip": "padding-box, border-box",
      "mask-clip": "padding-box, border-box",
      "-webkit-mask-origin": "padding-box, border-box",
      "mask-origin": "padding-box, border-box",
      "-webkit-mask-composite": "xor",
      "mask-composite": "exclude"
    },
    ".bjork-layered-button-accent": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000040",
      "background": "radial-gradient(ellipse at -20px top, rgba(255, 255, 255, 0.14), transparent), linear-gradient(180deg, #ec5c13 0%, #ec5c13 62%, color-mix(in oklab, #ec5c13 84%, black 16%) 100%)"
    },
    ".bjork-layered-button-raised": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, oklch(0.1881 0.006 265), oklch(0.5493 0.0081 265))"
    },
    ".dark .bjork-layered-button-raised": {
      "color": "oklch(0.2574 0.0056 265)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, color-mix(in oklab, oklch(0.9674 0.0013 265) 80%, oklch(0.2574 0.0056 265) 20%), color-mix(in oklab, oklch(0.669 0.0107 265) 80%, oklch(0.2574 0.0056 265) 20%))"
    },
    "@media (hover: hover)": {
      ".bjork-layered-button-accent:hover": {
        "filter": "brightness(1.08) saturate(1.04)"
      },
      ".bjork-layered-button-raised:hover": {
        "filter": "brightness(1.08) contrast(0.9)"
      }
    }
  },
  "meta": {
    "collection": "Blocks",
    "registryUrl": "https://ui.isaiahbjork.com/app-sign-in.json",
    "installCommand": "npx shadcn@latest add https://ui.isaiahbjork.com/app-sign-in.json"
  },
  "type": "registry:block"
}