{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"contact-template","type":"registry:ui","title":"Contact template","description":"A contact page that routes the message first: pick the topic and it says who answers and how soon. The form keeps what you typed if sending fails and confirms where the reply will go.","dependencies":["@radix-ui/react-popover","@radix-ui/react-slot","clsx","lucide-react","tailwind-merge"],"registryDependencies":["https://caliperui.site/r/styles.json"],"files":[{"path":"ui/alert.tsx","type":"registry:ui","target":"components/ui/alert.tsx","content":"\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { useId, type ComponentProps, type ReactNode } from \"react\";\nimport { Button } from \"./button\";\nimport { FeedbackIcon, type FeedbackVariant } from \"./feedback\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/alert.css\";\nimport \"./alert.css\";\n\nexport type AlertProps = Omit<ComponentProps<\"div\">, \"title\"> & {\n  title: ReactNode;\n  variant?: FeedbackVariant;\n  loading?: boolean;\n  action?: ReactNode;\n  onDismiss?: () => void;\n};\n\nexport function Alert({\n  title,\n  variant = \"neutral\",\n  loading = false,\n  children,\n  action,\n  onDismiss,\n  className,\n  ...props\n}: AlertProps) {\n  const titleId = useId();\n  return (\n    <div\n      data-slot=\"alert\"\n      data-variant={loading ? \"neutral\" : variant}\n      role={!loading && variant === \"error\" ? \"alert\" : \"status\"}\n      aria-busy={loading || undefined}\n      aria-atomic=\"true\"\n      aria-labelledby={titleId}\n      className={cn(\n        \"grid min-w-0 grid-cols-[2rem_minmax(0,1fr)_auto] items-start gap-3 rounded-overlay border border-border bg-surface p-4 text-sm text-foreground [overflow-wrap:anywhere]\",\n        className,\n      )}\n      {...props}\n    >\n      <FeedbackIcon variant={variant} loading={loading} />\n      <div data-slot=\"feedback-copy\" className=\"min-w-0 pt-1\">\n        <div id={titleId} data-slot=\"feedback-title\" className=\"font-semibold leading-5\">\n          <span key={loading ? \"loading\" : variant} data-slot=\"feedback-message\">{title}</span>\n        </div>\n        {children != null && (\n          <div data-slot=\"feedback-description\" className=\"mt-1 text-sm leading-5 text-muted-foreground\">\n            <span key={loading ? \"loading\" : variant} data-slot=\"feedback-message\">{children}</span>\n          </div>\n        )}\n        {action != null && (\n          <div data-slot=\"feedback-action\" className=\"mt-3 flex max-w-full flex-wrap items-center gap-2\">\n            {action}\n          </div>\n        )}\n      </div>\n      {onDismiss && (\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"-mt-1 -mr-1\"\n          aria-label=\"Dismiss alert\"\n          onClick={onDismiss}\n        >\n          <X aria-hidden=\"true\" />\n        </Button>\n      )}\n    </div>\n  );\n}\n"},{"path":"ui/block-concept.tsx","type":"registry:ui","target":"components/ui/block-concept.tsx","content":"import { ArrowRight } from \"lucide-react\";\nimport type { ComponentProps, ElementType, ReactNode } from \"react\";\nimport { cn } from \"./lib/cn\";\nimport { StageScope } from \"./stage-scope\";\nimport \"./block-concept.css\";\n\n/**\n * Marketing blocks share one voice: monochrome from the system tokens, Geist\n * display with two-tone titles, ink actions, ruled sections and ember only for\n * focus. Blocks follow the page theme, and the Caliper UI controls inside them too.\n */\n\nexport type BlockAction = { label: string; href?: string; onClick?: () => void };\n\n/**\n * A full-bleed band. The section is the container, so everything inside lays\n * out by the block's own width; `block-inner` holds the measure and rhythm.\n * `backdrop` paints behind the content edge to edge (grid lines, a tone).\n */\nexport function BlockRoot({\n  as: Tag = \"section\",\n  width = \"default\",\n  backdrop,\n  tone,\n  className,\n  innerClassName,\n  children,\n  ...props\n}: Omit<ComponentProps<\"section\">, \"ref\"> & {\n  /** The band's element; a site footer is a footer. */\n  as?: \"section\" | \"footer\" | \"div\";\n  /** default 1240px · narrow 960px · wide 1440px. */\n  width?: \"default\" | \"narrow\" | \"wide\";\n  backdrop?: ReactNode;\n  /** A band tone: the page colour or the sunken colour. */\n  tone?: \"page\" | \"sunken\" | \"ink\";\n  innerClassName?: string;\n}) {\n  return (\n    <Tag data-slot=\"block\" data-tone={tone} className={cn(\"@container relative isolate overflow-clip\", className)} {...props}>\n      {backdrop}\n      <div data-slot=\"block-inner\" data-width={width} className={innerClassName}>\n        {children}\n      </div>\n    </Tag>\n  );\n}\n\nexport function BlockEyebrow({ className, ...props }: ComponentProps<\"p\">) {\n  return <p data-slot=\"block-eyebrow\" className={className} {...props} />;\n}\n\n/** A display heading in the block face. size 1 is the hero headline. */\nexport function BlockTitle({\n  as: Tag = \"h2\",\n  size = 2,\n  className,\n  ...props\n}: ComponentProps<\"h2\"> & { as?: \"h1\" | \"h2\" | \"h3\"; size?: 1 | 2 | 3 }) {\n  return <Tag data-slot=\"block-title\" data-size={size} className={className} {...props} />;\n}\n\n/** The second tone of a title: these words land in grey. */\nexport function BlockEm({ className, ...props }: ComponentProps<\"em\">) {\n  return <em data-slot=\"block-em\" className={className} {...props} />;\n}\n\nexport function BlockLead({ className, ...props }: ComponentProps<\"p\">) {\n  return <p data-slot=\"block-lead\" className={className} {...props} />;\n}\n\n/**\n * The section opening every block shares: eyebrow, title and lead. `split`\n * sets the lead beside the title on wide blocks; `center` stacks it centred.\n */\nexport function BlockHeader({\n  eyebrow,\n  title,\n  lead,\n  align = \"start\",\n  aside,\n  headingLevel = 2,\n  className,\n}: {\n  eyebrow?: ReactNode;\n  title: ReactNode;\n  lead?: ReactNode;\n  align?: \"start\" | \"center\" | \"split\";\n  /** 1 when the block opens the page, e.g. a pricing page. */\n  headingLevel?: 1 | 2;\n  /** Sits at the end of the header row, e.g. a period toggle. */\n  aside?: ReactNode;\n  className?: string;\n}) {\n  return (\n    <header data-slot=\"block-header\" data-align={align} className={className}>\n      <div data-slot=\"block-header-title\">\n        {eyebrow && <BlockEyebrow>{eyebrow}</BlockEyebrow>}\n        <BlockTitle as={headingLevel === 1 ? \"h1\" : \"h2\"}>{title}</BlockTitle>\n      </div>\n      {(lead || aside) && (\n        <div data-slot=\"block-header-aside\">\n          {lead && <BlockLead>{lead}</BlockLead>}\n          {aside}\n        </div>\n      )}\n    </header>\n  );\n}\n\nexport function BlockActions({\n  primary,\n  secondary,\n  className,\n  block = false,\n}: {\n  primary?: BlockAction;\n  secondary?: BlockAction;\n  className?: string;\n  /** Full-width actions, e.g. on phones. */\n  block?: boolean;\n}) {\n  if (!primary && !secondary) return null;\n  return (\n    <div data-slot=\"block-actions\" className={cn(\"flex flex-wrap gap-3\", block && \"@max-[520px]:grid @max-[520px]:grid-cols-1\", className)}>\n      {primary && <BlockButton action={primary} kind=\"primary\" arrow />}\n      {secondary && <BlockButton action={secondary} kind=\"secondary\" />}\n    </div>\n  );\n}\n\nexport function BlockButton({ action, kind = \"primary\", arrow = false, link: Link = \"a\" }: { action: BlockAction; kind?: \"primary\" | \"secondary\"; arrow?: boolean; /** The element for a link action, e.g. your router's Link. */ link?: ElementType }) {\n  const shared = { \"data-slot\": \"block-button\", \"data-kind\": kind } as const;\n  const content = (\n    <>\n      {action.label}\n      {arrow && <ArrowRight aria-hidden=\"true\" data-slot=\"block-button-arrow\" className=\"size-4\" />}\n    </>\n  );\n  return action.href ? (\n    <Link href={action.href} {...shared}>\n      {content}\n    </Link>\n  ) : (\n    <button type=\"button\" onClick={action.onClick} {...shared}>\n      {content}\n    </button>\n  );\n}\n\nexport function BlockCard({ className, ...props }: ComponentProps<\"div\">) {\n  return <div data-slot=\"block-card\" className={className} {...props} />;\n}\n\nexport function BlockChip({ className, ...props }: ComponentProps<\"span\">) {\n  return <span data-slot=\"block-chip\" className={className} {...props} />;\n}\n\n/**\n * A product artefact on a stage: a dotted tonal panel holding a framed window, with an optional floating note that overlaps the\n * window's corner on wide blocks and sits beneath it on narrow ones.\n */\nexport function BlockStage({\n  title,\n  floating,\n  bare = false,\n  className,\n  children,\n}: {\n  /** Shown in the window bar, e.g. \"halden.app/close\". */\n  title?: string;\n  floating?: ReactNode;\n  /** No window frame: the children bring their own surfaces. */\n  bare?: boolean;\n  className?: string;\n  children: ReactNode;\n}) {\n  return (\n    <div data-slot=\"block-stage\" className={className}>\n      {bare ? (\n        <StageScope>{children}</StageScope>\n      ) : (\n        <div data-slot=\"block-window\">\n          <div data-slot=\"block-window-bar\" aria-hidden={title ? undefined : true}>\n            <span data-slot=\"block-window-dots\" aria-hidden=\"true\"><i /><i /><i /></span>\n            {title && <span data-slot=\"block-window-title\">{title}</span>}\n          </div>\n          <div data-slot=\"block-window-body\"><StageScope>{children}</StageScope></div>\n        </div>\n      )}\n      {floating && <div data-slot=\"block-float\">{floating}</div>}\n    </div>\n  );\n}\n\n/** Company names set as type in a quiet row; swap in SVG marks in production. */\nexport function BlockLogos({ label, logos, className }: { label?: string; logos: readonly string[]; className?: string }) {\n  return (\n    <div data-slot=\"block-logos\" className={className}>\n      {label && <p data-slot=\"block-logos-label\">{label}</p>}\n      <ul aria-label={label ?? \"Customers\"}>\n        {logos.map((logo) => (\n          <li key={logo}>{logo}</li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\n/** Initials from a name, in the block's highlight tones. */\nexport function BlockInitials({ name, size = 36, tone, className }: { name: string; size?: number; tone?: number; className?: string }) {\n  const words = name.trim().split(/\\s+/u);\n  const initials = ((words[0]?.[0] ?? \"\") + (words.length > 1 ? (words[words.length - 1]?.[0] ?? \"\") : \"\")).toUpperCase();\n  return (\n    <span data-slot=\"block-initials\" data-tone={tone === undefined ? undefined : tone % 4} aria-hidden=\"true\" className={className} style={{ width: size, height: size, fontSize: Math.round(size / 2.8) }}>\n      {initials}\n    </span>\n  );\n}\n\nexport type BlockStat = { value: ReactNode; label: string };\n"},{"path":"ui/block-nav.tsx","type":"registry:ui","target":"components/ui/block-nav.tsx","content":"\"use client\";\n\nimport { Menu, X } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState, type ElementType, type ReactNode } from \"react\";\nimport { BlockButton, type BlockAction } from \"./block-concept\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/site-header.css\";\nimport \"./block-nav.css\";\n\nexport type BlockNavLink = { label: string; href: string; current?: boolean };\n\n// Whole class names, so Tailwind sees them.\nconst folds = {\n  860: { nav: \"@[860px]:block\", row: \"@[860px]:flex\", menu: \"@[860px]:hidden\" },\n  1180: { nav: \"@[1180px]:block\", row: \"@[1180px]:flex\", menu: \"@[1180px]:hidden\" },\n};\n\n/**\n * The site header page templates share. It sits on the page colour, gains a\n * hairline and frost once the page scrolls, keeps the primary action in\n * reach, and on narrow pages folds the links into a menu disclosure that\n * closes with Escape and returns focus to its button.\n */\nexport function BlockNav({\n  brand,\n  brandHref = \"/\",\n  brandLabel,\n  logo,\n  nav = [],\n  signIn,\n  primary,\n  aside,\n  link: Link = \"a\",\n  foldAt = 860,\n  className,\n}: {\n  brand: string;\n  brandHref?: string;\n  /** The home link's accessible name when the visible brand is short, e.g. \"Halden home\". */\n  brandLabel?: string;\n  /** A mark set before the brand name; the name stays the accessible label. */\n  logo?: ReactNode;\n  nav?: readonly BlockNavLink[];\n  signIn?: BlockAction;\n  primary?: BlockAction;\n  /** Extra controls before the actions, e.g. a theme switch or search. */\n  aside?: ReactNode;\n  /** The element for the brand and nav links, e.g. your router's Link, so moving between pages stays in the app. */\n  link?: ElementType;\n  /** The header width (px) the links and actions need to sit in one row; narrower, they fold into the menu. 1180 suits six or more links beside extra controls. */\n  foldAt?: keyof typeof folds;\n  className?: string;\n}) {\n  const id = useId();\n  const sentinel = useRef<HTMLDivElement>(null);\n  const menuButton = useRef<HTMLButtonElement>(null);\n  const [scrolled, setScrolled] = useState(false);\n  const [menu, setMenu] = useState(false);\n  const fold = folds[foldAt];\n\n  useEffect(() => {\n    const top = sentinel.current;\n    if (!top) return;\n    const observer = new IntersectionObserver(([entry]) => setScrolled(!entry!.isIntersecting));\n    observer.observe(top);\n    return () => observer.disconnect();\n  }, []);\n\n  return (\n    <>\n      <div ref={sentinel} aria-hidden=\"true\" className=\"absolute top-0 h-px w-px\" />\n      <header data-slot=\"site-header\" data-scrolled={scrolled || undefined} className={cn(\"sticky top-0 z-20\", className)}>\n        <div data-slot=\"block-nav-row\">\n          <Link href={brandHref} aria-label={brandLabel} data-slot=\"block-nav-brand\" className={cn(logo && \"inline-flex items-center gap-2.5\")}>{logo}{brand}</Link>\n          {nav.length > 0 && (\n            <nav aria-label=\"Site\" className={cn(\"hidden\", fold.nav)}>\n              <ul data-slot=\"block-nav-list\">\n                {nav.map((item) => (\n                  <li key={item.href}>\n                    <Link href={item.href} aria-current={item.current ? \"page\" : undefined} data-slot=\"block-nav-link\">{item.label}</Link>\n                  </li>\n                ))}\n              </ul>\n            </nav>\n          )}\n          <span className={cn(\"ml-auto hidden items-center gap-2\", fold.row)}>\n            {aside}\n            {signIn && <BlockButton action={signIn} kind=\"secondary\" link={Link} />}\n            {primary && <BlockButton action={primary} link={Link} />}\n          </span>\n          {(nav.length > 0 || signIn) && (\n            <button\n              ref={menuButton}\n              type=\"button\"\n              aria-expanded={menu}\n              aria-controls={`${id}-menu`}\n              onClick={() => setMenu((open) => !open)}\n              // Focus stays on the button after it opens the menu, so Escape has to work here too.\n              onKeyDown={(event) => event.key === \"Escape\" && setMenu(false)}\n              data-slot=\"block-button\"\n              data-kind=\"secondary\"\n              className={cn(\"ml-auto !min-h-11 !px-4\", fold.menu)}\n            >\n              {menu ? <X aria-hidden=\"true\" className=\"size-4\" /> : <Menu aria-hidden=\"true\" className=\"size-4\" />}\n              Menu\n            </button>\n          )}\n        </div>\n        <div\n          id={`${id}-menu`}\n          hidden={!menu}\n          data-slot=\"site-menu\"\n          className={cn(\"absolute inset-x-0 top-full grid gap-1 border-b border-x border-border bg-[var(--elevated-glass)] px-5 pt-2 pb-6 backdrop-blur-[20px] backdrop-saturate-[1.4] [box-shadow:var(--shadow-overlay),inset_0_1px_0_var(--edge-light)]\", fold.menu)}\n          onKeyDown={(event) => {\n            if (event.key === \"Escape\") {\n              setMenu(false);\n              menuButton.current?.focus();\n            }\n          }}\n        >\n          {nav.map((item) => (\n            <Link key={item.href} href={item.href} aria-current={item.current ? \"page\" : undefined} onClick={() => setMenu(false)} className=\"flex h-12 items-center border-b border-[var(--k-line)] text-[17px]\">\n              {item.label}\n            </Link>\n          ))}\n          {menu && aside && <div className=\"mt-4 flex flex-wrap items-center gap-2\">{aside}</div>}\n          <span className=\"mt-4 grid gap-2\">\n            {signIn && <BlockButton action={signIn} kind=\"secondary\" link={Link} />}\n            {primary && <BlockButton action={primary} link={Link} />}\n          </span>\n        </div>\n      </header>\n    </>\n  );\n}\n"},{"path":"ui/button.tsx","type":"registry:ui","target":"components/ui/button.tsx","content":"\"use client\";\n\nimport { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport { LoaderCircle } from \"lucide-react\";\nimport {\n  useLayoutEffect,\n  useRef,\n  useState,\n  type ComponentProps,\n  type ReactNode,\n} from \"react\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/loading-indicator.css\";\nimport \"./button.css\";\n\n/* Colour, material and press live in button.css keyed on data-variant, so a\n   consumer's utility classes still override them. Raised to press: the\n   button sinks 1px into an inner shade instead of scaling. */\nconst variants = {\n  default: \"\",\n  secondary: \"\",\n  ghost: \"\",\n  destructive: \"\",\n};\n\nconst sizes = {\n  sm: \"h-8 gap-1.5 rounded-full px-3 text-[13px] max-md:min-h-11 [@media(pointer:coarse)]:min-h-11\",\n  md: \"h-[var(--control-height)] gap-2 rounded-full px-3.5 text-sm\",\n  lg: \"h-11 gap-2 rounded-full px-[18px] text-[15px]\",\n  icon: \"size-[var(--control-height)] rounded-full p-0\",\n};\n\nexport type ButtonProps = ComponentProps<\"button\"> & {\n  variant?: keyof typeof variants;\n  size?: keyof typeof sizes;\n  asChild?: boolean;\n  /** Shows a spinner over the label and swallows clicks while keeping focus and width. */\n  loading?: boolean;\n  /** Animate a meaningful label/icon change, such as saving → saved. */\n  feedbackKey?: string;\n};\n\nexport function Button({\n  className,\n  variant = \"default\",\n  size = \"md\",\n  asChild = false,\n  loading = false,\n  feedbackKey,\n  disabled,\n  \"aria-disabled\": ariaDisabled,\n  children,\n  onClick,\n  onClickCapture,\n  tabIndex,\n  type = \"button\",\n  ...props\n}: ButtonProps) {\n  const Component = asChild ? Slot : \"button\";\n  const nativeDisabled = Boolean(disabled);\n  const blocked =\n    nativeDisabled ||\n    loading ||\n    ariaDisabled === true ||\n    ariaDisabled === \"true\";\n\n  return (\n    <Component\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      type={asChild ? undefined : type}\n      className={cn(\n        \"relative inline-flex shrink-0 items-center justify-center whitespace-nowrap border border-transparent font-medium tracking-[-0.006em] outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none aria-disabled:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n        variants[variant],\n        sizes[size],\n        className,\n      )}\n      disabled={asChild ? undefined : nativeDisabled}\n      aria-disabled={nativeDisabled || ariaDisabled ? true : undefined}\n      aria-busy={loading || undefined}\n      tabIndex={asChild && nativeDisabled ? -1 : tabIndex}\n      {...props}\n      onKeyDown={(event) => {\n        if (blocked && (event.key === \"Enter\" || event.key === \" \")) {\n          event.preventDefault();\n        }\n        props.onKeyDown?.(event);\n      }}\n      onClickCapture={(event) => {\n        if (blocked) {\n          event.preventDefault();\n          event.stopPropagation();\n          return;\n        }\n        onClickCapture?.(event);\n      }}\n      onClick={onClick}\n    >\n      {loading && asChild && (\n        <LoaderCircle data-slot=\"loading-indicator\" aria-hidden=\"true\" />\n      )}\n      {loading && !asChild && feedbackKey === undefined && (\n        <span data-slot=\"button-busy\" aria-hidden=\"true\">\n          <LoaderCircle data-slot=\"loading-indicator\" />\n        </span>\n      )}\n      <Slottable>\n        {loading && !asChild && feedbackKey === undefined ? (\n          <span data-slot=\"button-label\" data-hidden=\"true\">\n            {children}\n          </span>\n        ) : feedbackKey !== undefined && !asChild ? (\n          <ButtonFeedback stateKey={feedbackKey}>\n            {loading && (\n              <LoaderCircle data-slot=\"loading-indicator\" aria-hidden=\"true\" />\n            )}\n            {children}\n          </ButtonFeedback>\n        ) : (\n          children\n        )}\n      </Slottable>\n    </Component>\n  );\n}\n\nfunction ButtonFeedback({\n  stateKey,\n  children,\n}: {\n  stateKey: string;\n  children: ReactNode;\n}) {\n  const content = useRef<HTMLSpanElement>(null);\n  const [initialKey] = useState(stateKey);\n  const [width, setWidth] = useState<number>();\n  useLayoutEffect(() => {\n    const element = content.current;\n    if (!element) return;\n    const measure = () => setWidth(element.offsetWidth);\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(element);\n    return () => observer.disconnect();\n  }, [stateKey]);\n  return (\n    <span data-slot=\"button-feedback\" style={{ width }}>\n      <span\n        key={stateKey}\n        ref={content}\n        data-slot=\"button-feedback-label\"\n        data-enter={stateKey !== initialKey}\n      >\n        {children}\n      </span>\n    </span>\n  );\n}\n"},{"path":"ui/contact-template.tsx","type":"registry:ui","target":"components/ui/contact-template.tsx","content":"\"use client\";\n\nimport { ArrowRight, CircleCheck, Clock, MapPin } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState, type ComponentProps, type FormEvent } from \"react\";\nimport { Alert } from \"./alert\";\nimport { BlockEyebrow, BlockLead, BlockRoot, BlockTitle, type BlockAction } from \"./block-concept\";\nimport type { BlockNavLink } from \"./block-nav\";\nimport { Button } from \"./button\";\nimport { FaqBlock } from \"./faq-block\";\nimport { Field, isEmail } from \"./field\";\nimport { Input } from \"./input\";\nimport { SelectMenu } from \"./select-menu\";\nimport { TemplatePage, type TemplateFooter } from \"./template-page\";\nimport { Textarea } from \"./textarea\";\nimport \"./contact-template.css\";\n\ntype Without<T> = Omit<T, \"className\">;\n\nexport type ContactTopic = {\n  /** Sent with the message, e.g. \"sales\". */\n  value: string;\n  label: string;\n  /** Who answers and when, e.g. \"The sales team replies within one working day.\" */\n  response: string;\n  /** A direct address for this topic, shown in the channel list. */\n  email?: string;\n};\n\nexport type ContactOffice = { city: string; address: string; hours?: string; timezone?: string; mapHref?: string };\n\nexport type ContactMessage = { topic: string; name: string; email: string; company: string; message: string };\n\ntype Errors = Partial<Record<\"name\" | \"email\" | \"message\", string>>;\n\nconst check = (values: ContactMessage): Errors => ({\n  ...(values.name.trim() ? {} : { name: \"Enter your name.\" }),\n  ...(isEmail(values.email) ? {} : { email: values.email.trim() ? \"Enter an email address like name@company.com.\" : \"Enter your email address.\" }),\n  ...(values.message.trim().length >= 20 ? {} : { message: `Tell us a little more: at least 20 characters (${values.message.trim().length} so far).` }),\n});\n\n/**\n * A contact page that routes the message before it is written: choose\n * what it is about and the page says who answers and how soon. The form\n * validates where you are, keeps what you typed if sending fails, and ends\n * on a confirmation that names where the reply will go. Offices and the\n * usual questions follow. Built only from the Free blocks.\n */\nexport function ContactTemplate({\n  brand,\n  nav,\n  signIn,\n  primary,\n  intro,\n  topics,\n  offices = [],\n  faq,\n  onSubmit,\n  footer,\n  className,\n}: {\n  brand: string;\n  nav?: readonly BlockNavLink[];\n  signIn?: BlockAction;\n  primary?: BlockAction;\n  intro: { eyebrow?: string; title: string; lead?: string };\n  topics: readonly ContactTopic[];\n  offices?: readonly ContactOffice[];\n  faq?: Without<ComponentProps<typeof FaqBlock>>;\n  /** Send the message. A thrown error is shown above the form and nothing typed is lost. */\n  onSubmit: (message: ContactMessage) => void | Promise<void>;\n  footer?: TemplateFooter;\n  className?: string;\n}) {\n  const id = useId();\n  const empty: ContactMessage = { topic: topics[0]?.value ?? \"\", name: \"\", email: \"\", company: \"\", message: \"\" };\n  const [values, setValues] = useState<ContactMessage>(empty);\n  const [errors, setErrors] = useState<Errors>({});\n  const [touched, setTouched] = useState<Record<string, boolean>>({});\n  const [sending, setSending] = useState(false);\n  const [failure, setFailure] = useState<string>();\n  const [sent, setSent] = useState<ContactMessage | null>(null);\n  const trap = useRef<HTMLInputElement>(null);\n  const refs = { name: useRef<HTMLInputElement>(null), email: useRef<HTMLInputElement>(null), message: useRef<HTMLTextAreaElement>(null) };\n  const done = useRef<HTMLHeadingElement>(null);\n  const topic = topics.find((item) => item.value === values.topic) ?? topics[0];\n\n  useEffect(() => {\n    if (sent) done.current?.focus();\n  }, [sent]);\n\n  const set = (key: keyof ContactMessage, value: string) => {\n    const next = { ...values, [key]: value };\n    setValues(next);\n    if (touched[key]) setErrors(check(next));\n  };\n  const blur = (key: keyof Errors) => {\n    setTouched((current) => ({ ...current, [key]: true }));\n    setErrors((current) => ({ ...current, [key]: check(values)[key] }));\n  };\n\n  async function submit(event: FormEvent) {\n    event.preventDefault();\n    if (trap.current?.value) return setSent(values); // A bot filled the hidden field: pretend it went.\n    const found = check(values);\n    setErrors(found);\n    setTouched({ name: true, email: true, message: true });\n    const first = ([\"name\", \"email\", \"message\"] as const).find((key) => found[key]);\n    if (first) return refs[first].current?.focus();\n    setSending(true);\n    setFailure(undefined);\n    try {\n      await onSubmit(values);\n      setSent(values);\n    } catch (reason) {\n      setFailure(reason instanceof Error ? reason.message : \"The message did not send. Try again in a moment.\");\n    } finally {\n      setSending(false);\n    }\n  }\n\n  return (\n    <TemplatePage brand={brand} nav={nav} signIn={signIn} primary={primary} footer={footer} className={className}>\n      <BlockRoot>\n        <div data-slot=\"contact-layout\">\n          <div className=\"grid content-start gap-6\">\n            {intro.eyebrow && <BlockEyebrow>{intro.eyebrow}</BlockEyebrow>}\n            <BlockTitle as=\"h1\" size={2} className=\"max-w-[12ch]\">{intro.title}</BlockTitle>\n            {intro.lead && <BlockLead>{intro.lead}</BlockLead>}\n            <ul data-slot=\"contact-channels\" aria-label=\"Who answers what\">\n              {topics.map((item) => (\n                <li key={item.value}>\n                  <span className=\"text-[15px] font-medium\">{item.label}</span>\n                  <span className=\"text-[14px] leading-relaxed text-[var(--k-muted)]\">{item.response}</span>\n                  {item.email && <a href={`mailto:${item.email}`} className=\"text-[14px] underline underline-offset-4\">{item.email}</a>}\n                </li>\n              ))}\n            </ul>\n          </div>\n\n          <div data-slot=\"contact-card\">\n            {sent ? (\n              <div className=\"grid justify-items-start gap-4\" role=\"status\">\n                <CircleCheck aria-hidden=\"true\" className=\"size-8 text-[var(--k-hi)]\" />\n                <h2 ref={done} tabIndex={-1} className=\"m-0 text-[24px] font-medium tracking-[-0.02em] outline-none\">Message sent</h2>\n                <p className=\"m-0 text-[15px] leading-relaxed text-[var(--k-muted)]\">\n                  Thanks, {sent.name.split(\" \")[0]}. {topics.find((item) => item.value === sent.topic)?.response} We will reply to <b className=\"font-medium text-[var(--k-ink)]\">{sent.email}</b>.\n                </p>\n                <Button\n                  variant=\"secondary\"\n                  onClick={() => {\n                    setSent(null);\n                    setValues({ ...empty, name: sent.name, email: sent.email, company: sent.company });\n                    setTouched({});\n                    setErrors({});\n                  }}\n                >\n                  Send another message\n                </Button>\n              </div>\n            ) : (\n              <form noValidate onSubmit={submit} className=\"grid gap-4\" aria-labelledby={`${id}-form`}>\n                <h2 id={`${id}-form`} className=\"m-0 text-[20px] font-medium tracking-[-0.01em]\">Send a message</h2>\n                {failure && <Alert variant=\"error\" title=\"Not sent\">{failure} Your message is still here.</Alert>}\n                <div className=\"grid gap-1.5\">\n                  <span id={`${id}-topic`} className=\"text-sm font-medium\">What is it about?</span>\n                  <SelectMenu aria-labelledby={`${id}-topic`} name=\"topic\" options={topics.map((item) => ({ value: item.value, label: item.label }))} value={values.topic} onValueChange={(value) => set(\"topic\", value)} disabled={sending} />\n                  <p className=\"m-0 flex items-center gap-1.5 text-[13px] text-[var(--k-muted)]\" aria-live=\"polite\">\n                    <Clock aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n                    {topic?.response}\n                  </p>\n                </div>\n                <div data-slot=\"contact-pair\">\n                  <Field label=\"Name\" required error={errors.name}>\n                    {(props) => <Input {...props} ref={refs.name} name=\"name\" autoComplete=\"name\" value={values.name} disabled={sending} onChange={(e) => set(\"name\", e.target.value)} onBlur={() => blur(\"name\")} />}\n                  </Field>\n                  <Field label=\"Work email\" required error={errors.email}>\n                    {(props) => <Input {...props} ref={refs.email} name=\"email\" type=\"email\" autoComplete=\"email\" value={values.email} disabled={sending} onChange={(e) => set(\"email\", e.target.value)} onBlur={() => blur(\"email\")} />}\n                  </Field>\n                </div>\n                <Field label=\"Company\" optional>\n                  {(props) => <Input {...props} name=\"company\" autoComplete=\"organization\" value={values.company} disabled={sending} onChange={(e) => set(\"company\", e.target.value)} />}\n                </Field>\n                <Field label=\"Message\" required error={errors.message} count={`${values.message.length} / 2000`}>\n                  {(props) => <Textarea {...props} ref={refs.message} name=\"message\" maxLength={2000} value={values.message} disabled={sending} onChange={(e) => set(\"message\", e.target.value)} onBlur={() => blur(\"message\")} />}\n                </Field>\n                <input ref={trap} type=\"text\" name=\"website\" tabIndex={-1} autoComplete=\"off\" aria-hidden=\"true\" className=\"absolute -left-[9999px] h-px w-px opacity-0\" />\n                <div className=\"flex flex-wrap items-center gap-3\">\n                  <Button type=\"submit\" size=\"lg\" loading={sending}>\n                    Send message\n                    <ArrowRight aria-hidden=\"true\" />\n                  </Button>\n                  <span className=\"text-[13px] text-[var(--k-muted)]\">We only use your details to reply.</span>\n                </div>\n              </form>\n            )}\n          </div>\n        </div>\n      </BlockRoot>\n\n      {offices.length > 0 && (\n        <BlockRoot tone=\"sunken\" aria-labelledby={`${id}-offices`}>\n          <BlockTitle id={`${id}-offices`} size={3} className=\"!mb-8\">Offices</BlockTitle>\n          <ul data-slot=\"contact-offices\">\n            {offices.map((office) => (\n              <li key={office.city}>\n                <span data-slot=\"contact-map\" aria-hidden=\"true\">\n                  <MapPin />\n                </span>\n                <span className=\"text-[20px] font-medium tracking-[-0.01em]\">{office.city}</span>\n                <address className=\"text-[15px] not-italic leading-relaxed text-[var(--k-muted)]\">{office.address}</address>\n                {(office.hours || office.timezone) && (\n                  <span className=\"text-[13px] text-[var(--k-muted)]\">\n                    {office.hours}\n                    {office.hours && office.timezone && \" · \"}\n                    {office.timezone}\n                  </span>\n                )}\n                {office.mapHref && <a href={office.mapHref} className=\"text-[14px] underline underline-offset-4\">Directions<span className=\"sr-only\"> to the {office.city} office</span></a>}\n              </li>\n            ))}\n          </ul>\n        </BlockRoot>\n      )}\n\n      {faq && <FaqBlock {...faq} />}\n    </TemplatePage>\n  );\n}\n"},{"path":"ui/faq-block.tsx","type":"registry:ui","target":"components/ui/faq-block.tsx","content":"\"use client\";\n\nimport { Search } from \"lucide-react\";\nimport { useId, useState } from \"react\";\nimport { BlockEyebrow, BlockLead, BlockRoot, BlockTitle } from \"./block-concept\";\nimport \"./faq-block.css\";\n\nexport type BlockFaq = { question: string; answer: string; doc?: { label: string; href: string } };\n\n/**\n * Questions buyers actually ask, searchable, one open at a time, each answer\n * linking to the exact doc. With no match it offers to ask, carrying the\n * query. On wide blocks the heading, search and contact hold the left column\n * while the questions run down the right.\n */\nexport function FaqBlock({\n  eyebrow,\n  title,\n  lead,\n  items,\n  searchable = true,\n  ask,\n  className,\n}: {\n  eyebrow?: string;\n  title: string;\n  lead?: string;\n  items: readonly BlockFaq[];\n  searchable?: boolean;\n  /** Where unanswered questions go, e.g. { label: \"Ask us\", href: \"mailto:help@halden.app\" }. */\n  ask?: { label: string; href: string };\n  className?: string;\n}) {\n  const id = useId();\n  const [query, setQuery] = useState(\"\");\n  const [open, setOpen] = useState(0);\n  const needle = query.trim().toLowerCase();\n  const visible = items.map((item, index) => ({ item, index })).filter(({ item }) => !needle || `${item.question} ${item.answer}`.toLowerCase().includes(needle));\n  const askHref = (text: string) => (ask ? `${ask.href}${ask.href.startsWith(\"mailto:\") && text ? `?subject=${encodeURIComponent(text)}` : \"\"}` : undefined);\n\n  return (\n    <BlockRoot tone=\"sunken\" className={className}>\n      <div className=\"grid gap-[clamp(32px,5cqi,80px)] @[900px]:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]\">\n        <div className=\"grid content-start justify-items-start gap-6 @[900px]:sticky @[900px]:top-8 @[900px]:self-start\">\n          {eyebrow && <BlockEyebrow>{eyebrow}</BlockEyebrow>}\n          <BlockTitle>{title}</BlockTitle>\n          {lead && <BlockLead>{lead}</BlockLead>}\n          {searchable && (\n            <label className=\"flex h-12 w-full max-w-[340px] items-center gap-2.5 rounded-[var(--k-btn-radius)] bg-[var(--k-surface)] px-4 shadow-[inset_0_0_0_1px_var(--k-line-strong)] focus-within:shadow-[inset_0_0_0_2px_var(--k-focus)]\">\n              <Search aria-hidden=\"true\" className=\"size-4 text-[var(--k-muted)]\" />\n              <input\n                type=\"search\"\n                aria-label=\"Search answers\"\n                placeholder=\"Search answers\"\n                value={query}\n                onChange={(event) => setQuery(event.currentTarget.value)}\n                className=\"min-w-0 flex-1 bg-transparent text-[15px] text-[var(--k-ink)] outline-none placeholder:text-[var(--k-muted)]\"\n              />\n            </label>\n          )}\n          {ask && !needle && (\n            <p className=\"text-sm text-[var(--k-muted)]\">\n              Not here? <a href={ask.href} className=\"text-[var(--k-ink)] underline underline-offset-4 hover:text-[var(--k-hi)]\">{ask.label}</a>\n            </p>\n          )}\n        </div>\n        <div>\n          <p className=\"sr-only\" aria-live=\"polite\">{needle ? `${visible.length} ${visible.length === 1 ? \"answer\" : \"answers\"}` : \"\"}</p>\n          <ul className=\"border-b border-[var(--k-line-strong)]\">\n            {visible.map(({ item, index }) => {\n              const expanded = open === index || (needle !== \"\" && visible.length === 1);\n              const panel = `${id}-answer-${index}`;\n              return (\n                <li key={item.question} className=\"border-t border-[var(--k-line-strong)]\">\n                  <h3 className=\"m-0\">\n                    <button\n                      type=\"button\"\n                      aria-expanded={expanded}\n                      aria-controls={panel}\n                      onClick={() => setOpen(expanded ? -1 : index)}\n                      className=\"flex w-full items-center justify-between gap-6 py-6 text-left font-[family-name:var(--k-display-font)] text-[clamp(18px,1.9cqi,23px)] leading-[1.25] font-normal tracking-[-0.01em] outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--k-focus)]\"\n                    >\n                      {item.question}\n                      <span data-slot=\"faq-marker\" aria-hidden=\"true\" />\n                    </button>\n                  </h3>\n                  <div id={panel} data-slot=\"faq-answer\" hidden={!expanded} className=\"max-w-[60ch] pb-6 pr-12 text-[15px] leading-[25px] text-[var(--k-muted)]\">\n                    {item.answer}{\" \"}\n                    {item.doc && <a href={item.doc.href} className=\"text-[var(--k-ink)] underline decoration-[var(--k-hi)] underline-offset-4\">{item.doc.label}</a>}\n                  </div>\n                </li>\n              );\n            })}\n          </ul>\n          {needle && visible.length === 0 && (\n            <p role=\"status\" className=\"py-6 text-[15px] text-[var(--k-muted)]\">\n              No answer mentions “{query.trim()}”.{\" \"}\n              {ask && <a href={askHref(query.trim())} className=\"text-[var(--k-hi)] underline underline-offset-4\">{ask.label}</a>}\n            </p>\n          )}\n        </div>\n      </div>\n    </BlockRoot>\n  );\n}\n"},{"path":"ui/feedback.tsx","type":"registry:ui","target":"components/ui/feedback.tsx","content":"import { CircleCheck, CircleX, Info, LoaderCircle, TriangleAlert } from \"lucide-react\";\nimport \"./feedback.css\";\n\nexport type FeedbackVariant = \"neutral\" | \"success\" | \"warning\" | \"error\";\n\nconst icons = {\n  neutral: Info,\n  success: CircleCheck,\n  warning: TriangleAlert,\n  error: CircleX,\n};\n\nexport function FeedbackIcon({\n  variant,\n  loading = false,\n}: {\n  variant: FeedbackVariant;\n  loading?: boolean;\n}) {\n  const Icon = loading ? LoaderCircle : icons[variant];\n  return (\n    <span data-slot=\"feedback-icon\" aria-hidden=\"true\">\n      <span data-slot=\"feedback-symbol\" key={loading ? \"loading\" : variant}>\n        <Icon data-slot={loading ? \"loading-indicator\" : undefined} />\n      </span>\n    </span>\n  );\n}\n"},{"path":"ui/field.tsx","type":"registry:ui","target":"components/ui/field.tsx","content":"\"use client\";\nimport { CircleAlert, CircleCheck, LoaderCircle } from \"lucide-react\";\nimport { useId, type ReactNode } from \"react\";\nimport { Label } from \"./label\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/field-message.css\";\nimport \"./shared/loading-indicator.css\";\nimport \"./field.css\";\n\nexport type FieldControlProps = {\n  id: string;\n  \"aria-describedby\"?: string;\n  \"aria-invalid\"?: true;\n  \"aria-required\"?: true;\n};\n\nexport const isEmail = (value: string) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value.trim());\n\n/**\n * A label, a control and one message line wired together for assistive\n * technology. Hint, async status and error share the same reserved line and\n * rewrite it in place, so validation never pushes the next field down.\n */\nexport function Field({\n  label,\n  description,\n  error,\n  status,\n  statusTone = \"pending\",\n  count,\n  required,\n  optional,\n  action,\n  className,\n  children,\n}: {\n  label: string;\n  /** Hint shown on the message line until a status or error replaces it. */\n  description?: ReactNode;\n  /** Replaces the hint and is announced when it appears. */\n  error?: string;\n  /** An async result such as “Checking availability…” or “Available”. */\n  status?: string;\n  statusTone?: \"pending\" | \"success\";\n  /** Right-aligned counter on the message line, e.g. “6 / 40”. */\n  count?: string;\n  required?: boolean;\n  /** Adds \"(optional)\" after the label. */\n  optional?: boolean;\n  /** Something aligned with the label, such as a \"Forgot password?\" link. */\n  action?: ReactNode;\n  className?: string;\n  children: (props: FieldControlProps) => ReactNode;\n}) {\n  const id = useId();\n  const message = `${id}-message`;\n  const hasMessage = Boolean(error || status || description || count);\n  const tone = error ? \"error\" : status ? statusTone : \"hint\";\n  return (\n    <div data-slot=\"field\" className={cn(\"grid gap-1.5\", className)}>\n      <div className=\"flex items-baseline justify-between gap-3\">\n        <Label htmlFor={id}>\n          {label}\n          {optional && <span className=\"ml-1 font-normal text-muted-foreground\">(optional)</span>}\n        </Label>\n        {action}\n      </div>\n      {children({\n        id,\n        \"aria-describedby\": hasMessage ? message : undefined,\n        \"aria-invalid\": error ? true : undefined,\n        \"aria-required\": required ? true : undefined,\n      })}\n      <p id={message} data-slot=\"field-message\" data-tone={tone} aria-live={error ? \"assertive\" : \"polite\"} role={error ? \"alert\" : undefined}>\n        {tone === \"error\" && <CircleAlert aria-hidden=\"true\" />}\n        {tone === \"success\" && <CircleCheck aria-hidden=\"true\" />}\n        {tone === \"pending\" && <LoaderCircle data-slot=\"loading-indicator\" aria-hidden=\"true\" />}\n        <span data-slot=\"field-error\" className=\"min-w-0 flex-1\">{error ?? status ?? description}</span>\n        {count && <span data-slot=\"field-count\">{count}</span>}\n      </p>\n    </div>\n  );\n}\n"},{"path":"ui/footer-block.tsx","type":"registry:ui","target":"components/ui/footer-block.tsx","content":"\"use client\";\n\nimport { ArrowRight, Check } from \"lucide-react\";\nimport { useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport { BlockRoot } from \"./block-concept\";\nimport { cn } from \"./lib/cn\";\nimport \"./footer-block.css\";\n\nexport type BlockFooterColumn = { title: string; links: readonly { label: string; href: string }[] };\n\n/** Sizes the wordmark so the name spans the band exactly, whatever its length. */\nfunction Wordmark({ children }: { children: string }) {\n  const ref = useRef<HTMLParagraphElement>(null);\n  useEffect(() => {\n    const node = ref.current;\n    const text = node?.firstElementChild as HTMLElement | null;\n    if (!node || !text) return;\n    // Scale from the current size, so a fitted wordmark measures as fitted and stops.\n    const fit = () => {\n      // Layout width, not the drawn box: inside a scaled preview or a transformed parent the drawn box is smaller.\n      const width = text.offsetWidth;\n      const size = parseFloat(getComputedStyle(node).fontSize);\n      if (!width || !size) return;\n      const next = Math.floor((size * node.clientWidth) / width);\n      if (Math.abs(next - size) >= 1) node.style.setProperty(\"--wordmark-size\", `${next}px`);\n    };\n    fit();\n    const observer = new ResizeObserver(() => requestAnimationFrame(fit));\n    observer.observe(node);\n    document.fonts?.ready.then(fit);\n    return () => observer.disconnect();\n  }, [children]);\n  return (\n    <p ref={ref} data-slot=\"footer-wordmark\" aria-hidden=\"true\">\n      <span>{children}</span>\n    </p>\n  );\n}\n\nconst isEmail = (value: string) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/u.test(value.trim());\n\n/**\n * The page's last word: what the company is, where everything lives, an\n * optional newsletter that confirms in place, the legal line, and the name\n * set as a wordmark across the full width of the band.\n */\nexport function FooterBlock({\n  brand,\n  tagline,\n  columns,\n  newsletter,\n  legal,\n  status,\n  wordmark = true,\n  className,\n}: {\n  brand: string;\n  tagline?: string;\n  columns: readonly BlockFooterColumn[];\n  /** An email sign-up. `onSubmit` may return a promise; the field confirms when it settles. */\n  newsletter?: { label: string; note?: string; onSubmit?: (email: string) => void | Promise<void> };\n  /** e.g. \"© 2026 Halden Ltd · Privacy · Terms\". */\n  legal?: ReactNode;\n  /** A live system status line, e.g. { label: \"All systems normal\", href: \"https://status…\" }. */\n  status?: { label: string; href: string };\n  /** Set the brand as a full-width wordmark at the foot. */\n  wordmark?: boolean;\n  className?: string;\n}) {\n  const id = useId();\n  const [email, setEmail] = useState(\"\");\n  const [state, setState] = useState<\"idle\" | \"invalid\" | \"busy\" | \"done\">(\"idle\");\n\n  async function submit(event: React.FormEvent) {\n    event.preventDefault();\n    if (!isEmail(email)) {\n      setState(\"invalid\");\n      return;\n    }\n    setState(\"busy\");\n    await newsletter?.onSubmit?.(email.trim());\n    setState(\"done\");\n  }\n\n  return (\n    <BlockRoot as=\"footer\" tone=\"sunken\" className={className} innerClassName=\"!pb-0\">\n      <div data-slot=\"footer-block\" className=\"grid gap-[clamp(40px,6cqi,72px)]\">\n        <div className=\"grid gap-12 @[900px]:grid-cols-[minmax(0,1fr)_minmax(0,1.6fr)]\">\n          <div className=\"grid content-start gap-6\">\n            <p data-slot=\"footer-brand\">{brand}</p>\n            {tagline && <p className=\"max-w-[36ch] text-[15px] leading-6 text-[var(--k-muted)]\">{tagline}</p>}\n            {newsletter &&\n              (state === \"done\" ? (\n                <p role=\"status\" className=\"flex items-center gap-2 text-[15px]\">\n                  <Check aria-hidden=\"true\" className=\"size-4 text-[var(--k-hi)]\" />\n                  Check {email.trim()} to confirm.\n                </p>\n              ) : (\n                <form noValidate onSubmit={submit} className=\"grid max-w-[400px] gap-2\">\n                  <label htmlFor={`${id}-email`} className=\"text-sm font-medium\">{newsletter.label}</label>\n                  <div data-slot=\"footer-field\" data-invalid={state === \"invalid\" || undefined}>\n                    <input\n                      id={`${id}-email`}\n                      type=\"email\"\n                      autoComplete=\"email\"\n                      placeholder=\"you@company.com\"\n                      value={email}\n                      aria-invalid={state === \"invalid\" || undefined}\n                      aria-describedby={`${id}-note`}\n                      onChange={(event) => {\n                        setEmail(event.currentTarget.value);\n                        if (state === \"invalid\") setState(\"idle\");\n                      }}\n                    />\n                    <button type=\"submit\" disabled={state === \"busy\"} aria-label={state === \"busy\" ? \"Subscribing\" : \"Subscribe\"}>\n                      <ArrowRight aria-hidden=\"true\" className=\"size-4\" />\n                    </button>\n                  </div>\n                  <p id={`${id}-note`} role={state === \"invalid\" ? \"alert\" : undefined} className={cn(\"min-h-5 text-[13px]\", state === \"invalid\" ? \"text-[var(--k-danger)]\" : \"text-[var(--k-muted)]\")}>\n                    {state === \"invalid\" ? \"Enter an email address like name@company.com.\" : newsletter.note}\n                  </p>\n                </form>\n              ))}\n          </div>\n          <nav aria-label=\"Footer\" className=\"grid grid-cols-2 gap-x-6 gap-y-10 @[640px]:grid-cols-[repeat(var(--cols),minmax(0,1fr))]\" style={{ [\"--cols\" as string]: Math.min(columns.length, 4) }}>\n            {columns.map((column) => (\n              <div key={column.title} className=\"grid content-start gap-4\">\n                <h2 data-slot=\"footer-heading\">{column.title}</h2>\n                <ul className=\"m-0 grid list-none gap-2.5 p-0\">\n                  {column.links.map((link) => (\n                    <li key={link.label}><a href={link.href} data-slot=\"footer-block-link\">{link.label}</a></li>\n                  ))}\n                </ul>\n              </div>\n            ))}\n          </nav>\n        </div>\n        <div className=\"flex flex-wrap items-center justify-between gap-x-8 gap-y-3 border-t border-[var(--k-line-strong)] pt-6 text-[13px] text-[var(--k-muted)]\">\n          {legal && <div>{legal}</div>}\n          {status && (\n            <a href={status.href} className=\"inline-flex items-center gap-2 hover:text-[var(--k-ink)]\">\n              <i aria-hidden=\"true\" data-slot=\"footer-status-dot\" />\n              {status.label}\n            </a>\n          )}\n        </div>\n        {wordmark && (\n          <Wordmark>{brand}</Wordmark>\n        )}\n      </div>\n    </BlockRoot>\n  );\n}\n"},{"path":"ui/input.tsx","type":"registry:ui","target":"components/ui/input.tsx","content":"import type { ComponentProps, ReactNode } from \"react\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/input.css\";\nimport \"./shared/input-2.css\";\nimport \"./shared/input-group.css\";\nimport \"./input.css\";\n\nconst fieldText =\n  \"w-full min-w-0 text-base text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed md:text-sm\";\n\nexport type InputProps = ComponentProps<\"input\"> & {\n  /** Fixed text or an icon before the value, e.g. \"caliper.app/\". */\n  leading?: ReactNode;\n  /** Text, an icon or a status after the value, e.g. a spinner or a count. */\n  trailing?: ReactNode;\n};\n\n/**\n * A recessed text field. Fields sit slightly below the paper (an inner shade\n * and a 3:1 ring); buttons stand on it, so what you fill and what you press\n * never look alike.\n */\nexport function Input({ className, leading, trailing, ...props }: InputProps) {\n  if (leading === undefined && trailing === undefined) {\n    return (\n      <input\n        data-slot=\"input\"\n        className={cn(\"flex h-[var(--control-height)] rounded-button px-3\", fieldText, className)}\n        {...props}\n      />\n    );\n  }\n  return (\n    <span\n      data-slot=\"input-group\"\n      data-disabled={props.disabled || undefined}\n      data-invalid={props[\"aria-invalid\"] === true || props[\"aria-invalid\"] === \"true\" || undefined}\n      data-readonly={props.readOnly || undefined}\n      className={cn(\"flex h-[var(--control-height)] w-full min-w-0 items-center gap-2 rounded-button px-3\", className)}\n    >\n      {leading !== undefined && <span data-slot=\"input-affix\">{leading}</span>}\n      <input data-slot=\"input-bare\" className={cn(\"h-full flex-1 bg-transparent\", fieldText)} {...props} />\n      {trailing !== undefined && <span data-slot=\"input-trailing\">{trailing}</span>}\n    </span>\n  );\n}\n"},{"path":"ui/label.tsx","type":"registry:ui","target":"components/ui/label.tsx","content":"import type { ComponentProps } from \"react\";\nimport { cn } from \"./lib/cn\";\n\nexport function Label({ className, ...props }: ComponentProps<\"label\">) {\n  return (\n    <label\n      data-slot=\"label\"\n      className={cn(\n        \"inline-block max-w-full text-sm leading-5 font-medium [overflow-wrap:anywhere]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"},{"path":"ui/lib/cn.ts","type":"registry:ui","target":"components/ui/lib/cn.ts","content":"import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n  extend: { theme: { radius: [\"control\", \"surface\", \"overlay\"], shadow: [\"overlay\"] } },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"},{"path":"ui/select-menu.tsx","type":"registry:ui","target":"components/ui/select-menu.tsx","content":"\"use client\";\n\nimport * as Popover from \"@radix-ui/react-popover\";\nimport { Check, ChevronsUpDown } from \"lucide-react\";\nimport {\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type ComponentProps,\n  type KeyboardEvent,\n  type ReactNode,\n  type Ref,\n} from \"react\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/select.css\";\nimport \"./shared/input-2.css\";\nimport \"./select-menu.css\";\n\nexport type SelectMenuOption = {\n  value: string;\n  label: string;\n  /** Short identifier shown in mono at the end of the row, e.g. \"eu-central-1\". */\n  code?: string;\n  /** A 16px mark before the label, e.g. a network dot or a person's initials. */\n  icon?: ReactNode;\n  disabled?: boolean;\n};\n\nconst ROW = 36;\nconst PAD = 4;\n// The list is a little wider than its trigger and starts before it, so the chosen row sits over the trigger.\nconst WIDER = 36;\nconst ALIGN = 28;\nconst EDGE = 12;\n\nfunction useCoarsePointer() {\n  const [coarse, setCoarse] = useState(false);\n  useEffect(() => {\n    const query = window.matchMedia(\"(pointer: coarse)\");\n    const update = () => setCoarse(query.matches);\n    update();\n    query.addEventListener(\"change\", update);\n    return () => query.removeEventListener(\"change\", update);\n  }, []);\n  return coarse;\n}\n\n/**\n * A select whose list opens with the chosen option lying exactly over the\n * trigger text, so the eye never jumps. On touch screens it is the platform\n * picker. A hidden input carries the value in native forms.\n */\nexport function SelectMenu({\n  options,\n  value: controlled,\n  defaultValue,\n  onValueChange,\n  placeholder = \"Choose…\",\n  name,\n  disabled,\n  size = \"md\",\n  variant = \"field\",\n  className,\n  ref,\n  ...aria\n}: {\n  options: readonly SelectMenuOption[];\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  placeholder?: string;\n  /** Submitted with the form. */\n  name?: string;\n  disabled?: boolean;\n  /** sm is 32px for toolbars, filters and table rows; md follows the control height. */\n  size?: \"sm\" | \"md\";\n  /** ghost drops the field ring, for a select inside a composed control (a currency in a money field). */\n  variant?: \"field\" | \"ghost\";\n  className?: string;\n  /** The trigger (or, on touch screens, the native select), e.g. for a dialog's first focus. */\n  ref?: Ref<HTMLButtonElement | HTMLSelectElement>;\n} & Pick<ComponentProps<\"button\">, \"id\" | \"aria-label\" | \"aria-labelledby\" | \"aria-describedby\" | \"aria-invalid\" | \"aria-required\">) {\n  const listId = useId();\n  const coarse = useCoarsePointer();\n  const [inner, setInner] = useState(defaultValue);\n  const value = controlled ?? inner;\n  const [open, setOpen] = useState(false);\n  const [active, setActive] = useState(0);\n  const [offset, setOffset] = useState(0);\n  // Keeps the list inside the dialog or card it opens in, when laying it over the trigger would cross the edge.\n  const [shift, setShift] = useState(0);\n  const [room, setRoom] = useState<number | undefined>(undefined);\n  const trigger = useRef<HTMLButtonElement>(null);\n  const list = useRef<HTMLDivElement>(null);\n  const selectedIndex = Math.max(0, options.findIndex((option) => option.value === value));\n  const selected = options.find((option) => option.value === value);\n\n  // If the change handler moves focus on (e.g. into a reply box), leave it there when the list closes.\n  const keepFocus = useRef(false);\n  // A form may disable the trigger while it saves the choice. Focus cannot return to a disabled button,\n  // so it returns as soon as the trigger is enabled again, unless the reader has moved on by then.\n  const refocus = useRef(false);\n  useEffect(() => {\n    if (disabled || !refocus.current) return;\n    refocus.current = false;\n    const now = document.activeElement;\n    if (!now || now === document.body) trigger.current?.focus();\n  }, [disabled]);\n  function choose(next: string) {\n    if (controlled === undefined) setInner(next);\n    onValueChange?.(next);\n    const now = document.activeElement;\n    keepFocus.current = !!now && now !== document.body && now !== trigger.current && !list.current?.contains(now);\n  }\n\n  function openList(nextOpen: boolean) {\n    if (nextOpen && trigger.current) {\n      // Lay the selected row over the trigger, unless that would leave the viewport.\n      const rect = trigger.current.getBoundingClientRect();\n      const above = PAD + selectedIndex * ROW;\n      const fits = rect.top - above > 8 && rect.top - above + options.length * ROW + PAD * 2 < window.innerHeight - 8;\n      setOffset(fits ? -(rect.height + above) - (ROW - rect.height) / 2 : 6);\n      const card = trigger.current.closest<HTMLElement>('[role=\"dialog\"]')?.getBoundingClientRect();\n      const left = rect.left - ALIGN;\n      const right = left + rect.width + WIDER;\n      setShift(!card ? 0 : left < card.left + EDGE ? card.left + EDGE - left : right > card.right - EDGE ? card.right - EDGE - right : 0);\n      setRoom(card ? card.width - EDGE * 2 : undefined);\n      setActive(selectedIndex);\n    }\n    setOpen(nextOpen);\n  }\n\n  function move(delta: number) {\n    let next = active;\n    for (let step = 0; step < options.length; step += 1) {\n      next = Math.min(options.length - 1, Math.max(0, next + delta));\n      if (!options[next].disabled) break;\n    }\n    setActive(next);\n  }\n\n  function onListKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n    if (event.key === \"ArrowDown\") move(1);\n    else if (event.key === \"ArrowUp\") move(-1);\n    else if (event.key === \"Home\") setActive(options.findIndex((option) => !option.disabled));\n    else if (event.key === \"End\") setActive(options.length - 1 - [...options].reverse().findIndex((option) => !option.disabled));\n    else if (event.key === \"Enter\" || event.key === \" \") {\n      const option = options[active];\n      if (option && !option.disabled) {\n        choose(option.value);\n        setOpen(false);\n      }\n    } else if (event.key === \"Tab\") setOpen(false);\n    else if (/^\\S$/.test(event.key)) {\n      const start = (active + 1) % options.length;\n      const order = [...options.slice(start), ...options.slice(0, start)];\n      const hit = order.find((option) => !option.disabled && option.label.toLowerCase().startsWith(event.key.toLowerCase()));\n      if (hit) setActive(options.indexOf(hit));\n    } else return;\n    event.preventDefault();\n  }\n\n  useEffect(() => {\n    if (!open) return;\n    list.current?.querySelector<HTMLElement>(`[data-index=\"${active}\"]`)?.scrollIntoView({ block: \"nearest\" });\n  }, [active, open]);\n\n  if (coarse) {\n    return (\n      <span data-slot=\"select-menu-native\" data-size={size} data-variant={variant} className={cn(\"relative block\", className)}>\n        <select\n          ref={ref as Ref<HTMLSelectElement>}\n          data-slot=\"select\"\n          {...aria}\n          name={name}\n          disabled={disabled}\n          value={value ?? \"\"}\n          onChange={(event) => choose(event.target.value)}\n          className={cn(\n            \"block w-full min-w-0 appearance-none text-base text-foreground outline-none\",\n            size === \"sm\" ? \"h-8 rounded-control pr-8 pl-2.5\" : \"h-[var(--control-height)] rounded-button pr-9 pl-3\",\n          )}\n        >\n          {!selected && <option value=\"\" disabled>{placeholder}</option>}\n          {options.map((option) => (\n            <option key={option.value} value={option.value} disabled={option.disabled}>\n              {option.label}\n              {option.code ? ` · ${option.code}` : \"\"}\n            </option>\n          ))}\n        </select>\n        <ChevronsUpDown aria-hidden=\"true\" className={cn(\"pointer-events-none absolute top-1/2 size-4 -translate-y-1/2 text-muted-foreground\", size === \"sm\" ? \"right-2.5\" : \"right-3\")} />\n      </span>\n    );\n  }\n\n  return (\n    <Popover.Root open={open} onOpenChange={openList}>\n      <Popover.Trigger asChild>\n        <button\n          ref={(node) => {\n            trigger.current = node;\n            if (typeof ref === \"function\") ref(node);\n            else if (ref) (ref as { current: HTMLButtonElement | HTMLSelectElement | null }).current = node;\n          }}\n          type=\"button\"\n          data-slot=\"select-menu\"\n          data-size={size}\n          data-variant={variant}\n          role=\"combobox\"\n          aria-haspopup=\"listbox\"\n          aria-expanded={open}\n          aria-controls={open ? listId : undefined}\n          disabled={disabled}\n          {...aria}\n          onKeyDown={(event) => {\n            if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n              event.preventDefault();\n              openList(true);\n            }\n          }}\n          className={cn(\n            \"flex w-full min-w-0 items-center justify-between gap-2 text-left text-foreground outline-none disabled:cursor-not-allowed\",\n            size === \"sm\" ? \"h-8 rounded-control px-2.5 text-[13px]\" : \"h-[var(--control-height)] rounded-button px-3 text-base md:text-sm\",\n            className,\n          )}\n        >\n          <span className={cn(\"flex min-w-0 items-center gap-2\", !selected && \"text-muted-foreground\")}>\n            {selected?.icon && <span data-slot=\"select-menu-icon\" aria-hidden=\"true\">{selected.icon}</span>}\n            <span className=\"truncate\">{selected ? selected.label : placeholder}</span>\n            {selected?.code && <span data-slot=\"select-menu-code\" className=\"shrink-0\">{selected.code}</span>}\n          </span>\n          <ChevronsUpDown aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n        </button>\n      </Popover.Trigger>\n      {name && <input type=\"hidden\" name={name} value={value ?? \"\"} />}\n      <Popover.Portal>\n        <Popover.Content\n          side=\"bottom\"\n          align=\"start\"\n          sideOffset={offset}\n          alignOffset={shift - ALIGN}\n          avoidCollisions={offset > 0}\n          onOpenAutoFocus={(event) => {\n            event.preventDefault();\n            keepFocus.current = false;\n            list.current?.focus();\n          }}\n          onCloseAutoFocus={(event) => {\n            if (keepFocus.current) event.preventDefault();\n            else if (trigger.current?.disabled) {\n              event.preventDefault();\n              refocus.current = true;\n            }\n          }}\n          data-slot=\"select-menu-content\"\n          style={{ width: `calc(var(--radix-popover-trigger-width) + ${WIDER}px)`, maxWidth: room, transformOrigin: `50% ${PAD + selectedIndex * ROW + ROW / 2}px` }}\n        >\n          <div\n            ref={list}\n            id={listId}\n            role=\"listbox\"\n            tabIndex={-1}\n            aria-labelledby={aria[\"aria-labelledby\"]}\n            aria-label={aria[\"aria-label\"]}\n            aria-activedescendant={`${listId}-${active}`}\n            onKeyDown={onListKeyDown}\n            className=\"max-h-[calc(8*36px+8px)] overflow-y-auto p-1 outline-none\"\n          >\n            {options.map((option, index) => (\n              <div\n                key={option.value}\n                id={`${listId}-${index}`}\n                role=\"option\"\n                data-index={index}\n                aria-selected={option.value === value}\n                aria-disabled={option.disabled || undefined}\n                data-active={index === active}\n                data-slot=\"select-menu-option\"\n                onPointerMove={() => !option.disabled && setActive(index)}\n                onClick={() => {\n                  if (option.disabled) return;\n                  choose(option.value);\n                  setOpen(false);\n                }}\n              >\n                <span className=\"grid size-4 place-items-center\">{option.value === value && <Check aria-hidden=\"true\" className=\"size-4\" />}</span>\n                {option.icon && <span data-slot=\"select-menu-icon\" aria-hidden=\"true\">{option.icon}</span>}\n                <span className=\"min-w-0 flex-1 truncate\">{option.label}</span>\n                {option.code && <span data-slot=\"select-menu-code\">{option.code}</span>}\n              </div>\n            ))}\n          </div>\n        </Popover.Content>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n"},{"path":"ui/stage-scope.tsx","type":"registry:ui","target":"components/ui/stage-scope.tsx","content":"\"use client\";\n\nimport { createContext, useContext, type ReactNode } from \"react\";\n\n/**\n * True inside a block stage: a frame shown there is a demonstration on a page\n * that already has its own main landmark, so it should not add a second one.\n */\nconst InStage = createContext(false);\n\nexport function StageScope({ children }: { children: ReactNode }) {\n  return <InStage.Provider value={true}>{children}</InStage.Provider>;\n}\n\nexport const useInStage = () => useContext(InStage);\n"},{"path":"ui/template-page.tsx","type":"registry:ui","target":"components/ui/template-page.tsx","content":"\"use client\";\n\nimport { useEffect, useId, useState, type ComponentProps, type ReactNode } from \"react\";\nimport type { BlockAction } from \"./block-concept\";\nimport { BlockNav, type BlockNavLink } from \"./block-nav\";\nimport { FooterBlock } from \"./footer-block\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/skip-link.css\";\nimport \"./template-page.css\";\n\nexport type TemplateFooter = Omit<ComponentProps<typeof FooterBlock>, \"brand\" | \"className\"> & { brand?: string };\n\n/**\n * The frame every Free page template shares: a skip link, the site header,\n * one main region that can take focus, and the footer block. Pages put\n * their bands inside.\n */\nexport function TemplatePage({\n  brand,\n  nav = [],\n  signIn,\n  primary,\n  footer,\n  className,\n  children,\n}: {\n  brand: string;\n  nav?: readonly BlockNavLink[];\n  signIn?: BlockAction;\n  primary?: BlockAction;\n  footer?: TemplateFooter;\n  className?: string;\n  children: ReactNode;\n}) {\n  const id = useId();\n  return (\n    <div data-slot=\"block\" className={cn(\"@container relative\", className)}>\n      <a href={`#${id}-main`} data-slot=\"skip-link\">Skip to content</a>\n      <BlockNav brand={brand} nav={nav} signIn={signIn} primary={primary} />\n      <main id={`${id}-main`} tabIndex={-1} className=\"outline-none\">\n        {children}\n      </main>\n      {footer && <FooterBlock {...footer} brand={footer.brand ?? brand} />}\n    </div>\n  );\n}\n\n/** The nearest element that scrolls, or null for the window. */\nexport function scrollParent(element: HTMLElement | null): HTMLElement | null {\n  for (let node = element?.parentElement; node; node = node.parentElement) {\n    const { overflowY } = getComputedStyle(node);\n    if ((overflowY === \"auto\" || overflowY === \"scroll\") && node.scrollHeight > node.clientHeight) return node;\n  }\n  return null;\n}\n\n/**\n * Which section the reader is in: the last heading that has passed a line\n * just under the sticky header. Works in the window or a scrolling panel.\n */\nexport function useScrollSpy(ids: readonly string[], offset = 120) {\n  const [active, setActive] = useState(ids[0] ?? \"\");\n  const key = ids.join(\"|\");\n  useEffect(() => {\n    const first = ids[0] ? document.getElementById(ids[0]) : null;\n    if (!first) return;\n    const scroller = scrollParent(first);\n    const target: HTMLElement | Window = scroller ?? window;\n    const update = () => {\n      const top = scroller ? scroller.getBoundingClientRect().top : 0;\n      let current = ids[0]!;\n      for (const id of ids) {\n        const element = document.getElementById(id);\n        if (element && element.getBoundingClientRect().top - top <= offset) current = id;\n      }\n      setActive(current);\n    };\n    update();\n    target.addEventListener(\"scroll\", update, { passive: true });\n    window.addEventListener(\"resize\", update);\n    return () => {\n      target.removeEventListener(\"scroll\", update);\n      window.removeEventListener(\"resize\", update);\n    };\n    // ids are compared by value through `key`.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [key, offset]);\n  return active;\n}\n\n/** How far through an element the reader is, 0 to 1. */\nexport function useReadingProgress(element: HTMLElement | null) {\n  const [progress, setProgress] = useState(0);\n  useEffect(() => {\n    if (!element) return;\n    const scroller = scrollParent(element);\n    const target: HTMLElement | Window = scroller ?? window;\n    const update = () => {\n      const rect = element.getBoundingClientRect();\n      const top = scroller ? scroller.getBoundingClientRect().top : 0;\n      const view = scroller ? scroller.clientHeight : window.innerHeight;\n      const travel = rect.height - view;\n      setProgress(travel <= 0 ? 1 : Math.min(1, Math.max(0, (top - rect.top) / travel)));\n    };\n    update();\n    target.addEventListener(\"scroll\", update, { passive: true });\n    window.addEventListener(\"resize\", update);\n    return () => {\n      target.removeEventListener(\"scroll\", update);\n      window.removeEventListener(\"resize\", update);\n    };\n  }, [element]);\n  return progress;\n}\n\n/** Jumps to a section inside whatever scrolls, then moves focus to its heading. */\nexport function goToSection(id: string) {\n  const element = document.getElementById(id);\n  if (!element) return;\n  const smooth = !window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n  element.scrollIntoView({ behavior: smooth ? \"smooth\" : \"auto\", block: \"start\" });\n  const heading = element.matches(\"h1, h2, h3\") ? element : element.querySelector<HTMLElement>(\"h2, h3\");\n  heading?.setAttribute(\"tabindex\", \"-1\");\n  heading?.focus({ preventScroll: true });\n}\n\n/**\n * The page outline: numbered when `numbered`, with the reader's section\n * marked as current. On narrow pages it folds into a disclosure.\n */\nexport function TemplateOutline({\n  items,\n  active,\n  label = \"On this page\",\n  numbered = false,\n  className,\n}: {\n  items: readonly { id: string; title: string }[];\n  active: string;\n  label?: string;\n  numbered?: boolean;\n  className?: string;\n}) {\n  const list = (\n    <ol data-slot=\"template-outline-list\" data-numbered={numbered || undefined}>\n      {items.map((item, index) => (\n        <li key={item.id}>\n          <a\n            href={`#${item.id}`}\n            aria-current={active === item.id ? \"location\" : undefined}\n            onClick={(event) => {\n              event.preventDefault();\n              goToSection(item.id);\n              history.replaceState(null, \"\", `#${item.id}`);\n            }}\n          >\n            {numbered && <span data-slot=\"template-outline-number\">{index + 1}.</span>}\n            {item.title}\n          </a>\n        </li>\n      ))}\n    </ol>\n  );\n  return (\n    <nav aria-label={label} data-slot=\"template-outline\" className={className}>\n      <p data-slot=\"template-outline-label\">{label}</p>\n      {list}\n      <details data-slot=\"template-outline-fold\">\n        <summary>{label}</summary>\n        {list}\n      </details>\n    </nav>\n  );\n}\n"},{"path":"ui/textarea.tsx","type":"registry:ui","target":"components/ui/textarea.tsx","content":"import type { ComponentProps } from \"react\";\nimport { cn } from \"./lib/cn\";\nimport \"./shared/input-2.css\";\nimport \"./textarea.css\";\n\n/** A recessed multi-line field that grows with the draft from 3 to 8 lines, then scrolls. */\nexport function Textarea({ className, ...props }: ComponentProps<\"textarea\">) {\n  return (\n    <textarea\n      data-slot=\"textarea\"\n      className={cn(\n        \"block min-h-[88px] max-h-[200px] w-full min-w-0 resize-none rounded-button px-3 py-[9px] text-base leading-[22px] text-foreground outline-none [field-sizing:content] placeholder:text-muted-foreground disabled:cursor-not-allowed md:text-sm md:leading-[22px]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"},{"path":"ui/shared/alert.css","type":"registry:file","target":"components/ui/shared/alert.css","content":"/* Shared by alert.tsx, toast.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"feedback-message\"] {\n    display: block;\n    animation: caliper-fade-in var(--duration-normal) var(--ease-out);\n  }\n  :is([data-slot=\"alert\"], [data-slot=\"toast\"])[data-variant=\"success\"]\n    [data-slot=\"feedback-icon\"] {\n    background: var(--success-subtle);\n    color: var(--success);\n  }\n  :is([data-slot=\"alert\"], [data-slot=\"toast\"])[data-variant=\"warning\"]\n    [data-slot=\"feedback-icon\"] {\n    background: var(--warning-subtle);\n    color: var(--warning);\n  }\n  :is([data-slot=\"alert\"], [data-slot=\"toast\"])[data-variant=\"error\"]\n    [data-slot=\"feedback-icon\"] {\n    background: var(--destructive-subtle);\n    color: var(--destructive);\n  }\n  @container (max-width: 22rem) {\n    [data-slot=\"feedback-copy\"] {\n      display: contents;\n    }\n    [data-slot=\"feedback-title\"] {\n      grid-column: 2;\n      grid-row: 1;\n      min-width: 0;\n      padding-top: 0.25rem;\n    }\n    [data-slot=\"feedback-description\"],\n    [data-slot=\"feedback-action\"] {\n      grid-column: 1 / -1;\n      margin-top: 0;\n    }\n    [data-slot=\"feedback-description\"] {\n      grid-row: 2;\n    }\n    [data-slot=\"feedback-action\"] {\n      grid-row: 3;\n    }\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"feedback-message\"] {\n      animation: none;\n    }\n  }\n}\n"},{"path":"ui/alert.css","type":"registry:file","target":"components/ui/alert.css","content":"/* alert.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"alert\"] {\n    container-type: inline-size;\n    opacity: 1;\n    transform: translateY(0);\n    transition-property: opacity, transform, display;\n    transition-duration: var(--duration-normal);\n    transition-timing-function: var(--ease-out);\n    transition-behavior: allow-discrete;\n  }\n  [data-slot=\"alert\"][hidden] {\n    display: none !important;\n    opacity: 0;\n    pointer-events: none;\n    transition-duration: var(--duration-fast);\n  }\n  @starting-style {\n    [data-slot=\"alert\"]:not([hidden]) {\n      opacity: 0;\n      transform: translateY(var(--motion-enter-distance));\n    }\n  }\n  @container (max-width: 22rem) {\n    [data-slot=\"alert\"] > [data-slot=\"button\"] {\n      grid-column: 3;\n      grid-row: 1;\n    }\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"alert\"] {\n      transition: none;\n      transform: none !important;\n    }\n    @starting-style {\n      [data-slot=\"alert\"]:not([hidden]) {\n        opacity: 1;\n        transform: none;\n      }\n    }\n  }\n}\n"},{"path":"ui/block-concept.css","type":"registry:file","target":"components/ui/block-concept.css","content":"/* block-concept.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* A site header used outside a block still needs the block tokens. */\n  [data-slot=\"block\"] {\n    --k-display-font: var(--font-display-block, var(--font-sans, system-ui, sans-serif));\n    --k-display-weight: 700;\n    --k-display-case: none;\n    --k-display-track: -0.03em;\n    --k-display-stretch: normal;\n    --k-display-lh: 1.04;\n    /* Monochrome, from the system tokens, so blocks follow the page theme. */\n    --k-bg: var(--background, #fff); --k-surface: var(--elevated, #fff); --k-sunken: var(--surface, #f7f7f8); --k-ink: var(--foreground, #18181b); --k-muted: var(--muted-foreground, #5d5d66); --k-line: var(--border, #e4e4e8);\n    --k-line-strong: color-mix(in oklab, var(--border-strong, #8a8a94) 55%, var(--k-line));\n    /* The grey second line of a two-tone title; 3:1 or better as large text in both themes. */\n    --k-faint: color-mix(in oklab, var(--k-muted) 70%, var(--k-bg));\n    --k-accent: var(--k-ink); --k-accent-ink: var(--k-bg); --k-hi: var(--accent-text, var(--k-ink)); --k-hi-soft: var(--accent-soft, var(--k-sunken));\n    --k-focus: var(--brand, #bb4400); --k-danger: var(--danger, #c2272d);\n    --k-stage: var(--surface, #f7f7f8); --k-stage-dot: color-mix(in oklab, var(--k-ink) 14%, var(--k-stage));\n    --k-shadow: none;\n    --k-radius: 12px; --k-radius-lg: 16px; --k-radius-xl: 20px; --k-btn-radius: 999px;\n    --k-tile-1: color-mix(in oklab, var(--k-ink) 5%, var(--k-bg)); --k-tile-2: color-mix(in oklab, var(--k-ink) 8%, var(--k-bg)); --k-tile-3: color-mix(in oklab, var(--k-ink) 11%, var(--k-bg)); --k-tile-4: color-mix(in oklab, var(--k-ink) 14%, var(--k-bg));\n    background: var(--k-bg);\n    color: var(--k-ink);\n    font-family: var(--font-sans, system-ui, sans-serif);\n  }\n  /* Band tones inside a concept: a sunken band, or an inverse ink band. */\n  [data-slot=\"block\"][data-tone=\"sunken\"] { background: var(--k-sunken); }\n\n  [data-slot=\"block-inner\"] {\n    position: relative;\n    z-index: 1;\n    margin-inline: auto;\n    max-inline-size: 1240px;\n    padding-inline: clamp(20px, 5cqi, 64px);\n    padding-block: clamp(64px, 10cqi, 128px);\n  }\n  [data-slot=\"block-inner\"][data-width=\"narrow\"] { max-inline-size: 960px; }\n  [data-slot=\"block-inner\"][data-width=\"wide\"] { max-inline-size: 1440px; }\n\n  /* ── Type ─────────────────────────────────────────── */\n  [data-slot=\"block-eyebrow\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: 8px;\n    margin: 0;\n    font-size: 14px;\n    line-height: 20px;\n    font-weight: 500;\n    color: var(--k-muted);\n  }\n\n  [data-slot=\"block-title\"] {\n    margin: 0;\n    font-family: var(--k-display-font);\n    font-weight: var(--k-display-weight);\n    font-stretch: var(--k-display-stretch);\n    font-variation-settings: \"wdth\" var(--k-display-wdth, 100);\n    text-transform: var(--k-display-case);\n    letter-spacing: var(--k-display-track);\n    font-optical-sizing: auto;\n    text-wrap: balance;\n    color: var(--k-ink);\n  }\n  [data-slot=\"block-title\"][data-size=\"1\"] { font-size: clamp(40px, 5cqi, 64px); line-height: 1.04; font-weight: 700; letter-spacing: -0.048em; }\n  [data-slot=\"block-title\"][data-size=\"2\"] { font-size: clamp(30px, 3.4cqi, 40px); line-height: 1.1; letter-spacing: -0.04em; }\n  [data-slot=\"block-title\"][data-size=\"3\"] { font-size: clamp(19px, 1.7cqi, 22px); line-height: 1.2; font-weight: 600; letter-spacing: -0.015em; }\n  /* Two-tone titles: the emphasised words land in grey. */\n  [data-slot=\"block-em\"] { font-style: normal; color: var(--k-faint); }\n\n  [data-slot=\"block-lead\"] {\n    margin: 0;\n    max-width: 50ch;\n    font-size: clamp(16px, 1.3cqi, 18px);\n    line-height: 1.6;\n    color: var(--k-muted);\n    text-wrap: pretty;\n  }\n\n  /* The shared section opening. */\n  [data-slot=\"block-header\"] {\n    display: grid;\n    gap: 20px 48px;\n    margin-bottom: clamp(36px, 5cqi, 64px);\n  }\n  [data-slot=\"block-header-title\"] { display: grid; gap: 18px; justify-items: start; }\n  [data-slot=\"block-header-aside\"] { display: grid; gap: 16px; align-content: end; justify-items: start; }\n  [data-slot=\"block-header\"][data-align=\"center\"] { justify-items: center; text-align: center; }\n  [data-slot=\"block-header\"][data-align=\"center\"] :is([data-slot=\"block-header-title\"], [data-slot=\"block-header-aside\"]) { justify-items: center; }\n  [data-slot=\"block-header\"][data-align=\"center\"] [data-slot=\"block-title\"] { max-width: 18ch; }\n  @container (min-width: 900px) {\n    [data-slot=\"block-header\"][data-align=\"split\"] { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); align-items: end; }\n    [data-slot=\"block-header\"][data-align=\"split\"] [data-slot=\"block-header-aside\"] { justify-items: start; padding-bottom: 6px; }\n  }\n\n  /* ── Buttons ──────────────────────────────────────── */\n  [data-slot=\"block-button\"] {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    min-height: 40px;\n    padding: 0 16px;\n    border: 0;\n    border-radius: var(--k-btn-radius);\n    font: 500 14px/1 var(--font-sans, system-ui, sans-serif);\n    letter-spacing: -0.005em;\n    text-decoration: none;\n    white-space: nowrap;\n    cursor: pointer;\n    transition: background-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), translate var(--duration-normal) var(--ease-settle);\n  }\n  [data-slot=\"block-button\"][data-kind=\"primary\"] {\n    background: var(--k-accent);\n    color: var(--k-accent-ink);\n    box-shadow: inset 0 1px 0 rgb(255 255 255 / 14%), 0 1px 2px rgb(0 0 0 / 20%);\n  }\n  [data-slot=\"block-button\"][data-kind=\"secondary\"] {\n    background: color-mix(in srgb, var(--k-surface) 60%, transparent);\n    color: var(--k-ink);\n    box-shadow: inset 0 0 0 1px var(--k-line-strong);\n  }\n  [data-slot=\"block-button-arrow\"] { transition: translate var(--duration-normal) var(--ease-settle); }\n  @media (hover: hover) {\n    [data-slot=\"block-button\"][data-kind=\"primary\"]:hover { background: color-mix(in srgb, var(--k-accent) 86%, var(--k-bg)); }\n    [data-slot=\"block-button\"][data-kind=\"secondary\"]:hover { background: var(--k-sunken); box-shadow: inset 0 0 0 1px var(--k-ink); }\n    [data-slot=\"block-button\"]:hover [data-slot=\"block-button-arrow\"] { translate: 3px 0; }\n  }\n  [data-slot=\"block-button\"]:active { translate: 0 var(--motion-press-travel); }\n  [data-slot=\"block-button\"]:focus-visible { outline: 2px solid var(--k-focus); outline-offset: 3px; }\n\n  [data-slot=\"block-card\"] {\n    border-radius: var(--k-radius-lg);\n    background: var(--k-surface);\n    box-shadow: 0 0 0 1px var(--k-line);\n  }\n  [data-slot=\"block-chip\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: 6px;\n    min-height: 28px;\n    padding: 0 12px;\n    border-radius: 999px;\n    font-size: 13px;\n    font-weight: 500;\n    background: var(--k-hi-soft);\n    color: var(--k-ink);\n    box-shadow: inset 0 0 0 1px var(--k-line);\n    text-decoration: none;\n  }\n  /* People in blocks are the library's light glass orbs (as Avatar), one pastel tone per person. */\n  [data-slot=\"block-initials\"] {\n    --av-a: #9ec5ff;\n    --av-b: #c4b2ff;\n    display: inline-grid;\n    flex: none;\n    place-items: center;\n    border-radius: 999px;\n    font-weight: 600;\n    color: #161616;\n    background:\n      radial-gradient(circle at 30% 24%, rgb(255 255 255 / 80%), rgb(255 255 255 / 0%) 36%),\n      radial-gradient(circle at 76% 84%, color-mix(in oklab, var(--av-b) 78%, #3b2a6b), transparent 58%),\n      linear-gradient(140deg, var(--av-a) 12%, var(--av-b) 88%);\n    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 34%), inset 0 -4px 8px rgb(0 0 0 / 8%);\n  }\n  [data-slot=\"block-initials\"][data-tone=\"1\"] { --av-a: #a3f0c8; --av-b: #7fd3e6; }\n  [data-slot=\"block-initials\"][data-tone=\"2\"] { --av-a: #c9adff; --av-b: #ffb2d8; }\n  [data-slot=\"block-initials\"][data-tone=\"3\"] { --av-a: #ffd6a0; --av-b: #ffab86; }\n\n  /* ── Stage: a product artefact framed on a tonal panel ─ */\n  [data-slot=\"block-stage\"] {\n    position: relative;\n    padding: clamp(14px, 4.5cqi, 64px);\n    padding-bottom: 0;\n    border-radius: var(--k-radius-xl);\n    background-color: var(--k-stage);\n    background-image: radial-gradient(var(--k-stage-dot) 1px, transparent 1.2px);\n    background-size: 16px 16px;\n    box-shadow: inset 0 0 0 1px var(--k-line);\n    overflow: hidden;\n  }\n  [data-slot=\"block-window\"] {\n    position: relative;\n    overflow: hidden;\n    border-radius: var(--k-radius-lg) var(--k-radius-lg) 0 0;\n    background: var(--k-surface);\n    box-shadow: 0 0 0 1px var(--k-line), var(--k-shadow);\n  }\n  [data-slot=\"block-window-bar\"] {\n    display: flex; align-items: center; gap: 12px;\n    height: 40px; padding: 0 16px;\n    border-bottom: 1px solid var(--k-line);\n    background: color-mix(in srgb, var(--k-sunken) 55%, var(--k-surface));\n  }\n  [data-slot=\"block-window-dots\"] { display: none; }\n  [data-slot=\"block-window-dots\"] i { width: 10px; height: 10px; border-radius: 999px; background: var(--k-line-strong); }\n  [data-slot=\"block-window-title\"] {\n    flex: 1 1 0; min-width: 0; width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n    text-align: left;\n    font-family: var(--font-mono, monospace); font-size: 12px; color: var(--k-muted);\n  }\n  [data-slot=\"block-window-body\"] { padding: clamp(14px, 2.6cqi, 28px); }\n  [data-slot=\"block-float\"] {\n    position: relative;\n    z-index: 2;\n    margin: 12px 0 14px;\n  }\n  @container (min-width: 760px) {\n    /* Over the window bar's empty right end, so it never hides a row. */\n    [data-slot=\"block-float\"] {\n      position: absolute;\n      top: clamp(12px, 2.4cqi, 36px);\n      right: clamp(12px, 2.4cqi, 36px);\n      margin: 0;\n      max-width: 320px;\n    }\n  }\n  [data-slot=\"block-float\"] > * {\n    border-radius: var(--k-radius);\n    background: var(--k-surface);\n    box-shadow: 0 0 0 1px var(--k-line), var(--k-shadow);\n  }\n\n  /* ── Logos ────────────────────────────────────────── */\n  [data-slot=\"block-logos\"] { display: grid; gap: 18px; }\n  [data-slot=\"block-logos-label\"] { margin: 0; font-size: 13px; color: var(--k-muted); }\n  [data-slot=\"block-logos\"] ul { display: flex; flex-wrap: wrap; align-items: center; gap: 14px clamp(24px, 4.5cqi, 56px); margin: 0; padding: 0; list-style: none; }\n  [data-slot=\"block-logos\"] li {\n    font-family: var(--k-display-font);\n    font-weight: var(--k-display-weight);\n    font-stretch: var(--k-display-stretch);\n    text-transform: var(--k-display-case);\n    font-size: clamp(18px, 2cqi, 24px);\n    letter-spacing: calc(var(--k-display-track) / 2);\n    color: var(--k-muted);\n    white-space: nowrap;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"block-button\"], [data-slot=\"block-button-arrow\"] { transition: none; }\n    [data-slot=\"block-button\"]:active { translate: none; }\n    [data-slot=\"block-button\"]:hover [data-slot=\"block-button-arrow\"] { translate: none; }\n  }\n  @media print {\n    footer[data-slot=\"block\"] { display: none !important; }\n    [data-slot=\"block\"] { background: #fff; }\n  }\n}\n"},{"path":"ui/shared/site-header.css","type":"registry:file","target":"components/ui/shared/site-header.css","content":"/* Shared by block-nav.tsx, site-header.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"site-header\"] {\n    background: color-mix(in srgb, var(--background) 92%, transparent);\n    border-bottom: 1px solid transparent;\n    transition: border-color var(--duration-normal) var(--ease-out);\n  }\n  [data-slot=\"site-header\"][data-sticky=\"true\"] {\n    position: sticky;\n    top: 0;\n    -webkit-backdrop-filter: blur(10px);\n    backdrop-filter: blur(10px);\n  }\n  [data-slot=\"site-header\"][data-scrolled=\"true\"] {\n    border-bottom-color: var(--border);\n  }\n  [data-slot=\"site-menu\"] {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n  [data-slot=\"site-menu\"] a {\n    display: grid;\n    gap: 0.125rem;\n    padding: 0.5rem 0.75rem;\n    border-radius: var(--radius-control);\n    text-decoration: none;\n    color: inherit;\n    outline: none;\n    transition: background-color var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"site-menu\"] a:hover,\n  [data-slot=\"site-menu\"] a:focus-visible {\n    background: var(--muted);\n  }\n  [data-slot=\"site-menu\"] a:focus-visible {\n    outline: 2px solid var(--ring);\n    outline-offset: -2px;\n  }\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"site-header\"] {\n      transition: none;\n    }\n  }\n  /* A site header used outside a block still needs the block tokens. */\n  [data-slot=\"site-header\"] {\n    --k-display-font: var(--font-display-block, var(--font-sans, system-ui, sans-serif));\n    --k-display-weight: 700;\n    --k-display-case: none;\n    --k-display-track: -0.03em;\n    --k-display-stretch: normal;\n    --k-display-lh: 1.04;\n    /* Monochrome, from the system tokens, so blocks follow the page theme. */\n    --k-bg: var(--background, #fff); --k-surface: var(--elevated, #fff); --k-sunken: var(--surface, #f7f7f8); --k-ink: var(--foreground, #18181b); --k-muted: var(--muted-foreground, #5d5d66); --k-line: var(--border, #e4e4e8);\n    --k-line-strong: color-mix(in oklab, var(--border-strong, #8a8a94) 55%, var(--k-line));\n    /* The grey second line of a two-tone title; 3:1 or better as large text in both themes. */\n    --k-faint: color-mix(in oklab, var(--k-muted) 70%, var(--k-bg));\n    --k-accent: var(--k-ink); --k-accent-ink: var(--k-bg); --k-hi: var(--accent-text, var(--k-ink)); --k-hi-soft: var(--accent-soft, var(--k-sunken));\n    --k-focus: var(--brand, #bb4400); --k-danger: var(--danger, #c2272d);\n    --k-stage: var(--surface, #f7f7f8); --k-stage-dot: color-mix(in oklab, var(--k-ink) 14%, var(--k-stage));\n    --k-shadow: none;\n    --k-radius: 12px; --k-radius-lg: 16px; --k-radius-xl: 20px; --k-btn-radius: 999px;\n    --k-tile-1: color-mix(in oklab, var(--k-ink) 5%, var(--k-bg)); --k-tile-2: color-mix(in oklab, var(--k-ink) 8%, var(--k-bg)); --k-tile-3: color-mix(in oklab, var(--k-ink) 11%, var(--k-bg)); --k-tile-4: color-mix(in oklab, var(--k-ink) 14%, var(--k-bg));\n    background: var(--k-bg);\n    color: var(--k-ink);\n    font-family: var(--font-sans, system-ui, sans-serif);\n  }\n\n  /* Landing: header gains a hairline once scrolled; phone action bar. */\n  [data-slot=\"site-header\"] { background: var(--k-bg); transition: box-shadow var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out); }\n  [data-slot=\"site-header\"][data-scrolled] {\n    background: color-mix(in srgb, var(--k-bg) 84%, transparent);\n    -webkit-backdrop-filter: blur(var(--blur-overlay, 20px)) saturate(1.2); backdrop-filter: blur(var(--blur-overlay, 20px)) saturate(1.2);\n    box-shadow: 0 1px 0 var(--k-line);\n  }\n  [data-slot=\"site-menu\"][hidden] { display: none; }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"site-header\"] { transition: none; }\n  }\n  @media print {\n    [data-slot=\"site-header\"] { display: none !important; }\n  }\n}\n"},{"path":"ui/block-nav.css","type":"registry:file","target":"components/ui/block-nav.css","content":"/* block-nav.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* ── Site nav (shared by page templates) ─────────── */\n  [data-slot=\"block-nav-row\"] {\n    display: flex; align-items: center; gap: 28px;\n    max-inline-size: 1240px; height: 68px; margin-inline: auto;\n    padding-inline: clamp(20px, 5cqi, 64px);\n  }\n  [data-slot=\"block-nav-brand\"] {\n    font-family: var(--k-display-font); font-weight: var(--k-display-weight); font-stretch: var(--k-display-stretch);\n    font-variation-settings: \"wdth\" var(--k-display-wdth, 100);\n    text-transform: var(--k-display-case); letter-spacing: calc(var(--k-display-track) / 2);\n    font-size: 22px; line-height: 1; color: var(--k-ink); text-decoration: none; white-space: nowrap;\n  }\n  [data-slot=\"block-nav-brand\"]:focus-visible, [data-slot=\"block-nav-link\"]:focus-visible { outline: 2px solid var(--k-focus); outline-offset: 4px; border-radius: 4px; }\n  [data-slot=\"block-nav-list\"] { display: flex; gap: 4px; margin: 0; padding: 0; list-style: none; }\n  [data-slot=\"block-nav-link\"] {\n    display: inline-flex; align-items: center; height: 36px; padding: 0 12px; border-radius: var(--k-btn-radius);\n    font-size: 14px; color: var(--k-muted); text-decoration: none;\n    transition: color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"block-nav-link\"]:hover, [data-slot=\"block-nav-link\"][aria-current=\"page\"] { color: var(--k-ink); background: color-mix(in srgb, var(--k-ink) 6%, transparent); }\n  [data-slot=\"site-header\"] [data-slot=\"block-button\"] { min-height: 40px; padding-inline: 16px; font-size: 14px; }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"block-nav-link\"] { transition: none; }\n  }\n  [data-slot=\"site-menu\"] [data-slot=\"block-button\"] { justify-content: center; }\n}\n"},{"path":"ui/shared/loading-indicator.css","type":"registry:file","target":"components/ui/shared/loading-indicator.css","content":"/* Shared by button.tsx, confirm-morph.tsx, expanding-button-group.tsx, field.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"loading-indicator\"] {\n    animation: caliper-spin var(--duration-loading) var(--ease-linear) infinite;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"loading-indicator\"] {\n      animation: none;\n    }\n  }\n}\n"},{"path":"ui/button.css","type":"registry:file","target":"components/ui/button.css","content":"/* button.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"button\"] {\n    transition-property: background-color, color, border-color;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-out);\n  }\n\n  /* Button material. Raised to press: a lit top edge and a small lift; the\n     press travels 1px down and swaps the lift for an inner shade. Only\n     translate animates (release on the settle curve); shadows swap. */\n  [data-slot=\"button\"] {\n    --b-bg: var(--raised);\n    --b-bg-hover: var(--raised-hover);\n    --b-bg-press: var(--raised-press);\n    --b-fg: var(--foreground);\n    --b-shadow: var(--shadow-raised);\n    --b-shadow-press: var(--shadow-raised-press);\n    background-color: var(--b-bg);\n    color: var(--b-fg);\n    box-shadow: var(--b-shadow);\n    transition-property: background-color, color, border-color, translate, scale;\n    transition-duration:\n      var(--duration-fast), var(--duration-fast), var(--duration-fast),\n      var(--duration-normal), var(--duration-normal);\n    transition-timing-function:\n      var(--ease-out), var(--ease-out), var(--ease-out), var(--ease-settle),\n      var(--ease-settle);\n  }\n  [data-slot=\"button\"][data-variant=\"default\"] {\n    --b-bg: var(--primary);\n    --b-bg-hover: var(--primary-hover);\n    --b-bg-press: var(--primary-press);\n    --b-fg: var(--primary-foreground);\n    --b-shadow: var(--shadow-accent);\n    --b-shadow-press: var(--shadow-accent-press);\n  }\n  [data-slot=\"button\"][data-variant=\"destructive\"] {\n    --b-bg: var(--danger-fill);\n    --b-bg-hover: var(--danger-fill-hover);\n    --b-bg-press: var(--danger-fill-press);\n    --b-fg: var(--danger-fill-foreground);\n    --b-shadow: var(--shadow-danger);\n    --b-shadow-press: var(--shadow-danger-press);\n  }\n  [data-slot=\"button\"][data-variant=\"ghost\"] {\n    --b-bg: transparent;\n    --b-bg-hover: var(--muted);\n    --b-bg-press: var(--muted);\n    --b-fg: var(--muted-foreground);\n    --b-shadow: none;\n    --b-shadow-press: none;\n  }\n  @media (hover: hover) {\n    [data-slot=\"button\"]:hover {\n      background-color: var(--b-bg-hover);\n    }\n    [data-slot=\"button\"][data-variant=\"ghost\"]:hover {\n      color: var(--foreground);\n    }\n  }\n  [data-slot=\"button\"]:active:not(:disabled):not([aria-disabled=\"true\"]):not(\n      [aria-busy=\"true\"]\n    ) {\n    translate: 0 var(--motion-press-travel);\n    scale: var(--motion-press-scale);\n    background-color: var(--b-bg-press);\n    box-shadow: var(--b-shadow-press);\n    transition-duration:\n      var(--duration-fast), var(--duration-fast), var(--duration-fast), 90ms,\n      90ms;\n    transition-timing-function: var(--ease-out);\n  }\n  [data-slot=\"button\"]:is(:disabled, [aria-disabled=\"true\"]):not([aria-busy=\"true\"]) {\n    background-color: var(--sunken);\n    color: var(--muted-foreground);\n  }\n  [data-slot=\"button\"][data-variant=\"ghost\"]:is(:disabled, [aria-disabled=\"true\"]):not(\n      [aria-busy=\"true\"]\n    ) {\n    background-color: transparent;\n    box-shadow: none;\n  }\n  /* Loading keeps the exact width: the label stays in flow but invisible. */\n  [data-slot=\"button-label\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: inherit;\n  }\n  /* Opacity, not visibility, so the label still names the button. */\n  [data-slot=\"button-label\"][data-hidden=\"true\"] {\n    opacity: 0;\n  }\n  [data-slot=\"button-busy\"] {\n    position: absolute;\n    inset: 0;\n    display: grid;\n    place-items: center;\n  }\n  [data-slot=\"button-feedback\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: inherit;\n    overflow: clip;\n    transition: width var(--duration-normal) var(--ease-out);\n  }\n  [data-slot=\"button-feedback-label\"] {\n    display: inline-flex;\n    flex-shrink: 0;\n    align-items: center;\n    gap: inherit;\n    width: max-content;\n  }\n  [data-slot=\"button-feedback-label\"][data-enter=\"true\"] {\n    animation: caliper-feedback-enter var(--duration-normal) var(--ease-out);\n  }\n\n  [data-slot=\"button\"][aria-busy=\"true\"] {\n    pointer-events: none;\n  }\n  [data-slot=\"button\"][data-action-phase=\"error\"] {\n    color: var(--danger);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"button\"],\n    [data-slot=\"button-feedback\"] {\n      transition: none;\n    }\n    [data-slot=\"button\"]:active:not(:disabled):not([aria-disabled=\"true\"]):not(\n        [aria-busy=\"true\"]\n      ) {\n      translate: none;\n    }\n    [data-slot=\"button-feedback-label\"],\n    [data-slot=\"button-feedback-label\"][data-enter=\"true\"] {\n      animation: none;\n    }\n  }\n\n  /* Toggle: pressed is recessed, as a toggled tool in a button group is. */\n  /* Four attributes, so it wins over the group and toolbar pressed rules in any stylesheet order. */\n  [data-slot=\"button\"][data-variant=\"ghost\"][data-toggle][aria-pressed=\"true\"] {\n    --b-bg: var(--raised-press);\n    --b-bg-hover: var(--raised-press);\n    --b-bg-press: var(--raised-press);\n    --b-fg: var(--foreground);\n    --b-shadow: var(--shadow-sunken);\n    --b-shadow-press: var(--shadow-sunken);\n  }\n}\n"},{"path":"ui/contact-template.css","type":"registry:file","target":"components/ui/contact-template.css","content":"/* contact-template.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* Contact */\n  [data-slot=\"contact-layout\"] { display: grid; gap: 40px; }\n  @container (min-width: 960px) { [data-slot=\"contact-layout\"] { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 72px; align-items: start; } }\n  [data-slot=\"contact-channels\"] { display: grid; margin: 12px 0 0; padding: 0; list-style: none; border-top: 1px solid var(--k-line-strong); }\n  [data-slot=\"contact-channels\"] li { display: grid; gap: 4px; padding-block: 16px; border-bottom: 1px solid var(--k-line); }\n  [data-slot=\"contact-card\"] { position: relative; padding: clamp(20px, 3.4cqi, 36px); border-radius: var(--k-radius-xl); background: var(--k-surface); box-shadow: 0 0 0 1px var(--k-line), var(--k-shadow); }\n  [data-slot=\"contact-pair\"] { display: grid; gap: 16px; }\n  @container (min-width: 560px) { [data-slot=\"contact-pair\"] { grid-template-columns: repeat(2, minmax(0, 1fr)); } }\n  [data-slot=\"contact-offices\"] { display: grid; gap: 20px; margin: 0; padding: 0; list-style: none; }\n  @container (min-width: 760px) { [data-slot=\"contact-offices\"] { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); } }\n  [data-slot=\"contact-offices\"] li { display: grid; align-content: start; gap: 8px; padding: 22px; border-radius: var(--k-radius-lg); background: var(--k-bg); box-shadow: 0 0 0 1px var(--k-line); }\n  [data-slot=\"contact-map\"] {\n    display: grid; place-items: center; height: 112px; margin: -8px -8px 6px; border-radius: var(--k-radius);\n    background: radial-gradient(circle, var(--k-stage-dot) 1px, transparent 1.2px) 0 0 / 12px 12px, var(--k-stage); color: var(--k-hi);\n  }\n  [data-slot=\"contact-map\"] svg { width: 26px; height: 26px; fill: var(--k-bg); }\n}\n"},{"path":"ui/faq-block.css","type":"registry:file","target":"components/ui/faq-block.css","content":"/* faq-block.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* FAQ answers open in place; the marker turns. */\n  [data-slot=\"faq-marker\"] {\n    position: relative; display: inline-grid; place-items: center; flex: none;\n    width: 32px; height: 32px; border-radius: 999px;\n    box-shadow: inset 0 0 0 1px var(--k-line-strong);\n    transition: rotate var(--duration-deliberate) var(--ease-settle), background-color var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"faq-marker\"]::before, [data-slot=\"faq-marker\"]::after { content: \"\"; position: absolute; width: 12px; height: 1.5px; border-radius: 1px; background: currentColor; }\n  [data-slot=\"faq-marker\"]::after { rotate: 90deg; transition: rotate var(--duration-deliberate) var(--ease-settle); }\n  [aria-expanded=\"true\"] > [data-slot=\"faq-marker\"] { background: var(--k-ink); color: var(--k-bg); box-shadow: none; }\n  [aria-expanded=\"true\"] > [data-slot=\"faq-marker\"]::after { rotate: 0deg; }\n  [data-slot=\"faq-answer\"]:not([hidden]) { animation: caliper-faq-open var(--duration-deliberate) var(--ease-out); }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"faq-answer\"]:not([hidden]) { animation: none; }\n    [data-slot=\"faq-marker\"], [data-slot=\"faq-marker\"]::after { transition: none; }\n  }\n}\n"},{"path":"ui/feedback.css","type":"registry:file","target":"components/ui/feedback.css","content":"/* feedback.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"feedback-icon\"] {\n    display: flex;\n    width: 2rem;\n    height: 2rem;\n    flex-shrink: 0;\n    align-items: center;\n    justify-content: center;\n    border-radius: var(--radius-surface);\n    background: var(--muted);\n    color: var(--muted-foreground);\n    transition:\n      color var(--duration-fast) var(--ease-out),\n      background-color var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"feedback-symbol\"] {\n    display: flex;\n    animation: caliper-feedback-symbol-enter var(--duration-normal) var(--ease-out);\n  }\n  [data-slot=\"feedback-icon\"] svg {\n    width: 1rem;\n    height: 1rem;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"feedback-icon\"] {\n      transition: none;\n      transform: none !important;\n    }\n    [data-slot=\"feedback-symbol\"] {\n      animation: none;\n    }\n  }\n}\n"},{"path":"ui/shared/field-message.css","type":"registry:file","target":"components/ui/shared/field-message.css","content":"/* Shared by date-input.tsx, field.tsx, scrub-field.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"field-message\"] {\n    display: flex;\n    align-items: flex-start;\n    gap: 6px;\n    min-height: 18px;\n    margin: 0;\n    font-size: 12px;\n    line-height: 18px;\n    color: var(--muted-foreground);\n  }\n  [data-slot=\"field-message\"] svg {\n    flex: none;\n    width: 14px;\n    height: 14px;\n    margin-top: 2px;\n  }\n  [data-slot=\"field-message\"][data-tone=\"error\"] {\n    color: var(--danger);\n  }\n  [data-slot=\"field-message\"][data-tone=\"success\"] {\n    color: var(--success);\n  }\n}\n"},{"path":"ui/field.css","type":"registry:file","target":"components/ui/field.css","content":"/* field.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"field-count\"] {\n    margin-left: auto;\n    color: var(--muted-foreground);\n    font-variant-numeric: tabular-nums;\n    font-size: 12px;\n    font-variant-numeric: tabular-nums;\n  }\n}\n"},{"path":"ui/footer-block.css","type":"registry:file","target":"components/ui/footer-block.css","content":"/* footer-block.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* ── Footer ───────────────────────────────────────── */\n  [data-slot=\"footer-brand\"] {\n    margin: 0; font-family: var(--k-display-font); font-weight: var(--k-display-weight); font-stretch: var(--k-display-stretch);\n    font-variation-settings: \"wdth\" var(--k-display-wdth, 100); text-transform: var(--k-display-case);\n    letter-spacing: calc(var(--k-display-track) / 2); font-size: 28px; line-height: 1;\n  }\n  /* A label, not code: Geist at the label step (DESIGN-SYSTEM.md keeps mono for code, commands and keycaps). */\n  [data-slot=\"footer-heading\"] { margin: 0; font-size: 13px; line-height: 18px; font-weight: 500; color: var(--k-muted); }\n  [data-slot=\"footer-block-link\"] { font-size: 15px; color: var(--k-ink); text-decoration: none; text-underline-offset: 4px; }\n  [data-slot=\"footer-block-link\"]:hover { text-decoration: underline; text-decoration-color: var(--k-hi); }\n  [data-slot=\"footer-block-link\"]:focus-visible { outline: 2px solid var(--k-focus); outline-offset: 3px; border-radius: 3px; }\n  [data-slot=\"footer-field\"] {\n    display: flex; align-items: center; gap: 6px; height: 50px; padding: 5px 5px 5px 16px;\n    border-radius: var(--k-btn-radius); background: var(--k-surface); box-shadow: inset 0 0 0 1px var(--k-line-strong);\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"footer-field\"]:focus-within { box-shadow: inset 0 0 0 2px var(--k-focus); }\n  [data-slot=\"footer-field\"][data-invalid] { box-shadow: inset 0 0 0 2px var(--k-danger); }\n  [data-slot=\"footer-field\"] input { flex: 1; min-width: 0; border: 0; background: transparent; color: var(--k-ink); font-size: 15px; outline: none; }\n  [data-slot=\"footer-field\"] input::placeholder { color: var(--k-muted); }\n  [data-slot=\"footer-field\"] button {\n    display: grid; place-items: center; width: 40px; height: 40px; flex: none; border-radius: calc(var(--k-btn-radius) - 4px);\n    background: var(--k-accent); color: var(--k-accent-ink); cursor: pointer;\n  }\n  [data-slot=\"footer-field\"] button:focus-visible { outline: 2px solid var(--k-focus); outline-offset: 2px; }\n  [data-slot=\"footer-field\"] button:disabled { opacity: 0.6; cursor: progress; }\n  [data-slot=\"footer-status-dot\"] { width: 8px; height: 8px; border-radius: 999px; background: var(--success, #0b7643); box-shadow: 0 0 0 3px color-mix(in srgb, var(--success, #0b7643) 22%, transparent); }\n  [data-slot=\"footer-wordmark\"] {\n    margin: 0 0 -0.16em; overflow: hidden;\n    font-family: var(--k-display-font); font-weight: var(--k-display-weight); font-stretch: var(--k-display-stretch);\n    font-variation-settings: \"wdth\" var(--k-display-wdth, 100); text-transform: var(--k-display-case);\n    font-size: var(--wordmark-size, clamp(72px, 21cqi, 300px)); line-height: 0.82; letter-spacing: -0.06em; white-space: nowrap;\n    color: var(--k-ink); user-select: none;\n  }\n  [data-slot=\"footer-wordmark\"] > span { display: inline-block; padding-right: 0.04em; }\n  [data-slot=\"footer-wordmark\"] { font-weight: 700; }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"footer-field\"] { transition: none; }\n  }\n}\n"},{"path":"ui/shared/input.css","type":"registry:file","target":"components/ui/shared/input.css","content":"/* Shared by combobox.tsx, input.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"input\"] {\n    transition-property: background-color, color, border-color;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-out);\n  }\n\n  /* Recessed to fill. One ring for every field: 3:1 at rest, darker on\n     hover, a 2px ember ring with a soft halo on focus, danger when invalid.\n     Disabled and read-only drop into the sunken fill and stay legible. */\n  [data-slot=\"input\"] {\n    border: 0;\n    background-color: var(--field);\n    box-shadow: var(--shadow-field);\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"input\"] {\n      transition: none;\n    }\n  }\n}\n"},{"path":"ui/shared/input-2.css","type":"registry:file","target":"components/ui/shared/input-2.css","content":"/* Shared by combobox.tsx, input.tsx, select-menu.tsx, select.tsx, textarea.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  @media (hover: hover) {\n    :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"]):hover:enabled:not(:focus-visible):not([aria-invalid=\"true\"]) {\n      box-shadow: var(--shadow-field-hover);\n    }\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"]):focus-visible {\n    outline: none;\n    box-shadow: var(--shadow-field-focus);\n    transition: none;\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"])[aria-invalid=\"true\"] {\n    box-shadow: var(--shadow-field-invalid);\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"])[aria-invalid=\"true\"]:focus-visible {\n    box-shadow:\n      var(--shadow-field-invalid),\n      0 0 0 4px color-mix(in srgb, var(--danger) 18%, transparent);\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"], [data-slot=\"select\"]):disabled {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n    color: var(--muted-foreground);\n  }\n  :is([data-slot=\"input\"], [data-slot=\"textarea\"])[readonly] {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n  }\n}\n"},{"path":"ui/shared/input-group.css","type":"registry:file","target":"components/ui/shared/input-group.css","content":"/* Shared by input.tsx, money-field.tsx, phone-field.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  @media (hover: hover) {\n    :is([data-slot=\"input-group\"], [data-slot=\"money-field\"], [data-slot=\"phone-field-control\"]):not([data-disabled]):not([data-invalid]):hover:not(:focus-within) {\n      box-shadow: var(--shadow-field-hover);\n    }\n  }\n  :is([data-slot=\"input-group\"], [data-slot=\"money-field\"], [data-slot=\"phone-field-control\"]):focus-within {\n    outline: none;\n    box-shadow: var(--shadow-field-focus);\n    transition: none;\n  }\n}\n"},{"path":"ui/input.css","type":"registry:file","target":"components/ui/input.css","content":"/* input.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  /* Recessed to fill. One ring for every field: 3:1 at rest, darker on\n     hover, a 2px ember ring with a soft halo on focus, danger when invalid.\n     Disabled and read-only drop into the sunken fill and stay legible. */\n  [data-slot=\"input-group\"] {\n    border: 0;\n    background-color: var(--field);\n    box-shadow: var(--shadow-field);\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"input-group\"][data-invalid] {\n    box-shadow: var(--shadow-field-invalid);\n  }\n  [data-slot=\"input-group\"][data-invalid]:focus-within {\n    box-shadow:\n      var(--shadow-field-invalid),\n      0 0 0 4px color-mix(in srgb, var(--danger) 18%, transparent);\n  }\n  [data-slot=\"input-group\"][data-disabled] {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n    color: var(--muted-foreground);\n  }\n  [data-slot=\"input-group\"][data-readonly] {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n  }\n  [data-slot=\"input-bare\"] {\n    border: 0;\n    box-shadow: none;\n    outline: none;\n  }\n  [data-slot=\"input-affix\"] {\n    display: inline-flex;\n    align-items: center;\n    height: 20px;\n    padding-inline-end: 10px;\n    margin-inline-end: 2px;\n    color: var(--muted-foreground);\n    box-shadow: 1px 0 0 var(--border);\n    white-space: nowrap;\n  }\n  [data-slot=\"input-affix\"]:dir(rtl) {\n    box-shadow: -1px 0 0 var(--border);\n  }\n  [data-slot=\"input-affix\"]:has(> svg:only-child) {\n    padding-inline-end: 0;\n    box-shadow: none;\n  }\n  [data-slot=\"input-trailing\"] {\n    display: inline-flex;\n    align-items: center;\n    gap: 6px;\n    color: var(--muted-foreground);\n    white-space: nowrap;\n    font-size: 12px;\n  }\n  :is([data-slot=\"input-affix\"], [data-slot=\"input-trailing\"]) svg {\n    width: 16px;\n    height: 16px;\n  }\n  [data-slot=\"docs-toolbar\"] [data-slot=\"input-group\"] { max-width: 24rem; }\n}\n"},{"path":"ui/shared/select.css","type":"registry:file","target":"components/ui/shared/select.css","content":"/* Shared by select-menu.tsx, select.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"select\"] {\n    transition-property: background-color, color, border-color;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-out);\n  }\n\n  [data-slot=\"select\"] {\n    cursor: pointer;\n    touch-action: manipulation;\n  }\n  /* The native select wears the select-menu trigger: no platform arrow, the\n     same up/down mark on the right. Lists (multiple or size > 1) keep theirs. */\n  [data-slot=\"select\"]:not([multiple]):not([size]) {\n    appearance: none;\n    padding-right: 36px;\n    background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235d5d66' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 15 5 5 5-5'/%3E%3Cpath d='m7 9 5-5 5 5'/%3E%3C/svg%3E\");\n    background-repeat: no-repeat;\n    background-position: right 10px center;\n    background-size: 16px 16px;\n  }\n  [data-theme=\"dark\"] [data-slot=\"select\"]:not([multiple]):not([size]),\n  [data-slot=\"select\"][data-theme=\"dark\"]:not([multiple]):not([size]) {\n    background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23ababb4' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 15 5 5 5-5'/%3E%3Cpath d='m7 9 5-5 5 5'/%3E%3C/svg%3E\");\n  }\n\n  /* Recessed to fill. One ring for every field: 3:1 at rest, darker on\n     hover, a 2px ember ring with a soft halo on focus, danger when invalid.\n     Disabled and read-only drop into the sunken fill and stay legible. */\n  [data-slot=\"select\"] {\n    border: 0;\n    background-color: var(--field);\n    box-shadow: var(--shadow-field);\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n\n  [data-slot=\"select\"]:disabled {\n    cursor: not-allowed;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"select\"] {\n      transition: none;\n    }\n  }\n}\n"},{"path":"ui/select-menu.css","type":"registry:file","target":"components/ui/select-menu.css","content":"/* select-menu.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"select-menu-native\"] [data-slot=\"select\"] {\n    background-image: none;\n  }\n  /* Select menu: the field trigger, and a frosted list that opens with the\n     chosen row over the trigger and grows around it. */\n  [data-slot=\"select-menu\"] {\n    border: 0;\n    background-color: var(--field);\n    box-shadow: var(--shadow-field);\n    cursor: pointer;\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n  @media (hover: hover) {\n    [data-slot=\"select-menu\"]:hover:enabled:not(:focus-visible):not([aria-expanded=\"true\"]):not([aria-invalid=\"true\"]) {\n      box-shadow: var(--shadow-field-hover);\n    }\n  }\n  [data-slot=\"select-menu\"]:focus-visible,\n  [data-slot=\"select-menu\"][aria-expanded=\"true\"] {\n    box-shadow: var(--shadow-field-focus);\n  }\n  [data-slot=\"select-menu\"][aria-invalid=\"true\"] {\n    box-shadow: var(--shadow-field-invalid);\n  }\n  [data-slot=\"select-menu\"]:disabled {\n    background-color: var(--sunken);\n    box-shadow: var(--shadow-field-quiet);\n    color: var(--muted-foreground);\n  }\n  [data-slot=\"select-menu-code\"] {\n    margin-left: auto;\n    font-family: var(--font-mono);\n    font-size: 12px;\n    color: var(--muted-foreground);\n  }\n  [data-slot=\"select-menu-content\"] {\n    z-index: 50;\n    border-radius: var(--radius-surface);\n    background: var(--elevated-glass);\n    -webkit-backdrop-filter: blur(var(--blur-overlay));\n    backdrop-filter: blur(var(--blur-overlay));\n    box-shadow: var(--shadow-overlay), 0 0 0 1px var(--edge-glow), inset 0 1px 0 var(--edge-light);\n    color: var(--foreground);\n    animation: caliper-select-in var(--duration-normal) var(--ease-out);\n  }\n  [data-slot=\"select-menu-option\"] {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    height: 36px;\n    padding: 0 10px;\n    border-radius: var(--radius-control);\n    font-size: 14px;\n    cursor: default;\n    transition: background-color var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"select-menu-option\"][data-active=\"true\"] {\n    background: var(--selected);\n  }\n  [data-slot=\"select-menu-option\"][aria-disabled=\"true\"] {\n    color: var(--muted-foreground);\n    opacity: 0.7;\n  }\n  [data-slot=\"select-menu-native\"] [data-slot=\"select\"] {\n    appearance: none;\n  }\n  [data-slot=\"select-menu-icon\"] {\n    display: inline-grid;\n    flex: none;\n    place-items: center;\n    width: 16px;\n    height: 16px;\n  }\n  [data-slot=\"select-menu-icon\"] svg { width: 16px; height: 16px; }\n  /* Ghost: no field ring, for a select inside a composed control. */\n  [data-slot=\"select-menu\"][data-variant=\"ghost\"],\n  [data-slot=\"select-menu-native\"][data-variant=\"ghost\"] [data-slot=\"select\"] {\n    background-color: transparent;\n    box-shadow: none;\n  }\n  @media (hover: hover) {\n    [data-slot=\"select-menu\"][data-variant=\"ghost\"]:hover:enabled:not([aria-expanded=\"true\"]) {\n      background-color: var(--raised-hover);\n      box-shadow: none;\n    }\n  }\n  [data-slot=\"select-menu\"][data-variant=\"ghost\"]:focus-visible,\n  [data-slot=\"select-menu\"][data-variant=\"ghost\"][aria-expanded=\"true\"] {\n    background-color: var(--raised-hover);\n    box-shadow: 0 0 0 2px var(--ring);\n  }\n  [data-slot=\"select-menu\"][data-variant=\"ghost\"]:disabled {\n    background-color: transparent;\n    box-shadow: none;\n  }\n  [data-slot=\"select-menu\"][data-size=\"sm\"] [data-slot=\"select-menu-code\"] { font-size: 12px; }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"select-menu-content\"] {\n      animation: none;\n    }\n  }\n  [data-slot=\"toolbar\"] [data-toolbar-generic] [data-slot=\"select-menu\"] { height: calc(var(--control-height) - 6px); }\n}\n"},{"path":"ui/shared/skip-link.css","type":"registry:file","target":"components/ui/shared/skip-link.css","content":"/* Shared by changelog-template.tsx, docs-template.tsx, landing-template.tsx, pricing-template.tsx, site-header.tsx, template-page.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"skip-link\"] {\n    position: absolute;\n    top: 0.5rem;\n    left: 0.5rem;\n    z-index: 50;\n    padding: 0.5rem 0.75rem;\n    border-radius: var(--radius-control);\n    background: var(--foreground);\n    color: var(--background);\n    font-size: 0.875rem;\n    transform: translateY(-200%);\n  }\n  [data-slot=\"skip-link\"]:focus {\n    transform: none;\n    outline: 2px solid var(--ring);\n    outline-offset: 2px;\n  }\n  [data-slot=\"skip-link\"] {\n    position: absolute; left: 12px; top: -48px; z-index: 50; padding: 8px 12px; border-radius: 12px;\n    background: var(--foreground); color: var(--background); font-size: 13px; text-decoration: none;\n  }\n  [data-slot=\"skip-link\"]:focus-visible { top: 12px; outline: 2px solid var(--ring); outline-offset: 2px; }\n  @media print {\n    [data-slot=\"skip-link\"] { display: none !important; }\n  }\n}\n"},{"path":"ui/template-page.css","type":"registry:file","target":"components/ui/template-page.css","content":"/* template-page.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"template-outline\"] { min-width: 0; }\n  [data-slot=\"template-outline-label\"] { display: none; margin: 0 0 12px; font-family: var(--font-mono, monospace); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--k-muted); }\n  [data-slot=\"template-outline\"] > [data-slot=\"template-outline-list\"] { display: none; }\n  @container (min-width: 960px) {\n    [data-slot=\"template-outline\"] { position: sticky; top: 96px; align-self: start; }\n    [data-slot=\"template-outline-label\"], [data-slot=\"template-outline\"] > [data-slot=\"template-outline-list\"] { display: block; }\n    [data-slot=\"template-outline-fold\"] { display: none; }\n  }\n  [data-slot=\"template-outline-list\"] { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }\n  [data-slot=\"template-outline-list\"] a {\n    position: relative; display: flex; gap: 8px; padding: 6px 0 6px 14px; font-size: 14px; line-height: 20px; color: var(--k-muted); text-decoration: none;\n    transition: color var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"template-outline-list\"] a::before { content: \"\"; position: absolute; left: 0; top: 6px; bottom: 6px; width: 2px; border-radius: 2px; background: var(--k-line); transition: background-color var(--duration-normal) var(--ease-out); }\n  [data-slot=\"template-outline-list\"] a:hover { color: var(--k-ink); }\n  [data-slot=\"template-outline-list\"] a[aria-current] { color: var(--k-ink); }\n  [data-slot=\"template-outline-list\"] a[aria-current]::before { background: var(--k-hi); }\n  [data-slot=\"template-outline-list\"] a:focus-visible { outline: 2px solid var(--k-focus, var(--k-hi)); outline-offset: 2px; border-radius: 4px; }\n  [data-slot=\"template-outline-number\"] { flex: none; min-width: 1.6em; font-variant-numeric: tabular-nums; }\n  [data-slot=\"template-outline-fold\"] { border-radius: var(--k-radius); background: var(--k-surface); box-shadow: 0 0 0 1px var(--k-line); }\n  [data-slot=\"template-outline-fold\"] > summary { display: flex; align-items: center; justify-content: space-between; min-height: 48px; padding: 0 16px; font-size: 14px; font-weight: 500; cursor: pointer; list-style: none; }\n  [data-slot=\"template-outline-fold\"] > summary::-webkit-details-marker { display: none; }\n  [data-slot=\"template-outline-fold\"] > summary::after { content: \"+\"; font-size: 18px; color: var(--k-muted); }\n  [data-slot=\"template-outline-fold\"][open] > summary::after { content: \"–\"; }\n  [data-slot=\"template-outline-fold\"] > summary:focus-visible { outline: 2px solid var(--k-focus, var(--k-hi)); outline-offset: 2px; border-radius: var(--k-radius); }\n  [data-slot=\"template-outline-fold\"] > [data-slot=\"template-outline-list\"] { padding: 0 16px 12px; }\n  @media print {\n    [data-slot=\"template-outline\"] { display: none !important; }\n  }\n}\n"},{"path":"ui/textarea.css","type":"registry:file","target":"components/ui/textarea.css","content":"/* textarea.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"textarea\"] {\n    transition-property: background-color, color, border-color;\n    transition-duration: var(--duration-fast);\n    transition-timing-function: var(--ease-out);\n  }\n\n  /* Recessed to fill. One ring for every field: 3:1 at rest, darker on\n     hover, a 2px ember ring with a soft halo on focus, danger when invalid.\n     Disabled and read-only drop into the sunken fill and stay legible. */\n  [data-slot=\"textarea\"] {\n    border: 0;\n    background-color: var(--field);\n    box-shadow: var(--shadow-field);\n    transition: box-shadow var(--duration-fast) var(--ease-out);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"textarea\"] {\n      transition: none;\n    }\n  }\n}\n"}],"categories":["free-pages"],"meta":{"tier":"free","license":"MIT","family":"reading-proof","docs":"/docs/components/contact-template"}}