{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kynnos",
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "motion",
    "ogl",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://usva.build/r/atmospheres-core.json"
  ],
  "files": [
    {
      "path": "kynnos-field.ts",
      "target": "components/ui/kynnos-field.ts",
      "type": "registry:ui",
      "content": "import type { BlendMode, Rgb } from \"./atmospheres-color\";\n\nexport interface KynnosParams {\n  /** Wheel origin in normalized units, y up, relative to the container centre. */\n  origin: [number, number];\n  /** Radians per second. One revolution per ~10min at the default. */\n  spin: number;\n  /** Furrows per unit radius. */\n  furrowFreq: number;\n  /** Radial domain warp, measured in furrow spacings, not in radius units. */\n  warpAmt: number;\n  /** Spatial frequency of the warp noise. */\n  warpFreq: number;\n  /** How readily a furrow thins out and breaks, 0..0.5. */\n  breakAmt: number;\n  /** Ridge profile exponent. Above 1 the floor broadens and the crest pinches. */\n  ridgeShape: number;\n  /** Furrow depth in normalized units. */\n  depth: number;\n  /** Gain on the height gradient before the normal is built. */\n  slope: number;\n  /** Grain frequency. */\n  microScale: number;\n  /** Grain strength. Perturbs the normal only, never the height. */\n  microAmt: number;\n  /** Craquelure cell frequency. */\n  crackScale: number;\n  /** Craquelure strength. */\n  crackAmt: number;\n  /** Crevice occlusion strength. */\n  ao: number;\n  /** Oren-Nayar roughness. Clay is near-Lambertian and then some. */\n  rough: number;\n  /** Ambient fill. */\n  ambient: number;\n  /** Key light strength. */\n  key: number;\n  /** How fast the noise fields evolve on their own, apart from the wheel. */\n  drift: number;\n  /** Dither amplitude. Banding is visible on light grounds. */\n  dither: number;\n}\n\nexport const KYNNOS_DEFAULTS: KynnosParams = {\n  origin: [0.62, 1],\n  spin: 0.01,\n  furrowFreq: 10,\n  warpAmt: 4.4,\n  warpFreq: 2.1,\n  breakAmt: 0.1,\n  ridgeShape: 1.5,\n  depth: 0.06,\n  slope: 2,\n  microScale: 190,\n  microAmt: 0.38,\n  crackScale: 70,\n  crackAmt: 0.05,\n  ao: 0.75,\n  rough: 1,\n  ambient: 0.5,\n  key: 1.05,\n  drift: 0.028,\n  dither: 0,\n};\n\n/**\n * Concentric circles are one bad parameter from a vinyl record, so the two\n * defences are floored here rather than left to whoever tunes the props next.\n *\n * The warp is in furrow spacings and the shader's warp field is normalized to\n * about [-1, 1], so a peak displacement of MIN_WARP_TURNS grooves is what the\n * number actually buys. Below ~2 the rings merely wobble and stay legible as\n * rings. Well above ~4 the warp gradient overtakes the radial one, grooves fold\n * back on themselves and the surface turns to mush.\n */\nexport const MIN_WARP_TURNS = 2;\nexport const MAX_WARP_TURNS = 4.5;\n\n/**\n * The wheel origin has to sit outside the frame or its convergence point is\n * visible, and a visible convergence point is the vinyl label. Whichever axis is\n * the shorter one spans exactly [-0.5, 0.5] in shader units, so forcing both\n * components past that puts the origin off-frame at every aspect ratio.\n */\nexport const MIN_ORIGIN_OFFSET = 0.62;\n\nfunction offFrame(v: number): number {\n  const sign = v < 0 ? -1 : 1;\n  return sign * Math.max(Math.abs(v), MIN_ORIGIN_OFFSET);\n}\n\nexport function resolveParams(\n  overrides: Partial<KynnosParams> = {},\n): KynnosParams {\n  const merged = { ...KYNNOS_DEFAULTS, ...overrides };\n  return {\n    ...merged,\n    origin: [offFrame(merged.origin[0]), offFrame(merged.origin[1])],\n    furrowFreq: Math.max(merged.furrowFreq, 1),\n    warpAmt: Math.min(Math.max(merged.warpAmt, MIN_WARP_TURNS), MAX_WARP_TURNS),\n    ridgeShape: Math.max(merged.ridgeShape, 1),\n    breakAmt: Math.min(Math.max(merged.breakAmt, 0), 0.5),\n    rough: Math.min(Math.max(merged.rough, 0), 1),\n  };\n}\n\n/** The furrow profile: 0 in the floor, 1 on the crest. */\nexport function ridge(x: number, shape: number): number {\n  const phase = x - Math.floor(x);\n  const tri = 1 - Math.abs(phase * 2 - 1);\n  return tri ** shape;\n}\n\n/** Non-linear luma, matching the clamp the shader applies in sRGB space. */\nexport function luma([r, g, b]: Rgb): number {\n  return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\nfunction mix(a: Rgb, b: Rgb, t: number): Rgb {\n  return [\n    a[0] + (b[0] - a[0]) * t,\n    a[1] + (b[1] - a[1]) * t,\n    a[2] + (b[2] - a[2]) * t,\n  ];\n}\n\nexport type KynnosRole =\n  | \"bg\"\n  | \"surface\"\n  | \"surface-2\"\n  | \"ink\"\n  | \"accent\"\n  | \"accent-alt\";\n\nexport const KYNNOS_ROLES = [\n  \"bg\",\n  \"surface\",\n  \"surface-2\",\n  \"ink\",\n  \"accent\",\n  \"accent-alt\",\n] as const;\n\nexport interface KynnosColors {\n  /** The clay itself, the value most of the frame sits at. */\n  body: Rgb;\n  /** The dry pale crest. The brightest pixel the atmosphere is allowed. */\n  ridge: Rgb;\n  /** The warm crescent inside the groove. */\n  shadow: Rgb;\n  /** Key light colour. Only the dark-ground material tints by it. */\n  key: Rgb;\n  /** Hard ceiling on output luma. */\n  maxLum: number;\n}\n\n/**\n * The theme swap flips the lighting model, not the shader. On a light ground the\n * clay is pigment: nothing brighter than surface-2, nothing darker than an\n * occlusion shadow. On a dark ground the same relief becomes brushed metal, lit\n * by a grazing accent key that only the crests catch.\n */\nexport function buildColors(\n  roles: Record<KynnosRole, Rgb>,\n  mode: BlendMode,\n  keyColor?: Rgb,\n): KynnosColors {\n  if (mode === \"absorptive\") {\n    const warm = mix(roles[\"accent-alt\"], roles.ink, 0.55);\n    const shadow = mix(mix(roles.bg, warm, 0.62), roles.accent, 0.12);\n    const ridge = roles[\"surface-2\"];\n    return {\n      body: mix(roles.bg, roles.surface, 0.35),\n      ridge,\n      shadow,\n      key: keyColor ?? ridge,\n      maxLum: luma(ridge),\n    };\n  }\n  return {\n    body: roles.bg,\n    ridge: mix(roles.bg, roles.accent, 0.9),\n    shadow: mix(roles.bg, [0, 0, 0], 0.45),\n    key: keyColor ?? roles.accent,\n    maxLum: 1,\n  };\n}\n\n/** A raking key. Low z is what makes a 2cm groove throw a shadow. */\nexport const DEFAULT_LIGHT: [number, number, number] = [-0.72, 0.5, 0.54];\n"
    },
    {
      "path": "kynnos-shader.ts",
      "target": "components/ui/kynnos-shader.ts",
      "type": "registry:ui",
      "content": "import { glsl } from \"./atmospheres-glsl\";\n\n/**\n * kynnos is a lit heightfield, not a volume: one clay surface seen from above,\n * turning on a wheel far slower than you first notice. It reflects, it never\n * emits. There is no specular term anywhere below and there must never be one:\n * a lobe of any kind turns fired clay into wet glaze, and matte is the brief.\n */\nexport const kynnosFragmentShader = /* glsl */ `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform vec2  uResolution;\nuniform vec2  uOrigin;\nuniform float uSpin;\nuniform float uFurrowFreq;\nuniform float uWarpAmt;\nuniform float uWarpFreq;\nuniform float uBreakAmt;\nuniform float uRidgeShape;\nuniform float uDepth;\nuniform float uSlope;\nuniform float uMicroScale;\nuniform float uMicroAmt;\nuniform float uCrackScale;\nuniform float uCrackAmt;\nuniform float uAo;\nuniform float uRough;\nuniform float uAmbient;\nuniform float uKey;\nuniform float uDrift;\nuniform float uDither;\nuniform float uAlpha;\nuniform float uAbsorb;\nuniform float uMaxLum;\nuniform vec3  uLightDir;\nuniform vec3  uBody;\nuniform vec3  uRidge;\nuniform vec3  uShadow;\nuniform vec3  uKeyColor;\n\nout vec4 fragColor;\n\n${glsl(\"fbm\", \"worley\", \"dither\", \"composite\")}\n\nconst vec3 VIEW = vec3(0.0, 0.0, 1.0);\n\nfloat ridgeProfile(float x) {\n  float tri = 1.0 - abs(fract(x) * 2.0 - 1.0);\n  return pow(tri, uRidgeShape);\n}\n\n/* fbm2 sums halving amplitudes from 0.5, so 3 octaves land in [0, 0.875] and 2\n   in [0, 0.75]. Recentre and rescale, or the warp is a third of the amplitude\n   the parameter claims and the rings survive it. */\nfloat signedFbm3(vec2 p) {\n  return (fbm2(p, 3) - 0.4375) / 0.4375;\n}\n\nfloat signedFbm2(vec2 p) {\n  return (fbm2(p, 2) - 0.375) / 0.375;\n}\n\nfloat lumaOf(vec3 c) {\n  return dot(c, vec3(0.2126, 0.7152, 0.0722));\n}\n\n/* Oren-Nayar: retroreflective, so the surface stays flat and powdery at grazing\n   angles instead of rolling off like plastic. Diffuse only, by law. */\nfloat orenNayar(vec3 n, vec3 l, float rough) {\n  float s2 = rough * rough;\n  float a = 1.0 - 0.5 * s2 / (s2 + 0.33);\n  float b = 0.45 * s2 / (s2 + 0.09);\n  float ndl = dot(n, l);\n  float ndv = dot(n, VIEW);\n  float thetaL = acos(clamp(ndl, -1.0, 1.0));\n  float thetaV = acos(clamp(ndv, -1.0, 1.0));\n  float alpha = max(thetaL, thetaV);\n  float beta = min(thetaL, thetaV);\n  vec3 lp = l - n * ndl;\n  vec3 vp = VIEW - n * ndv;\n  float cosPhi = 0.0;\n  if (length(lp) > 1e-4 && length(vp) > 1e-4) {\n    cosPhi = max(dot(normalize(lp), normalize(vp)), 0.0);\n  }\n  return max(ndl, 0.0) * (a + b * cosPhi * sin(alpha) * tan(min(beta, 1.5)));\n}\n\nvoid main() {\n  vec2 frag = gl_FragCoord.xy;\n  float unit = max(min(uResolution.x, uResolution.y), 1.0);\n  vec2 p = (frag - 0.5 * uResolution) / unit;\n  vec2 q = p - uOrigin;\n  float r = length(q);\n\n  float spin = uTime * uSpin;\n  float cs = cos(spin);\n  float sn = sin(spin);\n  vec2 qr = vec2(cs * q.x - sn * q.y, sn * q.x + cs * q.y);\n\n  /* The named risk. Unwarped concentric furrows are a vinyl record, and the\n     warp is what stops the rings reading as rings. It is load-bearing, and so is\n     the off-frame origin: both are floored in resolveParams. uWarpAmt counts\n     furrow spacings, so the displacement is several grooves and neighbouring\n     furrows genuinely cross each other's old radius rather than wobbling in\n     lockstep. The broad term shoves whole arcs off the circle; the finer term\n     undulates them at the scale of a few grooves. */\n  float w1 = signedFbm3(qr * uWarpFreq + vec2(0.0, uTime * uDrift));\n  float w2 = signedFbm2(qr * uWarpFreq * 2.7 + vec2(19.7, -4.3));\n  float spacing = 1.0 / uFurrowFreq;\n  float rw = r + uWarpAmt * spacing * (w1 + 0.22 * w2);\n\n  float breakField = fbm2(qr * 1.6 + vec2(7.3, uTime * uDrift * 0.6), 3);\n  float alive = smoothstep(0.5 - uBreakAmt, 0.5 + uBreakAmt, breakField + 0.14);\n  float thick = 0.55 + 0.9 * fbm2(qr * 0.85 + vec2(3.1, 11.9), 2);\n\n  float x = rw * uFurrowFreq;\n  float profile = ridgeProfile(x);\n  float h = uDepth * thick * alive * profile;\n\n  float e = 0.22;\n  float lap = ridgeProfile(x + e) - 2.0 * profile + ridgeProfile(x - e);\n  float cavity = pow(1.0 - profile, 1.6);\n  float occ = uAo * alive * thick * (0.7 * cavity + 0.5 * clamp(lap * 6.0, 0.0, 1.0));\n  float ao = 1.0 - clamp(occ, 0.0, 0.88);\n\n  float px = 1.0 / unit;\n  vec2 grad = vec2(dFdx(h), dFdy(h)) / px;\n  vec3 n = normalize(vec3(-grad * uSlope, 1.0));\n\n  /* Grain perturbs the normal and never the height. Fold it into h instead and\n     the occlusion term eats it, and the clay reads as moulded latex, not grog. */\n  vec2 gp = qr * uMicroScale;\n  float g0 = vnoise2(gp);\n  vec2 dg = vec2(\n    vnoise2(gp + vec2(0.6, 0.0)) - g0,\n    vnoise2(gp + vec2(0.0, 0.6)) - g0\n  );\n  n = normalize(n + vec3(dg * uMicroAmt, 0.0));\n\n  // halkeama: drying cracks, and only where clay pools, so only in the floors.\n  float crack = (1.0 - smoothstep(0.0, 0.06, craquelure(qr * uCrackScale)))\n    * (1.0 - smoothstep(0.15, 0.6, profile));\n  vec2 dc = vec2(dFdx(crack), dFdy(crack)) / px;\n  n = normalize(n - vec3(dc * uCrackAmt * 0.02, 0.0));\n\n  vec3 l = normalize(uLightDir);\n  float diff = orenNayar(n, l, uRough);\n  float lit = (uAmbient + uKey * diff) * ao * (1.0 - 0.35 * crack * uCrackAmt);\n\n  vec3 pigment = mix(uShadow, uBody, smoothstep(0.05, 0.62, lit));\n  pigment = mix(pigment, uRidge, smoothstep(0.62, 1.05, lit));\n\n  /* Dark ground: same relief, different material. A grazing key that only the\n     crests catch, furrows falling back to the ground. Brushed metal, not clay. */\n  float caught = pow(clamp(diff, 0.0, 1.0), 5.0);\n  float crest = profile * profile * profile * profile * profile;\n  float grazing = 1.0 - abs(n.z);\n  grazing *= grazing;\n  float ridgeCatch = crest * grazing * caught;\n  vec3 metal = uBody + uKeyColor * ((caught * uKey * 1.15 + ridgeCatch * 0.55) * ao)\n    + (uShadow - uBody) * (1.0 - ao);\n\n  vec3 col = mix(metal, pigment, uAbsorb);\n\n  float lum = lumaOf(col);\n  if (lum > uMaxLum) col *= uMaxLum / max(lum, 1e-4);\n\n  col = dither(col, frag, uDither);\n  fragColor = composite(col, uAlpha);\n}\n`;\n"
    },
    {
      "path": "kynnos-uniforms.ts",
      "target": "components/ui/kynnos-uniforms.ts",
      "type": "registry:ui",
      "content": "import {\n  setUniform,\n  type Uniforms,\n} from \"./atmospheres-gl\";\nimport type { KynnosColors, KynnosParams } from \"./kynnos-field\";\n\nexport interface KynnosFrame {\n  time: number;\n  alpha: number;\n  absorb: number;\n  light: [number, number, number];\n}\n\nexport function kynnosUniforms(\n  colors: KynnosColors,\n  params: KynnosParams,\n  light: [number, number, number],\n): Uniforms {\n  return {\n    uTime: { value: 0 },\n    uResolution: { value: [1, 1] },\n    uOrigin: { value: [params.origin[0], params.origin[1]] },\n    uSpin: { value: params.spin },\n    uFurrowFreq: { value: params.furrowFreq },\n    uWarpAmt: { value: params.warpAmt },\n    uWarpFreq: { value: params.warpFreq },\n    uBreakAmt: { value: params.breakAmt },\n    uRidgeShape: { value: params.ridgeShape },\n    uDepth: { value: params.depth },\n    uSlope: { value: params.slope },\n    uMicroScale: { value: params.microScale },\n    uMicroAmt: { value: params.microAmt },\n    uCrackScale: { value: params.crackScale },\n    uCrackAmt: { value: params.crackAmt },\n    uAo: { value: params.ao },\n    uRough: { value: params.rough },\n    uAmbient: { value: params.ambient },\n    uKey: { value: params.key },\n    uDrift: { value: params.drift },\n    uDither: { value: params.dither },\n    uAlpha: { value: 1 },\n    uAbsorb: { value: 1 },\n    uMaxLum: { value: colors.maxLum },\n    uLightDir: { value: [light[0], light[1], light[2]] },\n    uBody: { value: colors.body },\n    uRidge: { value: colors.ridge },\n    uShadow: { value: colors.shadow },\n    uKeyColor: { value: colors.key },\n  };\n}\n\nexport function setKynnosColors(u: Uniforms, colors: KynnosColors): void {\n  setUniform(u, \"uBody\", colors.body);\n  setUniform(u, \"uRidge\", colors.ridge);\n  setUniform(u, \"uShadow\", colors.shadow);\n  setUniform(u, \"uKeyColor\", colors.key);\n  setUniform(u, \"uMaxLum\", colors.maxLum);\n}\n\nexport function setKynnosParams(u: Uniforms, params: KynnosParams): void {\n  setUniform(u, \"uOrigin\", [params.origin[0], params.origin[1]]);\n  setUniform(u, \"uSpin\", params.spin);\n  setUniform(u, \"uFurrowFreq\", params.furrowFreq);\n  setUniform(u, \"uWarpAmt\", params.warpAmt);\n  setUniform(u, \"uWarpFreq\", params.warpFreq);\n  setUniform(u, \"uBreakAmt\", params.breakAmt);\n  setUniform(u, \"uRidgeShape\", params.ridgeShape);\n  setUniform(u, \"uDepth\", params.depth);\n  setUniform(u, \"uSlope\", params.slope);\n  setUniform(u, \"uMicroScale\", params.microScale);\n  setUniform(u, \"uMicroAmt\", params.microAmt);\n  setUniform(u, \"uCrackScale\", params.crackScale);\n  setUniform(u, \"uCrackAmt\", params.crackAmt);\n  setUniform(u, \"uAo\", params.ao);\n  setUniform(u, \"uRough\", params.rough);\n  setUniform(u, \"uAmbient\", params.ambient);\n  setUniform(u, \"uKey\", params.key);\n  setUniform(u, \"uDrift\", params.drift);\n  setUniform(u, \"uDither\", params.dither);\n}\n\nexport function setKynnosFrame(u: Uniforms, frame: KynnosFrame): void {\n  setUniform(u, \"uTime\", frame.time);\n  setUniform(u, \"uAlpha\", frame.alpha);\n  setUniform(u, \"uAbsorb\", frame.absorb);\n  setUniform(u, \"uLightDir\", [frame.light[0], frame.light[1], frame.light[2]]);\n}\n"
    },
    {
      "path": "kynnos.tsx",
      "target": "components/ui/kynnos.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  type BlendMode,\n  blendUniform,\n  resolveBlendMode,\n  resolveColor,\n} from \"./atmospheres-color\";\nimport { useGlCanvas } from \"./use-gl-canvas\";\nimport { useTokenColors } from \"./use-token-colors\";\nimport {\n  buildColors,\n  DEFAULT_LIGHT,\n  KYNNOS_ROLES,\n  type KynnosParams,\n  resolveParams,\n} from \"./kynnos-field\";\nimport { kynnosFragmentShader } from \"./kynnos-shader\";\nimport {\n  kynnosUniforms,\n  setKynnosColors,\n  setKynnosFrame,\n  setKynnosParams,\n} from \"./kynnos-uniforms\";\n\nexport interface KynnosLight {\n  /** Raking is the point: keep z low or the grooves stop throwing shadows. */\n  direction?: [number, number, number];\n  /** Defaults to the theme: a warm daylight on clay, the accent on metal. */\n  color?: string;\n}\n\nexport interface KynnosProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Wheel and drift rate multiplier. Defaults to 1, about 10min per revolution. */\n  speed?: number;\n  /** Overrides the material. Defaults from the ground: light clay, dark metal. */\n  mode?: BlendMode;\n  /** The lighting dial. This is what a theme swap actually changes. */\n  light?: KynnosLight;\n  /** Overall opacity, 0..1. Defaults to 1. */\n  opacity?: number;\n  /** Escape hatch for the field parameters, for tuning demos. */\n  params?: Partial<KynnosParams>;\n  children?: React.ReactNode;\n}\n\n/** Enough seconds in that the still frame lands on structure, not on seed noise. */\nconst STILL_TIME = 14;\n\nexport const Kynnos = React.forwardRef<HTMLDivElement, KynnosProps>(\n  (\n    {\n      speed = 1,\n      mode,\n      light,\n      opacity = 1,\n      params,\n      className,\n      children,\n      ...props\n    },\n    forwardedRef,\n  ) => {\n    const lightColor = light?.color;\n    const direction = light?.direction;\n\n    const speedRef = React.useRef(speed);\n    speedRef.current = speed;\n    const opacityRef = React.useRef(opacity);\n    opacityRef.current = opacity;\n\n    const scopeRef = React.useRef<HTMLDivElement | null>(null);\n    const tokens = useTokenColors(KYNNOS_ROLES, { scopeRef });\n    const blend = resolveBlendMode(mode, tokens.bg);\n\n    const colors = React.useMemo(\n      () =>\n        buildColors(\n          tokens.colors,\n          blend,\n          lightColor ? resolveColor(lightColor) : undefined,\n        ),\n      [tokens.colors, blend, lightColor],\n    );\n    const colorsRef = React.useRef(colors);\n    colorsRef.current = colors;\n\n    const resolved = resolveParams(params);\n    const paramsRef = React.useRef(resolved);\n    paramsRef.current = resolved;\n\n    const lightDir = direction ?? DEFAULT_LIGHT;\n    const lightRef = React.useRef(lightDir);\n    lightRef.current = lightDir;\n\n    const absorbRef = React.useRef(blendUniform(blend));\n    absorbRef.current = blendUniform(blend);\n\n    const canvas = useGlCanvas({\n      fragment: kynnosFragmentShader,\n      stillTime: STILL_TIME,\n      maxDpr: 1.5,\n      uniforms: () =>\n        kynnosUniforms(colorsRef.current, paramsRef.current, lightRef.current),\n      onFrame: (u, frame) => {\n        setKynnosColors(u, colorsRef.current);\n        setKynnosParams(u, paramsRef.current);\n        setKynnosFrame(u, {\n          time: frame.time * speedRef.current,\n          alpha: opacityRef.current,\n          absorb: absorbRef.current,\n          light: lightRef.current,\n        });\n      },\n    });\n\n    const { redraw } = canvas;\n    // biome-ignore lint/correctness/useExhaustiveDependencies: the still frame must repaint when the material or its light changes.\n    React.useEffect(() => {\n      redraw();\n    }, [redraw, colors, lightDir, blend]);\n\n    const on = canvas.active;\n\n    return (\n      <div\n        ref={(node) => {\n          canvas.containerRef.current = node;\n          scopeRef.current = node;\n          if (typeof forwardedRef === \"function\") forwardedRef(node);\n          else if (forwardedRef) forwardedRef.current = node;\n        }}\n        data-material={blend === \"absorptive\" ? \"clay\" : \"metal\"}\n        className={cn(\"relative isolate overflow-hidden\", className)}\n        {...props}\n      >\n        {on ? (\n          <div\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-0 -z-10\"\n          >\n            {/* No mix-blend-mode here, unlike the emissive atmospheres. A multiply\n                canvas can only darken, and a dry ridge has to sit above bg, up\n                to surface-2. kynnos is the ground rather than a stain on it, so it\n                composites normally and the mode flips the lighting model. */}\n            <canvas ref={canvas.canvasRef} className=\"block h-full w-full\" />\n          </div>\n        ) : null}\n        {children}\n      </div>\n    );\n  },\n);\nKynnos.displayName = \"Kynnos\";\n"
    }
  ]
}