{
  "name": "sofa",
  "title": "Sofa",
  "type": "registry:component",
  "description": "A sofa seating one to four: upholstered frame, seat and back cushions in soft shades of the fabric, square, rolled, or slim arms, pegs or a plinth, and throw pillows. Seeded, each choice pinnable.",
  "dependencies": [
    "@react-three/drei@^10.7.7",
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.13.0",
    "three@^0.184.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Sofa.tsx",
      "content": "import { RoundedBox } from '@react-three/drei'\nimport { CuboidCollider, RigidBody } from '@react-three/rapier'\nimport { pick, range, rng, sub, useWorld, type Vec3, type WorldComponentProps } from '@runek/core'\nimport { useMemo } from 'react'\nimport { Color } from 'three'\n\n/** `square` box arms, `rolled` arms with a bolster on top, `slim` thin arms, or `none`. */\nexport type SofaArms = 'square' | 'rolled' | 'slim' | 'none'\n/** One cushion per seat (`split`), or a single long `bench` cushion. */\nexport type SofaCushions = 'split' | 'bench'\n/** Short turned `pegs` at the corners, or a recessed `plinth`. */\nexport type SofaLegs = 'pegs' | 'plinth'\n\nexport interface SofaProps extends WorldComponentProps {\n  /** Seats across, 1 to 4. */\n  seats?: number\n  /** Depth along local Z, in units. Seats face +Z. */\n  depth?: number\n  /** Top of the seat cushions, in units. */\n  seatHeight?: number\n  /** Seeded when unset. */\n  arms?: SofaArms\n  /** Seeded when unset. */\n  cushions?: SofaCushions\n  /** Seeded when unset. */\n  legs?: SofaLegs\n  /** Throw pillows, 0 to 2. Seeded when unset. */\n  pillows?: number\n  /** Upholstery; defaults to the palette's `fabric`. */\n  color?: string\n  /** Throw pillows; defaults to the palette's `accent`. */\n  pillowColor?: string\n  /** Legs; defaults to the palette's `woodDark`. */\n  legColor?: string\n}\n\ninterface Cushion {\n  pos: Vec3\n  size: Vec3\n  tilt: number\n  color: string\n}\n\nconst ARMS: SofaArms[] = ['square', 'rolled', 'slim', 'square']\nconst SEAT = 0.62\nconst BACK_H = 0.82\n\n/**\n * A sofa: an upholstered frame, seat and back cushions, arms, and legs, seating one to four. Arm\n * style, cushions, legs, and throw pillows come from the seed unless you pin them, and every\n * cushion takes a slightly different shade of the fabric so it reads soft. Two cuboid colliders\n * (the seat block and the back). Seats face local +Z, evenly spaced between the arms.\n */\nexport function Sofa({\n  position = [0, 0, 0],\n  rotation = [0, 0, 0],\n  seed = 1,\n  seats = 3,\n  depth = 0.9,\n  seatHeight = 0.44,\n  arms,\n  cushions,\n  legs,\n  pillows,\n  color,\n  pillowColor,\n  legColor,\n}: SofaProps) {\n  const { unit, palette } = useWorld()\n  const fabric = color ?? palette.fabric\n  const legTone = legColor ?? palette.woodDark\n\n  const build = useMemo(() => {\n    const r = rng(sub(seed, 0))\n    const rolled = {\n      arms: pick(r, ARMS),\n      cushions: pick<SofaCushions>(r, ['split', 'split', 'bench']),\n      legs: pick<SofaLegs>(r, ['pegs', 'plinth']),\n      pillows: pick(r, [0, 1, 2, 2]),\n      pillowSide: r() < 0.5 ? -1 : 1,\n    }\n    const n = Math.min(Math.max(Math.round(seats), 1), 4)\n    const style = {\n      arms: arms ?? rolled.arms,\n      cushions: cushions ?? rolled.cushions,\n      legs: legs ?? rolled.legs,\n      pillows: Math.min(Math.max(Math.round(pillows ?? rolled.pillows), 0), 2),\n    }\n    const base = new Color(fabric)\n    const shade = () =>\n      base\n        .clone()\n        .multiplyScalar(range(r, 0.92, 1.06))\n        .getStyle()\n\n    const u = unit\n    const armW = (style.arms === 'none' ? 0 : style.arms === 'slim' ? 0.09 : 0.17) * u\n    const seatW = SEAT * u\n    const W = n * seatW + armW * 2\n    const D = depth * u\n    const legH = (style.legs === 'pegs' ? 0.1 : 0.04) * u\n    const cushionT = 0.13 * u\n    const frameTop = seatHeight * u - cushionT\n    const backT = 0.17 * u\n    const backH = BACK_H * u\n    const armH = (style.arms === 'slim' ? 0.56 : style.arms === 'rolled' ? 0.54 : 0.62) * u\n    const seatD = D - backT - 0.02 * u\n\n    const parts: Cushion[] = []\n    const seatZ = D / 2 - seatD / 2\n    if (style.cushions === 'bench') {\n      parts.push({\n        pos: [0, frameTop + cushionT / 2, seatZ],\n        size: [n * seatW - 0.01 * u, cushionT, seatD],\n        tilt: 0,\n        color: shade(),\n      })\n    } else {\n      for (let i = 0; i < n; i++)\n        parts.push({\n          pos: [-W / 2 + armW + seatW * (i + 0.5), frameTop + cushionT / 2, seatZ],\n          size: [seatW - 0.012 * u, cushionT, seatD],\n          tilt: 0,\n          color: shade(),\n        })\n    }\n    const backCushionH = backH - frameTop - cushionT - 0.04 * u\n    for (let i = 0; i < n; i++)\n      parts.push({\n        pos: [\n          -W / 2 + armW + seatW * (i + 0.5),\n          frameTop + cushionT + backCushionH / 2,\n          -D / 2 + backT + 0.07 * u,\n        ],\n        size: [seatW - 0.015 * u, backCushionH, 0.15 * u],\n        tilt: -0.14,\n        color: shade(),\n      })\n    for (let i = 0; i < style.pillows; i++) {\n      const side = i === 0 ? rolled.pillowSide : -rolled.pillowSide\n      parts.push({\n        pos: [\n          side * (W / 2 - armW - 0.2 * u),\n          frameTop + cushionT + 0.17 * u,\n          -D / 2 + backT + 0.2 * u,\n        ],\n        size: [0.36 * u, 0.34 * u, 0.12 * u],\n        tilt: -0.3,\n        color: pillowColor ?? palette.accent,\n      })\n    }\n\n    return { style, W, D, legH, frameTop, backT, backH, armW, armH, parts }\n  }, [\n    seed,\n    seats,\n    depth,\n    seatHeight,\n    arms,\n    cushions,\n    legs,\n    pillows,\n    fabric,\n    pillowColor,\n    palette,\n    unit,\n  ])\n\n  const { style, W, D, legH, frameTop, backT, backH, armW, armH, parts } = build\n  const frameH = frameTop - legH\n  const u = unit\n\n  return (\n    <RigidBody type=\"fixed\" colliders={false} position={position} rotation={rotation}>\n      <CuboidCollider\n        args={[W / 2, seatHeight * u * 0.5, D / 2]}\n        position={[0, seatHeight * u * 0.5, 0]}\n      />\n      <CuboidCollider\n        args={[W / 2, (backH - seatHeight * u) / 2, backT / 2]}\n        position={[0, (backH + seatHeight * u) / 2, -D / 2 + backT / 2]}\n      />\n\n      {/* frame: the base under the seat, and the back */}\n      <RoundedBox\n        args={[W, frameH, D]}\n        radius={0.03 * u}\n        smoothness={2}\n        position={[0, legH + frameH / 2, 0]}\n        castShadow\n        receiveShadow\n      >\n        <meshStandardMaterial color={fabric} roughness={0.95} />\n      </RoundedBox>\n      <RoundedBox\n        args={[W, backH - legH, backT]}\n        radius={0.04 * u}\n        smoothness={2}\n        position={[0, legH + (backH - legH) / 2, -D / 2 + backT / 2]}\n        castShadow\n        receiveShadow\n      >\n        <meshStandardMaterial color={fabric} roughness={0.95} />\n      </RoundedBox>\n\n      {parts.map((c) => (\n        <RoundedBox\n          key={`${c.pos[0].toFixed(3)}:${c.pos[1].toFixed(3)}:${c.pos[2].toFixed(3)}`}\n          args={c.size}\n          radius={Math.min(c.size[1], c.size[2]) * 0.3}\n          smoothness={3}\n          position={c.pos}\n          rotation={[c.tilt, 0, 0]}\n          castShadow\n          receiveShadow\n        >\n          <meshStandardMaterial color={c.color} roughness={0.95} />\n        </RoundedBox>\n      ))}\n\n      {style.arms !== 'none' &&\n        [-1, 1].map((s) => (\n          <group key={`arm${s}`} position={[s * (W / 2 - armW / 2), 0, 0]}>\n            <RoundedBox\n              args={[armW, armH - legH, D]}\n              radius={Math.min(0.04 * u, armW * 0.45)}\n              smoothness={2}\n              position={[0, legH + (armH - legH) / 2, 0]}\n              castShadow\n              receiveShadow\n            >\n              <meshStandardMaterial color={fabric} roughness={0.95} />\n            </RoundedBox>\n            {style.arms === 'rolled' && (\n              <mesh\n                position={[s * 0.02 * u, armH, 0.01 * u]}\n                rotation={[Math.PI / 2, 0, 0]}\n                castShadow\n              >\n                <cylinderGeometry args={[armW * 0.62, armW * 0.62, D, 18]} />\n                <meshStandardMaterial color={fabric} roughness={0.95} />\n              </mesh>\n            )}\n          </group>\n        ))}\n\n      {style.legs === 'pegs' ? (\n        [-1, 1].flatMap((sx) =>\n          [-1, 1].map((sz) => (\n            <mesh\n              key={`leg${sx}${sz}`}\n              position={[sx * (W / 2 - 0.06 * u), legH / 2, sz * (D / 2 - 0.06 * u)]}\n              castShadow\n            >\n              <cylinderGeometry args={[0.022 * u, 0.016 * u, legH, 10]} />\n              <meshStandardMaterial color={legTone} roughness={0.6} />\n            </mesh>\n          )),\n        )\n      ) : (\n        <mesh position={[0, legH / 2, 0]} receiveShadow>\n          <boxGeometry args={[W - 0.08 * u, legH, D - 0.08 * u]} />\n          <meshStandardMaterial color={legTone} roughness={0.8} />\n        </mesh>\n      )}\n    </RigidBody>\n  )\n}\n\nSofa.groundSitting = true\n",
      "type": "registry:component"
    }
  ]
}
