{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shipping-container",
  "type": "registry:component",
  "title": "ISO shipping container",
  "description": "ISO 668 series 1 freight container in 10, 20, 40 ft or 40 ft high-cube: trapezoidal corrugated side, end and roof panels, ISO 1161 corner castings with apertures, corner posts and rails, a door end with two corrugated leaves, four locking bars with cam keepers, handles and hinges, CSC plate and ISO 6346 marking. 10 and 20 ft units get forklift pockets; side walls carry labyrinth vents and the leaves have EPDM gaskets. doorsOpen swings the leaves back 250° to show the plywood-floored interior; the CSC plate is on the left leaf. The optional offshore lifting set adds padeyes, shackles and a 4-leg sling to a master link. Length runs along X with the doors at +X.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "lifting/shipping-container.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/lifting/shipping-container.tsx",
      "content": "// ISO 668 series 1 freight container (10/20/40 ft, 40 ft high-cube): corrugated steel walls, ISO 1161 corner castings, door end with locking bars, forklift pockets, optional offshore lifting set.\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 { tagPlate } from '../lib/parts'\nimport { textMaterial } from '../lib/textures'\nimport { corrugated } from './_corrugated'\nimport { boxMM, padeyeShackle, slingSet } from './_rigging'\n\nexport type ContainerSize = '10ft' | '20ft' | '40ft' | '40ft-hc'\n\nexport interface ShippingContainerProps {\n  size?: ContainerSize\n  color?: string\n  /** owner code and serial (ISO 6346) painted on the doors */\n  marking?: string\n  /** offshore padeyes, shackles and 4-leg sling set to a master link */\n  liftingSet?: boolean\n  /** door leaves swung fully open */\n  doorsOpen?: boolean\n  /** equipment tag on a stainless plate by the CSC plate */\n  tag?: string\n}\n\nexport const defaults = {\n  size: '20ft' as ContainerSize,\n  color: '#c4561c',\n  marking: 'DTKU 220516 4',\n  liftingSet: false,\n  doorsOpen: false,\n  tag: 'CT-101',\n}\n\n// ISO 668 external dimensions (m) and fork-pocket centres (ISO 1496-1), 0 = none\nexport const ISO668: Record<ContainerSize, { L: number; W: number; H: number; fork: number }> = {\n  '10ft': { L: 2.991, W: 2.438, H: 2.591, fork: 1.2 },\n  '20ft': { L: 6.058, W: 2.438, H: 2.591, fork: 2.05 },\n  '40ft': { L: 12.192, W: 2.438, H: 2.591, fork: 0 },\n  '40ft-hc': { L: 12.192, W: 2.438, H: 2.896, fork: 0 },\n}\n\nexport function ShippingContainer({ size = defaults.size, color = defaults.color, marking = defaults.marking, liftingSet = defaults.liftingSet, doorsOpen = defaults.doorsOpen, tag = defaults.tag, ...group }: ShippingContainerProps & GroupProps) {\n  const data = useKit((k) => build(k, ISO668[size] ?? ISO668['20ft'], color, marking, liftingSet, tag, doorsOpen), [size, color, marking, liftingSet, tag, doorsOpen])\n  return (\n    <group {...group}>\n      <KitMesh data={data} />\n    </group>\n  )\n}\n\nexport default ShippingContainer\n\nconst V = (x: number, y: number, z: number) => new THREE.Vector3(x, y, z)\n\nfunction build(k: Kit, d: { L: number; W: number; H: number; fork: number }, color: string, marking: string, lifting: boolean, tag: string, open: boolean) {\n  const { L, W, H } = d\n  const P = paint(color, { weather: 1 })\n  const dark: MatKey = 'rubberBlack'\n  const hx = L / 2\n  const hz = W / 2\n  const cc = { l: 0.178, h: 0.118, w: 0.162 }\n  const post = 0.15\n  const yB = 0.17\n  const yT = H - 0.1\n\n  // corner castings with apertures, corner posts\n  for (const sx of [-1, 1])\n    for (const sz of [-1, 1])\n      for (const top of [false, true]) {\n        const y0 = top ? H - cc.h : 0\n        boxMM(k, P, [sx * hx - (sx > 0 ? cc.l : 0), y0, sz * hz - (sz > 0 ? cc.w : 0)], [sx * hx + (sx < 0 ? cc.l : 0), y0 + cc.h, sz * hz + (sz < 0 ? cc.w : 0)])\n        const cx = sx * (hx - cc.l / 2)\n        const cz = sz * (hz - cc.w / 2)\n        const ov = (w: number, h: number, pos: V3, n: V3) => {\n          const g = new THREE.CircleGeometry(0.5, 16)\n          g.scale(w, h, 1)\n          k.add(dark, g, new THREE.Matrix4().compose(V(...pos), new THREE.Quaternion().setFromUnitVectors(V(0, 0, 1), V(...n)), V(1, 1, 1)))\n        }\n        ov(0.124, 0.064, [cx, top ? H + 0.001 : -0.001, cz], [0, top ? 1 : -1, 0])\n        ov(0.078, 0.064, [cx, y0 + cc.h / 2, sz * (hz + 0.001)], [0, 0, sz])\n        ov(0.064, 0.064, [sx * (hx + 0.001), y0 + cc.h / 2, cz], [sx, 0, 0])\n      }\n  for (const sx of [-1, 1])\n    for (const sz of [-1, 1]) boxMM(k, P, [sx * hx - (sx > 0 ? post : 0), cc.h, sz * hz - (sz > 0 ? post * 0.9 : 0)], [sx * hx + (sx < 0 ? post : 0), H - cc.h, sz * hz + (sz < 0 ? post * 0.9 : 0)])\n\n  // side rails, end rails, door header and sill\n  const xr0 = -hx + cc.l\n  const xr1 = hx - cc.l\n  for (const sz of [-1, 1]) {\n    boxMM(k, P, [xr0, 0.0125, sz * hz - (sz > 0 ? 0.05 : 0)], [xr1, yB, sz * hz + (sz < 0 ? 0.05 : 0)])\n    boxMM(k, P, [xr0, yT, sz * hz - (sz > 0 ? 0.06 : 0)], [xr1, H - 0.004, sz * hz + (sz < 0 ? 0.06 : 0)])\n  }\n  const zr0 = -hz + cc.w\n  const zr1 = hz - cc.w\n  boxMM(k, P, [-hx, 0.0125, zr0], [-hx + 0.08, yB, zr1])\n  boxMM(k, P, [-hx, yT, zr0], [-hx + 0.08, H - 0.004, zr1])\n  boxMM(k, P, [hx - 0.1, 0.0125, zr0], [hx, 0.13, zr1])\n  boxMM(k, P, [hx - 0.1, H - 0.2, zr0], [hx, H - 0.004, zr1])\n  // floor cross-members underneath\n  const nx = Math.round((xr1 - xr0) / 0.3)\n  for (let i = 0; i <= nx; i++) {\n    const x = xr0 + ((xr1 - xr0) * i) / nx\n    boxMM(k, P, [x - 0.025, 0.03, -hz + 0.05], [x + 0.025, yB - 0.01, hz - 0.05])\n  }\n\n  // corrugated side walls, roof and front end wall\n  const wh = yT - yB\n  const dep = 0.036\n  for (const sz of [-1, 1]) corrugated(k, P, L - 2 * post + 0.02, wh, V(-hx + post - 0.01, yB, sz * (hz - 0.004 - dep)), [V(1, 0, 0), V(0, 0, sz), V(0, 1, 0)])\n  corrugated(k, P, xr1 - xr0 + 0.02, W - 0.1, V(xr0 - 0.01, H - 0.04 - 0.02, -hz + 0.05), [V(1, 0, 0), V(0, 1, 0), V(0, 0, 1)], { pitch: 0.2, depth: 0.02, outer: 0.09 })\n  corrugated(k, P, zr1 - zr0 + 0.06, wh, V(-hx + 0.004 + 0.045, yB, zr0 - 0.03), [V(0, 0, 1), V(-1, 0, 0), V(0, 1, 0)], { pitch: 0.33, depth: 0.045 })\n\n  // labyrinth ventilators near the top of each side wall\n  for (const sx of [-1, 1])\n    for (const sz of [-1, 1]) {\n      boxMM(k, P, [sx * (hx - 0.45) - 0.09, H - 0.36, sz * (hz - 0.004) - 0.012], [sx * (hx - 0.45) + 0.09, H - 0.24, sz * (hz - 0.004) + 0.012])\n      boxMM(k, dark, [sx * (hx - 0.45) - 0.07, H - 0.365, sz * (hz - 0.004) - 0.008], [sx * (hx - 0.45) + 0.07, H - 0.355, sz * (hz - 0.004) + 0.008])\n    }\n\n  // door end: two leaves with corrugated panels, frames, gaskets, hinges, 4 locking bars with cam keepers and handles\n  const dz0 = -hz + post * 0.9\n  const dz1 = hz - post * 0.9\n  const dy0 = 0.13\n  const dy1 = H - 0.2\n  const xd = hx - 0.06\n  const pq = new THREE.Quaternion().setFromAxisAngle(V(0, 1, 0), Math.PI / 2)\n  for (const sz of [-1, 1]) {\n    const a = sz < 0 ? dz0 : 0.012\n    const b = sz < 0 ? -0.012 : dz1\n    const zp = sz * (hz - 0.04)\n    // leaf geometry hinged about its outer pin; open leaves swing back 250° towards the side walls\n    const ang = open ? -sz * (250 * Math.PI) / 180 : 0\n    const hinge = new THREE.Matrix4().makeTranslation(hx + 0.03, 0, zp).multiply(new THREE.Matrix4().makeRotationY(ang)).multiply(new THREE.Matrix4().makeTranslation(-hx - 0.03, 0, -zp))\n    for (const y of [dy0 + 0.25, dy0 + 0.95, dy1 - 0.95, dy1 - 0.25]) {\n      k.cyl('galvanised', 0.018, 0.1, [hx + 0.03, y, zp], 'y', 10)\n      boxMM(k, 'galvanised', [hx - 0.02, y + 0.05, zp - 0.03], [hx + 0.04, y + 0.07, zp + 0.03])\n    }\n    k.with(hinge, () => {\n      corrugated(k, P, b - a - 0.08, dy1 - dy0 - 0.12, V(xd, dy0 + 0.06, a + 0.04), [V(0, 0, 1), V(1, 0, 0), V(0, 1, 0)], { pitch: 0.29, depth: 0.03 })\n      boxMM(k, P, [xd, dy0, a], [xd + 0.045, dy0 + 0.06, b])\n      boxMM(k, P, [xd, dy1 - 0.06, a], [xd + 0.045, dy1, b])\n      boxMM(k, P, [xd, dy0, a], [xd + 0.045, dy1, a + 0.045])\n      boxMM(k, P, [xd, dy0, b - 0.045], [xd + 0.045, dy1, b])\n      // EPDM door gasket round the inside of the leaf\n      boxMM(k, dark, [xd - 0.012, dy0 + 0.01, a + 0.005], [xd, dy0 + 0.035, b - 0.005])\n      boxMM(k, dark, [xd - 0.012, dy1 - 0.035, a + 0.005], [xd, dy1 - 0.01, b - 0.005])\n      for (const z of [a + 0.005, b - 0.03]) boxMM(k, dark, [xd - 0.012, dy0 + 0.01, z], [xd, dy1 - 0.01, z + 0.025])\n      for (const y of [dy0 + 0.25, dy0 + 0.95, dy1 - 0.95, dy1 - 0.25]) boxMM(k, 'galvanised', [xd + 0.03, y - 0.03, zp - sz * 0.25], [hx + 0.03, y + 0.03, zp - sz * 0.012])\n      for (const f of [0.28, 0.72]) {\n        const z = a + (b - a) * (sz < 0 ? f : 1 - f)\n        const xb = xd + 0.1\n        k.cyl('galvanised', 0.0135, dy1 - dy0 + 0.1, [xb, (dy0 + dy1) / 2, z], 'y', 10)\n        for (const y of [dy0 - 0.03, dy1 + 0.03]) k.box('galvanised', [0.05, 0.05, 0.035], [xb + 0.015, y, z])\n        for (const y of [dy0 + 0.35, dy1 - 0.35]) boxMM(k, 'galvanised', [xd + 0.03, y - 0.04, z - 0.03], [xb + 0.02, y + 0.04, z + 0.03])\n        const yh = 1.25\n        const zd = Math.sign(z) * 0.2\n        k.rod('galvanised', [xb + 0.025, yh, z], [xb + 0.025, yh, z + zd], 0.014, 8)\n        boxMM(k, 'galvanised', [xd + 0.03, yh - 0.06, z + zd - 0.02 * Math.sign(zd)], [xb + 0.05, yh + 0.06, z + zd + 0.03 * Math.sign(zd)])\n      }\n      // CSC safety approval plate and tag on the left leaf, owner code on the right leaf\n      if (sz > 0) {\n        k.box('nameplate', [0.36, 0.2, 0.002], [hx - 0.027, dy0 + 0.6, 0.55], pq)\n        k.plane(textMaterial('CSC SAFETY APPROVAL', '#e9eae6', '#222222', 512, 64), 0.32, 0.04, [hx - 0.0255, dy0 + 0.67, 0.55], pq)\n        if (tag) tagPlate(k, tag, [hx - 0.027, dy0 + 0.82, 0.55], 'x', 0.26)\n      } else if (marking) k.plane(textMaterial(marking, color, '#f4f4f0', 768, 112), 0.42, 0.062, [hx - 0.0265, dy1 - 0.22, (dz0 - 0.012) * 0.5], pq)\n    })\n    // cam keepers on the header and sill (fixed to the frame)\n    for (const f of [0.28, 0.72]) {\n        const z = a + (b - a) * (sz < 0 ? f : 1 - f)\n        for (const y of [dy0 - 0.03, dy1 + 0.03]) boxMM(k, 'galvanised', [xd + 0.05, y - 0.035, z - 0.045], [xd + 0.135, y + 0.035, z - 0.01])\n      }\n  }\n  if (!open) boxMM(k, dark, [xd - 0.01, dy0, -0.012], [xd + 0.03, dy1, 0.012])\n  if (marking) k.plane(textMaterial(marking, color, '#f4f4f0', 768, 112), 1.2, 0.175, [hx - cc.l - 0.75, H - 0.38, hz + 0.002])\n  // plywood floor (visible with the doors open)\n  boxMM(k, paint('#8a6a45', { weather: 0.4, clearcoat: 0 }), [-hx + 0.08, yB - 0.028, -hz + 0.05], [hx - 0.1, yB, hz - 0.05])\n\n  // forklift pockets (10 / 20 ft)\n  if (d.fork)\n    for (const sx of [-1, 1])\n      for (const sz of [-1, 1]) {\n        const x = (sx * d.fork) / 2\n        boxMM(k, P, [x - 0.22, 0.0125, sz * (hz + 0.006) - 0.006], [x + 0.22, yB, sz * (hz + 0.006) + 0.006])\n        boxMM(k, dark, [x - 0.178, 0.03, sz * (hz + 0.013) - 0.002], [x + 0.178, 0.145, sz * (hz + 0.013) + 0.002])\n      }\n\n  if (lifting) {\n    const eyes: V3[] = []\n    for (const sx of [-1, 1])\n      for (const sz of [-1, 1]) eyes.push(padeyeShackle(k, [sx * (hx - 0.45), H - 0.004, sz * (hz - 0.03)], 'z', 1.4, P))\n    const half = Math.hypot(hx - 0.45, hz - 0.03)\n    slingSet(k, eyes, [0, H + half * 1.05, 0], 0.3, 0.014)\n  }\n}\n"
    },
    {
      "path": "lifting/_corrugated.ts",
      "type": "registry:file",
      "target": "components/deck-twin/lifting/_corrugated.ts",
      "content": "// Trapezoidal corrugated sheet (container walls, roofs, doors) as an extruded profile.\nimport * as THREE from 'three'\nimport type { Kit, MatKey } from '../lib/kit'\n\n/**\n * Corrugated sheet: profile runs along local u (0 … length), outer flats at d = depth, inner flats at d = 0,\n * extruded `height` along local w. `basis` maps (u, d, w) to the item frame; `origin` is the (0, 0, 0) corner.\n */\nexport function corrugated(\n  k: Kit,\n  mat: MatKey,\n  length: number,\n  height: number,\n  origin: THREE.Vector3,\n  basis: [THREE.Vector3, THREE.Vector3, THREE.Vector3],\n  o: { pitch?: number; depth?: number; outer?: number; t?: number } = {},\n) {\n  const pitch = o.pitch ?? 0.278\n  const depth = o.depth ?? 0.036\n  const outer = o.outer ?? 0.26 * pitch\n  const slope = 0.25 * pitch\n  const inner = pitch - outer - 2 * slope\n  const t = o.t ?? 0.002\n  const pts: [number, number][] = [[0, depth]]\n  let u = 0\n  const n = Math.max(1, Math.round(length / pitch))\n  const sc = length / (n * pitch)\n  for (let i = 0; i < n; i++) {\n    const a = u\n    pts.push([a + (outer / 2) * sc, depth], [a + (outer / 2 + slope) * sc, 0], [a + (outer / 2 + slope + inner) * sc, 0], [a + (pitch - outer / 2) * sc, depth])\n    u += pitch * sc\n  }\n  pts.push([length, depth])\n  const sh = new THREE.Shape()\n  pts.forEach(([x, y], i) => (i ? sh.lineTo(x, y) : sh.moveTo(x, y)))\n  for (let i = pts.length - 1; i >= 0; i--) sh.lineTo(pts[i][0], pts[i][1] - t)\n  sh.closePath()\n  const m = new THREE.Matrix4().makeBasis(basis[0], basis[1], basis[2]).setPosition(origin)\n  k.extrude(mat, sh, height, m, 1)\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": "ShippingContainer",
    "anchor": "base",
    "standards": [
      "ISO 668",
      "ISO 1161",
      "ISO 1496-1",
      "ISO 6346",
      "DNV 2.7-1 (lifting set)"
    ]
  }
}