{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lifeboat-davit",
  "type": "registry:component",
  "title": "Lifeboat (TEMPSC) and gravity davit",
  "description": "Totally enclosed motor-propelled survival craft in orange, sized from its certified capacity (about 6–11 m), with lofted hull and canopy, gunwale fender, coxswain cupola, windows, boarding doors, hatches, grab rails, keel, rudder and propeller nozzle, and on-load release hooks with the fall rings seated in upturned tongues. It hangs stowed in two gravity davits (deck frame, inclined track, cradle arm with head sheave, falls and fall blocks), with a winch and brake lever and a cantilevered embarkation platform. The boat lies along X, outboard at +Z; the deck edge is at z ≈ 0.25 and the hull hangs below deck level, as it does on a real platform.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "lifting/lifeboat-davit.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/lifting/lifeboat-davit.tsx",
      "content": "// Totally enclosed lifeboat (TEMPSC, SOLAS / LSA Code IV) stowed in twin gravity davits with falls, winch and embarkation platform.\nimport * as THREE from 'three'\nimport { Kit, type MatKey, type V3 } from '../lib/kit'\nimport { KitMesh, useKit, type GroupProps } from '../lib/mesh'\nimport { paint } from '../lib/materials'\nimport { grating, handrail, member, tagPlate } from '../lib/parts'\nimport { signMaterial, textMaterial } from '../lib/textures'\nimport { bar, boxMM, ropeMaterial } from './_rigging'\n\nexport interface LifeboatDavitProps {\n  /** certified persons */\n  capacity?: number\n  /** boat number painted on the canopy */\n  tag?: string\n  color?: string\n}\n\nexport const defaults = {\n  capacity: 50,\n  tag: 'LB-1',\n  color: '#f26a1b',\n}\n\nconst clamp = (v: number, a: number, b: number) => Math.max(a, Math.min(b, v))\n\nexport function LifeboatDavit({ capacity = defaults.capacity, tag = defaults.tag, color = defaults.color, ...group }: LifeboatDavitProps & GroupProps) {\n  const data = useKit((k) => build(k, capacity, tag, color), [capacity, tag, color])\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n    </group>\n  )\n}\n\nexport default LifeboatDavit\n\nfunction boatDims(capacity: number) {\n  const Lb = clamp(5.5 + capacity * 0.045, 6, 11)\n  const B = clamp(0.33 * Lb + 0.3, 2.3, 3.8)\n  const Dh = 0.42 * B\n  const Hc = 0.6 * B\n  const yK = 0.25 - Dh\n  const zb = 0.75 + B / 2\n  return { Lb, B, Dh, Hc, yK, yG: yK + Dh, zb, xh: Lb / 2 - 0.95 }\n}\ntype BD = ReturnType<typeof boatDims>\n\n// half-beam, keel depth and sheer along the boat (u: 0 stern … 1 bow)\nfunction station(b: BD, u: number) {\n  const t = u < 0.45 ? (0.45 - u) / 0.45 : (u - 0.45) / 0.55\n  const e = u < 0.45 ? 3.2 : 1.9\n  const w = (b.B / 2) * Math.pow(Math.max(0, 1 - Math.pow(t, e)), 0.55)\n  const sheer = u > 0.45 ? 0.3 * t * t : 0.1 * t * t\n  const yG = b.yG + sheer\n  const D = b.Dh * (1 - 0.45 * Math.pow(t, 2.5)) + sheer\n  const Hc = b.Hc * Math.pow(Math.max(0, 1 - Math.pow(t, 3.2)), 0.5)\n  return { x: -b.Lb / 2 + u * b.Lb, w, yG, D, Hc }\n}\n\nconst se = (a: number, n: number) => Math.sign(a) * Math.pow(Math.abs(a), 2 / n)\n\n// Loft a closed hull or canopy surface over stations; ring(φ) gives (z, y) per station.\nfunction loft(k: Kit, mat: MatKey, b: BD, ring: (s: ReturnType<typeof station>, f: number) => [number, number], N = 48, M = 28) {\n  const pos: number[] = []\n  const idx: number[] = []\n  for (let i = 0; i <= N; i++) {\n    const u = 0.5 - 0.5 * Math.cos((i / N) * Math.PI)\n    const s = station(b, u)\n    for (let j = 0; j <= M; j++) {\n      const [z, y] = ring(s, j / M)\n      pos.push(s.x, y, z)\n    }\n  }\n  for (let i = 0; i < N; i++)\n    for (let j = 0; j < M; j++) {\n      const a = i * (M + 1) + j\n      const c = a + M + 1\n      idx.push(a, c, a + 1, c, c + 1, a + 1)\n    }\n  const g = new THREE.BufferGeometry()\n  g.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3))\n  g.setIndex(idx)\n  g.computeVertexNormals()\n  k.add(mat, g)\n}\n\nfunction build(k: Kit, capacity: number, tag: string, color: string) {\n  const b = boatDims(capacity)\n  const hull = paint(color, { weather: 0.4 })\n  const canopy = paint(color, { weather: 0.3, roughness: 0.4 })\n  const white = paint('#eeeeea', { weather: 0.3 })\n  const dav = paint('#d9dbd6', { weather: 1 })\n  const dark: MatKey = 'castIron'\n  const zb = b.zb\n\n  // ---- boat: hull (superelliptic sections, keel rocker, sheer) and canopy (flattened dome)\n  k.with(new THREE.Matrix4().makeTranslation(0, 0, zb), () => {\n    loft(k, hull, b, (s, f) => {\n      const p = -Math.PI / 2 + f * Math.PI\n      return [s.w * se(Math.sin(p), 2.6), s.yG - s.D * Math.pow(Math.cos(p), 2 / 2.6)]\n    })\n    loft(k, canopy, b, (s, f) => {\n      const p = f * Math.PI\n      return [s.w * 0.97 * se(Math.cos(p), 2.4), s.yG + s.Hc * Math.pow(Math.sin(p), 2 / 2.4)]\n    })\n    // gunwale fender, boot-top band, keel, rudder, propeller nozzle\n    for (const sz of [-1, 1]) {\n      const pts: V3[] = []\n      for (let i = 2; i <= 38; i++) {\n        const s = station(b, i / 40)\n        pts.push([s.x, s.yG - 0.04, sz * (s.w + 0.03)])\n      }\n      k.spline('rubberBlack', pts, 0.06, 8, 80)\n      const lo: V3[] = []\n      for (let i = 3; i <= 37; i++) {\n        const s = station(b, i / 40)\n        lo.push([s.x, s.yG - s.D * 0.55, sz * s.w * 0.93])\n      }\n      k.spline(white, lo, 0.035, 6, 80)\n    }\n    const s0 = station(b, 0.08)\n    const keel: V3[] = []\n    for (let i = 2; i <= 37; i++) {\n      const s = station(b, i / 40)\n      keel.push([s.x, s.yG - s.D - 0.02, 0])\n    }\n    k.spline(hull, keel, 0.07, 8, 60)\n    boxMM(k, white, [-b.Lb / 2 - 0.12, b.yK - 0.05, -0.025], [-b.Lb / 2 + 0.25, s0.yG - 0.35, 0.025])\n    k.lathe(dark, [[0.26, 0], [0.3, 0.02], [0.3, 0.28], [0.27, 0.3], [0.23, 0.3], [0.23, 0]], [-b.Lb / 2 + 0.55, b.yK + 0.3, 0], '-x', 24)\n    // coxswain cupola aft, windows, hatches, boarding doors, release hooks, grab rail, markings\n    const cs = station(b, 0.3)\n    const yTop = cs.yG + cs.Hc\n    k.rbox(canopy, [1.0, 0.55, 0.95], 0.18, [cs.x, yTop + 0.12, 0])\n    k.rbox('glass', [1.02, 0.22, 0.97], 0.1, [cs.x, yTop + 0.25, 0])\n    k.cyl(white, 0.05, 0.45, [cs.x - 0.25, yTop + 0.6, 0], 'y', 8)\n    k.sphere('lampLens', 0.06, [cs.x - 0.25, yTop + 0.85, 0], undefined, 10)\n    for (const u of [0.62, 0.84]) {\n      const s = station(b, u)\n      k.rbox(canopy, [0.75, 0.1, 0.65], 0.04, [s.x, s.yG + s.Hc + 0.02, 0])\n      k.rbox('rubberBlack', [0.78, 0.03, 0.68], 0.012, [s.x, s.yG + s.Hc - 0.02, 0])\n    }\n    for (const sz of [-1, 1]) {\n      // surface frame on the canopy (ph: 0 gunwale … π/2 crown) or hull side (p: 0 keel … π/2 gunwale)\n      const frame = (pt: (a: number) => [number, number], a: number, x: number) => {\n        const [z1, y1] = pt(a - 0.02)\n        const [z2, y2] = pt(a + 0.02)\n        const [z, y] = pt(a)\n        const t = new THREE.Vector3(0, y2 - y1, sz * (z2 - z1)).normalize()\n        const n = new THREE.Vector3(0, -t.z * sz, t.y * sz).multiplyScalar(sz).normalize()\n        if (n.z * sz < 0) n.negate()\n        return { p: new THREE.Vector3(x, y, sz * z), n, t }\n      }\n      const cpt = (s: ReturnType<typeof station>) => (ph: number): [number, number] => [s.w * 0.97 * se(Math.cos(ph), 2.4), s.yG + s.Hc * Math.pow(Math.max(0, Math.sin(ph)), 2 / 2.4)]\n      const hpt = (s: ReturnType<typeof station>) => (p: number): [number, number] => [s.w * se(Math.sin(p), 2.6), s.yG - s.D * Math.pow(Math.max(0, Math.cos(p)), 2 / 2.6)]\n      const onFace = (mat: MatKey, f: ReturnType<typeof frame>, w: number, h: number, off: number) => {\n        const q = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(new THREE.Vector3().crossVectors(f.t, f.n), f.t, f.n))\n        k.add(mat, new THREE.PlaneGeometry(w, h), new THREE.Matrix4().compose(f.p.clone().addScaledVector(f.n, off), q, new THREE.Vector3(1, 1, 1)))\n      }\n      for (let i = 0; i < 7; i++) {\n        const s = station(b, 0.2 + i * 0.1)\n        const f = frame(cpt(s), Math.PI * 0.3, s.x)\n        k.add('glass', new THREE.CircleGeometry(0.11, 18), new THREE.Matrix4().compose(f.p.clone().addScaledVector(f.n, 0.012), new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), f.n), new THREE.Vector3(1, 1, 1)))\n        k.torus('rubberBlack', 0.115, 0.012, f.p.clone().addScaledVector(f.n, 0.008).toArray() as V3, f.n.toArray() as V3, 6, 20)\n      }\n      const sd = station(b, 0.5)\n      const ph0 = Math.asin(Math.pow(0.05, 1.2))\n      const ph1 = Math.asin(Math.pow(0.62, 1.2))\n      const [za, ya] = cpt(sd)(ph0)\n      const [ze, ye] = cpt(sd)(ph1)\n      const fd = frame(cpt(sd), (ph0 + ph1) / 2, sd.x)\n      const dh = Math.hypot(ze - za, ye - ya)\n      onFace('rubberBlack', fd, 0.98, dh + 0.06, 0.03)\n      onFace(canopy, fd, 0.9, dh, 0.034)\n      const fh = frame(cpt(sd), (ph0 + ph1) / 2 - 0.05, sd.x + 0.36)\n      k.box(dark, [0.04, 0.12, 0.05], fh.p.clone().addScaledVector(fh.n, 0.06).toArray() as V3)\n      const st = station(b, 0.47)\n      const pText = Math.acos(Math.pow(0.32 / st.D, 1.3))\n      onFace(textMaterial(`${tag}   ${capacity} P`, '#ffffff', '#111111', 768, 128), { ...frame(hpt(st), pText, st.x), t: new THREE.Vector3(0, 1, 0), n: new THREE.Vector3(0, 0, sz) }, 1.1, 0.18, 0.03)\n      for (const u of [0.15, 0.85]) {\n        const s = station(b, u)\n        onFace(white, frame(cpt(s), Math.PI * 0.36, s.x), 0.3, 0.08, 0.008)\n      }\n      const rail: V3[] = []\n      for (let i = 0; i <= 12; i++) {\n        const s = station(b, 0.18 + i * 0.055)\n        const ph = Math.PI * 0.42\n        rail.push([s.x, s.yG + s.Hc * Math.pow(Math.sin(ph), 2 / 2.4) + 0.06, sz * s.w * 0.97 * se(Math.cos(ph), 2.4)])\n      }\n      k.tube('stainless', rail, 0.016, 6, 0.2)\n    }\n    for (const sx of [-1, 1]) {\n      const s = station(b, 0.5 + (sx * b.xh) / b.Lb)\n      const yt = s.yG + s.Hc\n      // on-load release hook: housing with hydrostatic interlock cable, upturned tongue holding the fall ring\n      boxMM(k, dark, [sx * b.xh - 0.14, yt - 0.05, -0.1], [sx * b.xh + 0.14, yt + 0.06, 0.1])\n      boxMM(k, dark, [sx * b.xh - 0.05, yt + 0.06, -0.035], [sx * b.xh + 0.05, yt + 0.3, 0.035])\n      k.add(dark, new THREE.TorusGeometry(0.065, 0.022, 8, 20, (3 * Math.PI) / 2).rotateZ(-sx * Math.PI / 2 - (sx > 0 ? Math.PI / 2 : 0)), new THREE.Matrix4().makeTranslation(sx * b.xh, yt + 0.375, 0))\n      k.spline('cable', [[sx * b.xh, yt + 0.04, 0.1], [sx * b.xh * 0.8, yt + 0.02, 0.2], [sx * b.xh * 0.6, yt, 0.25]], 0.008, 6, 16)\n    }\n  })\n\n  // ---- davits: deck frame, inclined track, cradle arm with head sheave; falls to the release hooks\n  const rope = ropeMaterial()\n  const capTop = (sx: number) => {\n    const s = station(b, 0.5 + (sx * b.xh) / b.Lb)\n    return s.yG + s.Hc + 0.27\n  }\n  const yHead = Math.max(capTop(-1), capTop(1)) + 1.1\n  const xw = -b.xh + 0.85\n  for (const sx of [-1, 1]) {\n    const x = sx * b.xh\n    const zP = 0.15\n    const yTrack = yHead - 0.6\n    member(k, [x, 0.13, -2.7], [x, 0.13, zP + 0.15], 'HEB 260', dav)\n    member(k, [x, 0.26, zP], [x, yTrack - 0.2, zP], 'HEB 260', dav, [0, 0, 1])\n    bar(k, dav, [x, 0.26, -2.45], [x, yTrack, zP], 0.26, 0.24, [0, 1, 0])\n    bar(k, dav, [x, 0.26, -1.2], [x, yTrack * 0.55, zP], 0.12, 0.12)\n    boxMM(k, dav, [x - 0.2, 0, -2.75], [x + 0.2, 0.025, zP + 0.3])\n    for (const z of [-2.6, -1.2, zP + 0.15]) for (const dx of [-0.14, 0.14]) k.hex('nut', 0.04, 0.025, [x + dx, 0.04, z])\n    // cradle arm resting on the track top: twin plates from the track to the head over the boat\n    const head: V3 = [x, yHead, zb]\n    const heel: V3 = [x, yTrack - 0.55, -0.55]\n    for (const dx of [-0.13, 0.13]) {\n      bar(k, dav, [x + dx, heel[1], heel[2]], [x + dx, head[1], head[2] - 0.15], 0.03, 0.42)\n      bar(k, dav, [x + dx, heel[1], heel[2]], [x + dx, yTrack + 0.45, zP + 0.2], 0.03, 0.3)\n    }\n    bar(k, dav, [x, yTrack + 0.45, zP + 0.2], [x, head[1] - 0.25, head[2] - 0.6], 0.29, 0.06)\n    for (const z of [heel[2] + 0.1, zP - 0.15]) k.cyl(dark, 0.11, 0.3, [x, (z === heel[2] + 0.1 ? heel[1] : yTrack + 0.05) - 0.02, z], 'x', 16)\n    k.cyl(dark, 0.22, 0.12, head, 'x', 22)\n    k.cyl(dark, 0.04, 0.34, head, 'x', 10)\n    // fall: head sheave → fall block → release hook; wire back along the arm to the winch\n    const hookTop = capTop(sx) - 0.02\n    const blk: V3 = [x, hookTop + 0.45, zb]\n    k.rod(rope, [x, yHead, zb + 0.22], [x, blk[1] + 0.12, zb + 0.08], 0.014, 6)\n    k.rod(rope, [x, yHead, zb - 0.22], [x, blk[1] + 0.12, zb - 0.08], 0.014, 6)\n    k.rbox(dark, [0.12, 0.3, 0.26], 0.03, blk)\n    k.torus('galvanised', 0.07, 0.02, [x, hookTop + 0.15, zb], 'x', 8, 16)\n    k.rod('galvanised', [x, blk[1] - 0.15, zb], [x, hookTop + 0.22, zb], 0.02, 8)\n    const guide: V3 = [x, heel[1] + 0.32, heel[2] + 0.1]\n    k.rod(rope, [x, yHead + 0.22, zb - 0.05], guide, 0.012, 6)\n    // both wires run to the winch beside the aft davit; the forward one overhead along the cross-tie\n    if (sx > 0) k.rod(rope, guide, [-x, guide[1], guide[2]], 0.012, 6)\n    else k.rod(dav, [x, guide[1] - 0.2, guide[2]], [-x, guide[1] - 0.2, guide[2]], 0.05, 10)\n    k.rod(rope, [-b.xh, guide[1], guide[2] + (sx > 0 ? 0.04 : -0.04)], [xw + sx * 0.2, 1.05, -2.05], 0.012, 6)\n    k.cyl(dark, 0.12, 0.08, [x, heel[1] + 0.2, heel[2] + 0.1], 'x', 14)\n    // station sign\n    k.plane(signMaterial('muster', `LIFEBOAT ${tag}`), 0.26, 0.19, [x, 1.8, zP + 0.132])\n    if (tag) tagPlate(k, `${tag}-D${sx > 0 ? 'F' : 'A'}`, [x, 1.55, zP + 0.131], 'z', 0.22)\n  }\n  // ---- davit winch with brake lever, crank and motor\n  k.with(new THREE.Matrix4().makeTranslation(xw, 0, 0), () => {\n  boxMM(k, dav, [-0.7, 0, -2.4], [0.7, 0.12, -1.7])\n  for (const sx of [-1, 1]) boxMM(k, dav, [sx * 0.42 - 0.03, 0.12, -2.35], [sx * 0.42 + 0.03, 1.0, -1.75])\n  k.cyl(dark, 0.22, 0.78, [0, 0.8, -2.05], 'x', 24)\n  k.cyl(rope, 0.25, 0.7, [0, 0.8, -2.05], 'x', 24)\n  for (const sx of [-1, 1]) k.cyl(dark, 0.32, 0.03, [sx * 0.36, 0.8, -2.05], 'x', 28)\n  k.cyl('motorPaint', 0.16, 0.42, [-0.68, 0.8, -2.05], 'x', 20)\n  k.rbox('machineryGreen', [0.25, 0.3, 0.3], 0.03, [0.6, 0.8, -2.05])\n  bar(k, 'actuatorRed', [0.72, 0.8, -2.05], [0.75, 1.55, -2.5], 0.04, 0.04)\n  k.sphere('actuatorRed', 0.09, [0.75, 1.55, -2.5], undefined, 12)\n  bar(k, dark, [0.74, 0.8, -1.9], [0.78, 0.8, -1.6], 0.03, 0.03)\n  })\n\n  // ---- embarkation platform: cantilevered grating from the deck edge to the boat door, rails with a gate gap\n  const pz0 = 0.25\n  const pz1 = zb - b.B / 2 - 0.1\n  const px = Math.min(1.1, b.xh - 0.4)\n  grating(k, [0, 0, (pz0 + pz1) / 2], 2 * px, pz1 - pz0)\n  for (const sx of [-1, 1]) member(k, [sx * (px - 0.05), -0.13, pz0 - 0.4], [sx * (px - 0.05), -0.13, pz1], 'UPN 200', dav, [0, 1, 0])\n  member(k, [-px, -0.13, pz1 - 0.04], [px, -0.13, pz1 - 0.04], 'UPN 200', dav)\n  for (const sx of [-1, 1]) handrail(k, [[sx * (px - 0.02), 0, pz0], [sx * (px - 0.02), 0, pz1 - 0.02]])\n  handrail(k, [[-b.xh + 0.3, 0, pz0 - 0.02], [-0.55, 0, pz0 - 0.02]])\n  handrail(k, [[0.55, 0, pz0 - 0.02], [b.xh - 0.3, 0, pz0 - 0.02]])\n  for (const y of [0.55, 1.05]) k.rod('actuatorYellow', [-0.55, y, pz0 - 0.02], [0.55, y - 0.08, pz0 - 0.02], 0.008, 6)\n}\n"
    },
    {
      "path": "lifting/_rigging.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/lifting/_rigging.tsx",
      "content": "// Shared rigging for the lifting category: wire rope, shackles, master links, 4-leg sling sets, hooks, hazard panels and oriented bars.\nimport * as THREE from 'three'\nimport { useMemo } from 'react'\nimport { Kit, mat4, uvScale, type MatKey, type V3 } from '../lib/kit'\nimport { paint } from '../lib/materials'\nimport { liftingLug } from '../lib/parts'\nimport { hazardStripeMaterial } from '../lib/textures'\n\nconst UNIT_ROPE = new THREE.CylinderGeometry(1, 1, 1, 8, 1, false)\n\n/** Galvanised wire rope (dark, oily). */\nexport const ropeMaterial = () => paint('#3a3d40', { metalness: 0.75, roughness: 0.42, clearcoat: 0, weather: 0 })\n/** Offshore crane / lifting-gear yellow (RAL 1003). */\nexport const craneYellow = () => paint('#eab308', { weather: 0.9 })\n\n/** Axis-aligned box from min to max corners. */\nexport function boxMM(k: Kit, mat: MatKey, a: V3, b: V3) {\n  k.box(mat, [Math.abs(b[0] - a[0]), Math.abs(b[1] - a[1]), Math.abs(b[2] - a[2])], [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2])\n}\n\n/** Rectangular bar from a to b, w across (perpendicular to up), h along `up`. */\nexport function bar(k: Kit, mat: MatKey, a: V3, b: V3, w: number, h: number, up: V3 = [0, 1, 0]) {\n  const A = new THREE.Vector3(...a)\n  const d = new THREE.Vector3(...b).sub(A)\n  const L = d.length()\n  if (L < 1e-6) return\n  const x = d.normalize()\n  let u = new THREE.Vector3(...up).normalize()\n  if (Math.abs(u.dot(x)) > 0.99) u = Math.abs(x.y) > 0.9 ? new THREE.Vector3(1, 0, 0) : new THREE.Vector3(0, 1, 0)\n  const z = new THREE.Vector3().crossVectors(x, u).normalize()\n  const y = new THREE.Vector3().crossVectors(z, x).normalize()\n  const m = new THREE.Matrix4().makeBasis(x, y, z).setPosition(A.addScaledVector(x, L / 2))\n  k.add(mat, new THREE.BoxGeometry(L, h, w), m)\n}\n\n/** Black/yellow hazard chevron panel of w × h, centred at pos, facing `normal`, laid 4 mm proud. */\nexport function hazardPanel(k: Kit, pos: V3, w: number, h: number, normal: V3, stripe = 0.3) {\n  const n = new THREE.Vector3(...normal).normalize()\n  const q = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), n)\n  const g = new THREE.PlaneGeometry(w, h)\n  uvScale(g, w / (stripe * 2), h / (stripe * 2))\n  const p = new THREE.Vector3(...pos).addScaledVector(n, 0.004)\n  k.add(hazardStripeMaterial(), g, mat4(p.toArray() as V3, q))\n}\n\n/** Bow shackle: bow and pin in the plane of `along` and +Y, pin centre at pos (pairs with a padeye plate across `along`). s = jaw width. */\nexport function shackle(k: Kit, pos: V3, s = 0.06, along: 'x' | 'z' = 'x', mat: MatKey = 'galvanised') {\n  const r = s * 0.22\n  const q = along === 'x' ? new THREE.Quaternion() : new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2)\n  const m = (p: V3) => new THREE.Matrix4().compose(new THREE.Vector3(...p).applyQuaternion(q).add(new THREE.Vector3(...pos)), q, new THREE.Vector3(1, 1, 1))\n  const bow = new THREE.TorusGeometry(s / 2, r, 6, 16, Math.PI)\n  k.add(mat, bow, m([0, s * 0.9, 0]))\n  for (const sx of [-1, 1]) k.add(mat, new THREE.CylinderGeometry(r, r, s * 0.9, 6), m([sx * (s / 2), s * 0.45, 0]))\n  k.add('nut', new THREE.CylinderGeometry(r * 0.95, r * 0.95, s + r * 6, 8).rotateZ(Math.PI / 2), m([0, 0, 0]))\n  for (const sx of [-1, 1]) k.add(mat, new THREE.SphereGeometry(r * 1.25, 8, 6), m([sx * (s / 2), 0, 0]))\n}\n\n/** Top of a shackle's bow (where a sling eye sits). */\nexport const shackleTop = (pos: V3, s = 0.06): V3 => [pos[0], pos[1] + s * 1.4, pos[2]]\n\n/** Oblong master link, long axis vertical, centre at pos. */\nexport function masterLink(k: Kit, pos: V3, s = 0.25, mat: MatKey = 'galvanised') {\n  const g = new THREE.TorusGeometry(s * 0.32, s * 0.08, 8, 24)\n  g.scale(1, 1.55, 1)\n  k.add(mat, g, mat4(pos))\n}\n\n/** Four-leg sling set: legs from each attachment point to a master link at `apex`, with thimble eyes and ferrules. */\nexport function slingSet(k: Kit, eyes: V3[], apex: V3, s = 0.25, rope = 0.012) {\n  const rm = ropeMaterial()\n  const link = s * 0.32 * 1.55\n  const bottom: V3 = [apex[0], apex[1] - link + s * 0.04, apex[2]]\n  masterLink(k, apex, s)\n  // intermediate links (one per pair of legs)\n  for (const sx of [-1, 1]) {\n    const g = new THREE.TorusGeometry(s * 0.16, s * 0.05, 6, 18)\n    g.scale(1, 1.4, 1)\n    k.add('galvanised', g, mat4([bottom[0] + sx * s * 0.12, bottom[1] - s * 0.12, bottom[2]], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2)))\n  }\n  const hang: V3 = [bottom[0], bottom[1] - s * 0.3, bottom[2]]\n  for (const e of eyes) {\n    const top: V3 = [hang[0] + Math.sign(e[0] - hang[0]) * s * 0.12, hang[1], hang[2] + Math.sign(e[2] - hang[2]) * s * 0.02]\n    const E = new THREE.Vector3(...e)\n    const T = new THREE.Vector3(...top)\n    const dir = T.clone().sub(E).normalize()\n    // thimble eye at the shackle, ferrule a little up the leg\n    const eyeC = E.clone().addScaledVector(dir, 0.06)\n    k.rod(rm, eyeC.toArray() as V3, T.toArray() as V3, rope, 6)\n    k.rod('aluminium', eyeC.clone().addScaledVector(dir, 0.12).toArray() as V3, eyeC.clone().addScaledVector(dir, 0.22).toArray() as V3, rope * 1.9, 8)\n    k.torus('galvanised', 0.035, rope * 0.9, eyeC.toArray() as V3, new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), new THREE.Vector3(-dir.z, 0, dir.x).normalize()), 6, 14)\n    k.rod(rm, T.toArray() as V3, [top[0], top[1] + 0.04, top[2]], rope, 6)\n  }\n}\n\n/** Forged single hook (DIN 15401 shape): shank up from pos, bowl below, latch. size = bowl inner radius. */\nexport function hook(k: Kit, pos: V3, size = 0.12, mat: MatKey = 'castIron', latch: MatKey = 'safetyYellow') {\n  const [x, y, z] = pos\n  const r = size\n  const t = size * 0.42\n  k.cyl(mat, t * 0.55, r * 1.3, [x, y + r * 0.65, z], 'y', 12)\n  k.hex('nut', t * 1.6, t * 0.6, [x, y + r * 1.35, z])\n  // bowl: 300° arc of a tapered section, open towards +X\n  const arc = new THREE.TorusGeometry(r, t * 0.6, 10, 24, (5 * Math.PI) / 3)\n  arc.rotateZ(Math.PI / 2 + Math.PI / 6)\n  k.add(mat, arc, mat4([x, y - r * 1.0, z], undefined, [1, 1.05, 1.3]))\n  k.sphere(mat, t * 0.55, [x + r * Math.cos(Math.PI / 6 + Math.PI / 2 + (5 * Math.PI) / 3), y - r + r * Math.sin(Math.PI / 6 + Math.PI / 2 + (5 * Math.PI) / 3), z], undefined, 10)\n  k.rod(latch, [x + t * 0.5, y + r * 0.1, z], [x + r * 0.95, y - r * 0.45, z], t * 0.12, 6)\n}\n\n/** Stretched-rope mesh between two points (moves with props without rebuilding geometry). */\nexport function Rope({ a, b, r = 0.022, material }: { a: V3; b: V3; r?: number; material?: THREE.Material }) {\n  const t = useMemo(() => {\n    const A = new THREE.Vector3(...a)\n    const d = new THREE.Vector3(...b).sub(A)\n    const L = Math.max(d.length(), 1e-4)\n    const q = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), d.normalize())\n    return { p: A.addScaledVector(d, L / 2), q, s: new THREE.Vector3(r, L, r) }\n  }, [a[0], a[1], a[2], b[0], b[1], b[2], r]) // eslint-disable-line react-hooks/exhaustive-deps\n  const m = material ?? ropeMaterial()\n  return <mesh geometry={UNIT_ROPE} material={m} position={t.p} quaternion={t.q} scale={t.s} castShadow />\n}\n\n/** Padeye plate (across `across`) with a bow shackle through its hole; returns the bow top for the sling eye. */\nexport function padeyeShackle(k: Kit, pos: V3, across: 'x' | 'z' = 'x', s = 1, mat: MatKey = 'structSteel'): V3 {\n  liftingLug(k, pos, across === 'x' ? 'z' : 'x', s, mat)\n  const pin: V3 = [pos[0], pos[1] + 0.12 * s * 0.65, pos[2]]\n  shackle(k, pin, 0.065 * s, across)\n  return shackleTop(pin, 0.065 * s)\n}\n"
    }
  ],
  "categories": [
    "lifting"
  ],
  "meta": {
    "export": "LifeboatDavit",
    "anchor": "base",
    "standards": [
      "SOLAS III",
      "IMO LSA Code IV/4.6 and VI/6.1",
      "IMO MSC.81(70)",
      "NORSOK R-002"
    ]
  }
}