{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "template-fieldnotes",
  "title": "Fieldnotes",
  "description": "A founder letter on warm paper, with margin notes, pull quotes, a transcript to tag and pricing, built from Glassy UI components.",
  "files": [
    {
      "path": "registry/glassyui/glass-provider.tsx",
      "content": "'use client';\n\nimport './styles/liquid.css';\n\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n  useSyncExternalStore,\n  type ReactNode,\n} from 'react';\nimport {\n  applyPreferences,\n  computeAutomaticSafeguards,\n  GLASS_THICKNESS,\n  loadPreferences,\n  persistPreferences,\n  type GlassyUiPreferences,\n  type GlassyUiTheme,\n} from './lib/preferences';\n\ntype PreferenceState = Required<GlassyUiPreferences>;\n\ntype SystemPreferenceState = Pick<\n  PreferenceState,\n  'theme' | 'quality' | 'reduceMotion' | 'reduceTransparency'\n>;\n\ntype PreferenceSources = {\n  explicit: GlassyUiPreferences;\n  system: SystemPreferenceState;\n};\n\ntype PreferenceContextValue = {\n  preferences: PreferenceState;\n  setPreferences: (prefs: Partial<GlassyUiPreferences>) => void;\n};\n\nconst DEFAULT_STATE: PreferenceState = {\n  theme: 'dark',\n  mode: 'clear',\n  reduceTransparency: false,\n  reduceMotion: false,\n  quality: 'high',\n  thickness: GLASS_THICKNESS.regular,\n};\n\nconst SERVER_SOURCES: PreferenceSources = {\n  explicit: {},\n  system: {\n    theme: DEFAULT_STATE.theme,\n    quality: DEFAULT_STATE.quality,\n    reduceMotion: DEFAULT_STATE.reduceMotion,\n    reduceTransparency: DEFAULT_STATE.reduceTransparency,\n  },\n};\n\nconst subscribeToHydration = () => () => {};\n\nconst transparencyQuery = '(prefers-reduced-transparency: reduce)';\n\nfunction subscribeToSystemTransparency(onChange: () => void) {\n  const query = window.matchMedia(transparencyQuery);\n  query.addEventListener('change', onChange);\n\n  return () => query.removeEventListener('change', onChange);\n}\n\nconst readSystemTransparency = () => window.matchMedia(transparencyQuery).matches;\n\nconst readServerTransparency = () => DEFAULT_STATE.reduceTransparency;\n\nconst PreferencesContext = createContext<PreferenceContextValue | null>(null);\n\nfunction warnPreferenceFailure(message: string, cause: unknown) {\n  console.warn(message, cause);\n}\n\nfunction mergePreferences(\n  base: PreferenceState,\n  next: Partial<GlassyUiPreferences>,\n): PreferenceState {\n  return {\n    ...base,\n    ...next,\n  };\n}\n\nfunction readSystemPreferences(): SystemPreferenceState {\n  if (typeof window === 'undefined') {\n    return {\n      theme: DEFAULT_STATE.theme,\n      quality: DEFAULT_STATE.quality,\n      reduceMotion: DEFAULT_STATE.reduceMotion,\n      reduceTransparency: DEFAULT_STATE.reduceTransparency,\n    };\n  }\n\n  const automatic = computeAutomaticSafeguards();\n\n  const systemPrefersLight =\n    window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;\n\n  return {\n    theme: systemPrefersLight ? 'light' : DEFAULT_STATE.theme,\n    quality: automatic.quality,\n    reduceMotion: automatic.reduceMotion,\n    reduceTransparency: automatic.reduceTransparency,\n  };\n}\n\nfunction readInitialPreferenceSources(): PreferenceSources {\n  if (typeof window === 'undefined') {\n    return { explicit: {}, system: readSystemPreferences() };\n  }\n\n  let explicit: GlassyUiPreferences = {};\n\n  try {\n    explicit = loadPreferences();\n  } catch (error) {\n    warnPreferenceFailure('Failed to load Glassy UI preferences', error);\n  }\n\n  return { explicit, system: readSystemPreferences() };\n}\n\n/**\n * Holds the glass preferences and applies them to the document. `defaultTheme` is the theme a\n * visitor sees until they choose one, whatever their system prefers; leave it out to follow the\n * system setting.\n */\nexport function GlassProvider({\n  children,\n  defaultTheme,\n}: {\n  children: ReactNode;\n  defaultTheme?: GlassyUiTheme;\n}) {\n  const [sources, setSources] = useState<PreferenceSources>(readInitialPreferenceSources);\n\n  const hydrated = useSyncExternalStore(\n    subscribeToHydration,\n    () => true,\n    () => false,\n  );\n\n  const activeSources = hydrated ? sources : SERVER_SOURCES;\n\n  const preferences = useMemo(\n    () =>\n      mergePreferences(\n        mergePreferences(\n          DEFAULT_STATE,\n          defaultTheme ? { ...activeSources.system, theme: defaultTheme } : activeSources.system,\n        ),\n        activeSources.explicit,\n      ),\n    [activeSources, defaultTheme],\n  );\n\n  useEffect(() => {\n    if (!hydrated || typeof document === 'undefined') {\n      return;\n    }\n\n    applyPreferences(document, preferences);\n  }, [hydrated, preferences]);\n\n  const update = useCallback((next: Partial<GlassyUiPreferences>) => {\n    // The document changes before React renders whatever asked for the change, so a\n    // control's own flip runs under the setting it lands in. Turning Reduce Motion off\n    // from its own switch then animates instead of snapping under the old setting.\n    if (typeof document !== 'undefined') {\n      applyPreferences(document, next);\n    }\n\n    setSources((previous) => {\n      const explicit = { ...previous.explicit, ...next };\n\n      if (typeof window !== 'undefined') {\n        try {\n          persistPreferences(explicit);\n        } catch (error) {\n          warnPreferenceFailure('Failed to persist Glassy UI preferences', error);\n        }\n      }\n\n      return { ...previous, explicit };\n    });\n  }, []);\n\n  useEffect(() => {\n    if (typeof window === 'undefined') {\n      return;\n    }\n\n    const queries = [\n      '(prefers-color-scheme: light)',\n      '(prefers-reduced-motion: reduce)',\n      '(prefers-reduced-transparency: reduce)',\n      '(pointer: coarse)',\n    ].map((query) => window.matchMedia(query));\n\n    const handler = () => {\n      setSources((previous) => ({ ...previous, system: readSystemPreferences() }));\n    };\n\n    queries.forEach((query) => query.addEventListener('change', handler));\n\n    return () => queries.forEach((query) => query.removeEventListener('change', handler));\n  }, []);\n\n  const value = useMemo<PreferenceContextValue>(\n    () => ({\n      preferences,\n      setPreferences: update,\n    }),\n    [preferences, update],\n  );\n\n  return <PreferencesContext.Provider value={value}>{children}</PreferencesContext.Provider>;\n}\n\nexport const PreferencesProvider = GlassProvider;\n\nexport function useOptionalPreferences() {\n  const context = useContext(PreferencesContext);\n\n  const reduceTransparency = useSyncExternalStore(\n    context ? subscribeToHydration : subscribeToSystemTransparency,\n    context ? () => context.preferences.reduceTransparency : readSystemTransparency,\n    readServerTransparency,\n  );\n\n  return (\n    context ?? {\n      preferences: { ...DEFAULT_STATE, reduceTransparency },\n      setPreferences: () => {},\n    }\n  );\n}\n\nexport function usePreferences() {\n  const context = useContext(PreferencesContext);\n\n  if (!context) {\n    throw new Error('usePreferences must be used within PreferencesProvider');\n  }\n\n  return context;\n}\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-provider.tsx"
    },
    {
      "path": "registry/glassyui/liquid-filters.tsx",
      "content": "export function LiquidFilters() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      focusable=\"false\"\n      className=\"lg-svg-filters\"\n      style={{ position: 'absolute', width: 0, height: 0, overflow: 'hidden' }}\n    >\n      <defs>\n        {/*\n          Simplified high-performance refraction filter.\n          Uses userSpaceOnUse for pixel-accurate displacement.\n          Chromatic aberration via RGB channel separation + offset.\n        */}\n        <filter\n          id=\"lg-liquid-refraction\"\n          x=\"-10%\"\n          y=\"-10%\"\n          width=\"120%\"\n          height=\"120%\"\n          filterUnits=\"objectBoundingBox\"\n          primitiveUnits=\"userSpaceOnUse\"\n          colorInterpolationFilters=\"sRGB\"\n        >\n          {/* Load bowl-shaped displacement map */}\n          <feImage href={DISPLACEMENT_MAP} preserveAspectRatio=\"none\" result=\"dispMap\" />\n\n          {/* Blur the background for glass diffusion - single pass for performance */}\n          <feGaussianBlur in=\"BackgroundImage\" stdDeviation=\"12\" result=\"blurred\" />\n\n          {/* Apply displacement based on bowl map */}\n          <feDisplacementMap\n            in=\"blurred\"\n            in2=\"dispMap\"\n            scale=\"20\"\n            xChannelSelector=\"R\"\n            yChannelSelector=\"G\"\n            result=\"displaced\"\n          />\n\n          {/* Chromatic aberration: separate RGB channels */}\n          <feColorMatrix\n            in=\"displaced\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0\"\n            result=\"redChannel\"\n          />\n          <feColorMatrix\n            in=\"displaced\"\n            type=\"matrix\"\n            values=\"0 0 0 0 0  0 1 0 0 0  0 0 0 0 0  0 0 0 1 0\"\n            result=\"greenChannel\"\n          />\n          <feColorMatrix\n            in=\"displaced\"\n            type=\"matrix\"\n            values=\"0 0 0 0 0  0 0 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n            result=\"blueChannel\"\n          />\n\n          {/* Offset channels for chromatic dispersion (Cauchy: blue bends more) */}\n          <feOffset in=\"redChannel\" dx=\"-1.5\" dy=\"-1\" result=\"redShift\" />\n          <feOffset in=\"greenChannel\" dx=\"0\" dy=\"0\" result=\"greenShift\" />\n          <feOffset in=\"blueChannel\" dx=\"1.5\" dy=\"1\" result=\"blueShift\" />\n\n          {/* Recombine RGB channels */}\n          <feBlend in=\"redShift\" in2=\"greenShift\" mode=\"screen\" result=\"rgMix\" />\n          <feBlend in=\"rgMix\" in2=\"blueShift\" mode=\"screen\" result=\"chromatic\" />\n\n          {/* Boost saturation slightly for vibrancy */}\n          <feColorMatrix in=\"chromatic\" type=\"saturate\" values=\"1.15\" result=\"refracted\" />\n\n          {/* Composite with source (glass tint overlay) */}\n          <feBlend in=\"SourceGraphic\" in2=\"refracted\" mode=\"normal\" />\n        </filter>\n\n        {/* Light mode specific filter with stronger chromatic aberration */}\n        <filter\n          id=\"lg-liquid-refraction-light\"\n          x=\"-10%\"\n          y=\"-10%\"\n          width=\"120%\"\n          height=\"120%\"\n          filterUnits=\"objectBoundingBox\"\n          primitiveUnits=\"userSpaceOnUse\"\n          colorInterpolationFilters=\"sRGB\"\n        >\n          <feImage href={DISPLACEMENT_MAP} preserveAspectRatio=\"none\" result=\"dispMap\" />\n\n          {/* Less blur in light mode for sharper refractions */}\n          <feGaussianBlur in=\"BackgroundImage\" stdDeviation=\"8\" result=\"blurred\" />\n\n          <feDisplacementMap\n            in=\"blurred\"\n            in2=\"dispMap\"\n            scale=\"25\"\n            xChannelSelector=\"R\"\n            yChannelSelector=\"G\"\n            result=\"displaced\"\n          />\n\n          {/* Stronger chromatic separation for light mode */}\n          <feColorMatrix\n            in=\"displaced\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0\"\n            result=\"redChannel\"\n          />\n          <feColorMatrix\n            in=\"displaced\"\n            type=\"matrix\"\n            values=\"0 0 0 0 0  0 1 0 0 0  0 0 0 0 0  0 0 0 1 0\"\n            result=\"greenChannel\"\n          />\n          <feColorMatrix\n            in=\"displaced\"\n            type=\"matrix\"\n            values=\"0 0 0 0 0  0 0 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n            result=\"blueChannel\"\n          />\n\n          {/* Stronger offsets for visible chromatic aberration */}\n          <feOffset in=\"redChannel\" dx=\"-2\" dy=\"-1.5\" result=\"redShift\" />\n          <feOffset in=\"greenChannel\" dx=\"0\" dy=\"0\" result=\"greenShift\" />\n          <feOffset in=\"blueChannel\" dx=\"2\" dy=\"1.5\" result=\"blueShift\" />\n\n          <feBlend in=\"redShift\" in2=\"greenShift\" mode=\"screen\" result=\"rgMix\" />\n          <feBlend in=\"rgMix\" in2=\"blueShift\" mode=\"screen\" result=\"chromatic\" />\n\n          <feColorMatrix in=\"chromatic\" type=\"saturate\" values=\"1.2\" result=\"refracted\" />\n\n          <feBlend in=\"SourceGraphic\" in2=\"refracted\" mode=\"normal\" />\n        </filter>\n\n        {/* Lightweight turbulence-based distortion for CSS filter usage */}\n        <filter\n          id=\"lg-glass-distort\"\n          x=\"-20%\"\n          y=\"-20%\"\n          width=\"140%\"\n          height=\"140%\"\n          colorInterpolationFilters=\"sRGB\"\n          filterUnits=\"objectBoundingBox\"\n        >\n          <feTurbulence\n            type=\"fractalNoise\"\n            baseFrequency=\"0.012 0.018\"\n            numOctaves=\"2\"\n            seed=\"9\"\n            result=\"noise\"\n          />\n          <feGaussianBlur in=\"noise\" stdDeviation=\"1.8\" result=\"softNoise\" />\n          <feDisplacementMap\n            in=\"SourceGraphic\"\n            in2=\"softNoise\"\n            scale=\"18\"\n            xChannelSelector=\"R\"\n            yChannelSelector=\"G\"\n          />\n        </filter>\n\n        {/* Switcher-style displacement map (used with backdrop-filter) */}\n        <filter\n          id=\"lg-switcher\"\n          x=\"0%\"\n          y=\"0%\"\n          width=\"100%\"\n          height=\"100%\"\n          filterUnits=\"objectBoundingBox\"\n          primitiveUnits=\"objectBoundingBox\"\n          colorInterpolationFilters=\"sRGB\"\n        >\n          {/* Static packed normal-ish map to bend the backdrop */}\n          <feImage href={SWITCHER_MAP} result=\"map\" preserveAspectRatio=\"none\" />\n\n          {/* Mild blur of the backdrop for diffusion */}\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"0.08\" result=\"blur\" />\n\n          <feDisplacementMap\n            in=\"blur\"\n            in2=\"map\"\n            scale=\"20\"\n            xChannelSelector=\"R\"\n            yChannelSelector=\"G\"\n          />\n        </filter>\n      </defs>\n    </svg>\n  );\n}\n\nconst DISPLACEMENT_MAP =\n  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAQnElEQVR4nO1daZLDKg6Grj7RnHBuzlTCpn3BJP2mZvQjhbHAWEKfJEzs+q9//bu0UluppbwK5VVehVd5FlbN4MQ1vfBDakr5AYf97A85LPj33UQtFFTQyqNQ1UKtwu/rVEWHr/IsjN91FtT0QmE1r0pQU2BhNvlt79JLzu1daON0L3Tpl8nQazpnwTW90AhPezesQ5H9ZKv7WnVV9mF07fKO+0GeqljoUpiVSCKQc7IRyb4PZktQ4PpwpV9L+d3igDroHO0trGkZZZ4aAtMLhLkCNUD2+lbGq3KeGlcDd36RiDLW9ISaWKdgYc2AbQpmATHPrtcpYi6/a3YjHcy5t2Q31MCmOZH+mOm4BkmfSbwtTdyWuEHUAoA+NB0MUWqiB8aBagB8celLEMR1UJAprJo9dqCPoS1oQ5IaEOb87J6+RmvIHWCJJkhhzV9YQ60GgAya+Kb0XxDUsULUAZzsfdoWXfSocgq3iNKfV5RB+mPKWIPdx+BiyDdIEEQmO/e3sJKgvyb9twVMl6tNf+SNO7x4oh+a06QPzFmgT9tCUy9k2AGSo6mG0RXzvaL0Xz6AQLyqg+VbYMBjin7YDcT9lHBr7gyd4Py01y3pH+nAU8PCIlL5CIKQDtis35FokVUyIs5yJP1JDz3zimuznYwEaOGJIu55TrCGiPQBBKFYRnHFMP4hU070AcuXLN8FcPZILF6zFuYELegNTaTdXtYGH+gGIOiLQgfg06X0iybpFLGog6EbJmuihgVEKxkRqH4G/GuISwUrcjukY64GBYtqWPrvPAD7VUsHRTcFkH2M5hFZ1PJPIV30iwT5GhPflD5UgwJBog6wKSxTlQ+VGIOXi8d8heRZDzyzBq6QGaZjSxPkcIdDmvTn9EdLETTYZ+6X62Bcd60UjWHgs+GQ49sGgYHeZuwOebFCEe+wx5P+KiPfvpYi9vRfzoANZIA7nyELnTKIb1F91I0Vj2aC0c0vGSiFILgGRzQhQb+eiIFrICCC9XxkYCpZt+bdds038QmP3kkXSBPMCtuKQZHggXXwmYkYRIxpcQvKiTPYnhaMyMYQAfqXnzigSLOWbrIlO9N1Wq9cgsJOUPpQARuCwNU4BGk6GB1h1pBwwxqoUUbaIDTZw4EQzBKgJVlzH4KPtA6BVkNJLLTuZa8FMR1Y9xe/9fpV7ysLNDJgU0O29KncaRRE+sIuQXTIKwCArZShmQHoVyJRKwbVUd7QEOnw9cxVwiLucoUwFEZBsr+VakhEzMmN9FUR1/JZYgKNJMY2Tw9k6PRXQL9oidgalaAJkB+oyBMRXCotKPeUMW/GWQkn/PUoQhWnv6QMCkEC1OAp37yI0/AKrkFU82YzTKFAKJUYU6750Jt3pS5CYAU4ECQYwXrKGBeE7lrtsPUO1ZuJccJdtwDs2BAk3guMOMlNCkZgHQudv+hPwqAV9WfQhi8CVMazwUdywsWGILJFB3kCLYF6ZhCIvqMJby5np/ziockBhB0tDC0/U9aaIfTNJvVkFmsGYcu5ruuWC7SkuW9C6hfdYiz+cV26gUJLBwiC+PTf+7TA0B3xnYZD9WGf2iom691dYLAsQ+chcCTmvdAI8FoQ2LZGSIDISxHnwTR3mZFw69Vk+Cg2FY2gWMvRbPrv3UiAiC0bCgply+Uv/HBgyRNOeRIjEcjizrzXDOkrHnhCENgPOh75TjyK2n7eJupfaaLFQCnvgTUiDplGQet5FvIEeLdsBIUPXLFPt5ShOINylosFgldoOjz4gYXf9vOe6WvXOAiHZKEHIxnlNKqu6tl6OxOGe5qEbhScSbtfxSz2w7KiRUFY+nu7MroL+/hcZNWNcbM0m1krCqZMKesz0xmBjqSDNwSJ0g88zrBdscJtVJQvuWIle5J5TC8dz6V1CJqhPZE+mpVHrljr4Ipjr/jQny0BN6DFPIcumiGboIOeCY/t/9AIDAQ3g5l4DB7lOcrFaBaWARCbh8x3rpu9G1WUFdbB8AFFkX4WUs7Zqud+D0jqiLrie7GmwUbWq4UoCP6v6HXCvGQqPnGRp+pVF92BHKXosaaRf1E+0FVwkWMbENwVsVbc9n9XdrcZOI4tLdzM7yKUdAAaG7Ebf2Mdc9HIAhYE9Vlvoz8cWVQst91AeZAHhJjrYxUGOJEFrOXo4QbimIPPGTzQhkS2ugoBHIuSCQhrsosRJN2kpQzCgDWZh7AQJ7zxh91GlDhwmZyOzmq5TkZ8Qh9s2Twep3RtzKuFoT/+TI5d8TrE11AjS0Z3Qx1XPaZZ/GgWoJERq1g861REfDVYl6FgZKLE9UYy4SfPARdNLQD+ZTlCmjnrdRnmWu5TUECeYjhzaDUC9tOkKMh2kQmBhFlr0IySY7Ddo5C7BgJQxO2PWB8DbEWioNDtxRMCXJvtvGYHo+2r0aZwfOeWvrL9REkruHolYjkHSzhTniOeRtTkkOwwcVWZPQqBfEDiNHQMmsWk9wOZ2Mr4cxM556/lhIJRUCprS1mP2Rw5Yc9he2M6bVtJwX0KlLy0vJENvpvIXRfREDxC6jaTXRYg6MlkDyUSR+H/I0omBMbellBQK7XVGk4IKodkw5/WbQQ0a6hlSBaRiFMLJW1fYu+2s6m3mBCknffrwpf6dJMs1h/AfaBV1kRkCHKucgsZaqbXWDirho92VuVFsVFS+jE8zfuBTLlJ2X2clR2bGaHURFxF0MDkAYAIixPGKAM0ErGv0cEe2+JlGyUVoQe6cqR8d7b2nXFRUjjPItgD0aQzYS/agUc1CFZGK71/g9IW8JF0TKEEPuiOVxbTlcxOOp8Nir4KQZ8KeNqDzsNTNdTbNQiKhRzfo2rVhRaWMz3fJSsw+6oFfF+f9UHblGU8GM8/AIIO/LmZap17zvhQ7tE/SwERsoT7R+/9+FYY+n/6AP33WUDTE6WzRO9/RgEBt9aC8B17xnBwNs/3IQV8KwaI0tl4/mkGUZN5QDRgeEgtwFMtLHqEPJ+/w/oHEHQ3vWwP2l5e6S3XKaOAQJR9d6Wl2Uuhsc0850/W8uRnKp9YDU0kPuJafH7x0ucPMmiM9ao//zYEtWeJUhMqzh8kRGvD568/D+jf0LkI8Yl0tMlr8fLfhsDTFZGBPjJjHFaT2IivY28bEFTDj5OOLhN84toUsILMj3YwxYAl3F2CVE/WDAjSIj/7Umdg3cJN7q4wn8n3QRrIuY8gyAQQwlIjT6lqeMnhzBkEWmk59tMFWp36zckQ5Owl1i9p7PgILSQ0/xIXKOWH66MJ7jTcEAQdnDMik5JztpGvd+q7OU9I32mKClk1P5wWOJZgEKT3HvlfThru6zO2FAU7eayPYAf9LR0CBMV3W9gRenq7fUNs/pDMTPggnNfyhnTcpTSoGgRF030XTxSJH1hPs0PSwGAIT1YfRgietkbFWfZ/iv3CN3fk9lOizpzricyN3Yz9f93g6qydvtgZnNk+cTFf3/OP2cMJrz/tRZegD96DajO31B1coNT7TUMoFA92wSuxsAUoFBGQARrqy4+a1aG7o8YnZcprnMJ6RoBN7dJDgv7neGQBFBNu/DFcvLbP1mLuQeU6OB3mkTjjl96fRuIWkEbD+EsqIq/WaaD5ByAonV7FRZwZ7bg7aAE7CnLRPOUe2b/IRaG3Wem8vj8FRCb4VBO7ZRTKhEkuD3w9zbaA5YelFugC0YgogjBV57mYjtVvsAUTAog/7ygIvFAdvVALTk83/G+5PCv1HrDLFBSfxFYfr/GNF5KBtyTOpQjwTnV7EJFX6yDmZgm99aYPov4IGXAhnkIyyJiFz1uBBbxlgp0w1oGTvl4MdSrwBAEDCpE5hdPhxgOAghVL+q+74WGobAdmfELi+tyaczXvJPn6nQM4jrWUzusJgRFrEemDMBSLfucEzyY7/duiiUVZB0AMpd52A3c/e7BwH0p/P5DRdODKnbjobHbWuDv5hNe9Adz6UcDi3yRI/10YPkDVgeEwI2YRM52m20RoWZSTDhGMxeNL8SiOvQMAl34ToyDyjundR0m46Icv4m88OnqWB6julzBGglTTzYq147skkhG8IahzS5+YifpAM2M4MZ2v5wGczbcenYNMXfhRkhn+jMKEIKwDVHkv1iTVNdvVXbroZu14YS48DF78xZ7thMknZqAOuLBs96vFPJaXruXPSPexWlwUccUDS5v6Xba2LIB81kEwAijCq+63BB8B8b0tN1wrPX/LFQNxaZtM1od6pChI/BI66PrM/UIey1Vk147yws3GnWeuWP0uG5ii772hP/JHJrcCvJyAfF/XivTtCKrQFewry0FC/CN1WvNZguMj54qb2HJ8JaxnwoYHJh8jTmdPQahpga4+QZGZnoGdjT1A7sIXOsEdj6UI67OT4OPQ07PsS8ob2cKeoLnz6MPkBpSEVfa95JiFm1p/Y3u6AEGzvA6RN3425XeMVM0bNjp36Uij0Q8Y1NgwYegJJj75pRBEFEBQaJXXlWrs+7puLtZMxx68c5U8h8lPO9APuiAA0MdMPC3vIQRB3BoQEKHL+mjj83ifWXhOObg7iEpB3Plyszj0IuivQpDmhIlB7PXqTPwj85S/zIRda3B5xmrzZOd2S5ZXZAgSTQFaA/QHMH1zlxbogwEy/CaL4RuPJNm5epRJNGXECIh6Ssyu/yuHngyLZOlrTjYykbPJ8G06iTU1toU8/ZkXUAkROvXDcFeECPqknipjBkXIx7oCTYZATUSq59hitnXOLC/NkYfoADTfNVMxaC1I8wSG9OFncrXRI/wBOXN8FzCiA6EGmlDkCatwhD1V1wEwgu6WiW5+jbwXSZ8rA4RGamy6hxm6oQYtKSiDFGXXlHQPsSLOeSBMfK6DXT8r0XI0Df9xuTBT2IrhT9DwyELU5NuG5w8iwwdMKvNrpYHf2pz4+xkA7mM8mATmIkdBIvgQO0ATnx2O0cRWMdtxYvyYElvbwH2heYKDfQRHGIg2rK0mcFsKFbon/d2deOiFlYeJ8QNyg30rAJ20oLwziYeGDrYzALHQrwA+ovQhzhA7AP4ArdwZ9EdT3iFzPB12NpqTW5ifBtu+oeo6WErq21JC0ofyXdInrniFEPxR2r4P9VZbXCNaMBprH0l9yeXWV37X/gY0EoC6SEO6DhYQsShIKqgTXzKCpQbouiHQO4kxJ3E1K0gep4Y8A0jfot9yxLKWb4OYAtcBhaCY9JHEwUzfY2dq4OCWozZv7QFSUReaufiOWNZeWjDNV7nXU/Vg94t0AArvJ2J8/YdLH6K8Im4IPpB5dBLXQfNFJtpNVMIBvpdowBb+HbSsP9NJyNPLhNnQQWezIIiuBfWLStZgqAHpYMUbmm8oX/TMUnQ/JubctQC3L2iih7N+BUVLDYYOQCKmQBCRvixxwwdEdFAFt/yhtTm5zzkAtIFHkr4relgJg/2tOQ2CfPeLJU6gHxaGGpYpaDqAmvhBd/Ed2i6TbJxi0u/j3NKHW0CY6EmICWs0N/DrSJ/4VRILYYkjmFJEv/0B1EQB3uUL1J+ezi0BaO8mk/7eWgL+V0SXQKXJPmoWv6IDtBQBoYNM/DECJnGxQJipDpjcK9DHVsZFfayAahWAxKEmhlRFBwD+V1TMAmJepgChDOiAQZDoexnic/DhxkEgCAp9409hiIQlv9R5JncIErvAtuhsueD/LSMIwtOcdw15xr1gT7ATMaCD/wDZtUM+16eZfQAAAABJRU5ErkJggg==';\n\nconst SWITCHER_MAP =\n  'data:image/webp;base64,UklGRq4vAABXRUJQVlA4WAoAAAAQAAAA5wEAhwAAQUxQSOYWAAABHAVpGzCrf9t7EiJCYdIGTDpvURGm9n7K+YS32rZ1W8q0LSSEBCQgAQlIwEGGA3CQOAAHSEDCJSEk4KDvUmL31vrYkSX3ufgXEb4gSbKt2LatxlqIgNBBzbM3ikHVkvUvq7btKpaOBCQgIRIiAQeNg46DwgE4oB1QDuKgS0IcXBykXieHkwdjX/4iAhZtK3ErSBYGEelp+4aM/5/+z14+//jLlz/++s/Xr4//kl9C8Ns8DaajU+lPX/74+viv/eWxOXsO+eHL3/88/ut/2b0zref99evjX8NLmNt1fP7178e/jJcw9k3G//XP49/Iy2qaa7328Xkk9ZnWx0VUj3bcyCY4Pi7C6reeEagEohnRCbQQwFmUp9ggYQj8MChjTSI0Ck7G/bh6P5ykNU9yP+10G8I2UAwXeQ96DQwNjqyPu/c4tK+5CtGOK0oM7AH5f767lHpotXVYYI66B+HjMhHj43C5wok3YDH4/vZFZRkB7rNnEfC39WS2Q3K78y525wFNTPf5f+/fN9YI1YyDvjuzV5rQtsfn1Ez1ka3PkeGxOZ6IODxDJqCLpF7vdb9Z3s/ufLr6jf/55zbW3LodwwVVg7Lmao+p3eGcqDFDGuuKnlBZAPSbnkYtTX+mZl2y57Gq85F3tDv7m7/yzpjXHoVA3YUObsHz80W3IUK1E8yRqggxTMzD4If2230ys7RDxWrLu9o9GdSWNwNRC2yMIg+HkTVT3BOZER49XLBMdljemLFMjw8VwZ8OdBti4lWdt7c7dzaSc5yILtztsTMT1GFGn/tysM23nF3xbOsnh/eQGKkxhWGEalljCvWZ+LDE+9t97uqEfb08rdYwZGhheLzG2SJzKS77OIAVgPDjf9jHt6c+0mjinS/v13iz9RV3vsPdmbNG1E+nD6s83jBrBEnlBiTojuJogGJNtzxtsIoD2CFuXYipzhGWHhWqCBSqd7l7GMrnuHzH6910FO+XYwgcDxoFRJNk2GUcpQ6I/GhLmqisuBS6uSFpfAz3Yb9Yatyed7r781ZYfr3+3FfXs1MykSbVcg4GiOKX19SZ9xFRwhG+UZGiROjsXhePVu12fCZTJ3CJ4Z3uXnyxz28RutHa5yCKG6jgfTBPuA9jHL7YdlAa2trNEr7BLANd3qNYcWZqnkvlDe8+F5Q/9k8jCFk17ObrIf0O/5U/iDnqcqA70mURr8FUN5pmQEzDcxuWvOPd1+KrbO4fd0vXK5OTtYEy5C2TA5L4ok6Y31WHR9ZR9lQr6IjwruSd775W6NVa2zz1fir2k1GWnT573Eu3mfMjIikYZkM4MDCnTWbmLrpK/Hs0KD5C8rZ3n0tnw0j76WuU8P1YBIjsvcESbnOQMY+gGC/sd/gG+hKKtDijJHhrcSj/GHa/FZ8oGLXeLx1IW+cgU8pqD0PzMzU3oG5lQ/ZaDPDMYq+aAPSEmHN+JiVIp0haHTvPt77732z5ed2K7NHs9FtCIk4BdNkKLRLvOKlFcw+UiovM4OB5sGgepyML+a4TEu/I29/dFtjJulojJR4Tg71ybApEdca0TSnaumNJyCWH2pjENASlQS/NIXMWtiPV9CHsvuftev08/lemYIcUnHSu6XEMvaBq41tqf/m0siLj7xeXsnBmhxY5z+nCwX4Iu4euTPaE4EQorgogisHrBtsAMdX+Huje7nlx3hMpKovdf+YftDQqytChXfEh7D5nyC8rzNTICINmpK5Ni0ngcAMzpmiYDwOMtmUTiCjvx2S2dIeSguP/QHZ3xYIeGhTt1CsCOIiEuVw8pGjVznDJppuojl30i9RvXccXzmXGj2b3H3XM38c/PZseyeOdplXhFekzZMZ2fUGuIBsKCcgQg4Ikqt4PDTkQiWQtMUBFAEhUH8vuvoAvnvGMCEP4/vMmZA2PnkmAJsQsHeFAIk43F00OS3sa/1TDJTPss2698T+i3V22L3PsIeFAHmWWi1FUh29TqpniVOt5hGA/q40Yubt4yXDEQomvldUNhfuuSvjHzPBysYhBMSmRrpuIUHJhQk5uw5V4EwpMp1NvklGkc03WYeC0KETcZ409HkEcwnEaE3EdNnIcfCb1jjWNfZyhhGH48AvsJ4WL+mYTM5i+yFNyM6PhbkuMGYREv48VihVyHXb9RjoE0HvoOuaO7fxxUYnQj1wB0DOZUagcEXfVkJ/nBgV+vl5yMfFaJs0myb9BjyNSsY9FbwZNq21wEFOEJ8Pk/vO1fSa6bOPZFCMc7grz9YXf8rBBPaK3qUJEfJG1A8nuytO1jg8CvWGEY1Z4o1gb3uEjILmNm5YfMXH3GtvyETX+j4jAXkkaA7FDQIdPzLZOcUJsqLQFxboX/MZ95f7MqPku/6IAGXer6xchZyiqcG2Tw4oSVcO0Q0vqOlmEcpsyBw2pwzcifb6t2th64vASkXGXzY9U7aFvkqJEOWSkEU0oL0FrnOfr432tJ5OtPUG1T0cg5yqNTNFAqKFxl80fxGGPFzIiASv+sEPaGMmewBjUEZNFtVCwzaG3PVSe5l+AIRNeFCzu2+H/7Cp2pbOjRUjNFFMX8ZEGl0D4uNWi4ykocIgBkGF+HAIHRNjAqioi4y7vjPtlTPTMXwl7aQD7gu9yVk+VdBwmVMnljIx4++8hq0qOtmjkwT1+RW4N0LhPQuahKrjGVIMy2hW3lgO8lqoLLBHAaTvRIgaPLNFx5ChJ8hTcsBdO383ouHspeqwelcvfEOELFMF0a+jWZJzZYWqZQlj9FnUeMq37zGWfbwRbvkDKOR0OKzAUNO5y8O+H24nczTdDZniPDwMUgIJDV1sEJn7xWMscorAcT3niXE+kcQS0NUMjkkoiNu43cbvQGGagTd6ycWgkkPbSb0Fi0iiYKTpXlKyTCKKHsWssGuM4dhzIaZqIjXvg2w1xqK8sqkQKhJUqWoGxcXTK4gi12ecTaa8+jmMYItoS41KhA4pbAWS2MyLk3n/lS0c4Cq4KcdLYTv4c3OPQZWJx+B9dSytYPUmGUKbKpg+Oy/g0iGuMDw+WRMjdCftaM30PxVSEW8Y6IeUpcGDoTFyDExFIC0coBCNDjx8XXBMWW53qAz2LgJA7G/zPcBcq5mjyfMo/dYTJMBQ3mkxItV2HHpsltIs49LLZK4w6TscoK/1x8FCEkPvP90Y3XVDu468z/HBkAdUMZLNwt3AqNiHOLQM/EYqMbxAWcgW1Rd5PFOnuX08+iNwt7wFWBWYdpDb3F5inFIe4vlXFLkUO3zVjzvJJWXGJOhyBSxV4O8z1FPBmVgZA7p+Ov5oh0XYD5DazDBODdJHHK3O3U5k2REDOWh7ZQSw6fDLBl4P4hixhuzJpGLmv9Ok/12dnFEMDomZm9pikmMevpvEAvZSq1rPziRSaXHMokc0TwRInpAVh5B7os8LBX4+z8rYaZxxQViQ7bndIOnucpgFahg7nBRTv9mUP1epZ+zzFYkXJvfvxUmkdewGhR3FtEE5gGUdAz8DbBFDQypm3jgUlFMru4RG5VIXGaThK7uZnNNDVq3igkGgQVnnSqodKgLGNEPnkAH3YgM0ABowQ5RsDpa4C8wuMrXP8JeioiBC5//ltLZOuePmXgZauU9FcpsvPvYH5yWt8P65HuRjLI62+zmNH28fZZ4odgbjp6AswlNzd74PbIkojkpXSKKF8h79BOJxhZFhDeSWAvb3D5jw2NtUDppI4eRSg5L7+5bTUdm0e7FZh2BgmZdVY/+WE7DLuqWZm3YvOEoQ0WcIIlI8bckcO2SkgZcHI/f63KJb0uWUR6gtorxgCE5ytH3wRr3kiWHlcdGk/SZO0UU+RYuFrCTjCdUAwGdEouf//Si1AhNmg7ZFRuMR+5qeQAaAdwKrG5O5pUnNAa8Ecb9Y2b6B8Rejwcffv5ii5h69Dhm55nhpJ3o/FYpTL1AWgmLIAG4t3qK8ocYnXxF06Fe0Dtv9kvv/LJZTcg/D4OB1FEtaC+mvh3RNhPLlOg3QniC0jov2Qjw3adeA/2GAIohAxCwSGlTsJ+pkOHU6K0EyY5osnN6tVyv56/OJNAOP9Kvi1wZx55EIcz0F2IYWAkvvDRypWSXUuGExX4QjQt4o5ptXHEaXK4z5RYV1C7cs6aLTigJYW8Lwcrv/R9cHuLsl1cfKzRlB5hgWzp/tpPDUF2sWA4tApdUKqSRX+TTogKnATAH44OLk7d36DCknABBAqTWQQz1QgQeq3EImJiwWdYSahYYXVOJmPCa6LqAvdEojcVT+xjjtNZoCcsYRHnvdK7bf2GreoKKsKDtgn5emh3lGmCdDzkDJPGid3PFAb/Bbwj1MCf2pdZqkSUBwWXgGpLWaUEjFG+0PmcDzclQBH2FDsA+UcILmHrzrHY6DKev0bBOYPD6lGy0Nw60gIAeP8HXWq0vZo5rbFGsYXSDtNb+QnSu7hPyLzvfMcaBTM2oF6rLx2CQaaYSljdEeodTvY2uqwUYvPtFlqNo0wxoWSu/8rQgNHO9WjggPFdxIG3socz0BCkQY1umhJ1oHI/lta72+zuU9tESX3+5++GF3dZeON4RZCnaoHjExonNAkjSXSyOtbbjmATzeZJBoWDR202FweApL78uWpYAitcpVDELbG9a7R9zukHUYYLTBBrysZM7cj0rgs1lgo1EXNwwmS+3P65ZvqICNr2C+AXNaOP04VKUZtyPItDaBCa2hawRB761AYFwgNmPsZRZDcn8OPBuIoKsjgxJOUP9x8f2TEHH5pcKqZXyCi2eduB3r9o1Kg1SSC0/OkCBEld/O5E6gWQmJ1s8jYY4HW5KGgNvD9RZpUY+3vwYBZfyHIM+koswIT86IJ6xCDjzuvo/v0laJA06ySyQbx7adCMiTg4oCWrHkUBFHcAAw8Zs1e1fEhrXkE0UDh/hoYuT/o0/OBjuEg97O4QpJ5B8QMB2u4oo/SPDGuW4Z3fnTbzgoUmpQCeZMIdAzBYuR+p09f9lD88wtshQ9yqJEpJnSslPMpqdjN/n61ba2dIiF+IoGkABIBlxnhcWdVOnY9rvmGIYoJgyI98CQrWXxRfWGzDi3jICiEzX2N3Fgp89vN2GmbsTN0uhJG7la4vt78WCwjaJc8uu+EUg7rMkghSWwuHuP0+4fLvRC0swGQZXSKb5yFmAFyf+7sfhkWMMId2oT4bFT06oNHcBJhNmNZ4dgZrb1ZOFoetT1gjgje0l51XkfExz25Q90Xc0it+06TRIXW1fHOGfK4RQxx2dNtriJ8cyns0pG11RrpikqJIlyA3J8uvXvsBRnhre1fOT2hASX6pqQf5xrRQaPAjJmaCvRIxI85yzm0mnXYKSWHxj0pwsjPavDyPJkuhnWPvoKptc/U9bt8HISJ2y1ag/TVNA6kOmIWEhbSWk0xPEBA4y7en+7Tb3oQPoAj9t+tzyxTpIkdIZ9pEVbOohduiU53ry0Vdw2hDhAgz99R4XF/Llx+Ov+OVrAv3zmzaX2m4cHVUcIP+dEs+U7Yx0qioIrQHrW3QJTXDR2cb3X4uBvxqRw5j5I1q1w2CLsuEwtNSVNQMAZ4l+lziBHy8eAjYEeK3DclFBt3tp1sbmNUO+KqVwSSpcbAdb4ns6h1mxhKtLTEQqgYuMP5RggqzoFXsQYHx/05pvL5HySE1MM6T9QLUUoxv5Rm4OLcKHkl9lvjEAib4QmNwyNqkwjk8uM7LO5cekr1LytEk045FrgejisDNO0G2yPXcEMVzVjdaWEgF5p+JmrETExrlwOEIAkb95UE+WntFZTua82BrGaS6C5uOI6HwKMzADyxqDQTVeqUgUIOyVivuQBABGN8SVzcWbTi+WjiH7EAB35nAKMGup7f4dQVE6QhErT0bSeowYYcX6D4DVExZm3wjn+8cMYf1u78CaZHxkeSIil45UfK3e2eUG8kDbJGM7cVHhlrwU3q84RUQOcXIHaeIjI+ot3Tsgbd44jjvRE0Sksd1EhDvHUEP7nF1H32sz52Ou4/UWAJX9cwEuQF5KSwdFpORCCr5KPanWVWGtGdgg8bevpjyXVDslUNnA/DnQoE2oRFQuKJx2/9es1eAUWd+aB251ZhQl3QkSPbMGRCIbVR05huHlcaC62eRAQ8yoymNW0RTZtFryPwnOa6MH9Iu/N+hZGVgrFO6fcbLFQMgtqHO2MMExdtMOI8penvNgQ1kIf4tBoOgFT0Qe3+7I/l0++DKIjLczbIN4MgrE9g9bqlDsi8G8mke4qmdN3Mr50dzcClH+dbCvsD2v3of3b7ZRzsY/wRMxriY36nlzDfVgswAhnCYDtsSITFClQM1Kw1BvFyTmnCh7J7OkZj+x+cGj7Kji60BplH5QypyMurm06L3JxRmfET0Wv/mVW3PZDnsYbrg9n9aI+6agYZuPj748JQugCkYc+RvXhLjKrSKTAeEiCFdV1FOd3vh1jaUTFO6uPZ3ZNSfvjncFtE0encKTkeU2SWsbhvKL54q0BTvpx8Ti1dAw1jVXKBa56NjOg+jt0Fn851+17mLainZ5viWtCEOleMm9X30Mddnx+59DpVNDZ7JjAlsQHC66PYXeHTJFyTEDDsci4KjA4Gm/ki8gMLEH8cAI19miOaUDWciVwEg9oedUDAYxMuYGDkg9j9e5ZShnz+um4PqZiL1oUkJWXtqlDHJzacvb8wGbkCU/j4Auefwb95hKV5xT+c7Q2St78793VM8mK+z2mks8fKOne2NtQqxRtHTuHsICa4macwO7QASsGcqINdIqT3v3tm0At/A67o6BD2mVbfCoYVAc/XfiLkfHN8rxcO7SdByZqHA6HYXgsUrnS65BP2vndP65L3p5dL4JvF5xtXJnIOMU5DKuStoQ59dsATxnO+RbuizcMTcpgkzqzV3vjuXCbK1992KMc5EaQ7Ko2M49wTsJALU9zDbDFpe/be9XF78rg+Oe4kanJF9J53V665yUcaP84L7vcNeXIJhe4tGIgJWv5jbZSoiER6FyriakY5YRv2d7y7IAuV0T8vu8UYaKk0e0YDJIZmiMqsuvDFQHqGc5+uWA5JAWgdQMxEgsmgUomN/m53l+QfUeGFqWaIFQ8Z0r/Db5DtM6WPYRwvFOKIqbL4QjcoQYF7EAb+drA6XfwI3+Pu6rVGZ1iDEeTq0hU4GHuciUHR1EmRacJiw44+IgA2QerjHCcOfFymK5L9VndX95ZL5g1hteUCIgDBHLwKiBOTJvQJXwTCg64VTcq4koFWfBAr2bA/K84nFQO/zd0PstVbLk/ww2bAWDaGICruS5Qm3DEcBDZyM+2I1hmlALKEAiOA6Tnf9yKl5/3tfiiOSuvPX8+PDV8fTJK7VCZaNqXFT0z547T10hzRrbfkj1XwHDimUYtJnJC3trtCd0vl9Yf5P2OfFR07o5s1Poxa1028bQ179kADrFZAtP9gb6SyIwYRZWxnqICqBkHmbeyuKVfcyVpDP/9+/mH1+HNU7v8q2qebw40v0IIQGEKJGwH8AvcDJTujYPFfR1BukLyb3TX5O6qkv9g7D3WyQHxRpWVIVeTqAXZ06Ik1CG5TYho7ooYOl8j3VEdQmnOwv4vdVWEj1dMf/v5O/6hOboXnGsZRQyDbyxz+Xwe+2Af8OE9IOupywuEhObDNAnhyy2fiFgkvvSuR72B3lfgkrCnn4W6047HzdQMUiyI4mufKTtUzyOEmp+F4SnkqZoeDS61FIyWjwF0GPQ337Hd+d1Rbf/jz8S/jpUDOqoP+/VzeUiM6hCvUaqbhL02rMTXXZLp9U7SamG4MlyN+6qhVNcuFcIQpiW/X4fx+AX5NeNfTKdS67fGL//mxOkun0s4M07L5EH7NH6vw2FY3mnp/CRBWUDggohgAADCGAJ0BKugBiAA+CQKBQIFmAAAQljaJLsWP/evrr7yi95IzsLxfJF/2VI9gDe9A/k2qd8QY6lh2+t9N/1LcuP1fYJiMX2v6T+M3b3zv9d/bfkx+Rn0Ocj+C3kPvH+7P+c/NK5S/Dy9+dr9B/gvyE+hv/b9af55/3fuC/pz/jv7B+7n9s+kHqs84v7oevB6XP8Z6hH9o/ynW0f0z/S+wj+zvrWf+v92fic/s/+2/c34DP2L///sAf//1AOi/9c+ADsaf1P4GnCn+Ht64N1GgnpjzX+f/yvRF9M+wT+q//L7AHoHfqOOffdUrKzVBhoFjf+JrTNIbKavxIA43AGpRqNz94rvyITk0o7pDGdWKgSfGnuMbT2yi7ALm4hyj6CcOnqm+n+fcJzmlIX9LduCbKqsU70TXwY3VVr0DFnyXcrzU/mHGg5O9KxgeBQidY8s/wX6gwOv4tUAPB8UFY38s/ahNxIMAbSmfoMUSx7t22EEj1+nJW7W36fP95EmUdMpkp3MTnc8vK/FrxQyHosWJTsvFYL+aHJU7JPsURW6LHIoqFllL+X5eFH0c1Ou+dkkOAUNUYQdDOTOWSm8ox3d7KJRwfMq2gEoo1LtS6tp+6zT/DKeqNJc2lNngkj0YRY484IxStFHED0Wz85S7YcIGM5ujhLXWdKPSO9Z6fZg2+ACpQeNvZ8/BRPUgOo6nklsaa3T8bJR8sC1Bh4OJ9I7mTlCz9Si1sNw7YB0T5rMvo6pDOR7xBIob/J0Bk/WGqwiUUvSIxTVR6g9I2kFpZyMB7h31vzWJOeBT3Lqew9hkH7bTdyUX9oXvzKE1S3WEjn7/iqwuVhztoPLzOPmnNerBqi+/sBGkTd/eRE5haqeHZOF4ybepTNf166A0arLq7d5qnpp5YXS9BCHyCsI0qG5xv4M2wKD3+maQE/x9Cdk+bUUVhpnvxHvDQ2wUccLKtOgDDtYX94D75aC+scPRaQGIUdXT9gL3vlhEAM4U27J4y1CfTIBqegwfuawnGNwgU3hNT69pVnz9gLuP0eqFQRc8DLwg3K/8Jn4YoLJ1lCaMy38fuYM2PTBp6vgHz/HtLKUD5xknyudwUb2Tqjnq5x2wL8PWRt65WlWXOJVLJkVFM3mv4Y+Jf5uaHwCGTf2/HrWszu2Ak4XD+xIo+g5TymY5uVfyfoFW439EWi22Q+QeY4zSh0T8OCbyXLh3nvr05tqxBMSLicoK3AgUSqDSksUZEe5dk3wR+0sUjXrh2erGdfuRwcGndYZxAnno4UWkNujHNUIU1WlT1nHfS7oB5qtLosyS2rNAIHkrSKilUP+MjaFPgWrwGg5fvVDWrWHHU8j37w3L9edYPoZqs5gJ3VREhecIWw59tAKLU2IuHpO7ZM8ydy2/ixnvTazHkX+HrCcadQ1YJcznZQDQDmtXpUlb0XBlDr7T9S/GDjR4AP7yZyAN///VgzJQHDWO7JErTE6Q/8CVSeWGd1zi72rvaZweKvqG52uuIv/9lVLpodKLbPcHXy86eQPaxQvGFy7n79F8J19siKJBMyFeMWwCk1osPBOI2uIu/0ExgOZAf9W332Lz2lYrHy9osPBOI7tdLZMzfb4RIgFpmExg5YeWn2/kUjSmPn2gZJwrXsevSwM6M4acUqOt2NFT6VwXXWLTC/zlWgCkmrg8ENPmBdISa5IRf9qwwc/v7+p7GDfRuWnwUW01Ey2TtAKd6HPgaNTND7wz05JMYG5FO7jrJI3360LRBoQisvpNEmktubHAth8V+QZ2WHqNA/EEmPZ3s2GzECfkO4vF3yFZZsCOP7y5QN+sH6VVrBXw6jpT6+Ou8IuVPS70ncDlsVE1eizPy11GQsswbduvja3hUe502hsaRRfW6eiOi3jvc99GEULqUTGu1kO+SpGHbmGypsVOQRX/MWqXFNz0e5dCRQvx7iY0DaC41xQOchtLl0t9IZMNNUNM4uhev47e4eJ983TdZ46veF6igpbAOx+B+OPipJUMRuHVAWOmo+yM0OHpdu7rFF8+6PfPlba/sfAjG/PMMWR8pafMsGcLbEfwxR+I4eFefK3rnowrEztg5/opz6sgCnTk3wdhjQcWRyZ5wDThXfXkLW35kjwP8XazddeGgtmSli1NJGpuiNjL//tS2Gb7vvbFKxjd5r8Efb2wFS/8X1i/ycBAIovjZaDO5rejgWIe8M/zwvvkRCRpvXQ26djqnZ3gbVe5pd6SzZwE+MtG7EqjrkvtDpWWNwPx2pI90+IwwphAABe//6iX/c1yZu7yAkGhNE1SoElwtyedmjmMsYC90jLx1jKEH//qJhEYR+Anbn92bXoKoC9POJ1A0jXjBWCRN3AGUuyQp461MBAfArnmbWdvCGvYWnWdycn61UYXYlyu3GuPxrd2pOFoF0kp+3tBOteItlFykyHZN0IHG1qaqyhprA7WnnQjYfhwe/K5FQsjeGxl0IiopkLbH6zvlC1O7oNIQNtLYuW/9y4W3LLoEp8qPtkUEnFmHX9Q71XVJqiuAEGnJ05arcEWpQJ+B9XO1vNkg61BD25ad6DU7V5XKrNEFurlwj7SBRAxV0ddpukTklX+VHeaaL2IBWdVBxEFoPerNNDWalYqO5kWpcRiLh71ClcjXwVqDePqPCSppvPjqN0rFqh+jMR5jrJcA3BI9av0RVeiOISKeesvvovvN7VzyxVOPnZuai7uhQ9ARrOFjEmYEUIA5Ck668QMT+h10WZxO5MOQcIoSUkVLe60jYgHb+dIVdDrG7lXaZdbrgXRYR1zxNy+qRr+hTVxeIBfmZJceN6sppr0OhaIjVtNalIr7euJFAHtZRKc/05i2Zyuwd6ohqW/zjFlNVAyS72/mHeo3sFqDO68T3XRouaKIoigOvekhgawA12lE+vyV8zYrzeoshDs2PA/XINrlBzCBW1Dd+4Yy/nUSjsfYAshLy1V/HjF6/0jXqwcYS1ztA/CQXivW9bZpN0JUOmBpb8UfU2g73GSp7TndPBHlP36XYM/fwawslzjMExtd9kGwelcXR/4Lj1MYtcil7QlG5IzQjMGgQQ3sb7R3QRMffX5cov5HJ9jXnfx2BX8Wwa8sIYezPyGQoqa3f8RI7JHk0mHSyqLksQg1AB2//0DbqDX20Yi6lYerVNFW/TSDwKwzYAmSGji6qmaoLzY/lHc7xZlo/0UahT3OTCWW1JuCWCiRuHmzlKtvcxxjf5k7HzojsFMz5MG2w3GHa+QiNjB9ssLhgMnxcSP+R2KbFmDADKD5yAI5LhAUNE0OL2WjaQ/jz2BwC/cIbb4iNnEv2/xrSlZAt+xgwNnoUuecP2nrYI2qPIEMs4zUca+YhLnMGv6mRGVNv95oribYJW84iuKWiuI2pjSPDBu4b4fKrkqB11/w9YBF9wE0DrAsIDi6Qb3a+e2p+T4dh9fRyj2DG07p8ZSy2PP9lxReMJhrurEwpgUMd+kxE9tUH6w2MXFM9aaxw0sUc88WHo9J32IroFH9pl0zlXEBtdtdobPVhJlilkLyRIEJ2PeJiUs4T03Pbx3T5L2aJ3nENQFD8+5ZmmoItfvh/KD7+74j1PiKMfpGvETStnoqG9OFN7yDP+uzDc9QV1qChSo9CQFabEZy1nqDBXr9q8hdIO+nfioC1JnRywRApGoL0INympsaeUKa8K+Aeq/etDYmdge/sAWALCUDee4xoxQnZPHqhQ9G+0d2eb/ZKOsq06z8FgmuDLWLckr3RPoSxWbNbzu8IUMn5g5lkrWKQjlsvzpsJp5nfmxwATK0gM1HVodoOVt//CC1VHAkEjpRC/HXPw9PvSu/g9PeZ/hP9AM+I3qepTNa3Fw5h3mkeE8ctflAx+rYRohuXGLj9wyPC7lWGtHTD+mZhrXP7EKOCnhSeX2JXD1ckY2+qbF+UNniELgAjxBpe+d0nSlPclyQ1vf02W22OWe6tgE4fpzZLpFH19VCl6MAw5jVG0Yfrfxdt/4PJ6fciOdJFUKNWiPVFxQqGHl44hfESLyV0KAvwVh3wHQgH753B5VYT0r5fjpZswNubx2tD8aCcT3BwoCktAjXzgBluKeV9KVtD5cIZCTU5qniHgU1IJGEfseEfSnBiNAKi1GkNXqb025Djdhg54SX/ZiDy9qUTN3K5AAHhmivTTjfObrVrF/lTUJOdXfPUDONVE8RCavJ3VEVV7V/PuVmgfjfwTfpX2uL02YCcaQvTt8Js+6z6F6bhJXSG8vbIh6q+/GBJFUjp/T4CfhW45bL9ET2WNf3SDBwslbjtlYu8Y1d0rsC4Sr4Ms1qReyaJ6+hYhZrGc+rDDLZ8itVMMEEXqTlGVgtqLlZNwrXZfzSpHbksZYeamBldwy3aFYlgoe6agXUIGXoHs/WfnmRmqjhMSU1LrRX7Ur1lpYpmhUbaXxZQ+tjCpao5xE30OSwgo8ItFsTt3h1eN8O2hI16IFcey81Mqjaa4JJZpEYmFe6hKObPaF4+2ogGHMJt9mQIbHEfpKihu2ekNLoExJtq3TByI84fzLVmGV7nO+Ub9AqCwiCtnbBLZSYRHh1MOiEmqUT/qN94PjnCdBPbInn3Qe/G5hhhqtqdLFyBjMSyWoCoDiEZTeurhc2vRD9yOBhCe+eL1K3rKpQZoN79+/w5/qK6WyN8nK/xHyousGN/RuH7tP+H8h6h0WymgzNS2TeIYwwBma/iLQ5+K52/Tv/+ESwqKjPJZQXCxgVWbYvK7ttdrsD3WSajikrvZ4TORd/gnxtFGm8iv4w/CxIgJ8iJsIVr4PNSnXTQI5Jx7T5y2dOyCsdj8nH6QK9ZqI6X4vQB2lSc3yOuJ9vuOPcgtEY3npHAJtqotqH6UVBAk/f0u7tz04wQ7UsJ/jGi0dwO8Thrw1zn0GeGn4Yonv92g9xSj+5WHsnwLjiTHG0RbgIbPZExOpmZbPfP+JlRmLBL6rZRpr4kpYTCgtlmt1JIp3bFHSTkvKNbEYjFxNCV6pnbM9Vd4J5NRT4MGXRyr7Uh8ASGnQvQlVoal8esOq4gJ/BRdaIjLIZDr3cJFFi03+mXkDC7rk0foA78kwWplSi2Bj5c2zv64KWAhYRiYffzJF3s0Gv7nGwchgy+0uLS42RCJ/rQ8HSsyHph7GBF8F2Cu1UtCbfCsPzbD5AG2xHTM4o5/ZeuXvoGgCZKe4DeXvxsURC9I7e7ykXJtCpWvlRf9JyKk9oYcF0YKnlDctspM8zjCv/FV7PkeospbI1Ja14j0ezgpuzohbjhiTF7c7v4+Fe3SYyb0EF/a6PIIk6I+D/Beb6mIhzUvVV/mnfjatzoc4W17kdNZek8QD1fdtX7i80RwbPn4NMCJresfSz3x1qpypg4LR0CgjLk8LQVrxXj1tzWhuGJ+6pQuTiJ4X3JeTjoU0VYuo55ZnLKnirh1CEvzkmoQ6VkoNAMeZrjPC7na07UHkadYWPDibMyt+OQ5VKs4SjvRqT4pu3Z89kSJBjPM4e06IsFmSqr1tdygMTLn82/KssPGApDHZEZKXzJkbQCnRiK8+17uBmmvRAzDQP+WrMjNi87v6tU6pwbRjSzjbKowMMd1AthO83+uCZ7SQcq8lUzaCb8pgJfxTngJno0WJr+lUjVEp9BHAqJ1DKp3cmZjr4/OoLbkkFt8YW1jLzCJdk6KuB4/2hLTCK4dTzpiLvxyFxskuySJKxftyF5wpA0JxN/+ClYCcisFeOoYu/tsgaVBe33i4vc3OxY7rakkVqdxqfza6eik7Ik5bTgx5hVC+8sBQIEyfVWlSGUq/txNTH7CBPdqgB0GUIzeJEQDEd314WANa1jQ5OwPXx0P5GASXo40M9HdK9QmJTe1+F3oXaQ8rxnUcXcQuNH+QyxdR0xt9fn3tReRpUg1zRk0UQN6aGr/iyW2sZKI2+QcA0jxav2Wu2G38T96nALwknFHwv6p7wx5zT8mjdpOff1AcZp9RsbiGEh5aT96KOVk6numlJmNeBJJ4KCjWi1g9YJKlJlstu8loc7oRv1xVd52+JsliVl5rUAue8Yysuy8oywiTfPtN6QbzbnQ3UGf1s5+Anq5bWGsaPxfVgGDjh8NTf0vvDuvos/vvzz9lKDoDVL9/zKqxfyvg8Suli1JHOKENdR1TQwyAL1426NY5Xtvc+L6XhHgxaL3vm2227BzEXWGM7vmi0e2MTma6SKn/+g59MLDbgobZC5QfwuOzKkLMcdldE1XBd4qYgf3itU0UmiQhxjX9M92YKOpPWQJf47frjeaCsd9Ck9BiSwVJGChTnIuF35WM5a14R+RXTbXOZdMsPNOwpOtI4p/th2PG0q/aEAoUKPfauCJxLBol/KU9lFn7jX6rnnNj6vQycRXiJVMatMWso3AFyE+XDPlZMmXxNOjABHwwsPMY0A4PrZn3BwBrWu5ytpA6zZEyacL5NLkivpuC3WT2uZvy48J7HGXC2NHSWbEWNxDutXEJIqUSD5YtyAy2tpNXK8YJldVLPqSUNQVQb+ryBJd/BT4+BbZfcvp6jZyJLueG9hHYte9C4pNQiM+AqoPTTzq3i4++9ar+ZTEwTvtp0omx2JhQCbVw9A2V0X4qEqXSBUewag0BBvIPGyb2xn9m1ryFDiUWPBQ4X76rFnmQGPuJR3Rm2tdlaJXlsOq23MP8oxZrU+OxiOJhTvVkynDerx5PuLnWG+8i1JYMPKjRPXZwZYsUPAKO8JrdptcLZ57M7nEmw/zKmKyhdeOjFC9WZ9QHCmYnXoB6BPq45Kwr8QmQJDZdbV355yi2in3RFIlpOVI1phHqv3aRqRSspZgDX6WcsMQgSKtkhZuAvyU5E1r9sCOnXe3n5jm3DQjcI64f6Jbaua4BKzmCnTGMiPaA1GgVtYQ+Se/ayJ2df3KZVFLsabDAkbqZyROEN3KHoAHOJobNVXYzkML+BqHKtaiFycwpkbntr3m/ocfs3jIXaTE1ficzPVB/85+6ICzmJzNnO3SWnCkxdINqfx8sz+8jxESCECbmN+0jnQDbi3+qg2NZp9HUlHxaVkmdl87DlE/yX0w6d5/G2v705ZZ+D85C9Z8GOSYTNO7+3PAVVHerlJ064ZT/nns1XE6H0p6zPAiGiht81bxpelObALTxFfES5//2Es+Ba/WU6aarmpAQPwksJoaFWG4iiKfqjt41Rv8aMw+NsH8Sbm/42pjCnttQd34yxVtD/T2xK4wqqnErqzLWBybKJqB77YX3JyRiVv5EHtXYMbKmkSAeO5zzsnfMS0FpQGEQCj1uSeAnujYZprjQNqNUAW8b5Q1dyFdT6q3wsoTgUV1bbkZg4V2hMmxmpAepAGLXbyoiVMN3k/3w0Jri7AFKFUwF9VNTX0kSlMvb1f7akoPC9aZyBEl+SLntnihC9vfBhNDJny2Qj7cCaI7EkK8IVwkACWYuKaGIW2Q15qZJuMnh4zgBCQm7KBMwWbbIJamIxgPtbzxIl5Ae7BW+n7txDNBZV43MIjgieXPYU7uTE17HknT7vxOeLO9fAQa7LQZSMCW387r0ei3R4IkzZJ5UrsPvlKq0fhJ8T29rGzlKS4n4MwuiruiTphOI/aATXDPq/dP/OLX6DU1ddyKQQ3jRxQe/Et1y/QnEMsolK/JoiQ0vYJio7SqosjFnBZIyQP39OG89r4f+Fnq8eXHfbTwVb5E0KXwf3WpPeKN3khkv0PRJJZmN7dsxkxGHLPmL70YgZweduYDTlE050bJsjQ3Tm8GfZvwPDew5sF8eYUBw3WjTeQqnxwgInrsUhtZYn0SZyfJ9///1fKxw9/8J1/J4X/0KEvAbVYsCV93mOlxsJ/+eY5CCUKygaAAAAAAA7YNi3HNYm68tdNCZKFjl2Gi8z9vaHjzOfbK5A0XLtfbQUTHoMcHfx0X+hZYIDKsG7ftQW/BAAQKh+jt9Tg//s6ZspKVp+BQOd+6aqGBkPAlViEZEaXLPLcRqsGNRwaDX+dTxP8dQ/0M+gtWLSf+Lh/F0C3c5FZ4CqFHe8va7ViehM4ENJOsXSkeBAtKBqwM1373DUjaeVZbgEJd5dMUfD1F7+xKN1bMJRaxnWQIDR6XHcCEOrdJcRsODH9UWSAMQIflMzTDD7MYsmzX+NxzlK6a4uHXiQNAmGoko23f+XQaxN2JaMM7YPNqm5Bq2PjAhmm/HW94ap41ZlBo6YCyvUd19/5DQawyUmIczRBdcQA19yxjvSMwR4WP3GTVWAnYmT/EKRw5EHnovBEXEhGhI43usyHHOQxJhOzjYZAQ2YyFVajfwN+2+gL0o14wMk8OQgCAl5J17ETpAnlSObY9MzP9W2gDrS9sAT7uB2yvsDfYslLmyPOdT0+nuK/jZk3fbZA8pc67mAHovryD/rsA1WFz6Wzo947pY9at/nv2VMf/xt///8wP52PpbzXZFkqu+6Yb0Qbu6o8HRXu9sU62+bAAAAAAAAA==';\n",
      "type": "registry:ui",
      "target": "components/glassyui/liquid-filters.tsx"
    },
    {
      "path": "registry/glassyui/lib/preferences.ts",
      "content": "export type GlassyUiMode = 'clear' | 'tinted';\n\nexport type GlassyUiQuality = 'low' | 'medium' | 'high';\n\nexport type GlassyUiTheme = 'light' | 'dark';\n\nexport interface GlassyUiPreferences {\n  theme?: GlassyUiTheme;\n  mode?: GlassyUiMode;\n  reduceTransparency?: boolean;\n  reduceMotion?: boolean;\n  quality?: GlassyUiQuality;\n  /**\n   * How thick the glass is, as a multiple of regular: its rim widens, bends the\n   * backdrop further and splits more colour. Kept between 0.25 and 2.\n   */\n  thickness?: number;\n}\n\n/** The range of the thickness preference. 1 is regular glass. */\nexport const GLASS_THICKNESS = { min: 0.25, max: 2, regular: 1 } as const;\n\nconst THICKNESS_PROPERTY = '--lg-glass-thickness';\n\nfunction clampThickness(value: number) {\n  return Math.min(Math.max(value, GLASS_THICKNESS.min), GLASS_THICKNESS.max);\n}\n\ntype NavigatorWithMemory = Navigator & { deviceMemory?: number };\n\nconst DATA_KEYS = {\n  theme: 'data-lg-theme',\n  mode: 'data-lg-mode',\n  reduceTransparency: 'data-lg-reduce-transparency',\n  reduceMotion: 'data-lg-reduce-motion',\n  quality: 'data-lg-quality',\n} as const;\n\nconst STORAGE_KEY = 'glassy-ui-preferences';\n\nconst THEMES = ['light', 'dark'] as const satisfies readonly GlassyUiTheme[];\n\nconst MODES = ['clear', 'tinted'] as const satisfies readonly GlassyUiMode[];\n\nconst QUALITIES = ['low', 'medium', 'high'] as const satisfies readonly GlassyUiQuality[];\n\nconst FLAGS = [true, false] as const;\n\nexport function applyPreferences(target: Document, prefs: GlassyUiPreferences) {\n  const root = target.documentElement;\n\n  if (prefs.theme) {\n    root.setAttribute(DATA_KEYS.theme, prefs.theme);\n  }\n\n  if (prefs.mode) {\n    root.setAttribute(DATA_KEYS.mode, prefs.mode);\n  }\n\n  if (prefs.reduceTransparency !== undefined) {\n    root.setAttribute(DATA_KEYS.reduceTransparency, prefs.reduceTransparency ? '1' : '0');\n  }\n\n  if (prefs.reduceMotion !== undefined) {\n    root.setAttribute(DATA_KEYS.reduceMotion, prefs.reduceMotion ? '1' : '0');\n  }\n\n  if (prefs.quality) {\n    root.setAttribute(DATA_KEYS.quality, prefs.quality);\n  }\n\n  if (prefs.thickness !== undefined) {\n    root.style.setProperty(THICKNESS_PROPERTY, String(clampThickness(prefs.thickness)));\n  }\n}\n\nexport function readPreferences(source: Document): Required<GlassyUiPreferences> {\n  const root = source.documentElement;\n\n  const readOption = <T extends string>(name: string, options: readonly T[], fallback: T) => {\n    const value = root.getAttribute(name);\n\n    return options.find((option) => option === value) ?? fallback;\n  };\n\n  return {\n    theme: readOption(DATA_KEYS.theme, THEMES, 'dark'),\n    mode: readOption(DATA_KEYS.mode, MODES, 'clear'),\n    reduceTransparency: root.getAttribute(DATA_KEYS.reduceTransparency) === '1',\n    reduceMotion: root.getAttribute(DATA_KEYS.reduceMotion) === '1',\n    quality: readOption(DATA_KEYS.quality, QUALITIES, 'high'),\n    thickness: clampThickness(\n      Number.parseFloat(root.style.getPropertyValue(THICKNESS_PROPERTY)) || GLASS_THICKNESS.regular,\n    ),\n  };\n}\n\nexport function persistPreferences(\n  prefs: GlassyUiPreferences,\n  storage: Storage = window.localStorage,\n) {\n  const current = loadPreferences(storage);\n  const merged = { ...current, ...prefs } satisfies GlassyUiPreferences;\n  storage.setItem(STORAGE_KEY, JSON.stringify(merged));\n}\n\nexport function loadPreferences(\n  storage: Pick<Storage, 'getItem'> = window.localStorage,\n): GlassyUiPreferences {\n  const raw = storage.getItem(STORAGE_KEY);\n\n  if (!raw) {\n    return {};\n  }\n\n  try {\n    return decodePreferences(raw);\n  } catch (error) {\n    console.warn('Failed to parse Glassy UI preferences', error);\n\n    return {};\n  }\n}\n\n/** Keep the stored fields that hold a known value; drop stale or malformed entries. */\nfunction decodePreferences(raw: string): GlassyUiPreferences {\n  const stored: ReadonlyMap<string, unknown> = new Map(Object.entries(JSON.parse(raw)));\n  const prefs: GlassyUiPreferences = {};\n\n  const keep = <K extends keyof GlassyUiPreferences>(\n    key: K,\n    options: readonly NonNullable<GlassyUiPreferences[K]>[],\n  ) => {\n    const value = options.find((option) => option === stored.get(key));\n\n    if (value !== undefined) {\n      prefs[key] = value;\n    }\n  };\n\n  keep('theme', THEMES);\n  keep('mode', MODES);\n  keep('reduceTransparency', FLAGS);\n  keep('reduceMotion', FLAGS);\n  keep('quality', QUALITIES);\n\n  const thickness = Number(stored.get('thickness'));\n\n  if (Number.isFinite(thickness) && stored.has('thickness')) {\n    prefs.thickness = clampThickness(thickness);\n  }\n\n  return prefs;\n}\n\nexport { STORAGE_KEY as PREFERENCE_STORAGE_KEY };\n\nexport function computeAutomaticSafeguards(): Required<\n  Pick<GlassyUiPreferences, 'quality' | 'reduceMotion' | 'reduceTransparency'>\n> {\n  if (typeof window === 'undefined') {\n    return { quality: 'high', reduceMotion: false, reduceTransparency: false };\n  }\n\n  const supportsMatchMedia = 'matchMedia' in window;\n  const coarsePointer = supportsMatchMedia && window.matchMedia('(pointer: coarse)').matches;\n\n  const prefersReducedMotion =\n    supportsMatchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n  const prefersReducedTransparency =\n    supportsMatchMedia && window.matchMedia('(prefers-reduced-transparency: reduce)').matches;\n\n  // SAFETY: deviceMemory is a number where the Device Memory API exists and absent elsewhere.\n  const deviceMemory = (navigator as NavigatorWithMemory).deviceMemory;\n  const lowMemory = deviceMemory !== undefined && deviceMemory <= 4;\n\n  // Device heuristics only lower rendering quality. Opaque surfaces and stopped\n  // motion stay reserved for the user's own accessibility settings.\n  const quality: GlassyUiQuality =\n    prefersReducedTransparency || lowMemory\n      ? 'low'\n      : coarsePointer || prefersReducedMotion\n        ? 'medium'\n        : 'high';\n\n  return {\n    quality,\n    reduceMotion: Boolean(prefersReducedMotion),\n    reduceTransparency: Boolean(prefersReducedTransparency),\n  };\n}\n",
      "type": "registry:lib",
      "target": "components/glassyui/lib/preferences.ts"
    },
    {
      "path": "registry/glassyui/styles/liquid.css",
      "content": "/* Glassy UI styles live in the components layer, below Tailwind utilities, so\n   a className such as rounded-xl or p-8 overrides the library default. These\n   statements repeat Tailwind 4's own layer order, so it holds even when this\n   file loads before Tailwind. */\n@layer properties;\n@layer theme, base, components, utilities;\n\n@property --lg-fresnel {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-specular {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-refraction-scale {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-refraction-blur {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-refraction-scale-a {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-refraction-scale-b {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-refraction-blur-a {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-refraction-blur-b {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-refraction-dispersion {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-rim-width-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-highlight-strength {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n@property --lg-rim-micro-mask {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-rim-corner-mask {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-micro-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-corner-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-ca-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-ca-offset-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-rim-thickness {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-rim-alpha-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-tangent-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-debug-highlight-scale {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n@property --lg-filter-scale {\n  syntax: '<number>';\n  initial-value: 0.98;\n  inherits: true;\n}\n\n@property --lg-bg {\n  syntax: '<color>';\n  initial-value: #1c1c1e;\n  inherits: true;\n}\n\n@property --lg-panel {\n  syntax: '<color>';\n  initial-value: rgba(255, 255, 255, 0.04);\n  inherits: true;\n}\n\n@property --lg-press {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: true;\n}\n\n/* How high a button's glass rests above the page: 1 at rest, lower under a\n   pointer, nearly down while pressed. Its shadow and the light it lets\n   through follow the height; the glass itself stays put. Registered so the\n   height eases instead of jumping. */\n@property --lg-button-rise {\n  syntax: '<number>';\n  initial-value: 1;\n  inherits: true;\n}\n\n/* Where the light on a prominent button comes from: 0deg straight overhead. A\n   pointer swings it over to the top left. Registered so the light eases\n   across instead of jumping. */\n@property --lg-button-light-angle {\n  syntax: '<angle>';\n  initial-value: 0deg;\n  inherits: true;\n}\n\n/* Glass scene material. Registered so the renderer reads resolved values and\n   tints can transition; engines without @property resolve them by probe. */\n@property --lg-scene-tint {\n  syntax: '<color>';\n  initial-value: transparent;\n  inherits: false;\n}\n\n@property --lg-scene-frost {\n  syntax: '<length>';\n  initial-value: 0px;\n  inherits: false;\n}\n\n@property --lg-scene-merge {\n  syntax: '<length>';\n  initial-value: 0px;\n  inherits: true;\n}\n\n@property --lg-scene-flow {\n  syntax: '<number>';\n  initial-value: 0;\n  inherits: false;\n}\n\n@property --lg-scene-ink {\n  syntax: '<color>';\n  initial-value: transparent;\n  inherits: false;\n}\n\n@layer base {\n  :where(.lg-glass-surface > *) {\n    position: relative;\n    z-index: 2;\n  }\n}\n\n@layer components {\n  :root {\n    /* Core Physics: the frost every glass layer reads. Controls, alerts, badges and\n       dock labels cap the blur at their own size and follow the rest. */\n    --lg-glass-blur: 20px;\n    --lg-glass-sat: 1.8;\n    --lg-glass-contrast: 0.9;\n    --lg-glass-brightness: 1;\n    --lg-glass-alpha: 0.02;\n    --lg-bg: #1c1c1e;\n    --lg-panel: rgba(255, 255, 255, 0.04);\n    --lg-foreground: #f5f5f7;\n    --lg-muted-foreground: #e5e5ea;\n    --lg-border: rgba(245, 245, 247, 0.22);\n    --lg-control-surface: rgba(245, 245, 247, 0.08);\n    --lg-control-surface-hover: rgba(245, 245, 247, 0.14);\n    --lg-opaque-surface: #2c2c2e;\n    /* Floating layers keep 4.5:1 text contrast over a pure white or black backdrop. */\n    --lg-overlay-surface: rgb(44 44 46 / 0.78);\n    --lg-scrim: rgb(0 0 0 / 0.45);\n    /* Slabs are clear glass. The page shows through, dimmed by brightness so\n       muted text keeps 4.5:1 over pure white. The lens rim is dimmed less, but\n       never above what holds that bar at the very edge. */\n    --lg-slab-tint: rgb(28 28 30 / 0.3);\n    --lg-slab-brightness: 0.38;\n    --lg-slab-rim-brightness: 0.46;\n    --lg-slab-blur: 3;\n    /* The header is a clearer slab. Its links sit clear of the rim, so the rim\n       lets the page through undimmed, bent and split into colour, while the top\n       keeps muted links at 4.5:1 over pure white. */\n    --lg-header-tint: rgb(28 28 30 / 0.35);\n    --lg-header-brightness: 0.55;\n    --lg-header-rim-brightness: 1;\n    /* Familiar blue for actions; Vermilion belongs to the brand and artwork. */\n    --lg-accent: #0067d9;\n    --lg-accent-foreground: #ffffff;\n    /* Prominent buttons are blue glass. The tint recolours whatever lies\n       behind them and caps how bright it can get, so white text keeps 4.5:1\n       over any backdrop. The fill stands in where there is no glass: reduced\n       transparency, forced colours and a GlassScene. */\n    --lg-prominent-fill: #0067d9;\n    --lg-prominent-tint: grayscale(1) contrast(0.4) brightness(0.55) sepia(1) hue-rotate(174deg)\n      saturate(6) brightness(1.12);\n    --lg-prominent-glow: 178 214 255;\n    --lg-prominent-shade: 4 33 65;\n    /* Focus stays neutral so it does not read as a blue selected state. */\n    --lg-focus-ring: #b8b8be;\n    --lg-invalid: #ff7a8e;\n    --lg-disabled-opacity: 0.45;\n    --lg-motion-duration: 200ms;\n    /* Springy settle for presses, toggles and moving indicators */\n    --lg-spring: cubic-bezier(0.34, 1.56, 0.64, 1);\n    --lg-ease-out: cubic-bezier(0.32, 0.72, 0, 1);\n    /* System controls: the resting track, the switch's on state and the white\n       knob that turns to clear glass under a finger */\n    --lg-track: rgb(118 118 128 / 0.36);\n    --lg-switch-on: #0067d9;\n    --lg-knob: #ffffff;\n    --lg-knob-shadow:\n      0 2px 6px rgb(28 28 30 / 0.16), 0 1px 2px rgb(28 28 30 / 0.1),\n      0 0 0 0.5px rgb(28 28 30 / 0.05);\n\n    /* The fixed light the CSS glass is lit by: where its highlight sits (0 to\n       1 across the glass), the direction it strikes the rims from, and its\n       strength. The glass never follows the pointer. */\n    --lg-light-x: 0.5;\n    --lg-light-y: 0.5;\n    --lg-light-dir-x: 0;\n    --lg-light-dir-y: 0;\n    --lg-light-level: 0;\n\n    /* Theme Colors */\n    --lg-ink-dark: #ffffff;\n    --lg-ink-light: #000000;\n    --lg-fresnel: 0;\n    --lg-specular: 0;\n    --lg-refraction-scale: 24;\n    --lg-refraction-blur: 16;\n\n    /* Edge reflex: strength of the lit bevel and of the shaded inner edge */\n    --lg-reflex-light: 0.42;\n    --lg-reflex-dark: 1.4;\n    --lg-button-reflex-light: 0.7;\n    --lg-sheen-alpha: 0.05;\n\n    /* Specular edge: a hairline ring brightest where the light enters (top\n       left), with a softer counter highlight on the far edge. The glow is the\n       light caught inside the glass thickness. */\n    --lg-rim-width: 1px;\n    --lg-specular-peak: 0.45;\n    --lg-specular-counter: 0.22;\n    --lg-specular-floor: 0.07;\n    --lg-glow-alpha: 0.06;\n\n    /* Lighting & Depth: outer shadows only, the glass edge lives in --lg-glass-rim */\n    --lg-shadow: 0 1px 2px rgb(0 0 0 / 0.18), 0 8px 24px -4px rgb(0 0 0 / 0.28);\n    --lg-shadow-raised: 0 2px 6px rgb(0 0 0 / 0.2), 0 30px 60px -18px rgb(0 0 0 / 0.55);\n    --lg-input-shadow: inset 0 1px 2px rgb(0 0 0 / 0.24), inset 0 -1px 0 rgb(255 255 255 / 0.06);\n\n    /* Chromium edge lens, read as plain numbers by useLensFilter:\n       depth scales the edge bend, blur is the frost radius in px. */\n    --lg-lens-depth: 1;\n    --lg-lens-blur: 6;\n    --lg-lens-saturation: 1.5;\n    --lg-lens-bevel: 0;\n    --lg-lens-thickness: 0;\n    --lg-lens-ior: 1.5;\n    --lg-lens-dispersion: 0.16;\n    --lg-lens-body-brightness: 1;\n    --lg-lens-rim-brightness: 1;\n    --lg-glass-thickness: 1;\n\n    --lg-noise-alpha: 0.03;\n\n    --lg-radius-md: 16px;\n    --lg-radius-lg: 24px;\n\n    /* Glass scene: shadow and highlight strength, and extra white in the tint. */\n    --lg-scene-shadow: 0.34;\n    --lg-scene-highlight: 1;\n    --lg-scene-tint-boost: 0;\n\n    --lg-noise-image: url('data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 120 120\"%3E%3Cfilter id=\"grain\"%3E%3CfeTurbulence type=\"fractalNoise\" baseFrequency=\"0.8\" numOctaves=\"3\" stitchTiles=\"stitch\"/%3E%3CfeColorMatrix type=\"saturate\" values=\"0\"/%3E%3C/filter%3E%3Crect width=\"120\" height=\"120\" filter=\"url(%23grain)\" opacity=\"0.5\"/%3E%3C/svg%3E');\n  }\n\n  [data-lg-theme='dark'] {\n    color-scheme: dark;\n  }\n\n  /* Light theme */\n  [data-lg-theme='light'] {\n    color-scheme: light;\n    --lg-bg: #faf9f7;\n    --lg-panel: rgba(255, 255, 255, 0.5);\n    --lg-glass-alpha: 0.15;\n    --lg-glass-blur: 16px;\n    --lg-glass-contrast: 1;\n    --lg-glass-sat: 1.6;\n    --lg-glass-brightness: 1.06;\n    --lg-ink-dark: #222224;\n    --lg-ink-light: #222224;\n    --lg-ink: var(--lg-ink-dark);\n    --lg-foreground: #222224;\n    --lg-muted-foreground: #505055;\n    --lg-border: rgba(0, 0, 0, 0.14);\n    --lg-control-surface: rgba(255, 255, 255, 0.72);\n    --lg-control-surface-hover: rgba(255, 255, 255, 0.95);\n    --lg-opaque-surface: #ffffff;\n    --lg-overlay-surface: rgb(255 255 255 / 0.82);\n    --lg-scrim: rgb(0 0 0 / 0.16);\n    --lg-slab-tint: rgb(255 255 255 / 0.8);\n    --lg-slab-brightness: 1;\n    --lg-slab-rim-brightness: 1;\n    --lg-slab-blur: 3;\n    /* The tint alone keeps muted header links at 4.5:1 over pure black. */\n    --lg-header-tint: rgb(255 255 255 / 0.77);\n    --lg-header-brightness: 1;\n    --lg-header-rim-brightness: 1;\n    --lg-accent: #0067d9;\n    --lg-prominent-tint: grayscale(1) contrast(0.5) brightness(0.35) sepia(1) hue-rotate(172deg)\n      saturate(6) brightness(1.4);\n    --lg-focus-ring: #66666a;\n    --lg-invalid: #c4153f;\n    --lg-track: rgb(118 118 128 / 0.2);\n    --lg-switch-on: #0067d9;\n    --lg-noise-alpha: 0.02;\n    --lg-reflex-light: 1;\n    --lg-reflex-dark: 0.8;\n    --lg-button-reflex-light: 1;\n    --lg-sheen-alpha: 0.08;\n    --lg-specular-peak: 0.7;\n    --lg-specular-counter: 0.55;\n    --lg-specular-floor: 0.35;\n    --lg-glow-alpha: 0.14;\n    --lg-shadow: 0 1px 2px rgb(0 0 0 / 0.06), 0 8px 24px -6px rgb(0 0 0 / 0.14);\n    --lg-shadow-raised: 0 2px 6px rgb(0 0 0 / 0.06), 0 28px 56px -18px rgb(0 0 0 / 0.26);\n    --lg-input-shadow: inset 0 1px 2px rgb(0 0 0 / 0.08), inset 0 -1px 0 rgb(255 255 255 / 0.8);\n\n    --lg-refraction-scale: 24;\n    --lg-refraction-blur: 20;\n    --lg-fresnel: 0.1;\n    --lg-specular: 0.05;\n    --lg-highlight-strength: 0.1;\n\n    /* The light sits a little above centre and catches the top rims. */\n    --lg-light-y: 0.3;\n    --lg-light-dir-y: -0.35;\n    --lg-light-level: 0.15;\n\n    --lg-scene-shadow: 0.16;\n    --lg-scene-tint-boost: 0.06;\n  }\n\n  [data-lg-theme='dark'][data-lg-mode='clear'] {\n    --lg-glass-alpha: 0.02;\n    --lg-glass-sat: 1.8;\n    --lg-glass-contrast: 0.9;\n  }\n\n  [data-lg-theme='dark'][data-lg-mode='tinted'] {\n    --lg-glass-alpha: 0.12;\n    --lg-glass-sat: 1.5;\n    --lg-glass-contrast: 0.9;\n  }\n\n  [data-lg-theme='light'][data-lg-mode='clear'] {\n    --lg-glass-alpha: 0.08;\n    --lg-glass-sat: 1.8;\n    --lg-glass-contrast: 1.02;\n    --lg-glass-blur: 20px;\n  }\n\n  [data-lg-theme='light'][data-lg-mode='tinted'] {\n    --lg-glass-alpha: 0.25;\n    --lg-glass-sat: 1.4;\n    --lg-glass-contrast: 1;\n    --lg-glass-blur: 14px;\n  }\n\n  /* Tinted slabs frost harder and hold a denser tint; the rim still lets more\n     light through. The header matches the other slabs. */\n  [data-lg-theme='dark'][data-lg-mode='tinted'] {\n    --lg-slab-tint: rgb(28 28 30 / 0.55);\n    --lg-slab-brightness: 0.55;\n    --lg-slab-rim-brightness: 0.65;\n    --lg-slab-blur: 14;\n    --lg-header-tint: rgb(28 28 30 / 0.55);\n    --lg-header-brightness: 0.55;\n    --lg-header-rim-brightness: 0.65;\n  }\n\n  [data-lg-theme='light'][data-lg-mode='tinted'] {\n    --lg-slab-tint: rgb(255 255 255 / 0.9);\n    --lg-slab-blur: 14;\n    --lg-header-tint: rgb(255 255 255 / 0.9);\n  }\n\n  [data-lg-mode='tinted'] {\n    --lg-glass-alpha: 0.72;\n    --lg-glass-blur: 32px;\n    --lg-glass-sat: 1.4;\n    --lg-glass-contrast: 0.9;\n    --lg-lens-blur: 10;\n  }\n\n  /* Increased contrast keeps the glass and makes it predominantly black or\n     white, as the system's own glass does: dark glass dims what lies behind it\n     and light glass frosts denser, so text keeps 7:1 over any backdrop, and\n     muted text gets full contrast. Dark glass adds no white of its own, and\n     its Chromium lens dims as its frost does. Toasts and tooltips darken. A\n     ring in the text colour edges the glass (see Increased contrast below).\n     The dimming outranks the quality settings, which still lighten the frost. */\n  @media (prefers-contrast: more) {\n    [data-lg-theme='dark'],\n    [data-lg-theme='dark'][data-lg-mode],\n    [data-lg-theme='dark'][data-lg-mode][data-lg-quality] {\n      --lg-muted-foreground: var(--lg-foreground);\n      --lg-glass-alpha: 0.02;\n      --lg-glass-brightness: 0.22;\n      --lg-glass-contrast: 1;\n      --lg-sheen-alpha: 0;\n      --lg-lens-body-brightness: var(--lg-glass-brightness);\n      --lg-lens-rim-brightness: var(--lg-glass-brightness);\n      --lg-overlay-surface: rgb(28 28 30 / 0.88);\n      --lg-slab-tint: rgb(28 28 30 / 0.55);\n      --lg-slab-brightness: 0.5;\n      --lg-slab-rim-brightness: 0.65;\n      --lg-slab-blur: 14;\n      --lg-header-tint: rgb(28 28 30 / 0.55);\n      --lg-header-brightness: 0.5;\n      --lg-header-rim-brightness: 0.65;\n    }\n\n    [data-lg-theme='light'],\n    [data-lg-theme='light'][data-lg-mode] {\n      --lg-muted-foreground: var(--lg-foreground);\n      --lg-glass-alpha: 0.7;\n      --lg-slab-tint: rgb(255 255 255 / 0.9);\n      --lg-slab-blur: 14;\n      --lg-header-tint: rgb(255 255 255 / 0.9);\n    }\n  }\n\n  :root[data-lg-quality='low'] {\n    --lg-scene-highlight: 0.6;\n    --lg-glass-blur: 6px;\n    --lg-glass-sat: 1.05;\n    --lg-glass-contrast: 1;\n    --lg-glass-brightness: 1;\n    --lg-refraction-scale: 0;\n    --lg-refraction-blur: 0;\n    --lg-highlight-strength: 0;\n    --lg-lens-depth: 0;\n  }\n\n  /* Touch devices and reduced-motion settings land here. The colour split costs six\n     more filter passes a frame on every lens in view, so it is left out. */\n  :root[data-lg-quality='medium'] {\n    --lg-lens-dispersion: 0;\n    --lg-glass-blur: 12px;\n    --lg-glass-sat: 1.4;\n    --lg-glass-contrast: 0.96;\n    --lg-refraction-scale: 10;\n    --lg-refraction-blur: 8;\n  }\n\n  :root[data-lg-quality='high'] {\n    --lg-refraction-scale: 16;\n    --lg-refraction-blur: 10;\n  }\n\n  :root[data-lg-reduce-transparency='1'] {\n    --lg-glass-blur: 0px;\n    --lg-glass-alpha: 0.95;\n    --lg-noise-alpha: 0.01;\n    --lg-glass-contrast: 1;\n    --lg-glass-sat: 1;\n    --lg-glass-brightness: 1;\n    --lg-refraction-scale: 0;\n    --lg-lens-depth: 0;\n  }\n\n  [data-lg-theme='dark'][data-lg-reduce-transparency='1'] {\n    --lg-opaque-surface: #2c2c2e;\n    --lg-control-surface: #3a3a3c;\n    --lg-control-surface-hover: #48484a;\n    --lg-track: #48484a;\n  }\n\n  [data-lg-theme='light'][data-lg-reduce-transparency='1'] {\n    --lg-opaque-surface: #ffffff;\n    --lg-control-surface: #f0f0f2;\n    --lg-control-surface-hover: #e3e3e7;\n    --lg-track: #e6e6e9;\n  }\n\n  [data-lg-reduce-motion='1'] {\n    --lg-motion-duration: 0ms;\n  }\n\n  [data-lg-reduce-motion='1'] *,\n  [data-lg-reduce-motion='1'] *::before,\n  [data-lg-reduce-motion='1'] *::after {\n    animation-duration: 0ms !important;\n    transition-duration: 0ms !important;\n  }\n\n  /* A switch still shows its own change, in a short plain slide and a colour\n     fade: no spring, swell or glass. Everything else holds still. */\n  [data-lg-reduce-motion='1'] .lg-switch {\n    transition-duration: 160ms !important;\n  }\n\n  [data-lg-reduce-motion='1'] :is(.lg-switch-thumb, .lg-switch-lens-scene, .lg-switch-lens-track) {\n    transition-duration: 160ms !important;\n    transition-property: translate !important;\n    transition-timing-function: ease-out !important;\n  }\n\n  [data-lg-reduce-motion='1'] .lg-glass-surface::after {\n    transform: none !important;\n    transition: none !important;\n  }\n\n  [data-lg-reduce-motion='1'] .lg-button {\n    transform: none !important;\n    transition: none !important;\n  }\n\n  [data-lg-reduce-motion='1'] .lg-button__bloom {\n    display: none;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    :root {\n      --lg-motion-duration: 0ms;\n    }\n\n    .lg-glass-surface,\n    .lg-glass-surface::after,\n    .lg-button,\n    .lg-button__distortion,\n    .lg-button__shine,\n    .lg-input,\n    .lg-dialog-overlay,\n    .lg-dialog-content,\n    .lg-popover-content,\n    .lg-tabs-trigger,\n    .lg-accordion-trigger,\n    .lg-accordion-chevron,\n    .lg-refraction-background,\n    .lg-noise,\n    .lg-switch,\n    .lg-switch-thumb,\n    .lg-slider-thumb,\n    .lg-checkbox,\n    .lg-radio,\n    .lg-check-tick,\n    .lg-check-dash,\n    .lg-radio-dot,\n    .lg-select-chevron,\n    .lg-progress-fill,\n    .lg-spinner-ring,\n    .lg-toast,\n    .lg-toast-root,\n    .lg-toast-close,\n    .lg-toast-action,\n    .lg-tooltip-content,\n    .lg-accordion-content,\n    .lg-avatar-image,\n    .lg-alert,\n    .lg-alert-fold,\n    .lg-skeleton::after,\n    .lg-command-list,\n    .lg-command-search,\n    .lg-page,\n    .lg-alert-close,\n    .lg-segmented-item,\n    .lg-segmented-indicator,\n    .lg-tabs-indicator,\n    .lg-dock-item,\n    .lg-dock-label,\n    .lg-sheet-content,\n    .lg-menu-content,\n    .lg-select-content,\n    .lg-lens,\n    .lg-scene-drop {\n      animation-duration: 0ms !important;\n      transition-duration: 0ms !important;\n    }\n\n    .lg-switch {\n      transition-duration: 160ms !important;\n    }\n\n    .lg-switch-thumb,\n    .lg-switch-lens-scene,\n    .lg-switch-lens-track {\n      transition-duration: 160ms !important;\n      transition-property: translate !important;\n      transition-timing-function: ease-out !important;\n    }\n\n    .lg-glass-surface::after,\n    .lg-button__distortion,\n    .lg-refraction-background,\n    .lg-button {\n      transform: none !important;\n    }\n\n    .lg-button__bloom {\n      display: none;\n    }\n  }\n\n  @keyframes lg-grain {\n    0% {\n      transform: translate3d(0, 0, 0);\n    }\n    20% {\n      transform: translate3d(-2%, 2%, 0);\n    }\n    40% {\n      transform: translate3d(-2%, -2%, 0);\n    }\n    60% {\n      transform: translate3d(2%, -2%, 0);\n    }\n    80% {\n      transform: translate3d(2%, 2%, 0);\n    }\n    100% {\n      transform: translate3d(0, 0, 0);\n    }\n  }\n\n  /* ============================================\n     Glass edge\n     Two layers make every glass edge. The bevel is an inset shadow stack: the\n     light caught in the glass thickness, a bright bevel on the lit top-left, a\n     softer counter-bevel, a shaded inner reflex, a faint chromatic fringe and\n     a highlight cast by the fixed light. The specular ring is a masked\n     hairline lit from the top left, with a glint where that light sits. Custom\n     properties resolve on the declaring element, so surfaces and buttons feed\n     both their own tokens.\n     ============================================ */\n\n  .lg-glass-surface,\n  .lg-button {\n    --lg-rim-gain: calc(\n      var(--lg-rim-tangent-alpha, 1) * var(--lg-debug-tangent-scale, 1) *\n        var(--lg-rim-micro-mask, 1) * var(--lg-debug-micro-scale, 1) *\n        var(--lg-rim-corner-mask, 1) * var(--lg-debug-corner-scale, 1) *\n        var(--lg-debug-rim-alpha-scale, 1)\n    );\n    --lg-glass-rim:\n      inset 0 0 calc(12px * var(--lg-rim-scale)) calc(-4px * var(--lg-rim-scale))\n        rgb(255 255 255 / calc(var(--lg-glow-alpha) * var(--lg-rim-gain))),\n      inset calc(1.5px * var(--lg-rim-scale)) calc(2.5px * var(--lg-rim-scale))\n        calc(1px * var(--lg-rim-scale)) -2px rgb(255 255 255 / calc(var(--lg-rim-light) * 0.7)),\n      inset calc(-1.5px * var(--lg-rim-scale)) calc(-2px * var(--lg-rim-scale))\n        calc(1px * var(--lg-rim-scale)) -2px rgb(255 255 255 / calc(var(--lg-rim-light) * 0.55)),\n      inset -0.3px -1px 4px 0 rgb(0 0 0 / calc(var(--lg-rim-dark) * 0.1)),\n      inset -1.5px 2.5px 0 -2px rgb(0 0 0 / calc(var(--lg-rim-dark) * 0.14)),\n      inset 0 3px 4px -2px rgb(0 0 0 / calc(var(--lg-rim-dark) * 0.14)),\n      inset var(--lg-rim-ca-offset) 0 3px -1px rgb(80 160 255 / var(--lg-rim-ca-alpha)),\n      inset calc(-1 * var(--lg-rim-ca-offset)) 0 3px -1px rgb(255 90 150 / var(--lg-rim-ca-alpha)),\n      inset var(--lg-rim-offset-x) var(--lg-rim-offset-y) 3px rgb(255 255 255 / var(--lg-rim-alpha));\n    /* to bottom right keeps both highlights on the corners at any aspect\n       ratio; a fixed angle would smear them along a wide pill. */\n    --lg-specular-ring:\n      radial-gradient(\n        circle at calc(var(--lg-light-x) * 100%) calc(var(--lg-light-y) * 100%),\n        rgb(255 255 255 / calc(var(--lg-light-level) * 0.85)),\n        transparent 42%\n      ),\n      linear-gradient(\n        to bottom right,\n        rgb(255 255 255 / calc(var(--lg-specular-peak) * var(--lg-rim-gain))) 0%,\n        rgb(255 255 255 / calc(var(--lg-specular-floor) * var(--lg-rim-gain))) 32%,\n        rgb(255 255 255 / calc(var(--lg-specular-floor) * var(--lg-rim-gain))) 68%,\n        rgb(255 255 255 / calc(var(--lg-specular-counter) * var(--lg-rim-gain))) 100%\n      );\n  }\n\n  /* Glass Surface - Global Refraction */\n  .lg-glass-surface {\n    --lg-thickness: calc(1 + var(--lg-light-strength) * 0.35);\n    --lg-surface-blur-base: calc(var(--lg-glass-blur) * var(--lg-thickness));\n    --lg-surface-blur: var(--lg-surface-blur-base);\n    --lg-surface-alpha: var(--lg-glass-alpha);\n    --lg-surface-lift: 0;\n    --lg-light-strength: calc(var(--lg-light-level) * 0.45);\n    /* Bevel thickness (scaled by distance-aware factor) */\n    --lg-rim-scale: calc(var(--lg-rim-width-scale, 1) * var(--lg-debug-rim-thickness, 1));\n    --lg-rim-offset-max: 4.5px;\n    --lg-rim-offset-max-scaled: calc(var(--lg-rim-offset-max) * var(--lg-rim-scale));\n    --lg-rim-tangent-alpha: 1;\n    --lg-rim-micro-mask: 1;\n    --lg-rim-corner-mask: 1;\n    --lg-rim-ca-offset: calc(2px * var(--lg-debug-ca-offset-scale, 1));\n    --lg-rim-ca-alpha: calc(0.06 * var(--lg-debug-ca-scale, 1));\n    --lg-rim-offset-x: clamp(\n      calc(-1 * var(--lg-rim-offset-max-scaled)),\n      calc(\n        var(--lg-light-dir-x) * -1 * var(--lg-rim-offset-max-scaled) *\n          (0.6 + var(--lg-light-strength))\n      ),\n      var(--lg-rim-offset-max-scaled)\n    );\n    --lg-rim-offset-y: clamp(\n      calc(-1 * var(--lg-rim-offset-max-scaled)),\n      calc(\n        var(--lg-light-dir-y) * -1 * var(--lg-rim-offset-max-scaled) *\n          (0.6 + var(--lg-light-strength))\n      ),\n      var(--lg-rim-offset-max-scaled)\n    );\n    --lg-rim-light: calc(var(--lg-reflex-light) * var(--lg-rim-gain));\n    --lg-rim-dark: var(--lg-reflex-dark);\n    --lg-rim-alpha: calc(\n      (0.05 + var(--lg-light-strength) * 0.12 + var(--lg-fresnel, 0) * 0.35 + var(--lg-specular) *\n          0.32) *\n        var(--lg-rim-gain)\n    );\n\n    position: relative;\n    border-radius: var(--lg-panel-radius, var(--lg-radius-lg));\n    isolation: isolate;\n    background: transparent;\n    color: var(--lg-foreground);\n    transition:\n      box-shadow var(--lg-motion-duration) ease,\n      transform var(--lg-motion-duration) ease;\n    box-shadow: var(--lg-panel-elevation-shadow, var(--lg-shadow));\n    overflow: hidden;\n    contain: layout paint style;\n  }\n\n  .lg-glass-surface[data-lg-preset='subtle'] {\n    --lg-surface-blur: min(var(--lg-surface-blur-base), 8px);\n    --lg-surface-alpha: max(var(--lg-glass-alpha), 0.06);\n    --lg-rim-offset-max: 1.5px;\n  }\n\n  .lg-glass-surface[data-lg-preset='standard'] {\n    --lg-surface-blur: min(var(--lg-surface-blur-base), 14px);\n    --lg-surface-alpha: max(var(--lg-glass-alpha), 0.08);\n  }\n\n  .lg-glass-surface[data-lg-preset='prominent'] {\n    --lg-surface-blur: min(var(--lg-surface-blur-base), 18px);\n    --lg-surface-alpha: max(var(--lg-glass-alpha), 0.12);\n  }\n\n  .lg-glass-surface[data-lg-preset='control'] {\n    --lg-surface-blur: min(var(--lg-surface-blur-base), 10px);\n    --lg-surface-alpha: max(var(--lg-glass-alpha), 0.08);\n    --lg-rim-offset-max: 1.25px;\n  }\n\n  .lg-glass-surface[data-lg-preset='showcase'] {\n    --lg-surface-blur: min(var(--lg-surface-blur-base), 24px);\n  }\n\n  /* An interactive surface brightens a touch on hover, wherever the pointer\n     rests on it. */\n  .lg-glass-surface[data-lg-interactive='true']:hover {\n    --lg-surface-lift: 0.04;\n  }\n\n  /* The lift eases in and out. Other frost has no transition, so it changes\n     with a theme or preference switch at once. */\n  .lg-glass-surface[data-lg-interactive='true']::before {\n    transition: background-color var(--lg-motion-duration) ease;\n  }\n\n  /* Panel shapes and spacing. Component defaults live here rather than in\n     className utilities, so a consumer's p-4 or rounded-none always wins. */\n  .lg-panel--panel {\n    --lg-panel-radius: var(--lg-radius-lg);\n    padding: 1.5rem;\n  }\n\n  .lg-panel--card {\n    --lg-panel-radius: var(--lg-radius-md);\n    padding: 1.25rem;\n  }\n\n  .lg-panel--pill {\n    --lg-panel-radius: 9999px;\n    padding: 0.75rem 1.25rem;\n  }\n\n  .lg-panel--raised {\n    --lg-panel-elevation-shadow: var(--lg-shadow-raised);\n  }\n\n  .lg-refraction-filter {\n    position: absolute;\n    width: 0;\n    height: 0;\n    overflow: hidden;\n    pointer-events: none;\n  }\n\n  /* The generated scene sits under the frost layer, which then drops its\n     backdrop blur and lays only its tint, bevel and sheen over the scene.\n     WebKit draws a filtered layer from its overflow origin, so a viewport\n     scene hanging off the top or left edge would land shifted. Clipping\n     keeps both origins at the surface corner. */\n  .lg-glass-surface > .lg-refraction-background {\n    position: absolute;\n    inset: 0;\n    z-index: -1;\n    overflow: hidden;\n    border-radius: inherit;\n    pointer-events: none;\n    transform: translateZ(0);\n  }\n\n  .lg-refraction-background > .lg-refraction-scene {\n    position: absolute;\n    inset: 0;\n  }\n\n  .lg-refraction-scene > * {\n    min-width: 100%;\n    min-height: 100%;\n  }\n\n  /* Position the scene without a transform: WebKit shifts transformed input\n     when the SVG filter combines refracted colour channels. Chromium filters\n     composited layers, so a scroll-driven animation on translate still carries\n     the scene through the scroll, and left/top hold what scrolling leaves unchanged. */\n  .lg-glass-surface[data-lg-refraction-background-space='viewport']\n    > .lg-refraction-background\n    > .lg-refraction-scene {\n    inset: 0 auto auto 0;\n    top: var(--lg-refraction-scene-y, 0px);\n    left: var(--lg-refraction-scene-x, 0px);\n    width: var(--lg-refraction-scene-width, 100vw);\n    height: var(--lg-refraction-scene-height, 100dvh);\n  }\n\n  .lg-glass-surface[data-lg-refraction='soft'] {\n    --lg-surface-blur: calc(var(--lg-surface-blur-base) * 0.9);\n  }\n\n  .lg-glass-surface[data-lg-refraction='strong'] {\n    --lg-surface-blur: calc(var(--lg-surface-blur-base) * 1.12);\n  }\n\n  /* Frost, bevel and sheen. Keep blend modes, masks, filters and opacity off\n     every ancestor of this layer: in Chromium each of them isolates the\n     backdrop and silently turns the blur into a no-op. The layer carries its\n     own rounded shape: Chromium drops the host's rounded clip from a backdrop\n     blur that sits inside another rounded overflow clip, such as a card. The\n     tint is background-color alone, so a consumer's before:bg-* utility\n     recolors the glass and keeps the sheen. */\n  .lg-glass-surface::before {\n    content: '';\n    position: absolute;\n    inset: 0;\n    z-index: 0;\n    border-radius: inherit;\n    pointer-events: none;\n\n    background-color: rgb(255 255 255 / calc(var(--lg-surface-alpha) + var(--lg-surface-lift)));\n    background-image:\n      radial-gradient(\n        circle at calc(var(--lg-light-x) * 100%) calc(var(--lg-light-y) * 100%),\n        rgb(\n          255 255 255 /\n            calc(\n              (var(--lg-light-strength) * 0.1 + var(--lg-highlight-strength) * 0.12) *\n                var(--lg-debug-highlight-scale, 1)\n            )\n        ),\n        transparent 65%\n      ),\n      linear-gradient(180deg, rgb(255 255 255 / var(--lg-sheen-alpha)), transparent 45%);\n    box-shadow: var(--lg-glass-rim);\n\n    -webkit-backdrop-filter: blur(var(--lg-surface-blur)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(var(--lg-surface-blur)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n  }\n\n  .lg-glass-surface[data-lg-generated-refraction='true']::before {\n    -webkit-backdrop-filter: none;\n    backdrop-filter: none;\n  }\n\n  /* Specular ring. The mask lives on this sibling of the frost layer, never\n     on an ancestor, so it cannot flatten the blur. */\n  .lg-glass-surface::after {\n    content: '';\n    position: absolute;\n    inset: 0;\n    z-index: 1;\n    padding: var(--lg-rim-width);\n    border-radius: inherit;\n    pointer-events: none;\n    background: var(--lg-specular-ring);\n    -webkit-mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    -webkit-mask-composite: xor;\n    mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    mask-composite: exclude;\n  }\n\n  /* Chromium edge lens: one SVG filter frosts, bends and saturates the\n     backdrop. Its displacement map is measured from the padding box the\n     frost layer fills. */\n  .lg-glass-surface[data-lg-lens='active']::before {\n    backdrop-filter: var(--lg-lens-filter);\n  }\n\n  /* Glass inside glass samples a backdrop the outer layer already saturated,\n     so it adds no saturation of its own. */\n  .lg-glass-surface .lg-glass-surface {\n    --lg-glass-sat: 1;\n    --lg-lens-saturation: 1;\n  }\n\n  .lg-noise {\n    position: absolute;\n    inset: 0;\n    z-index: 1;\n    border-radius: inherit;\n    pointer-events: none;\n    background-image: var(--lg-noise-image);\n    background-size: 128px 128px;\n    opacity: var(--lg-noise-alpha);\n    /* Static by default to avoid per-frame repaints; opt-in with data-lg-animate-noise=\"1\" on <html>. */\n    animation: none;\n    will-change: auto;\n  }\n\n  [data-lg-animate-noise='1'] .lg-noise {\n    animation: lg-grain 4s steps(10) infinite;\n  }\n\n  [data-lg-reduce-motion='1'] .lg-noise {\n    animation: none !important;\n  }\n\n  /* Light mode glass surface - emphasize refraction, minimize dynamic lighting */\n  [data-lg-theme='light'] .lg-glass-surface {\n    --lg-rim-offset-max: 2px;\n    --lg-rim-ca-alpha: calc(0.05 * var(--lg-debug-ca-scale, 1));\n  }\n\n  /* ============================================\n     Floating layers\n     Dialogs, popovers, toasts and tooltips frost on the element itself, so the\n     material survives their scrolling and their opacity animations. Their\n     denser tint keeps text readable over any backdrop.\n     ============================================ */\n\n  .lg-dialog-content,\n  .lg-popover-content,\n  .lg-toast-root,\n  .lg-tooltip-content,\n  .lg-sheet-content,\n  .lg-menu-content,\n  .lg-select-content {\n    background: var(--lg-overlay-surface);\n    -webkit-backdrop-filter: blur(var(--lg-surface-blur)) saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(var(--lg-surface-blur)) saturate(var(--lg-glass-sat));\n  }\n\n  .lg-dialog-content::before,\n  .lg-popover-content::before,\n  .lg-toast-root::before,\n  .lg-tooltip-content::before,\n  .lg-sheet-content::before,\n  .lg-menu-content::before,\n  .lg-select-content::before {\n    background-color: transparent;\n    -webkit-backdrop-filter: none;\n    backdrop-filter: none;\n  }\n\n  /* Dialogs, popovers, sheets and menus are slabs of glass: the page shows\n     through, dimmed or lifted just enough to keep their text legible, and where\n     Chromium can run the lens their rim bends it. */\n  .lg-dialog-content,\n  .lg-popover-content,\n  .lg-sheet-content,\n  .lg-menu-content,\n  .lg-select-content {\n    --lg-lens-blur: var(--lg-slab-blur);\n    --lg-lens-body-brightness: var(--lg-slab-brightness);\n    --lg-lens-rim-brightness: var(--lg-slab-rim-brightness);\n\n    background: var(--lg-slab-tint);\n    -webkit-backdrop-filter: blur(calc(var(--lg-slab-blur) * 1px)) saturate(var(--lg-glass-sat))\n      brightness(var(--lg-slab-brightness));\n    backdrop-filter: blur(calc(var(--lg-slab-blur) * 1px)) saturate(var(--lg-glass-sat))\n      brightness(var(--lg-slab-brightness));\n  }\n\n  .lg-dialog-content[data-lg-lens='active'],\n  .lg-popover-content[data-lg-lens='active'],\n  .lg-sheet-content[data-lg-lens='active'],\n  .lg-menu-content[data-lg-lens='active'],\n  .lg-select-content[data-lg-lens='active'] {\n    -webkit-backdrop-filter: var(--lg-lens-filter);\n    backdrop-filter: var(--lg-lens-filter);\n  }\n\n  .lg-dialog-content[data-lg-lens='active']::before,\n  .lg-popover-content[data-lg-lens='active']::before,\n  .lg-sheet-content[data-lg-lens='active']::before,\n  .lg-menu-content[data-lg-lens='active']::before,\n  .lg-select-content[data-lg-lens='active']::before {\n    -webkit-backdrop-filter: none;\n    backdrop-filter: none;\n  }\n\n  .lg-dialog-content {\n    position: fixed;\n    top: 50%;\n    left: 50%;\n    z-index: 50;\n    width: calc(100vw - 2rem);\n    max-width: 520px;\n    max-height: calc(100dvh - 2rem);\n    translate: -50% -50%;\n    overflow-x: hidden;\n    overflow-y: auto;\n  }\n\n  @media (width >= 40rem) {\n    .lg-dialog-content {\n      padding: 2rem;\n    }\n  }\n\n  .lg-popover-content {\n    z-index: 50;\n    width: min(22rem, calc(100vw - 2rem));\n    max-height: calc(100dvh - 2rem);\n    overflow-x: hidden;\n    overflow-y: auto;\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n  }\n\n  .lg-dialog-content:focus,\n  .lg-popover-content:focus {\n    outline: none;\n  }\n\n  .lg-tooltip-content {\n    z-index: 50;\n    max-width: calc(100vw - 2rem);\n    padding: 0.375rem 0.75rem;\n    font-size: 0.75rem;\n    line-height: calc(1 / 0.75);\n    font-weight: 500;\n  }\n\n  .lg-dialog-overlay {\n    position: fixed;\n    inset: 0;\n    z-index: 40;\n    background: var(--lg-scrim);\n    -webkit-backdrop-filter: blur(1.5px);\n    backdrop-filter: blur(1.5px);\n  }\n\n  /* Toasts stack in the top-right corner; each glass card keeps its own frost. */\n  .lg-toast-viewport {\n    position: fixed;\n    top: 1rem;\n    right: 1rem;\n    z-index: 100;\n    display: flex;\n    flex-direction: column;\n    gap: 0.75rem;\n    width: min(22rem, calc(100vw - 2rem));\n    max-width: calc(100vw - 2rem);\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    pointer-events: none;\n  }\n\n  /* Radix turns selection off inline for the swipe, without the -webkit- prefix Safari needs. */\n  .lg-toast {\n    position: relative;\n    width: 100%;\n    list-style: none;\n    outline: none;\n    pointer-events: auto;\n    user-select: none;\n  }\n\n  .lg-toast-root {\n    display: grid;\n    grid-template-columns: minmax(0, 1fr) auto;\n    width: 100%;\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n  }\n\n  .lg-toast-root > * {\n    grid-column: 1;\n  }\n\n  /* The dismiss button stands beside the words, not over them. */\n  .lg-toast-root > .lg-toast-close {\n    grid-column: 2;\n    grid-row: 1 / span 2;\n    align-self: start;\n  }\n\n  .lg-toast-close {\n    display: inline-flex;\n    flex: none;\n    align-items: center;\n    justify-content: center;\n    width: 1.75rem;\n    height: 1.75rem;\n    margin-block: -0.25rem 0;\n    margin-inline: 0.75rem -0.375rem;\n    padding: 0;\n    border: 0;\n    border-radius: 9999px;\n    background: transparent;\n    color: var(--lg-muted-foreground);\n    cursor: pointer;\n    transition:\n      background-color 180ms ease,\n      color 180ms ease;\n  }\n\n  .lg-toast-close:hover {\n    background: rgb(255 255 255 / 0.14);\n    color: var(--lg-foreground);\n  }\n\n  [data-lg-theme='light'] .lg-toast-close:hover {\n    background: rgb(0 0 0 / 0.07);\n  }\n\n  .lg-toast-close:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 1px;\n    color: var(--lg-foreground);\n  }\n\n  .lg-toast-close > svg {\n    width: 1rem;\n    height: 1rem;\n  }\n\n  .lg-toast-action {\n    grid-column: 1;\n    justify-self: start;\n    height: 1.875rem;\n    margin-top: 0.625rem;\n    padding: 0 0.875rem;\n    border: 0;\n    border-radius: 9999px;\n    background: var(--lg-control-surface-hover);\n    box-shadow: inset 0 0 0 1px var(--lg-border);\n    color: var(--lg-foreground);\n    cursor: pointer;\n    font: inherit;\n    font-size: 0.8125rem;\n    font-weight: 600;\n    transition:\n      background-color 180ms ease,\n      scale 320ms var(--lg-spring);\n  }\n\n  .lg-toast-action:active {\n    scale: 0.94;\n  }\n\n  .lg-toast-action:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-toast-title {\n    color: var(--lg-foreground);\n    font-size: 1rem;\n    line-height: 1.5;\n    font-weight: 600;\n  }\n\n  .lg-toast-description {\n    margin-top: 0.25rem;\n    color: var(--lg-muted-foreground);\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n  }\n\n  /* ============================================\n     Reduced transparency\n     ============================================ */\n\n  /* Preference guards are !important because consumer utilities outrank every\n     other library default. Restyle them through --lg-opaque-surface and\n     --lg-control-surface, never by undoing the guard. */\n  [data-lg-reduce-transparency='1'] .lg-glass-surface::before {\n    -webkit-backdrop-filter: none !important;\n    backdrop-filter: none !important;\n    background: var(--lg-opaque-surface) !important;\n    box-shadow: none !important;\n    transform: none;\n  }\n\n  /* The ring keeps its mask and turns into a plain hairline border. */\n  [data-lg-reduce-transparency='1'] .lg-glass-surface::after,\n  [data-lg-reduce-transparency='1'] .lg-tabs-trigger[data-state='active']::after {\n    background: var(--lg-border);\n  }\n\n  /* The button draws the same hairline as an inset ring. A masked fill would\n     read to contrast checkers as a veil over the label. */\n  [data-lg-reduce-transparency='1'] .lg-button__rim {\n    background: none;\n    box-shadow: inset 0 0 0 var(--lg-rim-width) var(--lg-border);\n  }\n\n  [data-lg-reduce-transparency='1']\n    :is(\n      .lg-dialog-content,\n      .lg-popover-content,\n      .lg-toast-root,\n      .lg-tooltip-content,\n      .lg-sheet-content,\n      .lg-menu-content,\n      .lg-select-content,\n      .lg-dock-label\n    ) {\n    background: var(--lg-opaque-surface) !important;\n    -webkit-backdrop-filter: none !important;\n    backdrop-filter: none !important;\n  }\n\n  [data-lg-reduce-transparency='1']\n    :is(\n      .lg-button__distortion,\n      .lg-input,\n      .lg-accordion-trigger,\n      .lg-dialog-overlay,\n      .lg-badge,\n      .lg-alert\n    ) {\n    -webkit-backdrop-filter: none !important;\n    backdrop-filter: none !important;\n  }\n\n  /* Prominent glass has no fill of its own, so without the glass it is the\n     plain fill. */\n  [data-lg-reduce-transparency='1']\n    .lg-button--prominent\n    > .lg-button__layers\n    > .lg-button__distortion {\n    background: var(--lg-prominent-fill) !important;\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-button__layers > .lg-button__light {\n    display: none !important;\n  }\n\n  /* Knobs stay solid white instead of turning to clear glass. */\n  [data-lg-reduce-transparency='1'] :is(.lg-switch-thumb, .lg-slider-thumb) {\n    background: var(--lg-knob) !important;\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-switch-lens,\n  [data-lg-reduce-transparency='1'] .lg-switch-thumb::after {\n    display: none;\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-button__shine,\n  [data-lg-reduce-transparency='1'] .lg-button__tint {\n    opacity: 0;\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-button__bloom {\n    display: none;\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-refraction-background {\n    display: none;\n  }\n\n  /* ============================================\n     Increased contrast: glass dimmed or frosted denser (see the tokens above),\n     edged by a ring in the text colour. The ring is the specular layer, which\n     paints above the frost, so every engine shows it.\n     Forced colors: solid surfaces, real outlines.\n     Both: buttons and fields drop their blur, and controls get outlines in the\n     text colour.\n     ============================================ */\n\n  @media (prefers-contrast: more) {\n    /* A guard like the transparency ones, so their hairline cannot replace it. */\n    .lg-glass-surface::after {\n      background: var(--lg-foreground) !important;\n    }\n\n    /* Dark glass adds no white of its own: presets and hover keep the base veil. */\n    [data-lg-theme='dark'] .lg-glass-surface {\n      --lg-surface-alpha: var(--lg-glass-alpha) !important;\n      --lg-surface-lift: 0 !important;\n    }\n\n    /* Light segmented tracks frost like the other glass, not as a slate well. */\n    [data-lg-theme='light']:not([data-lg-reduce-transparency='1']) .lg-segmented::before {\n      background-color: rgb(255 255 255 / var(--lg-glass-alpha));\n    }\n\n    /* A generated refraction shows its backdrop undimmed, so the pane frosts\n       and dims the page behind it instead. */\n    .lg-refraction-background {\n      display: none;\n    }\n\n    .lg-glass-surface[data-lg-generated-refraction='true']::before {\n      -webkit-backdrop-filter: blur(var(--lg-surface-blur)) brightness(var(--lg-glass-brightness))\n        contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n      backdrop-filter: blur(var(--lg-surface-blur)) brightness(var(--lg-glass-brightness))\n        contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n    }\n\n    /* Badges and alerts keep the solid fill their tone colours are set on. */\n    .lg-badge,\n    .lg-alert {\n      -webkit-backdrop-filter: none !important;\n      backdrop-filter: none !important;\n      background: var(--lg-opaque-surface) !important;\n    }\n\n    /* Overlays scroll their content, and their ring layer with it, so they\n       carry the ring as an outline that stays on their edge, focused or not. */\n    :is(\n      .lg-dialog-content,\n      .lg-popover-content,\n      .lg-sheet-content,\n      .lg-menu-content,\n      .lg-select-content\n    ) {\n      outline: 1px solid var(--lg-foreground) !important;\n      outline-offset: -1px !important;\n    }\n\n    :is(\n      .lg-dialog-content,\n      .lg-popover-content,\n      .lg-sheet-content,\n      .lg-menu-content,\n      .lg-select-content\n    )::after {\n      display: none;\n    }\n  }\n\n  @media (forced-colors: active) {\n    .lg-glass-surface {\n      outline: 1px solid CanvasText !important;\n      outline-offset: -1px !important;\n    }\n\n    .lg-glass-surface::before,\n    .lg-dialog-content,\n    .lg-popover-content,\n    .lg-toast-root,\n    .lg-tooltip-content,\n    .lg-sheet-content,\n    .lg-menu-content,\n    .lg-select-content,\n    .lg-dock-label,\n    .lg-badge,\n    .lg-alert {\n      -webkit-backdrop-filter: none !important;\n      backdrop-filter: none !important;\n      background: var(--lg-opaque-surface) !important;\n    }\n\n    .lg-glass-surface::before {\n      box-shadow: none !important;\n    }\n\n    /* The 1px outline replaces the specular ring. */\n    .lg-glass-surface::after {\n      display: none;\n    }\n\n    .lg-refraction-background {\n      display: none;\n    }\n  }\n\n  @media (prefers-contrast: more), (forced-colors: active) {\n    .lg-badge,\n    .lg-alert,\n    .lg-kbd,\n    .lg-progress-track,\n    .lg-dock-label,\n    .lg-switch,\n    .lg-slider-track,\n    .lg-segmented-indicator,\n    .lg-tabs-indicator {\n      outline: 1px solid CanvasText;\n      outline-offset: -1px;\n    }\n\n    .lg-segmented-indicator::after,\n    .lg-tabs-indicator::after,\n    .lg-lens::after,\n    .lg-button__bloom,\n    .lg-switch-lens,\n    .lg-switch-thumb::after {\n      display: none;\n    }\n\n    /* The switch's knob stays solid instead of turning to clear glass; forced\n       colours paint it with system colours below. */\n    .lg-switch-thumb {\n      background: var(--lg-knob) !important;\n    }\n\n    .lg-lens {\n      outline: 2px solid CanvasText;\n    }\n\n    .lg-tabs-trigger[data-state='active']::after {\n      display: none;\n    }\n\n    .lg-button:not(.lg-button--ghost, .lg-button--prominent) {\n      --lg-button-fill: var(--lg-opaque-surface) !important;\n      --lg-button-fill-hover: var(--lg-opaque-surface) !important;\n    }\n\n    .lg-button--prominent > .lg-button__layers > .lg-button__distortion {\n      background: var(--lg-prominent-fill) !important;\n    }\n\n    .lg-button__layers > .lg-button__light {\n      display: none !important;\n    }\n\n    .lg-button__distortion,\n    .lg-input,\n    .lg-accordion-trigger,\n    .lg-dialog-overlay {\n      -webkit-backdrop-filter: none !important;\n      backdrop-filter: none !important;\n    }\n\n    .lg-button .lg-button__rim {\n      padding: 0;\n      border: 1px solid CanvasText;\n      background: none;\n      -webkit-mask: none;\n      mask: none;\n    }\n\n    .lg-input,\n    .lg-checkbox,\n    .lg-radio,\n    .lg-accordion-trigger {\n      border-color: CanvasText !important;\n    }\n\n    /* Focusable surfaces such as tab panels need a ring distinct from the\n       1px surface outline. */\n    .lg-button:focus-visible,\n    .lg-input:focus,\n    .lg-checkbox:focus-visible,\n    .lg-radio:focus-visible,\n    .lg-glass-surface:focus-visible {\n      outline: 2px solid CanvasText !important;\n      outline-offset: 2px !important;\n    }\n  }\n\n  /* Forced colors repaint every author color, so the controls whose state is\n     their color opt out and use system colors directly. */\n  @media (forced-colors: active) {\n    .lg-switch,\n    .lg-switch-thumb,\n    .lg-slider-range,\n    .lg-slider-thumb,\n    .lg-segmented-indicator,\n    .lg-tabs-indicator,\n    .lg-checkbox,\n    .lg-radio,\n    .lg-progress-fill,\n    .lg-menu-item,\n    .lg-command-item {\n      forced-color-adjust: none;\n    }\n\n    .lg-progress-fill {\n      background: Highlight;\n      box-shadow: none;\n    }\n\n    .lg-page[data-current] {\n      background: Highlight;\n      color: HighlightText;\n      forced-color-adjust: none;\n    }\n\n    .lg-switch {\n      background: Canvas;\n    }\n\n    .lg-checkbox,\n    .lg-radio {\n      border-color: CanvasText;\n      background: Canvas;\n      box-shadow: none;\n      color: HighlightText;\n    }\n\n    .lg-checkbox[data-state='checked'],\n    .lg-checkbox[data-state='indeterminate'],\n    .lg-radio[data-state='checked'] {\n      border-color: Highlight;\n      background: Highlight;\n    }\n\n    .lg-radio-dot {\n      background: HighlightText;\n      box-shadow: none;\n    }\n\n    /* Forced colours strip the fill that gives an avatar its shape. */\n    .lg-avatar-face {\n      border: 1px solid CanvasText;\n      box-sizing: border-box;\n    }\n\n    .lg-switch[data-state='checked'],\n    .lg-slider-range,\n    .lg-menu-item[data-highlighted],\n    .lg-command-item[data-selected='true'] {\n      background: Highlight;\n      color: HighlightText;\n    }\n\n    /* A guard, like the transparency ones: the bubbles' glass rules are more\n       specific. */\n    .lg-segmented-indicator,\n    .lg-tabs-indicator {\n      background: Highlight !important;\n    }\n\n    /* The list prefix outranks the controls' own later state colors. */\n    .lg-tabs-list .lg-tabs-trigger[data-state='active'] {\n      color: HighlightText;\n      forced-color-adjust: none;\n    }\n\n    .lg-tabs-list:not([data-lg-indicator='ready']) .lg-tabs-trigger[data-state='active'] {\n      background: Highlight;\n    }\n\n    .lg-switch-thumb,\n    .lg-slider-thumb {\n      background: CanvasText !important;\n      box-shadow: none;\n    }\n\n    .lg-switch[data-state='checked'] .lg-switch-thumb {\n      background: HighlightText !important;\n    }\n\n    .lg-segmented .lg-segmented-item[data-state='checked'] {\n      color: HighlightText;\n      forced-color-adjust: none;\n    }\n\n    .lg-menu-item,\n    .lg-command-item,\n    .lg-toast-close {\n      color: CanvasText;\n    }\n  }\n\n  /* --- Buttons --- */\n\n  .lg-button {\n    --lg-radius: 999px;\n    --lg-surface-blur: min(var(--lg-glass-blur), 16px);\n    --lg-ink: var(--lg-foreground);\n    --lg-button-fill: rgb(255 255 255 / 0.07);\n    --lg-button-fill-hover: rgb(255 255 255 / 0.11);\n    --lg-button-sheen: 0.1;\n    --lg-button-glow: 0.14;\n    --lg-button-glow-pressed: 0.26;\n    --lg-button-shine: var(--lg-button-glow);\n    /* The fill highlight sits between the light and the centre. */\n    --lg-fill-x: calc(0.5 + (var(--lg-light-x) - 0.5) * 0.35);\n    --lg-fill-y: calc(0.5 + (var(--lg-light-y) - 0.5) * 0.35);\n    --lg-rim-scale: var(--lg-debug-rim-thickness, 1);\n    --lg-rim-offset-max: 1.1px;\n    --lg-rim-tangent-alpha: 1;\n    --lg-rim-micro-mask: 1;\n    --lg-rim-corner-mask: 1;\n    --lg-rim-offset-x: clamp(\n      calc(-1 * var(--lg-rim-offset-max)),\n      calc(var(--lg-light-dir-x) * -1 * var(--lg-rim-offset-max) * (0.7 + var(--lg-light-level))),\n      var(--lg-rim-offset-max)\n    );\n    --lg-rim-offset-y: clamp(\n      calc(-1 * var(--lg-rim-offset-max)),\n      calc(var(--lg-light-dir-y) * -1 * var(--lg-rim-offset-max) * (0.7 + var(--lg-light-level))),\n      var(--lg-rim-offset-max)\n    );\n    --lg-rim-light: calc(var(--lg-button-reflex-light) * var(--lg-rim-gain));\n    --lg-rim-dark: var(--lg-reflex-dark);\n    --lg-rim-alpha: calc((0.08 + var(--lg-light-level) * 0.12) * var(--lg-rim-gain));\n    --lg-rim-ca-offset: calc(1.5px * var(--lg-debug-ca-offset-scale, 1));\n    --lg-rim-ca-alpha: calc(0.06 * var(--lg-debug-ca-scale, 1));\n\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: 0.5rem;\n    padding: 0.75rem 1.5rem;\n\n    border-radius: var(--lg-radius);\n    border: none;\n    background: transparent;\n    color: var(--lg-ink);\n    text-decoration: none;\n\n    font-weight: 500;\n    letter-spacing: -0.01em;\n    isolation: isolate;\n    cursor: pointer;\n    contain: layout paint style;\n\n    /* Each button is a slab of glass resting just above the page, lit from\n       above. A pointer lowers it halfway and a press sets it down, so its\n       shadow draws in under it; the glass itself stays put. */\n    --lg-button-rise: 1;\n    --lg-button-rise-time: calc(var(--lg-motion-duration) * 1.6);\n    --lg-button-shade: 0 0 0;\n    --lg-button-shade-near: 0.32;\n    --lg-button-shade-far: 0.36;\n    transition: --lg-button-rise var(--lg-button-rise-time) var(--lg-ease-out);\n  }\n\n  @media (hover: hover) {\n    .lg-button:not(:disabled, [aria-disabled='true']):hover {\n      --lg-button-rise: 0.5;\n    }\n  }\n\n  .lg-button[data-pressed='true']:not(:disabled, [aria-disabled='true']) {\n    --lg-button-rise: 0.2;\n    --lg-button-rise-time: calc(var(--lg-motion-duration) * 0.6);\n  }\n\n  .lg-button[data-lg-preset='control'] {\n    --lg-surface-blur: min(var(--lg-glass-blur), 10px);\n  }\n\n  .lg-button[data-lg-preset='showcase'] {\n    --lg-surface-blur: min(var(--lg-glass-blur), 16px);\n  }\n\n  .lg-button:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  /* Disabled buttons dim their content, never the host: opacity on an ancestor\n     of the frost layer isolates its backdrop and flattens the glass. */\n  .lg-button:disabled,\n  .lg-button[aria-disabled='true'] {\n    cursor: not-allowed;\n    transform: none;\n  }\n\n  .lg-button:disabled :is(.lg-button__label, .lg-button__tint, .lg-button__rim),\n  .lg-button[aria-disabled='true'] :is(.lg-button__label, .lg-button__tint, .lg-button__rim) {\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-button__layers {\n    position: absolute;\n    inset: 0;\n    border-radius: inherit;\n    pointer-events: none;\n    z-index: 0;\n    overflow: hidden;\n  }\n\n  /* Icons and text sit in one row, spaced by the button's own gap. */\n  .lg-button__label {\n    position: relative;\n    z-index: 1;\n    display: inline-flex;\n    align-items: center;\n    gap: inherit;\n  }\n\n  .lg-button__layers > span {\n    position: absolute;\n    inset: 0;\n    border-radius: inherit;\n  }\n\n  /* 0. The light the glass lets through, under it. Tinted glass shows it. */\n  .lg-button__layers > .lg-button__light {\n    display: none;\n  }\n\n  /* 1. Frost: the only backdrop layer */\n  .lg-button__distortion {\n    background: var(--lg-button-fill);\n    -webkit-backdrop-filter: blur(var(--lg-surface-blur)) saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(var(--lg-surface-blur)) saturate(var(--lg-glass-sat));\n    transition: background-color var(--lg-motion-duration) ease;\n  }\n\n  .lg-button[data-lg-lens='active'] > .lg-button__layers > .lg-button__distortion {\n    backdrop-filter: var(--lg-lens-filter);\n  }\n\n  .lg-button:not(:disabled, [aria-disabled='true']):hover .lg-button__distortion {\n    background: var(--lg-button-fill-hover);\n  }\n\n  /* 2. Sheen and bevel: top-lit gradient, a gentle fixed highlight and the\n     shared glass bevel */\n  .lg-button__tint {\n    background:\n      radial-gradient(\n        circle at calc(var(--lg-fill-x) * 100%) calc(var(--lg-fill-y) * 100%),\n        rgb(255 255 255 / calc(var(--lg-light-level) * 0.08)),\n        transparent 70%\n      ),\n      linear-gradient(180deg, rgb(255 255 255 / var(--lg-button-sheen)), transparent 60%);\n    box-shadow: var(--lg-glass-rim);\n  }\n\n  /* 3. Shine: hover glow that lights up from within while pressed */\n  .lg-button__shine {\n    background: radial-gradient(\n      circle at calc(var(--lg-fill-x) * 100%) calc(var(--lg-fill-y) * 100%),\n      rgb(255 255 255 / var(--lg-button-shine)),\n      transparent 60%\n    );\n    opacity: 0;\n    transition: opacity var(--lg-motion-duration) ease;\n  }\n\n  .lg-button:not(:disabled, [aria-disabled='true']):hover .lg-button__shine,\n  .lg-button[data-pressed='true']:not(:disabled, [aria-disabled='true']) .lg-button__shine {\n    opacity: 1;\n  }\n\n  .lg-button[data-pressed='true'] {\n    --lg-button-shine: var(--lg-button-glow-pressed);\n  }\n\n  /* 4. Bloom: a press lights the glass from the fingertip outward. Script\n     places it at the press point and plays it once per press. It stays\n     faint: the slab setting down carries the press, and the light only\n     marks where it landed. */\n  .lg-button__layers > .lg-button__bloom {\n    inset: auto;\n    top: var(--lg-bloom-y, 50%);\n    left: var(--lg-bloom-x, 50%);\n    width: var(--lg-bloom-size, 0px);\n    height: var(--lg-bloom-size, 0px);\n    border-radius: 50%;\n    background: radial-gradient(\n      closest-side,\n      rgb(255 255 255 / 0.18),\n      rgb(255 255 255 / 0.05) 60%,\n      transparent\n    );\n    opacity: 0;\n    translate: -50% -50%;\n  }\n\n  /* 5. Rim: the shared specular ring */\n  .lg-button__rim {\n    padding: var(--lg-rim-width);\n    background: var(--lg-specular-ring);\n    -webkit-mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    -webkit-mask-composite: xor;\n    mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    mask-composite: exclude;\n  }\n\n  /* Button Variants. A slab's shadow falls straight down from it and draws in\n     as it lowers: a near, sharp contact shadow and a soft one further out. */\n  .lg-button--primary,\n  .lg-button--soft {\n    box-shadow:\n      0 calc(var(--lg-button-rise) * 1.8px) calc(var(--lg-button-rise) * 2px)\n        calc(var(--lg-button-rise) * -1px) rgb(var(--lg-button-shade) / var(--lg-button-shade-near)),\n      0 calc(var(--lg-button-rise) * 6.7px) calc(var(--lg-button-rise) * 10px)\n        calc(var(--lg-button-rise) * -5px) rgb(var(--lg-button-shade) / var(--lg-button-shade-far));\n  }\n\n  .lg-button--soft {\n    --lg-button-fill: rgb(255 255 255 / 0.04);\n    --lg-button-fill-hover: rgb(255 255 255 / 0.08);\n    --lg-button-shade-near: 0.2;\n    --lg-button-shade-far: 0.18;\n  }\n\n  .lg-button--ghost {\n    --lg-button-fill: transparent;\n    --lg-button-fill-hover: rgb(255 255 255 / 0.06);\n    box-shadow: none;\n  }\n\n  .lg-button--ghost .lg-button__distortion {\n    -webkit-backdrop-filter: none;\n    backdrop-filter: none;\n  }\n\n  .lg-button--ghost :is(.lg-button__rim, .lg-button__tint) {\n    display: none;\n  }\n\n  /* Sizes */\n  .lg-button--sm {\n    font-size: 0.8rem;\n    padding: 0.4rem 0.8rem;\n  }\n  .lg-button--md {\n    font-size: 0.95rem;\n    padding: 0.7rem 1.4rem;\n  }\n  .lg-button--lg {\n    font-size: 1.1rem;\n    padding: 1rem 2rem;\n  }\n\n  /* ============================================\n     LIGHT THEME - Button Overrides\n     ============================================ */\n\n  [data-lg-theme='light'] .lg-button {\n    --lg-button-fill: rgb(255 255 255 / 0.45);\n    --lg-button-fill-hover: rgb(255 255 255 / 0.6);\n    --lg-button-sheen: 0.35;\n    --lg-button-glow: 0.45;\n    --lg-button-glow-pressed: 0.75;\n    --lg-button-shade: 52 62 84;\n    --lg-button-shade-near: 0.18;\n    --lg-button-shade-far: 0.2;\n  }\n\n  [data-lg-theme='light'] .lg-button--soft {\n    --lg-button-shade-near: 0.12;\n    --lg-button-shade-far: 0.12;\n  }\n\n  [data-lg-theme='light'] .lg-button--soft {\n    --lg-button-fill: rgb(255 255 255 / 0.3);\n    --lg-button-fill-hover: rgb(255 255 255 / 0.45);\n  }\n\n  [data-lg-theme='light'] .lg-button--ghost {\n    --lg-button-fill: transparent;\n    --lg-button-fill-hover: rgb(0 0 0 / 0.04);\n  }\n\n  /* Prominent: a slab of blue glass. It passes on the light of whatever\n     lies behind it, tinted blue, so the page, the light it lets through and\n     the shadow under it all show in it. The tint caps how bright that light\n     can get, which keeps white text legible over any backdrop. Two classes\n     outrank the light-theme overrides above. */\n  .lg-button.lg-button--prominent {\n    --lg-ink: var(--lg-accent-foreground);\n    --lg-button-fill: var(--lg-prominent-fill);\n    --lg-button-fill-hover: var(--lg-prominent-fill);\n    --lg-button-sheen: 0;\n    --lg-button-glow: 0;\n    --lg-button-glow-pressed: 0;\n    /* The rays of the light cross the glass along x and y. */\n    --lg-button-ray-x: calc(sin(var(--lg-button-light-angle)) * -1);\n    --lg-button-ray-y: cos(var(--lg-button-light-angle));\n    /* The light eases over, gently at both ends, and returns briskly. */\n    --lg-button-rise-time: calc(var(--lg-motion-duration) * 1.5);\n    --lg-button-light-time: calc(var(--lg-motion-duration) * 1.75);\n    --lg-button-light-ease: cubic-bezier(0.25, 0.8, 0.3, 1);\n    /* The shadow stays under the slab and draws in as it lowers; only the\n       light along the rim follows the light. */\n    box-shadow:\n      0 calc(var(--lg-button-rise) * 1.8px) calc(var(--lg-button-rise) * 2px)\n        calc(var(--lg-button-rise) * -1px) rgb(var(--lg-prominent-shade) / 0.4),\n      0 calc(var(--lg-button-rise) * 6.7px) calc(var(--lg-button-rise) * 10px)\n        calc(var(--lg-button-rise) * -5px) rgb(var(--lg-prominent-shade) / 0.4);\n    transition:\n      --lg-button-rise var(--lg-button-rise-time) cubic-bezier(0.25, 0.8, 0.3, 1),\n      --lg-button-light-angle var(--lg-button-light-time) var(--lg-button-light-ease);\n  }\n\n  /* A pointer lowers the slab like any button and swings its light 50deg over\n     to the top left. */\n  @media (hover: hover) {\n    .lg-button.lg-button--prominent:not(:disabled, [aria-disabled='true']):hover {\n      --lg-button-light-angle: -50deg;\n      --lg-button-light-time: calc(var(--lg-motion-duration) * 2.75);\n      --lg-button-light-ease: cubic-bezier(0.45, 0, 0.55, 1);\n    }\n  }\n\n  /* Under the glass lies the light it lets through: a dim pool, a dark line\n     where the rounded edge turns the light aside and a bright one just inside\n     it where the edge gathers it. It sits a little below the glass, further\n     the higher the glass rests, so a press all but hides it. The pool is dense\n     enough that, should an ancestor cut the glass off from the page (opacity,\n     a filter or a mask), it alone keeps the white label at 4.5:1 over white. */\n  .lg-button--prominent > .lg-button__layers > .lg-button__light {\n    --lg-rise-cubed: calc(var(--lg-button-rise) * var(--lg-button-rise) * var(--lg-button-rise));\n\n    display: block;\n    /* Light coming in at a slant crosses the glass at a slant, so the pool\n       moves away from it. */\n    translate: calc(var(--lg-button-ray-x) * 1.2px) calc(var(--lg-rise-cubed) * 2px);\n    background: rgb(var(--lg-prominent-shade) / 0.6);\n    box-shadow:\n      inset 0 0 0 calc(1px + var(--lg-rise-cubed) * 1.6px) rgb(var(--lg-prominent-shade) / 0.5),\n      inset 0 0 0 calc(2px + var(--lg-rise-cubed) * 2.2px) rgb(var(--lg-prominent-glow) / 0.75);\n    filter: blur(calc(0.2px + var(--lg-button-rise) * 0.4px));\n  }\n\n  /* The face is that light, tinted. Its faint sheen turns with the light. */\n  .lg-button--prominent > .lg-button__layers > .lg-button__distortion {\n    -webkit-backdrop-filter: blur(0.6px) var(--lg-prominent-tint);\n    backdrop-filter: blur(0.6px) var(--lg-prominent-tint);\n    /* The solid fill replaces the tint and light together. Fading only the\n       fill exposes the untinted backdrop while those layers switch. */\n    transition: none;\n  }\n\n  .lg-button--prominent > .lg-button__layers > .lg-button__distortion,\n  .lg-button--prominent:not(:disabled, [aria-disabled='true']):hover\n    > .lg-button__layers\n    > .lg-button__distortion {\n    background: linear-gradient(\n      calc(180deg + var(--lg-button-light-angle)),\n      rgb(255 255 255 / 0.07),\n      transparent 45%\n    );\n  }\n\n  /* In Chromium the edge lens bends the light at the rim, then it is tinted. */\n  .lg-button--prominent[data-lg-lens='active'] > .lg-button__layers > .lg-button__distortion {\n    backdrop-filter: var(--lg-lens-filter) var(--lg-prominent-tint);\n  }\n\n  /* The rim: a dark line at the very edge, where it turns the light aside, a\n     soft sheen under the top, where it faces the light, and a glow along the\n     bottom, where the light the slab carried across leaves it. Set down, the\n     glass gathers more of that light and glows all round. All of it turns\n     with the light: the sheen moves round to the edge facing it and the glow\n     round to the far edge. */\n  .lg-button--prominent > .lg-button__layers > .lg-button__tint {\n    --lg-button-sunk: calc(1 - var(--lg-button-rise));\n\n    background: none;\n    box-shadow:\n      inset 0 0 0 1px rgb(var(--lg-prominent-shade) / 0.32),\n      inset calc(var(--lg-button-ray-x) * -5px) calc(var(--lg-button-ray-y) * -5px) 7px -4px\n        rgb(var(--lg-prominent-glow) / calc(0.5 + var(--lg-button-sunk) * 0.35)),\n      inset 0 0 calc(4px + var(--lg-button-sunk) * 10px) 0\n        rgb(var(--lg-prominent-glow) / calc(var(--lg-button-sunk) * 0.32)),\n      inset calc(var(--lg-button-ray-x) * 7px) calc(var(--lg-button-ray-y) * 7px) 8px -7px\n        rgb(255 255 255 / 0.22),\n      inset calc(var(--lg-button-ray-x) * 3px) calc(var(--lg-button-ray-y) * 3px) 4px -2px\n        rgb(0 0 0 / 0.08);\n  }\n\n  /* Just inside the dark line, a bright one where the edge gathers the\n     light: strongest along the top, faint at the sides, blue below. It turns\n     with the light, so the bright line moves round to the end facing it and\n     the blue one to the far end. */\n  .lg-button--prominent > .lg-button__layers > .lg-button__rim {\n    inset: 1px;\n    padding: 1px;\n    background: linear-gradient(\n      calc(180deg + var(--lg-button-light-angle)),\n      rgb(255 255 255 / 0.78),\n      rgb(255 255 255 / 0.12) 36% 64%,\n      rgb(var(--lg-prominent-glow) / 0.62)\n    );\n  }\n\n  /* Over a pale page the bright line brightens as the light swings over, so\n     it reads as clearly as it does against a dark one. */\n  [data-lg-theme='light'] .lg-button--prominent > .lg-button__layers > .lg-button__rim {\n    background: linear-gradient(\n      calc(180deg + var(--lg-button-light-angle)),\n      rgb(255 255 255 / calc(0.78 + max(0, var(--lg-button-ray-x)) * 0.2)),\n      rgb(255 255 255 / 0.12) 36% 64%,\n      rgb(var(--lg-prominent-glow) / 0.62)\n    );\n  }\n\n  /* Without a backdrop filter the face is the plain fill. */\n  @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {\n    .lg-button--prominent > .lg-button__layers > .lg-button__distortion {\n      background: var(--lg-prominent-fill);\n    }\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-button:not(.lg-button--ghost, .lg-button--prominent) {\n    --lg-button-fill: var(--lg-control-surface) !important;\n    --lg-button-fill-hover: var(--lg-control-surface-hover) !important;\n  }\n\n  /* Input Fields: a recessed glass well */\n  .lg-input {\n    width: 100%;\n    padding: 0.875rem 1.25rem;\n    border-radius: 16px;\n    border: 1px solid var(--lg-border);\n    background: var(--lg-control-surface);\n    color: var(--lg-foreground);\n    font-size: 1rem;\n    outline: none;\n    transition:\n      background var(--lg-motion-duration) ease,\n      border-color var(--lg-motion-duration) ease,\n      box-shadow var(--lg-motion-duration) ease;\n    -webkit-backdrop-filter: blur(min(var(--lg-glass-blur), 16px))\n      brightness(var(--lg-glass-brightness)) contrast(var(--lg-glass-contrast))\n      saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(min(var(--lg-glass-blur), 16px)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n    box-shadow: var(--lg-input-shadow);\n  }\n\n  /* A focused field fills in and its caret shows where typing goes. It draws no\n     ring: a text field counts as keyboard-focused even when clicked, so a ring\n     would follow every tap. The select trigger has no caret, so keyboard focus\n     still outlines it. */\n  .lg-input:focus {\n    background: var(--lg-control-surface-hover);\n  }\n\n  .lg-select-trigger:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-input::placeholder {\n    color: var(--lg-muted-foreground);\n    opacity: 0.72;\n  }\n\n  .lg-input[aria-invalid='true'] {\n    border-color: var(--lg-invalid);\n    box-shadow: 0 0 0 1px var(--lg-invalid);\n  }\n\n  .lg-input:disabled {\n    cursor: not-allowed;\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  /* ============================================\n     Checkbox and radio: a small glass well that fills with the accent, with a\n     mark that draws itself in and back out, so clearing a choice moves as\n     smoothly as making it. Every transition sits on the base rule, never on\n     the checked one, so both directions animate.\n     ============================================ */\n\n  .lg-check-field {\n    display: flex;\n    align-items: flex-start;\n    gap: 0.75rem;\n    color: var(--lg-foreground);\n    font-size: 0.9375rem;\n    line-height: 1.4;\n  }\n\n  .lg-check-text {\n    display: flex;\n    min-width: 0;\n    flex-direction: column;\n    padding-top: 0.0625rem;\n  }\n\n  /* Clicking the label toggles the box, so a quick second click must not select its text. The\n     description below it stays selectable. */\n  .lg-check-text > label {\n    cursor: pointer;\n    user-select: none;\n  }\n\n  .lg-check-text > span {\n    color: var(--lg-muted-foreground);\n    font-size: 0.8125rem;\n    line-height: 1.35;\n  }\n\n  .lg-check-field[data-size='sm'] {\n    gap: 0.625rem;\n    font-size: 0.875rem;\n  }\n\n  .lg-check-field[data-disabled] {\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-check-field[data-disabled] .lg-check-text > label {\n    cursor: not-allowed;\n  }\n\n  .lg-radio-group {\n    display: grid;\n    gap: 0.75rem;\n  }\n\n  .lg-checkbox,\n  .lg-radio {\n    --lg-check-size: 1.25rem;\n    /* The edge of an empty box is what says it can be ticked, so it holds 3:1 over the\n       glass it sits on, which the hairline border of a text field does not. */\n    --lg-check-edge: rgb(244 245 249 / 0.78);\n\n    position: relative;\n    display: inline-grid;\n    flex: none;\n    place-items: center;\n    box-sizing: border-box;\n    width: var(--lg-check-size);\n    height: var(--lg-check-size);\n    margin: 0;\n    padding: 0;\n    border: 1.5px solid var(--lg-check-edge);\n    background: var(--lg-control-surface);\n    box-shadow: var(--lg-input-shadow);\n    color: #fff;\n    cursor: pointer;\n    -webkit-tap-highlight-color: transparent;\n    transition:\n      background-color 220ms ease,\n      border-color 220ms ease,\n      box-shadow 220ms ease,\n      scale 320ms var(--lg-spring);\n  }\n\n  [data-lg-theme='light'] .lg-checkbox,\n  [data-lg-theme='light'] .lg-radio {\n    --lg-check-edge: rgb(27 29 36 / 0.6);\n  }\n\n  .lg-checkbox {\n    border-radius: 0.4375rem;\n  }\n\n  .lg-radio {\n    border-radius: 9999px;\n  }\n\n  .lg-checkbox[data-size='sm'],\n  .lg-check-field[data-size='sm'] .lg-checkbox {\n    --lg-check-size: 1rem;\n\n    border-radius: 0.3125rem;\n  }\n\n  .lg-checkbox[data-state='unchecked']:hover:not([data-disabled]),\n  .lg-radio[data-state='unchecked']:hover:not([data-disabled]) {\n    background: var(--lg-control-surface-hover);\n  }\n\n  .lg-checkbox:active:not([data-disabled]),\n  .lg-radio:active:not([data-disabled]) {\n    scale: 0.9;\n  }\n\n  .lg-checkbox:focus-visible,\n  .lg-radio:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-checkbox[aria-invalid='true'],\n  .lg-radio[aria-invalid='true'] {\n    border-color: var(--lg-invalid);\n  }\n\n  .lg-checkbox[data-disabled],\n  .lg-radio[data-disabled] {\n    cursor: not-allowed;\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-checkbox[data-state='checked'],\n  .lg-checkbox[data-state='indeterminate'],\n  .lg-radio[data-state='checked'] {\n    border-color: transparent;\n    background:\n      linear-gradient(180deg, rgb(255 255 255 / 0.24), rgb(255 255 255 / 0) 62%), var(--lg-accent);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.4),\n      inset 0 -1px 0 rgb(0 0 0 / 0.14),\n      0 1px 3px rgb(0 0 0 / 0.28);\n  }\n\n  .lg-check-indicator {\n    display: grid;\n    width: 100%;\n    height: 100%;\n    place-items: center;\n    pointer-events: none;\n  }\n\n  .lg-check-mark {\n    width: 84%;\n    height: 84%;\n    fill: none;\n    stroke: currentColor;\n    stroke-width: 2.1;\n    stroke-linecap: round;\n    stroke-linejoin: round;\n  }\n\n  .lg-check-tick,\n  .lg-check-dash {\n    stroke-dasharray: 1;\n    stroke-dashoffset: 1;\n    transition: stroke-dashoffset 300ms var(--lg-ease-out);\n  }\n\n  .lg-checkbox[data-state='checked'] .lg-check-tick,\n  .lg-checkbox[data-state='indeterminate'] .lg-check-dash {\n    stroke-dashoffset: 0;\n  }\n\n  .lg-radio-dot {\n    width: 42%;\n    height: 42%;\n    border-radius: 9999px;\n    background: #fff;\n    box-shadow: 0 1px 2px rgb(0 0 0 / 0.3);\n    scale: 0;\n    transition: scale 160ms cubic-bezier(0.4, 0, 1, 1);\n  }\n\n  /* The dot springs in and shrinks straight out: the spring would swing it\n     through zero on the way back. */\n  .lg-radio[data-state='checked'] .lg-radio-dot {\n    scale: 1;\n    transition: scale 340ms var(--lg-spring);\n  }\n\n  /* ============================================\n     Textarea and select: the Input's recessed glass field\n     ============================================ */\n\n  .lg-textarea {\n    display: block;\n    min-height: 5.5rem;\n    line-height: 1.5;\n    resize: vertical;\n  }\n\n  .lg-textarea[data-resize='auto'] {\n    resize: none;\n    overflow-y: hidden;\n  }\n\n  .lg-select-trigger {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    gap: 0.75rem;\n    text-align: start;\n    cursor: pointer;\n    user-select: none;\n  }\n\n  .lg-select-trigger > span:first-child {\n    min-width: 0;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n\n  .lg-select-trigger[data-placeholder] {\n    color: var(--lg-muted-foreground);\n  }\n\n  .lg-select-chevron {\n    display: inline-flex;\n    flex: none;\n    color: var(--lg-muted-foreground);\n    transition: rotate 260ms var(--lg-spring);\n  }\n\n  .lg-select-chevron > svg {\n    width: 1rem;\n    height: 1rem;\n  }\n\n  .lg-select-trigger[data-state='open'] .lg-select-chevron {\n    rotate: 180deg;\n  }\n\n  .lg-select-content {\n    --lg-panel-radius: 1.25rem;\n\n    z-index: 60;\n    box-sizing: border-box;\n    min-width: var(--radix-select-trigger-width);\n    max-width: calc(100vw - 1rem);\n    max-height: var(--radix-select-content-available-height, 24rem);\n    padding: 0.375rem;\n    font-size: 0.9375rem;\n    line-height: 1.4;\n    transform-origin: var(--radix-select-content-transform-origin);\n  }\n\n  .lg-select-content:focus {\n    outline: none;\n  }\n\n  .lg-select-viewport {\n    max-height: inherit;\n    overflow-y: auto;\n    overscroll-behavior: contain;\n    scrollbar-width: none;\n  }\n\n  .lg-select-viewport::-webkit-scrollbar {\n    display: none;\n  }\n\n  .lg-select-scroll {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    height: 1.5rem;\n    color: var(--lg-muted-foreground);\n    cursor: default;\n  }\n\n  .lg-select-scroll > svg {\n    width: 1rem;\n    height: 1rem;\n  }\n\n  /* ============================================\n     Progress, spinner and skeleton: waiting, told quietly\n     ============================================ */\n\n  .lg-visually-hidden {\n    position: absolute;\n    width: 1px;\n    height: 1px;\n    margin: -1px;\n    padding: 0;\n    overflow: hidden;\n    border: 0;\n    clip-path: inset(50%);\n    white-space: nowrap;\n  }\n\n  .lg-progress {\n    --lg-progress-height: 0.5rem;\n\n    display: grid;\n    gap: 0.5rem;\n    width: 100%;\n    color: var(--lg-foreground);\n    font-size: 0.875rem;\n    line-height: 1.25rem;\n  }\n\n  .lg-progress[data-size='sm'] {\n    --lg-progress-height: 0.25rem;\n  }\n\n  .lg-progress[data-size='lg'] {\n    --lg-progress-height: 0.75rem;\n  }\n\n  .lg-progress-header {\n    display: flex;\n    align-items: baseline;\n    justify-content: space-between;\n    gap: 1rem;\n  }\n\n  .lg-progress-value {\n    color: var(--lg-muted-foreground);\n    font-variant-numeric: tabular-nums;\n  }\n\n  .lg-progress-track {\n    position: relative;\n    height: var(--lg-progress-height);\n    overflow: hidden;\n    border-radius: 9999px;\n    background: var(--lg-track);\n    box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.18);\n  }\n\n  /* The whole pill slides in from the left, so its leading end stays round. */\n  .lg-progress-fill {\n    width: 100%;\n    height: 100%;\n    border-radius: inherit;\n    background-color: var(--lg-accent);\n    background-image: linear-gradient(180deg, rgb(255 255 255 / 0.34), rgb(255 255 255 / 0) 60%);\n    box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.4);\n    translate: calc((var(--lg-progress, 0) - 100) * 1%) 0;\n    transition: translate 640ms var(--lg-ease-out);\n  }\n\n  .lg-progress[data-state='indeterminate'] .lg-progress-fill {\n    width: 40%;\n    translate: 75% 0;\n    transition: none;\n    /* Stepped at 30 frames a second: a loader that runs for a while does not need a frame\n       every 8 ms, and the GPU is spared most of them. */\n    animation: lg-progress-sweep 1.6s steps(48) infinite;\n  }\n\n  @keyframes lg-progress-sweep {\n    from {\n      translate: -100% 0;\n    }\n\n    to {\n      translate: 250% 0;\n    }\n  }\n\n  .lg-spinner {\n    --lg-spinner-size: 1.5rem;\n    --lg-spinner-width: 2px;\n\n    display: inline-flex;\n    flex: none;\n    width: var(--lg-spinner-size);\n    height: var(--lg-spinner-size);\n    border-radius: 9999px;\n    box-shadow: inset 0 0 0 var(--lg-spinner-width)\n      color-mix(in srgb, currentColor 20%, transparent);\n    color: var(--lg-foreground);\n    vertical-align: middle;\n  }\n\n  .lg-spinner[data-size='sm'] {\n    --lg-spinner-size: 1rem;\n    --lg-spinner-width: 1.5px;\n  }\n\n  .lg-spinner[data-size='lg'] {\n    --lg-spinner-size: 2.25rem;\n    --lg-spinner-width: 3px;\n  }\n\n  .lg-spinner-ring {\n    width: 100%;\n    height: 100%;\n    border-radius: inherit;\n    background: conic-gradient(from 0deg, transparent, currentColor);\n    -webkit-mask: radial-gradient(\n      farthest-side,\n      transparent calc(100% - var(--lg-spinner-width) - 0.5px),\n      #000 calc(100% - var(--lg-spinner-width))\n    );\n    mask: radial-gradient(\n      farthest-side,\n      transparent calc(100% - var(--lg-spinner-width) - 0.5px),\n      #000 calc(100% - var(--lg-spinner-width))\n    );\n    animation: lg-spinner-turn 0.9s linear infinite;\n  }\n\n  @keyframes lg-spinner-turn {\n    to {\n      rotate: 360deg;\n    }\n  }\n\n  .lg-skeleton {\n    position: relative;\n    display: block;\n    min-height: 1rem;\n    overflow: hidden;\n    border-radius: 0.625rem;\n    background: var(--lg-control-surface);\n  }\n\n  .lg-skeleton[data-variant='circle'] {\n    width: 2.5rem;\n    min-height: 0;\n    aspect-ratio: 1;\n    border-radius: 9999px;\n  }\n\n  .lg-skeleton[data-variant='line'] {\n    min-height: 0;\n    height: 0.875rem;\n    border-radius: 9999px;\n  }\n\n  .lg-skeleton::after {\n    content: '';\n    position: absolute;\n    inset: 0;\n    background: linear-gradient(\n      100deg,\n      transparent 20%,\n      rgb(255 255 255 / 0.14) 50%,\n      transparent 80%\n    );\n    translate: -100% 0;\n    /* Stepped at 30 frames a second, like the progress sweep. */\n    animation: lg-skeleton-sheen 1.8s steps(54) infinite;\n  }\n\n  [data-lg-theme='light'] .lg-skeleton::after {\n    background: linear-gradient(\n      100deg,\n      transparent 20%,\n      rgb(255 255 255 / 0.75) 50%,\n      transparent 80%\n    );\n  }\n\n  @keyframes lg-skeleton-sheen {\n    to {\n      translate: 100% 0;\n    }\n  }\n\n  .lg-skeleton-text {\n    display: grid;\n    gap: 0.625rem;\n    width: 100%;\n  }\n\n  .lg-skeleton-text > .lg-skeleton:last-child:not(:only-child) {\n    width: 62%;\n  }\n\n  /* ============================================\n     Alert: tinted glass with the badge's tones. Every tone keeps 4.5:1 text\n     contrast over a pure black or pure white backdrop in both themes.\n     ============================================ */\n\n  .lg-alert {\n    --lg-alert-fill: 13 44 82;\n    --lg-alert-ink: #b2d6ff;\n    --lg-alert-tint: 0 103 217;\n    --lg-alert-highlight: rgb(255 255 255 / 0.12);\n\n    display: flex;\n    align-items: flex-start;\n    gap: 0.75rem;\n    box-sizing: border-box;\n    width: 100%;\n    padding: 0.875rem 1rem;\n    border-radius: 1.125rem;\n    background: rgb(var(--lg-alert-fill) / 0.88);\n    box-shadow:\n      inset 0 1px 0 var(--lg-alert-highlight),\n      inset 0 0 0 0.5px rgb(var(--lg-alert-tint) / 0.45);\n    color: var(--lg-foreground);\n    font-size: 0.9375rem;\n    line-height: 1.4;\n    -webkit-backdrop-filter: blur(min(var(--lg-glass-blur), 16px))\n      brightness(var(--lg-glass-brightness)) contrast(var(--lg-glass-contrast))\n      saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(min(var(--lg-glass-blur), 16px)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n  }\n\n  .lg-alert[data-tone='success'] {\n    --lg-alert-fill: 22 60 48;\n    --lg-alert-ink: #7cebc0;\n    --lg-alert-tint: 51 214 159;\n  }\n\n  .lg-alert[data-tone='warning'] {\n    --lg-alert-fill: 70 54 22;\n    --lg-alert-ink: #ffd27a;\n    --lg-alert-tint: 255 189 56;\n  }\n\n  .lg-alert[data-tone='danger'] {\n    --lg-alert-fill: 76 30 44;\n    --lg-alert-ink: #ffa6b7;\n    --lg-alert-tint: 255 92 122;\n  }\n\n  [data-lg-theme='light'] .lg-alert {\n    --lg-alert-fill: 229 241 255;\n    --lg-alert-ink: #0054b3;\n    --lg-alert-tint: 0 103 217;\n    --lg-alert-highlight: rgb(255 255 255 / 0.9);\n  }\n\n  [data-lg-theme='light'] .lg-alert[data-tone='success'] {\n    --lg-alert-fill: 224 247 238;\n    --lg-alert-ink: #08603f;\n    --lg-alert-tint: 20 168 116;\n  }\n\n  [data-lg-theme='light'] .lg-alert[data-tone='warning'] {\n    --lg-alert-fill: 255 243 219;\n    --lg-alert-ink: #824c00;\n    --lg-alert-tint: 232 150 12;\n  }\n\n  [data-lg-theme='light'] .lg-alert[data-tone='danger'] {\n    --lg-alert-fill: 255 229 236;\n    --lg-alert-ink: #a8113a;\n    --lg-alert-tint: 232 52 90;\n  }\n\n  /* Dismissing folds the alert to nothing. The alert sits in a one-row grid, and the row shrinks\n     from its content to zero, so its padding and margin go with it and nothing is measured. */\n  .lg-alert-fold {\n    display: grid;\n    grid-template-rows: 1fr;\n  }\n\n  .lg-alert-fold-inner {\n    min-height: 0;\n  }\n\n  .lg-alert-fold[data-state='folding'] {\n    grid-template-rows: 0fr;\n    transition: grid-template-rows 240ms var(--lg-ease-out);\n  }\n\n  .lg-alert-fold[data-state='folding'] > .lg-alert-fold-inner {\n    overflow: hidden;\n  }\n\n  .lg-alert-fold[data-state='folding'] .lg-alert {\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity 160ms ease-in;\n  }\n\n  .lg-alert-icon {\n    display: inline-flex;\n    flex: none;\n    width: 1.25rem;\n    height: 1.25rem;\n    margin-top: 0.0625rem;\n    color: var(--lg-alert-ink);\n  }\n\n  .lg-alert-icon > svg {\n    display: block;\n    width: 100%;\n    height: 100%;\n  }\n\n  .lg-alert-body {\n    display: grid;\n    flex: 1;\n    gap: 0.125rem;\n    min-width: 0;\n  }\n\n  .lg-alert-title {\n    margin: 0;\n    font-weight: 600;\n  }\n\n  .lg-alert-description {\n    opacity: 0.82;\n  }\n\n  .lg-alert-close {\n    display: inline-flex;\n    flex: none;\n    align-items: center;\n    justify-content: center;\n    width: 1.75rem;\n    height: 1.75rem;\n    margin: -0.25rem -0.375rem -0.25rem 0;\n    padding: 0;\n    border: 0;\n    border-radius: 9999px;\n    background: transparent;\n    color: var(--lg-foreground);\n    cursor: pointer;\n    opacity: 0.7;\n    transition:\n      background-color 180ms ease,\n      opacity 180ms ease;\n  }\n\n  .lg-alert-close:hover {\n    background: rgb(255 255 255 / 0.14);\n    opacity: 1;\n  }\n\n  [data-lg-theme='light'] .lg-alert-close:hover {\n    background: rgb(0 0 0 / 0.07);\n  }\n\n  .lg-alert-close:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 1px;\n    opacity: 1;\n  }\n\n  .lg-alert-close > svg {\n    width: 1rem;\n    height: 1rem;\n  }\n\n  /* ============================================\n     Avatar: a round photo or coloured initials. A dot or a neighbour cuts\n     the circle away instead of ringing it, so it works on any backdrop.\n     ============================================ */\n\n  .lg-avatar {\n    --lg-avatar-size: 2.5rem;\n    --lg-avatar-dot: 0.625rem;\n\n    position: relative;\n    display: inline-flex;\n    flex: none;\n    width: var(--lg-avatar-size);\n    height: var(--lg-avatar-size);\n    font-size: calc(var(--lg-avatar-size) * 0.38);\n    vertical-align: middle;\n  }\n\n  .lg-avatar[data-size='xs'] {\n    --lg-avatar-size: 1.5rem;\n    --lg-avatar-dot: 0.4375rem;\n  }\n\n  .lg-avatar[data-size='sm'] {\n    --lg-avatar-size: 2rem;\n    --lg-avatar-dot: 0.5rem;\n  }\n\n  .lg-avatar[data-size='lg'] {\n    --lg-avatar-size: 3.5rem;\n    --lg-avatar-dot: 0.875rem;\n  }\n\n  .lg-avatar[data-size='xl'] {\n    --lg-avatar-size: 5rem;\n    --lg-avatar-dot: 1.125rem;\n  }\n\n  .lg-avatar-face {\n    position: relative;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n    border-radius: 9999px;\n    background: var(--lg-control-surface);\n    color: #fff;\n    font-weight: 600;\n    line-height: 1;\n    user-select: none;\n  }\n\n  .lg-avatar-face::after {\n    content: '';\n    position: absolute;\n    inset: 0;\n    border-radius: inherit;\n    box-shadow:\n      inset 0 0 0 1px rgb(255 255 255 / 0.18),\n      inset 0 1px 0 rgb(255 255 255 / 0.22);\n    pointer-events: none;\n  }\n\n  [data-lg-theme='light'] .lg-avatar-face::after {\n    box-shadow:\n      inset 0 0 0 1px rgb(0 0 0 / 0.1),\n      inset 0 1px 0 rgb(255 255 255 / 0.3);\n  }\n\n  .lg-avatar-image {\n    display: block;\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n  }\n\n  .lg-avatar-fallback {\n    --lg-avatar-from: #747479;\n    --lg-avatar-to: #535359;\n\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    height: 100%;\n    background: linear-gradient(145deg, var(--lg-avatar-from), var(--lg-avatar-to));\n    text-shadow: 0 1px 1px rgb(0 0 0 / 0.22);\n  }\n\n  .lg-avatar-fallback[data-tone='0'] {\n    --lg-avatar-from: #2d6de9;\n    --lg-avatar-to: #1a4fc7;\n  }\n\n  .lg-avatar-fallback[data-tone='1'] {\n    --lg-avatar-from: #b93f24;\n    --lg-avatar-to: #882c1a;\n  }\n\n  .lg-avatar-fallback[data-tone='2'] {\n    --lg-avatar-from: #707075;\n    --lg-avatar-to: #56565b;\n  }\n\n  .lg-avatar-fallback[data-tone='3'] {\n    --lg-avatar-from: #d0386f;\n    --lg-avatar-to: #a82458;\n  }\n\n  .lg-avatar-fallback[data-tone='4'] {\n    --lg-avatar-from: #b9540f;\n    --lg-avatar-to: #9c4308;\n  }\n\n  .lg-avatar-fallback[data-tone='5'] {\n    --lg-avatar-from: #1f8150;\n    --lg-avatar-to: #146c3c;\n  }\n\n  .lg-avatar-fallback[data-tone='6'] {\n    --lg-avatar-from: #0e7d8b;\n    --lg-avatar-to: #0a6674;\n  }\n\n  .lg-avatar-fallback[data-tone='7'] {\n    --lg-avatar-from: #747479;\n    --lg-avatar-to: #535359;\n  }\n\n  .lg-avatar-status {\n    --lg-status: #8e8e93;\n\n    position: absolute;\n    top: calc(var(--lg-avatar-size) * 0.8536 - var(--lg-avatar-dot) / 2);\n    left: calc(var(--lg-avatar-size) * 0.8536 - var(--lg-avatar-dot) / 2);\n    width: var(--lg-avatar-dot);\n    height: var(--lg-avatar-dot);\n    border-radius: 9999px;\n    background: var(--lg-status);\n  }\n\n  .lg-avatar[data-status='online'] > .lg-avatar-status {\n    --lg-status: #33d69f;\n  }\n\n  .lg-avatar[data-status='away'] > .lg-avatar-status {\n    --lg-status: #ffbd38;\n  }\n\n  .lg-avatar[data-status='busy'] > .lg-avatar-status {\n    --lg-status: #ff5c7a;\n  }\n\n  [data-lg-theme='light'] .lg-avatar[data-status='online'] > .lg-avatar-status {\n    --lg-status: #14a874;\n  }\n\n  [data-lg-theme='light'] .lg-avatar[data-status='away'] > .lg-avatar-status {\n    --lg-status: #e8960c;\n  }\n\n  [data-lg-theme='light'] .lg-avatar[data-status='busy'] > .lg-avatar-status {\n    --lg-status: #e8345a;\n  }\n\n  .lg-avatar[data-status] > .lg-avatar-face {\n    --lg-cut-status: radial-gradient(\n      circle at calc(var(--lg-avatar-size) * 0.8536) calc(var(--lg-avatar-size) * 0.8536),\n      transparent calc(var(--lg-avatar-dot) / 2 + 2px),\n      #000 calc(var(--lg-avatar-dot) / 2 + 2.5px)\n    );\n\n    -webkit-mask-image: var(--lg-cut-status), var(--lg-cut-next, linear-gradient(#000, #000));\n    mask-image: var(--lg-cut-status), var(--lg-cut-next, linear-gradient(#000, #000));\n    -webkit-mask-composite: source-in;\n    mask-composite: intersect;\n  }\n\n  .lg-avatar-group {\n    display: inline-flex;\n    align-items: center;\n    isolation: isolate;\n  }\n\n  .lg-avatar-group[data-size='xs'] .lg-avatar {\n    --lg-avatar-size: 1.5rem;\n    --lg-avatar-dot: 0.4375rem;\n  }\n\n  .lg-avatar-group[data-size='sm'] .lg-avatar {\n    --lg-avatar-size: 2rem;\n    --lg-avatar-dot: 0.5rem;\n  }\n\n  .lg-avatar-group[data-size='md'] .lg-avatar {\n    --lg-avatar-size: 2.5rem;\n    --lg-avatar-dot: 0.625rem;\n  }\n\n  .lg-avatar-group[data-size='lg'] .lg-avatar {\n    --lg-avatar-size: 3.5rem;\n    --lg-avatar-dot: 0.875rem;\n  }\n\n  .lg-avatar-group[data-size='xl'] .lg-avatar {\n    --lg-avatar-size: 5rem;\n    --lg-avatar-dot: 1.125rem;\n  }\n\n  .lg-avatar-group > .lg-avatar + .lg-avatar {\n    margin-inline-start: calc(var(--lg-avatar-size) * -0.2);\n  }\n\n  .lg-avatar-group > .lg-avatar:not(:last-child) > .lg-avatar-face {\n    --lg-cut-next: radial-gradient(\n      circle at calc(var(--lg-avatar-size) * 1.3) 50%,\n      transparent calc(var(--lg-avatar-size) / 2 + 2px),\n      #000 calc(var(--lg-avatar-size) / 2 + 2.5px)\n    );\n\n    -webkit-mask-image: var(--lg-cut-status, linear-gradient(#000, #000)), var(--lg-cut-next);\n    mask-image: var(--lg-cut-status, linear-gradient(#000, #000)), var(--lg-cut-next);\n    -webkit-mask-composite: source-in;\n    mask-composite: intersect;\n  }\n\n  [dir='rtl'] .lg-avatar-group > .lg-avatar:not(:last-child) > .lg-avatar-face {\n    --lg-cut-next: radial-gradient(\n      circle at calc(var(--lg-avatar-size) * -0.3) 50%,\n      transparent calc(var(--lg-avatar-size) / 2 + 2px),\n      #000 calc(var(--lg-avatar-size) / 2 + 2.5px)\n    );\n  }\n\n  .lg-avatar-more {\n    background: var(--lg-control-surface-hover);\n    color: var(--lg-foreground);\n    font-size: calc(var(--lg-avatar-size) * 0.32);\n  }\n\n  /* ============================================\n     Kbd: a keycap with a lit top edge and a dark lip\n     ============================================ */\n\n  .lg-kbd {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    box-sizing: border-box;\n    min-width: 1.5rem;\n    height: 1.5rem;\n    padding: 0 0.4375rem;\n    border-radius: 0.4375rem;\n    background: var(--lg-control-surface);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.18),\n      inset 0 0 0 1px var(--lg-border),\n      0 1px 0 rgb(0 0 0 / 0.35);\n    color: var(--lg-foreground);\n    font: inherit;\n    font-size: 0.75rem;\n    font-weight: 600;\n    letter-spacing: 0.02em;\n    line-height: 1;\n  }\n\n  [data-lg-theme='light'] .lg-kbd {\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.9),\n      inset 0 0 0 1px var(--lg-border),\n      0 1px 0 rgb(0 0 0 / 0.16);\n  }\n\n  .lg-kbd-group {\n    display: inline-flex;\n    align-items: center;\n    gap: 0.25rem;\n  }\n\n  /* ============================================\n     Scroll area: native scrolling with a thin glass scrollbar over its edge.\n     The bar shows while the pointer is over the area or it scrolls, rests\n     narrow, and widens under the pointer.\n     ============================================ */\n\n  .lg-scroll-area {\n    position: relative;\n    overflow: hidden;\n  }\n\n  /* A max-height on the area bounds the viewport as well as a height does. */\n  .lg-scroll-area-viewport {\n    width: 100%;\n    height: 100%;\n    max-height: inherit;\n    border-radius: inherit;\n  }\n\n  .lg-scroll-area-viewport:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: -2px;\n  }\n\n  .lg-scrollbar {\n    z-index: 1;\n    display: flex;\n    box-sizing: border-box;\n    padding: 3px;\n    touch-action: none;\n    user-select: none;\n    transition: padding 160ms var(--lg-ease-out);\n  }\n\n  .lg-scrollbar[data-orientation='vertical'] {\n    width: 10px;\n  }\n\n  .lg-scrollbar[data-orientation='horizontal'] {\n    flex-direction: column;\n    height: 10px;\n  }\n\n  .lg-scrollbar:hover {\n    padding: 2px;\n  }\n\n  .lg-scrollbar[data-state='visible'] {\n    animation: lg-scrollbar-in 160ms var(--lg-ease-out);\n  }\n\n  .lg-scrollbar[data-state='hidden'] {\n    animation: lg-scrollbar-out 240ms ease-in forwards;\n  }\n\n  /* A sliver of clear glass: a faint tint of the text colour, a lit rim and a\n     blur of what scrolls beneath it. */\n  .lg-scrollbar-thumb {\n    position: relative;\n    flex: 1;\n    border-radius: 999px;\n    background: color-mix(in srgb, var(--lg-foreground) 24%, transparent);\n    box-shadow: inset 0 0 0 0.5px rgb(255 255 255 / 0.24);\n    -webkit-backdrop-filter: blur(6px) saturate(1.6);\n    backdrop-filter: blur(6px) saturate(1.6);\n    transition: background-color 160ms var(--lg-ease-out);\n  }\n\n  [data-lg-theme='light'] .lg-scrollbar-thumb {\n    box-shadow:\n      inset 0 0 0 0.5px rgb(255 255 255 / 0.7),\n      0 0 0 0.5px rgb(0 0 0 / 0.06);\n  }\n\n  /* The pointer can catch more than the sliver it sees. */\n  .lg-scrollbar-thumb::before {\n    content: '';\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    width: 100%;\n    height: 100%;\n    min-width: 24px;\n    min-height: 24px;\n    transform: translate(-50%, -50%);\n  }\n\n  .lg-scrollbar:hover .lg-scrollbar-thumb {\n    background: color-mix(in srgb, var(--lg-foreground) 40%, transparent);\n  }\n\n  @keyframes lg-scrollbar-in {\n    from {\n      opacity: 0;\n    }\n  }\n\n  @keyframes lg-scrollbar-out {\n    to {\n      opacity: 0;\n    }\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-scrollbar-thumb {\n    -webkit-backdrop-filter: none;\n    backdrop-filter: none;\n    background: color-mix(in srgb, var(--lg-foreground) 55%, var(--lg-bg));\n  }\n\n  @media (prefers-contrast: more) {\n    .lg-scrollbar-thumb {\n      background: color-mix(in srgb, var(--lg-foreground) 70%, transparent);\n    }\n  }\n\n  @media (forced-colors: active) {\n    .lg-scrollbar-thumb {\n      forced-color-adjust: none;\n      background: CanvasText;\n      box-shadow: none;\n    }\n  }\n\n  /* ============================================\n     Command: a search field over a list that filters as you type. The\n     highlighted row is the menu's, and the list resizes with its results.\n     ============================================ */\n\n  .lg-command {\n    display: flex;\n    flex-direction: column;\n    width: 100%;\n    overflow: hidden;\n    border-radius: inherit;\n    color: var(--lg-foreground);\n    font-size: 0.9375rem;\n    line-height: 1.4;\n  }\n\n  .lg-command-dialog {\n    --lg-panel-radius: 1.5rem;\n\n    top: min(18vh, 9rem);\n    max-width: 40rem;\n    padding: 0;\n    translate: -50% 0;\n    overflow: hidden;\n  }\n\n  .lg-command-search {\n    display: flex;\n    flex: none;\n    align-items: center;\n    gap: 0.75rem;\n    height: 3.25rem;\n    padding: 0 1.125rem;\n    border-bottom: 1px solid var(--lg-border);\n    color: var(--lg-muted-foreground);\n    transition: box-shadow 200ms ease;\n  }\n\n  .lg-command-search:focus-within {\n    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--lg-focus-ring) 60%, transparent);\n  }\n\n  .lg-command-search > svg {\n    flex: none;\n    width: 1.125rem;\n    height: 1.125rem;\n  }\n\n  .lg-command-input {\n    flex: 1;\n    min-width: 0;\n    height: 100%;\n    padding: 0;\n    border: 0;\n    background: transparent;\n    color: var(--lg-foreground);\n    font: inherit;\n    font-size: 1rem;\n    outline: none;\n  }\n\n  .lg-command-input::placeholder {\n    color: var(--lg-muted-foreground);\n    opacity: 0.72;\n  }\n\n  .lg-command-list {\n    box-sizing: border-box;\n    height: min(calc(var(--cmdk-list-height, 22rem) + 0.75rem), 22rem);\n    max-height: min(22rem, 60dvh);\n    padding: 0.375rem;\n    overflow-x: hidden;\n    overflow-y: auto;\n    overscroll-behavior: contain;\n    scroll-padding-block: 0.375rem;\n    transition: height 160ms var(--lg-ease-out);\n  }\n\n  .lg-command-group [cmdk-group-heading] {\n    padding: 0.5rem 0.75rem 0.25rem;\n    color: var(--lg-muted-foreground);\n    font-size: 0.75rem;\n    font-weight: 600;\n    line-height: 1rem;\n    user-select: none;\n  }\n\n  .lg-command-item {\n    display: flex;\n    align-items: center;\n    gap: 0.625rem;\n    box-sizing: border-box;\n    min-height: 2.5rem;\n    padding: 0.5rem 0.75rem;\n    border-radius: 0.75rem;\n    color: var(--lg-foreground);\n    cursor: default;\n    outline: none;\n    user-select: none;\n  }\n\n  .lg-command-item > svg {\n    flex: none;\n    width: 1.125rem;\n    height: 1.125rem;\n    color: var(--lg-muted-foreground);\n  }\n\n  .lg-command-item[data-selected='true'] {\n    background: rgb(255 255 255 / 0.14);\n  }\n\n  [data-lg-theme='light'] .lg-command-item[data-selected='true'] {\n    background: rgb(0 0 0 / 0.06);\n  }\n\n  .lg-command-item[data-disabled='true'] {\n    opacity: var(--lg-disabled-opacity);\n    pointer-events: none;\n  }\n\n  .lg-command-empty {\n    padding: 2rem 1rem;\n    color: var(--lg-muted-foreground);\n    text-align: center;\n  }\n\n  .lg-command-separator {\n    height: 1px;\n    margin: 0.375rem 0.625rem;\n    background: var(--lg-border);\n  }\n\n  .lg-command-shortcut {\n    display: inline-flex;\n    align-items: center;\n    margin-inline-start: auto;\n    padding-inline-start: 1.5rem;\n    color: var(--lg-muted-foreground);\n    font-size: 0.75rem;\n    letter-spacing: 0.06em;\n  }\n\n  /* ============================================\n     Breadcrumb and pagination: where you are, and the way on\n     ============================================ */\n\n  .lg-breadcrumb {\n    color: var(--lg-muted-foreground);\n    font-size: 0.9375rem;\n    line-height: 1.4;\n  }\n\n  .lg-breadcrumb-list {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    gap: 0.125rem;\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  .lg-breadcrumb-item {\n    display: inline-flex;\n    align-items: center;\n    gap: 0.125rem;\n    min-width: 0;\n  }\n\n  .lg-breadcrumb-link,\n  .lg-breadcrumb-ellipsis,\n  .lg-breadcrumb-page {\n    display: inline-flex;\n    align-items: center;\n    box-sizing: border-box;\n    min-height: 1.875rem;\n    padding: 0 0.625rem;\n    border-radius: 9999px;\n  }\n\n  .lg-breadcrumb-link,\n  .lg-breadcrumb-ellipsis {\n    border: 0;\n    background: transparent;\n    color: var(--lg-muted-foreground);\n    cursor: pointer;\n    font: inherit;\n    text-decoration: none;\n    transition:\n      background-color 180ms ease,\n      color 180ms ease;\n  }\n\n  .lg-breadcrumb-link:hover,\n  .lg-breadcrumb-ellipsis:hover,\n  .lg-breadcrumb-ellipsis[data-state='open'] {\n    background: rgb(255 255 255 / 0.1);\n    color: var(--lg-foreground);\n  }\n\n  [data-lg-theme='light'] .lg-breadcrumb-link:hover,\n  [data-lg-theme='light'] .lg-breadcrumb-ellipsis:hover,\n  [data-lg-theme='light'] .lg-breadcrumb-ellipsis[data-state='open'] {\n    background: rgb(0 0 0 / 0.06);\n  }\n\n  .lg-breadcrumb-link:focus-visible,\n  .lg-breadcrumb-ellipsis:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 1px;\n  }\n\n  .lg-breadcrumb-ellipsis {\n    padding: 0 0.375rem;\n  }\n\n  .lg-breadcrumb-ellipsis > svg {\n    width: 1.25rem;\n    height: 1.25rem;\n  }\n\n  .lg-breadcrumb-page {\n    color: var(--lg-foreground);\n    font-weight: 600;\n  }\n\n  .lg-breadcrumb-separator {\n    display: inline-flex;\n    color: var(--lg-muted-foreground);\n    opacity: 0.65;\n  }\n\n  .lg-breadcrumb-separator > svg {\n    width: 1rem;\n    height: 1rem;\n  }\n\n  [dir='rtl'] .lg-breadcrumb-separator {\n    scale: -1 1;\n  }\n\n  .lg-breadcrumb-item:last-child > .lg-breadcrumb-separator {\n    display: none;\n  }\n\n  .lg-pagination-list {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    gap: 0.25rem;\n    margin: 0;\n    padding: 0;\n    list-style: none;\n  }\n\n  .lg-page {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    box-sizing: border-box;\n    min-width: 2.25rem;\n    height: 2.25rem;\n    padding: 0 0.625rem;\n    border: 0;\n    border-radius: 9999px;\n    background: transparent;\n    box-shadow: inset 0 0 0 1px transparent;\n    color: var(--lg-foreground);\n    cursor: pointer;\n    font: inherit;\n    font-size: 0.9375rem;\n    font-variant-numeric: tabular-nums;\n    -webkit-tap-highlight-color: transparent;\n    transition:\n      background-color 200ms ease,\n      box-shadow 200ms ease,\n      scale 320ms var(--lg-spring);\n  }\n\n  .lg-page > svg {\n    width: 1.125rem;\n    height: 1.125rem;\n  }\n\n  .lg-page:hover:not(:disabled, [data-current]) {\n    background: var(--lg-control-surface-hover);\n  }\n\n  .lg-page:active:not(:disabled) {\n    scale: 0.92;\n  }\n\n  .lg-page:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-page:disabled {\n    cursor: not-allowed;\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-page[data-current] {\n    background: rgb(255 255 255 / 0.16);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.28),\n      inset 0 0 0 1px rgb(255 255 255 / 0.12);\n    font-weight: 600;\n  }\n\n  [data-lg-theme='light'] .lg-page[data-current] {\n    background: rgb(0 0 0 / 0.08);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.9),\n      inset 0 0 0 1px rgb(0 0 0 / 0.06);\n  }\n\n  .lg-page-gap {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    min-width: 1.5rem;\n    color: var(--lg-muted-foreground);\n    user-select: none;\n  }\n\n  /* Tabs */\n  .lg-tabs-list {\n    display: flex;\n    width: 100%;\n    max-width: 100%;\n    gap: 0.25rem;\n    padding: 0.25rem;\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n  }\n\n  .lg-tabs-trigger {\n    position: relative;\n    display: flex;\n    flex: 1 1 0%;\n    min-width: 0;\n    align-items: center;\n    justify-content: center;\n    border-radius: 9999px;\n    padding: 0.5rem 0.75rem;\n    color: var(--lg-muted-foreground);\n    transition:\n      color var(--lg-motion-duration) ease,\n      background-color var(--lg-motion-duration) ease,\n      box-shadow var(--lg-motion-duration) ease;\n  }\n\n  .lg-tabs-trigger:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-tabs-trigger:disabled {\n    pointer-events: none;\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-tabs-content {\n    margin-top: 0.75rem;\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n  }\n\n  /* The active trigger is a small raised glass bubble */\n  .lg-tabs-trigger[data-state='active'] {\n    color: var(--lg-foreground);\n    background: rgb(255 255 255 / 0.12);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.22),\n      inset 0 -1px 0 rgb(0 0 0 / 0.12),\n      0 1px 3px rgb(0 0 0 / 0.24),\n      0 4px 12px -4px rgb(0 0 0 / 0.4);\n  }\n\n  [data-lg-theme='light'] .lg-tabs-trigger[data-state='active'] {\n    background: rgb(255 255 255 / 0.82);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.9),\n      0 1px 3px rgb(0 0 0 / 0.1),\n      0 4px 12px -4px rgb(0 0 0 / 0.16);\n  }\n\n  /* The bubble catches the same specular light as the glass around it. */\n  .lg-tabs-trigger[data-state='active']::after,\n  .lg-tabs-indicator::after {\n    content: '';\n    position: absolute;\n    inset: 0;\n    padding: var(--lg-rim-width);\n    border-radius: inherit;\n    pointer-events: none;\n    background: linear-gradient(\n      to bottom right,\n      rgb(255 255 255 / var(--lg-specular-peak)),\n      rgb(255 255 255 / var(--lg-specular-floor)) 38% 62%,\n      rgb(255 255 255 / var(--lg-specular-counter))\n    );\n    -webkit-mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    -webkit-mask-composite: xor;\n    mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    mask-composite: exclude;\n  }\n\n  /* Once scripts run, one bubble for the whole list moves between triggers\n     and the active trigger stops drawing its own. */\n  .lg-tabs-indicator {\n    display: none;\n  }\n\n  .lg-tabs-list[data-lg-indicator='ready'] > .lg-tabs-indicator {\n    --lg-bubble-alpha: 0.12;\n\n    position: absolute;\n    z-index: 1;\n    display: block;\n    border-radius: 9999px;\n    pointer-events: none;\n  }\n\n  [data-lg-theme='light'] .lg-tabs-list[data-lg-indicator='ready'] > .lg-tabs-indicator {\n    --lg-bubble-alpha: 0.82;\n  }\n\n  .lg-tabs-list[data-lg-indicator='ready'] .lg-tabs-trigger[data-state='active'] {\n    background: transparent;\n    box-shadow: none;\n    transition: color var(--lg-motion-duration) ease;\n  }\n\n  .lg-tabs-list[data-lg-indicator='ready'] .lg-tabs-trigger[data-state='active']::after {\n    display: none;\n  }\n\n  /* Accordion: the trigger shares the content card's corner radius */\n  .lg-accordion-trigger {\n    display: flex;\n    width: 100%;\n    justify-content: space-between;\n    border: 1px solid var(--lg-border);\n    border-radius: var(--lg-radius-md);\n    padding: 0.75rem 1rem;\n    background: var(--lg-control-surface);\n    box-shadow: none;\n    color: var(--lg-foreground);\n    font-size: 1rem;\n    line-height: 1.5;\n    font-weight: 500;\n    text-align: left;\n    -webkit-backdrop-filter: blur(min(var(--lg-glass-blur), 12px))\n      brightness(var(--lg-glass-brightness)) contrast(var(--lg-glass-contrast))\n      saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(min(var(--lg-glass-blur), 12px)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n    transition:\n      transform var(--lg-motion-duration) cubic-bezier(0.2, 0, 0, 1),\n      background-color var(--lg-motion-duration) ease;\n  }\n\n  .lg-accordion-trigger:hover {\n    background: var(--lg-control-surface-hover);\n  }\n\n  .lg-accordion-trigger:disabled {\n    pointer-events: none;\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-accordion-content {\n    margin-top: 0.5rem;\n    overflow: hidden;\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n  }\n\n  .lg-accordion-chevron {\n    flex: none;\n    transition: transform var(--lg-motion-duration) ease;\n  }\n\n  .lg-accordion-trigger[data-state='open'] .lg-accordion-chevron {\n    transform: rotate(180deg);\n  }\n\n  /* ============================================\n     Enter and exit motion. Radix keeps a closing layer mounted until its exit\n     animation ends; the reduced motion guards above cut both to zero. The\n     keyframes move scale and translate, never transform, so they compose\n     with positioning and with a sheet's drag offset.\n     ============================================ */\n\n  @keyframes lg-fade-in {\n    from {\n      opacity: 0;\n    }\n  }\n\n  @keyframes lg-fade-out {\n    to {\n      opacity: 0;\n    }\n  }\n\n  /* A layer grows out of whatever opened it. --lg-pop-x and --lg-pop-y hold the last few pixels\n     of its travel, away from its trigger; a dialog keeps its own centring in them and rises. */\n  @keyframes lg-pop-in {\n    from {\n      opacity: 0;\n      scale: 0.96;\n      translate: var(--lg-pop-x, 0) var(--lg-pop-y, 0);\n    }\n  }\n\n  @keyframes lg-pop-out {\n    to {\n      opacity: 0;\n      scale: 0.96;\n      translate: var(--lg-pop-x, 0) var(--lg-pop-y, 0);\n    }\n  }\n\n  .lg-dialog-content {\n    --lg-pop-x: -50%;\n    --lg-pop-y: calc(-50% + 0.5rem);\n  }\n\n  .lg-popover-content[data-side='bottom'],\n  .lg-menu-content[data-side='bottom'],\n  .lg-select-content[data-side='bottom'],\n  .lg-tooltip-content[data-side='bottom'] {\n    --lg-pop-y: -0.25rem;\n  }\n\n  .lg-popover-content[data-side='top'],\n  .lg-menu-content[data-side='top'],\n  .lg-select-content[data-side='top'],\n  .lg-tooltip-content[data-side='top'] {\n    --lg-pop-y: 0.25rem;\n  }\n\n  .lg-popover-content[data-side='right'],\n  .lg-menu-content[data-side='right'],\n  .lg-select-content[data-side='right'],\n  .lg-tooltip-content[data-side='right'] {\n    --lg-pop-x: -0.25rem;\n  }\n\n  .lg-popover-content[data-side='left'],\n  .lg-menu-content[data-side='left'],\n  .lg-select-content[data-side='left'],\n  .lg-tooltip-content[data-side='left'] {\n    --lg-pop-x: 0.25rem;\n  }\n\n  .lg-dialog-overlay[data-state='open'] {\n    animation: lg-fade-in 320ms var(--lg-ease-out);\n  }\n\n  .lg-dialog-overlay[data-state='closed'] {\n    animation: lg-fade-out 220ms ease-in forwards;\n  }\n\n  .lg-dialog-content[data-state='open'],\n  .lg-popover-content[data-state='open'],\n  .lg-menu-content[data-state='open'],\n  .lg-select-content[data-state='open'] {\n    animation: lg-pop-in 240ms var(--lg-ease-out);\n  }\n\n  .lg-dialog-content[data-state='closed'],\n  .lg-popover-content[data-state='closed'],\n  .lg-menu-content[data-state='closed'],\n  .lg-select-content[data-state='closed'] {\n    animation: lg-pop-out 160ms ease-in forwards;\n  }\n\n  .lg-popover-content {\n    transform-origin: var(--radix-popover-content-transform-origin);\n  }\n\n  .lg-menu-content {\n    transform-origin: var(--radix-dropdown-menu-content-transform-origin);\n  }\n\n  .lg-select-content {\n    transform-origin: var(--radix-select-content-transform-origin);\n  }\n\n  /* A tip settles in from the side it opens toward and fades on the way out. Moving from one\n     trigger to the next skips the entrance, so the tip follows the pointer instead of restarting. */\n  .lg-tooltip-content {\n    transform-origin: var(--radix-tooltip-content-transform-origin);\n  }\n\n  .lg-tooltip-content[data-state='delayed-open'] {\n    animation: lg-pop-in 140ms var(--lg-ease-out);\n  }\n\n  .lg-tooltip-content[data-state='instant-open'] {\n    animation: lg-fade-in 90ms ease-out;\n  }\n\n  .lg-tooltip-content[data-state='closed'] {\n    animation: lg-pop-out 100ms ease-in forwards;\n  }\n\n  /* A toast slides in from the side it will leave toward and back out when it goes: the side of\n     its swipe direction, the right unless the provider says otherwise. The slide is on the list\n     item and the fade on the glass itself: opacity on an ancestor of the frost would turn its blur\n     off until the animation ended. A swipe drags it and a fling sends it on its way. */\n  .lg-toast {\n    --lg-toast-shift-x: 2rem;\n    --lg-toast-shift-y: 0px;\n    --lg-toast-fly-x: calc(100% + 2rem);\n    --lg-toast-fly-y: 0px;\n  }\n\n  .lg-toast[data-swipe-direction='left'] {\n    --lg-toast-shift-x: -2rem;\n    --lg-toast-fly-x: calc(-100% - 2rem);\n  }\n\n  .lg-toast[data-swipe-direction='up'] {\n    --lg-toast-shift-x: 0px;\n    --lg-toast-shift-y: -2rem;\n    --lg-toast-fly-x: 0px;\n    --lg-toast-fly-y: calc(-100% - 2rem);\n  }\n\n  .lg-toast[data-swipe-direction='down'] {\n    --lg-toast-shift-x: 0px;\n    --lg-toast-shift-y: 2rem;\n    --lg-toast-fly-x: 0px;\n    --lg-toast-fly-y: calc(100% + 2rem);\n  }\n\n  @keyframes lg-toast-slide-in {\n    from {\n      translate: var(--lg-toast-shift-x) var(--lg-toast-shift-y);\n    }\n  }\n\n  @keyframes lg-toast-slide-out {\n    to {\n      translate: var(--lg-toast-shift-x) var(--lg-toast-shift-y);\n    }\n  }\n\n  @keyframes lg-toast-swipe-out {\n    from {\n      translate: var(--radix-toast-swipe-end-x, 0px) var(--radix-toast-swipe-end-y, 0px);\n    }\n\n    to {\n      translate: var(--lg-toast-fly-x) var(--lg-toast-fly-y);\n    }\n  }\n\n  .lg-toast[data-state='open'] {\n    animation: lg-toast-slide-in 420ms var(--lg-ease-out);\n  }\n\n  .lg-toast[data-state='open'] > .lg-toast-root {\n    animation: lg-fade-in 300ms ease-out;\n  }\n\n  .lg-toast[data-state='closed'] {\n    animation: lg-toast-slide-out 220ms ease-in forwards;\n  }\n\n  .lg-toast[data-state='closed'] > .lg-toast-root {\n    animation: lg-fade-out 200ms ease-in forwards;\n  }\n\n  .lg-toast[data-swipe='move'] {\n    translate: var(--radix-toast-swipe-move-x, 0px) var(--radix-toast-swipe-move-y, 0px);\n  }\n\n  .lg-toast[data-swipe='cancel'] {\n    translate: 0 0;\n    transition: translate 260ms var(--lg-spring);\n  }\n\n  .lg-toast[data-swipe='end'] {\n    animation: lg-toast-swipe-out 160ms ease-out forwards;\n  }\n\n  /* The card opens and closes like a curtain. Height and padding move, opacity does not, for\n     the same reason as the toast: the card's frost sits in a child layer. */\n  @keyframes lg-accordion-down {\n    from {\n      height: 0;\n      margin-top: 0;\n      padding-block: 0;\n    }\n\n    to {\n      height: var(--radix-accordion-content-height);\n    }\n  }\n\n  @keyframes lg-accordion-up {\n    from {\n      height: var(--radix-accordion-content-height);\n    }\n\n    to {\n      height: 0;\n      margin-top: 0;\n      padding-block: 0;\n    }\n  }\n\n  .lg-accordion-content[data-state='open'] {\n    animation: lg-accordion-down 300ms var(--lg-ease-out);\n  }\n\n  .lg-accordion-content[data-state='closed'] {\n    animation: lg-accordion-up 220ms ease-in forwards;\n  }\n\n  @keyframes lg-avatar-in {\n    from {\n      opacity: 0;\n      scale: 1.06;\n    }\n  }\n\n  .lg-avatar-image {\n    animation: lg-avatar-in 260ms var(--lg-ease-out);\n  }\n\n  /* ============================================\n     Switch: a pill track and a white knob that turns to clear glass while it\n     is held, the way the system toggle does\n     ============================================ */\n\n  .lg-switch {\n    --lg-switch-width: 3.75rem;\n    --lg-switch-height: 1.75rem;\n    --lg-switch-thumb-width: 2.25rem;\n    --lg-switch-thumb-height: 1.5rem;\n    --lg-switch-inset: 0.125rem;\n    --lg-switch-direction: 1;\n    /* Held, the knob swells past the track, and the lens shows what lies under it at\n       this share of its size. */\n    --lg-switch-swell: 1.57;\n    --lg-switch-minify: 0.85;\n    /* The colour of the track under the knob, which its rim and glow pick up. */\n    --lg-switch-lens-tint: var(--lg-track);\n\n    position: relative;\n    display: inline-flex;\n    flex: none;\n    align-items: center;\n    box-sizing: border-box;\n    width: var(--lg-switch-width);\n    height: var(--lg-switch-height);\n    margin: 0;\n    padding: 0 var(--lg-switch-inset);\n    border: none;\n    border-radius: 9999px;\n    background: var(--lg-track);\n    box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);\n    cursor: pointer;\n    -webkit-tap-highlight-color: transparent;\n    transition: background-color 250ms ease;\n  }\n\n  .lg-switch[data-size='sm'] {\n    --lg-switch-width: 2.875rem;\n    --lg-switch-height: 1.5rem;\n    --lg-switch-thumb-width: 1.75rem;\n    --lg-switch-thumb-height: 1.25rem;\n  }\n\n  [dir='rtl'] .lg-switch,\n  .lg-switch[dir='rtl'] {\n    --lg-switch-direction: -1;\n  }\n\n  .lg-switch[data-state='checked'] {\n    --lg-switch-lens-tint: var(--lg-switch-on);\n\n    background: var(--lg-switch-on);\n  }\n\n  .lg-switch:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-switch[data-disabled] {\n    cursor: not-allowed;\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-switch-thumb {\n    position: relative;\n    display: block;\n    flex: none;\n    width: var(--lg-switch-thumb-width);\n    height: var(--lg-switch-thumb-height);\n    border-radius: 9999px;\n    background: var(--lg-knob);\n    box-shadow: var(--lg-knob-shadow);\n    pointer-events: none;\n    translate: 0 0;\n    transition:\n      translate 420ms var(--lg-spring),\n      scale 320ms var(--lg-spring),\n      background-color 180ms ease,\n      box-shadow 180ms ease;\n  }\n\n  .lg-switch[data-state='checked'] .lg-switch-thumb {\n    translate: calc(\n        (var(--lg-switch-width) - var(--lg-switch-thumb-width) - var(--lg-switch-inset) * 2) *\n          var(--lg-switch-direction)\n      )\n      0;\n  }\n\n  /* Held, the knob swells past the track into a lens of clear glass. Through it the\n     track and what lies around it show a little smaller, and they bend at the rim. Lit\n     from the top left, the rim takes the track's colour, heavier along the bottom and\n     right where the light leaves the glass, and glints on the left. A soft glow in the\n     same colour lies around it. After a flip it stays this size and glass while it\n     slides, and only then shrinks back. */\n  .lg-switch:is(:active, [data-lg-moving]):not([data-disabled]) .lg-switch-thumb {\n    background: rgb(255 255 255 / 0);\n    box-shadow:\n      0.4px 0.8px 0.8px rgb(0 0 0 / 0.14),\n      0 2px 5px rgb(0 0 0 / 0.06),\n      0 0 12px 3px color-mix(in srgb, var(--lg-switch-lens-tint) 18%, transparent);\n    scale: var(--lg-switch-swell);\n  }\n\n  .lg-switch-thumb::after {\n    content: '';\n    position: absolute;\n    inset: 0;\n    z-index: 1;\n    border-radius: inherit;\n    pointer-events: none;\n    opacity: 0;\n    /* Gradients and unblurred shadows only: WebKit draws a blurred inset shadow at low\n       resolution, which the swell would show as blocks. */\n    background:\n      radial-gradient(19% 7% at 15% 20%, rgb(255 255 255 / 0.95), rgb(255 255 255 / 0)),\n      radial-gradient(19% 7% at 15% 80%, rgb(255 255 255 / 0.85), rgb(255 255 255 / 0)),\n      linear-gradient(\n        to top,\n        color-mix(in srgb, var(--lg-switch-lens-tint) 18%, transparent),\n        transparent 32%\n      ),\n      linear-gradient(rgb(0 0 0 / 0.05), transparent 14%),\n      linear-gradient(rgb(255 255 255 / 0.07), rgb(255 255 255 / 0.02));\n    box-shadow:\n      inset 0.35px 0.6px color-mix(in srgb, var(--lg-switch-lens-tint) 80%, black),\n      inset 0 0 0 0.4px var(--lg-switch-lens-tint),\n      inset -0.6px -1.4px oklch(from var(--lg-switch-lens-tint) calc(l + 0.1) c h),\n      inset -0.9px -2.2px\n        color-mix(\n          in srgb,\n          oklch(from var(--lg-switch-lens-tint) calc(l + 0.18) c h) 45%,\n          transparent\n        );\n    transition:\n      opacity 180ms ease,\n      box-shadow 250ms ease;\n  }\n\n  .lg-switch:is(:active, [data-lg-moving]):not([data-disabled]) .lg-switch-thumb::after {\n    opacity: 1;\n  }\n\n  /* The lens reaches a margin past the knob so its rim has track to bend in, and cuts the\n     filtered view back to the knob, half a pixel inside so the rim's highlight covers the\n     copy's edge. A clip on the filtered element itself would let Chromium draw the\n     filter's margin outside it. It is hidden at rest, so the filter only runs while the\n     knob is glass. */\n  .lg-switch-lens {\n    position: absolute;\n    top: calc(var(--lg-switch-lens-margin, 0px) * -1);\n    left: calc(var(--lg-switch-lens-margin, 0px) * -1);\n    width: calc(var(--lg-switch-thumb-width) + var(--lg-switch-lens-margin, 0px) * 2);\n    height: calc(var(--lg-switch-thumb-height) + var(--lg-switch-lens-margin, 0px) * 2);\n    clip-path: inset(calc(var(--lg-switch-lens-margin, 0px) + 0.5px) round 9999px);\n    pointer-events: none;\n    opacity: 0;\n    visibility: hidden;\n    transition:\n      opacity 180ms ease,\n      visibility 0s linear 180ms;\n  }\n\n  /* The view is laid out at --lg-switch-lens-scale times the lens and scaled back down on\n     its own layer, so engines that filter at layout resolution bend it smoothly too.\n     WebKit draws a filtered layer from its overflow origin, so the view clips its own\n     overflow on a composited layer. It is filled with the colour behind the switch,\n     which the component reads when the knob is pressed, so the real track under the\n     knob stays hidden. */\n  .lg-switch-lens-view {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: calc(100% * var(--lg-switch-lens-scale, 1));\n    height: calc(100% * var(--lg-switch-lens-scale, 1));\n    overflow: hidden;\n    background: var(--lg-switch-lens-backdrop, var(--lg-bg));\n    transform-origin: 0 0;\n    transform: translateZ(0) scale(calc(1 / var(--lg-switch-lens-scale, 1)));\n  }\n\n  .lg-switch:is(:active, [data-lg-moving]):not([data-disabled]) .lg-switch-lens {\n    opacity: 1;\n    visibility: visible;\n    transition-delay: 0s;\n  }\n\n  /* The scene under the lens, around the knob's centre. It undoes the knob's swell with\n     the same timing, and shows what lies under the knob at --lg-switch-minify of its\n     size. */\n  .lg-switch-lens-scene {\n    position: absolute;\n    inset: 0;\n    scale: var(--lg-switch-minify);\n    transition: scale 320ms var(--lg-spring);\n  }\n\n  .lg-switch:is(:active, [data-lg-moving]):not([data-disabled]) .lg-switch-lens-scene {\n    scale: calc(var(--lg-switch-minify) / var(--lg-switch-swell));\n  }\n\n  /* The track as it lies under the knob, at the lens's scale: the knob's slide is undone\n     with the same timing, so the copy stays put while the knob passes over it. */\n  .lg-switch-lens-track {\n    position: absolute;\n    inset-inline-start: calc(\n      (var(--lg-switch-lens-margin, 0px) - var(--lg-switch-inset)) * var(--lg-switch-lens-scale, 1)\n    );\n    top: calc(\n      (var(--lg-switch-lens-margin, 0px) -\n          (var(--lg-switch-height) - var(--lg-switch-thumb-height)) / 2) *\n        var(--lg-switch-lens-scale, 1)\n    );\n    width: calc(var(--lg-switch-width) * var(--lg-switch-lens-scale, 1));\n    height: calc(var(--lg-switch-height) * var(--lg-switch-lens-scale, 1));\n    border-radius: 9999px;\n    background: var(--lg-track);\n    box-shadow: inset 0 calc(var(--lg-switch-lens-scale, 1) * 1px)\n      calc(var(--lg-switch-lens-scale, 1) * 2px) rgb(0 0 0 / 0.1);\n    translate: 0 0;\n    transition:\n      translate 420ms var(--lg-spring),\n      background-color 250ms ease;\n  }\n\n  .lg-switch[data-state='checked'] .lg-switch-lens-track {\n    background: var(--lg-switch-on);\n    translate: calc(\n        (var(--lg-switch-width) - var(--lg-switch-thumb-width) - var(--lg-switch-inset) * 2) *\n          var(--lg-switch-direction) * var(--lg-switch-lens-scale, 1) * -1\n      )\n      0;\n  }\n\n  /* ============================================\n     Slider: a thin track, an accent fill and the same capsule knob\n     ============================================ */\n\n  .lg-slider {\n    --lg-slider-thumb-width: 2.375rem;\n    --lg-slider-thumb-height: 1.5rem;\n    --lg-slider-track-size: 0.375rem;\n\n    position: relative;\n    display: flex;\n    align-items: center;\n    width: 100%;\n    height: var(--lg-slider-thumb-height);\n    touch-action: none;\n    user-select: none;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  .lg-slider[data-orientation='vertical'] {\n    flex-direction: column;\n    width: var(--lg-slider-thumb-height);\n    height: 100%;\n    min-height: 8rem;\n  }\n\n  .lg-slider[data-disabled] {\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  .lg-slider-track {\n    position: relative;\n    flex-grow: 1;\n    overflow: hidden;\n    border-radius: 9999px;\n    background: var(--lg-track);\n  }\n\n  .lg-slider-track[data-orientation='horizontal'] {\n    height: var(--lg-slider-track-size);\n  }\n\n  .lg-slider-track[data-orientation='vertical'] {\n    width: var(--lg-slider-track-size);\n  }\n\n  .lg-slider-range {\n    position: absolute;\n    border-radius: inherit;\n    background: var(--lg-accent);\n  }\n\n  .lg-slider-range[data-orientation='horizontal'] {\n    height: 100%;\n  }\n\n  .lg-slider-range[data-orientation='vertical'] {\n    width: 100%;\n  }\n\n  .lg-slider-thumb {\n    display: block;\n    width: var(--lg-slider-thumb-width);\n    height: var(--lg-slider-thumb-height);\n    border-radius: 9999px;\n    background: var(--lg-knob);\n    box-shadow: var(--lg-knob-shadow);\n    cursor: grab;\n    /* Script stretches a dragged knob along the track with its speed. */\n    transform: scale(var(--lg-stretch-x, 1), var(--lg-stretch-y, 1));\n    transition:\n      scale 320ms var(--lg-spring),\n      background-color 180ms ease,\n      box-shadow 180ms ease;\n  }\n\n  .lg-slider[data-orientation='vertical'] .lg-slider-thumb {\n    width: var(--lg-slider-thumb-height);\n    height: var(--lg-slider-thumb-width);\n  }\n\n  .lg-slider-thumb:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-slider[data-disabled] .lg-slider-thumb {\n    cursor: not-allowed;\n  }\n\n  /* Radix focuses the thumb it drags, so only that one turns to glass. */\n  .lg-slider:not([data-disabled]):active .lg-slider-thumb:focus {\n    background: rgb(255 255 255 / 0.16);\n    box-shadow:\n      inset 0 0 0 0.5px rgb(255 255 255 / 0.55),\n      inset 1.5px 2px 1px -1px rgb(255 255 255 / 0.9),\n      inset -1.5px -2px 1px -1px rgb(255 255 255 / 0.55),\n      inset 0 0 8px rgb(255 255 255 / 0.28),\n      0 4px 14px rgb(0 0 0 / 0.2);\n    cursor: grabbing;\n    scale: 1.3 1.4;\n  }\n\n  /* ============================================\n     Segmented control: one raised bubble slides between equal segments.\n     :has() counts the segments and finds the checked one, so the bubble is\n     placed before any script runs. Up to six segments.\n     ============================================ */\n\n  .lg-segmented {\n    --lg-segmented-pad: 0.1875rem;\n\n    display: grid;\n    grid-auto-columns: minmax(0, 1fr);\n    grid-auto-flow: column;\n    width: 100%;\n    padding: var(--lg-segmented-pad);\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n  }\n\n  .lg-segmented:has(> button:nth-of-type(2):last-of-type) {\n    --lg-segmented-count: 2;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(3):last-of-type) {\n    --lg-segmented-count: 3;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(4):last-of-type) {\n    --lg-segmented-count: 4;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(5):last-of-type) {\n    --lg-segmented-count: 5;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(6):last-of-type) {\n    --lg-segmented-count: 6;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(2)[data-state='checked']) {\n    --lg-segmented-index: 1;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(3)[data-state='checked']) {\n    --lg-segmented-index: 2;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(4)[data-state='checked']) {\n    --lg-segmented-index: 3;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(5)[data-state='checked']) {\n    --lg-segmented-index: 4;\n  }\n\n  .lg-segmented:has(> button:nth-of-type(6)[data-state='checked']) {\n    --lg-segmented-index: 5;\n  }\n\n  .lg-segmented > .lg-segmented-indicator {\n    position: absolute;\n    top: var(--lg-segmented-pad);\n    bottom: var(--lg-segmented-pad);\n    left: var(--lg-segmented-pad);\n    z-index: 1;\n    width: calc((100% - var(--lg-segmented-pad) * 2) / var(--lg-segmented-count, 1));\n    border-radius: 9999px;\n    background: rgb(255 255 255 / 0.14);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.22),\n      inset 0 -1px 0 rgb(0 0 0 / 0.12),\n      0 1px 3px rgb(0 0 0 / 0.24),\n      0 4px 12px -4px rgb(0 0 0 / 0.4);\n    pointer-events: none;\n    translate: calc(var(--lg-segmented-index, 0) * 100%) 0;\n    transition:\n      translate 460ms var(--lg-spring),\n      opacity 200ms ease;\n  }\n\n  [dir='rtl'] .lg-segmented > .lg-segmented-indicator {\n    right: var(--lg-segmented-pad);\n    left: auto;\n    translate: calc(var(--lg-segmented-index, 0) * -100%) 0;\n  }\n\n  .lg-segmented:not(:has(> button[data-state='checked'])) > .lg-segmented-indicator {\n    opacity: 0;\n  }\n\n  /* Light glass on a white page would hide the white bubble, so the track\n     sinks into a faint slate well. */\n  [data-lg-theme='light']:not([data-lg-reduce-transparency='1']) .lg-segmented::before {\n    background-color: rgb(100 108 140 / 0.12);\n  }\n\n  [data-lg-theme='light'] .lg-segmented > .lg-segmented-indicator {\n    background: rgb(255 255 255 / 0.9);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.9),\n      0 1px 3px rgb(0 0 0 / 0.1),\n      0 4px 12px -4px rgb(0 0 0 / 0.16);\n  }\n\n  /* Once measured, script owns the bubble. It springs each edge toward the\n     selected item and writes --lg-lift while the bubble travels or is held,\n     which turns it into clearer glass that floats higher. */\n  .lg-segmented[data-lg-indicator='ready'] {\n    touch-action: pan-y;\n  }\n\n  .lg-segmented[data-lg-indicator='ready'] > .lg-segmented-indicator {\n    --lg-bubble-alpha: 0.14;\n  }\n\n  [data-lg-theme='light'] .lg-segmented[data-lg-indicator='ready'] > .lg-segmented-indicator {\n    --lg-bubble-alpha: 0.9;\n  }\n\n  .lg-segmented[data-lg-indicator='ready'] > .lg-segmented-indicator,\n  .lg-tabs-list[data-lg-indicator='ready'] > .lg-tabs-indicator {\n    --lg-bubble-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.22), inset 0 -1px 0 rgb(0 0 0 / 0.12),\n      0 1px 3px rgb(0 0 0 / 0.24), 0 4px 12px -4px rgb(0 0 0 / 0.4);\n\n    top: 0;\n    right: auto;\n    bottom: auto;\n    left: 0;\n    /* A bead of glass: lit from above, clearer while it is lifted, and it\n       brightens and sharpens what lies under it. */\n    background: linear-gradient(\n      180deg,\n      rgb(255 255 255 / calc((var(--lg-bubble-alpha) + 0.08) * (1 - var(--lg-lift, 0) * 0.55))),\n      rgb(255 255 255 / calc(var(--lg-bubble-alpha) * (1 - var(--lg-lift, 0) * 0.55)))\n    );\n    -webkit-backdrop-filter: blur(2px) saturate(1.5)\n      brightness(calc(1.08 + var(--lg-lift, 0) * 0.14));\n    backdrop-filter: blur(2px) saturate(1.5) brightness(calc(1.08 + var(--lg-lift, 0) * 0.14));\n    box-shadow:\n      inset 0 0 0 calc(var(--lg-lift, 0) * 0.75px) rgb(255 255 255 / calc(var(--lg-lift, 0) * 0.5)),\n      inset 0 0 calc(var(--lg-lift, 0) * 12px) rgb(255 255 255 / calc(var(--lg-lift, 0) * 0.2)),\n      0 calc(var(--lg-lift, 0) * 8px) calc(var(--lg-lift, 0) * 22px)\n        rgb(0 0 0 / calc(var(--lg-lift, 0) * 0.25)),\n      var(--lg-bubble-shadow);\n    transition: opacity 200ms ease;\n  }\n\n  [data-lg-theme='light'] .lg-segmented[data-lg-indicator='ready'] > .lg-segmented-indicator,\n  [data-lg-theme='light'] .lg-tabs-list[data-lg-indicator='ready'] > .lg-tabs-indicator {\n    --lg-bubble-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.9), 0 1px 3px rgb(0 0 0 / 0.1),\n      0 4px 12px -4px rgb(0 0 0 / 0.16);\n  }\n\n  :is(.lg-segmented-indicator, .lg-tabs-indicator)[data-lg-empty] {\n    opacity: 0;\n  }\n\n  .lg-segmented[data-lg-dragging] {\n    cursor: grabbing;\n    -webkit-user-select: none;\n    user-select: none;\n  }\n\n  .lg-segmented-indicator::after {\n    content: '';\n    position: absolute;\n    inset: 0;\n    padding: var(--lg-rim-width);\n    border-radius: inherit;\n    pointer-events: none;\n    background: linear-gradient(\n      to bottom right,\n      rgb(255 255 255 / var(--lg-specular-peak)),\n      rgb(255 255 255 / var(--lg-specular-floor)) 38% 62%,\n      rgb(255 255 255 / var(--lg-specular-counter))\n    );\n    -webkit-mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    -webkit-mask-composite: xor;\n    mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    mask-composite: exclude;\n  }\n\n  [data-lg-reduce-transparency='1'] :is(.lg-segmented-indicator, .lg-tabs-indicator)::after {\n    background: var(--lg-border);\n  }\n\n  [data-lg-reduce-transparency='1'] .lg-segmented > .lg-segmented-indicator,\n  [data-lg-reduce-transparency='1']\n    :is(.lg-segmented, .lg-tabs-list)[data-lg-indicator='ready']\n    > :is(.lg-segmented-indicator, .lg-tabs-indicator) {\n    background: var(--lg-control-surface-hover);\n  }\n\n  .lg-segmented-item {\n    position: relative;\n    z-index: 2;\n    display: flex;\n    min-width: 0;\n    align-items: center;\n    justify-content: center;\n    gap: 0.375rem;\n    margin: 0;\n    padding: 0.4375rem 0.875rem;\n    border: none;\n    border-radius: 9999px;\n    background: transparent;\n    color: var(--lg-muted-foreground);\n    font: inherit;\n    font-weight: 500;\n    white-space: nowrap;\n    cursor: pointer;\n    -webkit-tap-highlight-color: transparent;\n    transition: color var(--lg-motion-duration) ease;\n  }\n\n  .lg-segmented-item:hover:not([data-disabled]),\n  .lg-segmented-item[data-state='checked'] {\n    color: var(--lg-foreground);\n  }\n\n  .lg-segmented-item:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: -2px;\n  }\n\n  .lg-segmented-item[data-disabled] {\n    cursor: not-allowed;\n    opacity: var(--lg-disabled-opacity);\n  }\n\n  /* ============================================\n     Badge: a small frosted capsule. Every tone keeps 4.5:1 text contrast\n     over a pure black or pure white backdrop in both themes.\n     ============================================ */\n\n  .lg-badge {\n    --lg-badge-fill: 44 44 46;\n    --lg-badge-ink: #f5f5f7;\n    --lg-badge-tint: 142 142 147;\n    --lg-badge-highlight: rgb(255 255 255 / 0.12);\n\n    display: inline-flex;\n    align-items: center;\n    gap: 0.375rem;\n    box-sizing: border-box;\n    padding: 0.1875rem 0.625rem;\n    border-radius: 9999px;\n    background: rgb(var(--lg-badge-fill) / 0.88);\n    box-shadow:\n      inset 0 1px 0 var(--lg-badge-highlight),\n      inset 0 0 0 0.5px rgb(var(--lg-badge-tint) / 0.4);\n    color: var(--lg-badge-ink);\n    font-size: 0.75rem;\n    line-height: 1rem;\n    font-weight: 600;\n    white-space: nowrap;\n    -webkit-backdrop-filter: blur(min(var(--lg-glass-blur), 12px))\n      brightness(var(--lg-glass-brightness)) contrast(var(--lg-glass-contrast))\n      saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(min(var(--lg-glass-blur), 12px)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n  }\n\n  .lg-badge[data-tone='blue'] {\n    --lg-badge-fill: 13 44 82;\n    --lg-badge-ink: #b2d6ff;\n    --lg-badge-tint: 0 103 217;\n  }\n\n  .lg-badge[data-tone='green'] {\n    --lg-badge-fill: 22 60 48;\n    --lg-badge-ink: #7cebc0;\n    --lg-badge-tint: 51 214 159;\n  }\n\n  .lg-badge[data-tone='orange'] {\n    --lg-badge-fill: 70 54 22;\n    --lg-badge-ink: #ffd27a;\n    --lg-badge-tint: 255 189 56;\n  }\n\n  .lg-badge[data-tone='red'] {\n    --lg-badge-fill: 76 30 44;\n    --lg-badge-ink: #ffa6b7;\n    --lg-badge-tint: 255 92 122;\n  }\n\n  .lg-badge[data-tone='purple'] {\n    --lg-badge-fill: 44 36 92;\n    --lg-badge-ink: #c7b8ff;\n    --lg-badge-tint: 155 128 255;\n  }\n\n  [data-lg-theme='light'] .lg-badge {\n    --lg-badge-fill: 242 242 244;\n    --lg-badge-ink: #222224;\n    --lg-badge-highlight: rgb(255 255 255 / 0.9);\n  }\n\n  [data-lg-theme='light'] .lg-badge[data-tone='blue'] {\n    --lg-badge-fill: 229 241 255;\n    --lg-badge-ink: #0054b3;\n    --lg-badge-tint: 0 103 217;\n  }\n\n  [data-lg-theme='light'] .lg-badge[data-tone='green'] {\n    --lg-badge-fill: 224 247 238;\n    --lg-badge-ink: #08603f;\n    --lg-badge-tint: 20 168 116;\n  }\n\n  [data-lg-theme='light'] .lg-badge[data-tone='orange'] {\n    --lg-badge-fill: 255 243 219;\n    --lg-badge-ink: #824c00;\n    --lg-badge-tint: 232 150 12;\n  }\n\n  [data-lg-theme='light'] .lg-badge[data-tone='red'] {\n    --lg-badge-fill: 255 229 236;\n    --lg-badge-ink: #a8113a;\n    --lg-badge-tint: 232 52 90;\n  }\n\n  [data-lg-theme='light'] .lg-badge[data-tone='purple'] {\n    --lg-badge-fill: 237 232 255;\n    --lg-badge-ink: #5532d0;\n    --lg-badge-tint: 106 76 240;\n  }\n\n  .lg-badge-dot {\n    flex: none;\n    width: 0.375rem;\n    height: 0.375rem;\n    border-radius: 9999px;\n    background: rgb(var(--lg-badge-tint));\n  }\n\n  /* ============================================\n     Dock: a glass shelf under a row of icons that name themselves on hover\n     ============================================ */\n\n  .lg-dock {\n    --lg-dock-size: 3rem;\n    --lg-dock-gap: 0.5rem;\n    --lg-dock-pad: 0.625rem;\n\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    gap: var(--lg-dock-gap);\n    box-sizing: border-box;\n    height: calc(var(--lg-dock-size) + var(--lg-dock-pad) * 2);\n    padding: var(--lg-dock-pad) calc(var(--lg-dock-pad) + 0.125rem);\n    isolation: isolate;\n  }\n\n  .lg-dock > .lg-dock-shelf {\n    --lg-panel-radius: calc(var(--lg-dock-size) * 0.24 + var(--lg-dock-pad));\n\n    position: absolute;\n    inset: 0;\n    z-index: 0;\n    pointer-events: none;\n  }\n\n  .lg-dock-item {\n    position: relative;\n    z-index: 1;\n    display: flex;\n    flex: none;\n    align-items: center;\n    justify-content: center;\n    box-sizing: border-box;\n    width: calc(var(--lg-dock-size) * var(--lg-dock-scale, 1));\n    height: calc(var(--lg-dock-size) * var(--lg-dock-scale, 1));\n    margin: 0;\n    padding: 0;\n    border: none;\n    border-radius: 22.5%;\n    /* Nothing is painted behind the artwork, so an icon that bounces or has clear edges shows the\n       shelf. */\n    background: none;\n    color: inherit;\n    cursor: pointer;\n    /* The hover label sits outside the button, where a double click would otherwise select it\n       even while it is hidden. */\n    user-select: none;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  /* A consumer's icon dimensions take precedence over the full-size app artwork. */\n  .lg-dock-item > :where(:not(.lg-dock-label, .lg-dock-dot)) {\n    width: 100%;\n    height: 100%;\n  }\n\n  .lg-dock-item:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 3px;\n  }\n\n  .lg-dock-label {\n    position: absolute;\n    bottom: calc(100% + 0.625rem);\n    left: 50%;\n    z-index: 2;\n    padding: 0.375rem 0.625rem;\n    border-radius: 0.625rem;\n    background: var(--lg-overlay-surface);\n    box-shadow:\n      inset 0 0 0 0.5px var(--lg-border),\n      var(--lg-shadow);\n    color: var(--lg-foreground);\n    font-size: 0.8125rem;\n    line-height: 1.125rem;\n    font-weight: 500;\n    white-space: nowrap;\n    pointer-events: none;\n    opacity: 0;\n    translate: -50% 0.25rem;\n    transition:\n      opacity 150ms ease,\n      translate 220ms var(--lg-ease-out);\n    -webkit-backdrop-filter: blur(min(var(--lg-glass-blur), 16px))\n      brightness(var(--lg-glass-brightness)) contrast(var(--lg-glass-contrast))\n      saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(min(var(--lg-glass-blur), 16px)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n  }\n\n  .lg-dock-item:hover > .lg-dock-label,\n  .lg-dock-item:focus-visible > .lg-dock-label {\n    opacity: 1;\n    translate: -50% 0;\n  }\n\n  .lg-dock-dot {\n    position: absolute;\n    bottom: calc(var(--lg-dock-pad) * -1 + 0.1875rem);\n    left: 50%;\n    width: 0.25rem;\n    height: 0.25rem;\n    border-radius: 9999px;\n    background: var(--lg-foreground);\n    opacity: 0;\n    translate: -50% 0;\n    transition: opacity 200ms ease;\n  }\n\n  .lg-dock-item[data-active='true'] > .lg-dock-dot {\n    opacity: 0.75;\n  }\n\n  .lg-dock-separator {\n    position: relative;\n    z-index: 1;\n    flex: none;\n    align-self: stretch;\n    width: 1px;\n    margin: 0.375rem 0.25rem;\n    background: var(--lg-border);\n  }\n\n  /* ============================================\n     Sheet: a glass card that slides in from an edge. A bottom sheet can be\n     dragged down by its grabber to dismiss it.\n     ============================================ */\n\n  @keyframes lg-sheet-in-bottom {\n    from {\n      translate: 0 calc(100% + 1rem);\n    }\n  }\n\n  @keyframes lg-sheet-out-bottom {\n    to {\n      translate: 0 calc(100% + 1rem);\n    }\n  }\n\n  @keyframes lg-sheet-in-top {\n    from {\n      translate: 0 calc(-100% - 1rem);\n    }\n  }\n\n  @keyframes lg-sheet-out-top {\n    to {\n      translate: 0 calc(-100% - 1rem);\n    }\n  }\n\n  @keyframes lg-sheet-in-left {\n    from {\n      translate: calc(-100% - 1rem) 0;\n    }\n  }\n\n  @keyframes lg-sheet-out-left {\n    to {\n      translate: calc(-100% - 1rem) 0;\n    }\n  }\n\n  @keyframes lg-sheet-in-right {\n    from {\n      translate: calc(100% + 1rem) 0;\n    }\n  }\n\n  @keyframes lg-sheet-out-right {\n    to {\n      translate: calc(100% + 1rem) 0;\n    }\n  }\n\n  .lg-sheet-content {\n    --lg-panel-radius: 2.5rem;\n\n    position: fixed;\n    z-index: 50;\n    display: flex;\n    flex-direction: column;\n    box-sizing: border-box;\n    overflow-x: hidden;\n    overflow-y: auto;\n    overscroll-behavior: contain;\n  }\n\n  .lg-sheet-content:focus {\n    outline: none;\n  }\n\n  .lg-sheet-content[data-side='bottom'],\n  .lg-sheet-content[data-side='top'] {\n    right: 0.5rem;\n    left: 0.5rem;\n    max-width: 40rem;\n    max-height: calc(100dvh - 3rem);\n    margin-inline: auto;\n  }\n\n  .lg-sheet-content[data-side='bottom'] {\n    bottom: 0.5rem;\n  }\n\n  .lg-sheet-content[data-side='top'] {\n    top: 0.5rem;\n  }\n\n  .lg-sheet-content[data-side='left'],\n  .lg-sheet-content[data-side='right'] {\n    top: 0.5rem;\n    bottom: 0.5rem;\n    width: min(24rem, calc(100vw - 1rem));\n  }\n\n  .lg-sheet-content[data-side='left'] {\n    left: 0.5rem;\n  }\n\n  .lg-sheet-content[data-side='right'] {\n    right: 0.5rem;\n  }\n\n  .lg-sheet-content[data-side='bottom'][data-state='open'] {\n    animation: lg-sheet-in-bottom 500ms var(--lg-ease-out);\n  }\n\n  .lg-sheet-content[data-side='bottom'][data-state='closed'] {\n    animation: lg-sheet-out-bottom 300ms var(--lg-ease-out) forwards;\n  }\n\n  .lg-sheet-content[data-side='top'][data-state='open'] {\n    animation: lg-sheet-in-top 500ms var(--lg-ease-out);\n  }\n\n  .lg-sheet-content[data-side='top'][data-state='closed'] {\n    animation: lg-sheet-out-top 300ms var(--lg-ease-out) forwards;\n  }\n\n  .lg-sheet-content[data-side='left'][data-state='open'] {\n    animation: lg-sheet-in-left 500ms var(--lg-ease-out);\n  }\n\n  .lg-sheet-content[data-side='left'][data-state='closed'] {\n    animation: lg-sheet-out-left 300ms var(--lg-ease-out) forwards;\n  }\n\n  .lg-sheet-content[data-side='right'][data-state='open'] {\n    animation: lg-sheet-in-right 500ms var(--lg-ease-out);\n  }\n\n  .lg-sheet-content[data-side='right'][data-state='closed'] {\n    animation: lg-sheet-out-right 300ms var(--lg-ease-out) forwards;\n  }\n\n  .lg-sheet-handle {\n    display: flex;\n    flex: none;\n    justify-content: center;\n    margin: -1rem -1.5rem 0.25rem;\n    padding: 0.625rem 0 0.75rem;\n    cursor: grab;\n    touch-action: none;\n  }\n\n  .lg-sheet-handle:active {\n    cursor: grabbing;\n  }\n\n  .lg-sheet-handle::before {\n    content: '';\n    width: 2.25rem;\n    height: 0.3125rem;\n    border-radius: 9999px;\n    background: var(--lg-muted-foreground);\n    opacity: 0.5;\n  }\n\n  /* ============================================\n     Menu: a frosted list with a soft lozenge under the highlighted row\n     ============================================ */\n\n  .lg-menu-content {\n    --lg-panel-radius: 1.125rem;\n\n    z-index: 50;\n    box-sizing: border-box;\n    min-width: 14rem;\n    max-width: calc(100vw - 1rem);\n    max-height: var(--radix-dropdown-menu-content-available-height, 100dvh);\n    overflow-x: hidden;\n    overflow-y: auto;\n    padding: 0.375rem;\n    font-size: 0.875rem;\n    line-height: calc(1.25 / 0.875);\n    transform-origin: var(--radix-dropdown-menu-content-transform-origin);\n  }\n\n  .lg-menu-content:focus {\n    outline: none;\n  }\n\n  .lg-menu-item {\n    position: relative;\n    display: flex;\n    align-items: center;\n    gap: 0.625rem;\n    min-height: 2.25rem;\n    box-sizing: border-box;\n    padding: 0.4375rem 0.75rem;\n    border-radius: 0.75rem;\n    color: var(--lg-foreground);\n    cursor: default;\n    outline: none;\n    user-select: none;\n  }\n\n  .lg-menu-item > svg {\n    flex: none;\n    width: 1rem;\n    height: 1rem;\n  }\n\n  .lg-menu-item[data-highlighted],\n  .lg-menu-item[data-state='open'] {\n    background: rgb(255 255 255 / 0.14);\n  }\n\n  [data-lg-theme='light'] .lg-menu-item[data-highlighted],\n  [data-lg-theme='light'] .lg-menu-item[data-state='open'] {\n    background: rgb(0 0 0 / 0.06);\n  }\n\n  .lg-menu-item[data-disabled] {\n    opacity: var(--lg-disabled-opacity);\n    pointer-events: none;\n  }\n\n  .lg-menu-item[data-tone='destructive'] {\n    color: var(--lg-invalid);\n  }\n\n  .lg-menu-item--inset {\n    padding-inline-start: 2rem;\n  }\n\n  .lg-menu-indicator {\n    position: absolute;\n    inset-inline-start: 0.5rem;\n    display: inline-flex;\n    width: 1rem;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .lg-menu-indicator > svg {\n    width: 1rem;\n    height: 1rem;\n  }\n\n  .lg-menu-label {\n    padding: 0.375rem 0.75rem 0.25rem;\n    color: var(--lg-muted-foreground);\n    font-size: 0.75rem;\n    line-height: 1rem;\n    font-weight: 600;\n    user-select: none;\n  }\n\n  .lg-menu-separator {\n    height: 1px;\n    margin: 0.375rem 0.625rem;\n    background: var(--lg-border);\n  }\n\n  .lg-menu-shortcut {\n    margin-inline-start: auto;\n    padding-inline-start: 1.5rem;\n    color: var(--lg-muted-foreground);\n    font-size: 0.75rem;\n    letter-spacing: 0.06em;\n  }\n\n  .lg-menu-chevron {\n    margin-inline-start: auto;\n    color: var(--lg-muted-foreground);\n  }\n\n  [dir='rtl'] .lg-menu-chevron {\n    scale: -1 1;\n  }\n\n  /* ============================================\n     Lens: a draggable magnifier. Its view repeats the stage content, zoomed\n     around the lens center and bent at the rim by the generated refraction\n     filter where the engine supports it.\n     ============================================ */\n\n  .lg-lens-stage {\n    position: relative;\n    /* Focus must not scroll the padded lens view and shift the content beneath it. */\n    overflow: clip;\n    isolation: isolate;\n  }\n\n  .lg-lens {\n    --lg-lens-size: 10rem;\n    --lg-lens-zoom: 1.6;\n    --lg-lens-fill: var(--lg-bg);\n    --lg-lens-margin: 0px;\n\n    position: absolute;\n    top: 0;\n    left: 0;\n    z-index: 2;\n    width: var(--lg-lens-size);\n    height: var(--lg-lens-size);\n    border-radius: 50%;\n    box-shadow:\n      0 14px 34px -10px rgb(0 0 0 / 0.5),\n      0 2px 8px rgb(0 0 0 / 0.22);\n    cursor: grab;\n    touch-action: none;\n    -webkit-tap-highlight-color: transparent;\n    translate: calc(var(--lg-lens-x, 0px) - var(--lg-lens-size) / 2)\n      calc(var(--lg-lens-y, 0px) - var(--lg-lens-size) / 2);\n    transition:\n      scale 320ms var(--lg-spring),\n      box-shadow 200ms ease;\n  }\n\n  .lg-lens:active {\n    box-shadow:\n      0 22px 44px -12px rgb(0 0 0 / 0.55),\n      0 4px 12px rgb(0 0 0 / 0.24);\n    cursor: grabbing;\n    scale: 1.04;\n  }\n\n  .lg-lens:focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 3px;\n  }\n\n  /* The view reaches a margin past the lens so the rim filter has content to bend in.\n     WebKit draws a filtered layer from its overflow origin, so the view clips its own\n     overflow on a composited layer and is cut back to the lens by clip-path. */\n  .lg-lens-view {\n    position: absolute;\n    inset: calc(var(--lg-lens-margin) * -1);\n    overflow: hidden;\n    background: var(--lg-lens-fill);\n    clip-path: circle(calc(var(--lg-lens-size) / 2));\n    pointer-events: none;\n    transform: translateZ(0);\n  }\n\n  .lg-lens-scene {\n    position: absolute;\n    top: var(--lg-lens-margin);\n    left: var(--lg-lens-margin);\n    width: var(--lg-lens-stage-width, 100%);\n    height: var(--lg-lens-stage-height, 100%);\n    transform-origin: 0 0;\n    translate: calc(var(--lg-lens-size) / 2 - var(--lg-lens-x, 0px) * var(--lg-lens-zoom))\n      calc(var(--lg-lens-size) / 2 - var(--lg-lens-y, 0px) * var(--lg-lens-zoom));\n    scale: var(--lg-lens-zoom);\n  }\n\n  /* Bevel and sheen over the view, then the specular ring on top. */\n  .lg-lens::before {\n    content: '';\n    position: absolute;\n    inset: 0;\n    z-index: 1;\n    border-radius: inherit;\n    pointer-events: none;\n    background:\n      radial-gradient(circle at 32% 24%, rgb(255 255 255 / 0.22), transparent 42%),\n      linear-gradient(180deg, rgb(255 255 255 / 0.08), transparent 50%);\n    box-shadow:\n      inset 0 0 22px -6px rgb(255 255 255 / 0.5),\n      inset 2px 3px 1px -2px rgb(255 255 255 / 0.9),\n      inset -2px -3px 1px -2px rgb(255 255 255 / 0.55),\n      inset 0 -10px 18px -10px rgb(0 0 0 / 0.3);\n  }\n\n  .lg-lens::after {\n    content: '';\n    position: absolute;\n    inset: 0;\n    z-index: 2;\n    padding: 1px;\n    border-radius: inherit;\n    pointer-events: none;\n    background: linear-gradient(\n      to bottom right,\n      rgb(255 255 255 / 0.9),\n      rgb(255 255 255 / 0.16) 36% 64%,\n      rgb(255 255 255 / 0.5)\n    );\n    -webkit-mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    -webkit-mask-composite: xor;\n    mask:\n      linear-gradient(#000 0 0) content-box,\n      linear-gradient(#000 0 0);\n    mask-composite: exclude;\n  }\n\n  /* ============================================\n     GLASS SCENE\n     One WebGL canvas draws every registered glass element in the scene as a\n     single refracting body. The canvas shows only once it has drawn; before\n     that, and whenever WebGL2 is missing, lost or switched off, every element\n     keeps its CSS glass. data-lg-scene=\"active\" marks an element whose\n     material the canvas has taken over.\n     ============================================ */\n\n  .lg-scene {\n    position: relative;\n    isolation: isolate;\n  }\n\n  /* The backdrop and the canvas sit below the scene's children. Their wrapper\n     adds no box, so they stack as before, except over a viewport backdrop\n     (below). */\n  .lg-scene-layers {\n    display: contents;\n    border-radius: inherit;\n  }\n\n  .lg-scene-backdrop,\n  .lg-scene-canvas {\n    position: absolute;\n    inset: 0;\n    z-index: -1;\n    border-radius: inherit;\n    pointer-events: none;\n  }\n\n  .lg-scene-backdrop {\n    display: block;\n    background: var(--lg-scene-backdrop, var(--lg-scene-image, none)) center / cover no-repeat;\n  }\n\n  /* A viewport backdrop holds still while the page scrolls the glass across\n     it, as background-attachment: fixed does. clip-path keeps it inside the\n     scene without becoming its containing block, and the canvas reads where\n     the layer sits, so the glass bends the backdrop that shows beside it. */\n  .lg-scene[data-lg-backdrop-space='viewport'] {\n    clip-path: inset(0);\n  }\n\n  /* The two layers share one box below the children, so the fixed backdrop\n     itself keeps a z-index of auto: over a fixed backdrop with a negative\n     z-index, iOS Safari paints the status bar solid and cuts the page off in a\n     hard line beneath it. */\n  .lg-scene[data-lg-backdrop-space='viewport'] > .lg-scene-layers {\n    display: block;\n    position: absolute;\n    inset: 0;\n    z-index: -1;\n    pointer-events: none;\n  }\n\n  .lg-scene[data-lg-backdrop-space='viewport']\n    > .lg-scene-layers\n    > :is(.lg-scene-backdrop, .lg-scene-canvas) {\n    z-index: auto;\n  }\n\n  .lg-scene[data-lg-backdrop-space='viewport'] > .lg-scene-layers > .lg-scene-backdrop {\n    position: fixed;\n    border-radius: 0;\n  }\n\n  .lg-scene-canvas {\n    display: none;\n    width: 100%;\n    height: 100%;\n  }\n\n  .lg-scene[data-lg-scene-renderer='webgl'] > .lg-scene-layers > .lg-scene-canvas {\n    display: block;\n  }\n\n  /* Each element hands the canvas the same material its CSS glass shows,\n     and the faintest colour of its text, which the face keeps legible. The\n     canvas frosts over a disk, which spreads light as far as a blur of half\n     its radius. A tenth of the surface blur keeps the shapes behind the glass\n     sharp, so its rim has something to bend. Milk-white frost hides them and\n     the glass reads as acrylic. */\n  .lg-scene .lg-glass-surface {\n    --lg-scene-tint: rgb(\n      255 255 255 /\n        calc(var(--lg-surface-alpha) + var(--lg-scene-tint-boost) + var(--lg-surface-lift))\n    );\n    --lg-scene-frost: calc(var(--lg-surface-blur) * 0.1);\n    --lg-scene-ink: var(--lg-muted-foreground);\n    transition:\n      box-shadow var(--lg-motion-duration) ease,\n      transform var(--lg-motion-duration) ease,\n      --lg-scene-tint var(--lg-motion-duration) ease;\n  }\n\n  /* The dock shelf carries no text, so it keeps its full colour. */\n  .lg-scene .lg-glass-surface.lg-dock-shelf {\n    --lg-scene-ink: transparent;\n  }\n\n  .lg-scene .lg-button {\n    --lg-scene-tint: var(--lg-button-fill);\n    --lg-scene-frost: calc(var(--lg-surface-blur) * 0.4);\n    --lg-scene-ink: var(--lg-ink);\n    transition:\n      transform calc(var(--lg-motion-duration) * 1.9) var(--lg-spring),\n      box-shadow var(--lg-motion-duration) ease,\n      --lg-scene-tint var(--lg-motion-duration) ease;\n  }\n\n  .lg-scene .lg-button:not(:disabled, [aria-disabled='true']):hover {\n    --lg-scene-tint: var(--lg-button-fill-hover);\n  }\n\n  .lg-scene .lg-glass-surface[data-lg-scene='active'],\n  .lg-scene .lg-button[data-lg-scene='active'] {\n    box-shadow: none !important;\n  }\n\n  .lg-scene .lg-glass-surface[data-lg-scene='active']::before,\n  .lg-scene .lg-glass-surface[data-lg-scene='active']::after,\n  .lg-scene .lg-glass-surface[data-lg-scene='active'] > .lg-noise,\n  .lg-scene\n    .lg-button[data-lg-scene='active']\n    > .lg-button__layers\n    > :is(\n      .lg-button__light,\n      .lg-button__distortion,\n      .lg-button__tint,\n      .lg-button__shine,\n      .lg-button__rim\n    ) {\n    display: none;\n  }\n\n  /* A button on a pane the canvas draws sits on glass already. It keeps its\n     frost but neither bends nor saturates that face again, which would undo\n     the face's legible range. */\n  .lg-scene\n    [data-lg-scene='active']\n    .lg-button:not(.lg-button--ghost)\n    > .lg-button__layers\n    > .lg-button__distortion {\n    -webkit-backdrop-filter: blur(var(--lg-surface-blur));\n    backdrop-filter: blur(var(--lg-surface-blur));\n  }\n\n  /* Glass nested in a scene's glass, like a segmented track, keeps its frost\n     and edges but casts no shadow, and its tint moves away from the pane's\n     text: darker under light text, lighter under dark text. On the canvas,\n     text on it keeps at least the contrast the face promised; the CSS glass\n     looks the same, so nothing changes when the canvas takes over. */\n  .lg-scene :is(.lg-glass-surface, [data-lg-scene='active']) .lg-glass-surface {\n    box-shadow: none;\n  }\n\n  .lg-scene :is(.lg-glass-surface, [data-lg-scene='active']) .lg-glass-surface::before {\n    background: rgb(0 0 0 / 0.14);\n    -webkit-backdrop-filter: blur(var(--lg-surface-blur));\n    backdrop-filter: blur(var(--lg-surface-blur));\n  }\n\n  [data-lg-theme='light']\n    .lg-scene\n    :is(.lg-glass-surface, [data-lg-scene='active'])\n    .lg-glass-surface::before {\n    background: rgb(255 255 255 / 0.22);\n  }\n\n  /* A plain piece of glass for scenes: a droplet, lens or pebble. Outside a\n     scene, or before the canvas draws, it shows as frosted CSS glass. */\n  .lg-scene-drop {\n    --lg-scene-tint: rgb(255 255 255 / calc(0.1 + var(--lg-scene-tint-boost)));\n    --lg-scene-frost: 3px;\n    --lg-scene-flow: 1;\n    border-radius: var(--lg-radius-lg);\n    background: rgb(255 255 255 / 0.08);\n    box-shadow:\n      inset 0 1px 0 rgb(255 255 255 / 0.35),\n      inset 0 0 0 1px rgb(255 255 255 / 0.14),\n      0 8px 24px rgb(0 0 0 / 0.18);\n    -webkit-backdrop-filter: blur(min(var(--lg-glass-blur), 8px))\n      brightness(var(--lg-glass-brightness)) contrast(var(--lg-glass-contrast))\n      saturate(var(--lg-glass-sat));\n    backdrop-filter: blur(min(var(--lg-glass-blur), 8px)) brightness(var(--lg-glass-brightness))\n      contrast(var(--lg-glass-contrast)) saturate(var(--lg-glass-sat));\n  }\n\n  .lg-scene-drop[data-lg-scene='active'] {\n    background: none !important;\n    box-shadow: none !important;\n    -webkit-backdrop-filter: none !important;\n    backdrop-filter: none !important;\n  }\n\n  :root[data-lg-reduce-transparency='1'] .lg-scene-drop {\n    background: var(--lg-opaque-surface);\n    -webkit-backdrop-filter: none;\n    backdrop-filter: none;\n  }\n\n  /* The system setting also protects components installed without a provider,\n     including their first paint. An explicit provider choice takes precedence. */\n  @media (prefers-reduced-transparency: reduce) {\n    :root:not([data-lg-reduce-transparency='0']) {\n      --lg-glass-blur: 0px;\n      --lg-glass-alpha: 0.95;\n      --lg-noise-alpha: 0.01;\n      --lg-glass-contrast: 1;\n      --lg-glass-sat: 1;\n      --lg-glass-brightness: 1;\n      --lg-refraction-scale: 0;\n      --lg-lens-depth: 0;\n      --lg-opaque-surface: #2c2c2e;\n      --lg-control-surface: #3a3a3c;\n      --lg-control-surface-hover: #48484a;\n      --lg-track: #48484a;\n    }\n\n    :root[data-lg-theme='light']:not([data-lg-reduce-transparency='0']) {\n      --lg-opaque-surface: #ffffff;\n      --lg-control-surface: #f0f0f2;\n      --lg-control-surface-hover: #e3e3e7;\n      --lg-track: #e6e6e9;\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) .lg-glass-surface::before {\n      -webkit-backdrop-filter: none !important;\n      backdrop-filter: none !important;\n      background: var(--lg-opaque-surface) !important;\n      box-shadow: none !important;\n      transform: none;\n    }\n\n    :root:not([data-lg-reduce-transparency='0'])\n      :is(.lg-glass-surface, .lg-tabs-trigger[data-state='active'])::after,\n    :root:not([data-lg-reduce-transparency='0'])\n      :is(.lg-segmented-indicator, .lg-tabs-indicator)::after {\n      background: var(--lg-border);\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) .lg-button__rim {\n      background: none;\n      box-shadow: inset 0 0 0 var(--lg-rim-width) var(--lg-border);\n    }\n\n    :root:not([data-lg-reduce-transparency='0'])\n      :is(\n        .lg-dialog-content,\n        .lg-popover-content,\n        .lg-toast-root,\n        .lg-tooltip-content,\n        .lg-sheet-content,\n        .lg-menu-content,\n        .lg-select-content,\n        .lg-dock-label\n      ) {\n      background: var(--lg-opaque-surface) !important;\n      -webkit-backdrop-filter: none !important;\n      backdrop-filter: none !important;\n    }\n\n    :root:not([data-lg-reduce-transparency='0'])\n      :is(\n        .lg-button__distortion,\n        .lg-input,\n        .lg-accordion-trigger,\n        .lg-dialog-overlay,\n        .lg-badge,\n        .lg-alert\n      ) {\n      -webkit-backdrop-filter: none !important;\n      backdrop-filter: none !important;\n    }\n\n    :root:not([data-lg-reduce-transparency='0'])\n      .lg-button--prominent\n      > .lg-button__layers\n      > .lg-button__distortion {\n      background: var(--lg-prominent-fill) !important;\n    }\n\n    :root:not([data-lg-reduce-transparency='0'])\n      .lg-button:not(.lg-button--ghost, .lg-button--prominent) {\n      --lg-button-fill: var(--lg-control-surface) !important;\n      --lg-button-fill-hover: var(--lg-control-surface-hover) !important;\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) .lg-button__layers > .lg-button__light {\n      display: none !important;\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) :is(.lg-switch-thumb, .lg-slider-thumb) {\n      background: var(--lg-knob) !important;\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) :is(.lg-button__shine, .lg-button__tint) {\n      opacity: 0;\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) :is(.lg-button__bloom, .lg-refraction-background) {\n      display: none;\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) .lg-segmented > .lg-segmented-indicator,\n    :root:not([data-lg-reduce-transparency='0'])\n      :is(.lg-segmented, .lg-tabs-list)[data-lg-indicator='ready']\n      > :is(.lg-segmented-indicator, .lg-tabs-indicator) {\n      background: var(--lg-control-surface-hover);\n    }\n\n    :root:not([data-lg-reduce-transparency='0']) .lg-scene-drop {\n      background: var(--lg-opaque-surface);\n      -webkit-backdrop-filter: none;\n      backdrop-filter: none;\n    }\n  }\n}\n",
      "type": "registry:file",
      "target": "components/glassyui/styles/liquid.css"
    },
    {
      "path": "registry/glassyui/glass-surface.tsx",
      "content": "'use client';\n\nimport clsx from 'clsx';\nimport { Children, forwardRef, useMemo, useRef, useSyncExternalStore } from 'react';\nimport type { CSSProperties, HTMLAttributes, ReactNode } from 'react';\nimport { mergeRefs } from './lib/merge-refs';\nimport { useGlassScenePane } from './lib/scene-context';\nimport { useLensFilter, useRefractionFilter } from './lib/use-refraction-filter';\nimport { useOptionalPreferences } from './glass-provider';\n\nexport type SurfaceRefraction = 'none' | 'soft' | 'strong';\n\nexport type RefractionBackgroundSpace = 'local' | 'viewport';\n\nexport type SurfacePreset = 'subtle' | 'standard' | 'prominent' | 'control' | 'showcase';\n\nexport type GlassSurfaceProps = HTMLAttributes<HTMLDivElement> & {\n  /** Brightens the surface a touch on hover. */\n  interactive?: boolean;\n  /** Chromium edge lens over the frost. On by default for control and showcase presets. */\n  lens?: boolean;\n  preset?: SurfacePreset;\n  refraction?: SurfaceRefraction;\n  refractionBackground?: ReactNode;\n  refractionBackgroundSpace?: RefractionBackgroundSpace;\n  refractionDisplacement?: number;\n  /**\n   * How thick this glass is, as a multiple of regular. A thicker slab has a\n   * wider rim that bends the backdrop further and splits more colour. Defaults\n   * to the site's thickness preference. Works with a supplied backdrop in every\n   * browser, or with the Chromium lens over live content.\n   */\n  thickness?: number;\n};\n\n// Increased contrast dims the lens through its tokens, which the lens reads only when it measures.\nconst contrastQuery = '(prefers-contrast: more)';\n\nconst subscribeToContrast = (onChange: () => void) => {\n  const query = window.matchMedia(contrastQuery);\n  query.addEventListener('change', onChange);\n\n  return () => query.removeEventListener('change', onChange);\n};\n\nconst readContrast = () => window.matchMedia(contrastQuery).matches;\n\nconst readServerContrast = () => false;\n\nexport const GlassSurface = forwardRef<HTMLDivElement, GlassSurfaceProps>(\n  (\n    {\n      className,\n      children,\n      interactive = false,\n      preset = 'standard',\n      lens = preset === 'control' || preset === 'showcase',\n      refraction = preset === 'showcase' ? 'strong' : 'none',\n      refractionBackground,\n      refractionBackgroundSpace = 'local',\n      refractionDisplacement,\n      style,\n      thickness,\n      ...rest\n    },\n    forwardedRef,\n  ) => {\n    const { preferences } = useOptionalPreferences();\n    const reduceMotion = Boolean(preferences?.reduceMotion);\n    const reduceTransparency = Boolean(preferences?.reduceTransparency);\n    const surfaceRef = useRef<HTMLDivElement | null>(null);\n    const ref = useMemo(() => mergeRefs(surfaceRef, forwardedRef), [forwardedRef]);\n\n    const generatedRefractionEnabled =\n      refraction !== 'none' &&\n      Boolean(refractionBackground) &&\n      !reduceMotion &&\n      !reduceTransparency;\n\n    const { active, filterElement, filterStyle, sceneRef } = useRefractionFilter(surfaceRef, {\n      displacement: refractionDisplacement,\n      enabled: generatedRefractionEnabled,\n      reduceTransparency,\n      strength: refraction === 'none' ? 'soft' : refraction,\n      thickness: thickness ?? preferences?.thickness,\n      viewportAligned: refractionBackgroundSpace === 'viewport',\n    });\n\n    const moreContrast = useSyncExternalStore(\n      subscribeToContrast,\n      readContrast,\n      readServerContrast,\n    );\n\n    // One refraction technique at a time: a generated background scene covers the frost.\n    const lensFilter = useLensFilter(surfaceRef, {\n      enabled: lens && !generatedRefractionEnabled && !reduceTransparency,\n      signature: `${preferences?.theme}:${preferences?.mode}:${preferences?.quality}:${thickness ?? preferences?.thickness}:${moreContrast}`,\n    });\n\n    // Inside a GlassScene the canvas draws this glass; a generated refraction\n    // scene is its own backdrop and keeps its CSS material.\n    useGlassScenePane(surfaceRef, !generatedRefractionEnabled);\n\n    return (\n      <div\n        ref={ref}\n        data-lg-generated-refraction={active ? 'true' : 'false'}\n        data-lg-interactive={interactive ? 'true' : 'false'}\n        data-lg-lens={lensFilter.active ? 'active' : undefined}\n        data-lg-preset={preset}\n        data-lg-refraction={refraction}\n        data-lg-refraction-background-space={refractionBackgroundSpace}\n        className={clsx('lg-glass-surface', className)}\n        style={\n          // SAFETY: CSSProperties has no custom-property index; these add string-valued properties.\n          lensFilter.filterStyle || thickness !== undefined\n            ? ({\n                ...style,\n                ...(thickness === undefined ? null : { '--lg-glass-thickness': thickness }),\n                ...(lensFilter.filterStyle ? { '--lg-lens-filter': lensFilter.filterStyle } : null),\n              } as CSSProperties)\n            : style\n        }\n        {...rest}\n      >\n        {filterElement}\n        {lensFilter.filterElement}\n        {active ? (\n          <div\n            className=\"lg-refraction-background\"\n            aria-hidden=\"true\"\n            style={{ filter: filterStyle }}\n          >\n            <div ref={sceneRef} className=\"lg-refraction-scene\">\n              {refractionBackground}\n            </div>\n          </div>\n        ) : null}\n        <span className=\"lg-noise\" aria-hidden />\n        {Children.map(children, (child) =>\n          // Children.map yields null, elements, portals or text; wrap only the text.\n          child === null || child instanceof Object ? (\n            child\n          ) : (\n            <span className=\"lg-glass-text\">{child}</span>\n          ),\n        )}\n      </div>\n    );\n  },\n);\n\nGlassSurface.displayName = 'GlassSurface';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-surface.tsx"
    },
    {
      "path": "registry/glassyui/lib/merge-refs.ts",
      "content": "import type { Ref } from 'react';\n\nexport function mergeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n  return (node: T) => {\n    for (const ref of refs) {\n      if (!ref) continue;\n\n      if ('current' in ref) {\n        try {\n          ref.current = node;\n        } catch {\n          // Read-only refs cannot be assigned.\n        }\n      } else {\n        ref(node);\n      }\n    }\n  };\n}\n",
      "type": "registry:lib",
      "target": "components/glassyui/lib/merge-refs.ts"
    },
    {
      "path": "registry/glassyui/lib/refraction-map.ts",
      "content": "import { SLAB_AIR_GAP, SLAB_MAX_SLOPE, SLAB_MIN_RISE, SLAB_RISE_SPAN } from './slab-optics';\n\nexport type GeneratedRefractionStrength = 'soft' | 'strong';\n\ntype RefractionPreset = {\n  centerNeutralRatio: number;\n  defaultDisplacement: number;\n  edgeBandRatio: number;\n  edgeWidth: number;\n  /** Rim pull at the default displacement, as a share of the edge band. */\n  rimPull: number;\n};\n\nexport type RefractionMapOptions = {\n  devicePixelRatio?: number;\n  displacement?: number;\n  height: number;\n  radius: number;\n  strength: GeneratedRefractionStrength;\n  thickness?: number;\n  width: number;\n};\n\nexport type RefractionMapMetrics = {\n  cacheKey: string;\n  dispersion: number;\n  edgeBand: number;\n  /** Corner radius of the bend field, never tighter than the edge band. */\n  fieldRadius: number;\n  filterBlur: number;\n  filterPadding: number;\n  filterScale: number;\n  height: number;\n  mapHeight: number;\n  mapPixels: number;\n  mapWidth: number;\n  paddedHeight: number;\n  paddedWidth: number;\n  /** Inward offset at the rim, in CSS pixels. */\n  pull: number;\n  radius: number;\n  width: number;\n};\n\nexport type RefractionMapBuildResult = {\n  href: string;\n  metrics: RefractionMapMetrics;\n};\n\nexport const MAX_REFRACTION_MAP_PIXELS = 160000;\n\nconst REFRACTION_PRESETS: Record<GeneratedRefractionStrength, RefractionPreset> = {\n  soft: {\n    centerNeutralRatio: 0.68,\n    defaultDisplacement: 72,\n    edgeBandRatio: 0.18,\n    edgeWidth: 1.08,\n    rimPull: 0.22,\n  },\n  strong: {\n    centerNeutralRatio: 0.58,\n    defaultDisplacement: 112,\n    edgeBandRatio: 0.2,\n    edgeWidth: 1.22,\n    rimPull: 0.34,\n  },\n};\n\n/**\n * Scene blur under the rim, in CSS pixels. Chromium and Firefox sample the bend\n * nearest-neighbour, so the magnified rim would stair-step at 1x pixel density. The\n * map's blue channel keeps the blur off the flat centre.\n */\nconst RIM_BLUR = 0.8;\n\n/**\n * The bend falls off as (1 - t)^2 across the edge band, steepest at the rim. Any pull\n * under half the band keeps each sample further in than its outer neighbour, so the rim\n * compresses the view like thick glass and never mirrors it. The base pull is 0.34;\n * even the furthest colour stays below 0.4 to leave room for the map's 8-bit rounding.\n */\nconst MAX_RIM_PULL = 0.34;\n\nconst MAX_CHROMATIC_RIM_PULL = 0.4;\n\nconst refractionMapCache = new Map<string, string>();\n\nconst maxCachedRefractionMaps = 48;\n\nexport function clamp(value: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, value));\n}\n\nfunction smoothstep(edge0: number, edge1: number, value: number) {\n  const t = clamp((value - edge0) / Math.max(edge1 - edge0, 0.0001), 0, 1);\n\n  return t * t * (3 - 2 * t);\n}\n\ntype Edge = { distance: number; normalX: number; normalY: number };\n\n// Distance to a rounded rectangle plus its outward unit normal.\nfunction roundedRectEdge(\n  x: number,\n  y: number,\n  width: number,\n  height: number,\n  radius: number,\n  edge: Edge,\n) {\n  const px = x - width / 2;\n  const py = y - height / 2;\n  const qx = Math.abs(px) - (width / 2 - radius);\n  const qy = Math.abs(py) - (height / 2 - radius);\n  const signX = px < 0 ? -1 : 1;\n  const signY = py < 0 ? -1 : 1;\n\n  if (qx > 0 && qy > 0) {\n    const length = Math.hypot(qx, qy);\n\n    edge.distance = length - radius;\n    edge.normalX = (signX * qx) / length;\n    edge.normalY = (signY * qy) / length;\n  } else if (qx > qy) {\n    edge.distance = qx - radius;\n    edge.normalX = signX;\n    edge.normalY = 0;\n  } else {\n    edge.distance = qy - radius;\n    edge.normalX = 0;\n    edge.normalY = signY;\n  }\n\n  return edge;\n}\n\nfunction fitMapSize(width: number, height: number, pixelRatio: number) {\n  let mapWidth = clamp(Math.round(width * pixelRatio), 32, 1800);\n  let mapHeight = clamp(Math.round(height * pixelRatio), 32, 1200);\n\n  const pixels = mapWidth * mapHeight;\n\n  if (pixels <= MAX_REFRACTION_MAP_PIXELS) {\n    return { mapWidth, mapHeight };\n  }\n\n  const ratio = Math.sqrt(MAX_REFRACTION_MAP_PIXELS / pixels);\n  mapWidth = Math.max(32, Math.floor(mapWidth * ratio));\n  mapHeight = Math.max(32, Math.floor(mapHeight * ratio));\n\n  while (mapWidth * mapHeight > MAX_REFRACTION_MAP_PIXELS) {\n    if (mapWidth >= mapHeight) {\n      mapWidth -= 1;\n    } else {\n      mapHeight -= 1;\n    }\n  }\n\n  return { mapWidth, mapHeight };\n}\n\nexport function computeRefractionMapMetrics(options: RefractionMapOptions): RefractionMapMetrics {\n  const preset = REFRACTION_PRESETS[options.strength];\n  const width = Math.max(0, options.width);\n  const height = Math.max(0, options.height);\n  const minDimension = Math.min(width, height);\n  const radius = clamp(options.radius, 0, minDimension / 2);\n  const displacement = clamp(options.displacement ?? preset.defaultDisplacement, 0, 196);\n  const thickness = clamp(options.thickness ?? 1, 0.1, 4);\n  const rimWidth = 0.6 + 0.4 * thickness;\n  const dispersion = clamp((thickness - 1) * 0.2, 0, 0.6);\n  const blueFactor = 1 + dispersion / 2;\n  const neutralInset = (minDimension * (1 - preset.centerNeutralRatio)) / 2;\n\n  const edgeBand = clamp(\n    Math.min(minDimension * preset.edgeBandRatio, neutralInset) * preset.edgeWidth * rimWidth,\n    6,\n    Math.min(72 * rimWidth, (radius || 56) * 1.15 * rimWidth, minDimension * 0.48),\n  );\n\n  // Inside a tighter corner the inset contours would turn sharp and pinch the bend.\n  const fieldRadius = clamp(Math.max(radius, edgeBand), 0, minDimension / 2);\n\n  const pull =\n    edgeBand *\n    Math.min(\n      MAX_RIM_PULL,\n      MAX_CHROMATIC_RIM_PULL / blueFactor,\n      (preset.rimPull * displacement) / preset.defaultDisplacement,\n    );\n\n  // WebKit shifts a clipped, filtered scene unless the filter region reaches past the\n  // box as far as the primitives read: the blue pull plus three blur deviations.\n  const filterPadding = Math.ceil(pull * blueFactor + RIM_BLUR * 3) + 2;\n  const paddedWidth = width + filterPadding * 2;\n  const paddedHeight = height + filterPadding * 2;\n  const targetRatio = clamp((options.devicePixelRatio ?? 1) * 1.12, 1.15, 2.1);\n  const targetPixels = paddedWidth * paddedHeight * targetRatio * targetRatio;\n\n  const pixelRatio =\n    targetPixels > MAX_REFRACTION_MAP_PIXELS\n      ? Math.max(1, targetRatio * Math.sqrt(MAX_REFRACTION_MAP_PIXELS / targetPixels))\n      : targetRatio;\n\n  const { mapWidth, mapHeight } = fitMapSize(paddedWidth, paddedHeight, pixelRatio);\n\n  const cacheKey = [\n    mapWidth,\n    mapHeight,\n    Math.round(width),\n    Math.round(height),\n    Math.round(fieldRadius * 10),\n    Math.round(edgeBand * 10),\n    filterPadding,\n  ].join(':');\n\n  return {\n    cacheKey,\n    dispersion,\n    edgeBand,\n    fieldRadius,\n    filterBlur: RIM_BLUR,\n    filterPadding,\n    // Map channels hold a unit offset; the scale turns it into CSS pixels.\n    filterScale: Math.round(pull * 2 * 100) / 100,\n    height,\n    mapHeight,\n    mapPixels: mapWidth * mapHeight,\n    mapWidth,\n    paddedHeight,\n    paddedWidth,\n    pull,\n    radius,\n    width,\n  };\n}\n\n/**\n * Encodes a unit rim field: R/G = 0.5 + 0.5 * inward offset. Past the field's edge the\n * rim offset carries on unchanged, so the map stays smooth across the clipped border.\n * B is the share of softened scene: full through the outer half of the band, which\n * holds every point the steep rim samples, and gone by the flat centre.\n */\nexport async function buildRefractionMapDataUrl(\n  options: RefractionMapOptions,\n): Promise<RefractionMapBuildResult> {\n  const metrics = computeRefractionMapMetrics(options);\n  const cached = refractionMapCache.get(metrics.cacheKey);\n\n  if (cached) return { href: cached, metrics };\n\n  if (typeof document === 'undefined') {\n    throw new Error('Refraction maps require a browser document');\n  }\n\n  const canvas = document.createElement('canvas');\n  canvas.width = metrics.mapWidth;\n  canvas.height = metrics.mapHeight;\n\n  const ctx = canvas.getContext('2d');\n\n  if (!ctx) {\n    throw new Error('Canvas 2D context unavailable');\n  }\n\n  const {\n    edgeBand,\n    fieldRadius,\n    filterPadding,\n    height,\n    mapHeight,\n    mapWidth,\n    paddedHeight,\n    paddedWidth,\n    width,\n  } = metrics;\n\n  const data = new Uint8ClampedArray(mapWidth * mapHeight * 4);\n  const edge: Edge = { distance: 0, normalX: 0, normalY: 0 };\n\n  for (let yIndex = 0; yIndex < mapHeight; yIndex += 1) {\n    const y = ((yIndex + 0.5) / mapHeight) * paddedHeight - filterPadding;\n\n    for (let xIndex = 0; xIndex < mapWidth; xIndex += 1) {\n      const x = ((xIndex + 0.5) / mapWidth) * paddedWidth - filterPadding;\n      const index = (yIndex * mapWidth + xIndex) * 4;\n      roundedRectEdge(x, y, width, height, fieldRadius, edge);\n      const depth = clamp(-edge.distance / edgeBand, 0, 1);\n      const falloff = (1 - depth) ** 2;\n      const offsetX = -edge.normalX * falloff;\n      const offsetY = -edge.normalY * falloff;\n\n      data[index] = Math.round((0.5 + offsetX / 2) * 255);\n      data[index + 1] = Math.round((0.5 + offsetY / 2) * 255);\n      data[index + 2] = Math.round((1 - smoothstep(0.5, 1, depth)) * 255);\n      data[index + 3] = 255;\n    }\n  }\n\n  ctx.putImageData(new ImageData(data, mapWidth, mapHeight), 0, 0);\n  const href = canvas.toDataURL('image/png');\n  refractionMapCache.set(metrics.cacheKey, href);\n\n  if (refractionMapCache.size > maxCachedRefractionMaps) {\n    const oldestKey = refractionMapCache.keys().next().value;\n\n    if (oldestKey) refractionMapCache.delete(oldestKey);\n  }\n\n  return { href, metrics };\n}\n\n/** Corner radii in CSS pixels, clockwise from the top left. */\nexport type LensRadii = readonly [number, number, number, number];\n\nexport type LensMapOptions = {\n  /** Width of the curved rim in CSS pixels. */\n  bevel: number;\n  /** Multiplies how far the rim bends the backdrop. 0 leaves it flat. */\n  depth: number;\n  devicePixelRatio?: number;\n  height: number;\n  /** Refractive index of the glass. */\n  ior: number;\n  radii: LensRadii;\n  /** Height of the slab in CSS pixels. Thicker glass bends further. */\n  thickness: number;\n  width: number;\n};\n\nexport type LensMapMetrics = {\n  bevel: number;\n  cacheKey: string;\n  height: number;\n  mapHeight: number;\n  mapWidth: number;\n  /** The furthest the rim reads the backdrop from, in CSS pixels. */\n  reach: number;\n  width: number;\n};\n\nexport type LensMapBuildResult = {\n  href: string;\n  metrics: LensMapMetrics;\n};\n\nconst MAX_LENS_MAP_PIXELS = 420000;\n\nexport const MAX_LENS_SURFACE_AREA = 720000;\n\n// The rim reads the backdrop from no further than this share of its width, and\n// the point it reads from advances by at least a quarter of every pixel across\n// the rim, so the view compresses toward the edge and never folds back on itself.\nconst LENS_REACH = 0.7;\n\nconst LENS_FOLD = 0.75;\n\nconst LENS_PROFILE_STEPS = 512;\n\n// The rim and the flat top are the same frosted glass, but smoked glass lets more\n// light through its rim. Blue holds the share of the rim's light: all of it near\n// the edge, none past four fifths of the rim or a sixth of the surface, whichever\n// comes first, so small pieces keep the top's shade in the middle.\nconst LENS_RIM_LIGHT = 0.8;\n\nconst LENS_RIM_LIGHT_SURFACE = 0.16;\n\nconst LENS_RIM_LIGHT_FALLOFF = 0.45;\n\nconst lensMapCache = new Map<string, LensMapBuildResult>();\n\nconst maxCachedLensMaps = 32;\n\n// Native byte order keeps the neutral RGBA pixel identical on every platform.\nconst FLAT_LENS_PIXEL = new Uint32Array(new Uint8Array([128, 128, 0, 255]).buffer)[0];\n\n/**\n * How far inward a view ray lands on the backdrop after it crosses a slab of glass\n * whose top curves down into a rim, s pixels in from the edge. The top follows\n * (1 - (1 - u)^4)^(1/4) across the rim, Snell's law bends the ray into the glass and\n * again out of its flat underside, and it crosses an air gap to the backdrop. This is\n * the WebGL scene's `through()`, for a ray straight down.\n */\nfunction slabOffset(s: number, bevel: number, thickness: number, ior: number) {\n  const u = s / bevel;\n  const v = 1 - Math.max(u, 0.002);\n  let rise = 1;\n  let steepness = 0;\n\n  if (u < 1) {\n    const v4 = v ** 4;\n\n    rise = (1 - v4) ** 0.25;\n    steepness = v ** 3 * Math.max(1 - v4, 1e-6) ** -0.75;\n  }\n\n  const slope = Math.min(((SLAB_RISE_SPAN * thickness) / bevel) * steepness, SLAB_MAX_SLOPE);\n  const height = thickness * (SLAB_MIN_RISE + SLAB_RISE_SPAN * rise);\n  const tilt = Math.hypot(slope, 1);\n  const sinTilt = slope / tilt;\n  const cosTilt = 1 / tilt;\n  const eta = 1 / ior;\n  const root = Math.sqrt(Math.max(1 - eta * eta * sinTilt * sinTilt, 0));\n  const bend = eta * cosTilt - root;\n  const along = bend * sinTilt;\n  const down = Math.max(eta - bend * cosTilt, 0.05);\n  const sinOut = ior * Math.abs(along);\n  const cosOut = Math.sqrt(Math.max(1 - sinOut * sinOut, 0));\n  const leaving = (ior * along) / Math.max(sinOut, 1);\n\n  return -(along * (height / down) + leaving * (SLAB_AIR_GAP / Math.max(cosOut, 0.1)));\n}\n\n/**\n * The inward offset across the rim, from the edge to its inner boundary. The physical\n * offset is softly capped, then limited so it never falls faster than the pixels it\n * crosses, which would fold the view back on itself.\n */\nfunction lensProfile(bevel: number, thickness: number, ior: number, depth: number) {\n  const cap = LENS_REACH * bevel;\n  const step = bevel / LENS_PROFILE_STEPS;\n  const offsets = new Float32Array(LENS_PROFILE_STEPS + 1);\n\n  for (let index = 0; index < LENS_PROFILE_STEPS; index += 1) {\n    offsets[index] =\n      cap * Math.tanh((depth * slabOffset(index * step, bevel, thickness, ior)) / cap);\n  }\n\n  for (let index = LENS_PROFILE_STEPS - 1; index >= 0; index -= 1) {\n    offsets[index] = Math.min(offsets[index], offsets[index + 1] + LENS_FOLD * step);\n  }\n\n  return { offsets, reach: Math.max(...offsets, 0.01) };\n}\n\n// Distance to a rounded rectangle with its own radius in every corner, plus its outward unit normal.\nfunction roundedBoxEdge(\n  x: number,\n  y: number,\n  width: number,\n  height: number,\n  radii: LensRadii,\n  edge: Edge,\n) {\n  const px = x - width / 2;\n  const py = y - height / 2;\n  const radius = px < 0 ? (py < 0 ? radii[0] : radii[3]) : py < 0 ? radii[1] : radii[2];\n  const qx = Math.abs(px) - (width / 2 - radius);\n  const qy = Math.abs(py) - (height / 2 - radius);\n  const signX = px < 0 ? -1 : 1;\n  const signY = py < 0 ? -1 : 1;\n\n  if (qx > 0 && qy > 0) {\n    const length = Math.hypot(qx, qy);\n\n    edge.distance = length - radius;\n    edge.normalX = (signX * qx) / length;\n    edge.normalY = (signY * qy) / length;\n  } else if (qx > qy) {\n    edge.distance = qx - radius;\n    edge.normalX = signX;\n    edge.normalY = 0;\n  } else {\n    edge.distance = qy - radius;\n    edge.normalX = 0;\n    edge.normalY = signY;\n  }\n\n  return edge;\n}\n\nfunction computeLensMapMetrics(options: LensMapOptions) {\n  const width = Math.max(1, options.width);\n  const height = Math.max(1, options.height);\n  const bevel = clamp(options.bevel, 1, Math.min(width, height) / 2);\n  const pixelRatio = clamp(options.devicePixelRatio ?? 1, 1, 2);\n  let mapWidth = clamp(Math.round(width * pixelRatio), 32, 1800);\n  let mapHeight = clamp(Math.round(height * pixelRatio), 32, 1200);\n\n  if (mapWidth * mapHeight > MAX_LENS_MAP_PIXELS) {\n    const ratio = Math.sqrt(MAX_LENS_MAP_PIXELS / (mapWidth * mapHeight));\n\n    mapWidth = Math.max(32, Math.floor(mapWidth * ratio));\n    mapHeight = Math.max(32, Math.floor(mapHeight * ratio));\n  }\n\n  const cacheKey = [\n    mapWidth,\n    mapHeight,\n    Math.round(width),\n    Math.round(height),\n    ...options.radii.map((radius) => Math.round(radius * 10)),\n    Math.round(bevel * 10),\n    Math.round(options.thickness * 10),\n    Math.round(options.ior * 100),\n    Math.round(options.depth * 100),\n  ].join(':');\n\n  return { bevel, cacheKey, height, mapHeight, mapWidth, width };\n}\n\n/**\n * Encodes the rim of a slab of glass as a displacement field: R/G = 0.5 + 0.5 * the\n * offset the backdrop is read from, as a share of `reach`. The SVG feDisplacementMap\n * scale of twice `reach` turns it back into CSS pixels. B is the share of the rim's\n * light, which smoked glass dims less than its top. The field is flat past the rim.\n */\nexport function buildLensMapDataUrl(options: LensMapOptions): LensMapBuildResult {\n  const base = computeLensMapMetrics(options);\n  const cached = lensMapCache.get(base.cacheKey);\n\n  if (cached) {\n    lensMapCache.delete(base.cacheKey);\n    lensMapCache.set(base.cacheKey, cached);\n\n    return cached;\n  }\n\n  if (typeof document === 'undefined') {\n    throw new Error('Lens maps require a browser document');\n  }\n\n  const canvas = document.createElement('canvas');\n  canvas.width = base.mapWidth;\n  canvas.height = base.mapHeight;\n\n  const ctx = canvas.getContext('2d');\n\n  if (!ctx) {\n    throw new Error('Canvas 2D context unavailable');\n  }\n\n  const { bevel, height, mapHeight, mapWidth, width } = base;\n  const { offsets, reach } = lensProfile(bevel, options.thickness, options.ior, options.depth);\n\n  const litTo = Math.min(\n    LENS_RIM_LIGHT,\n    (LENS_RIM_LIGHT_SURFACE * Math.min(width, height)) / bevel,\n  );\n\n  const litFrom = litTo * LENS_RIM_LIGHT_FALLOFF;\n  const data = new Uint8ClampedArray(mapWidth * mapHeight * 4);\n  const pixels = new Uint32Array(data.buffer);\n  const edge: Edge = { distance: 0, normalX: 0, normalY: 0 };\n  // Beyond every corner and the bevel, the map is exactly neutral. Keep a full\n  // texel inside that rectangle so fractional dimensions cannot round a rim in.\n  const flatInset = Math.max(bevel, ...options.radii);\n  const flatLeft = Math.min(mapWidth, Math.ceil((flatInset / width) * mapWidth + 0.5));\n  const flatRight = Math.max(flatLeft, Math.floor(((width - flatInset) / width) * mapWidth - 0.5));\n  const flatTop = Math.ceil((bevel / height) * mapHeight + 0.5);\n  const flatBottom = Math.floor(((height - bevel) / height) * mapHeight - 0.5);\n\n  for (let yIndex = 0; yIndex < mapHeight; yIndex += 1) {\n    const y = ((yIndex + 0.5) / mapHeight) * height;\n    const flatRow = yIndex >= flatTop && yIndex < flatBottom;\n\n    for (let xIndex = 0; xIndex < mapWidth; xIndex += 1) {\n      if (flatRow && xIndex === flatLeft && flatRight > flatLeft) {\n        pixels.fill(FLAT_LENS_PIXEL, yIndex * mapWidth + flatLeft, yIndex * mapWidth + flatRight);\n        xIndex = flatRight - 1;\n\n        continue;\n      }\n\n      const x = ((xIndex + 0.5) / mapWidth) * width;\n      const index = (yIndex * mapWidth + xIndex) * 4;\n      roundedBoxEdge(x, y, width, height, options.radii, edge);\n      const inset = Math.max(-edge.distance, 0);\n      let offset = 0;\n\n      if (inset < bevel) {\n        const position = (inset / bevel) * LENS_PROFILE_STEPS;\n        const lower = Math.floor(position);\n        const fraction = position - lower;\n\n        offset = offsets[lower] * (1 - fraction) + offsets[lower + 1] * fraction;\n      }\n\n      data[index] = Math.round(clamp(0.5 - (offset * edge.normalX) / (2 * reach), 0, 1) * 255);\n      data[index + 1] = Math.round(clamp(0.5 - (offset * edge.normalY) / (2 * reach), 0, 1) * 255);\n      data[index + 2] = Math.round((1 - smoothstep(litFrom, litTo, inset / bevel)) * 255);\n      data[index + 3] = 255;\n    }\n  }\n\n  ctx.putImageData(new ImageData(data, mapWidth, mapHeight), 0, 0);\n\n  const result = {\n    href: canvas.toDataURL('image/png'),\n    metrics: { ...base, reach },\n  };\n\n  lensMapCache.set(base.cacheKey, result);\n\n  if (lensMapCache.size > maxCachedLensMaps) {\n    const oldestKey = lensMapCache.keys().next().value;\n\n    if (oldestKey) lensMapCache.delete(oldestKey);\n  }\n\n  return result;\n}\n\nexport type MagnifierMapOptions = {\n  devicePixelRatio?: number;\n  size: number;\n};\n\nexport type MagnifierMapMetrics = {\n  /** Width of the ring that bends, in CSS pixels. */\n  band: number;\n  cacheKey: string;\n  /** Outward offset at the rim, in CSS pixels. */\n  depth: number;\n  mapSize: number;\n  size: number;\n};\n\nexport type MagnifierMapBuildResult = {\n  href: string;\n  metrics: MagnifierMapMetrics;\n};\n\nconst magnifierMapCache = new Map<string, string>();\n\nconst maxCachedMagnifierMaps = 8;\n\nexport function computeMagnifierMapMetrics(options: MagnifierMapOptions): MagnifierMapMetrics {\n  const size = Math.max(1, options.size);\n  const radius = size / 2;\n  const mapSize = clamp(Math.round(size * clamp(options.devicePixelRatio ?? 1, 1, 2)), 32, 400);\n\n  return {\n    band: radius * 0.46,\n    cacheKey: [mapSize, Math.round(size * 10)].join(':'),\n    depth: radius * 0.5,\n    mapSize,\n    size,\n  };\n}\n\n/**\n * Encodes an outward offset that grows toward the rim of a circle:\n * R/G = 0.5 + 0.5 * offset / depth. Each pixel samples a little further out than its\n * inner neighbour, so the view compresses into the rim like thick glass and never folds\n * back on itself, even over sharp text.\n */\nexport function buildMagnifierMapDataUrl(options: MagnifierMapOptions): MagnifierMapBuildResult {\n  const metrics = computeMagnifierMapMetrics(options);\n  const cached = magnifierMapCache.get(metrics.cacheKey);\n\n  if (cached) return { href: cached, metrics };\n\n  if (typeof document === 'undefined') {\n    throw new Error('Magnifier maps require a browser document');\n  }\n\n  const canvas = document.createElement('canvas');\n  canvas.width = metrics.mapSize;\n  canvas.height = metrics.mapSize;\n\n  const ctx = canvas.getContext('2d');\n\n  if (!ctx) {\n    throw new Error('Canvas 2D context unavailable');\n  }\n\n  const { band, mapSize, size } = metrics;\n  const radius = size / 2;\n  const data = new Uint8ClampedArray(mapSize * mapSize * 4);\n\n  for (let yIndex = 0; yIndex < mapSize; yIndex += 1) {\n    const dy = ((yIndex + 0.5) / mapSize) * size - radius;\n\n    for (let xIndex = 0; xIndex < mapSize; xIndex += 1) {\n      const dx = ((xIndex + 0.5) / mapSize) * size - radius;\n      const index = (yIndex * mapSize + xIndex) * 4;\n      const distance = Math.hypot(dx, dy) || 1;\n      // Flat in the centre, cubic through the ring so the bend starts without a crease.\n      const rim = clamp((distance - (radius - band)) / band, 0, 1) ** 3;\n\n      data[index] = Math.round((0.5 + (0.5 * rim * dx) / distance) * 255);\n      data[index + 1] = Math.round((0.5 + (0.5 * rim * dy) / distance) * 255);\n      data[index + 2] = 128;\n      data[index + 3] = 255;\n    }\n  }\n\n  ctx.putImageData(new ImageData(data, mapSize, mapSize), 0, 0);\n  const href = canvas.toDataURL('image/png');\n  magnifierMapCache.set(metrics.cacheKey, href);\n\n  if (magnifierMapCache.size > maxCachedMagnifierMaps) {\n    const oldestKey = magnifierMapCache.keys().next().value;\n\n    if (oldestKey) magnifierMapCache.delete(oldestKey);\n  }\n\n  return { href, metrics };\n}\n\nexport type CapsuleLensMapOptions = {\n  devicePixelRatio?: number;\n  height: number;\n  /** Multiple of regular glass: a thicker capsule bends further at its rim. */\n  thickness?: number;\n  width: number;\n};\n\ntype CapsuleLensMapMetrics = {\n  /** Width of the band inside the rim that bends, in CSS pixels. */\n  band: number;\n  cacheKey: string;\n  /** Outward offset at the rim, in CSS pixels. */\n  depth: number;\n  height: number;\n  mapHeight: number;\n  mapWidth: number;\n  width: number;\n};\n\nexport type CapsuleLensMapBuildResult = {\n  href: string;\n  metrics: CapsuleLensMapMetrics;\n};\n\n// The lens itself shows its scene evenly, a little smaller; only a thin band at the rim bends.\nconst CAPSULE_BAND = 0.3;\n\nconst CAPSULE_DEPTH = 0.25;\n\nconst capsuleLensMapCache = new Map<string, string>();\n\nconst maxCachedCapsuleLensMaps = 8;\n\nfunction computeCapsuleLensMapMetrics(options: CapsuleLensMapOptions): CapsuleLensMapMetrics {\n  const width = Math.max(1, options.width);\n  const height = Math.max(1, options.height);\n  const radius = Math.min(width, height) / 2;\n  const thickness = clamp(options.thickness ?? 1, 0.1, 4);\n  // The map is drawn at twice the device scale, so the bend stays smooth on a small knob.\n  const scale = clamp(options.devicePixelRatio ?? 1, 1, 2) * 2;\n  const mapWidth = clamp(Math.round(width * scale), 32, 400);\n  const mapHeight = clamp(Math.round(height * scale), 32, 400);\n  const band = radius * CAPSULE_BAND;\n  const depth = radius * CAPSULE_DEPTH * thickness;\n\n  return {\n    band,\n    cacheKey: [mapWidth, mapHeight, Math.round(width * 10), Math.round(height * 10)].join(':'),\n    depth,\n    height,\n    mapHeight,\n    mapWidth,\n    width,\n  };\n}\n\n/**\n * The rim's field for a capsule lens, such as a switch knob: R/G = 0.5 + 0.5 * the outward\n * offset as a share of `depth`, along the outward normal of the nearest rim. It is flat in\n * the middle and grows cubically through a thin band, so each pixel there samples a little\n * further out than its inner neighbour: the view compresses into the rim like thick glass\n * and never folds back on itself. The ends and the straight sides meet without a crease.\n * B rises through the band, so the filter softens only the scene the rim bends and the\n * middle stays sharp.\n */\nexport function buildCapsuleLensMapDataUrl(\n  options: CapsuleLensMapOptions,\n): CapsuleLensMapBuildResult {\n  const metrics = computeCapsuleLensMapMetrics(options);\n  const cached = capsuleLensMapCache.get(metrics.cacheKey);\n\n  if (cached) return { href: cached, metrics };\n\n  if (typeof document === 'undefined') {\n    throw new Error('Lens maps require a browser document');\n  }\n\n  const canvas = document.createElement('canvas');\n  canvas.width = metrics.mapWidth;\n  canvas.height = metrics.mapHeight;\n\n  const ctx = canvas.getContext('2d');\n\n  if (!ctx) {\n    throw new Error('Canvas 2D context unavailable');\n  }\n\n  const { band, height, mapHeight, mapWidth, width } = metrics;\n  const radius = Math.min(width, height) / 2;\n  const radii: LensRadii = [radius, radius, radius, radius];\n  const data = new Uint8ClampedArray(mapWidth * mapHeight * 4);\n  const edge: Edge = { distance: 0, normalX: 0, normalY: 0 };\n\n  for (let yIndex = 0; yIndex < mapHeight; yIndex += 1) {\n    const y = ((yIndex + 0.5) / mapHeight) * height;\n\n    for (let xIndex = 0; xIndex < mapWidth; xIndex += 1) {\n      const x = ((xIndex + 0.5) / mapWidth) * width;\n      const index = (yIndex * mapWidth + xIndex) * 4;\n      roundedBoxEdge(x, y, width, height, radii, edge);\n      const inBand = clamp((band + edge.distance) / band, 0, 1);\n      const rim = inBand ** 3;\n\n      data[index] = Math.round((0.5 + 0.5 * rim * edge.normalX) * 255);\n      data[index + 1] = Math.round((0.5 + 0.5 * rim * edge.normalY) * 255);\n      data[index + 2] = Math.round(inBand * 255);\n      data[index + 3] = 255;\n    }\n  }\n\n  ctx.putImageData(new ImageData(data, mapWidth, mapHeight), 0, 0);\n  const href = canvas.toDataURL('image/png');\n  capsuleLensMapCache.set(metrics.cacheKey, href);\n\n  if (capsuleLensMapCache.size > maxCachedCapsuleLensMaps) {\n    const oldestKey = capsuleLensMapCache.keys().next().value;\n\n    if (oldestKey) capsuleLensMapCache.delete(oldestKey);\n  }\n\n  return { href, metrics };\n}\n",
      "type": "registry:lib",
      "target": "components/glassyui/lib/refraction-map.ts"
    },
    {
      "path": "registry/glassyui/lib/slab-optics.ts",
      "content": "// The glass floats this far above its backdrop, in CSS pixels.\nexport const SLAB_AIR_GAP = 8;\n\n// Fractions of full slab thickness at the rim and added across its curved top.\nexport const SLAB_MIN_RISE = 0.3;\n\nexport const SLAB_RISE_SPAN = 0.7;\n\nexport const SLAB_MAX_SLOPE = 12;\n",
      "type": "registry:lib",
      "target": "components/glassyui/lib/slab-optics.ts"
    },
    {
      "path": "registry/glassyui/lib/use-refraction-filter.tsx",
      "content": "'use client';\n\nimport { useCallback, useEffect, useId, useMemo, useRef, useState, type RefObject } from 'react';\nimport {\n  buildCapsuleLensMapDataUrl,\n  buildLensMapDataUrl,\n  buildMagnifierMapDataUrl,\n  buildRefractionMapDataUrl,\n  clamp,\n  computeMagnifierMapMetrics,\n  MAX_LENS_SURFACE_AREA,\n  type CapsuleLensMapBuildResult,\n  type GeneratedRefractionStrength,\n  type LensMapMetrics,\n  type LensRadii,\n  type MagnifierMapBuildResult,\n  type RefractionMapMetrics,\n} from './refraction-map';\n\ntype RefractionFilterOptions = {\n  displacement?: number;\n  enabled: boolean;\n  reduceTransparency?: boolean;\n  strength: GeneratedRefractionStrength;\n  thickness?: number;\n  viewportAligned?: boolean;\n};\n\ntype RefractionFilterState = {\n  href: string | null;\n  metrics: RefractionMapMetrics | null;\n};\n\nfunction isProductionRuntime() {\n  // SAFETY: only Node and bundler shims define `process`, so every step of the probe stays optional.\n  const runtime = globalThis as typeof globalThis & {\n    process?: { env?: { NODE_ENV?: string } };\n  };\n\n  return runtime.process?.env?.NODE_ENV === 'production';\n}\n\nfunction parseRadiusValue(value: string, width: number, height: number) {\n  const [first] = value.trim().split(/\\s+/);\n  const maxRadius = Math.min(width, height) / 2;\n\n  const parsed = Number.parseFloat(first);\n\n  if (first.endsWith('%')) {\n    return clamp((parsed / 100) * Math.min(width, height), 0, maxRadius);\n  }\n\n  return clamp(Number.isFinite(parsed) ? parsed : maxRadius, 0, maxRadius);\n}\n\nfunction readUniformRadius(el: HTMLElement, width: number, height: number) {\n  const style = window.getComputedStyle(el);\n\n  const rawRadii = [\n    style.borderTopLeftRadius,\n    style.borderTopRightRadius,\n    style.borderBottomRightRadius,\n    style.borderBottomLeftRadius,\n  ];\n\n  const radii = rawRadii.map((value) => parseRadiusValue(value, width, height));\n  const first = radii[0];\n  const mixed = radii.some((radius) => Math.abs(radius - first) > 0.5);\n\n  return mixed ? null : first;\n}\n\nfunction readCornerRadii(style: CSSStyleDeclaration, width: number, height: number): LensRadii {\n  return [\n    parseRadiusValue(style.borderTopLeftRadius, width, height),\n    parseRadiusValue(style.borderTopRightRadius, width, height),\n    parseRadiusValue(style.borderBottomRightRadius, width, height),\n    parseRadiusValue(style.borderBottomLeftRadius, width, height),\n  ];\n}\n\n/**\n * A scroll event reaches script a frame after the compositor has scrolled, so a scene moved from\n * script trails the page. Chromium filters a composited layer on its compositor, which lets a\n * scroll-driven animation carry the scene in step with the scroll and still bend it. WebKit leaves\n * a scene on its own compositing layer out of the SVG filter and Gecko has no scroll timelines, so\n * both keep moving the scene from script. Only Chromium has userAgentData.\n */\nfunction supportsCompositedSceneScroll() {\n  return (\n    typeof ViewTimeline !== 'undefined' &&\n    typeof navigator !== 'undefined' &&\n    'userAgentData' in navigator\n  );\n}\n\n// Across the timeline the scene moves down exactly as far as the scroll carries the surface up.\nconst sceneScrollKeyframes = (span: number): Keyframe[] => [\n  { translate: '0px 0px' },\n  { translate: `0px ${span}px` },\n];\n\n/**\n * A view timeline places its subject where it would sit if nothing were fixed or stuck, so it runs\n * on while such a surface stands still. Those surfaces keep following the scroll from script.\n */\nfunction standsStillWhileScrolling(el: Element, source: Element) {\n  for (let node: Element | null = el; node && node !== source; node = node.parentElement) {\n    const { position } = window.getComputedStyle(node);\n\n    if (position === 'fixed' || position === 'sticky') {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nexport function useRefractionFilter(\n  surfaceRef: RefObject<HTMLElement | null>,\n  {\n    displacement,\n    enabled,\n    reduceTransparency = false,\n    strength,\n    thickness,\n    viewportAligned = false,\n  }: RefractionFilterOptions,\n) {\n  const rawId = useId().replaceAll(':', '');\n  const filterId = `lg-refraction-${rawId}`;\n  const generationRef = useRef(0);\n  const warnedMixedRadiusRef = useRef(false);\n  const [state, setState] = useState<RefractionFilterState>({ href: null, metrics: null });\n  const sceneRef = useRef<HTMLDivElement | null>(null);\n  // Set while the scene is kept aligned, so a scene that mounts is aligned before it first paints.\n  const alignSceneNowRef = useRef<(() => void) | null>(null);\n\n  const setScene = useCallback((node: HTMLDivElement | null) => {\n    sceneRef.current = node;\n    alignSceneNowRef.current?.();\n  }, []);\n\n  useEffect(() => {\n    if (!enabled || reduceTransparency || !viewportAligned) {\n      return;\n    }\n\n    const el = surfaceRef.current;\n\n    if (!el) {\n      return;\n    }\n\n    let alignmentFrame = 0;\n    let disposed = false;\n    // Aligning a scene that no one can see only costs a layout, a style pass and a repaint on\n    // every scroll frame, so it waits until the surface is within a quarter screen of the viewport.\n    let nearViewport = true;\n\n    const timeline = supportsCompositedSceneScroll()\n      ? new ViewTimeline({ axis: 'block', subject: el })\n      : null;\n\n    let sceneScroll: Animation | null = null;\n    let sceneScrollSpan = Number.NaN;\n\n    // Keeps the scene's scroll-driven animation spanning the timeline and returns how far along\n    // the block axis it has carried the scene, which the offset below then leaves out. Past\n    // either end of the timeline the animation holds still, but the unclamped distance keeps the\n    // offset constant, so plain scrolling rewrites nothing and the scene never waits on script.\n    const followScroll = () => {\n      const scene = sceneRef.current;\n      const source = timeline?.source ?? null;\n\n      if (\n        !timeline ||\n        !scene ||\n        !source ||\n        timeline.currentTime === null ||\n        standsStillWhileScrolling(el, source)\n      ) {\n        sceneScroll?.cancel();\n        sceneScroll = null;\n\n        return 0;\n      }\n\n      const start = timeline.startOffset.to('px').value;\n      const span = timeline.endOffset.to('px').value - start;\n\n      if (sceneScroll?.effect instanceof KeyframeEffect && sceneScroll.effect.target === scene) {\n        if (span !== sceneScrollSpan) {\n          sceneScroll.effect.setKeyframes(sceneScrollKeyframes(span));\n        }\n      } else {\n        sceneScroll?.cancel();\n        sceneScroll = scene.animate(sceneScrollKeyframes(span), { fill: 'both', timeline });\n      }\n\n      sceneScrollSpan = span;\n\n      return source.scrollTop - start;\n    };\n\n    const writeProperty = (property: string, value: string) => {\n      if (el.style.getPropertyValue(property) !== value) {\n        el.style.setProperty(property, value);\n      }\n    };\n\n    const alignScene = () => {\n      alignmentFrame = 0;\n\n      if (disposed) {\n        return;\n      }\n\n      const followed = followScroll();\n      const rect = el.getBoundingClientRect();\n      writeProperty('--lg-refraction-scene-x', `${-(rect.left + el.clientLeft)}px`);\n      writeProperty('--lg-refraction-scene-y', `${-(rect.top + el.clientTop) - followed}px`);\n      writeProperty('--lg-refraction-scene-width', `${document.documentElement.clientWidth}px`);\n      writeProperty('--lg-refraction-scene-height', `${document.documentElement.clientHeight}px`);\n    };\n\n    const scheduleAlignment = () => {\n      if (nearViewport && !alignmentFrame) {\n        alignmentFrame = requestAnimationFrame(alignScene);\n      }\n    };\n\n    alignSceneNowRef.current = () => {\n      cancelAnimationFrame(alignmentFrame);\n      alignScene();\n    };\n\n    const visibilityObserver =\n      typeof IntersectionObserver === 'undefined'\n        ? null\n        : new IntersectionObserver(\n            (entries) => {\n              const wasNear = nearViewport;\n              nearViewport = entries[entries.length - 1]?.isIntersecting ?? true;\n\n              // Coming into range aligns at once, in this task. A frame asked for from here would run\n              // after whatever the page queued for the next frame, and that could read the scene\n              // where it still was.\n              if (nearViewport && !wasNear) {\n                cancelAnimationFrame(alignmentFrame);\n                alignScene();\n              }\n            },\n            { rootMargin: '25% 0px' },\n          );\n\n    visibilityObserver?.observe(el);\n\n    const resizeObserver =\n      typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(scheduleAlignment);\n\n    for (let ancestor: HTMLElement | null = el; ancestor; ancestor = ancestor.parentElement) {\n      resizeObserver?.observe(ancestor);\n    }\n\n    const readExternalStyleSignature = () => {\n      const declarations: string[] = [];\n\n      for (let index = 0; index < el.style.length; index += 1) {\n        const property = el.style.item(index);\n\n        if (property.startsWith('--lg-refraction-scene-')) {\n          continue;\n        }\n\n        declarations.push(\n          `${property}:${el.style.getPropertyValue(property)}:${el.style.getPropertyPriority(property)}`,\n        );\n      }\n\n      return declarations.sort().join(';');\n    };\n\n    let externalStyleSignature = readExternalStyleSignature();\n\n    const mutationObserver =\n      typeof MutationObserver === 'undefined'\n        ? null\n        : new MutationObserver((records) => {\n            const hasExternalMutation = records.some((record) => {\n              if (\n                record.type === 'attributes' &&\n                record.target === el &&\n                record.attributeName === 'style'\n              ) {\n                const nextExternalStyleSignature = readExternalStyleSignature();\n\n                if (nextExternalStyleSignature === externalStyleSignature) {\n                  return false;\n                }\n\n                externalStyleSignature = nextExternalStyleSignature;\n              }\n\n              return true;\n            });\n\n            if (hasExternalMutation) {\n              scheduleAlignment();\n            }\n          });\n\n    mutationObserver?.observe(document.body, {\n      attributeFilter: ['class', 'hidden', 'open', 'style'],\n      attributes: true,\n      characterData: true,\n      childList: true,\n      subtree: true,\n    });\n    document.addEventListener('scroll', scheduleAlignment, {\n      capture: true,\n      passive: true,\n    });\n    document.addEventListener('load', scheduleAlignment, {\n      capture: true,\n      passive: true,\n    });\n    document.fonts?.addEventListener('loadingdone', scheduleAlignment);\n    window.addEventListener('resize', scheduleAlignment, { passive: true });\n    window.visualViewport?.addEventListener('resize', scheduleAlignment, { passive: true });\n    window.visualViewport?.addEventListener('scroll', scheduleAlignment, { passive: true });\n    scheduleAlignment();\n\n    return () => {\n      disposed = true;\n      alignSceneNowRef.current = null;\n      cancelAnimationFrame(alignmentFrame);\n      sceneScroll?.cancel();\n      resizeObserver?.disconnect();\n      mutationObserver?.disconnect();\n      visibilityObserver?.disconnect();\n      document.removeEventListener('scroll', scheduleAlignment, { capture: true });\n      document.removeEventListener('load', scheduleAlignment, { capture: true });\n      document.fonts?.removeEventListener('loadingdone', scheduleAlignment);\n      window.removeEventListener('resize', scheduleAlignment);\n      window.visualViewport?.removeEventListener('resize', scheduleAlignment);\n      window.visualViewport?.removeEventListener('scroll', scheduleAlignment);\n      el.style.removeProperty('--lg-refraction-scene-height');\n      el.style.removeProperty('--lg-refraction-scene-width');\n      el.style.removeProperty('--lg-refraction-scene-x');\n      el.style.removeProperty('--lg-refraction-scene-y');\n    };\n  }, [enabled, reduceTransparency, surfaceRef, viewportAligned]);\n\n  useEffect(() => {\n    generationRef.current += 1;\n    const generation = generationRef.current;\n\n    if (!enabled || reduceTransparency || typeof ResizeObserver === 'undefined') {\n      queueMicrotask(() => {\n        if (generationRef.current === generation) {\n          setState({ href: null, metrics: null });\n        }\n      });\n\n      return;\n    }\n\n    const el = surfaceRef.current;\n\n    if (!el) {\n      queueMicrotask(() => {\n        if (generationRef.current === generation) {\n          setState({ href: null, metrics: null });\n        }\n      });\n\n      return;\n    }\n\n    let raf = 0;\n    let disposed = false;\n\n    const clear = () => {\n      if (generationRef.current === generation) {\n        setState({ href: null, metrics: null });\n      }\n    };\n\n    const schedule = () => {\n      cancelAnimationFrame(raf);\n      raf = requestAnimationFrame(() => {\n        const rect = el.getBoundingClientRect();\n        const width = Math.max(0, rect.width);\n        const height = Math.max(0, rect.height);\n\n        if (!width || !height) {\n          clear();\n\n          return;\n        }\n\n        const radius = readUniformRadius(el, width, height);\n\n        if (radius === null) {\n          if (!isProductionRuntime() && !warnedMixedRadiusRef.current) {\n            console.warn(\n              'Glassy UI supplied-background refraction currently supports uniform rounded rectangles only.',\n            );\n            warnedMixedRadiusRef.current = true;\n          }\n\n          clear();\n\n          return;\n        }\n\n        void buildRefractionMapDataUrl({\n          devicePixelRatio: window.devicePixelRatio || 1,\n          displacement,\n          height,\n          radius,\n          strength,\n          thickness,\n          width,\n        })\n          .then((result) => {\n            if (!disposed && generationRef.current === generation) {\n              setState({ href: result.href, metrics: result.metrics });\n            }\n          })\n          .catch(() => {\n            if (!disposed) {\n              clear();\n            }\n          });\n      });\n    };\n\n    const observer = new ResizeObserver(schedule);\n    observer.observe(el);\n    schedule();\n\n    return () => {\n      disposed = true;\n      cancelAnimationFrame(raf);\n      observer.disconnect();\n    };\n  }, [displacement, enabled, reduceTransparency, strength, surfaceRef, thickness]);\n\n  const filterElement = useMemo(() => {\n    if (!state.href || !state.metrics) return null;\n\n    const {\n      dispersion,\n      filterBlur,\n      filterPadding,\n      filterScale,\n      height,\n      paddedHeight,\n      paddedWidth,\n      width,\n    } = state.metrics;\n\n    // Every colour follows the same two-axis rim normal, so the sides split as much as the ends.\n    const channel = (name: string, matrix: string, factor: number) => (\n      <>\n        <feDisplacementMap\n          in=\"scene\"\n          in2=\"map\"\n          scale={Math.round(filterScale * factor * 100) / 100}\n          xChannelSelector=\"R\"\n          yChannelSelector=\"G\"\n          result={`${name}-scene`}\n        />\n        <feColorMatrix in={`${name}-scene`} type=\"matrix\" values={matrix} result={`${name}-bent`} />\n      </>\n    );\n\n    return (\n      <svg aria-hidden=\"true\" className=\"lg-refraction-filter\" focusable=\"false\" viewBox=\"0 0 0 0\">\n        <defs>\n          <filter\n            id={filterId}\n            x={-filterPadding}\n            y={-filterPadding}\n            width={paddedWidth}\n            height={paddedHeight}\n            filterUnits=\"userSpaceOnUse\"\n            primitiveUnits=\"userSpaceOnUse\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation={filterBlur} result=\"blurred\" />\n            <feImage\n              href={state.href}\n              x={-filterPadding}\n              y={-filterPadding}\n              width={width + filterPadding * 2}\n              height={height + filterPadding * 2}\n              preserveAspectRatio=\"none\"\n              result=\"map\"\n            />\n            {/* The map's blue channel softens only the scene the rim magnifies. */}\n            <feColorMatrix\n              in=\"map\"\n              type=\"matrix\"\n              values=\"0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 1 0 0\"\n              result=\"rim\"\n            />\n            <feComposite in=\"blurred\" in2=\"rim\" operator=\"in\" result=\"softRim\" />\n            <feComposite in=\"softRim\" in2=\"SourceGraphic\" operator=\"over\" result=\"scene\" />\n            {dispersion > 0 ? (\n              <>\n                {channel('red', '1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1', 1 - dispersion / 2)}\n                {channel('green', '0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1', 1)}\n                {channel('blue', '0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1', 1 + dispersion / 2)}\n                <feBlend in=\"red-bent\" in2=\"green-bent\" mode=\"screen\" result=\"red-green\" />\n                <feBlend in=\"red-green\" in2=\"blue-bent\" mode=\"screen\" result=\"rgb\" />\n                {/* Combine opaque colour, then restore coverage once so translucent scenes stay translucent. */}\n                <feComposite in=\"rgb\" in2=\"green-scene\" operator=\"in\" />\n              </>\n            ) : (\n              <feDisplacementMap\n                in=\"scene\"\n                in2=\"map\"\n                scale={filterScale}\n                xChannelSelector=\"R\"\n                yChannelSelector=\"G\"\n              />\n            )}\n          </filter>\n        </defs>\n      </svg>\n    );\n  }, [filterId, state.href, state.metrics]);\n\n  return {\n    active: Boolean(state.href && state.metrics),\n    filterElement,\n    filterStyle: state.href ? `url(#${filterId})` : undefined,\n    sceneRef: setScene,\n  };\n}\n\ntype LensFilterOptions = {\n  enabled: boolean;\n  /** Changes whenever theme, mode, quality or variant tokens may have changed. */\n  signature?: string;\n};\n\ntype LensFilterState = {\n  blur: number;\n  bodyBrightness: number;\n  dispersion: number;\n  href: string;\n  metrics: LensMapMetrics;\n  rimBrightness: number;\n  saturation: number;\n};\n\n/**\n * Only Chromium renders SVG references inside backdrop-filter. WebKit and\n * Gecko drop the whole declaration when url() is present, and CSS.supports()\n * reports true everywhere, so the engine is detected explicitly.\n */\nfunction supportsBackdropLens() {\n  return (\n    typeof navigator !== 'undefined' &&\n    'userAgentData' in navigator &&\n    typeof ResizeObserver !== 'undefined'\n  );\n}\n\nfunction readNumberToken(style: CSSStyleDeclaration, name: string) {\n  const value = Number.parseFloat(style.getPropertyValue(name));\n\n  return Number.isFinite(value) ? value : 0;\n}\n\nfunction readTokenOr(style: CSSStyleDeclaration, name: string, fallback: number) {\n  const value = Number.parseFloat(style.getPropertyValue(name));\n\n  return Number.isFinite(value) ? value : fallback;\n}\n\nfunction sameLens(previous: LensFilterState | null, next: LensFilterState) {\n  return (\n    previous !== null &&\n    previous.href === next.href &&\n    previous.blur === next.blur &&\n    previous.bodyBrightness === next.bodyBrightness &&\n    previous.dispersion === next.dispersion &&\n    previous.rimBrightness === next.rimBrightness &&\n    previous.saturation === next.saturation\n  );\n}\n\n// A rim as wide as the corners are round, up to a little wider than the WebGL scene's default.\nconst LENS_BEVEL = 36;\n\n// Slab height per pixel of rim, as in the scene (40 px of glass under a 28 px rim).\nconst LENS_THICKNESS_RATIO = 1.4;\n\n// The least frost the lens reads through, so bent edges never stair-step.\nconst MIN_FROST = 0.5;\n\n// Each colour follows the same rim normal: red bends least and blue most.\n// Both live-backdrop glass and the magnifier use this material pass.\nfunction refractColours(source: string, map: string, scale: number, dispersion: number) {\n  if (dispersion <= 0.005) {\n    return (\n      <feDisplacementMap\n        in={source}\n        in2={map}\n        scale={scale}\n        xChannelSelector=\"R\"\n        yChannelSelector=\"G\"\n        result=\"bent\"\n      />\n    );\n  }\n\n  const channel = (name: string, matrix: string, factor: number) => (\n    <>\n      <feDisplacementMap\n        in={source}\n        in2={map}\n        scale={Math.round(scale * factor * 100) / 100}\n        xChannelSelector=\"R\"\n        yChannelSelector=\"G\"\n        result={`${name}-scene`}\n      />\n      <feColorMatrix in={`${name}-scene`} type=\"matrix\" values={matrix} result={`${name}-bent`} />\n    </>\n  );\n\n  return (\n    <>\n      {channel('red', '1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1', 1 - dispersion / 2)}\n      {channel('green', '0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1', 1)}\n      {channel('blue', '0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1', 1 + dispersion / 2)}\n      <feBlend in=\"red-bent\" in2=\"green-bent\" mode=\"screen\" result=\"red-green\" />\n      <feBlend in=\"red-green\" in2=\"blue-bent\" mode=\"screen\" result=\"rgb\" />\n      {/* Combine opaque colour, then restore the reference channel's coverage once. */}\n      <feComposite in=\"rgb\" in2=\"green-scene\" operator=\"in\" result=\"bent\" />\n    </>\n  );\n}\n\n/**\n * Chromium-only lens for glass over live content. The backdrop is frosted, then read\n * through a slab of glass whose top curves down into a rounded rim, so content bends\n * and splits into colour along the edge and lies flat everywhere else. The\n * --lg-lens-* tokens shape it: depth, bevel, thickness, ior, dispersion, blur and\n * saturation, and --lg-glass-thickness scales the whole slab at once. Other engines,\n * zero depth and oversized surfaces keep the CSS glass.\n */\nexport function useLensFilter(\n  surfaceRef: RefObject<HTMLElement | null>,\n  { enabled, signature }: LensFilterOptions,\n) {\n  const rawId = useId().replaceAll(':', '');\n  const filterId = `lg-lens-${rawId}`;\n  const generationRef = useRef(0);\n  const [state, setState] = useState<LensFilterState | null>(null);\n\n  useEffect(() => {\n    generationRef.current += 1;\n    const generation = generationRef.current;\n    const el = surfaceRef.current;\n\n    if (!enabled || !el || !supportsBackdropLens()) {\n      queueMicrotask(() => {\n        if (generationRef.current === generation) {\n          setState(null);\n        }\n      });\n\n      return;\n    }\n\n    let frame = 0;\n    let disposed = false;\n\n    const measure = () => {\n      frame = 0;\n\n      if (disposed || generationRef.current !== generation) {\n        return;\n      }\n\n      // The lens layer fills the padding box, so borders and scrollbars stay out of the map.\n      const width = el.clientWidth;\n      const height = el.clientHeight;\n      const style = window.getComputedStyle(el);\n      const depth = readNumberToken(style, '--lg-lens-depth');\n\n      if (depth <= 0 || !width || !height || width * height > MAX_LENS_SURFACE_AREA) {\n        setState(null);\n\n        return;\n      }\n\n      const radii = readCornerRadii(style, width, height);\n      const roundest = Math.max(...radii);\n      const multiplier = clamp(readTokenOr(style, '--lg-glass-thickness', 1), 0.1, 4);\n      const bevelToken = readNumberToken(style, '--lg-lens-bevel');\n      const thicknessToken = readNumberToken(style, '--lg-lens-thickness');\n      const baseBevel = bevelToken > 0 ? bevelToken : clamp(roundest, 6, LENS_BEVEL);\n      // A thicker slab also has a wider rim, though not in proportion.\n      const bevel = clamp(baseBevel * (0.6 + 0.4 * multiplier), 4, Math.min(width, height) / 2);\n\n      const thickness =\n        (thicknessToken > 0 ? thicknessToken : baseBevel * LENS_THICKNESS_RATIO) * multiplier;\n\n      const { href, metrics } = buildLensMapDataUrl({\n        bevel,\n        depth,\n        devicePixelRatio: window.devicePixelRatio || 1,\n        height,\n        ior: Math.max(1, readTokenOr(style, '--lg-lens-ior', 1.5)),\n        radii,\n        thickness,\n        width,\n      });\n\n      const next = {\n        blur: Math.max(0, readNumberToken(style, '--lg-lens-blur')),\n        bodyBrightness: clamp(readTokenOr(style, '--lg-lens-body-brightness', 1), 0, 1),\n        dispersion: clamp(readNumberToken(style, '--lg-lens-dispersion') * multiplier, 0, 1),\n        href,\n        metrics,\n        rimBrightness: clamp(readTokenOr(style, '--lg-lens-rim-brightness', 1), 0, 1),\n        saturation: readNumberToken(style, '--lg-lens-saturation') || 1,\n      };\n\n      setState((previous) => (sameLens(previous, next) ? previous : next));\n    };\n\n    const schedule = () => {\n      if (!frame) {\n        frame = requestAnimationFrame(measure);\n      }\n    };\n\n    const observer = new ResizeObserver(schedule);\n    observer.observe(el);\n    schedule();\n\n    return () => {\n      disposed = true;\n      cancelAnimationFrame(frame);\n      observer.disconnect();\n    };\n  }, [enabled, signature, surfaceRef]);\n\n  const filterElement = useMemo(() => {\n    if (!state) return null;\n\n    const { blur, bodyBrightness, dispersion, href, metrics, rimBrightness, saturation } = state;\n    // The rim is the same frosted glass as the flat top: it bends and splits what the frost\n    // leaves and is never clearer, so content does not show through a sharp band at the edge.\n    const frost = Math.max(blur, MIN_FROST);\n    const padding = Math.ceil(frost * 3) + 2;\n    // Map channels hold a unit offset; the scale turns it into CSS pixels.\n    const scale = Math.round(2 * metrics.reach * 100) / 100;\n\n    // Smoked glass darkens the flat top more than its rim, which lets the light through. A\n    // primitive is added only where it changes something: each one is another pass on the GPU.\n    const dim = (input: string, brightness: number, result: string) =>\n      brightness < 1 ? (\n        <feComponentTransfer in={input} result={result}>\n          <feFuncR type=\"linear\" slope={brightness} />\n          <feFuncG type=\"linear\" slope={brightness} />\n          <feFuncB type=\"linear\" slope={brightness} />\n        </feComponentTransfer>\n      ) : null;\n\n    // The map's blue channel weights the rim's light over the top's. Where both let the same\n    // light through, the bent frost alone is the glass.\n    const shaded = rimBrightness !== bodyBrightness;\n    const rimSource = rimBrightness < 1 ? 'rim-lit' : 'bent';\n    const topSource = bodyBrightness < 1 ? 'top-lit' : 'bent';\n    const glass = shaded ? 'mixed' : topSource;\n\n    return (\n      <svg aria-hidden=\"true\" className=\"lg-refraction-filter\" focusable=\"false\" viewBox=\"0 0 0 0\">\n        <defs>\n          <filter\n            id={filterId}\n            x={-padding}\n            y={-padding}\n            width={metrics.width + padding * 2}\n            height={metrics.height + padding * 2}\n            filterUnits=\"userSpaceOnUse\"\n            primitiveUnits=\"userSpaceOnUse\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation={frost} result=\"frost\" />\n            <feImage\n              href={href}\n              x={0}\n              y={0}\n              width={metrics.width}\n              height={metrics.height}\n              preserveAspectRatio=\"none\"\n              result=\"lens\"\n            />\n            {refractColours('frost', 'lens', scale, dispersion)}\n            {shaded ? (\n              <>\n                <feColorMatrix\n                  in=\"lens\"\n                  type=\"matrix\"\n                  values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0\"\n                  result=\"rim\"\n                />\n                {dim('bent', rimBrightness, 'rim-lit')}\n                {dim('bent', bodyBrightness, 'top-lit')}\n                <feComposite in={rimSource} in2=\"rim\" operator=\"in\" result=\"rim-glass\" />\n                {/* The rim, weighted by its mask, over the top, which is opaque: the mask's\n                    complement is implied, so the top needs no mask of its own. */}\n                <feComposite in=\"rim-glass\" in2={topSource} operator=\"over\" result=\"mixed\" />\n              </>\n            ) : (\n              dim('bent', bodyBrightness, 'top-lit')\n            )}\n            {saturation === 1 ? null : (\n              <feColorMatrix in={glass} type=\"saturate\" values={String(saturation)} />\n            )}\n          </filter>\n        </defs>\n      </svg>\n    );\n  }, [filterId, state]);\n\n  return {\n    active: state !== null,\n    filterElement,\n    filterStyle: state ? `url(#${filterId})` : undefined,\n  };\n}\n\ntype MagnifierFilterOptions = LensFilterOptions & {\n  /** Diameter of the round lens in CSS pixels. */\n  size: number;\n};\n\n// Softens the compressed rim just enough to keep sharp edges from stair-stepping.\nconst MAGNIFIER_BLUR = 0.35;\n\n/**\n * Rim refraction for the round magnifier, applied with `filter` to its zoomed view, which\n * every engine with SVG filters on HTML supports. Near the rim each pixel samples content\n * from further out, so the view bends into the edge like a glass bead.\n *\n * The view extends `margin` past the lens on every side so the rim has content to sample.\n * WebKit only lines the filtered view up with the map when the view clips its own overflow\n * and the filter region reaches past it by as far as the primitives can read, so the\n * region is the view plus another margin and the map sits one margin in.\n */\nexport function useMagnifierFilter(\n  surfaceRef: RefObject<HTMLElement | null>,\n  { enabled, signature, size }: MagnifierFilterOptions,\n) {\n  const rawId = useId().replaceAll(':', '');\n  const filterId = `lg-magnifier-${rawId}`;\n  const [map, setMap] = useState<(MagnifierMapBuildResult & { dispersion: number }) | null>(null);\n\n  // Blue reaches furthest; leave enough scene beyond the rim for it and the blur.\n  const margin =\n    Math.ceil(\n      computeMagnifierMapMetrics({ size }).depth * (1 + (map?.dispersion ?? 0) / 2) +\n        MAGNIFIER_BLUR * 3,\n    ) + 2;\n\n  useEffect(() => {\n    let disposed = false;\n\n    // The map is drawn on a canvas, so it can only be built in the browser.\n    queueMicrotask(() => {\n      if (disposed) return;\n\n      const surface = surfaceRef.current;\n\n      if (!enabled || !surface) {\n        setMap(null);\n\n        return;\n      }\n\n      const style = window.getComputedStyle(surface);\n      const thickness = clamp(readTokenOr(style, '--lg-glass-thickness', 1), 0.1, 4);\n      setMap({\n        ...buildMagnifierMapDataUrl({ devicePixelRatio: window.devicePixelRatio || 1, size }),\n        dispersion: clamp(readNumberToken(style, '--lg-lens-dispersion') * thickness, 0, 1),\n      });\n    });\n\n    return () => {\n      disposed = true;\n    };\n  }, [enabled, signature, size, surfaceRef]);\n\n  const filterElement = useMemo(() => {\n    if (!map) return null;\n\n    const {\n      dispersion,\n      metrics: { depth, size: mapCssSize },\n    } = map;\n\n    return (\n      <svg aria-hidden=\"true\" className=\"lg-refraction-filter\" focusable=\"false\" viewBox=\"0 0 0 0\">\n        <defs>\n          <filter\n            id={filterId}\n            x={-margin}\n            y={-margin}\n            width={mapCssSize + margin * 4}\n            height={mapCssSize + margin * 4}\n            filterUnits=\"userSpaceOnUse\"\n            primitiveUnits=\"userSpaceOnUse\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation={MAGNIFIER_BLUR} result=\"soft\" />\n            <feImage\n              href={map.href}\n              x={margin}\n              y={margin}\n              width={mapCssSize}\n              height={mapCssSize}\n              preserveAspectRatio=\"none\"\n              result=\"bend\"\n            />\n            {refractColours('soft', 'bend', depth * 2, dispersion)}\n          </filter>\n        </defs>\n      </svg>\n    );\n  }, [filterId, map, margin]);\n\n  return {\n    active: map !== null,\n    filterElement,\n    filterStyle: map ? `url(#${filterId})` : undefined,\n    margin,\n  };\n}\n\ntype CapsuleLensFilterOptions = LensFilterOptions & {\n  /** Colour split at the rim for regular glass; --lg-glass-thickness scales it. */\n  dispersion: number;\n  /**\n   * How many times the capsule's size the filtered copy is laid out at before it is scaled\n   * back down. WebKit filters at layout resolution, so a small lens bends in steps without it.\n   */\n  supersample?: number;\n};\n\ntype CapsuleLensState = CapsuleLensMapBuildResult & { dispersion: number };\n\n/**\n * Rim refraction for a capsule lens, such as a switch knob, applied with `filter` to a copy\n * of what lies under it. Every engine with SVG filters on HTML runs it. Near the rim each\n * pixel samples the copy from further out, so it bends into the edge like a bead of glass\n * and splits into colour there; --lg-glass-thickness deepens both. The copy must reach\n * `margin` past the capsule on every side so the rim has content to sample, and the filter\n * region reaches one margin further, as the round magnifier's does.\n */\nexport function useCapsuleLensFilter(\n  surfaceRef: RefObject<HTMLElement | null>,\n  { dispersion, enabled, signature, supersample = 1 }: CapsuleLensFilterOptions,\n) {\n  const rawId = useId().replaceAll(':', '');\n  const filterId = `lg-capsule-${rawId}`;\n  const [map, setMap] = useState<CapsuleLensState | null>(null);\n\n  useEffect(() => {\n    const el = surfaceRef.current;\n\n    if (!enabled || !el) {\n      queueMicrotask(() => setMap(null));\n\n      return;\n    }\n\n    let frame = 0;\n    let disposed = false;\n\n    const measure = () => {\n      frame = 0;\n\n      if (disposed) return;\n\n      const width = el.clientWidth;\n      const height = el.clientHeight;\n\n      if (!width || !height) {\n        setMap(null);\n\n        return;\n      }\n\n      const thickness = clamp(\n        readTokenOr(window.getComputedStyle(el), '--lg-glass-thickness', 1),\n        0.1,\n        4,\n      );\n\n      const next = {\n        // A supersampled copy already has the resolution the map needs.\n        ...buildCapsuleLensMapDataUrl({\n          devicePixelRatio: supersample > 1 ? 1 : window.devicePixelRatio || 1,\n          height: height * supersample,\n          thickness,\n          width: width * supersample,\n        }),\n        dispersion: clamp(dispersion * thickness, 0, 1),\n      };\n\n      setMap((previous) =>\n        previous &&\n        previous.href === next.href &&\n        previous.metrics.depth === next.metrics.depth &&\n        previous.dispersion === next.dispersion\n          ? previous\n          : next,\n      );\n    };\n\n    const schedule = () => {\n      if (!frame) frame = requestAnimationFrame(measure);\n    };\n\n    const observer = new ResizeObserver(schedule);\n    observer.observe(el);\n    schedule();\n\n    return () => {\n      disposed = true;\n      cancelAnimationFrame(frame);\n      observer.disconnect();\n    };\n  }, [dispersion, enabled, signature, supersample, surfaceRef]);\n\n  // Blue reaches furthest; leave enough content beyond the rim for it and the blur. The\n  // filter works in the copy's supersampled pixels; the margin is returned in CSS pixels,\n  // rounded up to a whole one so the scaled copy lands on the knob without a sliver.\n  const blur = MAGNIFIER_BLUR * supersample;\n\n  const scaledMargin = map\n    ? Math.ceil(\n        (map.metrics.depth * (1 + map.dispersion / 2) + blur * 3 + 2 * supersample) / supersample,\n      ) * supersample\n    : 0;\n\n  const filterElement = useMemo(() => {\n    if (!map) return null;\n\n    const { depth, height, width } = map.metrics;\n    const margin = scaledMargin;\n\n    return (\n      <svg aria-hidden=\"true\" className=\"lg-refraction-filter\" focusable=\"false\" viewBox=\"0 0 0 0\">\n        <defs>\n          <filter\n            id={filterId}\n            x={-margin}\n            y={-margin}\n            width={width + margin * 4}\n            height={height + margin * 4}\n            filterUnits=\"userSpaceOnUse\"\n            primitiveUnits=\"userSpaceOnUse\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feGaussianBlur in=\"SourceGraphic\" stdDeviation={blur} result=\"blurred\" />\n            <feImage\n              href={map.href}\n              x={margin}\n              y={margin}\n              width={width}\n              height={height}\n              preserveAspectRatio=\"none\"\n              result=\"bend\"\n            />\n            {/* The map's blue channel softens only the scene the rim bends. */}\n            <feColorMatrix\n              in=\"bend\"\n              type=\"matrix\"\n              values=\"0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 1 0 0\"\n              result=\"rim\"\n            />\n            <feComposite in=\"blurred\" in2=\"rim\" operator=\"in\" result=\"softRim\" />\n            <feComposite in=\"softRim\" in2=\"SourceGraphic\" operator=\"over\" result=\"soft\" />\n            {refractColours('soft', 'bend', depth * 2, map.dispersion)}\n          </filter>\n        </defs>\n      </svg>\n    );\n  }, [blur, filterId, map, scaledMargin]);\n\n  return {\n    active: map !== null,\n    filterElement,\n    filterStyle: map ? `url(#${filterId})` : undefined,\n    margin: scaledMargin / supersample,\n    supersample,\n  };\n}\n",
      "type": "registry:hook",
      "target": "components/glassyui/lib/use-refraction-filter.tsx"
    },
    {
      "path": "registry/glassyui/lib/scene-context.ts",
      "content": "'use client';\n\nimport { createContext, useContext, useLayoutEffect } from 'react';\nimport type { RefObject } from 'react';\n\nexport type GlassSceneRegistry = {\n  elements: ReadonlySet<HTMLElement>;\n  register: (element: HTMLElement) => () => void;\n  subscribe: (listener: () => void) => () => void;\n};\n\n// The registry exists before any effect runs, because children register in\n// their effects and those run before the scene's own effect.\nexport function createGlassSceneRegistry(): GlassSceneRegistry {\n  const elements = new Set<HTMLElement>();\n  const listeners = new Set<() => void>();\n  const notify = () => listeners.forEach((listener) => listener());\n\n  return {\n    elements,\n    register(element) {\n      elements.add(element);\n      notify();\n\n      return () => {\n        elements.delete(element);\n        notify();\n      };\n    },\n    subscribe(listener) {\n      listeners.add(listener);\n\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n  };\n}\n\nexport const GlassSceneContext = createContext<GlassSceneRegistry | null>(null);\n\n/**\n * Lets a glass element hand its material to the nearest GlassScene. Outside a\n * scene it does nothing. It registers before paint, so glass that mounts into\n * a live scene never shows its CSS material first.\n */\nexport function useGlassScenePane(ref: RefObject<HTMLElement | null>, enabled = true) {\n  const registry = useContext(GlassSceneContext);\n\n  useLayoutEffect(() => {\n    const element = ref.current;\n\n    if (!registry || !enabled || !element) return;\n\n    return registry.register(element);\n  }, [enabled, ref, registry]);\n}\n",
      "type": "registry:lib",
      "target": "components/glassyui/lib/scene-context.ts"
    },
    {
      "path": "registry/glassyui/glass-panel.tsx",
      "content": "'use client';\n\nimport clsx from 'clsx';\nimport { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactNode } from 'react';\nimport { GlassSurface } from './glass-surface';\nimport type { RefractionBackgroundSpace } from './glass-surface';\n\ntype PanelVariant = 'panel' | 'card' | 'pill';\n\ntype PanelElevation = 'base' | 'raised';\n\ntype PanelRefraction = 'none' | 'soft' | 'strong';\n\ntype PanelPreset = 'subtle' | 'standard' | 'prominent' | 'control' | 'showcase';\n\nexport type GlassPanelProps = HTMLAttributes<HTMLDivElement> & {\n  variant?: PanelVariant;\n  elevation?: PanelElevation;\n  /** Brightens the surface a touch on hover. */\n  interactive?: boolean;\n  /** Chromium edge lens over the frost. On by default for control and showcase presets. */\n  lens?: boolean;\n  preset?: PanelPreset;\n  refraction?: PanelRefraction;\n  refractionBackground?: ReactNode;\n  refractionBackgroundSpace?: RefractionBackgroundSpace;\n  refractionDisplacement?: number;\n  /** How thick the glass is, as a multiple of regular. See GlassSurface. */\n  thickness?: number;\n};\n\n// Shape, spacing and elevation live in liquid.css, below Tailwind utilities.\nconst VARIANT_CLASS: Record<PanelVariant, string> = {\n  panel: 'lg-panel--panel',\n  card: 'lg-panel--card',\n  pill: 'lg-panel--pill',\n};\n\nexport const GlassPanel = forwardRef<HTMLDivElement, GlassPanelProps>(\n  (\n    {\n      className,\n      variant = 'panel',\n      elevation = 'base',\n      interactive = false,\n      lens,\n      preset = variant === 'pill' ? 'control' : 'standard',\n      refraction = preset === 'showcase' ? 'strong' : 'none',\n      refractionBackground,\n      refractionBackgroundSpace,\n      refractionDisplacement,\n      ...rest\n    },\n    forwardedRef,\n  ) => {\n    return (\n      <GlassSurface\n        ref={forwardedRef}\n        interactive={interactive}\n        lens={lens}\n        preset={preset}\n        refraction={refraction}\n        refractionBackground={refractionBackground}\n        refractionBackgroundSpace={refractionBackgroundSpace}\n        refractionDisplacement={refractionDisplacement}\n        className={clsx(\n          VARIANT_CLASS[variant],\n          elevation === 'raised' && 'lg-panel--raised',\n          className,\n        )}\n        {...rest}\n      />\n    );\n  },\n);\n\nGlassPanel.displayName = 'GlassPanel';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-panel.tsx"
    },
    {
      "path": "registry/glassyui/glass-accordion.tsx",
      "content": "'use client';\n\nimport * as AccordionPrimitive from '@radix-ui/react-accordion';\nimport clsx from 'clsx';\nimport { GlassPanel } from './glass-panel';\n\nconst radixSlotProps = { asChild: true };\n\nexport const GlassAccordion = AccordionPrimitive.Root;\n\nexport const GlassAccordionItem = AccordionPrimitive.Item;\n\nexport function GlassAccordionTrigger(\n  props: React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>,\n) {\n  const { className, children, ...rest } = props;\n\n  return (\n    <AccordionPrimitive.Header>\n      <AccordionPrimitive.Trigger\n        {...rest}\n        className={clsx('lg-button lg-accordion-trigger', className)}\n      >\n        <span>{children}</span>\n        <svg\n          viewBox=\"0 0 20 20\"\n          className=\"lg-accordion-chevron h-4 w-4 text-[var(--lg-muted-foreground)]\"\n          aria-hidden\n        >\n          <path\n            d=\"M5 7.5l5 5 5-5\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.6\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      </AccordionPrimitive.Trigger>\n    </AccordionPrimitive.Header>\n  );\n}\n\nexport function GlassAccordionContent(\n  props: React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>,\n) {\n  const { className, children, ...rest } = props;\n\n  return (\n    <AccordionPrimitive.Content {...rest} {...radixSlotProps}>\n      <GlassPanel\n        variant=\"card\"\n        elevation=\"base\"\n        refraction=\"soft\"\n        className={clsx('lg-accordion-content', className)}\n      >\n        {children}\n      </GlassPanel>\n    </AccordionPrimitive.Content>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-accordion.tsx"
    },
    {
      "path": "registry/glassyui/glass-avatar.tsx",
      "content": "import * as AvatarPrimitive from '@radix-ui/react-avatar';\nimport clsx from 'clsx';\nimport { Children, forwardRef } from 'react';\nimport type { HTMLAttributes, ReactNode } from 'react';\n\nexport type GlassAvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\nexport type GlassAvatarStatus = 'online' | 'away' | 'busy' | 'offline';\n\nexport type GlassAvatarProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {\n  /** Describes the image. Defaults to `name`. */\n  alt?: string;\n  /** Shown when there is no image or it fails to load. Defaults to the initials of `name`. */\n  fallback?: ReactNode;\n  /** Names the person. Its initials fill in for a missing image and pick the colour. */\n  name?: string;\n  size?: GlassAvatarSize;\n  src?: string;\n  /** A presence dot on the corner. */\n  status?: GlassAvatarStatus;\n};\n\n// The number of colours the stylesheet defines for initials.\nconst TONES = 8;\n\nfunction initialsOf(name: string) {\n  const words = name.trim().split(/\\s+/).filter(Boolean);\n\n  if (words.length === 0) return '';\n\n  const first = Array.from(words[0])[0] ?? '';\n  const last = words.length > 1 ? (Array.from(words[words.length - 1])[0] ?? '') : '';\n\n  return `${first}${last}`.toUpperCase();\n}\n\n// The same name always gets the same colour.\nfunction toneOf(name: string) {\n  let hash = 0;\n\n  for (const character of name) hash = (hash * 31 + (character.codePointAt(0) ?? 0)) >>> 0;\n\n  return hash % TONES;\n}\n\n/** A round photo that falls back to coloured initials. */\nexport const GlassAvatar = forwardRef<HTMLSpanElement, GlassAvatarProps>(\n  ({ alt, className, fallback, name, size = 'md', src, status, ...props }, forwardedRef) => {\n    const described = [alt ?? name, status].filter(Boolean).join(', ');\n\n    return (\n      <span\n        aria-label={described || undefined}\n        role={described ? 'img' : undefined}\n        {...props}\n        ref={forwardedRef}\n        className={clsx('lg-avatar', className)}\n        data-size={size}\n        data-status={status}\n      >\n        <AvatarPrimitive.Root className=\"lg-avatar-face\">\n          {src ? <AvatarPrimitive.Image alt=\"\" className=\"lg-avatar-image\" src={src} /> : null}\n          <AvatarPrimitive.Fallback\n            aria-hidden\n            className=\"lg-avatar-fallback\"\n            data-tone={name ? toneOf(name) : undefined}\n          >\n            {fallback ?? (name ? initialsOf(name) : null)}\n          </AvatarPrimitive.Fallback>\n        </AvatarPrimitive.Root>\n        {status ? <span aria-hidden className=\"lg-avatar-status\" /> : null}\n      </span>\n    );\n  },\n);\n\nGlassAvatar.displayName = 'GlassAvatar';\n\nexport type GlassAvatarGroupProps = HTMLAttributes<HTMLDivElement> & {\n  /** Shows this many avatars, then a \"+N\" for the rest. */\n  max?: number;\n  /** Sizes every avatar in the group. */\n  size?: GlassAvatarSize;\n};\n\n/** Avatars stacked edge to edge, each cut away where the next one overlaps it. */\nexport const GlassAvatarGroup = forwardRef<HTMLDivElement, GlassAvatarGroupProps>(\n  ({ children, className, max, size = 'md', ...props }, forwardedRef) => {\n    const items = Children.toArray(children);\n    const shown = max === undefined ? items : items.slice(0, Math.max(0, max));\n    const hidden = items.length - shown.length;\n\n    return (\n      <div\n        role=\"group\"\n        {...props}\n        ref={forwardedRef}\n        className={clsx('lg-avatar-group', className)}\n        data-size={size}\n      >\n        {shown}\n        {hidden > 0 ? (\n          <span aria-label={`${hidden} more`} className=\"lg-avatar\" data-size={size} role=\"img\">\n            <span aria-hidden className=\"lg-avatar-face lg-avatar-more\">\n              +{hidden}\n            </span>\n          </span>\n        ) : null}\n      </div>\n    );\n  },\n);\n\nGlassAvatarGroup.displayName = 'GlassAvatarGroup';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-avatar.tsx"
    },
    {
      "path": "registry/glassyui/glass-badge.tsx",
      "content": "import './styles/liquid.css';\n\nimport clsx from 'clsx';\nimport { forwardRef } from 'react';\nimport type { HTMLAttributes } from 'react';\n\nexport type GlassBadgeTone = 'neutral' | 'blue' | 'green' | 'orange' | 'red' | 'purple';\n\nexport type GlassBadgeProps = HTMLAttributes<HTMLSpanElement> & {\n  /** Leading status dot in the tone colour. */\n  dot?: boolean;\n  tone?: GlassBadgeTone;\n};\n\nexport const GlassBadge = forwardRef<HTMLSpanElement, GlassBadgeProps>(\n  ({ children, className, dot = false, tone = 'neutral', ...props }, forwardedRef) => (\n    <span ref={forwardedRef} data-tone={tone} className={clsx('lg-badge', className)} {...props}>\n      {dot ? <span aria-hidden className=\"lg-badge-dot\" /> : null}\n      {children}\n    </span>\n  ),\n);\n\nGlassBadge.displayName = 'GlassBadge';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-badge.tsx"
    },
    {
      "path": "registry/glassyui/glass-button.tsx",
      "content": "'use client';\n\nimport { Slot, Slottable } from '@radix-ui/react-slot';\nimport clsx from 'clsx';\nimport {\n  cloneElement,\n  forwardRef,\n  isValidElement,\n  use,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n} from 'react';\nimport type { CSSProperties, ReactNode } from 'react';\nimport { mergeRefs } from './lib/merge-refs';\nimport { useGlassScenePane } from './lib/scene-context';\nimport { useLensFilter } from './lib/use-refraction-filter';\nimport { useOptionalPreferences } from './glass-provider';\n\nexport type ButtonVariant = 'primary' | 'prominent' | 'ghost' | 'soft';\n\nexport type ButtonSize = 'sm' | 'md' | 'lg';\n\ntype ButtonPreset = 'control' | 'showcase';\n\nexport interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** Renders the single child element, such as a link, with the button's material. Links cannot be disabled. */\n  asChild?: boolean;\n  /** Chromium edge lens over the frost. On by default for filled variants. */\n  lens?: boolean;\n  preset?: ButtonPreset;\n  variant?: ButtonVariant;\n  size?: ButtonSize;\n}\n\nconst VARIANT_CLASS: Record<ButtonVariant, string> = {\n  primary: 'lg-button--primary',\n  prominent: 'lg-button--prominent',\n  ghost: 'lg-button--ghost',\n  soft: 'lg-button--soft',\n};\n\nconst SIZE_CLASS: Record<ButtonSize, string> = {\n  sm: 'lg-button--sm',\n  md: 'lg-button--md',\n  lg: 'lg-button--lg',\n};\n\nconst REACT_LAZY = Symbol.for('react.lazy');\n\n// A server component's element can reach a client component as a lazy reference, which React 19\n// does in development; its payload is the chunk the element arrives in, a kind of Promise. Slot\n// unwraps one before slotting it, so the label has to as well.\nfunction isLazyNode(node: unknown): node is { _payload: Promise<ReactNode> } {\n  return (\n    node instanceof Object &&\n    '$$typeof' in node &&\n    node.$$typeof === REACT_LAZY &&\n    '_payload' in node &&\n    node._payload instanceof Promise\n  );\n}\n\n// The label sits above the material layers, so a slotted child's content moves into it.\nfunction withLabel(children: ReactNode) {\n  if (!isValidElement<{ children?: ReactNode }>(children)) return children;\n\n  return cloneElement(\n    children,\n    undefined,\n    <span className=\"lg-button__label\">{children.props.children}</span>,\n  );\n}\n\nexport const GlassButton = forwardRef<HTMLButtonElement, ButtonProps>(\n  (\n    {\n      asChild = false,\n      className,\n      preset = 'control',\n      variant = 'primary',\n      lens = variant !== 'ghost',\n      size = 'md',\n      type,\n      disabled = false,\n      onPointerLeave,\n      onPointerCancel,\n      onPointerDown,\n      onPointerUp,\n      onBlur,\n      onKeyDown,\n      onKeyUp,\n      style,\n      children,\n      ...rest\n    },\n    forwardedRef,\n  ) => {\n    const { preferences } = useOptionalPreferences();\n    const reduceMotion = Boolean(preferences?.reduceMotion);\n    const reduceTransparency = Boolean(preferences?.reduceTransparency);\n    const buttonRef = useRef<HTMLButtonElement | null>(null);\n    const bloomRef = useRef<HTMLSpanElement | null>(null);\n    const ref = useMemo(() => mergeRefs(forwardedRef, buttonRef), [forwardedRef]);\n\n    const releasePress = useCallback((target: HTMLButtonElement) => {\n      target.style.setProperty('--lg-press', '0');\n      target.dataset.pressed = 'false';\n    }, []);\n\n    // A press lights the glass from the point it lands and the light spreads\n    // outward, as it does through Apple's glass under a fingertip.\n    const bloom = useCallback(\n      (target: HTMLElement, x: number, y: number) => {\n        const element = bloomRef.current;\n\n        if (!element || reduceMotion) return;\n\n        element.style.setProperty('--lg-bloom-x', `${x}px`);\n        element.style.setProperty('--lg-bloom-y', `${y}px`);\n        element.style.setProperty(\n          '--lg-bloom-size',\n          `${Math.max(target.offsetWidth, target.offsetHeight) * 2.4}px`,\n        );\n        element.animate?.(\n          [\n            { opacity: 0, transform: 'scale(0.08)' },\n            { opacity: 1, transform: 'scale(0.42)', offset: 0.16 },\n            { opacity: 0, transform: 'scale(1)' },\n          ],\n          { duration: 720, easing: 'cubic-bezier(0.22, 0.8, 0.3, 1)' },\n        );\n      },\n      [reduceMotion],\n    );\n\n    const handlePointerLeave = useCallback(\n      (event: React.PointerEvent<HTMLButtonElement>) => {\n        onPointerLeave?.(event);\n        event.currentTarget.releasePointerCapture?.(event.pointerId);\n        releasePress(event.currentTarget);\n      },\n      [onPointerLeave, releasePress],\n    );\n\n    const handlePointerCancel = useCallback(\n      (event: React.PointerEvent<HTMLButtonElement>) => {\n        onPointerCancel?.(event);\n        event.currentTarget.releasePointerCapture?.(event.pointerId);\n        releasePress(event.currentTarget);\n      },\n      [onPointerCancel, releasePress],\n    );\n\n    const handlePointerDown = useCallback(\n      (event: React.PointerEvent<HTMLButtonElement>) => {\n        onPointerDown?.(event);\n\n        if (event.defaultPrevented || disabled) return;\n        const target = event.currentTarget;\n        const bounds = target.getBoundingClientRect();\n        // A scaled button maps client pixels back to its own, so the bloom starts under the finger.\n        const scale = bounds.width / Math.max(target.offsetWidth, 1) || 1;\n\n        target.setPointerCapture?.(event.pointerId);\n        target.style.setProperty('--lg-press', '1');\n        target.dataset.pressed = 'true';\n        bloom(target, (event.clientX - bounds.left) / scale, (event.clientY - bounds.top) / scale);\n      },\n      [bloom, disabled, onPointerDown],\n    );\n\n    const handlePointerUp = useCallback(\n      (event: React.PointerEvent<HTMLButtonElement>) => {\n        onPointerUp?.(event);\n        event.currentTarget.releasePointerCapture?.(event.pointerId);\n        event.currentTarget.style.setProperty('--lg-press', '0');\n        event.currentTarget.dataset.pressed = 'false';\n      },\n      [onPointerUp],\n    );\n\n    const handleBlur = useCallback(\n      (event: React.FocusEvent<HTMLButtonElement>) => {\n        onBlur?.(event);\n        releasePress(event.currentTarget);\n      },\n      [onBlur, releasePress],\n    );\n\n    const handleKeyDown = useCallback(\n      (event: React.KeyboardEvent<HTMLButtonElement>) => {\n        onKeyDown?.(event);\n\n        if (event.defaultPrevented || disabled) return;\n\n        if (event.key === ' ' || event.key === 'Enter') {\n          const target = event.currentTarget;\n\n          target.style.setProperty('--lg-press', '1');\n          target.dataset.pressed = 'true';\n\n          if (!event.repeat) bloom(target, target.offsetWidth / 2, target.offsetHeight / 2);\n        }\n      },\n      [bloom, disabled, onKeyDown],\n    );\n\n    const handleKeyUp = useCallback(\n      (event: React.KeyboardEvent<HTMLButtonElement>) => {\n        onKeyUp?.(event);\n\n        if (event.key === ' ' || event.key === 'Enter') {\n          event.currentTarget.style.setProperty('--lg-press', '0');\n          event.currentTarget.dataset.pressed = 'false';\n        }\n      },\n      [onKeyUp],\n    );\n\n    // A button disabled mid-press lets go of it.\n    useEffect(() => {\n      if (disabled && buttonRef.current) releasePress(buttonRef.current);\n    }, [disabled, releasePress]);\n\n    // Inside a GlassScene the canvas draws this glass. Ghost buttons have none.\n    useGlassScenePane(buttonRef, variant !== 'ghost');\n\n    const lensFilter = useLensFilter(buttonRef, {\n      enabled: lens && !disabled && !reduceTransparency,\n      signature: `${preferences?.theme}:${preferences?.mode}:${preferences?.quality}:${preferences?.thickness}`,\n    });\n\n    // Without this, the client renders a lazy child's content outside the label: it sinks under\n    // the material layers, and hydration fails.\n    const slotted = asChild && isLazyNode(children) ? use(children._payload) : children;\n\n    const Component = asChild ? Slot : 'button';\n\n    return (\n      <Component\n        {...rest}\n        ref={ref}\n        type={asChild ? type : (type ?? 'button')}\n        disabled={asChild ? undefined : disabled}\n        className={clsx('lg-button', VARIANT_CLASS[variant], SIZE_CLASS[size], className)}\n        data-lg-lens={lensFilter.active ? 'active' : undefined}\n        data-lg-preset={preset}\n        style={\n          // SAFETY: CSSProperties has no custom-property index; this adds one string-valued property.\n          lensFilter.filterStyle\n            ? ({ ...style, '--lg-lens-filter': lensFilter.filterStyle } as CSSProperties)\n            : style\n        }\n        onPointerLeave={handlePointerLeave}\n        onPointerCancel={handlePointerCancel}\n        onPointerDown={handlePointerDown}\n        onPointerUp={handlePointerUp}\n        onBlur={handleBlur}\n        onKeyDown={handleKeyDown}\n        onKeyUp={handleKeyUp}\n        data-pressed=\"false\"\n      >\n        <span className=\"lg-button__layers\" aria-hidden=\"true\">\n          {lensFilter.filterElement}\n          <span className=\"lg-button__light\" />\n          <span className=\"lg-button__distortion\" />\n          <span className=\"lg-button__tint\" />\n          <span className=\"lg-button__shine\" />\n          <span ref={bloomRef} className=\"lg-button__bloom\" />\n          <span className=\"lg-button__rim\" />\n        </span>\n        {asChild ? (\n          <Slottable>{withLabel(slotted)}</Slottable>\n        ) : (\n          <span className=\"lg-button__label\">{children}</span>\n        )}\n      </Component>\n    );\n  },\n);\n\nGlassButton.displayName = 'GlassButton';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-button.tsx"
    },
    {
      "path": "registry/glassyui/glass-header.tsx",
      "content": "'use client';\n\nimport clsx from 'clsx';\nimport { forwardRef, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { HTMLAttributes, ReactNode, RefObject } from 'react';\nimport {\n  createFrameLoop,\n  createSpring,\n  motionReduced,\n  retuneSpring,\n  stepSpring,\n} from './lib/liquid-motion';\nimport { mergeRefs } from './lib/merge-refs';\nimport { GlassSurface } from './glass-surface';\nimport './styles/header.css';\n\nexport type GlassHeaderProps = HTMLAttributes<HTMLElement> & {\n  /** A wordmark or logo, usually linked to the home page. */\n  brand: ReactNode;\n  /** Buttons or links at the end of the capsule. */\n  actions?: ReactNode;\n  /**\n   * Opens out across the top of the page without glass, and condenses into the capsule once\n   * the page scrolls. Fixed headers only.\n   */\n  condenseOnScroll?: boolean;\n  /** Accessible name for the desktop and mobile navigation. */\n  navigationLabel?: string;\n  /** Fixed above the page by default; static keeps it in the surrounding layout. */\n  position?: 'fixed' | 'static';\n};\n\n// The header condenses once the page has scrolled past this many CSS pixels, and opens again\n// only when it is back at the top, so it never flickers on the threshold.\nconst CONDENSE_AFTER = 8;\n\nconst OPEN_WITHIN = 1;\n\ntype Feel = readonly [response: number, bounce: number];\n\n// Condensing, the glass forms at once, before a fast scroll can carry the page under bare\n// links, and the capsule gathers in behind it, settling with a slight squeeze. Opening, the bar\n// spreads out without overshoot while its glass thins a little more slowly, so the material is\n// seen to stretch before it goes.\nconst GATHER: Feel = [0.56, 0.22];\n\nconst FORM: Feel = [0.24, 0];\n\nconst SPREAD: Feel = [0.44, 0];\n\nconst THIN: Feel = [0.5, 0];\n\n/**\n * Springs the header between the open bar at the top of the page and the capsule. It writes\n * --lg-header-spread (1 open, 0 capsule) and --lg-header-material (0 no glass, 1 glass) while\n * it moves; at rest the data-condensed state sets both. Reduced motion jumps. Until this runs,\n * the state is unknown and CSS follows the scroll position on its own.\n */\nfunction useCondensing(headerRef: RefObject<HTMLElement | null>, enabled: boolean) {\n  const [condensed, setCondensed] = useState<boolean | null>(null);\n  const [moving, setMoving] = useState(false);\n\n  useLayoutEffect(() => {\n    const header = headerRef.current;\n\n    if (!enabled || !header) return;\n\n    const spread = createSpring(...SPREAD, 1);\n    const material = createSpring(...THIN, 0);\n    let target: boolean | null = null;\n\n    const paint = () => {\n      header.style.setProperty('--lg-header-spread', spread.value.toFixed(4));\n      header.style.setProperty(\n        '--lg-header-material',\n        Math.min(Math.max(material.value, 0), 1).toFixed(4),\n      );\n    };\n\n    const clear = () => {\n      header.style.removeProperty('--lg-header-spread');\n      header.style.removeProperty('--lg-header-material');\n    };\n\n    const loop = createFrameLoop((dt) => {\n      const reduced = motionReduced();\n      const spreading = stepSpring(spread, dt, reduced);\n      const forming = stepSpring(material, dt, reduced);\n\n      if (spreading || forming) {\n        paint();\n\n        return true;\n      }\n\n      clear();\n      setMoving(false);\n\n      return false;\n    });\n\n    const update = (animate: boolean) => {\n      const scrolled = window.scrollY;\n      const next = target ? scrolled > OPEN_WITHIN : scrolled > CONDENSE_AFTER;\n\n      if (next === target) return;\n\n      target = next;\n      spread.target = next ? 0 : 1;\n      material.target = next ? 1 : 0;\n      retuneSpring(spread, ...(next ? GATHER : SPREAD));\n      retuneSpring(material, ...(next ? FORM : THIN));\n      setCondensed(next);\n\n      if (animate && !motionReduced()) {\n        // A jump or a fling can carry the page further than the header reaches before it\n        // condenses. The glass is then there at once, so the links never sit bare on content;\n        // only the shape gathers in.\n        if (next && scrolled > header.getBoundingClientRect().bottom) {\n          material.value = 1;\n          material.velocity = 0;\n        }\n\n        // Pins the current shape inline before the new state's values apply.\n        paint();\n        setMoving(true);\n        loop.start();\n\n        return;\n      }\n\n      loop.stop();\n\n      for (const spring of [spread, material]) {\n        spring.value = spring.target;\n        spring.velocity = 0;\n      }\n\n      clear();\n      setMoving(false);\n    };\n\n    // A page that opens partway down, or returns there, starts condensed without moving.\n    update(false);\n\n    const onScroll = () => update(true);\n\n    window.addEventListener('scroll', onScroll, { passive: true });\n\n    return () => {\n      window.removeEventListener('scroll', onScroll);\n      loop.stop();\n      clear();\n      setMoving(false);\n    };\n  }, [enabled, headerRef]);\n\n  return { condensed, moving };\n}\n\nexport const GlassHeader = forwardRef<HTMLElement, GlassHeaderProps>(\n  (\n    {\n      brand,\n      actions,\n      children,\n      className,\n      condenseOnScroll = false,\n      navigationLabel = 'Primary',\n      position = 'fixed',\n      ...rest\n    },\n    forwardedRef,\n  ) => {\n    const headerRef = useRef<HTMLElement | null>(null);\n    const ref = useMemo(() => mergeRefs(headerRef, forwardedRef), [forwardedRef]);\n    const condensing = condenseOnScroll && position === 'fixed';\n    const { condensed, moving } = useCondensing(headerRef, condensing);\n\n    return (\n      <header\n        ref={ref}\n        className={clsx('lg-header', className)}\n        data-condense-on-scroll={condensing ? '' : undefined}\n        data-condensed={condensing && condensed !== null ? String(condensed) : undefined}\n        data-position={position}\n        {...rest}\n      >\n        {/* The lens map is drawn for one size, so a condensing header bends light only once its\n            capsule has settled; it frosts while the shape moves. */}\n        <GlassSurface\n          aria-hidden\n          className=\"lg-header-glass\"\n          lens={!condensing || (condensed === true && !moving)}\n          preset=\"standard\"\n        />\n        <div className=\"lg-header-bar\">\n          <div className=\"lg-header-brand\">{brand}</div>\n          {children ? (\n            <>\n              <nav aria-label={navigationLabel} className=\"lg-header-links lg-header-desktop\">\n                {children}\n              </nav>\n              <details\n                className=\"lg-header-menu\"\n                onKeyDown={(event) => {\n                  if (event.key === 'Escape' && event.currentTarget.open) {\n                    event.preventDefault();\n                    event.currentTarget.open = false;\n                    event.currentTarget.querySelector('summary')?.focus();\n                  }\n                }}\n              >\n                <summary>Menu</summary>\n                <nav\n                  aria-label={navigationLabel}\n                  className=\"lg-header-links lg-header-menu-links\"\n                  onClick={(event) => {\n                    if (event.target instanceof Element && event.target.closest('a[href]')) {\n                      const menu = event.currentTarget.closest('details');\n\n                      if (menu) {\n                        menu.open = false;\n                        menu.querySelector('summary')?.focus();\n                      }\n                    }\n                  }}\n                >\n                  {children}\n                </nav>\n              </details>\n            </>\n          ) : null}\n          {actions ? <div className=\"lg-header-actions\">{actions}</div> : null}\n        </div>\n      </header>\n    );\n  },\n);\n\nGlassHeader.displayName = 'GlassHeader';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-header.tsx"
    },
    {
      "path": "registry/glassyui/lib/liquid-motion.ts",
      "content": "'use client';\n\nimport { useLayoutEffect } from 'react';\nimport type { RefObject } from 'react';\n\nexport type Spring = {\n  value: number;\n  velocity: number;\n  target: number;\n  stiffness: number;\n  damping: number;\n  precision: number;\n};\n\n/*\n * Springs take a response and a bounce: response is the period of the\n * undamped oscillation in seconds, and bounce 0 is critically damped. For\n * unit mass that is stiffness (2π / response)² and damping\n * 4π (1 - bounce) / response.\n */\nexport const createSpring = (\n  response: number,\n  bounce: number,\n  value: number,\n  precision = 0.001,\n): Spring => ({\n  value,\n  velocity: 0,\n  target: value,\n  stiffness: ((2 * Math.PI) / response) ** 2,\n  damping: (4 * Math.PI * (1 - bounce)) / response,\n  precision,\n});\n\n// Changes how a spring moves without stopping it, so it carries its velocity\n// into the new motion.\nexport const retuneSpring = (spring: Spring, response: number, bounce: number) => {\n  spring.stiffness = ((2 * Math.PI) / response) ** 2;\n  spring.damping = (4 * Math.PI * (1 - bounce)) / response;\n};\n\n// Semi-implicit Euler in steps of at most 1/120 s is stable for every spring\n// here. Returns whether the spring is still moving.\nexport const stepSpring = (spring: Spring, dt: number, snap: boolean) => {\n  if (snap || (spring.value === spring.target && spring.velocity === 0)) {\n    spring.value = spring.target;\n    spring.velocity = 0;\n\n    return false;\n  }\n\n  const steps = Math.max(1, Math.ceil(dt * 120));\n  const step = dt / steps;\n\n  for (let index = 0; index < steps; index += 1) {\n    spring.velocity +=\n      (-spring.stiffness * (spring.value - spring.target) - spring.damping * spring.velocity) *\n      step;\n    spring.value += spring.velocity * step;\n  }\n\n  if (\n    Math.abs(spring.value - spring.target) < spring.precision &&\n    Math.abs(spring.velocity) < spring.precision * 10\n  ) {\n    spring.value = spring.target;\n    spring.velocity = 0;\n\n    return false;\n  }\n\n  return true;\n};\n\nlet motionQuery: MediaQueryList | undefined;\n\n// The provider mirrors its preference onto <html>; the system setting always\n// counts, as it does in liquid.css.\nexport function motionReduced() {\n  motionQuery ??= window.matchMedia('(prefers-reduced-motion: reduce)');\n\n  return (\n    document.documentElement.getAttribute('data-lg-reduce-motion') === '1' || motionQuery.matches\n  );\n}\n\nexport type FrameLoop = {\n  start: () => void;\n  stop: () => void;\n};\n\n// Calls step once a frame with the seconds since the last one until it\n// returns false. A long pause counts as one short frame.\nexport function createFrameLoop(step: (dt: number) => boolean): FrameLoop {\n  let frame = 0;\n  let last = 0;\n\n  const tick = (now: number) => {\n    const dt = last ? Math.min((now - last) / 1000, 1 / 20) : 1 / 60;\n\n    last = now;\n    frame = 0;\n\n    if (step(dt)) {\n      frame = requestAnimationFrame(tick);\n\n      return;\n    }\n\n    last = 0;\n  };\n\n  return {\n    start() {\n      if (!frame) frame = requestAnimationFrame(tick);\n    },\n    stop() {\n      cancelAnimationFrame(frame);\n      frame = 0;\n      last = 0;\n    },\n  };\n}\n\ntype Edge = 'left' | 'top' | 'right' | 'bottom';\n\ntype Box = Record<Edge, number>;\n\ntype Feel = readonly [response: number, bounce: number];\n\nconst EDGES: readonly Edge[] = ['left', 'top', 'right', 'bottom'];\n\n// The edge heading for the new item leads and the far edge follows, so the\n// bubble stretches into a drop on the way and rounds off when it lands. The\n// leading edge overshoots a little and rings back, which is what makes the\n// landing feel like a drop settling rather than a slide stopping.\nconst LEAD: Feel = [0.2, 0.32];\n\nconst TRAIL: Feel = [0.3, 0.12];\n\nconst EVEN: Feel = [0.26, 0.22];\n\n// Under a finger the bubble keeps up closely but still leans into the drag.\nconst DRAG_LEAD: Feel = [0.1, 0.12];\n\nconst DRAG_TRAIL: Feel = [0.18, 0.04];\n\n// How far a lifted bubble grows past its item, in CSS pixels. It never grows\n// past the control's own edge. Kept to a hair: more reads as the bubble\n// zooming out of its track.\nconst SWELL = 1;\n\nconst DRAG_THRESHOLD = 4;\n\n// The bubble counts as travelling, and stays lifted, while an edge moves faster\n// than this in CSS pixels a second, so it settles the moment it slows down.\nconst TRAVEL_SPEED = 90;\n\n// How far a press on another item wakes the bubble before it moves.\nconst ANTICIPATION = 0.5;\n\n// Measure before the containing plane is projected. Client rectangles describe the projected\n// bounding box, which cannot be scaled back into a rectangle once an ancestor turns in 3D.\nexport function localRect(element: HTMLElement, root: HTMLElement) {\n  const size = (node: HTMLElement, style: CSSStyleDeclaration) => {\n    const borderBox = style.boxSizing === 'border-box';\n    const width = parseFloat(style.width);\n    const height = parseFloat(style.height);\n\n    return {\n      width: Number.isFinite(width)\n        ? width +\n          (borderBox\n            ? 0\n            : parseFloat(style.paddingLeft) +\n              parseFloat(style.paddingRight) +\n              parseFloat(style.borderLeftWidth) +\n              parseFloat(style.borderRightWidth))\n        : node.offsetWidth,\n      height: Number.isFinite(height)\n        ? height +\n          (borderBox\n            ? 0\n            : parseFloat(style.paddingTop) +\n              parseFloat(style.paddingBottom) +\n              parseFloat(style.borderTopWidth) +\n              parseFloat(style.borderBottomWidth))\n        : node.offsetHeight,\n    };\n  };\n\n  const elementStyle = getComputedStyle(element);\n  const dimensions = size(element, elementStyle);\n  let matrix = new DOMMatrix();\n\n  for (let node: HTMLElement | null = element; node && node !== root;) {\n    const style = node === element ? elementStyle : getComputedStyle(node);\n    const parent: Element | null = node.offsetParent;\n    const { width, height } = size(node, style);\n\n    const length = (value: string, extent: number) =>\n      value.endsWith('%') ? (parseFloat(value) * extent) / 100 : parseFloat(value) || 0;\n\n    const origin = style.transformOrigin.split(' ');\n    const translate = style.translate.split(' ');\n    const scale = style.scale === 'none' ? [1, 1] : style.scale.split(' ').map(Number);\n\n    const left =\n      style.position === 'absolute' && style.left !== 'auto'\n        ? parseFloat(style.left)\n        : node.offsetLeft;\n\n    const top =\n      style.position === 'absolute' && style.top !== 'auto'\n        ? parseFloat(style.top)\n        : node.offsetTop;\n\n    const borderX =\n      parent instanceof HTMLElement && parent !== root ? parent.clientLeft - parent.scrollLeft : 0;\n\n    const borderY =\n      parent instanceof HTMLElement && parent !== root ? parent.clientTop - parent.scrollTop : 0;\n\n    const x = length(origin[0], width);\n    const y = length(origin[1], height);\n\n    const transform = new DOMMatrix()\n      .translate(left + borderX, top + borderY)\n      .translate(x, y)\n      .translate(length(translate[0], width), length(translate[1] ?? '0', height))\n      .scale(scale[0], scale[1] ?? scale[0])\n      .multiply(new DOMMatrix(style.transform === 'none' ? undefined : style.transform))\n      .translate(-x, -y);\n\n    matrix = transform.multiply(matrix);\n    node = parent instanceof HTMLElement ? parent : null;\n  }\n\n  const points = [\n    [0, 0],\n    [dimensions.width, 0],\n    [dimensions.width, dimensions.height],\n    [0, dimensions.height],\n  ].map(([x, y]) => new DOMPoint(x, y).matrixTransform(matrix));\n\n  const left = Math.min(...points.map((point) => point.x));\n  const top = Math.min(...points.map((point) => point.y));\n\n  return {\n    left,\n    top,\n    width: Math.max(...points.map((point) => point.x)) - left,\n    height: Math.max(...points.map((point) => point.y)) - top,\n  };\n}\n\nfunction boxWithin(element: HTMLElement, frame: HTMLElement): Box {\n  const { left, top, width, height } = localRect(element, frame);\n\n  return { left, top, right: left + width, bottom: top + height };\n}\n\n// Four zero-size points give the browser's actual projection, including perspective. Only a\n// drag needs these; the static bubble and glass never need to read projected geometry.\nfunction pointerPlane(element: HTMLElement) {\n  const probes = [\n    [0, 0],\n    [100, 0],\n    [100, 100],\n    [0, 100],\n  ].map(([x, y]) => {\n    const probe = document.createElement('span');\n    probe.ariaHidden = 'true';\n    probe.style.cssText = `position:absolute;left:${x}%;top:${y}%;width:0;height:0;visibility:hidden;pointer-events:none;`;\n    element.append(probe);\n\n    return probe;\n  });\n\n  return {\n    x(clientX: number, clientY: number) {\n      const [p0, p1, p2, p3] = probes.map((probe) => probe.getBoundingClientRect());\n\n      const dx1 = p1.x - p2.x,\n        dx2 = p3.x - p2.x,\n        dx3 = p0.x - p1.x + p2.x - p3.x;\n\n      const dy1 = p1.y - p2.y,\n        dy2 = p3.y - p2.y,\n        dy3 = p0.y - p1.y + p2.y - p3.y;\n\n      const determinant = dx1 * dy2 - dx2 * dy1;\n      const g = (dx3 * dy2 - dx2 * dy3) / determinant;\n      const h = (dx1 * dy3 - dx3 * dy1) / determinant;\n      const a = p1.x - p0.x + g * p1.x - clientX * g;\n      const b = p3.x - p0.x + h * p3.x - clientX * h;\n      const d = p1.y - p0.y + g * p1.y - clientY * g;\n      const e = p3.y - p0.y + h * p3.y - clientY * h;\n      const x = ((clientX - p0.x) * e - b * (clientY - p0.y)) / (a * e - b * d);\n\n      return x * element.clientWidth + element.scrollLeft;\n    },\n    destroy: () => probes.forEach((probe) => probe.remove()),\n  };\n}\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nexport type LiquidIndicatorOptions = {\n  /** Matches the item the bubble sits under. */\n  selected: string;\n  /** Matches every item the bubble can move to. */\n  item: string;\n  /** Lets a pointer pick the bubble up and drop it on another item. */\n  draggable?: boolean;\n  /** Strength of the bubble's stretch and lift. */\n  deformation?: number;\n};\n\n/**\n * Moves a selection bubble between items like a glass droplet: it\n * lifts, stretches toward the new item and lands with a little bounce.\n * Labels stay fixed while the bubble moves. CSS places the bubble until this\n * runs. Reduced motion jumps instead.\n */\nexport function useLiquidIndicator(\n  rootRef: RefObject<HTMLElement | null>,\n  indicatorRef: RefObject<HTMLElement | null>,\n  { selected, item, draggable = false, deformation = 1 }: LiquidIndicatorOptions,\n) {\n  useLayoutEffect(() => {\n    const root = rootRef.current;\n    const indicator = indicatorRef.current;\n\n    if (!root || !indicator) return;\n\n    const edges: Record<Edge, Spring> = {\n      left: createSpring(...EVEN, 0, 0.05),\n      top: createSpring(...EVEN, 0, 0.05),\n      right: createSpring(...EVEN, 0, 0.05),\n      bottom: createSpring(...EVEN, 0, 0.05),\n    };\n\n    const lift = createSpring(0.22, 0.2, 0);\n    let items: HTMLElement[] = [];\n    let centres: number[] = [];\n    let room = 0;\n    let placed = false;\n    let held: Element | null = null;\n    let drag: { pointer: number; startX: number; offset: number; active: boolean } | null = null;\n    let plane: ReturnType<typeof pointerPlane> | null = null;\n\n    const frameOf = () =>\n      indicator.offsetParent instanceof HTMLElement ? indicator.offsetParent : root;\n\n    const paint = () => {\n      const width = edges.right.value - edges.left.value;\n      const height = edges.bottom.value - edges.top.value;\n      const restWidth = Math.max(edges.right.target - edges.left.target, 1);\n      const restHeight = Math.max(edges.bottom.target - edges.top.target, 1);\n      // A stretched drop keeps its volume, so it thins across its travel.\n      const thinY = clamp(1 / Math.sqrt(Math.max(width / restWidth, 1)), 0.8, 1);\n      const thinX = clamp(1 / Math.sqrt(Math.max(height / restHeight, 1)), 0.8, 1);\n      // A spring that rings past rest must not lift the bubble below its resting size.\n      const raised = Math.max(lift.value, 0) * deformation;\n      const swell = raised * Math.min(room, SWELL);\n      const drawnWidth = restWidth + (width * thinX - restWidth) * deformation + swell * 2;\n      const drawnHeight = restHeight + (height * thinY - restHeight) * deformation + swell * 2;\n      const centreX = (edges.left.value + edges.right.value) / 2;\n      const centreY = (edges.top.value + edges.bottom.value) / 2;\n\n      indicator.style.translate = `${centreX - drawnWidth / 2}px ${centreY - drawnHeight / 2}px`;\n      indicator.style.width = `${drawnWidth}px`;\n      indicator.style.height = `${drawnHeight}px`;\n      indicator.style.setProperty('--lg-lift', raised.toFixed(3));\n    };\n\n    const step = (dt: number) => {\n      const reduced = motionReduced();\n      const travelling = EDGES.some((edge) => Math.abs(edges[edge].velocity) > TRAVEL_SPEED);\n      const pressed = held?.matches(selected) ?? false;\n      const woken = held !== null ? ANTICIPATION : 0;\n\n      lift.target = reduced ? 0 : pressed || drag?.active || travelling ? 1 : woken;\n      let moving = false;\n\n      for (const edge of EDGES) {\n        moving = stepSpring(edges[edge], dt, reduced) || moving;\n      }\n\n      moving = stepSpring(lift, dt, reduced) || moving;\n      paint();\n\n      return moving;\n    };\n\n    const loop = createFrameLoop(step);\n\n    const current = (): Box => ({\n      left: edges.left.value,\n      top: edges.top.value,\n      right: edges.right.value,\n      bottom: edges.bottom.value,\n    });\n\n    // Tunes each axis so the edge in front leads and the one behind trails.\n    const lean = (from: Box, to: Box, lead: Feel, trail: Feel) => {\n      const axes: [Edge, Edge, number][] = [\n        ['left', 'right', (to.left + to.right - from.left - from.right) / 2],\n        ['top', 'bottom', (to.top + to.bottom - from.top - from.bottom) / 2],\n      ];\n\n      for (const [start, end, distance] of axes) {\n        if (Math.abs(distance) < 1) {\n          retuneSpring(edges[start], ...EVEN);\n          retuneSpring(edges[end], ...EVEN);\n        } else {\n          retuneSpring(edges[distance > 0 ? end : start], ...lead);\n          retuneSpring(edges[distance > 0 ? start : end], ...trail);\n        }\n      }\n    };\n\n    const aim = (to: Box, jump: boolean) => {\n      for (const edge of EDGES) {\n        edges[edge].target = to[edge];\n\n        if (jump) {\n          edges[edge].value = to[edge];\n          edges[edge].velocity = 0;\n        }\n      }\n    };\n\n    const sync = (snap: boolean) => {\n      const frame = frameOf();\n      const target = root.querySelector<HTMLElement>(selected);\n\n      // Only a drag needs every item's centre; ordinary tabs measure their selected item.\n      items = draggable\n        ? Array.from(root.querySelectorAll<HTMLElement>(item))\n        : target\n          ? [target]\n          : [];\n\n      const boxes = items.map((element) => boxWithin(element, frame));\n\n      centres = boxes.map((box) => (box.left + box.right) / 2);\n\n      // A pointer owns the bubble until it lets go.\n      if (drag?.active) return;\n\n      if (!target) {\n        indicator.setAttribute('data-lg-empty', '');\n        placed = false;\n\n        return;\n      }\n\n      const targetIndex = items.indexOf(target);\n      const box = targetIndex < 0 ? boxWithin(target, frame) : boxes[targetIndex];\n      const jump = snap || !placed || motionReduced();\n\n      indicator.removeAttribute('data-lg-empty');\n      room = Math.max(0, Math.min(box.top, frame.clientHeight - box.bottom));\n\n      if (!jump) lean(current(), box, LEAD, TRAIL);\n\n      aim(box, jump);\n      placed = true;\n\n      if (jump) paint();\n\n      loop.start();\n    };\n\n    const follow = (event: PointerEvent) => {\n      if (!drag || !plane || centres.length === 0) return;\n\n      const width = edges.right.target - edges.left.target;\n\n      const centre = clamp(\n        plane.x(event.clientX, event.clientY) - drag.offset,\n        Math.min(...centres),\n        Math.max(...centres),\n      );\n\n      const from = current();\n      const to = { ...from, left: centre - width / 2, right: centre + width / 2 };\n\n      lean(from, to, DRAG_LEAD, DRAG_TRAIL);\n      aim({ ...to, top: edges.top.target, bottom: edges.bottom.target }, motionReduced());\n      loop.start();\n    };\n\n    // Drops a dragged bubble on the nearest enabled item, or puts it back.\n    const release = (commit: boolean) => {\n      if (!drag) return;\n\n      const { active, pointer } = drag;\n\n      drag = null;\n      plane?.destroy();\n      plane = null;\n      held = null;\n      root.removeAttribute('data-lg-dragging');\n\n      if (root.hasPointerCapture(pointer)) root.releasePointerCapture(pointer);\n\n      if (active && commit) {\n        const centre = (edges.left.target + edges.right.target) / 2;\n        let nearest = -1;\n\n        centres.forEach((value, index) => {\n          if (items[index].matches('[data-disabled]')) return;\n\n          if (nearest < 0 || Math.abs(value - centre) < Math.abs(centres[nearest] - centre))\n            nearest = index;\n        });\n\n        if (nearest >= 0) {\n          if (root.contains(document.activeElement)) items[nearest].focus({ preventScroll: true });\n\n          items[nearest].click();\n        }\n      }\n\n      sync(false);\n    };\n\n    const onPointerDown = (event: PointerEvent) => {\n      if (!event.isPrimary || event.button !== 0 || !(event.target instanceof Element)) return;\n\n      const pressed = event.target.closest(item);\n\n      if (\n        !(pressed instanceof HTMLElement) ||\n        !root.contains(pressed) ||\n        pressed.matches('[data-disabled]')\n      )\n        return;\n\n      held = pressed;\n      loop.start();\n\n      if (!draggable || !pressed.matches(selected)) return;\n\n      const centre = (edges.left.target + edges.right.target) / 2;\n      plane = pointerPlane(frameOf());\n\n      drag = {\n        pointer: event.pointerId,\n        startX: event.clientX,\n        offset: plane.x(event.clientX, event.clientY) - centre,\n        active: false,\n      };\n    };\n\n    const onPointerMove = (event: PointerEvent) => {\n      if (!drag || event.pointerId !== drag.pointer) return;\n\n      if (!drag.active) {\n        if (Math.abs(event.clientX - drag.startX) < DRAG_THRESHOLD) return;\n\n        drag.active = true;\n        root.setAttribute('data-lg-dragging', '');\n        root.setPointerCapture(event.pointerId);\n      }\n\n      follow(event);\n    };\n\n    // Listens on the window, so a press that ends outside the control still ends.\n    const onPointerUp = (event: PointerEvent) => {\n      if (drag && event.pointerId === drag.pointer) {\n        release(event.type === 'pointerup');\n\n        return;\n      }\n\n      if (!held) return;\n\n      held = null;\n      loop.start();\n    };\n\n    // Taking the pointer from a touched item fires this on the item; only the\n    // root losing it ends a drag.\n    const onLostCapture = (event: PointerEvent) => {\n      if (event.target === root && drag?.active && event.pointerId === drag.pointer) release(false);\n    };\n\n    const resizes = new ResizeObserver(() => sync(true));\n\n    const observeSizes = () => {\n      resizes.disconnect();\n      resizes.observe(root);\n      root.querySelectorAll<HTMLElement>(item).forEach((element) => resizes.observe(element));\n    };\n\n    const mutations = new MutationObserver((records) => {\n      if (records.some((record) => record.type === 'childList')) observeSizes();\n\n      sync(false);\n    });\n\n    root.setAttribute('data-lg-indicator', 'ready');\n    sync(true);\n    observeSizes();\n    mutations.observe(root, {\n      attributes: true,\n      attributeFilter: ['data-state', 'data-disabled'],\n      childList: true,\n      subtree: true,\n    });\n    root.addEventListener('pointerdown', onPointerDown);\n    root.addEventListener('pointermove', onPointerMove);\n    root.addEventListener('lostpointercapture', onLostCapture);\n    window.addEventListener('pointerup', onPointerUp);\n    window.addEventListener('pointercancel', onPointerUp);\n\n    return () => {\n      loop.stop();\n      plane?.destroy();\n      mutations.disconnect();\n      resizes.disconnect();\n      root.removeEventListener('pointerdown', onPointerDown);\n      root.removeEventListener('pointermove', onPointerMove);\n      root.removeEventListener('lostpointercapture', onLostCapture);\n      window.removeEventListener('pointerup', onPointerUp);\n      window.removeEventListener('pointercancel', onPointerUp);\n      root.removeAttribute('data-lg-indicator');\n      root.removeAttribute('data-lg-dragging');\n      indicator.removeAttribute('data-lg-empty');\n      ['translate', 'width', 'height', '--lg-lift'].forEach((property) =>\n        indicator.style.removeProperty(property),\n      );\n    };\n  }, [deformation, draggable, indicatorRef, item, rootRef, selected]);\n}\n",
      "type": "registry:lib",
      "target": "components/glassyui/lib/liquid-motion.ts"
    },
    {
      "path": "registry/glassyui/styles/header.css",
      "content": "@layer components {\n  /* A slab of glass floating clear of the page, with its menu outside the\n     material's paint clip. Container queries also fit embedded headers. */\n  .lg-header {\n    position: fixed;\n    inset: 14px 0 auto;\n    z-index: 40;\n    box-sizing: border-box;\n    width: min(920px, calc(100vw - 32px));\n    height: 56px;\n    margin-inline: auto;\n    border-radius: 999px;\n    color: var(--lg-foreground);\n    container-type: inline-size;\n  }\n\n  .lg-header[data-position='static'] {\n    position: relative;\n    inset: auto;\n    z-index: auto;\n    width: 100%;\n    max-width: 920px;\n  }\n\n  /* Frosted as lightly as the other slabs, so the page shows through and bends\n     at the rim. The --lg-header-* tint and dimming keep the links legible. */\n  .lg-header > .lg-header-glass {\n    --lg-panel-radius: 999px;\n    --lg-glass-blur: calc(var(--lg-slab-blur) * 1px);\n    --lg-lens-blur: var(--lg-slab-blur);\n    --lg-lens-body-brightness: var(--lg-header-brightness);\n    --lg-lens-rim-brightness: var(--lg-header-rim-brightness);\n    --lg-glass-contrast: 1;\n    --lg-glass-sat: 1.5;\n    --lg-glass-brightness: var(--lg-header-brightness);\n    --lg-panel-elevation-shadow:\n      0 2px 4px -2px rgb(52 62 84 / 0.18), 0 4px 14px -3px rgb(52 62 84 / 0.2);\n    position: absolute;\n    inset: 0;\n    height: 100%;\n    pointer-events: none;\n  }\n\n  .lg-header > .lg-header-glass::before {\n    background-color: var(--lg-header-tint);\n  }\n\n  /* A condensing header opens out across the top of the page with no glass and gathers into\n     the capsule once the page scrolls. GlassHeader springs --lg-header-spread (1 open, 0 the\n     capsule) and --lg-header-material (the glass, 0 to 1) while it moves; at rest its\n     data-condensed state sets them. The open bar is never narrower than the capsule, so the\n     links stay on one side of the menu breakpoint throughout. */\n  .lg-header[data-condense-on-scroll] {\n    --lg-header-spread: 1;\n    --lg-header-material: 0;\n    --lg-header-capsule-width: min(920px, calc(100vw - 32px));\n    --lg-header-bar-width: max(var(--lg-header-capsule-width), min(1200px, calc(100% - 48px)));\n  }\n\n  .lg-header[data-condensed='true'] {\n    --lg-header-spread: 0;\n    --lg-header-material: 1;\n  }\n\n  .lg-header[data-position='fixed'][data-condense-on-scroll] {\n    width: calc(\n      var(--lg-header-capsule-width) +\n        (var(--lg-header-bar-width) - var(--lg-header-capsule-width)) * var(--lg-header-spread)\n    );\n  }\n\n  /* Before the script runs, as on a page opened at an anchor, the scroll position alone picks\n     the shape: a custom property animates discretely, so it switches halfway, 8px down. */\n  @supports (animation-timeline: scroll()) {\n    .lg-header[data-condense-on-scroll]:not([data-condensed]) {\n      animation: lg-header-condensed linear both;\n      animation-timeline: scroll(root);\n      animation-range: 0px 16px;\n    }\n  }\n\n  @keyframes lg-header-condensed {\n    to {\n      --lg-header-spread: 0;\n      --lg-header-material: 1;\n    }\n  }\n\n  /* The glass fades on its own layers, never on an ancestor: an ancestor's opacity would cut\n     the frost off from the page it bends. The spring sets the shadow every frame, so the\n     surface's own shadow transition stays out of its way. */\n  .lg-header[data-condense-on-scroll] > .lg-header-glass {\n    --lg-panel-elevation-shadow:\n      0 2px 4px -2px rgb(52 62 84 / calc(0.18 * var(--lg-header-material))),\n      0 4px 14px -3px rgb(52 62 84 / calc(0.2 * var(--lg-header-material)));\n    transition: none;\n  }\n\n  .lg-header[data-condense-on-scroll] > .lg-header-glass::before,\n  .lg-header[data-condense-on-scroll] > .lg-header-glass::after {\n    opacity: var(--lg-header-material);\n  }\n\n  .lg-header[data-condense-on-scroll] > .lg-header-glass > .lg-noise {\n    opacity: calc(var(--lg-noise-alpha) * var(--lg-header-material));\n  }\n\n  /* The forced-colours outline edges the glass, so it goes with it: the open bar has none.\n     Increased contrast edges it with the ring above, which fades with the glass. */\n  @media (forced-colors: active) {\n    .lg-header[data-condense-on-scroll] > .lg-header-glass {\n      outline-width: calc(1px * var(--lg-header-material)) !important;\n    }\n  }\n\n  /* A small button at the end sits as far from the rim as from the top and bottom, so it nests\n     in the capsule's curve, and the brand starts as far in as that button's label. */\n  .lg-header-bar {\n    position: relative;\n    display: flex;\n    align-items: center;\n    gap: 16px;\n    height: 100%;\n    padding: 0 12px 0 24px;\n  }\n\n  .lg-header-brand {\n    flex: none;\n    min-width: 0;\n    margin-inline-end: auto;\n    font-size: 18px;\n    font-weight: 600;\n    line-height: 1;\n    letter-spacing: -0.03em;\n    white-space: nowrap;\n  }\n\n  .lg-header-brand a {\n    color: inherit;\n    text-decoration: none;\n  }\n\n  .lg-header-links {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 2px;\n  }\n\n  .lg-header-desktop {\n    margin-inline: auto;\n  }\n\n  .lg-header-links a {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    height: 38px;\n    padding: 0 12px;\n    border-radius: 999px;\n    color: var(--lg-muted-foreground);\n    font-size: 15px;\n    font-weight: 500;\n    line-height: 1;\n    letter-spacing: -0.01em;\n    text-decoration: none;\n    white-space: nowrap;\n    transition:\n      color var(--lg-motion-duration) ease,\n      background-color var(--lg-motion-duration) ease;\n  }\n\n  /* Colour alone marks hover and the current page in the bar: a fill would lay a pale patch\n     over the glass and whatever the page shows through it. The opaque menu keeps its fill. */\n  .lg-header-links a:hover,\n  .lg-header-links a[aria-current='page'] {\n    color: var(--lg-foreground);\n  }\n\n  .lg-header :is(a, summary):focus-visible {\n    outline: 2px solid var(--lg-focus-ring);\n    outline-offset: 2px;\n  }\n\n  .lg-header-actions {\n    display: flex;\n    flex: none;\n    align-items: center;\n    gap: 4px;\n  }\n\n  .lg-header-menu {\n    display: none;\n  }\n\n  .lg-header-menu summary {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    height: 40px;\n    padding: 0 12px;\n    border-radius: 999px;\n    font-size: 14px;\n    font-weight: 500;\n    cursor: pointer;\n    list-style: none;\n    user-select: none;\n  }\n\n  .lg-header-menu summary::-webkit-details-marker {\n    display: none;\n  }\n\n  .lg-header-menu-links {\n    position: absolute;\n    top: calc(100% + 8px);\n    right: 10px;\n    z-index: 1;\n    flex-direction: column;\n    align-items: stretch;\n    box-sizing: border-box;\n    width: min(240px, calc(100cqw - 20px));\n    max-height: calc(100dvh - 96px);\n    overflow-y: auto;\n    padding: 8px;\n    border: 1px solid var(--lg-border);\n    border-radius: 20px;\n    background: var(--lg-opaque-surface);\n    box-shadow: var(--lg-shadow-raised);\n  }\n\n  .lg-header-menu-links a {\n    justify-content: flex-start;\n    height: 44px;\n    padding: 0 16px;\n    border-radius: 12px;\n  }\n\n  .lg-header-menu-links a:hover,\n  .lg-header-menu-links a[aria-current='page'] {\n    background: var(--lg-control-surface);\n  }\n\n  .lg-header-menu:not([open]) > nav {\n    display: none;\n  }\n\n  @container (max-width: 780px) {\n    .lg-header-bar {\n      gap: 8px;\n    }\n\n    .lg-header-desktop {\n      display: none;\n    }\n\n    .lg-header-menu {\n      display: block;\n    }\n  }\n\n  /* The narrowest phones close up the items and the margins, so the bar still fits and an icon\n     button at the end still nests in the curve. */\n  @container (max-width: 320px) {\n    .lg-header-bar {\n      gap: 4px;\n      padding: 0 10px 0 20px;\n    }\n  }\n\n  @media (max-width: 700px) {\n    .lg-header[data-position='fixed'] {\n      top: 8px;\n      width: calc(100vw - 20px);\n    }\n\n    /* On a phone the open bar reaches the screen's edges and the capsule draws in from them. */\n    .lg-header[data-condense-on-scroll] {\n      --lg-header-capsule-width: calc(100vw - 20px);\n      --lg-header-bar-width: 100%;\n    }\n  }\n}\n",
      "type": "registry:file",
      "target": "components/glassyui/styles/header.css"
    },
    {
      "path": "registry/glassyui/glass-input.tsx",
      "content": "'use client';\n\nimport './styles/liquid.css';\n\nimport clsx from 'clsx';\nimport { forwardRef, useId } from 'react';\n\nexport interface GlassInputProps extends React.InputHTMLAttributes<HTMLInputElement> {\n  label?: string;\n  hint?: string;\n}\n\nexport const GlassInput = forwardRef<HTMLInputElement, GlassInputProps>(\n  (\n    { className, id, label, hint, type = 'text', 'aria-describedby': describedBy, ...props },\n    ref,\n  ) => {\n    const generatedId = useId();\n    const inputId = id ?? `lg-input-${generatedId}`;\n    const hintId = `${inputId}-hint`;\n    const descriptionIds = [describedBy, hint ? hintId : null].filter(Boolean).join(' ');\n\n    return (\n      <div className=\"flex w-full flex-col gap-2 text-sm text-[var(--lg-muted-foreground)]\">\n        {label ? (\n          <label\n            htmlFor={inputId}\n            className=\"font-medium text-[var(--lg-muted-foreground)] select-none\"\n          >\n            {label}\n          </label>\n        ) : null}\n        <input\n          {...props}\n          id={inputId}\n          ref={ref}\n          type={type}\n          aria-describedby={descriptionIds || undefined}\n          data-lg-refraction=\"none\"\n          className={clsx('lg-input', className)}\n        />\n        {hint ? (\n          <span id={hintId} className=\"text-xs text-[var(--lg-muted-foreground)]\">\n            {hint}\n          </span>\n        ) : null}\n      </div>\n    );\n  },\n);\n\nGlassInput.displayName = 'GlassInput';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-input.tsx"
    },
    {
      "path": "registry/glassyui/glass-kbd.tsx",
      "content": "import clsx from 'clsx';\nimport { forwardRef } from 'react';\nimport type { HTMLAttributes } from 'react';\n\n/** A keycap. Put one key, or a symbol such as ⌘, in each. */\nexport const GlassKbd = forwardRef<HTMLElement, HTMLAttributes<HTMLElement>>(\n  ({ className, ...props }, forwardedRef) => (\n    <kbd {...props} ref={forwardedRef} className={clsx('lg-kbd', className)} />\n  ),\n);\n\nGlassKbd.displayName = 'GlassKbd';\n\n/** Sets a shortcut's keys side by side. */\nexport const GlassKbdGroup = forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>(\n  ({ className, ...props }, forwardedRef) => (\n    <span {...props} ref={forwardedRef} className={clsx('lg-kbd-group', className)} />\n  ),\n);\n\nGlassKbdGroup.displayName = 'GlassKbdGroup';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-kbd.tsx"
    },
    {
      "path": "registry/glassyui/glass-progress.tsx",
      "content": "'use client';\n\nimport clsx from 'clsx';\nimport { forwardRef, useId } from 'react';\nimport type { CSSProperties, HTMLAttributes, ReactNode } from 'react';\n\nexport type GlassProgressSize = 'sm' | 'md' | 'lg';\n\nexport type GlassProgressProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {\n  /** Text above the bar. It also names the bar for assistive technology. */\n  label?: ReactNode;\n  /** The value that means done. Defaults to 100. */\n  max?: number;\n  /** Shows the percentage beside the label. */\n  showValue?: boolean;\n  size?: GlassProgressSize;\n  /** How far along. Leave it out, or pass null, when the length is unknown. */\n  value?: number | null;\n};\n\n/**\n * A glass track with a fill that slides to its value. Without a value the fill sweeps\n * along, for work of unknown length.\n */\nexport const GlassProgress = forwardRef<HTMLDivElement, GlassProgressProps>(\n  (\n    {\n      'aria-label': ariaLabel,\n      'aria-labelledby': ariaLabelledBy,\n      className,\n      label,\n      max = 100,\n      showValue = false,\n      size = 'md',\n      value,\n      ...props\n    },\n    forwardedRef,\n  ) => {\n    const labelId = useId();\n    const known = value !== null && value !== undefined && Number.isFinite(value);\n    const clamped = known ? Math.min(Math.max(value, 0), max) : 0;\n    const percent = known && max > 0 ? (clamped / max) * 100 : 0;\n    const state = !known ? 'indeterminate' : percent >= 100 ? 'complete' : 'loading';\n\n    return (\n      <div\n        {...props}\n        ref={forwardedRef}\n        className={clsx('lg-progress', className)}\n        data-size={size}\n        data-state={state}\n      >\n        {label || (showValue && known) ? (\n          <div className=\"lg-progress-header\">\n            <span id={labelId}>{label}</span>\n            {showValue && known ? (\n              <span className=\"lg-progress-value\">{Math.round(percent)}%</span>\n            ) : null}\n          </div>\n        ) : null}\n        <div\n          aria-label={ariaLabel}\n          aria-labelledby={ariaLabelledBy ?? (label ? labelId : undefined)}\n          aria-valuemax={max}\n          aria-valuemin={0}\n          aria-valuenow={known ? clamped : undefined}\n          className=\"lg-progress-track\"\n          role=\"progressbar\"\n        >\n          <div\n            className=\"lg-progress-fill\"\n            // SAFETY: CSSProperties has no custom-property index; this adds one number-valued property.\n            style={{ '--lg-progress': percent } as CSSProperties}\n          />\n        </div>\n      </div>\n    );\n  },\n);\n\nGlassProgress.displayName = 'GlassProgress';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-progress.tsx"
    },
    {
      "path": "registry/glassyui/glass-scroll-area.tsx",
      "content": "'use client';\n\nimport * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';\nimport clsx from 'clsx';\nimport { forwardRef } from 'react';\n\nexport type GlassScrollAreaProps = React.ComponentPropsWithoutRef<\n  typeof ScrollAreaPrimitive.Root\n> & {\n  /** Which scrollbars to draw. */\n  scrollbars?: 'vertical' | 'horizontal' | 'both';\n  /** Props for the element that scrolls, such as its ref, id or aria-label. */\n  viewportProps?: React.ComponentPropsWithRef<typeof ScrollAreaPrimitive.Viewport>;\n};\n\n// Native scrolling in a viewport, with a thin glass scrollbar over its edge in place of the\n// system's. By default the bar shows only while the pointer is over the area or it scrolls, so a\n// resting page shows none; see .lg-scroll-area in liquid.css.\nexport const GlassScrollArea = forwardRef<HTMLDivElement, GlassScrollAreaProps>(\n  (\n    { children, className, scrollbars = 'vertical', type = 'hover', viewportProps, ...props },\n    forwardedRef,\n  ) => (\n    <ScrollAreaPrimitive.Root\n      ref={forwardedRef}\n      type={type}\n      className={clsx('lg-scroll-area', className)}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        {...viewportProps}\n        className={clsx('lg-scroll-area-viewport', viewportProps?.className)}\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      {scrollbars === 'horizontal' ? null : <GlassScrollBar orientation=\"vertical\" />}\n      {scrollbars === 'vertical' ? null : <GlassScrollBar orientation=\"horizontal\" />}\n      {scrollbars === 'both' ? (\n        <ScrollAreaPrimitive.Corner className=\"lg-scroll-area-corner\" />\n      ) : null}\n    </ScrollAreaPrimitive.Root>\n  ),\n);\n\nGlassScrollArea.displayName = 'GlassScrollArea';\n\nfunction GlassScrollBar({ orientation }: { orientation: 'vertical' | 'horizontal' }) {\n  return (\n    <ScrollAreaPrimitive.Scrollbar className=\"lg-scrollbar\" orientation={orientation}>\n      <ScrollAreaPrimitive.Thumb className=\"lg-scrollbar-thumb\" />\n    </ScrollAreaPrimitive.Scrollbar>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-scroll-area.tsx"
    },
    {
      "path": "registry/glassyui/glass-segmented-control.tsx",
      "content": "'use client';\n\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group';\nimport clsx from 'clsx';\nimport { forwardRef, useMemo, useRef } from 'react';\nimport { useLiquidIndicator } from './lib/liquid-motion';\nimport { mergeRefs } from './lib/merge-refs';\nimport { GlassPanel } from './glass-panel';\n\nconst radixSlotProps = { asChild: true };\n\nexport type GlassSegmentedControlProps = React.ComponentPropsWithoutRef<\n  typeof RadioGroupPrimitive.Root\n>;\n\nconst INDICATOR = {\n  selected: '[role=\"radio\"][data-state=\"checked\"]',\n  item: '[role=\"radio\"]',\n  draggable: true,\n  deformation: 0.25,\n};\n\n/**\n * A single-choice control for two to six options. CSS places the indicator from the\n * checked item, so it is correct from the first server render; once scripts run it\n * moves like a drop of glass and can be dragged to another option.\n */\nexport const GlassSegmentedControl = forwardRef<HTMLDivElement, GlassSegmentedControlProps>(\n  ({ children, className, ...props }, forwardedRef) => {\n    const rootRef = useRef<HTMLDivElement | null>(null);\n    const indicatorRef = useRef<HTMLSpanElement | null>(null);\n    const ref = useMemo(() => mergeRefs(rootRef, forwardedRef), [forwardedRef]);\n\n    useLiquidIndicator(rootRef, indicatorRef, INDICATOR);\n\n    return (\n      <RadioGroupPrimitive.Root ref={ref} {...props} {...radixSlotProps}>\n        <GlassPanel\n          variant=\"pill\"\n          preset=\"control\"\n          refraction=\"none\"\n          className={clsx('lg-segmented', className)}\n        >\n          <span ref={indicatorRef} aria-hidden className=\"lg-segmented-indicator\" />\n          {children}\n        </GlassPanel>\n      </RadioGroupPrimitive.Root>\n    );\n  },\n);\n\nGlassSegmentedControl.displayName = 'GlassSegmentedControl';\n\nexport const GlassSegmentedControlItem = forwardRef<\n  HTMLButtonElement,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, forwardedRef) => (\n  <RadioGroupPrimitive.Item\n    ref={forwardedRef}\n    className={clsx('lg-segmented-item', className)}\n    {...props}\n  />\n));\n\nGlassSegmentedControlItem.displayName = 'GlassSegmentedControlItem';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-segmented-control.tsx"
    },
    {
      "path": "registry/glassyui/glass-sheet.tsx",
      "content": "'use client';\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport clsx from 'clsx';\nimport { useRef } from 'react';\nimport { GlassPanel } from './glass-panel';\n\nconst radixSlotProps = { asChild: true };\n\nexport const GlassSheet = DialogPrimitive.Root;\n\nexport const GlassSheetTrigger = DialogPrimitive.Trigger;\n\nexport const GlassSheetClose = DialogPrimitive.Close;\n\nexport const GlassSheetTitle = DialogPrimitive.Title;\n\nexport const GlassSheetDescription = DialogPrimitive.Description;\n\nexport type GlassSheetSide = 'top' | 'right' | 'bottom' | 'left';\n\nexport type GlassSheetContentProps = React.ComponentPropsWithoutRef<\n  typeof DialogPrimitive.Content\n> & {\n  closeLabel?: string;\n  side?: GlassSheetSide;\n  /** How thick the glass is, as a multiple of regular. See GlassSurface. */\n  thickness?: number;\n};\n\ntype SheetDrag = {\n  lastTime: number;\n  lastY: number;\n  offset: number;\n  pointerId: number;\n  startY: number;\n  velocity: number;\n};\n\n// A downward fling faster than this, in pixels per millisecond, dismisses the sheet.\nconst FLING_VELOCITY = 0.5;\n\nexport function GlassSheetContent({\n  children,\n  className,\n  closeLabel = 'Close sheet',\n  side = 'bottom',\n  ...props\n}: GlassSheetContentProps) {\n  const contentRef = useRef<HTMLDivElement>(null);\n  const closeRef = useRef<HTMLButtonElement>(null);\n  const dragRef = useRef<SheetDrag | null>(null);\n\n  const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n    if (event.button !== 0) return;\n\n    event.currentTarget.setPointerCapture(event.pointerId);\n    dragRef.current = {\n      lastTime: event.timeStamp,\n      lastY: event.clientY,\n      offset: 0,\n      pointerId: event.pointerId,\n      startY: event.clientY,\n      velocity: 0,\n    };\n  };\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    const drag = dragRef.current;\n    const content = contentRef.current;\n\n    if (!drag || !content || drag.pointerId !== event.pointerId) return;\n\n    const distance = event.clientY - drag.startY;\n    const elapsed = Math.max(1, event.timeStamp - drag.lastTime);\n\n    // Upward drags meet growing resistance instead of lifting the sheet off its edge.\n    drag.offset = distance >= 0 ? distance : -Math.sqrt(-distance) * 2;\n    drag.velocity = (event.clientY - drag.lastY) / elapsed;\n    drag.lastTime = event.timeStamp;\n    drag.lastY = event.clientY;\n    content.style.transform = `translate3d(0, ${drag.offset}px, 0)`;\n  };\n\n  const handlePointerEnd = (event: React.PointerEvent<HTMLDivElement>) => {\n    const drag = dragRef.current;\n    const content = contentRef.current;\n\n    if (!drag || !content || drag.pointerId !== event.pointerId) return;\n\n    dragRef.current = null;\n\n    const threshold = Math.min(120, content.offsetHeight * 0.25);\n\n    if (drag.offset > threshold || drag.velocity > FLING_VELOCITY) {\n      // The exit animation continues from where the finger let go.\n      closeRef.current?.click();\n\n      return;\n    }\n\n    content.style.transform = '';\n    content.animate(\n      [{ transform: `translate3d(0, ${drag.offset}px, 0)` }, { transform: 'translate3d(0, 0, 0)' }],\n      { duration: 420, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' },\n    );\n  };\n\n  return (\n    <DialogPrimitive.Portal>\n      <DialogPrimitive.Overlay className=\"lg-dialog-overlay\" />\n      <DialogPrimitive.Content {...props} {...radixSlotProps}>\n        <GlassPanel\n          ref={contentRef}\n          data-side={side}\n          elevation=\"raised\"\n          lens\n          preset=\"prominent\"\n          refraction=\"none\"\n          variant=\"panel\"\n          className={clsx('lg-sheet-content', className)}\n        >\n          {side === 'bottom' ? (\n            <div\n              aria-hidden\n              className=\"lg-sheet-handle\"\n              onLostPointerCapture={handlePointerEnd}\n              onPointerCancel={handlePointerEnd}\n              onPointerDown={handlePointerDown}\n              onPointerMove={handlePointerMove}\n              onPointerUp={handlePointerEnd}\n            />\n          ) : null}\n          {children}\n          <DialogPrimitive.Close\n            ref={closeRef}\n            aria-label={closeLabel}\n            className=\"absolute right-4 top-4 flex h-9 w-9 items-center justify-center rounded-full border border-[var(--lg-border)] bg-[var(--lg-control-surface)] text-[var(--lg-muted-foreground)] hover:bg-[var(--lg-control-surface-hover)] hover:text-[var(--lg-foreground)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--lg-focus-ring)]\"\n          >\n            <svg viewBox=\"0 0 20 20\" className=\"h-4 w-4\" aria-hidden>\n              <path\n                d=\"M5 5l10 10m0-10L5 15\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.6\"\n                strokeLinecap=\"round\"\n              />\n            </svg>\n          </DialogPrimitive.Close>\n        </GlassPanel>\n      </DialogPrimitive.Content>\n    </DialogPrimitive.Portal>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-sheet.tsx"
    },
    {
      "path": "registry/glassyui/glass-switch.tsx",
      "content": "'use client';\n\nimport './styles/liquid.css';\n\nimport * as SwitchPrimitive from '@radix-ui/react-switch';\nimport clsx from 'clsx';\nimport { forwardRef, useEffect, useMemo, useRef } from 'react';\nimport type { CSSProperties } from 'react';\nimport { motionReduced } from './lib/liquid-motion';\nimport { mergeRefs } from './lib/merge-refs';\nimport { useCapsuleLensFilter } from './lib/use-refraction-filter';\nimport { useOptionalPreferences } from './glass-provider';\n\nexport type GlassSwitchProps = React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root> & {\n  size?: 'sm' | 'md';\n};\n\n// How long the knob stays glass after a flip, a little longer than its slide.\nconst TRAVEL_MS = 460;\n\n// The colour split at the knob's rim. A lens this small, drawn only while the knob is\n// touched or sliding, costs little, so it splits colour at every quality but low.\nconst KNOB_DISPERSION = 0.3;\n\n// The lens's copy is laid out at three times the knob's size and scaled back down, so\n// engines that filter at layout resolution still bend it smoothly.\nconst LENS_SUPERSAMPLE = 3;\n\nlet backdropCanvas: HTMLCanvasElement | null = null;\n\n// The colour behind an element: its ancestors' background colours laid over one another,\n// from the page down. Undefined when none of them is opaque.\nfunction backdropColour(element: HTMLElement) {\n  const colours: string[] = [];\n\n  for (let node = element.parentElement; node; node = node.parentElement) {\n    const colour = window.getComputedStyle(node).backgroundColor;\n\n    if (colour && colour !== 'transparent' && colour !== 'rgba(0, 0, 0, 0)') colours.push(colour);\n  }\n\n  backdropCanvas ??= document.createElement('canvas');\n  backdropCanvas.width = 1;\n  backdropCanvas.height = 1;\n\n  const context = backdropCanvas.getContext('2d', { willReadFrequently: true });\n\n  if (!context || colours.length === 0) return undefined;\n\n  for (const colour of colours.reverse()) {\n    context.fillStyle = colour;\n    context.fillRect(0, 0, 1, 1);\n  }\n\n  const [red, green, blue, alpha] = context.getImageData(0, 0, 1, 1).data;\n\n  return alpha === 255 ? `rgb(${red} ${green} ${blue})` : undefined;\n}\n\n// The knob swells into glass while pressed and stays that way while it slides to\n// its new side, then shrinks back; see .lg-switch in liquid.css. The glass\n// is a lens over a copy of the track and the colour behind the switch: it shows them a\n// little smaller, and they bend into the rim and split into colour there in every engine.\nexport const GlassSwitch = forwardRef<HTMLButtonElement, GlassSwitchProps>(\n  (\n    { className, disabled, size = 'md', onCheckedChange, onPointerDown, ...props },\n    forwardedRef,\n  ) => {\n    const { preferences } = useOptionalPreferences();\n    const rootRef = useRef<HTMLButtonElement | null>(null);\n    const thumbRef = useRef<HTMLSpanElement | null>(null);\n    const travel = useRef(0);\n    const ref = useMemo(() => mergeRefs(rootRef, forwardedRef), [forwardedRef]);\n\n    const lens = useCapsuleLensFilter(thumbRef, {\n      dispersion: KNOB_DISPERSION,\n      enabled: !disabled && !preferences.reduceTransparency && preferences.quality !== 'low',\n      signature: `${preferences.theme}:${preferences.quality}:${preferences.thickness}`,\n      supersample: LENS_SUPERSAMPLE,\n    });\n\n    useEffect(() => () => window.clearTimeout(travel.current), []);\n\n    // The lens hides the real track under the knob behind the colour around the switch,\n    // read as the knob turns to glass, since the page under it can change.\n    const readBackdrop = () => {\n      const root = rootRef.current;\n      const thumb = thumbRef.current;\n\n      if (!lens.active || !root || !thumb) return;\n\n      const colour = backdropColour(root);\n\n      if (colour) thumb.style.setProperty('--lg-switch-lens-backdrop', colour);\n      else thumb.style.removeProperty('--lg-switch-lens-backdrop');\n    };\n\n    const handlePointerDown = (event: React.PointerEvent<HTMLButtonElement>) => {\n      onPointerDown?.(event);\n      readBackdrop();\n    };\n\n    const handleCheckedChange = (checked: boolean) => {\n      onCheckedChange?.(checked);\n\n      const root = rootRef.current;\n\n      if (!root || motionReduced()) return;\n\n      readBackdrop();\n      window.clearTimeout(travel.current);\n      root.setAttribute('data-lg-moving', '');\n      travel.current = window.setTimeout(() => root.removeAttribute('data-lg-moving'), TRAVEL_MS);\n    };\n\n    return (\n      <SwitchPrimitive.Root\n        ref={ref}\n        data-size={size}\n        className={clsx('lg-switch', className)}\n        disabled={disabled}\n        onCheckedChange={handleCheckedChange}\n        onPointerDown={handlePointerDown}\n        {...props}\n      >\n        {lens.filterElement}\n        <SwitchPrimitive.Thumb\n          ref={thumbRef}\n          className=\"lg-switch-thumb\"\n          style={\n            // SAFETY: CSSProperties has no custom-property index; this adds two custom properties.\n            lens.active\n              ? ({\n                  '--lg-switch-lens-margin': `${lens.margin}px`,\n                  '--lg-switch-lens-scale': lens.supersample,\n                } as CSSProperties)\n              : undefined\n          }\n        >\n          {lens.active ? (\n            <span aria-hidden className=\"lg-switch-lens\">\n              <span className=\"lg-switch-lens-view\" style={{ filter: lens.filterStyle }}>\n                <span className=\"lg-switch-lens-scene\">\n                  <span className=\"lg-switch-lens-track\" />\n                </span>\n              </span>\n            </span>\n          ) : null}\n        </SwitchPrimitive.Thumb>\n      </SwitchPrimitive.Root>\n    );\n  },\n);\n\nGlassSwitch.displayName = 'GlassSwitch';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-switch.tsx"
    },
    {
      "path": "registry/glassyui/glass-toast.tsx",
      "content": "'use client';\n\nimport * as ToastPrimitive from '@radix-ui/react-toast';\nimport clsx from 'clsx';\nimport { forwardRef, useSyncExternalStore } from 'react';\nimport { createPortal } from 'react-dom';\nimport { GlassPanel } from './glass-panel';\n\nconst subscribeToClient = () => () => {};\n\nconst getClientSnapshot = () => true;\n\nconst getServerSnapshot = () => false;\n\nexport const GlassToastProvider = ToastPrimitive.Provider;\n\nexport const GlassToastViewport = (\n  props: React.ComponentPropsWithoutRef<typeof ToastPrimitive.Viewport>,\n) => {\n  const canPortal = useSyncExternalStore(subscribeToClient, getClientSnapshot, getServerSnapshot);\n\n  if (!canPortal) return null;\n\n  return createPortal(\n    <ToastPrimitive.Viewport {...props} className={clsx('lg-toast-viewport', props.className)} />,\n    document.body,\n  );\n};\n\nexport function GlassToast(props: React.ComponentPropsWithoutRef<typeof ToastPrimitive.Root>) {\n  const { className, children, ...rest } = props;\n\n  return (\n    <ToastPrimitive.Root {...rest} className={clsx('lg-toast', className)}>\n      <GlassPanel refraction=\"soft\" variant=\"card\" elevation=\"raised\" className=\"lg-toast-root\">\n        {children}\n      </GlassPanel>\n    </ToastPrimitive.Root>\n  );\n}\n\nexport const GlassToastTitle = (\n  props: React.ComponentPropsWithoutRef<typeof ToastPrimitive.Title>,\n) => <ToastPrimitive.Title {...props} className={clsx('lg-toast-title', props.className)} />;\n\nexport const GlassToastDescription = (\n  props: React.ComponentPropsWithoutRef<typeof ToastPrimitive.Description>,\n) => (\n  <ToastPrimitive.Description\n    {...props}\n    className={clsx('lg-toast-description', props.className)}\n  />\n);\n\n/**\n * A round button that dismisses the toast. It draws its own × and is named \"Dismiss\"; give it\n * children and their text names it, or pass `aria-label`.\n */\nexport const GlassToastClose = forwardRef<\n  HTMLButtonElement,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Close>\n>(({ children, className, ...props }, forwardedRef) => (\n  <ToastPrimitive.Close\n    aria-label={children === undefined ? 'Dismiss' : undefined}\n    {...props}\n    ref={forwardedRef}\n    className={clsx('lg-toast-close', className)}\n  >\n    {children ?? (\n      <svg aria-hidden viewBox=\"0 0 20 20\">\n        <path\n          d=\"M5.5 5.5l9 9m0-9l-9 9\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeLinecap=\"round\"\n          strokeWidth=\"1.6\"\n        />\n      </svg>\n    )}\n  </ToastPrimitive.Close>\n));\n\nGlassToastClose.displayName = 'GlassToastClose';\n\n/** A pill under the message, such as Undo. `altText` says how to do it without the button. */\nexport const GlassToastAction = forwardRef<\n  HTMLButtonElement,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitive.Action>\n>(({ className, ...props }, forwardedRef) => (\n  <ToastPrimitive.Action\n    {...props}\n    ref={forwardedRef}\n    className={clsx('lg-toast-action', className)}\n  />\n));\n\nGlassToastAction.displayName = 'GlassToastAction';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-toast.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/fieldnotes.tsx",
      "content": "'use client';\n\nimport './fieldnotes.css';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { GlassButton } from '../../glass-button';\nimport { GlassHeader } from '../../glass-header';\nimport { GlassSheet, GlassSheetContent, GlassSheetTitle } from '../../glass-sheet';\nimport {\n  GlassToast,\n  GlassToastClose,\n  GlassToastDescription,\n  GlassToastProvider,\n  GlassToastViewport,\n} from '../../glass-toast';\nimport { motionReduced } from '../../lib/liquid-motion';\nimport { Chapters } from './chapters';\nimport { CONTENTS, Cover, Proof } from './cover';\nimport { Field } from './field';\nimport { FieldnotesMark, Icon } from './icons';\nimport { Letter } from './letter';\nimport { Letters } from './letters';\nimport { slotted } from './parts';\nimport { Rates } from './rates';\nimport { SignOff, type StartTrial } from './sign-off';\nimport { TagAMoment } from './tag-a-moment';\nimport { useReveals } from './use-reveals';\n\nexport type { StartTrial } from './sign-off';\n\n/*\n  Fieldnotes: a founder's letter for a research repository, set like a printed issue.\n\n  It is a light page: set data-lg-theme=\"light\" on <html>. Set --fieldnotes-accent (with\n  --fieldnotes-accent-rotate, about the accent's hue minus 40deg, for the prominent buttons) and\n  the --fieldnotes-serif and --fieldnotes-sans font stacks on any ancestor to make it yours;\n  --fieldnotes-display gives the headlines a face of their own.\n*/\n\nconst MASTHEAD = [\n  { id: 'letter', title: 'The letter' },\n  { id: 'try', title: 'Try it' },\n  { id: 'chapters', title: 'Chapters' },\n  { id: 'rates', title: 'Rates' },\n  { id: 'letters', title: 'Letters' },\n];\n\n// The sections the masthead follows as the page scrolls; those without a link clear it.\nconst SECTIONS = ['cover', 'letter', 'try', 'chapters', 'field', 'rates', 'letters', 'start'];\n\nconst FOOTER = [\n  {\n    title: 'Product',\n    links: [\n      { label: 'The tool', href: '#chapters' },\n      { label: 'Try it', href: '#try' },\n      { label: 'Rates', href: '#rates' },\n      { label: 'Questions', href: '#letters' },\n    ],\n  },\n  {\n    title: 'The letter',\n    links: [\n      { label: 'The letter', href: '#letter' },\n      { label: 'From the field', href: '#field' },\n      { label: 'Letters to the editor', href: '#letters' },\n      { label: 'Sign-off', href: '#start' },\n    ],\n  },\n  {\n    title: 'Company',\n    links: [\n      { label: 'Contact', href: 'mailto:hello@fieldnotes.example' },\n      { label: 'Book a walkthrough', href: 'mailto:maren@fieldnotes.example' },\n      { label: 'Careers', href: 'mailto:jobs@fieldnotes.example' },\n      { label: 'Press', href: 'mailto:press@fieldnotes.example' },\n    ],\n  },\n];\n\nconst wideQuery = '(min-width: 900px)';\n\n/** The section in the middle band of the screen, for the masthead to mark. */\nfunction useCurrentSection() {\n  const [current, setCurrent] = useState('');\n\n  useEffect(() => {\n    const observer = new IntersectionObserver(\n      (entries) => {\n        for (const entry of entries) {\n          if (entry.isIntersecting) setCurrent(entry.target.id);\n        }\n      },\n      { rootMargin: '-45% 0px -50% 0px' },\n    );\n\n    for (const id of SECTIONS) {\n      const section = document.getElementById(id);\n\n      if (section) observer.observe(section);\n    }\n\n    return () => observer.disconnect();\n  }, []);\n\n  return current;\n}\n\nfunction Brand() {\n  return (\n    <a className=\"fieldnotes-brand\" href=\"#cover\">\n      <FieldnotesMark className=\"fieldnotes-brand-mark\" />\n      <span>Fieldnotes</span>\n    </a>\n  );\n}\n\n/**\n * The Fieldnotes founder letter. Pass `onStart` to send each sign-up's work email a sign-in link;\n * without it the form is a demo that confirms on screen and sends nothing.\n */\nexport function Fieldnotes({ onStart }: { onStart?: StartTrial }) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const emailRef = useRef<HTMLInputElement>(null);\n  const startTimer = useRef(0);\n  const menuButtonRef = useRef<HTMLButtonElement>(null);\n  const menuReturn = useRef(true);\n  const [intro, setIntro] = useState(true);\n  const [menuOpen, setMenuOpen] = useState(false);\n  const [notice, setNotice] = useState({ count: 0, message: '' });\n  const current = useCurrentSection();\n\n  useReveals(rootRef);\n\n  // The cover plays once, as the page loads; after that nothing can replay it.\n  useEffect(() => {\n    const timer = window.setTimeout(() => setIntro(false), motionReduced() ? 0 : 2600);\n\n    return () => window.clearTimeout(timer);\n  }, []);\n\n  useEffect(() => {\n    const query = window.matchMedia(wideQuery);\n\n    const onChange = () => {\n      if (query.matches) setMenuOpen(false);\n    };\n\n    query.addEventListener('change', onChange);\n\n    return () => {\n      query.removeEventListener('change', onChange);\n      window.clearTimeout(startTimer.current);\n    };\n  }, []);\n\n  // Every \"Start free\" leads to the sign-off's field, once the page has scrolled there.\n  const startFree = useCallback(() => {\n    window.clearTimeout(startTimer.current);\n    startTimer.current = window.setTimeout(\n      () => emailRef.current?.focus({ preventScroll: true }),\n      motionReduced() ? 0 : 650,\n    );\n  }, []);\n\n  // A link in the menu takes the visitor to its section, so focus stays there, not on the button.\n  const leaveMenu = () => {\n    menuReturn.current = false;\n    setMenuOpen(false);\n  };\n\n  const notify = useCallback((message: string) => {\n    setNotice((previous) => ({ count: previous.count + 1, message }));\n  }, []);\n\n  return (\n    <GlassToastProvider duration={6000} label=\"Notification\">\n      <div\n        ref={rootRef}\n        className=\"fieldnotes fieldnotes-page\"\n        data-intro={intro ? '' : undefined}\n        data-lg-theme=\"light\"\n      >\n        <svg aria-hidden className=\"fieldnotes-defs\" focusable=\"false\" height=\"0\" width=\"0\">\n          <filter height=\"120%\" id=\"fieldnotes-ink\" width=\"120%\" x=\"-10%\" y=\"-10%\">\n            <feTurbulence\n              baseFrequency=\"0.9\"\n              numOctaves={2}\n              result=\"grain\"\n              seed={7}\n              type=\"fractalNoise\"\n            />\n            <feDisplacementMap in=\"SourceGraphic\" in2=\"grain\" scale=\"2.4\" />\n          </filter>\n        </svg>\n        <a className=\"fieldnotes-skip\" href=\"#letter\">\n          Skip to the letter\n        </a>\n\n        <GlassHeader\n          actions={\n            <>\n              <nav aria-label=\"Sections\" className=\"fieldnotes-mast-nav\">\n                {MASTHEAD.map((link) => (\n                  <a\n                    key={link.id}\n                    aria-current={current === link.id ? 'true' : undefined}\n                    href={`#${link.id}`}\n                  >\n                    {link.title}\n                  </a>\n                ))}\n              </nav>\n              <a className=\"fieldnotes-mast-signin\" href=\"#start\" onClick={startFree}>\n                Sign in\n              </a>\n              <GlassButton\n                {...slotted}\n                className=\"fieldnotes-button-sm fieldnotes-mast-start\"\n                size=\"sm\"\n              >\n                <a href=\"#start\" onClick={startFree}>\n                  Start free\n                </a>\n              </GlassButton>\n              <GlassButton\n                ref={menuButtonRef}\n                aria-expanded={menuOpen}\n                aria-haspopup=\"dialog\"\n                aria-label={menuOpen ? 'Close menu' : 'Open menu'}\n                className=\"fieldnotes-button-sm fieldnotes-mast-menu\"\n                size=\"sm\"\n                onClick={() => {\n                  menuReturn.current = true;\n                  setMenuOpen((open) => !open);\n                }}\n              >\n                <Icon name=\"menu\" />\n              </GlassButton>\n              <span aria-hidden className=\"fieldnotes-mast-progress\" />\n            </>\n          }\n          brand={\n            <span className=\"fieldnotes-mast-brand\">\n              <Brand />\n              <span className=\"fieldnotes-mast-issue\">\n                <b>No. 14</b>\n                <span>30 October 2026</span>\n              </span>\n            </span>\n          }\n          className=\"fieldnotes-mast\"\n        />\n\n        <main>\n          <Cover onStartFree={startFree} />\n          <Proof />\n          <Letter />\n          <TagAMoment />\n          <Chapters />\n          <Field />\n          <Rates onStartFree={startFree} />\n          <Letters />\n          <SignOff emailRef={emailRef} onNotice={notify} onStart={onStart} />\n        </main>\n\n        <footer className=\"fieldnotes-colophon\">\n          <div className=\"fieldnotes-wrap\">\n            <div className=\"fieldnotes-colophon-top\">\n              <div className=\"fieldnotes-colophon-brand\">\n                <Brand />\n                <p>A research repository for people who listen for a living.</p>\n              </div>\n              <nav aria-label=\"Footer\" className=\"fieldnotes-colophon-nav\">\n                {FOOTER.map((column) => (\n                  <div key={column.title}>\n                    <h2>{column.title}</h2>\n                    <ul>\n                      {column.links.map((link) => (\n                        <li key={link.label}>\n                          <a href={link.href}>{link.label}</a>\n                        </li>\n                      ))}\n                    </ul>\n                  </div>\n                ))}\n              </nav>\n              <div className=\"fieldnotes-colophon-note\">\n                <h2>Colophon</h2>\n                <p>\n                  This issue was set in Fraunces and Literata, printed on warm paper with oxblood\n                  ink, and bound with Glassy UI. Issue No. 14, 30 October 2026.\n                </p>\n              </div>\n            </div>\n            <div className=\"fieldnotes-colophon-base\">\n              <p>© 2026 Fieldnotes Ltd. Made in Bristol.</p>\n              <a className=\"fieldnotes-to-top\" href=\"#cover\">\n                Back to the cover\n                <Icon className=\"fieldnotes-lean fieldnotes-lean-up\" name=\"up\" />\n              </a>\n            </div>\n          </div>\n        </footer>\n\n        <GlassSheet open={menuOpen} onOpenChange={setMenuOpen}>\n          <GlassSheetContent\n            aria-describedby={undefined}\n            className=\"fieldnotes fieldnotes-sheet\"\n            closeLabel=\"Close menu\"\n            data-lg-theme=\"light\"\n            side=\"top\"\n            onCloseAutoFocus={(event) => {\n              // Closed by Escape or its close button, focus goes back to the menu button.\n              event.preventDefault();\n\n              if (menuReturn.current) menuButtonRef.current?.focus();\n            }}\n          >\n            <GlassSheetTitle className=\"fieldnotes-sr-only\">Sections</GlassSheetTitle>\n            <p aria-hidden className=\"fieldnotes-eyebrow fieldnotes-sheet-issue\">\n              No. 14 · 30 October 2026\n            </p>\n            <nav aria-label=\"Sections in this issue\" className=\"fieldnotes-sheet-nav\">\n              {CONTENTS.map((entry) => (\n                <a key={entry.id} href={`#${entry.id}`} onClick={leaveMenu}>\n                  <span>{entry.numeral}</span>\n                  {entry.menuTitle}\n                </a>\n              ))}\n            </nav>\n            <GlassButton\n              {...slotted}\n              className=\"fieldnotes-button-lg fieldnotes-sheet-start\"\n              variant=\"prominent\"\n            >\n              <a\n                href=\"#start\"\n                onClick={() => {\n                  leaveMenu();\n                  startFree();\n                }}\n              >\n                Start free\n              </a>\n            </GlassButton>\n            <a\n              className=\"fieldnotes-sheet-signin\"\n              href=\"#start\"\n              onClick={() => {\n                leaveMenu();\n                startFree();\n              }}\n            >\n              Sign in\n            </a>\n          </GlassSheetContent>\n        </GlassSheet>\n\n        {notice.count > 0 ? (\n          <GlassToast key={notice.count} className=\"fieldnotes fieldnotes-toast\">\n            <Icon className=\"fieldnotes-toast-icon\" name=\"check\" />\n            <GlassToastDescription className=\"fieldnotes-toast-text\">\n              {notice.message}\n            </GlassToastDescription>\n            <GlassToastClose className=\"fieldnotes-toast-close\" />\n          </GlassToast>\n        ) : null}\n        <GlassToastViewport className=\"fieldnotes fieldnotes-toasts\" data-lg-theme=\"light\" />\n      </div>\n    </GlassToastProvider>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/glassyui/templates/fieldnotes/fieldnotes.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/cover.tsx",
      "content": "'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { GlassBadge } from '../../glass-badge';\nimport { GlassButton } from '../../glass-button';\nimport { GlassPanel } from '../../glass-panel';\nimport { Icon, Stamp } from './icons';\nimport { Initials, slotted } from './parts';\n\n/** The issue's sections: the cover's contents and the menu list them, with their numerals. */\nexport const CONTENTS = [\n  { id: 'letter', numeral: 'I', title: 'The letter', menuTitle: 'The letter', page: '02' },\n  { id: 'try', numeral: 'II', title: 'Tag a moment', menuTitle: 'Tag a moment', page: '05' },\n  {\n    id: 'chapters',\n    numeral: 'III',\n    title: 'Four chapters',\n    menuTitle: 'Four chapters',\n    page: '06',\n  },\n  { id: 'field', numeral: 'IV', title: 'From the field', menuTitle: 'From the field', page: '09' },\n  { id: 'rates', numeral: 'V', title: 'Rates', menuTitle: 'Rates', page: '11' },\n  {\n    id: 'letters',\n    numeral: 'VI',\n    title: 'Letters',\n    menuTitle: 'Letters to the editor',\n    page: '12',\n  },\n] as const;\n\n// Nine of the fourteen participants have raised it.\nconst PARTICIPANTS = Array.from({ length: 14 }, (_, index) => index < 9);\n\nfunction Collage() {\n  return (\n    <div aria-hidden className=\"fieldnotes-collage\">\n      <div className=\"fieldnotes-piece fieldnotes-scrap fieldnotes-folder\">\n        <span className=\"fieldnotes-folder-label\">\n          Study 03<span className=\"fieldnotes-wide-only\"> · Month-end close</span>\n        </span>\n      </div>\n      <div className=\"fieldnotes-piece fieldnotes-scrap fieldnotes-index\">\n        <span className=\"fieldnotes-hand\">P07 keeps a second spreadsheet.</span>\n        <span className=\"fieldnotes-hand fieldnotes-hand-small\">ask P02 and P05 about theirs</span>\n      </div>\n      <div className=\"fieldnotes-piece fieldnotes-strip\">\n        <div className=\"fieldnotes-strip-paper\">\n          <span className=\"fieldnotes-typed\">\n            <b>04:19 · Dana R.</b>\n            <br />\n            <mark>Honestly, that spreadsheet is the real system.</mark> The official dashboard is\n            what I show my manager.\n          </span>\n        </div>\n      </div>\n      <span className=\"fieldnotes-piece fieldnotes-tape fieldnotes-cover-tape-a\" />\n      <span className=\"fieldnotes-piece fieldnotes-tape fieldnotes-cover-tape-b\" />\n      <Stamp\n        centre=\"P07\"\n        className=\"fieldnotes-piece fieldnotes-cover-stamp\"\n        id=\"fieldnotes-cover-stamp\"\n        ring=\"TAGGED · PAIN POINT · ISSUE 14 ·\"\n      />\n    </div>\n  );\n}\n\nexport function Cover({ onStartFree }: { onStartFree: () => void }) {\n  const floatRef = useRef<HTMLDivElement>(null);\n\n  // The card's slow drift runs only while it is on screen.\n  useEffect(() => {\n    const float = floatRef.current;\n\n    if (!float) return;\n\n    const observer = new IntersectionObserver(([entry]) =>\n      float.toggleAttribute('data-live', entry.isIntersecting),\n    );\n\n    observer.observe(float);\n\n    return () => observer.disconnect();\n  }, []);\n\n  return (\n    <section aria-labelledby=\"fieldnotes-cover-title\" className=\"fieldnotes-cover\" id=\"cover\">\n      <div className=\"fieldnotes-wrap\">\n        <div className=\"fieldnotes-dateline\">\n          <span>Volume II · Autumn</span>\n          <span className=\"fieldnotes-dateline-mid\">\n            The Fieldnotes letter, on research that lasts\n          </span>\n          <span>9 minute read</span>\n        </div>\n        <div className=\"fieldnotes-cover-grid\">\n          <p className=\"fieldnotes-eyebrow fieldnotes-kicker\">A letter from the founder</p>\n          <h1 className=\"fieldnotes-cover-title\" id=\"fieldnotes-cover-title\">\n            <span className=\"fieldnotes-line\">Research should</span>\n            <span className=\"fieldnotes-line\">\n              <em>outlive</em> the readout.\n            </span>\n          </h1>\n          <div className=\"fieldnotes-teaser\">\n            <p className=\"fieldnotes-eyebrow\">Inside, page 05</p>\n            <blockquote>\n              “I’ve stopped mentioning it because it sounds like complaining.”\n            </blockquote>\n            <a href=\"#try\">\n              <span>Tag it yourself</span>\n              <Icon className=\"fieldnotes-lean fieldnotes-lean-right\" name=\"right\" />\n            </a>\n          </div>\n          <div className=\"fieldnotes-lede\">\n            <p className=\"fieldnotes-standfirst\">\n              Most interviews are heard once, summarised in a deck and forgotten. Fieldnotes keeps\n              every conversation tagged and searchable, so the pattern you spot in September can\n              still find the quote from March.\n            </p>\n            <div className=\"fieldnotes-byline\">\n              <Initials initials=\"MA\" />\n              <p>\n                <span className=\"fieldnotes-byline-name\">By Maren Ashdown</span>\n                <span className=\"fieldnotes-byline-role\">\n                  Founder, and a researcher for eleven years\n                </span>\n              </p>\n            </div>\n            <div className=\"fieldnotes-cover-actions\">\n              <GlassButton {...slotted} className=\"fieldnotes-button-lg\" variant=\"prominent\">\n                <a href=\"#start\" onClick={onStartFree}>\n                  Start free\n                </a>\n              </GlassButton>\n              <GlassButton {...slotted} className=\"fieldnotes-button-lg\">\n                <a href=\"#letter\">\n                  Read the letter\n                  <Icon className=\"fieldnotes-lean fieldnotes-lean-down\" name=\"down\" />\n                </a>\n              </GlassButton>\n            </div>\n            <p className=\"fieldnotes-cover-note\">\n              Your first 20 interviews are free. No card needed.\n            </p>\n          </div>\n          <figure className=\"fieldnotes-cover-art\">\n            <Collage />\n            <div ref={floatRef} className=\"fieldnotes-cover-float\">\n              <GlassPanel className=\"fieldnotes-pane fieldnotes-cover-card\" lens variant=\"card\">\n                <div className=\"fieldnotes-cover-card-top\">\n                  <GlassBadge className=\"fieldnotes-badge\" dot>\n                    Pattern forming\n                  </GlassBadge>\n                  <span className=\"fieldnotes-cover-card-meta\">3 studies</span>\n                </div>\n                <p className=\"fieldnotes-cover-card-title\">The spreadsheet is the real system</p>\n                <p className=\"fieldnotes-cover-card-sub\">\n                  Raised by 9 of 14 participants since March\n                </p>\n                <div aria-hidden className=\"fieldnotes-dots\">\n                  {PARTICIPANTS.map((raised, index) => (\n                    <i key={index} data-on={raised ? '' : undefined} />\n                  ))}\n                </div>\n              </GlassPanel>\n            </div>\n            <figcaption className=\"fieldnotes-sr-only\">\n              An example pattern in Fieldnotes: the spreadsheet is the real system, raised by 9 of\n              14 participants across 3 studies.\n            </figcaption>\n          </figure>\n        </div>\n        <nav aria-label=\"In this issue\" className=\"fieldnotes-contents\">\n          <p className=\"fieldnotes-eyebrow\">In this issue</p>\n          <ol>\n            {CONTENTS.map((entry) => (\n              <li key={entry.id}>\n                <a href={`#${entry.id}`}>\n                  <span className=\"fieldnotes-contents-numeral\">{entry.numeral}</span>\n                  <span className=\"fieldnotes-contents-title\">{entry.title}</span>\n                  <span className=\"fieldnotes-contents-page\">{entry.page}</span>\n                </a>\n              </li>\n            ))}\n          </ol>\n        </nav>\n      </div>\n    </section>\n  );\n}\n\nconst MARKS = [\n  {\n    name: 'Larkmere',\n    width: 140,\n    art: (\n      <path\n        d=\"M4 22c5-8 11-12 19-11-5 2-8 5-10 10M12 21c4-4 9-6 14-5\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"1.8\"\n      />\n    ),\n    text: { x: 32, y: 22.5, className: 'fieldnotes-mark-larkmere', label: 'Larkmere' },\n  },\n  {\n    name: 'Tidelock',\n    width: 150,\n    art: (\n      <path\n        d=\"M4 13c3-3 6-3 9 0s6 3 9 0M4 20c3-3 6-3 9 0s6 3 9 0\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"1.8\"\n      />\n    ),\n    text: { x: 30, y: 21.5, className: 'fieldnotes-mark-tidelock', label: 'TIDELOCK' },\n  },\n  {\n    name: 'Orrery',\n    width: 120,\n    art: (\n      <>\n        <ellipse\n          cx=\"14\"\n          cy=\"16\"\n          fill=\"none\"\n          rx=\"11\"\n          ry=\"5.5\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.6\"\n          transform=\"rotate(-24 14 16)\"\n        />\n        <circle cx=\"14\" cy=\"16\" fill=\"currentColor\" r=\"3.4\" />\n      </>\n    ),\n    text: { x: 32, y: 21.5, className: 'fieldnotes-mark-orrery', label: 'orrery' },\n  },\n  {\n    name: 'Fernhaven',\n    width: 150,\n    art: (\n      <path\n        d=\"M12 27V7M12 11l-5-3M12 11l5-3M12 16l-6-3M12 16l6-3M12 21l-6-3M12 21l6-3\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"1.6\"\n      />\n    ),\n    text: { x: 26, y: 22, className: 'fieldnotes-mark-fernhaven', label: 'Fernhaven' },\n  },\n  {\n    name: 'Quillon',\n    width: 124,\n    art: (\n      <path\n        d=\"M5 26L22 6c-2 8-6 13-12 15\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.8\"\n      />\n    ),\n    text: { x: 28, y: 22, className: 'fieldnotes-mark-quillon', label: 'Quillon' },\n  },\n  {\n    name: 'Hollow Pine',\n    width: 160,\n    art: (\n      <path\n        d=\"M12 5l8 13h-5l6 9H3l6-9H4z\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.6\"\n      />\n    ),\n    text: { x: 28, y: 21.5, className: 'fieldnotes-mark-hollow-pine', label: 'HOLLOW PINE' },\n  },\n];\n\nexport function Proof() {\n  return (\n    <section aria-label=\"Teams using Fieldnotes\" className=\"fieldnotes-proof\">\n      <div className=\"fieldnotes-wrap fieldnotes-proof-inner\">\n        <p className=\"fieldnotes-proof-lead\">\n          <strong>4,200 researchers</strong> keep their interviews in Fieldnotes, at teams including\n        </p>\n        <ul className=\"fieldnotes-proof-marks\">\n          {MARKS.map((mark) => (\n            <li key={mark.name}>\n              <svg aria-label={mark.name} role=\"img\" viewBox={`0 0 ${mark.width} 32`}>\n                {mark.art}\n                <text\n                  className={mark.text.className}\n                  fill=\"currentColor\"\n                  x={mark.text.x}\n                  y={mark.text.y}\n                >\n                  {mark.text.label}\n                </text>\n              </svg>\n            </li>\n          ))}\n        </ul>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/cover.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/letter.tsx",
      "content": "'use client';\n\nimport {\n  createContext,\n  use,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  type ReactNode,\n} from 'react';\nimport { GlassPanel } from '../../glass-panel';\nimport { motionReduced } from '../../lib/liquid-motion';\nimport { Icon } from './icons';\nimport { Folio, Initials } from './parts';\n\ntype Lighting = { lit: number | null; light: (note: number | null, hold?: boolean) => void };\n\nconst NotesContext = createContext<Lighting>({ lit: null, light: () => {} });\n\n/**\n * The phrase a margin note annotates and its numbered reference. Pointing at either the\n * reference or the note lights both; following the reference brings the note into view.\n */\nfunction NoteAnchor({ children, note }: { children: ReactNode; note: number }) {\n  const { lit, light } = use(NotesContext);\n\n  const follow = (event: React.MouseEvent<HTMLAnchorElement>) => {\n    const target = document.getElementById(`note-${note}`);\n\n    if (!target) return;\n    event.preventDefault();\n    const box = target.getBoundingClientRect();\n\n    // Scrolls this page's own window, so a framed preview never moves the page around it.\n    if (box.top < 90 || box.bottom > window.innerHeight) {\n      window.scrollTo({\n        top: window.scrollY + box.top - (window.innerHeight - box.height) / 2,\n        behavior: motionReduced() ? 'auto' : 'smooth',\n      });\n    }\n\n    target.focus({ preventScroll: true });\n    light(note, true);\n  };\n\n  return (\n    <>\n      <span className=\"fieldnotes-note-anchor\" data-lit={lit === note ? '' : undefined}>\n        {children}\n      </span>\n      <a\n        aria-label={`Note ${note}`}\n        className=\"fieldnotes-note-ref\"\n        href={`#note-${note}`}\n        onBlur={() => light(null)}\n        onClick={follow}\n        onFocus={() => light(note)}\n        onMouseEnter={() => light(note)}\n        onMouseLeave={() => light(null)}\n      >\n        {note}\n      </a>\n    </>\n  );\n}\n\nfunction Sidenote({ children, note }: { children: ReactNode; note: number }) {\n  const { lit, light } = use(NotesContext);\n\n  return (\n    <GlassPanel\n      className=\"fieldnotes-pane fieldnotes-sidenote\"\n      data-lit={lit === note ? '' : undefined}\n      data-reveal=\"sidenote\"\n      id={`note-${note}`}\n      lens\n      role=\"note\"\n      tabIndex={-1}\n      variant=\"card\"\n      onMouseEnter={() => light(note)}\n      onMouseLeave={() => light(null)}\n    >\n      <span aria-hidden className=\"fieldnotes-sidenote-number\">\n        {note}\n      </span>\n      {children}\n    </GlassPanel>\n  );\n}\n\n/** A paragraph with its margin note, which shares the paragraph's row of the letter's grid. */\nfunction Noted({ children, note }: { children: ReactNode; note: ReactNode }) {\n  return (\n    <div className=\"fieldnotes-noted\">\n      {children}\n      {note}\n    </div>\n  );\n}\n\nexport function Letter() {\n  const [lit, setLit] = useState<number | null>(null);\n  const holdTimer = useRef(0);\n\n  const light = useCallback((note: number | null, hold = false) => {\n    window.clearTimeout(holdTimer.current);\n    setLit(note);\n\n    if (hold) holdTimer.current = window.setTimeout(() => setLit(null), 1800);\n  }, []);\n\n  useEffect(() => () => window.clearTimeout(holdTimer.current), []);\n\n  const notes = useMemo(() => ({ lit, light }), [lit, light]);\n\n  return (\n    <NotesContext value={notes}>\n      <section\n        aria-labelledby=\"fieldnotes-letter-title\"\n        className=\"fieldnotes-section fieldnotes-letter\"\n        id=\"letter\"\n      >\n        <div className=\"fieldnotes-wrap\">\n          <Folio name=\"The letter\" page=\"02\" />\n          <header className=\"fieldnotes-grid fieldnotes-letter-head\">\n            <h2\n              className=\"fieldnotes-h2 fieldnotes-letter-title\"\n              data-reveal=\"focus\"\n              id=\"fieldnotes-letter-title\"\n            >\n              On listening <em>twice</em>.\n            </h2>\n            <p className=\"fieldnotes-dek fieldnotes-letter-dek\">\n              Why the most useful thing a research team can own is its own past, and what we built\n              to keep it.\n            </p>\n            <div className=\"fieldnotes-letter-by\">\n              <Initials initials=\"MA\" />\n              <p>\n                <b>Maren Ashdown, founder</b>\n                <span>Written in Bristol, October 2026</span>\n              </p>\n            </div>\n          </header>\n          <div className=\"fieldnotes-grid fieldnotes-letter-body\">\n            <Noted\n              note={\n                <Sidenote note={1}>\n                  Spiral bound, eighty pages, coffee on page 31. It still holds more findings than\n                  that team’s shared drive ever did.\n                </Sidenote>\n              }\n            >\n              <p>\n                <span className=\"fieldnotes-dropcap\" data-reveal=\"dropcap\">\n                  E\n                </span>\n                leven years ago I ran my first usability study: six participants, a borrowed meeting\n                room and <NoteAnchor note={1}>a notebook I still own</NoteAnchor>. By the end of the\n                week I knew exactly why people were abandoning our checkout. By the end of the\n                quarter nobody remembered, including me. The findings lived in a slide deck that was\n                presented once and opened, according to its history, twice more.\n              </p>\n            </Noted>\n            <p>\n              That pattern followed me to every team I joined. We were good at hearing people and\n              bad at keeping what they said. Each study started from zero, and the same frustrations\n              surfaced interview after interview, recorded faithfully and filed where nobody would\n              look.\n            </p>\n            <Noted\n              note={\n                <Sidenote note={2}>\n                  In our beta, the median team held 412 hours of recorded interviews. Fewer than one\n                  session in sixteen was opened again after its first readout.\n                </Sidenote>\n              }\n            >\n              <p>\n                The problem was never a shortage of research. Most teams I’ve worked with had{' '}\n                <NoteAnchor note={2}>hundreds of hours of interviews</NoteAnchor>. The problem is\n                that a transcript is a terrible place to keep an insight. It is long, it is linear,\n                and it only answers the question you were asking on the day you recorded it.\n              </p>\n            </Noted>\n            <figure className=\"fieldnotes-pullquote fieldnotes-pullquote-inline\">\n              <blockquote>\n                <p>A transcript is a terrible place to keep an insight.</p>\n              </blockquote>\n            </figure>\n            <h3>\n              <span aria-hidden className=\"fieldnotes-section-mark\">\n                §\n              </span>\n              What a repository should do\n            </h3>\n            <p>\n              So we built Fieldnotes around one idea: the unit of research is not the study, it is\n              the moment. A sentence where someone describes a workaround. The pause before they\n              admit they gave up. Fieldnotes lets you tag that moment in a couple of keystrokes and\n              keeps it attached to the person, the date and their exact words.\n            </p>\n            <Noted\n              note={\n                <Sidenote note={3}>\n                  Patterns are suggestions, never verdicts. Fieldnotes proposes them from your tags,\n                  and a researcher confirms each one before anyone else sees it.\n                </Sidenote>\n              }\n            >\n              <p>\n                Once moments are tagged, they start to find each other. A pain point from a March\n                onboarding study sits beside one from a September pricing study, and you can see\n                they are the same problem described in two vocabularies.{' '}\n                <NoteAnchor note={3}>We call those patterns</NoteAnchor>, and they are what you\n                bring to the room instead of a deck.\n              </p>\n            </Noted>\n            <Noted\n              note={\n                <Sidenote note={4}>\n                  Every quote links to its second in the recording, so a stakeholder can hear the\n                  hesitation for themselves.\n                </Sidenote>\n              }\n            >\n              <p>\n                None of this replaces the researcher. Software can’t decide what matters, and I’d\n                distrust any that claimed to. What it can do is make sure that when you know\n                something, your whole team can find out why you know it, with{' '}\n                <NoteAnchor note={4}>the participant’s own words one click away</NoteAnchor>.\n              </p>\n            </Noted>\n            <p>\n              Below is the part I’m proudest of. It’s an excerpt from one of our own interviews,\n              shared with permission. Pick a sentence that matters to you and tag it. That small\n              act, repeated across a few hundred conversations, is how a team starts to remember.\n            </p>\n            <p>\n              <a className=\"fieldnotes-letter-more\" href=\"#try\">\n                <span>Try it on the transcript</span>\n                <Icon className=\"fieldnotes-lean fieldnotes-lean-down\" name=\"down\" />\n              </a>\n            </p>\n          </div>\n        </div>\n      </section>\n    </NotesContext>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/letter.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/tag-a-moment.tsx",
      "content": "'use client';\n\nimport { Fragment, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport { GlassBadge } from '../../glass-badge';\nimport { GlassButton } from '../../glass-button';\nimport { GlassKbd } from '../../glass-kbd';\nimport { GlassPanel } from '../../glass-panel';\nimport { GlassScrollArea } from '../../glass-scroll-area';\nimport { motionReduced } from '../../lib/liquid-motion';\nimport { Icon } from './icons';\nimport { Folio } from './parts';\n\ntype Tag = 'pain' | 'work' | 'quote';\n\ntype Theme = 'rekey' | 'shadow' | 'trust' | 'handoff';\n\nconst TAGS: readonly Tag[] = ['pain', 'work', 'quote'];\n\nconst TAG_NAMES: Record<Tag, string> = { pain: 'Pain point', work: 'Workaround', quote: 'Quote' };\n\nconst BOARD_NAMES: Record<Tag, string> = {\n  pain: 'Pain points',\n  work: 'Workarounds',\n  quote: 'Quotes',\n};\n\nconst DOCK_NAMES: Record<Tag, string> = { pain: 'Pain', work: 'Workaround', quote: 'Quote' };\n\ntype Sentence = { text: string; time: string; theme: Theme; suggest: Tag };\n\n/** Interview seven: what Dana said, sentence by sentence, with the tag each one suggests. */\nconst SENTENCES: readonly Sentence[] = [\n  {\n    text: 'So the first thing I do is export everything from the billing system.',\n    time: '04:06',\n    theme: 'rekey',\n    suggest: 'work',\n  },\n  {\n    text: 'Then I paste it into my own spreadsheet, the one nobody else knows about.',\n    time: '04:12',\n    theme: 'shadow',\n    suggest: 'work',\n  },\n  {\n    text: 'Honestly, that spreadsheet is the real system.',\n    time: '04:19',\n    theme: 'shadow',\n    suggest: 'quote',\n  },\n  {\n    text: 'The official dashboard is what I show my manager.',\n    time: '04:23',\n    theme: 'trust',\n    suggest: 'pain',\n  },\n  {\n    text: 'Because I don’t trust it after the fifth of the month.',\n    time: '04:35',\n    theme: 'trust',\n    suggest: 'pain',\n  },\n  { text: 'Numbers move and nobody tells me why.', time: '04:39', theme: 'trust', suggest: 'pain' },\n  {\n    text: 'So I screenshot it on the first, just so I have something to point at.',\n    time: '04:44',\n    theme: 'trust',\n    suggest: 'work',\n  },\n  { text: 'I message three people and wait.', time: '05:06', theme: 'handoff', suggest: 'pain' },\n  {\n    text: 'Usually it’s a refund someone processed by hand.',\n    time: '05:10',\n    theme: 'handoff',\n    suggest: 'pain',\n  },\n  {\n    text: 'It takes about a day, every month, and I’ve stopped mentioning it because it sounds like complaining.',\n    time: '05:15',\n    theme: 'handoff',\n    suggest: 'quote',\n  },\n];\n\n/** The conversation: the interviewer's questions, and Dana's answers as runs of sentences. */\nconst TURNS = [\n  { time: '03:58', question: 'Walk me through the last time you closed the month.' },\n  { time: '04:06', answer: [0, 1, 2, 3] },\n  { time: '04:31', question: 'Why not run the close from the dashboard?' },\n  { time: '04:35', answer: [4, 5, 6] },\n  { time: '05:02', question: 'And when the numbers don’t match?' },\n  { time: '05:06', answer: [7, 8, 9] },\n];\n\n/** Patterns from the study's earlier interviews, which a tag on the same theme brings up. */\nconst PATTERNS: readonly { theme: Theme; title: string; count: number; seen: string[] }[] = [\n  {\n    theme: 'rekey',\n    title: 'Data is re-keyed between tools',\n    count: 4,\n    seen: ['P02', 'P03', 'P06'],\n  },\n  { theme: 'shadow', title: 'Shadow spreadsheets carry the close', count: 3, seen: ['P02', 'P05'] },\n  { theme: 'trust', title: 'The dashboard isn’t trusted after day five', count: 2, seen: ['P03'] },\n  {\n    theme: 'handoff',\n    title: 'A mismatch takes three people and a day',\n    count: 3,\n    seen: ['P01', 'P04'],\n  },\n];\n\nconst PARTICIPANTS = ['P01', 'P02', 'P03', 'P04', 'P05', 'P06', 'P07'];\n\nconst EASE_OUT = 'cubic-bezier(0.32, 0.72, 0, 1)';\n\nconst EASE_IN = 'cubic-bezier(0.5, 0, 0.75, 0)';\n\n/** A sentence's current tag, and the colour its marker keeps while it wipes away. */\ntype Mark = { tag?: Tag; hue?: Tag };\n\n/** A card on the board for a tagged sentence. A leaving card plays its exit, then goes. */\ntype Moment = { key: number; sentence: number; tag: Tag; leaving?: boolean };\n\n/** Where the menu grows from: a point on a line of text, with the line's top and bottom. */\ntype Anchor = { x: number; top: number; bottom: number };\n\n/** A tagged sentence's copy flying from the transcript to its new card. */\ntype Flight = { key: number; tag: Tag; from: DOMRect; delay: number; text: string };\n\nconst shorten = (text: string) => (text.length > 42 ? `${text.slice(0, 40).trim()}…` : text);\n\n/** Where the arrow keys, Home and End take focus from a sentence, if anywhere. */\nfunction moveFrom(index: number, key: string) {\n  switch (key) {\n    case 'ArrowDown':\n    case 'ArrowRight':\n      return Math.min(SENTENCES.length - 1, index + 1);\n    case 'ArrowUp':\n    case 'ArrowLeft':\n      return Math.max(0, index - 1);\n    case 'Home':\n      return 0;\n    case 'End':\n      return SENTENCES.length - 1;\n    default:\n      return undefined;\n  }\n}\n\nconst onScreen = (box: DOMRect) =>\n  box.width > 0 && box.top >= 64 && box.bottom <= window.innerHeight - 8;\n\n/**\n * Try it: a real interview excerpt to tag. Select sentences or click one, pick a tag from the\n * menu that grows from the text, and the moment flies onto the insights board, where it can\n * bring up a pattern from the study's other interviews.\n */\nexport function TagAMoment() {\n  const highlighterRef = useRef<HTMLDivElement>(null);\n  const menuRef = useRef<HTMLDivElement>(null);\n  const listRef = useRef<HTMLDivElement>(null);\n  const sentenceRefs = useRef<(HTMLSpanElement | null)[]>([]);\n  const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);\n  const momentRefs = useRef(new Map<number, HTMLLIElement>());\n  const dockRefs = useRef(new Map<Tag, HTMLSpanElement>());\n  const positions = useRef(new Map<number, number>());\n  const leaving = useRef(new Set<number>());\n  const flights = useRef<Flight[]>([]);\n  const anchorRef = useRef<Anchor | null>(null);\n  const targetsRef = useRef<number[]>([]);\n  const nextKey = useRef(0);\n  const [marks, setMarks] = useState<Mark[]>(() => SENTENCES.map(() => ({})));\n  const [moments, setMoments] = useState<Moment[]>([]);\n  const [rove, setRove] = useState(0);\n  const [targets, setTargets] = useState<number[]>([]);\n  const [opened, setOpened] = useState(0);\n  const [confirmed, setConfirmed] = useState<Theme[]>([]);\n  const [announcement, setAnnouncement] = useState('');\n\n  const tagged = marks.flatMap((mark) => (mark.tag ? [mark.tag] : []));\n\n  const themes = new Set(\n    marks.flatMap((mark, index) => (mark.tag ? [SENTENCES[index].theme] : [])),\n  );\n\n  const countOf = (tag: Tag) => tagged.filter((entry) => entry === tag).length;\n\n  const announce = (message: string, next: Mark[]) => {\n    const count = next.filter((mark) => mark.tag).length;\n\n    setAnnouncement(`${message} ${count} ${count === 1 ? 'moment' : 'moments'} on the board.`);\n  };\n\n  const openMenu = (group: number[], anchor: Anchor) => {\n    targetsRef.current = group;\n    anchorRef.current = anchor;\n    setTargets(group);\n    setOpened((count) => count + 1);\n  };\n\n  const closeMenu = (returnFocus: boolean) => {\n    const menu = menuRef.current;\n\n    if (!menu || menu.dataset.state !== 'open') return;\n    menu.dataset.state = 'closed';\n    const last = targetsRef.current.at(-1);\n\n    targetsRef.current = [];\n    setTargets([]);\n\n    if (returnFocus && last !== undefined)\n      sentenceRefs.current[last]?.focus({ preventScroll: true });\n  };\n\n  // Grows the menu from its anchor, above the line where there is room and below it where not.\n  useLayoutEffect(() => {\n    const menu = menuRef.current;\n    const highlighter = highlighterRef.current;\n    const anchor = anchorRef.current;\n\n    if (!opened || !menu || !highlighter || !anchor) return;\n    anchorRef.current = null;\n\n    const box = highlighter.getBoundingClientRect();\n    const width = menu.offsetWidth;\n    const height = menu.offsetHeight;\n    const gap = 10;\n    const above = anchor.top - height - gap > 84;\n    const left = Math.max(-6, Math.min(anchor.x - box.left - width / 2, box.width - width + 6));\n    const top = above ? anchor.top - box.top - height - gap : anchor.bottom - box.top + gap;\n\n    menu.style.transition = 'none';\n    menu.dataset.state = 'closed';\n    menu.dataset.side = above ? 'top' : 'bottom';\n    menu.style.left = `${left}px`;\n    menu.style.top = `${top}px`;\n    menu.style.setProperty('--origin-x', `${Math.round(anchor.x - box.left - left)}px`);\n    menu.style.setProperty('--origin-y', `${above ? height + gap : -gap}px`);\n    // Reading the size applies the new place before the opening transition starts from it.\n    void menu.offsetWidth;\n    menu.style.transition = '';\n    menu.dataset.state = 'open';\n\n    const items = itemRefs.current.slice(0, TAGS.length);\n\n    const first =\n      items.find((item) => item?.getAttribute('aria-checked') === 'true') ??\n      items.find((item) => item?.dataset.suggested !== undefined) ??\n      items[0];\n\n    first?.focus({ preventScroll: true });\n  }, [opened]);\n\n  // A click anywhere else closes the menu, and so does a new width, which moves the text.\n  useEffect(() => {\n    let width = window.innerWidth;\n\n    const onPointerDown = (event: PointerEvent) => {\n      const menu = menuRef.current;\n\n      if (\n        menu?.dataset.state === 'open' &&\n        event.target instanceof Node &&\n        !menu.contains(event.target) &&\n        !(event.target instanceof Element && event.target.closest('[data-sentence]'))\n      ) {\n        closeMenu(false);\n      }\n    };\n\n    const onResize = () => {\n      if (window.innerWidth === width) return;\n      width = window.innerWidth;\n      closeMenu(false);\n    };\n\n    document.addEventListener('pointerdown', onPointerDown);\n    window.addEventListener('resize', onResize);\n\n    return () => {\n      document.removeEventListener('pointerdown', onPointerDown);\n      window.removeEventListener('resize', onResize);\n    };\n  }, []);\n\n  // Cards slide to their new places, leaving cards play their exit, and new ones fly in.\n  useLayoutEffect(() => {\n    const cards = momentRefs.current;\n    const still = motionReduced();\n    const root = highlighterRef.current?.closest<HTMLElement>('.fieldnotes') ?? document.body;\n    const spring = getComputedStyle(root).getPropertyValue('--lg-spring').trim() || 'ease-out';\n\n    for (const [key, card] of cards) {\n      const before = positions.current.get(key);\n      const shift = before === undefined ? 0 : before - card.offsetTop;\n\n      if (!still && Math.abs(shift) > 0.5) {\n        card.animate([{ transform: `translateY(${shift}px)` }, { transform: 'none' }], {\n          duration: 460,\n          easing: spring,\n        });\n      }\n    }\n\n    positions.current = new Map(Array.from(cards, ([key, card]) => [key, card.offsetTop]));\n\n    for (const moment of moments) {\n      if (!moment.leaving || leaving.current.has(moment.key)) continue;\n      leaving.current.add(moment.key);\n      const card = cards.get(moment.key);\n\n      const remove = () => {\n        leaving.current.delete(moment.key);\n        setMoments((current) => current.filter((entry) => entry.key !== moment.key));\n      };\n\n      if (still || !card) {\n        remove();\n        continue;\n      }\n\n      card\n        .animate(\n          [\n            { transform: 'none', opacity: 1 },\n            { transform: 'translateX(18px)', opacity: 0 },\n          ],\n          { duration: 170, easing: EASE_IN, fill: 'forwards' },\n        )\n        .finished.then(remove, remove);\n    }\n\n    const launches = flights.current.splice(0);\n\n    if (launches.length && listRef.current) listRef.current.scrollTop = 0;\n\n    for (const flight of launches) {\n      const card = cards.get(flight.key);\n\n      if (!card) continue;\n      fly(root, flight, card, dockRefs.current.get(flight.tag), spring);\n    }\n  }, [moments]);\n\n  const applyTag = (tag: Tag, group: number[]) => {\n    if (!group.length) return;\n    closeMenu(true);\n    window.getSelection()?.removeAllRanges();\n\n    const next = marks.slice();\n    let board = moments;\n    let delay = 0;\n\n    for (const sentence of group) {\n      if (next[sentence].tag === tag) continue;\n\n      // A retagged sentence swaps its card at once for one at the top of the board.\n      if (next[sentence].tag) {\n        board = board.filter((moment) => moment.sentence !== sentence || moment.leaving);\n      }\n\n      next[sentence] = { tag, hue: tag };\n      const key = nextKey.current++;\n      const from = sentenceRefs.current[sentence]?.getBoundingClientRect();\n\n      board = [{ key, sentence, tag }, ...board];\n\n      if (from && !motionReduced()) {\n        flights.current.push({ key, tag, from, delay, text: SENTENCES[sentence].text });\n      }\n\n      delay += 120;\n    }\n\n    setMarks(next);\n    setMoments(board);\n    announce(\n      group.length > 1\n        ? `${group.length} sentences tagged as ${TAG_NAMES[tag].toLowerCase()}.`\n        : `Tagged as ${TAG_NAMES[tag].toLowerCase()}.`,\n      next,\n    );\n  };\n\n  const untag = (sentences: number[], message: string) => {\n    const next = marks.slice();\n    const removing = new Set(sentences.filter((sentence) => next[sentence].tag));\n\n    if (!removing.size) return;\n\n    for (const sentence of removing) next[sentence] = { hue: next[sentence].hue };\n\n    // Focus on a card that is going moves back to its sentence.\n    const focused = document.activeElement;\n\n    for (const moment of moments) {\n      if (!removing.has(moment.sentence) || moment.leaving) continue;\n\n      if (focused && momentRefs.current.get(moment.key)?.contains(focused)) {\n        sentenceRefs.current[moment.sentence]?.focus({ preventScroll: true });\n        setRove(moment.sentence);\n      }\n    }\n\n    setMarks(next);\n    setMoments((current) =>\n      current.map((moment) =>\n        removing.has(moment.sentence) && !moment.leaving ? { ...moment, leaving: true } : moment,\n      ),\n    );\n    announce(message, next);\n  };\n\n  const selectionTargets = () => {\n    const selection = window.getSelection();\n    const highlighter = highlighterRef.current;\n\n    if (!highlighter || !selection || selection.isCollapsed || !selection.rangeCount) return null;\n    const range = selection.getRangeAt(0);\n\n    if (!highlighter.contains(range.commonAncestorContainer)) return null;\n\n    // A sentence counts when the selection takes some of its text, not just touches its edge.\n    const hit = sentenceRefs.current.flatMap((span, index) => {\n      if (!span || !selection.containsNode(span, true)) return [];\n      const text = span.firstChild;\n\n      if (range.startContainer === text && range.startOffset >= (text.textContent?.length ?? 0)) {\n        return [];\n      }\n\n      if (range.endContainer === text && range.endOffset === 0) return [];\n\n      return [index];\n    });\n\n    if (!hit.length) return null;\n    const lines = Array.from(range.getClientRects()).filter((line) => line.width > 1);\n    const first = lines[0] ?? range.getBoundingClientRect();\n    const last = lines.at(-1) ?? first;\n\n    return {\n      hit,\n      anchor: {\n        x: first.left + Math.min(first.width / 2, 160),\n        top: first.top,\n        bottom: last.bottom,\n      },\n    };\n  };\n\n  const onPointerUp = (event: React.PointerEvent<HTMLDivElement>) => {\n    const { clientX, clientY, target } = event;\n\n    if (target instanceof Node && menuRef.current?.contains(target)) return;\n\n    // The selection settles after pointerup.\n    window.setTimeout(() => {\n      const picked = selectionTargets();\n\n      if (picked) {\n        setRove(picked.hit[picked.hit.length - 1]);\n        openMenu(picked.hit, picked.anchor);\n\n        return;\n      }\n\n      const sentence =\n        target instanceof Element ? target.closest<HTMLElement>('[data-sentence]') : null;\n\n      if (!sentence) {\n        closeMenu(false);\n\n        return;\n      }\n\n      const index = Number(sentence.dataset.sentence);\n      const lines = Array.from(sentence.getClientRects());\n\n      const line =\n        lines.find((box) => clientY >= box.top - 2 && clientY <= box.bottom + 2) ?? lines[0];\n\n      setRove(index);\n      openMenu([index], { x: clientX, top: line.top, bottom: line.bottom });\n    }, 0);\n  };\n\n  const onSentenceKeyDown = (event: React.KeyboardEvent<HTMLSpanElement>, index: number) => {\n    const move = moveFrom(index, event.key);\n\n    if (move !== undefined) {\n      event.preventDefault();\n      setRove(move);\n      sentenceRefs.current[move]?.focus();\n\n      return;\n    }\n\n    if (event.key === 'Enter' || event.key === ' ') {\n      event.preventDefault();\n      const line = event.currentTarget.getClientRects()[0];\n\n      openMenu([index], {\n        x: line.left + Math.min(line.width / 2, 140),\n        top: line.top,\n        bottom: line.bottom,\n      });\n    } else if (event.key === '1' || event.key === '2' || event.key === '3') {\n      event.preventDefault();\n      applyTag(TAGS[Number(event.key) - 1], [index]);\n    } else if ((event.key === 'Backspace' || event.key === 'Delete') && marks[index].tag) {\n      event.preventDefault();\n      untag([index], 'Tag removed.');\n    }\n  };\n\n  const onMenuKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n    const items = itemRefs.current.flatMap((item) => (item && !item.hidden ? [item] : []));\n    const index = items.findIndex((item) => item === document.activeElement);\n    const focus = (next: number) => items[(next + items.length) % items.length]?.focus();\n\n    if (event.key === 'ArrowDown') {\n      event.preventDefault();\n      focus(index + 1);\n    } else if (event.key === 'ArrowUp') {\n      event.preventDefault();\n      focus(index - 1);\n    } else if (event.key === 'Home') {\n      event.preventDefault();\n      focus(0);\n    } else if (event.key === 'End') {\n      event.preventDefault();\n      focus(items.length - 1);\n    } else if (event.key === 'Escape' || event.key === 'Tab') {\n      event.preventDefault();\n      closeMenu(true);\n    } else if (event.key === '1' || event.key === '2' || event.key === '3') {\n      event.preventDefault();\n      applyTag(TAGS[Number(event.key) - 1], targetsRef.current);\n    }\n  };\n\n  const removeFromMenu = () => {\n    const group = targetsRef.current;\n\n    closeMenu(true);\n    untag(group, 'Tag removed.');\n  };\n\n  const clearBoard = () => {\n    untag(\n      SENTENCES.map((_, index) => index),\n      'Board cleared.',\n    );\n    setRove(0);\n    sentenceRefs.current[0]?.focus({ preventScroll: true });\n  };\n\n  // The menu's state, for the sentences it is open on.\n  const current =\n    targets.length && targets.every((index) => marks[index].tag === marks[targets[0]].tag)\n      ? marks[targets[0]].tag\n      : undefined;\n\n  const suggested = targets.length && !current ? SENTENCES[targets[0]].suggest : undefined;\n  const anyTagged = targets.some((index) => marks[index].tag);\n\n  return (\n    <section\n      aria-labelledby=\"fieldnotes-try-title\"\n      className=\"fieldnotes-section fieldnotes-try\"\n      id=\"try\"\n    >\n      <div className=\"fieldnotes-wrap\">\n        <Folio name=\"Try it\" page=\"05\" />\n        <div className=\"fieldnotes-grid fieldnotes-try-head\">\n          <h2 className=\"fieldnotes-h2\" data-reveal=\"focus\" id=\"fieldnotes-try-title\">\n            Tag a <em>moment</em>.\n          </h2>\n          <div className=\"fieldnotes-try-intro\">\n            <p>\n              Interview seven from our own month-end close study. Select any sentence, or click one,\n              then choose a tag. Watch what the board does with it.\n            </p>\n            <p className=\"fieldnotes-try-keys\" id=\"fieldnotes-tag-help\">\n              <span>Keyboard:</span> <GlassKbd>Tab</GlassKbd> into the transcript,{' '}\n              <GlassKbd>↑</GlassKbd>\n              <GlassKbd>↓</GlassKbd> to move, <GlassKbd>Enter</GlassKbd> to open tags, or press{' '}\n              <span className=\"fieldnotes-try-numbers\">\n                <GlassKbd>1</GlassKbd>\n                <GlassKbd>2</GlassKbd>\n                <GlassKbd>3</GlassKbd>.\n              </span>\n            </p>\n            {TAGS.map((tag) => (\n              <span key={tag} hidden id={`fieldnotes-tagged-${tag}`}>\n                Tagged as {TAG_NAMES[tag].toLowerCase()}.\n              </span>\n            ))}\n          </div>\n        </div>\n        <div className=\"fieldnotes-try-grid\">\n          <div className=\"fieldnotes-try-main\">\n            <div className=\"fieldnotes-transcript\">\n              <div className=\"fieldnotes-transcript-head\">\n                <p>Interview 07 · Month-end close</p>\n                <p>14 March 2026 · 41 min · Dana R., finance operations</p>\n              </div>\n              <div\n                ref={highlighterRef}\n                aria-label=\"Interview transcript\"\n                className=\"fieldnotes-highlighter\"\n                role=\"group\"\n                onPointerUp={onPointerUp}\n              >\n                {TURNS.map((turn) => (\n                  <div\n                    key={turn.time}\n                    className=\"fieldnotes-turn\"\n                    data-question={turn.question ? '' : undefined}\n                  >\n                    <span className=\"fieldnotes-turn-time\">{turn.time}</span>\n                    <span className=\"fieldnotes-turn-who\">\n                      {turn.question ? 'Interviewer' : 'Dana'}\n                    </span>\n                    <p>\n                      {turn.question ??\n                        turn.answer?.map((index, position) => (\n                          <Fragment key={index}>\n                            {position ? ' ' : null}\n                            <span\n                              ref={(element) => {\n                                sentenceRefs.current[index] = element;\n                              }}\n                              aria-controls=\"fieldnotes-tag-menu\"\n                              aria-describedby={\n                                marks[index].tag\n                                  ? `fieldnotes-tagged-${marks[index].tag} fieldnotes-tag-help`\n                                  : 'fieldnotes-tag-help'\n                              }\n                              aria-expanded={targets.includes(index)}\n                              aria-haspopup=\"menu\"\n                              className=\"fieldnotes-sentence\"\n                              data-hue={marks[index].hue}\n                              data-sentence={index}\n                              data-tag={marks[index].tag}\n                              role=\"button\"\n                              tabIndex={rove === index ? 0 : -1}\n                              onFocus={() => setRove(index)}\n                              onKeyDown={(event) => onSentenceKeyDown(event, index)}\n                            >\n                              {SENTENCES[index].text}\n                            </span>\n                          </Fragment>\n                        ))}\n                    </p>\n                  </div>\n                ))}\n                <GlassPanel\n                  ref={menuRef}\n                  aria-label=\"Tag this sentence\"\n                  className=\"fieldnotes-float fieldnotes-selection-menu\"\n                  data-state=\"closed\"\n                  id=\"fieldnotes-tag-menu\"\n                  role=\"menu\"\n                  variant=\"card\"\n                  onKeyDown={onMenuKeyDown}\n                >\n                  <p aria-hidden className=\"fieldnotes-selection-label\">\n                    {targets.length > 1 ? `Tag ${targets.length} sentences as` : 'Tag as'}\n                  </p>\n                  {TAGS.map((tag, index) => (\n                    <button\n                      key={tag}\n                      ref={(element) => {\n                        itemRefs.current[index] = element;\n                      }}\n                      aria-checked={current === tag}\n                      className=\"fieldnotes-selection-item\"\n                      data-suggested={suggested === tag ? '' : undefined}\n                      data-tag={tag}\n                      role=\"menuitemradio\"\n                      tabIndex={-1}\n                      type=\"button\"\n                      onClick={() => applyTag(tag, targetsRef.current)}\n                    >\n                      <span aria-hidden className=\"fieldnotes-tag-dot\" />\n                      <span className=\"fieldnotes-selection-name\">{TAG_NAMES[tag]}</span>\n                      <span className=\"fieldnotes-selection-hint\">Suggested</span>\n                      <kbd aria-hidden>{index + 1}</kbd>\n                      <Icon className=\"fieldnotes-selection-check\" name=\"check\" />\n                    </button>\n                  ))}\n                  <div className=\"fieldnotes-selection-rule\" hidden={!anyTagged} role=\"separator\" />\n                  <button\n                    ref={(element) => {\n                      itemRefs.current[TAGS.length] = element;\n                    }}\n                    className=\"fieldnotes-selection-item fieldnotes-selection-quiet\"\n                    hidden={!anyTagged}\n                    role=\"menuitem\"\n                    tabIndex={-1}\n                    type=\"button\"\n                    onClick={removeFromMenu}\n                  >\n                    <Icon name=\"close\" />\n                    <span>Remove tag</span>\n                  </button>\n                </GlassPanel>\n              </div>\n            </div>\n            <div className=\"fieldnotes-dock\">\n              <GlassPanel\n                aria-hidden\n                className=\"fieldnotes-float fieldnotes-dock-tally\"\n                lens={false}\n                variant=\"pill\"\n              >\n                {TAGS.map((tag) => (\n                  <GlassBadge\n                    key={tag}\n                    ref={(element) => {\n                      if (element) dockRefs.current.set(tag, element);\n                      else dockRefs.current.delete(tag);\n                    }}\n                    className=\"fieldnotes-badge\"\n                    data-tag={tag}\n                    dot\n                  >\n                    {DOCK_NAMES[tag]} <b>{countOf(tag)}</b>\n                  </GlassBadge>\n                ))}\n              </GlassPanel>\n            </div>\n          </div>\n          <div className=\"fieldnotes-board-wrap\" id=\"board\">\n            <div aria-hidden className=\"fieldnotes-piece fieldnotes-board-note\">\n              <span className=\"fieldnotes-hand fieldnotes-hand-small\">\n                confirm with P02 before Thursday\n              </span>\n            </div>\n            <div aria-hidden className=\"fieldnotes-piece fieldnotes-scrap fieldnotes-board-card\" />\n            <GlassPanel\n              aria-labelledby=\"fieldnotes-board-title\"\n              className=\"fieldnotes-pane fieldnotes-board\"\n              lens\n              role=\"region\"\n              variant=\"card\"\n            >\n              <div className=\"fieldnotes-board-head\">\n                <div>\n                  <h3 className=\"fieldnotes-board-title\" id=\"fieldnotes-board-title\">\n                    Insights board\n                  </h3>\n                  <p className=\"fieldnotes-board-sub\">Month-end close · 7 interviews</p>\n                </div>\n                {tagged.length ? (\n                  <GlassButton className=\"fieldnotes-button-sm\" size=\"sm\" onClick={clearBoard}>\n                    Clear\n                  </GlassButton>\n                ) : null}\n              </div>\n              <div aria-hidden className=\"fieldnotes-tally\">\n                {TAGS.map((tag) => (\n                  <GlassBadge key={tag} className=\"fieldnotes-badge\" data-tag={tag} dot>\n                    {BOARD_NAMES[tag]} <b>{countOf(tag)}</b>\n                  </GlassBadge>\n                ))}\n              </div>\n              {moments.length ? (\n                <GlassScrollArea className=\"fieldnotes-moments\" viewportProps={{ ref: listRef }}>\n                  <ul aria-label=\"Tagged moments\" className=\"fieldnotes-moments-list\">\n                    {moments.map((moment) => {\n                      const sentence = SENTENCES[moment.sentence];\n\n                      return (\n                        <li\n                          key={moment.key}\n                          ref={(element) => {\n                            if (element) momentRefs.current.set(moment.key, element);\n                            else momentRefs.current.delete(moment.key);\n                          }}\n                          className=\"fieldnotes-moment\"\n                          data-leaving={moment.leaving ? '' : undefined}\n                          data-tag={moment.tag}\n                        >\n                          <p className=\"fieldnotes-moment-quote\">“{sentence.text}”</p>\n                          <p className=\"fieldnotes-moment-meta\">\n                            <i aria-hidden />\n                            {TAG_NAMES[moment.tag]} · P07 · {sentence.time}\n                          </p>\n                          <button\n                            aria-label={`Remove tag from “${shorten(sentence.text)}”`}\n                            className=\"fieldnotes-moment-remove\"\n                            type=\"button\"\n                            onClick={() => untag([moment.sentence], 'Tag removed.')}\n                          >\n                            <Icon name=\"close\" />\n                          </button>\n                        </li>\n                      );\n                    })}\n                  </ul>\n                </GlassScrollArea>\n              ) : (\n                <p className=\"fieldnotes-board-empty\">\n                  Nothing on the board yet. Tag a sentence and it lands here, with who said it and\n                  when.\n                </p>\n              )}\n              <div className=\"fieldnotes-patterns\">\n                <div className=\"fieldnotes-patterns-head\">\n                  <h4>Patterns</h4>\n                  <span>across this study</span>\n                </div>\n                {themes.size ? (\n                  <ul className=\"fieldnotes-pattern-list\">\n                    {PATTERNS.flatMap((pattern) => {\n                      if (!themes.has(pattern.theme)) return [];\n                      const isConfirmed = confirmed.includes(pattern.theme);\n\n                      return [\n                        <li key={pattern.theme} className=\"fieldnotes-pattern\">\n                          <div className=\"fieldnotes-pattern-top\">\n                            <p className=\"fieldnotes-pattern-title\">{pattern.title}</p>\n                            <span className=\"fieldnotes-pattern-count\">\n                              <b>{pattern.count}</b> of 7\n                            </span>\n                          </div>\n                          <div className=\"fieldnotes-pattern-foot\">\n                            <span aria-hidden className=\"fieldnotes-pattern-dots\">\n                              {PARTICIPANTS.map((participant) => (\n                                <i\n                                  key={participant}\n                                  data-new={participant === 'P07' ? '' : undefined}\n                                  data-on={pattern.seen.includes(participant) ? '' : undefined}\n                                  title={participant}\n                                />\n                              ))}\n                            </span>\n                            <button\n                              aria-pressed={isConfirmed}\n                              className=\"fieldnotes-pattern-confirm\"\n                              type=\"button\"\n                              onClick={() =>\n                                setConfirmed((current) =>\n                                  isConfirmed\n                                    ? current.filter((theme) => theme !== pattern.theme)\n                                    : [...current, pattern.theme],\n                                )\n                              }\n                            >\n                              {isConfirmed ? 'Confirmed' : 'Confirm'}\n                            </button>\n                          </div>\n                        </li>,\n                      ];\n                    })}\n                  </ul>\n                ) : (\n                  <p className=\"fieldnotes-patterns-empty\">\n                    When a moment you tag matches ones from earlier interviews, a pattern appears\n                    here.\n                  </p>\n                )}\n              </div>\n              <p aria-live=\"polite\" className=\"fieldnotes-sr-only\">\n                {announcement}\n              </p>\n            </GlassPanel>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n\n/**\n * Flies a copy of a tagged sentence from the transcript to its card, or to the docked tally when\n * the card is off screen, as long as both ends are in view.\n */\nfunction fly(\n  root: HTMLElement,\n  flight: Flight,\n  card: HTMLLIElement,\n  chip: HTMLElement | undefined,\n  spring: string,\n) {\n  const { from } = flight;\n\n  if (from.bottom < 0 || from.top > window.innerHeight) return;\n  let to = card.getBoundingClientRect();\n  let landing: HTMLElement = card;\n\n  if (!onScreen(to)) {\n    const box = chip?.getBoundingClientRect();\n\n    if (!chip || !box || !onScreen(box)) return;\n    to = box;\n    landing = chip;\n  }\n\n  const ghost = document.createElement('div');\n\n  ghost.className = 'fieldnotes-ghost';\n  ghost.dataset.tag = flight.tag;\n  ghost.setAttribute('aria-hidden', 'true');\n  ghost.textContent = flight.text;\n  ghost.style.left = `${from.left}px`;\n  ghost.style.top = `${from.top}px`;\n  ghost.style.width = `${Math.max(Math.min(from.width, 520), 140)}px`;\n  root.append(ghost);\n\n  const scale = to.width / ghost.offsetWidth;\n  const dx = to.left - from.left;\n  const dy = to.top - from.top;\n\n  if (landing === card) card.dataset.incoming = '';\n\n  ghost\n    .animate(\n      [\n        { transform: 'none' },\n        {\n          transform: `translate(${dx * 0.42}px, ${dy * 0.42 - 56}px) rotate(-2.5deg) scale(${(1 + scale) / 2})`,\n          offset: 0.48,\n        },\n        { transform: `translate(${dx}px, ${dy}px) scale(${scale})` },\n      ],\n      { duration: 720, delay: flight.delay, easing: EASE_OUT, fill: 'both' },\n    )\n    .finished.then(\n      () => {\n        ghost.remove();\n        delete card.dataset.incoming;\n        landing.animate([{ transform: 'translateY(-4px)' }, { transform: 'none' }], {\n          duration: 420,\n          easing: spring,\n        });\n      },\n      () => ghost.remove(),\n    );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/tag-a-moment.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/chapters.tsx",
      "content": "'use client';\n\nimport { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';\nimport { GlassBadge } from '../../glass-badge';\nimport { GlassButton } from '../../glass-button';\nimport { GlassKbd } from '../../glass-kbd';\nimport { GlassPanel } from '../../glass-panel';\nimport { GlassProgress } from '../../glass-progress';\nimport { GlassSwitch } from '../../glass-switch';\nimport { Icon, type IconName } from './icons';\nimport { Folio, Initials, type Tone } from './parts';\n\nfunction Chapter({\n  body,\n  figure,\n  flip,\n  id,\n  label,\n  list,\n  numeral,\n  title,\n}: {\n  body: string;\n  figure: ReactNode;\n  flip?: boolean;\n  id: string;\n  label: string;\n  list: readonly [IconName, string][];\n  numeral: string;\n  title: string;\n}) {\n  return (\n    <article aria-labelledby={id} className=\"fieldnotes-chapter\" data-flip={flip ? '' : undefined}>\n      <div className=\"fieldnotes-chapter-text\">\n        <p className=\"fieldnotes-chapter-label\">\n          <span aria-hidden className=\"fieldnotes-chapter-numeral\">\n            {numeral}\n          </span>\n          <span className=\"fieldnotes-eyebrow\">{label}</span>\n        </p>\n        <h3 className=\"fieldnotes-chapter-title\" id={id}>\n          {title}\n        </h3>\n        <p className=\"fieldnotes-chapter-body\">{body}</p>\n        <ul className=\"fieldnotes-chapter-list\">\n          {list.map(([icon, text]) => (\n            <li key={text}>\n              <Icon name={icon} />\n              {text}\n            </li>\n          ))}\n        </ul>\n      </div>\n      {figure}\n    </article>\n  );\n}\n\n/** A figure: a plate of desk things with one pane of the product resting on them. */\nfunction Plate({\n  caption,\n  children,\n  kind,\n  number,\n}: {\n  caption: string;\n  children: ReactNode;\n  kind: 'capture' | 'tag' | 'pattern' | 'share';\n  number: number;\n}) {\n  return (\n    <figure className=\"fieldnotes-chapter-figure\">\n      <div className=\"fieldnotes-plate\" data-kind={kind} data-reveal=\"plate\">\n        {children}\n      </div>\n      <figcaption>\n        <span className=\"fieldnotes-figure-number\">Fig. {number}</span>\n        {caption}\n      </figcaption>\n    </figure>\n  );\n}\n\nconst SESSIONS: readonly { initials: string; tone: Tone; name: string; detail: string }[] = [\n  {\n    initials: 'DR',\n    tone: 'ochre',\n    name: 'Interview 07 · Dana R.',\n    detail: '41:12 · 2 names redacted',\n  },\n  { initials: 'MO', tone: 'sage', name: 'Interview 08 · Malik O.', detail: '' },\n  {\n    initials: 'IT',\n    tone: 'plum',\n    name: 'Interview 09 · Ines T.',\n    detail: '52:03 · uploaded 4 min ago',\n  },\n];\n\nfunction CaptureFigure() {\n  return (\n    <Plate\n      caption=\"Sessions arrive transcribed and redacted. Interview eight is still being transcribed.\"\n      kind=\"capture\"\n      number={1}\n    >\n      <div aria-hidden className=\"fieldnotes-piece fieldnotes-scrap fieldnotes-photo\" />\n      <div aria-hidden className=\"fieldnotes-piece fieldnotes-scrap fieldnotes-sleeve\">\n        <span className=\"fieldnotes-typed\">\n          <b>Recordings</b>\n          <br />\n          Week 11 · nine sessions\n        </span>\n      </div>\n      <span aria-hidden className=\"fieldnotes-piece fieldnotes-tape fieldnotes-capture-tape-a\" />\n      <span aria-hidden className=\"fieldnotes-piece fieldnotes-tape fieldnotes-capture-tape-b\" />\n      <GlassPanel className=\"fieldnotes-pane fieldnotes-figure-pane\" lens variant=\"card\">\n        <div className=\"fieldnotes-figure-bar\">\n          <p className=\"fieldnotes-figure-title\">Month-end close</p>\n          <GlassBadge className=\"fieldnotes-badge\">9 sessions</GlassBadge>\n        </div>\n        <ul className=\"fieldnotes-sessions\">\n          {SESSIONS.map((session, index) => (\n            <li key={session.name}>\n              <Initials initials={session.initials} small tone={session.tone} />\n              <span className=\"fieldnotes-session-who\">\n                <b>{session.name}</b>\n                {index === 1 ? (\n                  <GlassProgress\n                    aria-label=\"Transcribing interview 08\"\n                    className=\"fieldnotes-meter\"\n                    size=\"sm\"\n                    value={82}\n                  />\n                ) : (\n                  <span>{session.detail}</span>\n                )}\n              </span>\n              {index === 0 ? (\n                <span className=\"fieldnotes-session-state\" data-done=\"\">\n                  <Icon name=\"check\" />\n                  Ready\n                </span>\n              ) : (\n                <span className=\"fieldnotes-session-state\">{index === 1 ? '82%' : 'Queued'}</span>\n              )}\n            </li>\n          ))}\n        </ul>\n      </GlassPanel>\n    </Plate>\n  );\n}\n\nconst TAXONOMY: readonly { name: string; key: string; count: number; colour: string }[] = [\n  { name: 'Pain point', key: '1', count: 214, colour: 'pain' },\n  { name: 'Workaround', key: '2', count: 131, colour: 'work' },\n  { name: 'Quote', key: '3', count: 96, colour: 'quote' },\n  { name: 'Feature request', key: '4', count: 58, colour: 'request' },\n  { name: 'Delight', key: '5', count: 37, colour: 'delight' },\n];\n\nfunction TagFigure() {\n  return (\n    <Plate\n      caption=\"A team taxonomy. Each tag carries a shortcut and a running count.\"\n      kind=\"tag\"\n      number={2}\n    >\n      {(['pain', 'work', 'quote'] as const).map((tag) => (\n        <div\n          key={tag}\n          aria-hidden\n          className=\"fieldnotes-piece fieldnotes-scrap fieldnotes-tag-card\"\n          data-tag={tag}\n        >\n          <i className=\"fieldnotes-tab-sticker\" />\n          <span className=\"fieldnotes-hand\">{tag === 'work' ? 'workaround' : tag}</span>\n        </div>\n      ))}\n      <svg aria-hidden className=\"fieldnotes-piece fieldnotes-swash\" viewBox=\"0 0 240 40\">\n        <path d=\"M8 26c40-10 90-14 140-12s70 4 84 2\" />\n      </svg>\n      <GlassPanel className=\"fieldnotes-pane fieldnotes-figure-pane\" lens variant=\"card\">\n        <div className=\"fieldnotes-figure-bar\">\n          <p className=\"fieldnotes-figure-title\">Team taxonomy</p>\n          <GlassBadge className=\"fieldnotes-badge fieldnotes-badge-jade\" dot>\n            Shared\n          </GlassBadge>\n        </div>\n        <ul className=\"fieldnotes-taxonomy\">\n          {TAXONOMY.map((tag) => (\n            <li\n              key={tag.name}\n              data-colour={tag.colour}\n              // SAFETY: CSSProperties has no custom-property index; this adds one number-valued property.\n              style={{ '--w': tag.count / TAXONOMY[0].count } as CSSProperties}\n            >\n              <span aria-hidden className=\"fieldnotes-tag-dot\" />\n              <span>{tag.name}</span>\n              <GlassKbd>{tag.key}</GlassKbd>\n              <span className=\"fieldnotes-taxonomy-count\">{tag.count}</span>\n            </li>\n          ))}\n        </ul>\n      </GlassPanel>\n    </Plate>\n  );\n}\n\nconst STICKIES = [\n  { label: 'P02 · Mar', colour: 'yellow', place: 'a' },\n  { label: 'P05 · May', colour: 'rose', place: 'b' },\n  { label: 'P11 · Jul', colour: 'sage', place: 'c' },\n  { label: 'P07 · Sep', colour: 'yellow', place: 'd' },\n];\n\nconst MOMENTS = [\n  [26, 22],\n  [42, 22],\n  [62, 22],\n  [86, 22],\n  [176, 50],\n  [204, 50],\n  [236, 50],\n  [304, 78],\n  [322, 78],\n];\n\nconst MONTHS: readonly [string, number][] = [\n  ['Mar', 20],\n  ['May', 120],\n  ['Jul', 220],\n  ['Sep', 320],\n];\n\nconst PEOPLE: readonly [string, Tone][] = [\n  ['JK', 'oxblood'],\n  ['DR', 'ochre'],\n  ['LF', 'sage'],\n  ['SN', 'plum'],\n  ['BW', 'ink'],\n];\n\nfunction PatternFigure() {\n  return (\n    <Plate\n      caption=\"One pattern across three studies, nine participants and seven months.\"\n      kind=\"pattern\"\n      number={3}\n    >\n      <div aria-hidden className=\"fieldnotes-piece fieldnotes-graph\" />\n      {STICKIES.map((sticky) => (\n        <div\n          key={sticky.label}\n          aria-hidden\n          className=\"fieldnotes-piece fieldnotes-sticky\"\n          data-colour={sticky.colour}\n          data-place={sticky.place}\n        >\n          <span className=\"fieldnotes-hand\">{sticky.label}</span>\n        </div>\n      ))}\n      <svg\n        aria-hidden\n        className=\"fieldnotes-string\"\n        preserveAspectRatio=\"none\"\n        viewBox=\"0 0 100 100\"\n      >\n        <polyline points=\"18,10 83,79\" />\n        <polyline points=\"82,7 18,82\" />\n        <polyline points=\"18,10 82,7\" />\n      </svg>\n      <GlassPanel className=\"fieldnotes-pane fieldnotes-figure-pane\" lens variant=\"card\">\n        <div className=\"fieldnotes-figure-bar\">\n          <GlassBadge className=\"fieldnotes-badge\">\n            <Icon className=\"fieldnotes-icon-small\" name=\"pattern\" />\n            Pattern\n          </GlassBadge>\n          <span className=\"fieldnotes-figure-meta\">\n            Confirmed<span className=\"fieldnotes-wide-only\"> by Maren A.</span>\n          </span>\n        </div>\n        <p className=\"fieldnotes-figure-heading\">Data is re-keyed between tools</p>\n        <p className=\"fieldnotes-figure-sub\">9 participants · 3 studies · March to September</p>\n        <svg\n          aria-label=\"Nine moments across three studies, from March to September\"\n          className=\"fieldnotes-timeline\"\n          role=\"img\"\n          viewBox=\"0 0 340 112\"\n        >\n          <g className=\"fieldnotes-timeline-grid\">\n            {[20, 70, 120, 170, 220, 270, 320].map((x) => (\n              <line key={x} x1={x} x2={x} y1=\"6\" y2=\"90\" />\n            ))}\n          </g>\n          <path\n            className=\"fieldnotes-timeline-thread\"\n            d=\"M26 22H86C128 22 136 50 176 50H236C276 50 282 78 304 78H322\"\n            pathLength={1}\n          />\n          {MOMENTS.map(([x, y]) => (\n            <circle key={x} cx={x} cy={y} r=\"5\" />\n          ))}\n          <text className=\"fieldnotes-timeline-row\" x=\"20\" y=\"11\">\n            Onboarding\n          </text>\n          <text className=\"fieldnotes-timeline-row\" x=\"170\" y=\"39\">\n            Pricing\n          </text>\n          <text className=\"fieldnotes-timeline-row\" textAnchor=\"end\" x=\"326\" y=\"67\">\n            Month-end\n          </text>\n          {MONTHS.map(([month, x]) => (\n            <text key={month} textAnchor=\"middle\" x={x} y=\"106\">\n              {month}\n            </text>\n          ))}\n        </svg>\n        <div className=\"fieldnotes-people\">\n          {PEOPLE.map(([initials, tone]) => (\n            <Initials key={initials} initials={initials} small tone={tone} />\n          ))}\n          <span>and 4 more</span>\n        </div>\n      </GlassPanel>\n    </Plate>\n  );\n}\n\nconst WAVE = [\n  0.3, 0.5, 0.42, 0.7, 0.9, 0.62, 0.4, 0.55, 0.8, 1, 0.74, 0.5, 0.36, 0.6, 0.86, 0.66, 0.44, 0.3,\n  0.52, 0.78, 0.94, 0.7, 0.48, 0.34, 0.58, 0.82, 0.6, 0.4, 0.28, 0.46, 0.68, 0.5, 0.36, 0.24,\n];\n\nconst CLIP_SECONDS = 14;\n\nconst clock = (seconds: number) => `0:${String(Math.floor(seconds)).padStart(2, '0')}`;\n\n/** A fourteen-second clip that plays through its waveform, in place of a recording. */\nfunction Clip() {\n  const [playing, setPlaying] = useState(false);\n  const [position, setPosition] = useState(0);\n  const positionRef = useRef(0);\n\n  useEffect(() => {\n    if (!playing) return;\n    let last = performance.now();\n\n    let frame = requestAnimationFrame(function tick(now) {\n      positionRef.current += (now - last) / 1000;\n      last = now;\n\n      if (positionRef.current >= CLIP_SECONDS) {\n        positionRef.current = 0;\n        setPosition(0);\n        setPlaying(false);\n\n        return;\n      }\n\n      setPosition(positionRef.current);\n      frame = requestAnimationFrame(tick);\n    });\n\n    return () => cancelAnimationFrame(frame);\n  }, [playing]);\n\n  const played = Math.round((position / CLIP_SECONDS) * WAVE.length);\n\n  return (\n    <div className=\"fieldnotes-clip\">\n      <GlassButton\n        aria-label={playing ? 'Pause clip' : 'Play clip from interview 07'}\n        aria-pressed={playing}\n        className=\"fieldnotes-button-sm fieldnotes-clip-play\"\n        size=\"sm\"\n        onClick={() => setPlaying((current) => !current)}\n      >\n        <Icon name={playing ? 'pause' : 'play'} />\n      </GlassButton>\n      <div aria-hidden className=\"fieldnotes-clip-wave\">\n        {WAVE.map((height, index) => (\n          <i\n            key={index}\n            data-on={index < played ? '' : undefined}\n            // SAFETY: CSSProperties has no custom-property index; this adds one number-valued property.\n            style={{ '--h': height } as CSSProperties}\n          />\n        ))}\n      </div>\n      <span className=\"fieldnotes-clip-time\">\n        {clock(position)} / {clock(CLIP_SECONDS)}\n      </span>\n    </div>\n  );\n}\n\nfunction ShareFigure() {\n  const [clips, setClips] = useState(true);\n\n  return (\n    <Plate\n      caption=\"An insight page. Readers hear the clip and see who said it.\"\n      kind=\"share\"\n      number={4}\n    >\n      <div aria-hidden className=\"fieldnotes-piece fieldnotes-scrap fieldnotes-readout\">\n        <span className=\"fieldnotes-typed\">\n          <b>Readout · Q3</b>\n        </span>\n        <span className=\"fieldnotes-readout-lines\">\n          {[92, 84, 88, 60, 90, 72].map((width) => (\n            <i key={width} style={{ width: `${width}%` }} />\n          ))}\n        </span>\n      </div>\n      <div aria-hidden className=\"fieldnotes-piece fieldnotes-postage\">\n        <div className=\"fieldnotes-postage-art\">\n          Fieldnotes\n          <br />\n          No. 14\n        </div>\n      </div>\n      <svg aria-hidden className=\"fieldnotes-piece fieldnotes-postmark\" viewBox=\"0 0 200 80\">\n        <circle cx=\"42\" cy=\"40\" fill=\"none\" r=\"30\" stroke=\"currentColor\" strokeWidth=\"2\" />\n        <text\n          className=\"fieldnotes-postmark-place\"\n          fill=\"currentColor\"\n          textAnchor=\"middle\"\n          x=\"42\"\n          y=\"37\"\n        >\n          BRISTOL\n        </text>\n        <text\n          className=\"fieldnotes-postmark-date\"\n          fill=\"currentColor\"\n          textAnchor=\"middle\"\n          x=\"42\"\n          y=\"50\"\n        >\n          30 OCT 26\n        </text>\n        <path\n          d=\"M80 22c10-6 20 6 30 0s20 6 30 0 20 6 30 0 14 4 20 2M80 36c10-6 20 6 30 0s20 6 30 0 20 6 30 0 14 4 20 2M80 50c10-6 20 6 30 0s20 6 30 0 20 6 30 0 14 4 20 2M80 64c10-6 20 6 30 0s20 6 30 0 20 6 30 0 14 4 20 2\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n        />\n      </svg>\n      <div aria-hidden className=\"fieldnotes-piece fieldnotes-envelope\" />\n      <GlassPanel className=\"fieldnotes-pane fieldnotes-figure-pane\" lens variant=\"card\">\n        <div className=\"fieldnotes-figure-bar\">\n          <p className=\"fieldnotes-figure-meta\">\n            Insight<span className=\"fieldnotes-wide-only\"> · published</span>\n            <span className=\"fieldnotes-roomy-only\"> · 12 October</span>\n          </p>\n          <GlassBadge className=\"fieldnotes-badge\">\n            <Icon className=\"fieldnotes-icon-small\" name=\"users\" />\n            14 readers\n          </GlassBadge>\n        </div>\n        <p className=\"fieldnotes-figure-heading\">\n          Finance stops trusting the dashboard after day five\n        </p>\n        <Clip />\n        <blockquote className=\"fieldnotes-clip-quote\">\n          “Because I don’t trust it after the fifth of the month.”\n          <span>Dana R. · interview 07 · 04:35</span>\n        </blockquote>\n        <div className=\"fieldnotes-setting\">\n          <label htmlFor=\"fieldnotes-reader-clips\">\n            Readers can play clips\n            <span id=\"fieldnotes-reader-clips-note\">\n              {clips\n                ? 'Readers hear the recording and see who said it.'\n                : 'Readers see the quote, not the recording.'}\n            </span>\n          </label>\n          <GlassSwitch\n            aria-describedby=\"fieldnotes-reader-clips-note\"\n            aria-label=\"Readers can play clips\"\n            checked={clips}\n            id=\"fieldnotes-reader-clips\"\n            onCheckedChange={setClips}\n          />\n        </div>\n      </GlassPanel>\n    </Plate>\n  );\n}\n\nexport function Chapters() {\n  return (\n    <section\n      aria-labelledby=\"fieldnotes-chapters-title\"\n      className=\"fieldnotes-section fieldnotes-chapters\"\n      id=\"chapters\"\n    >\n      <div className=\"fieldnotes-wrap\">\n        <Folio name=\"Four chapters\" page=\"06\" />\n        <div className=\"fieldnotes-section-head\">\n          <h2 className=\"fieldnotes-h2\" data-reveal=\"focus\" id=\"fieldnotes-chapters-title\">\n            The tool, in <em>four</em> chapters.\n          </h2>\n          <p className=\"fieldnotes-dek\">\n            Capture, tag, find, share. Each is small on its own. Together they are how a team\n            remembers what it heard.\n          </p>\n        </div>\n        <Chapter\n          body=\"Drop in recordings from any meeting tool, or connect a folder and let new sessions arrive on their own. Fieldnotes transcribes in 31 languages, labels speakers and keeps every word pinned to its second in the recording.\"\n          figure={<CaptureFigure />}\n          id=\"fieldnotes-chapter-1\"\n          label=\"Chapter one · Capture\"\n          list={[\n            ['upload', 'Audio or video up to four hours long'],\n            ['speaker', 'Speaker labels you can correct in place'],\n            ['lock', 'Names and numbers redacted before anyone else reads them'],\n          ]}\n          numeral=\"I\"\n          title=\"Bring every conversation into one room.\"\n        />\n        <Chapter\n          body=\"Select a sentence and press a number. Tags come from a taxonomy your team agrees on, so a pain point means the same thing to everyone who reads it. Most researchers tag an hour of interview in under fifteen minutes.\"\n          figure={<TagFigure />}\n          flip\n          id=\"fieldnotes-chapter-2\"\n          label=\"Chapter two · Tag\"\n          list={[\n            ['keys', 'Number keys for your five most used tags'],\n            ['list', 'One shared taxonomy, with a definition for every tag'],\n            ['clock', 'Every tag keeps the speaker, the date and the second'],\n          ]}\n          numeral=\"II\"\n          title=\"Mark the moment, not the meeting.\"\n        />\n        <Chapter\n          body=\"Search every interview your team has run, by tag, by phrase or by who was speaking. When moments from different studies describe the same thing, Fieldnotes suggests a pattern. A researcher confirms it before anyone else sees it.\"\n          figure={<PatternFigure />}\n          id=\"fieldnotes-chapter-3\"\n          label=\"Chapter three · Find\"\n          list={[\n            ['search', 'Search across studies, tags and speakers'],\n            ['pattern', 'Suggested patterns, always confirmed by a person'],\n            ['growth', 'See when a pattern first appeared and how it grew'],\n          ]}\n          numeral=\"III\"\n          title=\"Let a March study meet a September one.\"\n        />\n        <Chapter\n          body=\"Publish a pattern as a short page your stakeholders can read in two minutes. Every claim sits beside the quotes behind it, and every quote plays from its own second in the recording. Readers are free, so everyone who should listen can.\"\n          figure={<ShareFigure />}\n          flip\n          id=\"fieldnotes-chapter-4\"\n          label=\"Chapter four · Share\"\n          list={[\n            ['page', 'Insight pages that show the quotes behind each claim'],\n            ['wave', 'Clips that play from the exact second'],\n            ['users', 'Unlimited free readers, with comments'],\n          ]}\n          numeral=\"IV\"\n          title=\"Hand over evidence, not opinions.\"\n        />\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/chapters.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/field.tsx",
      "content": "import { Count, Folio, PullQuote } from './parts';\n\nconst STATS = [\n  { label: 'researchers keep their interviews here', value: 4200 },\n  { label: 'moments tagged so far', value: 1.3, decimals: 1, unit: 'million' },\n  { label: 'median time to find a quote', value: 38, unit: 'seconds' },\n  { label: 'free readers for every researcher', value: 11 },\n];\n\nexport function Field() {\n  return (\n    <section\n      aria-labelledby=\"fieldnotes-field-title\"\n      className=\"fieldnotes-section fieldnotes-field\"\n      id=\"field\"\n    >\n      <div className=\"fieldnotes-wrap\">\n        <Folio name=\"From the field\" page=\"09\" />\n        <h2 className=\"fieldnotes-h2\" data-reveal=\"focus\" id=\"fieldnotes-field-title\">\n          From the <em>field</em>.\n        </h2>\n        <div className=\"fieldnotes-field-lead\">\n          <PullQuote\n            initials=\"PL\"\n            large\n            name=\"Priya Lindqvist\"\n            quote=\"I stopped writing readout decks. Now I send one page, and people actually press play on the quotes.\"\n            role=\"Research lead, Larkmere\"\n            tone=\"sage\"\n          />\n        </div>\n        <div className=\"fieldnotes-field-pair\">\n          <PullQuote\n            initials=\"TW\"\n            name=\"Tomasz Wren\"\n            quote=\"Our designers search Fieldnotes before they ask me. That was the whole point.\"\n            role=\"Senior researcher, Tidelock\"\n            tone=\"ochre\"\n          />\n          <PullQuote\n            initials=\"AO\"\n            name=\"Adaeze Okafor\"\n            quote=\"In our first week we found the same complaint in two studies run nineteen months apart.\"\n            role=\"Head of research, Fernhaven\"\n            tone=\"plum\"\n          />\n        </div>\n        <dl className=\"fieldnotes-stats\">\n          {STATS.map((stat) => (\n            <div key={stat.label} className=\"fieldnotes-stat\">\n              <dt>{stat.label}</dt>\n              <dd>\n                <Count decimals={stat.decimals} to={stat.value} />\n                {stat.unit ? <small> {stat.unit}</small> : null}\n              </dd>\n            </div>\n          ))}\n        </dl>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/field.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/rates.tsx",
      "content": "'use client';\n\nimport { useEffect, useRef, useState } from 'react';\nimport { GlassButton } from '../../glass-button';\nimport { GlassPanel } from '../../glass-panel';\nimport { GlassSegmentedControl, GlassSegmentedControlItem } from '../../glass-segmented-control';\nimport { motionReduced } from '../../lib/liquid-motion';\nimport { Stamp } from './icons';\nimport { Folio, slotted } from './parts';\n\ntype Billing = 'monthly' | 'yearly';\n\nconst PLANS = [\n  {\n    name: 'Reader',\n    monthly: 0,\n    yearly: 0,\n    per: { monthly: 'free, always', yearly: 'free, always' },\n    audience: 'For stakeholders who read and listen',\n    action: 'Join as a reader',\n  },\n  {\n    name: 'Researcher',\n    monthly: 18,\n    yearly: 15,\n    per: { monthly: 'per editor a month', yearly: 'per editor a month, billed yearly' },\n    audience: 'For people who run and tag interviews',\n    action: 'Start free',\n  },\n  {\n    name: 'Team',\n    monthly: 32,\n    yearly: 27,\n    per: { monthly: 'per editor a month', yearly: 'per editor a month, billed yearly' },\n    audience: 'For teams who share one taxonomy',\n    action: 'Talk to us',\n  },\n];\n\n// An empty cell is a dash on screen and its reason, \"Not included\" unless the row says otherwise,\n// to assistive technology.\nconst FEATURES: readonly { name: string; cells: string[]; absent?: string }[] = [\n  { name: 'Interviews', cells: ['Read anything shared', 'Unlimited', 'Unlimited'] },\n  { name: 'Transcription', cells: ['', '20 hours a month', '60 hours a month per editor'] },\n  {\n    name: 'Tags and patterns',\n    cells: ['View', 'Tag, search and confirm', 'Plus a shared taxonomy'],\n  },\n  { name: 'Insight pages', cells: ['Read and comment', 'Publish', 'Publish with review'] },\n  { name: 'Readers', cells: ['', 'Unlimited, free', 'Unlimited, free'], absent: 'Not applicable' },\n  { name: 'Redaction', cells: ['', 'Names and numbers', 'Custom rules per study'] },\n  {\n    name: 'Retention',\n    cells: ['', 'Delete recordings after a set time', 'Per-study schedules and legal hold'],\n  },\n  { name: 'Single sign-on', cells: ['', '', 'Included'] },\n  { name: 'Support', cells: ['Help centre', 'Email, next working day', 'A named person'] },\n];\n\n/**\n * The prices count over to yearly billing, and on a phone one plan shows at a time beside the\n * features.\n */\nexport function Rates({ onStartFree }: { onStartFree: () => void }) {\n  const [billing, setBilling] = useState<Billing>('monthly');\n  const [plan, setPlan] = useState('1');\n  const [prices, setPrices] = useState(() => PLANS.map((entry) => entry.monthly));\n  const frame = useRef(0);\n\n  useEffect(() => () => cancelAnimationFrame(frame.current), []);\n\n  const changeBilling = (value: string) => {\n    const next: Billing = value === 'yearly' ? 'yearly' : 'monthly';\n    const targets = PLANS.map((entry) => entry[next]);\n    const from = prices;\n\n    setBilling(next);\n    cancelAnimationFrame(frame.current);\n\n    if (motionReduced()) {\n      setPrices(targets);\n\n      return;\n    }\n\n    const start = performance.now();\n\n    const step = (now: number) => {\n      const progress = Math.min(1, (now - start) / 460);\n      const eased = 1 - (1 - progress) ** 3;\n\n      setPrices(from.map((price, index) => Math.round(price + (targets[index] - price) * eased)));\n\n      if (progress < 1) frame.current = requestAnimationFrame(step);\n    };\n\n    frame.current = requestAnimationFrame(step);\n  };\n\n  return (\n    <section\n      aria-labelledby=\"fieldnotes-rates-title\"\n      className=\"fieldnotes-section fieldnotes-rates\"\n      id=\"rates\"\n    >\n      <div className=\"fieldnotes-wrap\">\n        <Folio name=\"Rates\" page=\"11\" />\n        <div className=\"fieldnotes-grid fieldnotes-rates-head\">\n          <h2 className=\"fieldnotes-h2\" data-reveal=\"focus\" id=\"fieldnotes-rates-title\">\n            Subscription <em>rates</em>.\n          </h2>\n          <div className=\"fieldnotes-rates-side\">\n            <p className=\"fieldnotes-dek\">\n              Readers are always free. You pay for the people who tag.\n            </p>\n            <GlassSegmentedControl\n              aria-label=\"Billing period\"\n              className=\"fieldnotes-segmented\"\n              value={billing}\n              onValueChange={changeBilling}\n            >\n              <GlassSegmentedControlItem value=\"monthly\">Monthly</GlassSegmentedControlItem>\n              <GlassSegmentedControlItem value=\"yearly\">Yearly</GlassSegmentedControlItem>\n            </GlassSegmentedControl>\n            <p>Yearly billing gives you two months free.</p>\n          </div>\n        </div>\n        <GlassSegmentedControl\n          aria-label=\"Show plan\"\n          className=\"fieldnotes-segmented fieldnotes-rates-plans\"\n          value={plan}\n          onValueChange={setPlan}\n        >\n          {PLANS.map((entry, index) => (\n            <GlassSegmentedControlItem key={entry.name} value={String(index)}>\n              {entry.name}\n            </GlassSegmentedControlItem>\n          ))}\n        </GlassSegmentedControl>\n        <div\n          aria-label=\"Plans and prices\"\n          className=\"fieldnotes-pricing\"\n          data-show={plan}\n          role=\"table\"\n        >\n          <GlassPanel\n            aria-hidden\n            className=\"fieldnotes-pane fieldnotes-pricing-glass\"\n            lens\n            variant=\"card\"\n          />\n          <Stamp\n            centre=\"★\"\n            className=\"fieldnotes-pricing-stamp\"\n            id=\"fieldnotes-pricing-stamp\"\n            ring=\"MOST TEAMS START HERE ·\"\n          />\n          <div className=\"fieldnotes-pricing-row fieldnotes-pricing-head\" role=\"row\">\n            <span role=\"columnheader\">\n              <span className=\"fieldnotes-sr-only\">Feature</span>\n            </span>\n            {PLANS.map((entry, index) => (\n              <div key={entry.name} data-column={index} role=\"columnheader\">\n                <p className=\"fieldnotes-pricing-plan\">{entry.name}</p>\n                <p className=\"fieldnotes-pricing-price\">\n                  <span className=\"fieldnotes-pricing-amount\">${prices[index]}</span>\n                  <span className=\"fieldnotes-pricing-per\">{entry.per[billing]}</span>\n                </p>\n                <span className=\"fieldnotes-pricing-for\">{entry.audience}</span>\n              </div>\n            ))}\n          </div>\n          {FEATURES.map((feature) => (\n            <div key={feature.name} className=\"fieldnotes-pricing-row\" role=\"row\">\n              <span role=\"rowheader\">{feature.name}</span>\n              {feature.cells.map((cell, index) => (\n                <span\n                  key={index}\n                  data-absent={cell ? undefined : ''}\n                  data-column={index}\n                  role=\"cell\"\n                >\n                  {cell || (\n                    <>\n                      <span aria-hidden>—</span>\n                      <span className=\"fieldnotes-sr-only\">{feature.absent ?? 'Not included'}</span>\n                    </>\n                  )}\n                </span>\n              ))}\n            </div>\n          ))}\n          <div className=\"fieldnotes-pricing-row fieldnotes-pricing-foot\" role=\"row\">\n            <span role=\"cell\">\n              <span className=\"fieldnotes-sr-only\">Choose a plan</span>\n            </span>\n            <span data-column={0} role=\"cell\">\n              <GlassButton {...slotted} className=\"fieldnotes-button-md\">\n                <a href=\"#start\" onClick={onStartFree}>\n                  {PLANS[0].action}\n                </a>\n              </GlassButton>\n            </span>\n            <span data-column={1} role=\"cell\">\n              <GlassButton {...slotted} className=\"fieldnotes-button-md\" variant=\"prominent\">\n                <a href=\"#start\" onClick={onStartFree}>\n                  {PLANS[1].action}\n                </a>\n              </GlassButton>\n            </span>\n            <span data-column={2} role=\"cell\">\n              <GlassButton {...slotted} className=\"fieldnotes-button-md\">\n                <a href=\"mailto:hello@fieldnotes.example\">{PLANS[2].action}</a>\n              </GlassButton>\n            </span>\n          </div>\n        </div>\n        <p className=\"fieldnotes-rates-note\">\n          Prices exclude VAT. Billed in US dollars, euros or pounds.\n        </p>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/rates.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/letters.tsx",
      "content": "'use client';\n\nimport {\n  GlassAccordion,\n  GlassAccordionContent,\n  GlassAccordionItem,\n  GlassAccordionTrigger,\n} from '../../glass-accordion';\nimport { Folio, slotted } from './parts';\n\nconst LETTERS = [\n  [\n    {\n      question:\n        'Our interviews are full of health details and names. Where does all of this live, and who can read it?',\n      from: 'A researcher in Leeds',\n      reply:\n        'Your workspace lives in the EU or the US, your choice, encrypted at rest and in transit. Names, emails and card numbers are redacted from transcripts before anyone but the interviewer sees them, and recordings can delete themselves after a set number of days.',\n    },\n    {\n      question:\n        'We already keep everything in a carefully named shared drive. Why would we move it?',\n      from: 'Two researchers in Lisbon',\n      reply:\n        'You don’t have to. Connect the folder and Fieldnotes reads new sessions as they land. The drive stays your archive. Fieldnotes becomes the place where moments are tagged, found and shared.',\n    },\n    {\n      question: 'Will it tag my interviews for me? I’m not sure I want it to.',\n      from: 'A sceptic in Montréal',\n      reply:\n        'It suggests and you decide. Suggestions are marked as suggestions, and nothing is shared until a researcher confirms it. Most teams treat them as a second pair of ears on long sessions.',\n    },\n  ],\n  [\n    {\n      question: 'Do our product managers need a paid seat to read what we find?',\n      from: 'A team of one in Austin',\n      reply:\n        'No. Readers are free and unlimited. They can read insight pages, play clips and leave comments. You pay only for the people who tag.',\n    },\n    {\n      question: 'What happens to our work if we leave?',\n      from: 'A head of research in Utrecht',\n      reply:\n        'You take it with you. Export transcripts, tags, patterns and clips at any time as plain text, CSV, JSON and video files. Cancelling is a button, not a call.',\n    },\n    {\n      question: 'Which recordings can it read?',\n      from: 'A researcher on a train',\n      reply:\n        'Audio and video files up to four hours, uploaded directly or from a connected folder, in 31 languages. If your meeting tool can save a recording, Fieldnotes can read it.',\n    },\n  ],\n];\n\nexport function Letters() {\n  return (\n    <section\n      aria-labelledby=\"fieldnotes-letters-title\"\n      className=\"fieldnotes-section fieldnotes-letters\"\n      id=\"letters\"\n    >\n      <div className=\"fieldnotes-wrap\">\n        <Folio name=\"Correspondence\" page=\"12\" />\n        <div className=\"fieldnotes-section-head\">\n          <h2 className=\"fieldnotes-h2\" data-reveal=\"focus\" id=\"fieldnotes-letters-title\">\n            Letters to the <em>editor</em>.\n          </h2>\n          <p className=\"fieldnotes-dek\">\n            Questions readers sent us this month, answered by the founder.\n          </p>\n        </div>\n        <GlassAccordion className=\"fieldnotes-letters-list\" type=\"multiple\">\n          {LETTERS.map((column) => (\n            <div key={column[0].from} className=\"fieldnotes-letters-column\">\n              {column.map((letter) => (\n                <GlassAccordionItem key={letter.from} {...slotted} value={letter.from}>\n                  <article className=\"fieldnotes-lte\">\n                    <p className=\"fieldnotes-lte-salute\">Dear editor,</p>\n                    {/* The letter itself is the heading and the button that opens its reply. */}\n                    <GlassAccordionTrigger className=\"fieldnotes-lte-toggle\">\n                      <span className=\"fieldnotes-lte-question\">{letter.question}</span>\n                      <span className=\"fieldnotes-lte-from\">{letter.from}</span>\n                      <span className=\"fieldnotes-lte-replies\">The editor replies</span>\n                    </GlassAccordionTrigger>\n                    <GlassAccordionContent className=\"fieldnotes-lte-reply\">\n                      <p>{letter.reply}</p>\n                    </GlassAccordionContent>\n                  </article>\n                </GlassAccordionItem>\n              ))}\n            </div>\n          ))}\n        </GlassAccordion>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/letters.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/sign-off.tsx",
      "content": "'use client';\n\nimport { useState, type Ref } from 'react';\nimport { GlassButton } from '../../glass-button';\nimport { GlassInput } from '../../glass-input';\nimport { Folio } from './parts';\n\n/** Sends a sign-in link to start a trial. Reject to tell the visitor it did not go through. */\nexport type StartTrial = (email: string) => Promise<void>;\n\nconst looksLikeEmail = (value: string) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(value);\n\n/** Without `onStart` the form is a demo: it confirms on screen and sends nothing. */\nexport function SignOff({\n  emailRef,\n  onNotice,\n  onStart,\n}: {\n  emailRef: Ref<HTMLInputElement>;\n  onNotice: (message: string) => void;\n  onStart?: StartTrial;\n}) {\n  const [email, setEmail] = useState('');\n  const [error, setError] = useState('');\n  const [pending, setPending] = useState(false);\n  const [sent, setSent] = useState(false);\n\n  const submit = async (event: React.FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n\n    if (pending) return;\n    const field = event.currentTarget.elements.namedItem('email');\n    const value = email.trim();\n\n    if (!looksLikeEmail(value)) {\n      setError('Enter a work email, like dana@company.com.');\n\n      if (field instanceof HTMLInputElement) field.focus();\n\n      return;\n    }\n\n    setError('');\n    setPending(true);\n\n    try {\n      await onStart?.(value);\n    } catch {\n      setPending(false);\n      setError('That did not go through. Try again in a moment.');\n\n      return;\n    }\n\n    setPending(false);\n    setEmail('');\n    setSent(true);\n    onNotice(`Check ${value} for your sign-in link.`);\n  };\n\n  return (\n    <section\n      aria-labelledby=\"fieldnotes-signoff-title\"\n      className=\"fieldnotes-section fieldnotes-signoff\"\n      id=\"start\"\n    >\n      <div className=\"fieldnotes-wrap\">\n        <Folio name=\"Sign-off\" page=\"14\" />\n        <div className=\"fieldnotes-signoff-sheet\">\n          <h2 className=\"fieldnotes-h2\" id=\"fieldnotes-signoff-title\">\n            Thank you for reading this far.\n          </h2>\n          <div className=\"fieldnotes-signoff-body\">\n            <p>\n              If any of this sounded like your last quarter, try Fieldnotes on the interviews you\n              already have. Bring twenty and they’re on us. Tag a few, and see which of them already\n              agree with each other.\n            </p>\n          </div>\n          <div className=\"fieldnotes-signature\" data-reveal=\"signature\">\n            <p className=\"fieldnotes-signature-close\">Yours in the field,</p>\n            <p aria-hidden className=\"fieldnotes-signature-name\">\n              Maren\n              <svg\n                className=\"fieldnotes-signature-swash\"\n                preserveAspectRatio=\"none\"\n                viewBox=\"0 0 300 36\"\n              >\n                <path d=\"M4 22C40 30 86 30 128 22S214 6 262 12c14 2 24 6 34 10\" pathLength={1} />\n              </svg>\n            </p>\n            <p className=\"fieldnotes-signature-who\">Maren Ashdown, founder of Fieldnotes</p>\n          </div>\n          {/* method=\"dialog\" keeps a form submitted before the page hydrates from sending the\n              address anywhere; once it has, submit() handles every submission. */}\n          <form className=\"fieldnotes-start\" method=\"dialog\" noValidate onSubmit={submit}>\n            <svg aria-hidden className=\"fieldnotes-start-art\" viewBox=\"0 0 440 120\">\n              <circle cx=\"386\" cy=\"60\" fill=\"none\" r=\"46\" stroke=\"currentColor\" strokeWidth=\"2.4\" />\n              <circle cx=\"386\" cy=\"60\" fill=\"none\" r=\"36\" stroke=\"currentColor\" strokeWidth=\"1\" />\n              <text fill=\"currentColor\" textAnchor=\"middle\" x=\"386\" y=\"56\">\n                FIELDNOTES\n              </text>\n              <text fill=\"currentColor\" textAnchor=\"middle\" x=\"386\" y=\"72\">\n                30 OCT 2026\n              </text>\n              <path\n                d=\"M0 38c14-8 28 8 42 0s28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0M0 60c14-8 28 8 42 0s28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0M0 82c14-8 28 8 42 0s28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0 28 8 42 0\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n              />\n            </svg>\n            <label className=\"fieldnotes-start-label\" htmlFor=\"fieldnotes-email\">\n              Your work email\n            </label>\n            <div className=\"fieldnotes-start-row\">\n              <GlassInput\n                ref={emailRef}\n                aria-describedby={\n                  error ? 'fieldnotes-email-error fieldnotes-email-help' : 'fieldnotes-email-help'\n                }\n                aria-invalid={error ? true : undefined}\n                autoCapitalize=\"off\"\n                autoComplete=\"email\"\n                className=\"fieldnotes-start-input\"\n                id=\"fieldnotes-email\"\n                inputMode=\"email\"\n                name=\"email\"\n                placeholder=\"dana@company.com\"\n                spellCheck={false}\n                type=\"email\"\n                value={email}\n                onChange={(event) => {\n                  setEmail(event.target.value);\n\n                  if (error) setError('');\n                }}\n              />\n              <GlassButton\n                aria-busy={pending || undefined}\n                className=\"fieldnotes-button-xl\"\n                type=\"submit\"\n                variant=\"prominent\"\n              >\n                {pending ? 'Sending' : 'Start free'}\n              </GlassButton>\n            </div>\n            <p aria-live=\"assertive\" className=\"fieldnotes-start-error\" id=\"fieldnotes-email-error\">\n              {error}\n            </p>\n            <p className=\"fieldnotes-start-help\" id=\"fieldnotes-email-help\">\n              We’ll send a sign-in link. Your first 20 interviews are free, no card needed.\n            </p>\n            {sent && !onStart ? (\n              <p className=\"fieldnotes-start-demo\" role=\"status\">\n                Demo. Nothing was sent.\n              </p>\n            ) : null}\n          </form>\n          <p className=\"fieldnotes-postscript\">\n            <b>P.S.</b> Prefer to talk first?{' '}\n            <a href=\"mailto:maren@fieldnotes.example\">Book a 20 minute walkthrough</a> with me. I\n            still take most of them myself.\n          </p>\n        </div>\n      </div>\n    </section>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/sign-off.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/parts.tsx",
      "content": "'use client';\n\nimport clsx from 'clsx';\nimport { useEffect, useRef, useState } from 'react';\nimport { GlassAvatar } from '../../glass-avatar';\nimport { motionReduced } from '../../lib/liquid-motion';\n\n// Spread onto a component rather than written as a prop: for Base UI projects the shadcn CLI\n// rewrites a literal asChild prop into a render prop, which these Radix-based parts do not take.\nexport const slotted = { asChild: true };\n\n/** A section's running head. */\nexport function Folio({ name, page }: { name: string; page: string }) {\n  return (\n    <div className=\"fieldnotes-folio\">\n      <span>{name}</span>\n      <span className=\"fieldnotes-folio-page\">{page}</span>\n    </div>\n  );\n}\n\nexport type Tone = 'oxblood' | 'ochre' | 'sage' | 'plum' | 'ink';\n\n/** Decorative: the name always sits beside it. */\nexport function Initials({\n  initials,\n  small,\n  tone = 'oxblood',\n}: {\n  initials: string;\n  small?: boolean;\n  tone?: Tone;\n}) {\n  return (\n    <GlassAvatar\n      aria-hidden\n      className=\"fieldnotes-avatar\"\n      data-tone={tone}\n      fallback={initials}\n      size={small ? 'sm' : 'md'}\n    />\n  );\n}\n\nexport function PullQuote({\n  initials,\n  large,\n  name,\n  quote,\n  role,\n  tone,\n}: {\n  initials: string;\n  large?: boolean;\n  name: string;\n  quote: string;\n  role: string;\n  tone: Tone;\n}) {\n  return (\n    <figure\n      className={clsx('fieldnotes-pullquote', large && 'fieldnotes-pullquote-large')}\n      data-reveal=\"rise\"\n    >\n      <span aria-hidden className=\"fieldnotes-pullquote-mark\">\n        “\n      </span>\n      <blockquote>\n        <p>{quote}</p>\n      </blockquote>\n      <figcaption>\n        <Initials initials={initials} tone={tone} />\n        <span>\n          <b>{name}</b>\n          {role}\n        </span>\n      </figcaption>\n    </figure>\n  );\n}\n\nconst format = (value: number, decimals: number) =>\n  value.toLocaleString('en-GB', {\n    minimumFractionDigits: decimals,\n    maximumFractionDigits: decimals,\n  });\n\n/**\n * A number that counts up from zero as it scrolls into view. It starts counting only if it was\n * below the fold, so a number already on screen never drops back, and it stays put under reduced\n * motion. Assistive technology reads the final value.\n */\nexport function Count({ decimals = 0, to }: { decimals?: number; to: number }) {\n  const ref = useRef<HTMLSpanElement>(null);\n  const [shown, setShown] = useState(to);\n\n  useEffect(() => {\n    const element = ref.current;\n\n    if (!element || motionReduced()) return;\n\n    let frame = 0;\n\n    const count = new IntersectionObserver(\n      ([entry]) => {\n        if (!entry.isIntersecting) return;\n        count.disconnect();\n        const start = performance.now();\n\n        const step = (now: number) => {\n          const progress = Math.min(1, (now - start) / 1200);\n\n          setShown(to * (1 - (1 - progress) ** 3));\n\n          if (progress < 1) frame = requestAnimationFrame(step);\n        };\n\n        frame = requestAnimationFrame(step);\n      },\n      { rootMargin: '0px 0px -8% 0px', threshold: 0.15 },\n    );\n\n    const arm = new IntersectionObserver(\n      ([entry]) => {\n        if (!entry.isIntersecting) return;\n        arm.disconnect();\n\n        if (entry.boundingClientRect.top < window.innerHeight) return;\n        setShown(0);\n        count.observe(element);\n      },\n      { rootMargin: '0px 0px 45% 0px' },\n    );\n\n    arm.observe(element);\n\n    return () => {\n      arm.disconnect();\n      count.disconnect();\n      cancelAnimationFrame(frame);\n    };\n  }, [to]);\n\n  return (\n    <>\n      <span ref={ref} aria-hidden>\n        {format(shown, decimals)}\n      </span>\n      <span className=\"fieldnotes-sr-only\">{format(to, decimals)}</span>\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/parts.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/icons.tsx",
      "content": "import clsx from 'clsx';\nimport type { ReactNode } from 'react';\n\nconst PATHS = {\n  down: <path d=\"M10 4v12M5 11l5 5 5-5\" />,\n  right: <path d=\"M4 10h12M11 5l5 5-5 5\" />,\n  up: <path d=\"M10 16V4M5 9l5-5 5 5\" />,\n  menu: <path d=\"M3.5 6.5h13M3.5 10h13M3.5 13.5h8\" />,\n  close: <path d=\"M5.5 5.5l9 9M14.5 5.5l-9 9\" />,\n  check: <path d=\"M4.5 10.5l3.6 3.6 7.4-8.2\" />,\n  chevron: <path d=\"M5.5 8l4.5 4.5L14.5 8\" />,\n  play: <path d=\"M7 4.8v10.4l8.2-5.2z\" fill=\"currentColor\" />,\n  pause: <path d=\"M7.2 5v10M12.8 5v10\" />,\n  upload: <path d=\"M10 13V3.5M6.2 7.2L10 3.5l3.8 3.7M3.5 12.5v3a1 1 0 001 1h11a1 1 0 001-1v-3\" />,\n  speaker: (\n    <>\n      <circle cx=\"10\" cy=\"7\" r=\"3.2\" />\n      <path d=\"M4 16.5c1-3 3.3-4.6 6-4.6s5 1.6 6 4.6\" />\n    </>\n  ),\n  lock: (\n    <>\n      <rect x=\"4.5\" y=\"9\" width=\"11\" height=\"8\" rx=\"2\" />\n      <path d=\"M7 9V6.8a3 3 0 016 0V9\" />\n    </>\n  ),\n  keys: (\n    <>\n      <rect x=\"2.5\" y=\"5\" width=\"15\" height=\"10\" rx=\"2.2\" />\n      <path d=\"M5.8 8.3h.4M9.8 8.3h.4M13.8 8.3h.4M6.5 11.8h7\" />\n    </>\n  ),\n  list: <path d=\"M8 5.5h8.5M8 10h8.5M8 14.5h8.5M3.6 5.5h.8M3.6 10h.8M3.6 14.5h.8\" />,\n  clock: (\n    <>\n      <circle cx=\"10\" cy=\"10\" r=\"7\" />\n      <path d=\"M10 6v4.2l2.8 1.8\" />\n    </>\n  ),\n  search: (\n    <>\n      <circle cx=\"8.8\" cy=\"8.8\" r=\"5.3\" />\n      <path d=\"M13 13l3.8 3.8\" />\n    </>\n  ),\n  pattern: (\n    <>\n      <circle cx=\"5\" cy=\"6\" r=\"2\" />\n      <circle cx=\"15\" cy=\"5\" r=\"2\" />\n      <circle cx=\"10\" cy=\"15\" r=\"2\" />\n      <path d=\"M6.7 7.5l2.4 5.6M13.9 6.7l-2.8 6.5M7 5.8l6-.6\" />\n    </>\n  ),\n  growth: (\n    <>\n      <path d=\"M3.5 15.5L8 11l3 3 5.5-6.5\" />\n      <path d=\"M12.5 7.5h4v4\" />\n    </>\n  ),\n  page: (\n    <>\n      <path d=\"M5.5 3h6l3.5 3.5V16a1 1 0 01-1 1h-8.5a1 1 0 01-1-1V4a1 1 0 011-1z\" />\n      <path d=\"M11.5 3v3.5H15M7.5 10.5h5M7.5 13.5h3.5\" />\n    </>\n  ),\n  wave: <path d=\"M3 10h1.5M6.5 7v6M10 4.5v11M13.5 7v6M16.5 10H17\" />,\n  users: (\n    <>\n      <circle cx=\"7.5\" cy=\"7.5\" r=\"2.8\" />\n      <path d=\"M2.8 16c.8-2.6 2.6-4 4.7-4s3.9 1.4 4.7 4\" />\n      <path d=\"M12.8 5a2.6 2.6 0 010 5.1M14.6 12.3c1.3.5 2.2 1.7 2.7 3.7\" />\n    </>\n  ),\n} satisfies Record<string, ReactNode>;\n\nexport type IconName = keyof typeof PATHS;\n\nexport function Icon({ className, name }: { className?: string; name: IconName }) {\n  return (\n    <svg aria-hidden className={clsx('fieldnotes-icon', className)} viewBox=\"0 0 20 20\">\n      {PATHS[name]}\n    </svg>\n  );\n}\n\n/** A notebook with an oxblood ribbon. */\nexport function FieldnotesMark({ className }: { className?: string }) {\n  return (\n    <svg aria-hidden className={className} viewBox=\"0 0 28 28\">\n      <rect\n        fill=\"none\"\n        height=\"21\"\n        rx=\"3.6\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.8\"\n        width=\"18\"\n        x=\"5\"\n        y=\"3.5\"\n      />\n      <path\n        d=\"M9.5 11h9M9.5 15h9M9.5 19h5.5\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"1.8\"\n      />\n      <path className=\"fieldnotes-mark-ribbon\" d=\"M16.6 3.5h4.4v7.6l-2.2-1.6-2.2 1.6z\" />\n    </svg>\n  );\n}\n\n/** A rubber stamp, roughened by the page's ink filter. */\nexport function Stamp({\n  centre,\n  className,\n  id,\n  ring,\n}: {\n  centre: string;\n  className?: string;\n  /** Names the circle the ring of words runs round; unique on the page. */\n  id: string;\n  ring: string;\n}) {\n  return (\n    <svg aria-hidden className={clsx('fieldnotes-stamp', className)} viewBox=\"0 0 120 120\">\n      <path d=\"M60 60m-41 0a41 41 0 1 1 82 0a41 41 0 1 1-82 0\" fill=\"none\" id={id} />\n      <circle cx=\"60\" cy=\"60\" fill=\"none\" r=\"55\" stroke=\"currentColor\" strokeWidth=\"2.5\" />\n      <circle cx=\"60\" cy=\"60\" fill=\"none\" r=\"30\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\n      <text className=\"fieldnotes-stamp-ring\" fill=\"currentColor\">\n        <textPath href={`#${id}`}>{ring}</textPath>\n      </text>\n      <text\n        className=\"fieldnotes-stamp-centre\"\n        fill=\"currentColor\"\n        textAnchor=\"middle\"\n        x=\"60\"\n        y=\"66\"\n      >\n        {centre}\n      </text>\n    </svg>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/glassyui/templates/fieldnotes/icons.tsx"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/use-reveals.ts",
      "content": "'use client';\n\nimport { useEffect, type RefObject } from 'react';\nimport { motionReduced } from '../../lib/liquid-motion';\n\n/**\n * Moves each [data-reveal] element under the root into place as it scrolls in. An element is\n * displaced only while it is still below the fold, so nothing on screen ever jumps back, and\n * without script or with reduced motion nothing is displaced at all. The stylesheet draws the\n * displaced state for data-reveal-state=\"armed\" and the move for \"in\".\n */\nexport function useReveals(rootRef: RefObject<HTMLElement | null>) {\n  useEffect(() => {\n    const root = rootRef.current;\n\n    if (!root || motionReduced()) return;\n\n    const reveal = new IntersectionObserver(\n      (entries) => {\n        for (const entry of entries) {\n          if (!entry.isIntersecting) continue;\n          reveal.unobserve(entry.target);\n          entry.target.setAttribute('data-reveal-state', 'in');\n        }\n      },\n      { rootMargin: '0px 0px -8% 0px', threshold: 0.15 },\n    );\n\n    const arm = new IntersectionObserver(\n      (entries) => {\n        for (const entry of entries) {\n          if (!entry.isIntersecting) continue;\n          arm.unobserve(entry.target);\n\n          if (entry.boundingClientRect.top < window.innerHeight) continue;\n          entry.target.setAttribute('data-reveal-state', 'armed');\n          reveal.observe(entry.target);\n        }\n      },\n      { rootMargin: '0px 0px 45% 0px' },\n    );\n\n    for (const element of root.querySelectorAll('[data-reveal]')) arm.observe(element);\n\n    return () => {\n      arm.disconnect();\n      reveal.disconnect();\n    };\n  }, [rootRef]);\n}\n",
      "type": "registry:hook",
      "target": "components/glassyui/templates/fieldnotes/use-reveals.ts"
    },
    {
      "path": "registry/glassyui/templates/fieldnotes/fieldnotes.css",
      "content": "/*\n  Fieldnotes, a Glassy UI template. Every rule is scoped to .fieldnotes. Set --fieldnotes-accent,\n  --fieldnotes-accent-rotate and --fieldnotes-accent-ink on any ancestor to change the accent, and\n  --fieldnotes-serif (text, and headlines unless --fieldnotes-display gives them their own face)\n  and --fieldnotes-sans (keycaps and two wordmarks) to change the fonts.\n*/\n\n.fieldnotes {\n  --fn-paper: #f4efe6;\n  --fn-paper-3: #fcf9f3;\n  --fn-card: #fffdf8;\n  --fn-ink: #2a2420;\n  --fn-ink-2: #5e544c;\n  --fn-rule: rgb(42 36 32 / 0.18);\n  --fn-rule-soft: rgb(42 36 32 / 0.09);\n  --fn-ox: var(--fieldnotes-accent, #9f1239);\n  --fn-ox-ink: var(--fieldnotes-accent-ink, #fff);\n  --fn-ox-soft: color-mix(in srgb, var(--fn-ox) 12%, transparent);\n  --fn-pain: #9f1239;\n  --fn-pain-mark: rgb(222 84 122 / 0.26);\n  --fn-work: #845300;\n  --fn-work-mark: rgb(232 176 52 / 0.36);\n  --fn-quote: #286450;\n  --fn-quote-mark: rgb(86 164 128 / 0.28);\n  --fn-display: var(\n    --fieldnotes-display,\n    var(--fieldnotes-serif, ui-serif, 'Iowan Old Style', Georgia, serif)\n  );\n  --fn-serif: var(--fieldnotes-serif, ui-serif, 'Iowan Old Style', Georgia, serif);\n  --fn-sans: var(\n    --fieldnotes-sans,\n    -apple-system,\n    BlinkMacSystemFont,\n    'Segoe UI',\n    system-ui,\n    sans-serif\n  );\n  --fn-gutter: clamp(20px, 4.4vw, 64px);\n  --fn-gap: clamp(16px, 2vw, 28px);\n  --fn-shadow: 56 36 26;\n  --fn-ease-in: cubic-bezier(0.5, 0, 0.75, 0);\n\n  /* The library's tokens, in paper and oxblood. */\n  --lg-foreground: var(--fn-ink);\n  --lg-muted-foreground: var(--fn-ink-2);\n  --lg-accent: var(--fn-ox);\n  --lg-accent-foreground: var(--fn-ox-ink);\n  --lg-prominent-fill: var(--fn-ox);\n  /* Recolours whatever sits behind a prominent button into a deep oxblood, so its white label\n     keeps 4.5:1 or more. */\n  --lg-prominent-tint: grayscale(1) contrast(0.5) brightness(0.35) sepia(1)\n    hue-rotate(var(--fieldnotes-accent-rotate, 300deg)) saturate(7) brightness(1.1);\n  --lg-prominent-glow: 255 206 216;\n  --lg-prominent-shade: 70 8 26;\n  --lg-switch-on: var(--fn-ox);\n  --lg-focus-ring: var(--fn-ox);\n  --lg-invalid: var(--fn-ox);\n  --lg-control-surface: rgb(255 252 247 / 0.64);\n  --lg-control-surface-hover: rgb(255 252 247 / 0.92);\n  --lg-border: rgb(42 36 32 / 0.16);\n  --lg-overlay-surface: rgb(252 248 241 / 0.86);\n  --lg-slab-tint: rgb(252 248 241 / 0.86);\n  --lg-header-tint: rgb(252 249 243 / 0.62);\n\n  color: var(--fn-ink);\n  font-family: var(--fn-serif);\n  font-optical-sizing: auto;\n  letter-spacing: normal;\n}\n\n@layer base {\n  .fieldnotes :where(h1, h2, h3, h4, p, ul, ol, blockquote, figure, dl, dd) {\n    margin: 0;\n    padding: 0;\n  }\n\n  .fieldnotes :where(ul, ol) {\n    list-style: none;\n  }\n\n  .fieldnotes :where(h1, h2, h3, h4) {\n    font-weight: 400;\n    text-wrap: balance;\n  }\n\n  .fieldnotes :where(p) {\n    text-wrap: pretty;\n  }\n\n  .fieldnotes :where(a) {\n    color: inherit;\n  }\n\n  .fieldnotes :where(svg) {\n    display: block;\n  }\n}\n\nhtml:has(.fieldnotes-page) {\n  background-color: #f4efe6;\n  scroll-padding-top: 96px;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  html:has(.fieldnotes-page):not([data-lg-reduce-motion='1']) {\n    scroll-behavior: smooth;\n  }\n}\n\n/* Warm paper with a fine grain and a faint ruled line every 36 px. */\n.fieldnotes-page {\n  position: relative;\n  min-height: 100vh;\n  min-width: 320px;\n  overflow-x: clip;\n  background-color: var(--fn-paper);\n  background-image:\n    url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .17 0 0 0 0 .13 0 0 0 0 .1 0 0 0 .07 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E\"),\n    repeating-linear-gradient(180deg, transparent 0 35px, rgb(42 36 32 / 0.055) 35px 36px);\n  font-size: 18px;\n  line-height: 1.6;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  text-rendering: optimizeLegibility;\n}\n\n.fieldnotes ::selection {\n  background: rgb(159 18 57 / 0.2);\n}\n\n.fieldnotes\n  :where(a, button, summary, [role='button'], [tabindex]:not([tabindex='-1'])):focus-visible {\n  outline: 2px solid var(--fn-ox);\n  outline-offset: 3px;\n  border-radius: 4px;\n}\n\n.fieldnotes [tabindex='-1']:focus {\n  outline: none;\n}\n\n.fieldnotes-defs {\n  position: absolute;\n}\n\n.fieldnotes-sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  padding: 0;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n  border: 0;\n}\n\n.fieldnotes-skip {\n  position: fixed;\n  top: 16px;\n  left: 16px;\n  z-index: 100;\n  padding: 12px 18px;\n  border-radius: 999px;\n  background: var(--fn-ink);\n  color: var(--fn-paper);\n  font: 600 14px/1 var(--fn-serif);\n  text-decoration: none;\n  transform: translateY(-200%);\n}\n\n.fieldnotes-skip:focus {\n  transform: none;\n}\n\n.fieldnotes-wrap {\n  width: min(100% - 2 * var(--fn-gutter), 1280px);\n  margin-inline: auto;\n}\n\n.fieldnotes-grid {\n  display: grid;\n  grid-template-columns: repeat(12, minmax(0, 1fr));\n  column-gap: var(--fn-gap);\n}\n\n.fieldnotes-icon {\n  width: 18px;\n  height: 18px;\n  flex: none;\n  fill: none;\n  stroke: currentColor;\n  stroke-width: 1.9;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n.fieldnotes-icon-small {\n  width: 15px;\n  height: 15px;\n  stroke-width: 2.2;\n}\n\n.fieldnotes-mark-ribbon {\n  fill: var(--fn-ox);\n}\n\n.fieldnotes-eyebrow {\n  color: var(--fn-ink-2);\n  font: 600 12px/1.3 var(--fn-serif);\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-h2 {\n  font-family: var(--fn-display);\n  font-size: clamp(42px, 6.2vw, 92px);\n  line-height: 0.96;\n  letter-spacing: -0.04em;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 0;\n}\n\n.fieldnotes-h2 em {\n  color: var(--fn-ox);\n  font-style: italic;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 1;\n}\n\n.fieldnotes-dek {\n  max-width: 34em;\n  color: var(--fn-ink-2);\n  font: italic 400 clamp(19px, 1.7vw, 24px) / 1.42 var(--fn-display);\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-section {\n  padding-top: clamp(88px, 11vw, 168px);\n}\n\n.fieldnotes-section-head {\n  margin-top: clamp(36px, 5vw, 64px);\n}\n\n.fieldnotes-section-head .fieldnotes-dek {\n  margin-top: 20px;\n}\n\n/* A section's running head */\n.fieldnotes-folio {\n  position: relative;\n  display: flex;\n  justify-content: space-between;\n  align-items: baseline;\n  padding-top: 12px;\n  border-top: 1px solid var(--fn-ink);\n  color: var(--fn-ink-2);\n  font: 600 12px/1 var(--fn-serif);\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-folio::before {\n  content: '';\n  position: absolute;\n  inset: 3px 0 auto;\n  border-top: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-folio-page {\n  color: var(--fn-ox);\n  font: italic 500 16px/1 var(--fn-display);\n  letter-spacing: 0;\n  text-transform: none;\n}\n\n/* A link's arrow leans the way it goes. */\n.fieldnotes-lean {\n  transition: transform 420ms var(--lg-spring);\n}\n\n.fieldnotes a:is(:hover, :focus-visible) .fieldnotes-lean-down {\n  transform: translateY(3px);\n}\n\n.fieldnotes a:is(:hover, :focus-visible) .fieldnotes-lean-right {\n  transform: translateX(4px);\n}\n\n.fieldnotes a:is(:hover, :focus-visible) .fieldnotes-lean-up {\n  transform: translateY(-3px);\n}\n\n.fieldnotes .lg-kbd {\n  min-width: 22px;\n  height: 22px;\n  padding: 0 6px;\n  border-radius: 6px;\n  background: rgb(255 253 248 / 0.7);\n  box-shadow:\n    inset 0 0 0 1px var(--fn-rule),\n    0 1px 0 var(--fn-rule);\n  color: var(--fn-ink-2);\n  font: 600 12px/1 var(--fn-sans);\n  letter-spacing: 0;\n}\n\n.fieldnotes-avatar {\n  --lg-avatar-size: 44px;\n  --fn-avatar: #c0395c, #7a0d2b 62%, #4f0a1e;\n  font-size: 14px;\n}\n\n.fieldnotes-avatar[data-size='sm'] {\n  --lg-avatar-size: 30px;\n  font-size: 11px;\n}\n\n.fieldnotes-avatar .lg-avatar-face {\n  box-shadow:\n    inset 0 1px 0 rgb(255 255 255 / 0.25),\n    0 1px 2px rgb(var(--fn-shadow) / 0.3);\n  color: #fff;\n  font-family: var(--fn-serif);\n  font-weight: 600;\n  letter-spacing: 0.04em;\n}\n\n.fieldnotes-avatar .lg-avatar-fallback {\n  background: radial-gradient(120% 120% at 30% 20%, var(--fn-avatar));\n}\n\n.fieldnotes-avatar[data-tone='ink'] {\n  --fn-avatar: #6f645b, #3a322c 60%, #241e1a;\n}\n\n.fieldnotes-avatar[data-tone='ochre'] {\n  --fn-avatar: #c08a2a, #8a5a0c 60%, #5c3b06;\n}\n\n.fieldnotes-avatar[data-tone='sage'] {\n  --fn-avatar: #4f8f76, #2a6450 60%, #173d30;\n}\n\n.fieldnotes-avatar[data-tone='plum'] {\n  --fn-avatar: #8a5a8f, #5a3360 60%, #3a1f3f;\n}\n\n/* Chips: the library's badge on paper, with a dot in the tag's colour. */\n.fieldnotes-badge {\n  --lg-badge-fill: 255 253 248;\n  --lg-badge-ink: var(--fn-ink);\n  --lg-badge-tint: 42 36 32;\n  --lg-badge-highlight: rgb(255 255 255 / 0.85);\n  gap: 6px;\n  height: 28px;\n  padding: 0 12px;\n  font: 600 13px/1 var(--fn-serif);\n}\n\n.fieldnotes-badge .lg-badge-dot {\n  width: 7px;\n  height: 7px;\n  background: var(--fn-tag, var(--fn-ox));\n}\n\n.fieldnotes-badge b {\n  font-variant-numeric: tabular-nums;\n}\n\n.fieldnotes-badge-jade {\n  --fn-tag: #14a874;\n}\n\n.fieldnotes [data-tag='pain'],\n.fieldnotes [data-colour='pain'] {\n  --fn-tag: var(--fn-pain);\n  --fn-tag-mark: var(--fn-pain-mark);\n}\n\n.fieldnotes [data-tag='work'],\n.fieldnotes [data-colour='work'] {\n  --fn-tag: var(--fn-work);\n  --fn-tag-mark: var(--fn-work-mark);\n}\n\n.fieldnotes [data-tag='quote'],\n.fieldnotes [data-colour='quote'] {\n  --fn-tag: var(--fn-quote);\n  --fn-tag-mark: var(--fn-quote-mark);\n}\n\n.fieldnotes [data-colour='request'] {\n  --fn-tag: #5a3d86;\n}\n\n.fieldnotes [data-colour='delight'] {\n  --fn-tag: #2f5f8f;\n}\n\n/* Panes take the paper into their frost; floats hold a denser sheet of it. */\n.fieldnotes-pane::before {\n  background-color: rgb(252 249 243 / 0.6);\n}\n\n.fieldnotes-float::before {\n  background-color: rgb(252 248 241 / 0.86);\n}\n\n.fieldnotes-float.lg-glass-surface {\n  --lg-panel-elevation-shadow:\n    0 2px 6px rgb(var(--fn-shadow) / 0.08), 0 24px 56px -16px rgb(var(--fn-shadow) / 0.34);\n}\n\n/* Buttons: the library's slabs at the page's sizes, in its serif. */\n.fieldnotes\n  :is(.fieldnotes-button-sm, .fieldnotes-button-md, .fieldnotes-button-lg, .fieldnotes-button-xl) {\n  font-family: var(--fn-serif);\n  font-weight: 600;\n  line-height: 1;\n  letter-spacing: -0.005em;\n  white-space: nowrap;\n}\n\n.fieldnotes .fieldnotes-button-sm {\n  min-height: 32px;\n  padding: 0 15px;\n  font-size: 13px;\n}\n\n.fieldnotes .fieldnotes-button-md {\n  min-height: 44px;\n  padding: 0 20px;\n  font-size: 15px;\n}\n\n.fieldnotes .fieldnotes-button-lg {\n  min-height: 48px;\n  padding: 0 22px;\n  font-size: 16px;\n}\n\n.fieldnotes .fieldnotes-button-xl {\n  min-height: 56px;\n  padding: 0 26px;\n  font-size: 17px;\n}\n\n/* A prominent button's glass recolours the page behind it. Floats and the sheet frost their own\n   backdrop, and in Safari and Firefox the glass may see little or none of the paper, so its face\n   would show the light paper behind a white label. There it takes the solid fill instead, as the\n   library does where glass cannot be drawn. */\n.fieldnotes-sheet .lg-button--prominent > .lg-button__layers > .lg-button__distortion {\n  background-color: var(--lg-prominent-fill);\n  -webkit-backdrop-filter: none;\n  backdrop-filter: none;\n}\n\n@supports not (-webkit-app-region: drag) {\n  .fieldnotes .lg-button--prominent > .lg-button__layers > .lg-button__distortion {\n    background-color: var(--lg-prominent-fill);\n    -webkit-backdrop-filter: none;\n    backdrop-filter: none;\n  }\n\n  /* Without the lens, floats and the sheet are paper, so the page under them never shows sharp. */\n  .fieldnotes-float::before {\n    background-color: var(--fn-paper-3);\n  }\n\n  .fieldnotes-sheet.lg-sheet-content {\n    background: var(--fn-paper-3);\n  }\n}\n\n/* Masthead */\n.fieldnotes-mast {\n  inset: 12px 0 auto;\n  width: min(1312px, 100% - 2 * var(--fn-gutter));\n  height: 60px;\n  border-radius: 20px;\n  color: var(--fn-ink);\n  font-family: var(--fn-serif);\n}\n\n.fieldnotes-mast > .lg-header-glass {\n  --lg-panel-radius: 20px;\n}\n\n.fieldnotes-mast .lg-header-bar {\n  gap: clamp(12px, 1.8vw, 24px);\n  padding: 0 10px 0 16px;\n}\n\n.fieldnotes-mast .lg-header-brand {\n  font: inherit;\n  letter-spacing: normal;\n}\n\n.fieldnotes-mast-brand {\n  display: flex;\n  align-items: center;\n  gap: clamp(12px, 1.8vw, 24px);\n}\n\n.fieldnotes-brand {\n  display: inline-flex;\n  align-items: center;\n  gap: 9px;\n  border-radius: 8px;\n  color: var(--fn-ink);\n  font: 600 22px/1 var(--fn-display);\n  letter-spacing: -0.025em;\n  text-decoration: none;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 0;\n}\n\n.fieldnotes-brand-mark {\n  width: 26px;\n  height: 26px;\n}\n\n.fieldnotes-mast-issue {\n  display: grid;\n  gap: 3px;\n  padding-left: clamp(12px, 1.8vw, 20px);\n  border-left: 1px solid var(--fn-rule);\n  color: var(--fn-ink-2);\n  font: 600 10.5px/1.1 var(--fn-serif);\n  letter-spacing: 0.13em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-mast-issue b {\n  color: var(--fn-ox);\n  font: italic 500 14px/1 var(--fn-display);\n  letter-spacing: 0;\n  text-transform: none;\n}\n\n.fieldnotes-mast .lg-header-actions {\n  gap: 6px;\n}\n\n.fieldnotes-mast-nav {\n  display: flex;\n  gap: 2px;\n  margin-right: clamp(0px, 1.4vw, 18px);\n}\n\n.fieldnotes-mast-nav a,\n.fieldnotes-mast-signin {\n  position: relative;\n  padding: 10px 12px;\n  border-radius: 999px;\n  font: 500 15px/1 var(--fn-serif);\n  text-decoration: none;\n  white-space: nowrap;\n  transition:\n    background-color 200ms var(--lg-ease-out),\n    color 200ms var(--lg-ease-out);\n}\n\n.fieldnotes-mast-nav a:hover,\n.fieldnotes-mast-signin:hover {\n  background: rgb(42 36 32 / 0.06);\n}\n\n.fieldnotes-mast-nav a::after {\n  content: '';\n  position: absolute;\n  left: 50%;\n  bottom: 3px;\n  width: 4px;\n  height: 4px;\n  margin-left: -2px;\n  border-radius: 50%;\n  background: var(--fn-ox);\n  transform: scale(0);\n  transition: transform 420ms var(--lg-spring);\n}\n\n.fieldnotes-mast-nav a[aria-current='true'] {\n  color: var(--fn-ox);\n}\n\n.fieldnotes-mast-nav a[aria-current='true']::after {\n  transform: none;\n}\n\n.fieldnotes .fieldnotes-mast-menu {\n  display: none;\n  width: 40px;\n  padding: 0;\n}\n\n.fieldnotes-mast-progress {\n  position: absolute;\n  left: 20px;\n  right: 20px;\n  bottom: 0;\n  height: 2px;\n  border-radius: 2px;\n  background: var(--fn-ox);\n  transform: scaleX(0);\n  transform-origin: left;\n  pointer-events: none;\n}\n\n@supports (animation-timeline: scroll()) {\n  .fieldnotes-mast-progress {\n    animation: fieldnotes-progress linear both;\n    animation-timeline: scroll(root);\n  }\n}\n\n@keyframes fieldnotes-progress {\n  to {\n    transform: scaleX(1);\n  }\n}\n\n/* The menu on narrow screens */\n.fieldnotes-sheet {\n  color: var(--fn-ink);\n}\n\n.fieldnotes-sheet.lg-sheet-content[data-side='top'] {\n  --lg-panel-radius: 24px;\n  top: 82px;\n  left: auto;\n  right: var(--fn-gutter);\n  width: min(360px, 100vw - 2 * var(--fn-gutter));\n  max-height: calc(100dvh - 96px);\n  margin: 0;\n  padding: 14px;\n}\n\n.fieldnotes-sheet-issue {\n  padding: 12px 52px 18px 8px;\n}\n\n.fieldnotes-sheet-nav {\n  display: grid;\n}\n\n.fieldnotes-sheet-nav a {\n  display: flex;\n  align-items: baseline;\n  gap: 14px;\n  padding: 12px 8px;\n  border-top: 1px solid var(--fn-rule-soft);\n  font: 400 24px/1.1 var(--fn-display);\n  letter-spacing: -0.02em;\n  text-decoration: none;\n}\n\n.fieldnotes-sheet-nav a span {\n  width: 26px;\n  color: var(--fn-ox);\n  font: italic 500 15px/1 var(--fn-display);\n}\n\n.fieldnotes .fieldnotes-sheet-start {\n  width: 100%;\n  margin-top: 14px;\n}\n\n.fieldnotes-sheet-signin {\n  display: block;\n  padding: 14px 8px 4px;\n  font: 500 15px/1 var(--fn-serif);\n  text-align: center;\n  text-decoration: underline;\n}\n\n/* Cover */\n.fieldnotes-cover {\n  padding-top: calc(72px + clamp(14px, 2.6vh, 28px));\n}\n\n.fieldnotes-dateline {\n  position: relative;\n  display: grid;\n  grid-template-columns: 1fr auto 1fr;\n  align-items: center;\n  gap: 16px;\n  padding: 11px 0 10px;\n  color: var(--fn-ink-2);\n  font: 600 11px/1 var(--fn-serif);\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-dateline::before,\n.fieldnotes-dateline::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  border-top: 1px solid var(--fn-ink);\n}\n\n.fieldnotes-dateline::before {\n  top: 0;\n}\n\n.fieldnotes-dateline::after {\n  bottom: 0;\n  border-top-color: var(--fn-rule);\n}\n\n.fieldnotes-dateline-mid {\n  color: var(--fn-ink);\n  font: italic 400 15px/1 var(--fn-display);\n  letter-spacing: -0.005em;\n  text-transform: none;\n}\n\n.fieldnotes-dateline > :last-child {\n  text-align: right;\n}\n\n.fieldnotes-cover-grid {\n  display: grid;\n  grid-template-columns: repeat(12, minmax(0, 1fr));\n  column-gap: var(--fn-gap);\n  margin-top: clamp(22px, 3.6vh, 40px);\n}\n\n.fieldnotes-kicker {\n  grid-column: 1 / -1;\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  color: var(--fn-ox);\n}\n\n.fieldnotes-kicker::before {\n  content: '';\n  width: 32px;\n  border-top: 1px solid currentColor;\n}\n\n.fieldnotes-cover-title {\n  position: relative;\n  z-index: 2;\n  grid-column: 1 / -1;\n  grid-row: 2;\n  margin-top: 14px;\n  font-family: var(--fn-display);\n  font-size: clamp(46px, 10.2vw, 150px);\n  line-height: 0.88;\n  letter-spacing: -0.045em;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 0;\n}\n\n.fieldnotes-line {\n  display: block;\n}\n\n.fieldnotes-cover-title em {\n  position: relative;\n  isolation: isolate;\n  color: var(--fn-ox);\n  font-style: italic;\n  font-weight: 360;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 1;\n}\n\n/* The headline's key word arrives already tagged, in the transcript's pain-point marker. */\n.fieldnotes-cover-title em::after {\n  content: '';\n  position: absolute;\n  z-index: -1;\n  left: 0.01em;\n  right: -0.05em;\n  bottom: 0.2em;\n  height: 0.25em;\n  border-radius: 0.04em 0.1em 0.06em 0.12em;\n  background: linear-gradient(\n    90deg,\n    rgb(222 84 122 / 0.3),\n    rgb(222 84 122 / 0.22) 70%,\n    rgb(222 84 122 / 0.3)\n  );\n  transform: rotate(-1.2deg);\n  transform-origin: 0 50%;\n}\n\n.fieldnotes-teaser {\n  position: relative;\n  z-index: 3;\n  grid-column: 10 / -1;\n  grid-row: 2;\n  align-self: start;\n  margin-top: 26px;\n  padding-top: 14px;\n  border-top: 2px solid var(--fn-ox);\n}\n\n.fieldnotes-teaser blockquote {\n  margin-top: 12px;\n  font: italic 400 21px/1.3 var(--fn-display);\n  letter-spacing: -0.01em;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 1;\n}\n\n.fieldnotes-teaser a {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  margin-top: 14px;\n  color: var(--fn-ox);\n  font: 600 14px/1.3 var(--fn-serif);\n  text-decoration: none;\n}\n\n.fieldnotes-teaser a:hover span {\n  text-decoration: underline;\n  text-underline-offset: 3px;\n}\n\n.fieldnotes-lede {\n  position: relative;\n  z-index: 2;\n  grid-row: 3;\n  grid-column: 1 / span 6;\n  margin-top: clamp(22px, 3.6vh, 40px);\n}\n\n.fieldnotes-standfirst {\n  max-width: 33em;\n  font-size: clamp(18px, 1.5vw, 21.5px);\n  line-height: 1.5;\n}\n\n.fieldnotes-byline {\n  display: flex;\n  align-items: center;\n  gap: 14px;\n  margin-top: 22px;\n}\n\n.fieldnotes-byline p {\n  display: grid;\n  gap: 3px;\n}\n\n.fieldnotes-byline-name {\n  font: 600 15.5px/1.25 var(--fn-serif);\n}\n\n.fieldnotes-byline-role {\n  color: var(--fn-ink-2);\n  font: 400 14px/1.3 var(--fn-serif);\n}\n\n.fieldnotes-cover-actions {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 12px;\n  margin-top: 26px;\n}\n\n.fieldnotes-cover-note {\n  margin-top: 14px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n.fieldnotes-cover-art {\n  position: relative;\n  z-index: 1;\n  grid-column: 7 / -1;\n  grid-row: 3;\n  height: clamp(330px, 29vw, 410px);\n  margin-top: clamp(-12px, -0.6vw, 0px);\n}\n\n.fieldnotes-collage {\n  position: absolute;\n  inset: 0;\n}\n\n/* Desk things */\n.fieldnotes-piece {\n  position: absolute;\n  transform: rotate(var(--r, 0deg));\n}\n\n.fieldnotes-scrap {\n  background: var(--fn-paper-3);\n  box-shadow:\n    0 1px 1px rgb(var(--fn-shadow) / 0.1),\n    0 12px 26px -14px rgb(var(--fn-shadow) / 0.5);\n}\n\n.fieldnotes-folder {\n  --r: 3.5deg;\n  --d: 260ms;\n  --fx: 20px;\n  --fy: -20px;\n  --fr: 5deg;\n  left: 22%;\n  top: 8%;\n  width: 74%;\n  height: 66%;\n  border-radius: 4px 12px 6px 6px;\n  background: linear-gradient(162deg, #e2cfaa, #cfb487 68%, #c5a979);\n}\n\n.fieldnotes-folder::before {\n  content: '';\n  position: absolute;\n  left: 46%;\n  top: -15px;\n  width: 30%;\n  height: 17px;\n  border-radius: 9px 9px 0 0;\n  background: #e0cca6;\n}\n\n.fieldnotes-folder-label {\n  position: absolute;\n  left: 46%;\n  top: 12px;\n  color: #5a452e;\n  font: 600 10px/1 var(--fn-serif);\n  letter-spacing: 0.15em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-index {\n  --r: -6deg;\n  --d: 420ms;\n  --fx: -30px;\n  --fy: -10px;\n  --fr: -9deg;\n  left: 1%;\n  top: 15%;\n  width: 50%;\n  height: 45%;\n  padding: 33px 16px 10px 18px;\n  overflow: hidden;\n  background:\n    linear-gradient(transparent 25px, rgb(190 44 70 / 0.5) 25px 26.5px, transparent 26.5px),\n    repeating-linear-gradient(transparent 0 21px, rgb(82 120 160 / 0.22) 21px 22px) 0 5px,\n    var(--fn-card);\n}\n\n.fieldnotes-hand {\n  display: block;\n  color: #33405a;\n  font: italic 400 clamp(15px, 1.35vw, 19px) / 22px var(--fn-display);\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 1;\n}\n\n.fieldnotes-hand-small {\n  color: #5a6378;\n  font-size: clamp(13px, 1.05vw, 15px);\n}\n\n.fieldnotes-strip {\n  --r: 2.5deg;\n  --d: 560ms;\n  --fx: -20px;\n  --fy: 10px;\n  --fr: 7deg;\n  right: 0;\n  top: 41%;\n  width: 55%;\n  filter: drop-shadow(0 8px 10px rgb(var(--fn-shadow) / 0.22));\n}\n\n.fieldnotes-strip-paper {\n  padding: 12px 14px 24px;\n  background: #fffefb;\n  clip-path: polygon(\n    0 0,\n    100% 0,\n    100% 84%,\n    95% 91%,\n    90% 85%,\n    84% 94%,\n    78% 87%,\n    71% 95%,\n    65% 88%,\n    58% 96%,\n    51% 89%,\n    45% 95%,\n    38% 88%,\n    31% 94%,\n    25% 87%,\n    18% 95%,\n    12% 89%,\n    6% 94%,\n    0 88%\n  );\n}\n\n.fieldnotes-typed {\n  display: block;\n  color: #4b4039;\n  font: 400 11px/1.6 var(--fn-serif);\n  letter-spacing: 0.02em;\n}\n\n.fieldnotes-typed b {\n  font-size: 10px;\n  font-weight: 700;\n  letter-spacing: 0.12em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-typed mark {\n  padding: 0 1px;\n  border-radius: 2px;\n  background: var(--fn-pain-mark);\n  color: inherit;\n}\n\n.fieldnotes-tape {\n  width: 84px;\n  height: 24px;\n  background: linear-gradient(rgb(246 238 216 / 0.86), rgb(232 220 190 / 0.86));\n  clip-path: polygon(\n    3% 0,\n    97% 5%,\n    100% 30%,\n    96% 55%,\n    100% 82%,\n    95% 100%,\n    4% 95%,\n    0 70%,\n    4% 45%,\n    0 18%\n  );\n}\n\n.fieldnotes-cover-tape-a {\n  --r: -34deg;\n  --d: 640ms;\n  left: 3%;\n  top: 14%;\n}\n\n.fieldnotes-cover-tape-b {\n  --r: 22deg;\n  --d: 700ms;\n  right: -1%;\n  top: 40%;\n}\n\n.fieldnotes-stamp {\n  color: color-mix(in srgb, var(--fn-ox) 80%, transparent);\n  filter: url(#fieldnotes-ink);\n}\n\n.fieldnotes-stamp-ring {\n  font: 700 11px var(--fn-serif);\n  letter-spacing: 2.4px;\n}\n\n.fieldnotes-stamp-centre {\n  font: italic 400 19px var(--fn-display);\n}\n\n.fieldnotes-cover-stamp {\n  --r: -14deg;\n  --d: 820ms;\n  --fy: -10px;\n  --fr: -30deg;\n  right: 5%;\n  bottom: 3%;\n  width: clamp(84px, 7.6vw, 108px);\n}\n\n.fieldnotes-cover-float {\n  position: absolute;\n  left: 3%;\n  bottom: 0;\n  width: min(66%, 350px);\n}\n\n.fieldnotes-cover-card.lg-glass-surface {\n  --lg-panel-radius: 24px;\n  padding: 18px 20px 20px;\n  transform: rotate(-1.5deg);\n}\n\n.fieldnotes-cover-card-top {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 10px;\n}\n\n.fieldnotes-cover-card-top .fieldnotes-badge,\n.fieldnotes-cover-card-meta {\n  white-space: nowrap;\n}\n\n.fieldnotes-cover-card-meta {\n  color: var(--fn-ink-2);\n  font: 600 11px/1 var(--fn-serif);\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-cover-card-title {\n  margin-top: 14px;\n  font: 500 22px/1.15 var(--fn-display);\n  letter-spacing: -0.015em;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-cover-card-sub {\n  margin-top: 6px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n.fieldnotes-dots {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 5px;\n  margin-top: 14px;\n}\n\n.fieldnotes-dots i {\n  position: relative;\n  flex: none;\n  width: 11px;\n  height: 11px;\n  border-radius: 50%;\n  box-shadow: inset 0 0 0 1.5px rgb(42 36 32 / 0.35);\n}\n\n.fieldnotes-dots i[data-on] {\n  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--fn-ox) 45%, transparent);\n}\n\n.fieldnotes-dots i[data-on]::after {\n  content: '';\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  background: var(--fn-ox);\n}\n\n.fieldnotes-dots i:nth-child(2) {\n  --k: 1;\n}\n\n.fieldnotes-dots i:nth-child(3) {\n  --k: 2;\n}\n\n.fieldnotes-dots i:nth-child(4) {\n  --k: 3;\n}\n\n.fieldnotes-dots i:nth-child(5) {\n  --k: 4;\n}\n\n.fieldnotes-dots i:nth-child(6) {\n  --k: 5;\n}\n\n.fieldnotes-dots i:nth-child(7) {\n  --k: 6;\n}\n\n.fieldnotes-dots i:nth-child(8) {\n  --k: 7;\n}\n\n.fieldnotes-dots i:nth-child(9) {\n  --k: 8;\n}\n\n.fieldnotes-contents {\n  display: grid;\n  grid-template-columns: auto 1fr;\n  align-items: start;\n  gap: 28px;\n  margin-top: clamp(40px, 6vh, 64px);\n  padding-top: 14px;\n  border-top: 1px solid var(--fn-ink);\n}\n\n.fieldnotes-contents ol {\n  display: grid;\n  grid-template-columns: repeat(6, minmax(0, 1fr));\n  gap: 8px 20px;\n}\n\n.fieldnotes-contents a {\n  display: grid;\n  grid-template-columns: auto 1fr auto;\n  align-items: baseline;\n  gap: 8px;\n  padding: 4px 0;\n  text-decoration: none;\n}\n\n.fieldnotes-contents-numeral {\n  min-width: 1.4em;\n  color: var(--fn-ox);\n  font: italic 500 15px/1 var(--fn-display);\n}\n\n.fieldnotes-contents-title {\n  display: flex;\n  align-items: baseline;\n  min-width: 0;\n  font: 500 15px/1.3 var(--fn-serif);\n  text-decoration: underline 1px transparent;\n  text-underline-offset: 4px;\n  transition: text-decoration-color 200ms var(--lg-ease-out);\n}\n\n.fieldnotes-contents-title::after {\n  content: '';\n  flex: 1 1 0;\n  min-width: 0;\n  height: 2px;\n  margin-left: 10px;\n  background: radial-gradient(circle at 1px 1px, rgb(42 36 32 / 0.42) 0.85px, transparent 1.15px) 0\n    0 / 6px 2px repeat-x;\n  transform: translateY(-0.24em);\n}\n\n.fieldnotes-contents a:hover .fieldnotes-contents-title {\n  text-decoration-color: currentColor;\n}\n\n.fieldnotes-contents-page {\n  color: var(--fn-ink-2);\n  font: 500 13px/1 var(--fn-serif);\n  font-variant-numeric: tabular-nums;\n}\n\n/* Proof */\n.fieldnotes-proof {\n  padding-top: clamp(56px, 7vw, 96px);\n}\n\n.fieldnotes-proof-inner {\n  display: grid;\n  grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);\n  align-items: center;\n  gap: var(--fn-gap);\n  padding-block: 26px;\n  border-block: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-proof-lead {\n  max-width: 22em;\n  color: var(--fn-ink-2);\n  font-size: 16px;\n  line-height: 1.45;\n}\n\n.fieldnotes-proof-lead strong {\n  color: var(--fn-ink);\n  font: 500 19px/1.2 var(--fn-display);\n  letter-spacing: -0.01em;\n}\n\n.fieldnotes-proof-marks {\n  display: grid;\n  grid-template-columns: repeat(6, minmax(0, 1fr));\n  align-items: center;\n  gap: 20px;\n  color: var(--fn-ink-2);\n}\n\n.fieldnotes-proof-marks svg {\n  width: 100%;\n  height: 30px;\n}\n\n.fieldnotes-mark-larkmere {\n  font: italic 500 20px var(--fn-display);\n}\n\n.fieldnotes-mark-tidelock {\n  font: 700 14px var(--fn-sans);\n  letter-spacing: 3px;\n}\n\n.fieldnotes-mark-orrery {\n  font: 600 20px var(--fn-serif);\n  letter-spacing: -0.3px;\n}\n\n.fieldnotes-mark-fernhaven {\n  font: 650 19px var(--fn-display);\n  letter-spacing: -0.5px;\n}\n\n.fieldnotes-mark-quillon {\n  font: 800 18px var(--fn-sans);\n  letter-spacing: -0.6px;\n}\n\n.fieldnotes-mark-hollow-pine {\n  font: 600 13.5px var(--fn-serif);\n  letter-spacing: 2.2px;\n}\n\n/* The letter */\n.fieldnotes-letter-head {\n  margin-top: clamp(40px, 6vw, 72px);\n}\n\n.fieldnotes-letter-title {\n  grid-column: 1 / span 10;\n  font-size: clamp(50px, 7.4vw, 112px);\n}\n\n.fieldnotes-letter-dek {\n  grid-column: 3 / span 7;\n  margin-top: 26px;\n}\n\n.fieldnotes-letter-by {\n  grid-column: 3 / span 7;\n  display: flex;\n  align-items: center;\n  gap: 14px;\n  margin-top: 26px;\n  padding-top: 18px;\n  border-top: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-letter-by p {\n  display: grid;\n  gap: 3px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n.fieldnotes-letter-by b {\n  color: var(--fn-ink);\n  font: 600 15px/1.2 var(--fn-serif);\n}\n\n.fieldnotes-letter-body {\n  position: relative;\n  margin-top: clamp(44px, 6vw, 72px);\n}\n\n/* The margin's double red rule, as on a pad of writing paper. */\n.fieldnotes-letter-body::before {\n  content: '';\n  position: absolute;\n  top: -12px;\n  bottom: 0;\n  left: calc((100% + var(--fn-gap)) * 8 / 12 + 22px);\n  width: 4px;\n  border-inline: 1px solid color-mix(in srgb, var(--fn-ox) 34%, transparent);\n  border-right-color: color-mix(in srgb, var(--fn-ox) 16%, transparent);\n}\n\n.fieldnotes-letter-body > * {\n  grid-column: 3 / span 6;\n}\n\n.fieldnotes-noted {\n  display: grid;\n  grid-column: 1 / -1;\n  grid-template-columns: subgrid;\n  align-items: start;\n}\n\n.fieldnotes-noted > p,\n.fieldnotes-letter-body > p {\n  grid-column: 3 / span 6;\n  margin-bottom: 1.15em;\n}\n\n.fieldnotes-letter-body p {\n  font-size: 18px;\n  line-height: 1.66;\n}\n\n.fieldnotes-dropcap {\n  float: left;\n  margin: 0.08em 0.1em -0.1em -0.04em;\n  color: var(--fn-ox);\n  font: 500 6.5em/0.74 var(--fn-display);\n  transform-origin: 50% 0;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 0;\n}\n\n.fieldnotes-letter-body h3 {\n  position: relative;\n  margin: 1.3em 0 0.7em;\n  font: 500 clamp(26px, 2.4vw, 34px) / 1.15 var(--fn-display);\n  letter-spacing: -0.02em;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-section-mark {\n  position: absolute;\n  right: calc(100% + 22px);\n  top: 0.06em;\n  color: var(--fn-ox);\n  font-style: italic;\n}\n\n.fieldnotes-letter-more {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  margin-top: 6px;\n  color: var(--fn-ox);\n  font: 600 16px/1.2 var(--fn-serif);\n  text-decoration: none;\n}\n\n.fieldnotes-letter-more:hover span {\n  text-decoration: underline;\n  text-underline-offset: 4px;\n}\n\n/* Margin notes */\n.fieldnotes-sidenote.lg-glass-surface {\n  --lg-panel-radius: 16px;\n  grid-column: 9 / -1;\n  max-width: 330px;\n  margin-top: 4px;\n  padding: 14px 18px 15px 42px;\n  color: var(--fn-ink);\n  font: 400 14.5px/1.52 var(--fn-serif);\n  transition: transform 700ms var(--lg-spring);\n}\n\n.fieldnotes-sidenote[data-lit]::before {\n  background-color: color-mix(in srgb, var(--fn-ox) 9%, rgb(252 249 243 / 0.7));\n}\n\n.fieldnotes-sidenote[data-lit].lg-glass-surface {\n  transform: translateX(-6px);\n}\n\n.fieldnotes-sidenote-number {\n  position: absolute;\n  left: 15px;\n  top: 13px;\n  color: var(--fn-ox);\n  font: italic 500 18px/1 var(--fn-display);\n}\n\n.fieldnotes-note-ref {\n  margin-left: 0.08em;\n  padding: 0 0.12em;\n  border-radius: 4px;\n  color: var(--fn-ox);\n  font: 700 0.66em/0 var(--fn-serif);\n  text-decoration: none;\n  vertical-align: super;\n}\n\n.fieldnotes-note-ref:hover {\n  background: var(--fn-ox-soft);\n}\n\n.fieldnotes-note-anchor {\n  background-image: linear-gradient(\n    color-mix(in srgb, var(--fn-ox) 14%, transparent),\n    color-mix(in srgb, var(--fn-ox) 14%, transparent)\n  );\n  background-repeat: no-repeat;\n  background-size: 0% 100%;\n  transition: background-size 460ms var(--lg-ease-out);\n}\n\n.fieldnotes-note-anchor[data-lit] {\n  background-size: 100% 100%;\n}\n\n/* Pull quotes */\n.fieldnotes-pullquote {\n  position: relative;\n}\n\n.fieldnotes-pullquote-mark {\n  display: block;\n  height: 0.5em;\n  color: var(--fn-ox);\n  font: 400 clamp(84px, 9vw, 140px) / 1 var(--fn-display);\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-pullquote blockquote p {\n  font: italic 340 clamp(24px, 2.4vw, 32px) / 1.2 var(--fn-display);\n  letter-spacing: -0.02em;\n  text-wrap: balance;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 1;\n}\n\n.fieldnotes-pullquote figcaption {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-top: 22px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n  line-height: 1.4;\n}\n\n.fieldnotes-pullquote figcaption b {\n  display: block;\n  color: var(--fn-ink);\n  font: 600 15px/1.25 var(--fn-serif);\n}\n\n.fieldnotes-pullquote-large blockquote p {\n  font-size: clamp(32px, 4.4vw, 64px);\n  line-height: 1.08;\n  letter-spacing: -0.03em;\n}\n\n.fieldnotes-letter-body > .fieldnotes-pullquote-inline {\n  grid-column: 2 / span 7;\n  margin: 0.4em 0 1.5em;\n  padding: 22px 0 24px;\n  border-top: 2px solid var(--fn-ox);\n  border-bottom: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-pullquote-inline blockquote p {\n  font-size: clamp(27px, 2.9vw, 42px);\n  line-height: 1.12;\n}\n\n/* Try it */\n.fieldnotes-try-head {\n  align-items: end;\n  row-gap: 20px;\n  margin-top: clamp(36px, 5vw, 64px);\n}\n\n.fieldnotes-try-head .fieldnotes-h2 {\n  grid-column: 1 / span 6;\n}\n\n.fieldnotes-try-intro {\n  grid-column: 8 / -1;\n  display: grid;\n  gap: 16px;\n  font-size: 17px;\n}\n\n.fieldnotes-try-keys {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 6px 8px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n.fieldnotes-try-numbers {\n  white-space: nowrap;\n}\n\n.fieldnotes-try-numbers .lg-kbd + .lg-kbd {\n  margin-left: 6px;\n}\n\n@media (hover: none) and (pointer: coarse) {\n  .fieldnotes-try-keys {\n    display: none;\n  }\n}\n\n.fieldnotes-try-grid {\n  display: grid;\n  grid-template-columns: repeat(12, minmax(0, 1fr));\n  align-items: start;\n  column-gap: var(--fn-gap);\n  margin-top: clamp(32px, 4vw, 56px);\n}\n\n.fieldnotes-try-main {\n  grid-column: 1 / span 7;\n}\n\n.fieldnotes-transcript {\n  position: relative;\n  padding: 26px clamp(18px, 3vw, 40px) 30px clamp(36px, 4.4vw, 64px);\n  border-radius: 5px;\n  background: #fffcf7;\n  box-shadow:\n    0 1px 0 rgb(var(--fn-shadow) / 0.06),\n    0 2px 3px rgb(var(--fn-shadow) / 0.06),\n    0 30px 50px -30px rgb(var(--fn-shadow) / 0.45),\n    inset 0 0 0 1px rgb(42 36 32 / 0.05);\n}\n\n.fieldnotes-transcript::before {\n  content: '';\n  position: absolute;\n  left: clamp(22px, 3vw, 42px);\n  top: 0;\n  bottom: 0;\n  width: 1px;\n  background: color-mix(in srgb, var(--fn-ox) 28%, transparent);\n}\n\n.fieldnotes-transcript-head {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  gap: 6px 16px;\n  margin-bottom: 8px;\n  padding-bottom: 14px;\n  border-bottom: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-transcript-head p:first-child {\n  font: 500 17px/1.3 var(--fn-display);\n  letter-spacing: -0.01em;\n}\n\n.fieldnotes-transcript-head p:last-child {\n  align-self: end;\n  color: var(--fn-ink-2);\n  font-size: 13.5px;\n}\n\n/* The transcript */\n.fieldnotes-highlighter {\n  position: relative;\n}\n\n.fieldnotes-turn {\n  display: grid;\n  grid-template-columns: 50px minmax(0, 1fr);\n  grid-template-areas:\n    'time who'\n    'time text';\n  column-gap: 14px;\n  padding-block: 9px;\n}\n\n.fieldnotes-turn > p {\n  grid-area: text;\n  font-size: 17px;\n  line-height: 1.7;\n}\n\n.fieldnotes-turn-time {\n  grid-area: time;\n  padding-top: 1px;\n  color: var(--fn-ink-2);\n  font: 500 12.5px/1.6 var(--fn-serif);\n  font-variant-numeric: tabular-nums;\n}\n\n.fieldnotes-turn-who {\n  grid-area: who;\n  display: block;\n  margin-bottom: 3px;\n  color: var(--fn-ink-2);\n  font: 700 11px/1.6 var(--fn-serif);\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-turn[data-question] > p {\n  color: var(--fn-ink-2);\n  font-style: italic;\n}\n\n/* A tagged sentence: its marker wipes across every line it wraps onto. */\n.fieldnotes-sentence {\n  padding-block: 0.08em;\n  border-radius: 3px;\n  background-image: linear-gradient(var(--fn-hl, transparent), var(--fn-hl, transparent));\n  background-repeat: no-repeat;\n  background-position: 0 0;\n  background-size: 0% 100%;\n  cursor: pointer;\n  transition:\n    background-size 640ms var(--lg-ease-out),\n    background-color 200ms var(--lg-ease-out);\n}\n\n.fieldnotes-sentence:hover:not([data-tag]) {\n  text-decoration: underline dotted color-mix(in srgb, var(--fn-ox) 60%, transparent) 1.5px;\n  text-underline-offset: 5px;\n}\n\n.fieldnotes-sentence[data-hue='pain'] {\n  --fn-hl: var(--fn-pain-mark);\n}\n\n.fieldnotes-sentence[data-hue='work'] {\n  --fn-hl: var(--fn-work-mark);\n}\n\n.fieldnotes-sentence[data-hue='quote'] {\n  --fn-hl: var(--fn-quote-mark);\n}\n\n.fieldnotes-sentence[data-tag] {\n  background-size: 100% 100%;\n}\n\n.fieldnotes-sentence[aria-expanded='true'] {\n  background-color: rgb(42 36 32 / 0.07);\n}\n\n.fieldnotes .fieldnotes-sentence:focus-visible {\n  outline-offset: 2px;\n  border-radius: 3px;\n}\n\n.fieldnotes-selection-menu.lg-glass-surface {\n  --lg-panel-radius: 16px;\n  position: absolute;\n  z-index: 6;\n  left: 0;\n  top: 0;\n  width: 284px;\n  padding: 6px;\n  visibility: hidden;\n  transform: translateY(var(--drift, 6px)) scale(0.18);\n  transform-origin: var(--origin-x, 50%) var(--origin-y, 100%);\n  transition:\n    transform 150ms var(--fn-ease-in),\n    visibility 0s linear 150ms;\n}\n\n.fieldnotes-selection-menu[data-side='bottom'] {\n  --drift: -6px;\n}\n\n.fieldnotes-selection-menu[data-state='open'].lg-glass-surface {\n  visibility: visible;\n  transform: none;\n  transition:\n    transform 460ms var(--lg-spring),\n    visibility 0s;\n}\n\n.fieldnotes-selection-label {\n  padding: 8px 10px 6px;\n  color: var(--fn-ink-2);\n  font: 700 10.5px/1 var(--fn-serif);\n  letter-spacing: 0.15em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-selection-item {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  width: 100%;\n  height: 40px;\n  padding: 0 10px;\n  border: 0;\n  border-radius: 10px;\n  background: none;\n  color: var(--fn-ink);\n  font: 500 15px/1 var(--fn-serif);\n  text-align: left;\n  white-space: nowrap;\n  cursor: pointer;\n}\n\n.fieldnotes-selection-item:hover {\n  background: rgb(42 36 32 / 0.07);\n}\n\n.fieldnotes .fieldnotes-selection-item:focus-visible {\n  outline: none;\n  background: rgb(42 36 32 / 0.07);\n  box-shadow: inset 0 0 0 2px var(--fn-ox);\n}\n\n.fieldnotes-selection-item[hidden],\n.fieldnotes-selection-rule[hidden] {\n  display: none;\n}\n\n.fieldnotes-selection-name {\n  flex: 1;\n}\n\n.fieldnotes-tag-dot {\n  flex: none;\n  width: 10px;\n  height: 10px;\n  border-radius: 50%;\n  background: var(--fn-tag);\n  box-shadow: 0 0 0 3px color-mix(in oklab, var(--fn-tag) 18%, transparent);\n}\n\n.fieldnotes-selection-hint {\n  display: none;\n  padding: 3px 7px;\n  border-radius: 999px;\n  background: var(--fn-ox-soft);\n  color: var(--fn-ox);\n  font: 700 9.5px/1 var(--fn-serif);\n  letter-spacing: 0.12em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-selection-item[data-suggested] .fieldnotes-selection-hint {\n  display: inline-block;\n}\n\n.fieldnotes-selection-item kbd {\n  display: inline-grid;\n  place-items: center;\n  min-width: 22px;\n  height: 22px;\n  padding: 0 6px;\n  border-radius: 6px;\n  background: rgb(255 253 248 / 0.7);\n  box-shadow:\n    inset 0 0 0 1px var(--fn-rule),\n    0 1px 0 var(--fn-rule);\n  color: var(--fn-ink-2);\n  font: 600 12px/1 var(--fn-sans);\n}\n\n.fieldnotes-selection-check {\n  display: none;\n  width: 16px;\n  height: 16px;\n  color: var(--fn-ox);\n}\n\n.fieldnotes-selection-item[aria-checked='true'] .fieldnotes-selection-check {\n  display: block;\n}\n\n.fieldnotes-selection-item[aria-checked='true'] kbd {\n  display: none;\n}\n\n.fieldnotes-selection-quiet {\n  color: var(--fn-ink-2);\n}\n\n.fieldnotes-selection-quiet .fieldnotes-icon {\n  width: 12px;\n  height: 12px;\n}\n\n.fieldnotes-selection-rule {\n  height: 1px;\n  margin: 4px 8px;\n  background: var(--fn-rule);\n}\n\n.fieldnotes-tally {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 6px;\n}\n\n/* The tally docks at the foot of the screen on narrow screens, where the board sits below. */\n.fieldnotes-dock {\n  position: sticky;\n  bottom: 14px;\n  z-index: 7;\n  display: none;\n  justify-content: center;\n  margin-top: 18px;\n}\n\n.fieldnotes-dock-tally.lg-glass-surface {\n  --lg-panel-radius: 999px;\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  padding: 6px;\n}\n\n.fieldnotes-board-wrap {\n  position: sticky;\n  top: 92px;\n  grid-column: 8 / -1;\n}\n\n.fieldnotes-board-wrap .fieldnotes-piece {\n  z-index: 0;\n}\n\n.fieldnotes-board-note {\n  --r: 7deg;\n  right: -10px;\n  top: -18px;\n  width: 120px;\n  height: 100px;\n  padding: 14px 12px;\n  background: linear-gradient(170deg, #f5dc8f, #ecc964);\n  box-shadow: 0 10px 20px -12px rgb(var(--fn-shadow) / 0.5);\n}\n\n.fieldnotes-board-note .fieldnotes-hand {\n  color: #5a4520;\n}\n\n.fieldnotes-board-card {\n  --r: -6deg;\n  left: -14px;\n  bottom: 34px;\n  width: 180px;\n  height: 120px;\n  background:\n    repeating-linear-gradient(transparent 0 19px, rgb(82 120 160 / 0.2) 19px 20px) 0 30px,\n    var(--fn-card);\n}\n\n.fieldnotes-board-card::before {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 24px;\n  border-top: 1.5px solid rgb(190 44 70 / 0.45);\n}\n\n.fieldnotes-board.lg-glass-surface {\n  --lg-panel-radius: 28px;\n  z-index: 1;\n  padding: 22px 22px 20px;\n}\n\n.fieldnotes-board-head {\n  display: flex;\n  justify-content: space-between;\n  align-items: start;\n  gap: 12px;\n}\n\n.fieldnotes-board-title {\n  font: 500 24px/1.1 var(--fn-display);\n  letter-spacing: -0.02em;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-board-sub {\n  margin-top: 4px;\n  color: var(--fn-ink-2);\n  font-size: 13.5px;\n}\n\n.fieldnotes-board .fieldnotes-tally {\n  margin-top: 16px;\n}\n\n.fieldnotes-moments {\n  max-height: 236px;\n  margin-top: 16px;\n  border-radius: 12px;\n}\n\n.fieldnotes-moments-list {\n  position: relative;\n  display: grid;\n  gap: 8px;\n  padding: 2px;\n}\n\n.fieldnotes-moment {\n  position: relative;\n  padding: 10px 40px 11px 14px;\n  border-radius: 12px;\n  background: rgb(255 253 249 / 0.84);\n  box-shadow:\n    inset 3px 0 0 var(--fn-tag),\n    0 1px 2px rgb(var(--fn-shadow) / 0.1),\n    0 6px 14px -10px rgb(var(--fn-shadow) / 0.4);\n}\n\n.fieldnotes-moment[data-incoming] {\n  visibility: hidden;\n}\n\n.fieldnotes-moment[data-leaving] {\n  pointer-events: none;\n}\n\n.fieldnotes-moment-quote {\n  display: -webkit-box;\n  overflow: hidden;\n  font-size: 14.5px;\n  line-height: 1.45;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 2;\n}\n\n.fieldnotes-moment-meta {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  margin-top: 5px;\n  color: var(--fn-ink-2);\n  font: 600 11.5px/1.2 var(--fn-serif);\n  letter-spacing: 0.04em;\n}\n\n.fieldnotes-moment-meta i {\n  width: 7px;\n  height: 7px;\n  border-radius: 50%;\n  background: var(--fn-tag);\n}\n\n.fieldnotes-moment-remove {\n  position: absolute;\n  right: 6px;\n  top: 6px;\n  display: grid;\n  place-items: center;\n  width: 30px;\n  height: 30px;\n  border: 0;\n  border-radius: 50%;\n  background: none;\n  color: var(--fn-ink-2);\n  cursor: pointer;\n}\n\n.fieldnotes-moment-remove:hover {\n  background: rgb(42 36 32 / 0.07);\n  color: var(--fn-ink);\n}\n\n.fieldnotes-moment-remove .fieldnotes-icon {\n  width: 14px;\n  height: 14px;\n  stroke-width: 2.3;\n}\n\n.fieldnotes-board-empty {\n  margin-top: 16px;\n  padding: 16px;\n  border-radius: 14px;\n  background: rgb(255 253 249 / 0.4);\n  box-shadow: inset 0 0 0 1px var(--fn-rule);\n  color: var(--fn-ink-2);\n  font-size: 14.5px;\n  line-height: 1.5;\n}\n\n.fieldnotes-patterns {\n  margin-top: 18px;\n  padding-top: 14px;\n  border-top: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-patterns-head {\n  display: flex;\n  justify-content: space-between;\n  align-items: baseline;\n  gap: 10px;\n}\n\n.fieldnotes-patterns-head h4 {\n  color: var(--fn-ink-2);\n  font: 700 11px/1 var(--fn-serif);\n  letter-spacing: 0.15em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-patterns-head span {\n  color: var(--fn-ink-2);\n  font-size: 12.5px;\n}\n\n.fieldnotes-pattern-list {\n  display: grid;\n  gap: 10px;\n  margin-top: 10px;\n}\n\n.fieldnotes-patterns-empty {\n  margin-top: 8px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n  line-height: 1.5;\n}\n\n.fieldnotes-pattern {\n  padding: 8px 12px 6px;\n  border-radius: 12px;\n  background: rgb(255 253 249 / 0.6);\n  box-shadow: inset 0 0 0 1px var(--fn-rule-soft);\n}\n\n.fieldnotes-pattern-top {\n  display: flex;\n  justify-content: space-between;\n  align-items: baseline;\n  gap: 10px;\n}\n\n.fieldnotes-pattern-title {\n  font: 500 16px/1.25 var(--fn-display);\n  letter-spacing: -0.01em;\n}\n\n.fieldnotes-pattern-count {\n  flex: none;\n  color: var(--fn-ink-2);\n  font-size: 13px;\n}\n\n.fieldnotes-pattern-count b {\n  color: var(--fn-ox);\n  font: 600 15px/1 var(--fn-display);\n}\n\n.fieldnotes-pattern-foot {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 10px;\n  margin-top: 2px;\n}\n\n.fieldnotes-pattern-dots {\n  display: flex;\n  gap: 4px;\n}\n\n.fieldnotes-pattern-dots i {\n  width: 10px;\n  height: 10px;\n  border-radius: 50%;\n  box-shadow: inset 0 0 0 1.5px rgb(42 36 32 / 0.3);\n}\n\n.fieldnotes-pattern-dots i[data-on] {\n  background: rgb(42 36 32 / 0.55);\n  box-shadow: none;\n}\n\n.fieldnotes-pattern-dots i[data-new] {\n  background: var(--fn-ox);\n  box-shadow: none;\n}\n\n.fieldnotes-pattern-confirm {\n  padding: 6px 10px;\n  border: 0;\n  border-radius: 999px;\n  background: none;\n  color: var(--fn-ox);\n  font: 600 13px/1 var(--fn-serif);\n  cursor: pointer;\n}\n\n.fieldnotes-pattern-confirm:hover {\n  background: var(--fn-ox-soft);\n}\n\n.fieldnotes-pattern-confirm[aria-pressed='true'] {\n  color: var(--fn-quote);\n}\n\n/* A tagged sentence's copy on its way to the board. */\n.fieldnotes-ghost {\n  position: fixed;\n  z-index: 70;\n  margin: 0;\n  padding: 7px 12px;\n  border-radius: 10px;\n  background: var(--fn-card);\n  box-shadow:\n    inset 3px 0 0 var(--fn-tag),\n    0 0 0 1px rgb(42 36 32 / 0.06),\n    0 18px 34px -14px rgb(var(--fn-shadow) / 0.55);\n  color: var(--fn-ink);\n  font: 400 15px/1.5 var(--fn-serif);\n  transform-origin: 0 0;\n  pointer-events: none;\n}\n\n.fieldnotes-ghost::after {\n  content: '';\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  background: var(--fn-tag-mark);\n}\n\n/* Chapters */\n.fieldnotes-chapter {\n  display: grid;\n  grid-template-columns: repeat(12, minmax(0, 1fr));\n  align-items: center;\n  column-gap: var(--fn-gap);\n  padding-block: clamp(56px, 7vw, 104px);\n  border-top: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-section-head + .fieldnotes-chapter {\n  margin-top: clamp(40px, 5vw, 64px);\n}\n\n.fieldnotes-chapter-text {\n  grid-column: 1 / span 5;\n}\n\n.fieldnotes-chapter-figure {\n  grid-column: 7 / -1;\n}\n\n.fieldnotes-chapter[data-flip] .fieldnotes-chapter-text {\n  grid-column: 8 / -1;\n  grid-row: 1;\n}\n\n.fieldnotes-chapter[data-flip] .fieldnotes-chapter-figure {\n  grid-column: 1 / span 6;\n  grid-row: 1;\n}\n\n.fieldnotes-chapter-label {\n  display: flex;\n  align-items: baseline;\n  gap: 16px;\n}\n\n.fieldnotes-chapter-numeral {\n  color: var(--fn-ox);\n  font: italic 300 clamp(64px, 7vw, 108px) / 0.8 var(--fn-display);\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 1;\n}\n\n.fieldnotes-chapter-title {\n  margin-top: 22px;\n  font: 400 clamp(32px, 3.4vw, 48px) / 1.04 var(--fn-display);\n  letter-spacing: -0.03em;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-chapter-body {\n  margin-top: 18px;\n  font-size: 17.5px;\n}\n\n.fieldnotes-chapter-list {\n  display: grid;\n  gap: 12px;\n  margin-top: 24px;\n  padding-top: 20px;\n  border-top: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-chapter-list li {\n  display: flex;\n  gap: 12px;\n  font-size: 16px;\n  line-height: 1.4;\n}\n\n.fieldnotes-chapter-list .fieldnotes-icon {\n  margin-top: 2px;\n  color: var(--fn-ox);\n}\n\n.fieldnotes-chapter-figure figcaption {\n  display: flex;\n  gap: 12px;\n  margin-top: 14px;\n  color: var(--fn-ink-2);\n  font: italic 400 15px/1.45 var(--fn-serif);\n}\n\n.fieldnotes-figure-number {\n  flex: none;\n  color: var(--fn-ox);\n  font: 600 12px/1.9 var(--fn-serif);\n  font-style: normal;\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-plate {\n  position: relative;\n  display: grid;\n  place-items: center;\n  min-height: clamp(380px, 36vw, 480px);\n  padding: clamp(60px, 7vw, 92px) clamp(20px, 5vw, 60px);\n  overflow: clip;\n  border-radius: 22px;\n  background: var(--fn-plate, #ebe2d3);\n  box-shadow:\n    inset 0 0 0 1px rgb(42 36 32 / 0.07),\n    inset 0 30px 60px -40px rgb(var(--fn-shadow) / 0.18);\n}\n\n.fieldnotes-plate[data-kind='capture'] {\n  --fn-plate: #e9dfcd;\n}\n\n.fieldnotes-plate[data-kind='tag'] {\n  --fn-plate: #ede4d4;\n}\n\n.fieldnotes-plate[data-kind='pattern'] {\n  --fn-plate: #e6dfd1;\n}\n\n.fieldnotes-plate[data-kind='share'] {\n  --fn-plate: #ebe1d0;\n}\n\n.fieldnotes-plate > .fieldnotes-piece:nth-child(2) {\n  --d: 70ms;\n}\n\n.fieldnotes-plate > .fieldnotes-piece:nth-child(3) {\n  --d: 140ms;\n}\n\n.fieldnotes-plate > .fieldnotes-piece:nth-child(4) {\n  --d: 210ms;\n}\n\n.fieldnotes-plate > .fieldnotes-piece:nth-child(5) {\n  --d: 280ms;\n}\n\n.fieldnotes-plate > .fieldnotes-piece:nth-child(6) {\n  --d: 350ms;\n}\n\n.fieldnotes-figure-pane.lg-glass-surface {\n  --lg-panel-radius: 24px;\n  position: relative;\n  z-index: 2;\n  width: min(100%, 410px);\n  padding: 18px;\n}\n\n.fieldnotes-figure-bar {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 14px;\n}\n\n.fieldnotes-figure-bar .fieldnotes-badge {\n  flex: none;\n}\n\n.fieldnotes-figure-title {\n  font: 600 15px/1.2 var(--fn-serif);\n}\n\n.fieldnotes-figure-meta {\n  color: var(--fn-ink-2);\n  font: 600 11px/1.2 var(--fn-serif);\n  letter-spacing: 0.13em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-figure-heading {\n  font: 500 21px/1.2 var(--fn-display);\n  letter-spacing: -0.015em;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-figure-sub {\n  margin-top: 4px;\n  color: var(--fn-ink-2);\n  font-size: 13.5px;\n  line-height: 1.45;\n}\n\n.fieldnotes-photo {\n  --r: -7deg;\n  --fx: -30px;\n  --fr: -8deg;\n  left: 6%;\n  top: 9%;\n  width: 40%;\n  aspect-ratio: 4 / 3;\n  border: 7px solid var(--fn-card);\n  border-bottom-width: 24px;\n  background:\n    radial-gradient(50% 45% at 72% 28%, #f7dcae, transparent 70%),\n    linear-gradient(180deg, transparent 58%, #5b4136 58% 64%, transparent 64%),\n    linear-gradient(\n      90deg,\n      transparent 18%,\n      rgb(255 236 200 / 0.28) 18% 34%,\n      transparent 34% 54%,\n      rgb(255 236 200 / 0.22) 54% 70%,\n      transparent 70%\n    ),\n    linear-gradient(172deg, #d49a6c, #9d6448 55%, #5a3e35);\n}\n\n.fieldnotes-sleeve {\n  --r: 5deg;\n  --fx: -20px;\n  --fy: 60px;\n  --fr: 6deg;\n  right: 5%;\n  bottom: 7%;\n  width: 50%;\n  height: 40%;\n  padding: 16px;\n  border-radius: 4px;\n  background: linear-gradient(162deg, #e4d1ad, #d1b78c);\n}\n\n.fieldnotes-sleeve::before {\n  content: '';\n  position: absolute;\n  inset: 0;\n  background:\n    linear-gradient(\n        to bottom right,\n        transparent calc(50% - 1px),\n        rgb(90 70 46 / 0.18) 50%,\n        transparent calc(50% + 1px)\n      )\n      left / 50% 100% no-repeat,\n    linear-gradient(\n        to bottom left,\n        transparent calc(50% - 1px),\n        rgb(90 70 46 / 0.18) 50%,\n        transparent calc(50% + 1px)\n      )\n      right / 50% 100% no-repeat;\n}\n\n.fieldnotes-sleeve .fieldnotes-typed {\n  position: relative;\n  color: #5a452e;\n}\n\n.fieldnotes-capture-tape-a {\n  --r: -28deg;\n  left: 4%;\n  top: 6%;\n}\n\n.fieldnotes-capture-tape-b {\n  --r: 16deg;\n  --fx: -10px;\n  right: 8%;\n  bottom: 43%;\n}\n\n.fieldnotes-sessions li {\n  display: grid;\n  grid-template-columns: auto minmax(0, 1fr) auto;\n  align-items: center;\n  gap: 12px;\n  padding-block: 10px;\n  border-top: 1px solid var(--fn-rule-soft);\n}\n\n.fieldnotes-session-who {\n  display: grid;\n  gap: 4px;\n  color: var(--fn-ink-2);\n  font-size: 13px;\n  line-height: 1.3;\n}\n\n.fieldnotes-session-who b {\n  color: var(--fn-ink);\n  font: 600 14.5px/1.25 var(--fn-serif);\n}\n\n.fieldnotes-session-state {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  color: var(--fn-ink-2);\n  font: 600 12.5px/1 var(--fn-serif);\n  font-variant-numeric: tabular-nums;\n}\n\n.fieldnotes-session-state[data-done] {\n  color: var(--fn-quote);\n}\n\n.fieldnotes-session-state .fieldnotes-icon {\n  width: 14px;\n  height: 14px;\n  stroke-width: 2.4;\n}\n\n.fieldnotes-meter {\n  margin-top: 3px;\n}\n\n.fieldnotes-tag-card {\n  width: 38%;\n  height: 46%;\n  padding: 34px 14px 10px;\n  border-radius: 3px;\n  background:\n    linear-gradient(transparent 26px, rgb(190 44 70 / 0.45) 26px 27.5px, transparent 27.5px),\n    repeating-linear-gradient(transparent 0 21px, rgb(82 120 160 / 0.2) 21px 22px) 0 5px,\n    var(--fn-card);\n}\n\n.fieldnotes-tag-card[data-tag='pain'] {\n  --r: -11deg;\n  --fx: -40px;\n  --fy: 20px;\n  --fr: -10deg;\n  left: 5%;\n  top: 9%;\n}\n\n.fieldnotes-tag-card[data-tag='work'] {\n  --r: -2deg;\n  --fy: -30px;\n  --fr: 5deg;\n  left: 31%;\n  top: 5%;\n}\n\n.fieldnotes-tag-card[data-tag='quote'] {\n  --r: 9deg;\n  --fx: -24px;\n  --fy: 30px;\n  --fr: 10deg;\n  right: 5%;\n  top: 11%;\n}\n\n.fieldnotes-tab-sticker {\n  position: absolute;\n  right: 12%;\n  top: -9px;\n  width: 30px;\n  height: 20px;\n  border-radius: 3px 3px 0 0;\n  background: var(--fn-tag);\n}\n\n.fieldnotes-swash {\n  --r: -4deg;\n  --fx: -40px;\n  --fy: 0px;\n  --fr: 0deg;\n  left: 8%;\n  bottom: 10%;\n  width: 46%;\n}\n\n.fieldnotes-swash path {\n  fill: none;\n  stroke: rgb(232 176 52 / 0.55);\n  stroke-width: 18;\n  stroke-linecap: round;\n}\n\n.fieldnotes-taxonomy {\n  display: grid;\n  gap: 4px;\n}\n\n.fieldnotes-taxonomy li {\n  position: relative;\n  isolation: isolate;\n  display: grid;\n  grid-template-columns: 10px minmax(0, 1fr) auto 36px;\n  align-items: center;\n  gap: 10px;\n  height: 38px;\n  padding: 0 10px;\n  border-radius: 10px;\n  font: 500 15px/1 var(--fn-serif);\n}\n\n/* Each tag's bar runs as far as its share of the most used tag. */\n.fieldnotes-taxonomy li::before {\n  content: '';\n  position: absolute;\n  inset: 0;\n  z-index: -1;\n  border-radius: inherit;\n  background: color-mix(in oklab, var(--fn-tag) 13%, transparent);\n  transform: scaleX(var(--w));\n  transform-origin: left;\n}\n\n.fieldnotes-taxonomy .fieldnotes-tag-dot {\n  box-shadow: none;\n}\n\n.fieldnotes-taxonomy-count {\n  color: var(--fn-ink-2);\n  font: 600 14px/1 var(--fn-serif);\n  font-variant-numeric: tabular-nums;\n  text-align: right;\n}\n\n.fieldnotes-graph {\n  --r: 1deg;\n  --fy: 30px;\n  --fr: -2deg;\n  left: 4%;\n  top: 5%;\n  width: 92%;\n  height: 90%;\n  background:\n    linear-gradient(rgb(82 120 160 / 0.16) 1px, transparent 1px) 0 0 / 18px 18px,\n    linear-gradient(90deg, rgb(82 120 160 / 0.16) 1px, transparent 1px) 0 0 / 18px 18px,\n    #fbf8f1;\n}\n\n.fieldnotes-sticky {\n  --fy: -30px;\n  width: 24%;\n  padding: 18px 10px 10px;\n  background: linear-gradient(170deg, #f6df95, #eccb66);\n  box-shadow: 0 10px 18px -12px rgb(var(--fn-shadow) / 0.55);\n}\n\n.fieldnotes-sticky[data-colour='rose'] {\n  background: linear-gradient(170deg, #f5cdd3, #eab3bd);\n}\n\n.fieldnotes-sticky[data-colour='sage'] {\n  background: linear-gradient(170deg, #d4e6d5, #b9d4bd);\n}\n\n/* A drawing pin. */\n.fieldnotes-sticky::before {\n  content: '';\n  position: absolute;\n  left: 50%;\n  top: -5px;\n  width: 14px;\n  height: 14px;\n  margin-left: -7px;\n  border-radius: 50%;\n  background: radial-gradient(circle at 35% 35%, #e46a86, #9f1239 58%, #5e0a22);\n  box-shadow: 0 3px 4px -1px rgb(var(--fn-shadow) / 0.5);\n}\n\n.fieldnotes-sticky .fieldnotes-hand {\n  color: #4a3b2a;\n  font-size: clamp(12px, 1vw, 14px);\n  line-height: 1.3;\n}\n\n.fieldnotes-sticky[data-place='a'] {\n  --r: -6deg;\n  --fr: -10deg;\n  left: 6%;\n  top: 9%;\n}\n\n.fieldnotes-sticky[data-place='b'] {\n  --r: 5deg;\n  --fr: 8deg;\n  right: 6%;\n  top: 6%;\n}\n\n.fieldnotes-sticky[data-place='c'] {\n  --r: 4deg;\n  --fr: 9deg;\n  left: 6%;\n  bottom: 9%;\n}\n\n.fieldnotes-sticky[data-place='d'] {\n  --r: -4deg;\n  --fr: -8deg;\n  right: 5%;\n  bottom: 8%;\n}\n\n.fieldnotes-string {\n  position: absolute;\n  inset: 0;\n  z-index: 1;\n  width: 100%;\n  height: 100%;\n  pointer-events: none;\n}\n\n.fieldnotes-string polyline {\n  fill: none;\n  stroke: #b3203f;\n  stroke-width: 1.4;\n  vector-effect: non-scaling-stroke;\n}\n\n.fieldnotes-timeline {\n  width: 100%;\n  height: auto;\n  margin-top: 14px;\n  overflow: visible;\n}\n\n.fieldnotes-timeline-grid line {\n  stroke: rgb(42 36 32 / 0.12);\n  stroke-width: 1;\n}\n\n.fieldnotes-timeline text {\n  fill: var(--fn-ink-2);\n  font: 500 11px var(--fn-serif);\n}\n\n.fieldnotes-timeline .fieldnotes-timeline-row {\n  font-size: 9.5px;\n  font-weight: 700;\n  letter-spacing: 0.08em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-timeline-thread {\n  fill: none;\n  stroke: var(--fn-ox);\n  stroke-width: 1.6;\n  stroke-dasharray: 1;\n  stroke-linecap: round;\n  opacity: 0.55;\n}\n\n.fieldnotes-timeline circle {\n  fill: var(--fn-ox);\n  stroke: #fffaf3;\n  stroke-width: 2;\n}\n\n.fieldnotes-people {\n  display: flex;\n  align-items: center;\n  margin-top: 12px;\n}\n\n.fieldnotes-people .fieldnotes-avatar {\n  margin-right: -4px;\n}\n\n.fieldnotes-people .fieldnotes-avatar .lg-avatar-face {\n  box-shadow: 0 0 0 2px #f8f2e8;\n}\n\n.fieldnotes-people > span:last-child {\n  margin-left: 14px;\n  color: var(--fn-ink-2);\n  font-size: 13px;\n}\n\n.fieldnotes-readout {\n  --r: -5deg;\n  --fx: -30px;\n  --fr: -6deg;\n  left: 5%;\n  top: 6%;\n  width: 58%;\n  height: 62%;\n  padding: 22px 20px;\n  background: var(--fn-card);\n}\n\n.fieldnotes-readout .fieldnotes-typed {\n  font-size: 10.5px;\n}\n\n.fieldnotes-readout-lines {\n  display: grid;\n  gap: 7px;\n  margin-top: 12px;\n}\n\n.fieldnotes-readout-lines i {\n  height: 4px;\n  border-radius: 2px;\n  background: rgb(42 36 32 / 0.13);\n}\n\n.fieldnotes-postage {\n  --r: 8deg;\n  --fx: -16px;\n  --fr: 10deg;\n  right: 3.5%;\n  top: 6%;\n  width: 92px;\n  height: 110px;\n  padding: 7px;\n  background: var(--fn-card);\n  /* Perforated edges. */\n  -webkit-mask:\n    linear-gradient(#000 0 0) content-box,\n    radial-gradient(circle 3.4px, transparent 96%, #000) -5px -5px / 10px 10px;\n  mask:\n    linear-gradient(#000 0 0) content-box,\n    radial-gradient(circle 3.4px, transparent 96%, #000) -5px -5px / 10px 10px;\n}\n\n.fieldnotes-postage-art {\n  display: grid;\n  align-content: end;\n  height: 100%;\n  padding: 6px;\n  background:\n    radial-gradient(30% 26% at 66% 32%, #f1c27a, transparent 72%),\n    linear-gradient(170deg, transparent 54%, #7a3a2c 54% 60%, transparent 60%),\n    linear-gradient(180deg, #c5523f, #9f1239 60%, #6e0d27);\n  color: #fff3e6;\n  font: 600 8px/1.1 var(--fn-serif);\n  letter-spacing: 0.12em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-postmark {\n  --r: -6deg;\n  --fy: 0px;\n  --fx: -24px;\n  --fr: 0deg;\n  right: 2%;\n  top: 18%;\n  width: 200px;\n  color: rgb(42 36 32 / 0.42);\n}\n\n.fieldnotes-postmark-place {\n  font: 700 9px var(--fn-serif);\n  letter-spacing: 1.2px;\n}\n\n.fieldnotes-postmark-date {\n  font: 700 9px var(--fn-serif);\n}\n\n.fieldnotes-envelope {\n  --r: 3deg;\n  --fy: 60px;\n  left: 30%;\n  bottom: 6%;\n  width: 64%;\n  height: 38%;\n  border-radius: 4px;\n  background: linear-gradient(162deg, #eadbbf, #d9c39b);\n}\n\n.fieldnotes-envelope::before {\n  content: '';\n  position: absolute;\n  inset: 0 0 auto;\n  height: 60%;\n  background: linear-gradient(162deg, #e3d1b0, #d3ba8f);\n  clip-path: polygon(0 0, 100% 0, 50% 100%);\n}\n\n.fieldnotes-clip {\n  display: grid;\n  grid-template-columns: auto minmax(0, 1fr) auto;\n  align-items: center;\n  gap: 12px;\n  margin-top: 14px;\n  padding: 8px 12px 8px 8px;\n  border-radius: 999px;\n  background: rgb(255 253 249 / 0.62);\n  box-shadow: inset 0 0 0 1px var(--fn-rule-soft);\n}\n\n.fieldnotes .fieldnotes-clip-play {\n  width: 32px;\n  padding: 0;\n}\n\n.fieldnotes-clip-play .fieldnotes-icon {\n  width: 15px;\n  height: 15px;\n  stroke-width: 2.2;\n}\n\n.fieldnotes-clip-wave {\n  display: flex;\n  align-items: center;\n  gap: 2px;\n  height: 26px;\n}\n\n.fieldnotes-clip-wave i {\n  flex: 1;\n  height: calc(var(--h) * 100%);\n  min-height: 3px;\n  border-radius: 2px;\n  background: rgb(42 36 32 / 0.24);\n  transition: background-color 120ms linear;\n}\n\n.fieldnotes-clip-wave i[data-on] {\n  background: var(--fn-ox);\n}\n\n.fieldnotes-clip-time {\n  color: var(--fn-ink-2);\n  font: 600 12px/1 var(--fn-serif);\n  font-variant-numeric: tabular-nums;\n}\n\n.fieldnotes-clip-quote {\n  margin-top: 14px;\n  font: italic 400 17px/1.4 var(--fn-display);\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-clip-quote span {\n  display: block;\n  margin-top: 6px;\n  color: var(--fn-ink-2);\n  font: 600 11.5px/1.3 var(--fn-serif);\n  font-style: normal;\n  letter-spacing: 0.06em;\n}\n\n.fieldnotes-setting {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 14px;\n  margin-top: 16px;\n  padding-top: 14px;\n  border-top: 1px solid var(--fn-rule-soft);\n}\n\n.fieldnotes-setting label {\n  display: grid;\n  gap: 3px;\n  font: 600 14.5px/1.25 var(--fn-serif);\n}\n\n.fieldnotes-setting label span {\n  color: var(--fn-ink-2);\n  font: 400 13px/1.35 var(--fn-serif);\n}\n\n/* From the field */\n.fieldnotes-field .fieldnotes-h2 {\n  margin-top: clamp(36px, 5vw, 64px);\n}\n\n.fieldnotes-field-lead {\n  display: grid;\n  grid-template-columns: repeat(12, minmax(0, 1fr));\n  column-gap: var(--fn-gap);\n  margin-top: clamp(40px, 5vw, 72px);\n}\n\n.fieldnotes-field-lead .fieldnotes-pullquote {\n  grid-column: 2 / span 10;\n}\n\n.fieldnotes-field-pair {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: clamp(28px, 4vw, 64px);\n  margin-top: clamp(56px, 6vw, 88px);\n}\n\n.fieldnotes-field-pair .fieldnotes-pullquote {\n  padding-top: 24px;\n  border-top: 1px solid var(--fn-ink);\n}\n\n.fieldnotes-field-pair .fieldnotes-pullquote-mark {\n  font-size: 72px;\n}\n\n.fieldnotes-stats {\n  display: grid;\n  grid-template-columns: repeat(4, minmax(0, 1fr));\n  margin-top: clamp(56px, 6vw, 96px);\n  border-block: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-stat {\n  display: flex;\n  flex-direction: column-reverse;\n  justify-content: flex-end;\n  gap: 10px;\n  padding: 26px 22px 28px;\n  border-left: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-stat:first-child {\n  padding-left: 0;\n  border-left: 0;\n}\n\n.fieldnotes-stat dd {\n  font: 400 clamp(40px, 4.4vw, 64px) / 0.95 var(--fn-display);\n  letter-spacing: -0.04em;\n  font-variant-numeric: tabular-nums lining-nums;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-stat dd small {\n  color: var(--fn-ox);\n  font: italic 400 0.42em/1 var(--fn-display);\n  letter-spacing: -0.01em;\n}\n\n.fieldnotes-stat dt {\n  max-width: 15em;\n  color: var(--fn-ink-2);\n  font-size: 15px;\n  line-height: 1.4;\n}\n\n/* Rates */\n.fieldnotes-rates-head {\n  align-items: end;\n  row-gap: 22px;\n  margin-top: clamp(36px, 5vw, 64px);\n}\n\n.fieldnotes-rates-head .fieldnotes-h2 {\n  grid-column: 1 / span 7;\n}\n\n.fieldnotes-rates-side {\n  grid-column: 9 / -1;\n  display: grid;\n  justify-items: start;\n  gap: 14px;\n}\n\n.fieldnotes-rates-side .fieldnotes-dek {\n  font-size: 20px;\n}\n\n.fieldnotes-rates-side p:not(.fieldnotes-dek) {\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n.fieldnotes .fieldnotes-segmented {\n  display: inline-grid;\n  width: auto;\n  padding: 4px;\n  font: 600 14px/1 var(--fn-serif);\n}\n\n.fieldnotes-segmented .lg-segmented-item {\n  min-height: 34px;\n  padding: 0 16px;\n  font: inherit;\n}\n\n.fieldnotes .fieldnotes-rates-plans {\n  display: none;\n  width: 100%;\n  margin-top: 32px;\n}\n\n/* PricingTable: one grid; each row a subgrid, so every column lines up. */\n.fieldnotes-pricing {\n  position: relative;\n  display: grid;\n  grid-template-columns: minmax(150px, 1.15fr) repeat(3, minmax(0, 1fr));\n  grid-template-rows: repeat(11, auto);\n  margin-top: clamp(40px, 5vw, 64px);\n}\n\n.fieldnotes-pricing-glass.lg-glass-surface {\n  --lg-panel-radius: 28px;\n  position: absolute;\n  z-index: 1;\n  inset: -14px -2px;\n  grid-column: 3 / 4;\n  grid-row: 1 / -1;\n  padding: 0;\n  pointer-events: none;\n}\n\n.fieldnotes-pricing-stamp {\n  position: absolute;\n  z-index: 3;\n  top: -46px;\n  right: -14px;\n  grid-column: 3 / 4;\n  grid-row: 1 / 2;\n  width: 84px;\n  transform: rotate(12deg);\n}\n\n.fieldnotes-pricing-stamp .fieldnotes-stamp-ring {\n  letter-spacing: 2.6px;\n}\n\n.fieldnotes-pricing-stamp .fieldnotes-stamp-centre {\n  font-size: 22px;\n}\n\n.fieldnotes-pricing-row {\n  position: relative;\n  z-index: 2;\n  display: grid;\n  grid-column: 1 / -1;\n  grid-template-columns: subgrid;\n  border-top: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-pricing-row > * {\n  padding: 15px 22px;\n  font-size: 15.5px;\n  line-height: 1.45;\n}\n\n.fieldnotes-pricing-row > :first-child {\n  padding-left: 0;\n  font-weight: 600;\n}\n\n.fieldnotes-pricing-head {\n  border-top: 0;\n}\n\n.fieldnotes-pricing-head > * {\n  padding-block: 26px 22px;\n}\n\n.fieldnotes-pricing-foot > * {\n  padding-block: 22px 26px;\n}\n\n.fieldnotes-pricing-plan {\n  font: 400 30px/1 var(--fn-display);\n  letter-spacing: -0.02em;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-pricing-price {\n  display: flex;\n  align-items: baseline;\n  gap: 6px;\n  margin-top: 16px;\n}\n\n.fieldnotes-pricing-amount {\n  font: 400 clamp(44px, 4.4vw, 60px) / 0.9 var(--fn-display);\n  letter-spacing: -0.04em;\n  font-variant-numeric: tabular-nums lining-nums;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-pricing-per {\n  color: var(--fn-ink-2);\n  font-size: 13.5px;\n  line-height: 1.35;\n}\n\n.fieldnotes-pricing-for {\n  display: block;\n  margin-top: 12px;\n  color: var(--fn-ink-2);\n  font-size: 14.5px;\n  font-weight: 400;\n  line-height: 1.4;\n}\n\n.fieldnotes-pricing [data-absent] {\n  color: var(--fn-ink-2);\n}\n\n.fieldnotes-pricing .lg-button {\n  width: 100%;\n}\n\n.fieldnotes-rates-note {\n  margin-top: 18px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n/* Letters to the editor */\n.fieldnotes-letters-list {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 0 clamp(28px, 4vw, 64px);\n  margin-top: clamp(40px, 5vw, 64px);\n}\n\n.fieldnotes-letters-column {\n  display: grid;\n  align-content: start;\n}\n\n.fieldnotes-lte {\n  padding: 26px 0 22px;\n  border-top: 1px solid var(--fn-rule);\n}\n\n.fieldnotes-lte-salute {\n  color: var(--fn-ink-2);\n  font: italic 400 17px/1 var(--fn-display);\n}\n\n.fieldnotes-lte .fieldnotes-lte-toggle {\n  display: block;\n  width: 100%;\n  padding: 0;\n  border: 0;\n  border-radius: 6px;\n  background: none;\n  box-shadow: none;\n  color: inherit;\n  font: inherit;\n  text-align: left;\n  -webkit-backdrop-filter: none;\n  backdrop-filter: none;\n}\n\n.fieldnotes-lte .fieldnotes-lte-toggle:hover {\n  background: none;\n}\n\n.fieldnotes-lte-toggle > span {\n  display: inline;\n}\n\n.fieldnotes-lte-question {\n  display: block;\n  margin-top: 12px;\n  font: 400 clamp(21px, 1.8vw, 25px) / 1.3 var(--fn-display);\n  letter-spacing: -0.012em;\n  text-wrap: balance;\n  font-variation-settings: 'SOFT' 100;\n}\n\n.fieldnotes-lte-from {\n  display: block;\n  margin-top: 12px;\n  color: var(--fn-ink-2);\n  font: 600 11.5px/1.3 var(--fn-serif);\n  letter-spacing: 0.13em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-lte-replies {\n  display: inline-block;\n  margin-top: 10px;\n  padding: 8px 0;\n  color: var(--fn-ox);\n  font: 600 15px/1 var(--fn-serif);\n}\n\n.fieldnotes-lte-toggle:hover .fieldnotes-lte-replies {\n  text-decoration: underline;\n  text-underline-offset: 3px;\n}\n\n.fieldnotes-lte .lg-accordion-chevron {\n  display: inline-block;\n  width: 16px;\n  height: 16px;\n  margin-left: 8px;\n  color: var(--fn-ox);\n  vertical-align: -2px;\n  transition: transform 420ms var(--lg-spring);\n}\n\n.fieldnotes-lte-reply.lg-glass-surface {\n  --lg-panel-elevation-shadow: none;\n  margin: 0;\n  padding: 0;\n  overflow: hidden;\n  background: none;\n  box-shadow: none;\n}\n\n.fieldnotes-lte-reply::before,\n.fieldnotes-lte-reply::after {\n  display: none;\n}\n\n.fieldnotes-lte-reply p {\n  margin: 6px 0;\n  padding: 2px 0 2px 18px;\n  border-left: 2px solid color-mix(in srgb, var(--fn-ox) 45%, transparent);\n  font-size: 16.5px;\n  line-height: 1.6;\n}\n\n/* Sign-off */\n.fieldnotes-signoff-sheet {\n  position: relative;\n  max-width: 900px;\n  margin: clamp(40px, 5vw, 64px) auto 0;\n  padding: clamp(36px, 6vw, 84px) clamp(22px, 7vw, 96px) clamp(36px, 5vw, 64px);\n  border-radius: 6px;\n  background:\n    repeating-linear-gradient(180deg, transparent 0 31px, rgb(82 120 160 / 0.08) 31px 32px) 0 20px,\n    #fffcf7;\n  box-shadow:\n    0 1px 0 rgb(var(--fn-shadow) / 0.06),\n    0 40px 70px -40px rgb(var(--fn-shadow) / 0.45),\n    inset 0 0 0 1px rgb(42 36 32 / 0.05);\n}\n\n.fieldnotes-signoff-sheet .fieldnotes-h2 {\n  font-size: clamp(38px, 4.8vw, 68px);\n}\n\n.fieldnotes-signoff-body {\n  display: grid;\n  gap: 1em;\n  max-width: 36em;\n  margin-top: 28px;\n  font-size: 18px;\n  line-height: 1.66;\n}\n\n.fieldnotes-signature {\n  margin-top: 34px;\n}\n\n.fieldnotes-signature-close {\n  color: var(--fn-ink-2);\n  font: italic 400 19px/1 var(--fn-display);\n}\n\n.fieldnotes-signature-name {\n  position: relative;\n  display: inline-block;\n  margin-top: 12px;\n  color: var(--fn-ox);\n  font: italic 300 clamp(56px, 7vw, 84px) / 1 var(--fn-display);\n  letter-spacing: -0.02em;\n  font-variation-settings:\n    'SOFT' 100,\n    'WONK' 1;\n}\n\n.fieldnotes-signature-swash {\n  position: absolute;\n  left: -6%;\n  bottom: -14px;\n  width: 130%;\n  height: 36px;\n  overflow: visible;\n}\n\n.fieldnotes-signature-swash path {\n  fill: none;\n  stroke: var(--fn-ox);\n  stroke-width: 1.8;\n  stroke-linecap: round;\n  stroke-dasharray: 1;\n  stroke-dashoffset: 0;\n}\n\n.fieldnotes-signature-who {\n  margin-top: 22px;\n  color: var(--fn-ink-2);\n  font-size: 15px;\n}\n\n.fieldnotes-start {\n  position: relative;\n  margin-top: clamp(36px, 5vw, 56px);\n  padding-top: 30px;\n  border-top: 1px solid var(--fn-rule);\n}\n\n/* A postmark behind the field, for the glass to bend. */\n.fieldnotes-start-art {\n  position: absolute;\n  right: -13.5%;\n  top: 27px;\n  width: min(62%, 420px);\n  color: color-mix(in srgb, var(--fn-ox) 50%, transparent);\n  pointer-events: none;\n}\n\n.fieldnotes-start-art text {\n  font: 700 11px var(--fn-serif);\n  letter-spacing: 1.4px;\n}\n\n.fieldnotes-start-label {\n  display: block;\n  font: 600 15px/1.2 var(--fn-serif);\n}\n\n.fieldnotes-start-row {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 10px;\n  margin-top: 12px;\n}\n\n.fieldnotes-start-row > div {\n  flex: 1 1 260px;\n  width: auto;\n  min-width: 0;\n}\n\n.fieldnotes .fieldnotes-start-input {\n  height: 56px;\n  padding-inline: 22px;\n  border-radius: 999px;\n  color: var(--fn-ink);\n  font: 400 17px var(--fn-serif);\n}\n\n.fieldnotes .fieldnotes-start-input::placeholder {\n  color: var(--fn-ink-2);\n}\n\n.fieldnotes .fieldnotes-start-input[aria-invalid='true'] {\n  box-shadow:\n    inset 0 0 0 2px var(--fn-ox),\n    inset 0 1px 2px rgb(var(--fn-shadow) / 0.2);\n}\n\n.fieldnotes-start-error {\n  color: var(--fn-ox);\n  font: 600 14px/1.4 var(--fn-serif);\n}\n\n.fieldnotes-start-error:not(:empty) {\n  margin-top: 10px;\n}\n\n.fieldnotes-start-help,\n.fieldnotes-start-demo {\n  margin-top: 12px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n.fieldnotes-start-demo {\n  font-style: italic;\n}\n\n.fieldnotes-postscript {\n  margin-top: 30px;\n  font-size: 16.5px;\n  line-height: 1.6;\n}\n\n.fieldnotes-postscript b {\n  color: var(--fn-ox);\n  font: italic 500 17px/1 var(--fn-display);\n}\n\n.fieldnotes-postscript a {\n  color: var(--fn-ox);\n  text-decoration: underline;\n  text-underline-offset: 3px;\n}\n\n/* Colophon */\n.fieldnotes-colophon {\n  margin-top: clamp(96px, 12vw, 168px);\n  padding-bottom: 36px;\n}\n\n.fieldnotes-colophon-top {\n  display: grid;\n  grid-template-columns: repeat(12, minmax(0, 1fr));\n  gap: 40px var(--fn-gap);\n  padding-top: 40px;\n  border-top: 1px solid var(--fn-ink);\n}\n\n.fieldnotes-colophon-brand {\n  grid-column: 1 / span 4;\n}\n\n.fieldnotes-colophon-brand .fieldnotes-brand {\n  font-size: 28px;\n}\n\n.fieldnotes-colophon-brand p {\n  max-width: 22em;\n  margin-top: 14px;\n  color: var(--fn-ink-2);\n  font-size: 15.5px;\n}\n\n.fieldnotes-colophon-nav {\n  grid-column: 6 / -1;\n  display: grid;\n  grid-template-columns: repeat(3, minmax(0, 1fr));\n  gap: 24px;\n}\n\n.fieldnotes-colophon-nav h2 {\n  color: var(--fn-ink-2);\n  font: 600 11.5px/1 var(--fn-serif);\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-colophon-nav ul {\n  display: grid;\n  gap: 10px;\n  margin-top: 16px;\n}\n\n.fieldnotes-colophon-nav a {\n  font-size: 15.5px;\n  text-decoration: none;\n}\n\n.fieldnotes-colophon-nav a:hover {\n  text-decoration: underline;\n  text-underline-offset: 3px;\n}\n\n.fieldnotes-colophon-note {\n  grid-column: 1 / -1;\n  display: grid;\n  grid-template-columns: auto minmax(0, 1fr);\n  gap: 18px;\n  padding: 20px 0;\n  border-block: 1px solid var(--fn-rule);\n  color: var(--fn-ink-2);\n  font: italic 400 15px/1.55 var(--fn-serif);\n}\n\n.fieldnotes-colophon-note h2 {\n  color: var(--fn-ox);\n  font: 600 11.5px/1.9 var(--fn-serif);\n  font-style: normal;\n  letter-spacing: 0.14em;\n  text-transform: uppercase;\n}\n\n.fieldnotes-colophon-base {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  gap: 12px 24px;\n  margin-top: 20px;\n  color: var(--fn-ink-2);\n  font-size: 14px;\n}\n\n.fieldnotes-to-top {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  text-decoration: none;\n}\n\n.fieldnotes-to-top:hover {\n  text-decoration: underline;\n}\n\n.fieldnotes-to-top .fieldnotes-icon {\n  width: 15px;\n  height: 15px;\n}\n\n/* Toast */\n.fieldnotes-toasts.lg-toast-viewport {\n  top: auto;\n  right: auto;\n  bottom: 20px;\n  left: 50%;\n  width: max-content;\n  max-width: calc(100vw - 32px);\n  transform: translateX(-50%);\n}\n\n.fieldnotes-toast .lg-toast-root {\n  --lg-panel-radius: 999px;\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  padding: 10px 10px 10px 18px;\n  color: var(--fn-ink);\n}\n\n.fieldnotes-toast .lg-toast-root::before {\n  background-color: rgb(252 248 241 / 0.86);\n}\n\n.fieldnotes-toast-icon {\n  color: var(--fn-quote);\n}\n\n.fieldnotes-toast-text {\n  font: 500 15px/1.35 var(--fn-serif);\n  text-wrap: pretty;\n}\n\n/* Cover choreography, on first load only: the page clears data-intro once it has played, so a\n   later resize never replays it. Every end state is the element's own style. */\n@keyframes fieldnotes-focus-in {\n  from {\n    filter: blur(10px);\n    transform: translateY(8px);\n  }\n}\n\n@keyframes fieldnotes-rule {\n  from {\n    transform: scaleX(0);\n  }\n}\n\n@keyframes fieldnotes-up {\n  from {\n    transform: translateY(18px);\n  }\n}\n\n@keyframes fieldnotes-mark {\n  from {\n    transform: rotate(-1.2deg) scaleX(0);\n  }\n}\n\n@keyframes fieldnotes-drop {\n  from {\n    transform: translate(var(--fx, 0px), var(--fy, -28px))\n      rotate(calc(var(--r, 0deg) + var(--fr, 8deg)));\n  }\n\n  to {\n    transform: rotate(var(--r, 0deg));\n  }\n}\n\n@keyframes fieldnotes-card {\n  from {\n    transform: translateY(44px) rotate(2deg);\n  }\n\n  to {\n    transform: rotate(-1.5deg);\n  }\n}\n\n@keyframes fieldnotes-dot {\n  from {\n    transform: scale(0);\n  }\n}\n\n@keyframes fieldnotes-drift {\n  0%,\n  100% {\n    transform: translateY(0);\n  }\n\n  50% {\n    transform: translateY(-6px);\n  }\n}\n\n@keyframes fieldnotes-pop {\n  from {\n    transform: translateY(-8px);\n  }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-dateline::before,\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-dateline::after {\n    animation: fieldnotes-rule 900ms var(--lg-ease-out) backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-dateline > * {\n    animation: fieldnotes-focus-in 700ms var(--lg-ease-out) 120ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-kicker {\n    animation: fieldnotes-focus-in 700ms var(--lg-ease-out) 160ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-line {\n    animation: fieldnotes-focus-in 900ms var(--lg-ease-out) 220ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1'])\n    .fieldnotes[data-intro]\n    .fieldnotes-line\n    + .fieldnotes-line {\n    animation-delay: 380ms;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-cover-title em::after {\n    animation: fieldnotes-mark 620ms var(--lg-ease-out) 1180ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-teaser {\n    animation: fieldnotes-focus-in 800ms var(--lg-ease-out) 620ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-standfirst {\n    animation: fieldnotes-focus-in 800ms var(--lg-ease-out) 560ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-byline {\n    animation: fieldnotes-focus-in 700ms var(--lg-ease-out) 700ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-cover-actions,\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-cover-note {\n    animation: fieldnotes-up 800ms var(--lg-ease-out) 760ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1'])\n    .fieldnotes[data-intro]\n    .fieldnotes-cover\n    .fieldnotes-piece {\n    animation: fieldnotes-drop 1000ms var(--lg-spring) var(--d, 300ms) backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1'])\n    .fieldnotes[data-intro]\n    .fieldnotes-cover-card.lg-glass-surface {\n    animation: fieldnotes-card 1000ms var(--lg-spring) 900ms backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1'])\n    .fieldnotes[data-intro]\n    .fieldnotes-dots\n    i[data-on]::after {\n    animation: fieldnotes-dot 420ms var(--lg-spring) calc(1160ms + var(--k, 0) * 46ms) backwards;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes[data-intro] .fieldnotes-contents {\n    animation: fieldnotes-up 800ms var(--lg-ease-out) 1000ms backwards;\n  }\n\n  /* The one quiet loop: the cover's card drifts a few pixels over fourteen seconds. */\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes-cover-float {\n    animation: fieldnotes-drift 14s ease-in-out infinite paused;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes-cover-float[data-live] {\n    animation-play-state: running;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes-pattern {\n    animation: fieldnotes-up 520ms var(--lg-spring) both;\n  }\n\n  :root:not([data-lg-reduce-motion='1']) .fieldnotes-pattern-dots i[data-new] {\n    animation: fieldnotes-pop 520ms var(--lg-spring) 240ms both;\n  }\n}\n\n/* Reveals: the page displaces an element just before it scrolls in, then moves it into place.\n   Without script, in still captures or with reduced motion, nothing is ever displaced. */\n.fieldnotes [data-reveal='focus'][data-reveal-state='armed'] {\n  filter: blur(10px);\n  transform: translateY(16px);\n}\n\n.fieldnotes [data-reveal='focus'][data-reveal-state='in'] {\n  transition:\n    filter 900ms var(--lg-ease-out),\n    transform 900ms var(--lg-ease-out);\n}\n\n.fieldnotes-pullquote[data-reveal-state='armed'] {\n  transform: translateY(36px);\n}\n\n.fieldnotes-pullquote[data-reveal-state='in'] {\n  transition: transform 1000ms var(--lg-ease-out);\n}\n\n.fieldnotes-sidenote[data-reveal-state='armed']:not([data-lit]).lg-glass-surface {\n  transform: translateX(28px);\n}\n\n.fieldnotes-dropcap[data-reveal-state='armed'] {\n  filter: blur(6px);\n  transform: translateY(-0.28em) rotate(-7deg);\n}\n\n.fieldnotes-dropcap[data-reveal-state='in'] {\n  transition:\n    transform 1000ms var(--lg-spring),\n    filter 600ms var(--lg-ease-out);\n}\n\n.fieldnotes-signature[data-reveal-state='armed'] .fieldnotes-signature-name {\n  filter: blur(8px);\n  transform: translateY(8px);\n}\n\n.fieldnotes-signature[data-reveal-state='armed'] .fieldnotes-signature-swash path {\n  stroke-dashoffset: 1;\n}\n\n.fieldnotes-signature[data-reveal-state='in'] .fieldnotes-signature-name {\n  transition:\n    filter 900ms var(--lg-ease-out),\n    transform 900ms var(--lg-ease-out);\n}\n\n.fieldnotes-signature[data-reveal-state='in'] .fieldnotes-signature-swash path {\n  transition: stroke-dashoffset 1300ms var(--lg-ease-out) 350ms;\n}\n\n.fieldnotes-plate[data-reveal-state='armed'] .fieldnotes-piece {\n  transform: translate(var(--fx, 0px), var(--fy, 40px))\n    rotate(calc(var(--r, 0deg) + var(--fr, 6deg)));\n}\n\n.fieldnotes-plate[data-reveal-state='in'] .fieldnotes-piece {\n  transition: transform 1100ms var(--lg-spring) var(--d, 0ms);\n}\n\n.fieldnotes-plate[data-reveal-state='armed'] .fieldnotes-figure-pane.lg-glass-surface {\n  transform: translateY(64px) rotate(1.5deg);\n}\n\n.fieldnotes-plate[data-reveal-state='in'] .fieldnotes-figure-pane.lg-glass-surface {\n  transition: transform 1200ms var(--lg-spring) 220ms;\n}\n\n.fieldnotes-plate[data-reveal-state='armed'] .fieldnotes-taxonomy li::before {\n  transform: scaleX(0);\n}\n\n.fieldnotes-plate[data-reveal-state='in'] .fieldnotes-taxonomy li::before {\n  transition: transform 900ms var(--lg-ease-out) 520ms;\n}\n\n.fieldnotes-plate[data-reveal-state='armed'] .fieldnotes-timeline-thread {\n  stroke-dashoffset: 1;\n}\n\n.fieldnotes-plate[data-reveal-state='in'] .fieldnotes-timeline-thread {\n  transition: stroke-dashoffset 1500ms var(--lg-ease-out) 600ms;\n}\n\n/* Layout */\n@media (max-width: 1100px) {\n  .fieldnotes-mast-issue,\n  .fieldnotes-teaser {\n    display: none;\n  }\n\n  .fieldnotes-proof-inner {\n    grid-template-columns: 1fr;\n  }\n\n  .fieldnotes-proof-marks {\n    grid-template-columns: repeat(3, minmax(0, 1fr));\n    gap: 18px 28px;\n  }\n\n  .fieldnotes-contents ol {\n    grid-template-columns: repeat(3, minmax(0, 1fr));\n  }\n}\n\n@media (max-width: 960px) {\n  .fieldnotes-lede {\n    grid-column: 1 / -1;\n  }\n\n  .fieldnotes-cover-art {\n    grid-column: 1 / -1;\n    grid-row: auto;\n    height: clamp(320px, 62vw, 420px);\n    margin-top: 44px;\n  }\n\n  .fieldnotes-contents {\n    grid-template-columns: 1fr;\n    gap: 10px;\n  }\n\n  .fieldnotes-contents ol {\n    grid-template-columns: repeat(3, minmax(0, 1fr));\n  }\n\n  .fieldnotes-letter-title,\n  .fieldnotes-letter-dek,\n  .fieldnotes-letter-by {\n    grid-column: 1 / -1;\n  }\n\n  .fieldnotes-letter-body::before {\n    display: none;\n  }\n\n  .fieldnotes-letter-body > *,\n  .fieldnotes-noted > p,\n  .fieldnotes-letter-body > p,\n  .fieldnotes-letter-body > .fieldnotes-pullquote-inline {\n    grid-column: 1 / -1;\n  }\n\n  .fieldnotes-noted {\n    display: block;\n  }\n\n  .fieldnotes-sidenote.lg-glass-surface {\n    max-width: none;\n    margin: -0.2em 0 1.5em clamp(0px, 4vw, 40px);\n  }\n\n  .fieldnotes-sidenote[data-reveal-state='armed']:not([data-lit]).lg-glass-surface {\n    transform: translateX(14px);\n  }\n\n  .fieldnotes-section-mark {\n    position: static;\n    margin-right: 10px;\n  }\n\n  .fieldnotes-try-head .fieldnotes-h2,\n  .fieldnotes-try-intro,\n  .fieldnotes-try-main,\n  .fieldnotes-board-wrap {\n    grid-column: 1 / -1;\n  }\n\n  .fieldnotes-board-wrap {\n    position: relative;\n    top: 0;\n    margin-top: 40px;\n  }\n\n  .fieldnotes-dock {\n    display: flex;\n  }\n\n  .fieldnotes-chapter-text,\n  .fieldnotes-chapter[data-flip] .fieldnotes-chapter-text {\n    grid-column: 1 / -1;\n    grid-row: 1;\n  }\n\n  .fieldnotes-chapter-figure,\n  .fieldnotes-chapter[data-flip] .fieldnotes-chapter-figure {\n    grid-column: 1 / -1;\n    grid-row: 2;\n    margin-top: 40px;\n  }\n\n  .fieldnotes-field-lead .fieldnotes-pullquote,\n  .fieldnotes-rates-head .fieldnotes-h2,\n  .fieldnotes-rates-side,\n  .fieldnotes-colophon-brand,\n  .fieldnotes-colophon-nav {\n    grid-column: 1 / -1;\n  }\n}\n\n@media (max-width: 899px) {\n  .fieldnotes-mast-nav,\n  .fieldnotes-mast-signin {\n    display: none;\n  }\n\n  .fieldnotes .fieldnotes-mast-menu {\n    display: inline-flex;\n  }\n}\n\n@media (max-width: 760px) {\n  .fieldnotes .fieldnotes-rates-plans {\n    display: grid;\n  }\n\n  .fieldnotes-pricing {\n    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);\n  }\n\n  .fieldnotes-pricing [data-column] {\n    display: none;\n  }\n\n  .fieldnotes-pricing[data-show='0'] [data-column='0'],\n  .fieldnotes-pricing[data-show='1'] [data-column='1'],\n  .fieldnotes-pricing[data-show='2'] [data-column='2'] {\n    display: block;\n  }\n\n  .fieldnotes-pricing-glass.lg-glass-surface {\n    display: none;\n    grid-column: 2 / 3;\n  }\n\n  .fieldnotes-pricing[data-show='1'] .fieldnotes-pricing-glass.lg-glass-surface {\n    display: block;\n  }\n\n  .fieldnotes-pricing-stamp {\n    display: none;\n  }\n\n  .fieldnotes-pricing-row > * {\n    padding: 14px;\n    font-size: 15px;\n  }\n\n  .fieldnotes-pricing-price {\n    flex-direction: column;\n    align-items: start;\n    gap: 8px;\n  }\n\n  .fieldnotes-field-pair {\n    grid-template-columns: 1fr;\n  }\n\n  .fieldnotes-stats {\n    grid-template-columns: 1fr 1fr;\n  }\n\n  .fieldnotes-stat:nth-child(3) {\n    padding-left: 0;\n    border-left: 0;\n  }\n\n  .fieldnotes-stat:nth-child(n + 3) {\n    border-top: 1px solid var(--fn-rule);\n  }\n\n  .fieldnotes-letters-list {\n    grid-template-columns: 1fr;\n  }\n}\n\n@media (max-width: 640px) {\n  .fieldnotes-dateline {\n    grid-template-columns: auto auto;\n    justify-content: space-between;\n    gap: 12px;\n  }\n\n  .fieldnotes-dateline-mid {\n    display: none;\n  }\n\n  .fieldnotes-cover-actions .lg-button {\n    flex: 1 1 auto;\n  }\n\n  .fieldnotes-cover-float {\n    left: 2%;\n    width: min(76%, 300px);\n  }\n\n  .fieldnotes-cover-card.lg-glass-surface {\n    padding: 15px 16px 16px;\n  }\n\n  .fieldnotes-cover-card-title {\n    margin-top: 12px;\n    font-size: 20px;\n  }\n\n  .fieldnotes-cover-card-sub {\n    font-size: 13px;\n  }\n\n  .fieldnotes-dots {\n    flex-wrap: nowrap;\n    gap: 4px;\n    margin-top: 12px;\n  }\n\n  .fieldnotes-dots i {\n    width: 9px;\n    height: 9px;\n  }\n\n  .fieldnotes-contents ol {\n    grid-template-columns: repeat(2, minmax(0, 1fr));\n  }\n\n  .fieldnotes-contents a {\n    gap: 6px;\n  }\n\n  .fieldnotes-contents-title::after {\n    margin-left: 6px;\n  }\n\n  .fieldnotes-proof-marks {\n    grid-template-columns: repeat(2, minmax(0, 1fr));\n  }\n\n  .fieldnotes-transcript {\n    padding-left: 38px;\n    padding-right: 16px;\n  }\n\n  .fieldnotes-turn {\n    grid-template-columns: auto minmax(0, 1fr);\n    grid-template-areas:\n      'who time'\n      'text text';\n    align-items: baseline;\n    column-gap: 10px;\n  }\n\n  .fieldnotes-turn-time {\n    padding: 0;\n    font-size: 12px;\n  }\n\n  .fieldnotes-wide-only {\n    display: none;\n  }\n\n  .fieldnotes-start-art {\n    top: 2px;\n    right: -26px;\n    width: 330px;\n  }\n\n  .fieldnotes-start-art path {\n    display: none;\n  }\n\n  .fieldnotes-dock-tally.lg-glass-surface {\n    gap: 4px;\n    padding: 5px;\n  }\n\n  .fieldnotes-dock-tally .fieldnotes-badge {\n    gap: 5px;\n    padding: 0 9px;\n  }\n\n  .fieldnotes-board.lg-glass-surface {\n    padding: 18px 16px;\n  }\n\n  .fieldnotes-board-note {\n    right: 0;\n    width: 96px;\n  }\n\n  .fieldnotes-board-card {\n    left: 0;\n  }\n\n  .fieldnotes-plate {\n    padding-inline: 14px;\n  }\n\n  .fieldnotes-stat {\n    padding: 22px 14px 22px 16px;\n  }\n\n  .fieldnotes-colophon-nav {\n    grid-template-columns: 1fr 1fr;\n  }\n\n  .fieldnotes-colophon-note {\n    grid-template-columns: 1fr;\n    gap: 6px;\n  }\n\n  .fieldnotes-start-row .lg-button {\n    width: 100%;\n  }\n}\n\n@media (max-width: 380px) {\n  .fieldnotes-roomy-only {\n    display: none;\n  }\n}\n\n/* Under 360 px the masthead keeps the brand and the menu, whose sheet has Start free. */\n@media (max-width: 359px) {\n  .fieldnotes .fieldnotes-mast-start {\n    display: none;\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .fieldnotes *,\n  .fieldnotes *::before,\n  .fieldnotes *::after {\n    animation: none !important;\n    transition-duration: 1ms !important;\n    transition-delay: 0ms !important;\n  }\n\n  .fieldnotes-mast-progress {\n    display: none;\n  }\n}\n\n:root[data-lg-reduce-motion='1'] .fieldnotes *,\n:root[data-lg-reduce-motion='1'] .fieldnotes *::before,\n:root[data-lg-reduce-motion='1'] .fieldnotes *::after {\n  animation: none !important;\n  transition-duration: 1ms !important;\n  transition-delay: 0ms !important;\n}\n\n:root[data-lg-reduce-motion='1'] .fieldnotes-mast-progress {\n  display: none;\n}\n\n@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {\n  .fieldnotes-moment,\n  .fieldnotes-pattern,\n  .fieldnotes-clip {\n    background: var(--fn-card);\n  }\n\n  .fieldnotes-pane::before,\n  .fieldnotes-float::before {\n    background-color: var(--fn-paper-3);\n  }\n}\n",
      "type": "registry:file",
      "target": "components/glassyui/templates/fieldnotes/fieldnotes.css"
    }
  ],
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "@radix-ui/react-accordion",
    "@radix-ui/react-avatar",
    "@radix-ui/react-slot",
    "@radix-ui/react-scroll-area",
    "@radix-ui/react-radio-group",
    "@radix-ui/react-dialog",
    "@radix-ui/react-switch",
    "@radix-ui/react-toast"
  ]
}