{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "laptop",
  "type": "registry:component",
  "title": "Laptop",
  "description": "A 14 inch laptop seen from the front and a little above, drawn as SVG paths in a simple or a realistic look, that opens, closes and turns, with live HTML on its display.",
  "author": "vectoys (https://vectoys.com)",
  "dependencies": [
    "@vinena/vectoys@^0.3.2",
    "@vinena/vectoys-react@^0.3.2"
  ],
  "files": [
    {
      "path": "registry/laptop/src/geometry.ts",
      "type": "registry:component",
      "target": "@components/vectoys/laptop/geometry.ts",
      "content": "// vectoys laptop, from https://vectoys.com/r/laptop.json\n// License: MIT. Credit: vectoys (https://vectoys.com)\nimport {\n  IDENTITY,\n  add,\n  aimed,\n  banded,\n  camera,\n  clamp,\n  combine,\n  compose,\n  dot,\n  fixed,\n  gradientSpace,\n  grow,\n  hull,\n  keep,\n  lerp,\n  line,\n  moved,\n  nearness,\n  panel,\n  place,\n  rotate,\n  rotateX,\n  rotateY,\n  rounded,\n  scrub,\n  shape,\n  slot,\n  subtract,\n  toward,\n  winding,\n  type Box,\n  type Boxed,\n  type Camera,\n  type CameraOptions,\n  type Easing,\n  type Flat,\n  type Pose,\n  type ScreenRect,\n  type Solid,\n} from \"@vinena/vectoys\";\n\n// A 14 inch laptop: 312.6 mm wide, 221.2 mm deep and 15.5 mm thick, of\n// which the lid is about 4.5 mm. Sizes are hundredths of the scene's width,\n// at 66 for the width, in a scene 100 wide. The display is 302.5 by\n// 196.5 mm inside a 5 mm bezel.\nexport const WIDTH = 66;\nexport const DEPTH = 46.7;\nconst BASE = 2.3;\nconst LID = 0.95;\nexport const BEZEL = 1.07;\nexport const CHIN = 4.15;\n// The lid stands LIFT above the deck.\nexport const LIFT = 0.15;\n// The corners of both slabs, and the aluminum rim round the display glass.\nexport const ROUND = 2.1;\nconst RIM = 0.25;\n// The lid turns about an axis 2.9 mm in from the base's back edge and 4.6 mm\n// under the shut lid's face, so open its chin drops behind the base. The\n// hinge fills a cut 237.6 by 4.5 mm in the back edge: a barrel at each end\n// and a dark cover between.\nconst PIVOT: Flat = [0.62, -0.98];\nconst CUT = 50.16;\nconst CUT_DEPTH = 0.94;\nconst CUT_ROUND = 0.12;\nconst BARREL = 3.16;\n\n/** One dot of the speaker grilles. */\nexport const HOLE = 0.085;\n\n/**\n * The display's glass in the lid's plane, x across and y down from the lid's\n * top edge to the hinge, for laying live content on it with `Screen`.\n */\nexport const GLASS: ScreenRect = { x: -WIDTH / 2 + BEZEL, y: -DEPTH + BEZEL, width: WIDTH - 2 * BEZEL, height: DEPTH - BEZEL - CHIN };\n\nconst HALF_WIDTH = WIDTH / 2;\nconst HALF_DEPTH = DEPTH / 2;\nconst RADIAN = Math.PI / 180;\n\n// The thumb slot cut into the front edge of the base: 56 mm wide, 2.8 mm\n// into the deck and 3.6 mm down the front, with a flat floor, rounded ends\n// and a small fillet where it leaves the edge.\nconst NOTCH = 11.8;\nconst SCOOP = 0.59;\nconst DIP = 0.76;\nconst FILLET = 0.15;\nconst END = 0.6;\n\n// The keyboard well and its keys, in key units across; every row adds up to\n// 14.5 with the arrow keys. The well is 279.6 by 114.9 mm, 16.6 mm in from the\n// deck's back edge, and the keys stand 0.44 apart.\nconst WELL: Box = [-29.516, -19.845, 29.516, 4.435];\nconst ROWS: readonly (readonly number[])[] = [\n  [1.5, ...Array<number>(12).fill(1), 1],\n  [...Array<number>(13).fill(1), 1.5],\n  [1.5, ...Array<number>(13).fill(1)],\n  [1.75, ...Array<number>(11).fill(1), 1.75],\n  [2.25, ...Array<number>(10).fill(1), 2.25],\n  [1, 1, 1, 1.25, 5, 1.25, 1],\n];\nconst PAD = 0.3;\nconst GAP = 0.22;\nconst UNIT = (WELL[2] - WELL[0] - 2 * PAD) / 14.5;\nconst ROW = (WELL[3] - WELL[1] - 2 * PAD) / ROWS.length;\n\nconst KEYS: Box[] = ROWS.flatMap((row, index) => {\n  const top = WELL[1] + PAD + index * ROW;\n  let left = WELL[0] + PAD;\n  return row.map((units): Box => {\n    const box: Box = [left + GAP, top + GAP, left + units * UNIT - GAP, top + ROW - GAP];\n    left += units * UNIT;\n    return box;\n  });\n});\n// Left, up over down, and right, the half-height arrows in the last row.\nconst ARROW_LEFT = WELL[0] + PAD + 11.5 * UNIT;\nconst ARROW_TOP = WELL[1] + PAD + 5 * ROW + GAP;\nconst ARROW_HALF = (ROW - 2 * GAP - 0.2) / 2;\nconst ARROWS: Box[] = [\n  [0, 1],\n  [1, 0],\n  [1, 1],\n  [2, 1],\n].map(([column, half]): Box => {\n  const left = ARROW_LEFT + column! * UNIT + GAP;\n  const top = ARROW_TOP + half! * (ARROW_HALF + 0.2);\n  return [left, top, left + UNIT - 2 * GAP, top + ARROW_HALF];\n});\nconst LOCK_KEY = KEYS[ROWS[0]!.length - 1]!;\n\n// The trackpad, 129.6 by 80.9 mm, 3 mm under the well.\nconst TRACKPAD: Box = [-13.68, 5.07, 13.68, 22.17];\n\n// The speaker grilles' holes, a grid beside the keyboard, 1.6 mm from the\n// side edge and the well and 4.5 mm short of its ends: 0.93 mm apart across,\n// 2.3 mm down the foreshortened deck so they look as far apart.\nconst GRILLE_GAP = 0.34;\nconst GRILLE_END = 0.95;\n\n// The lid's outline in its own plane, x across and d up from the hinge.\nconst LID_RING = rounded([-HALF_WIDTH, -DEPTH, HALF_WIDTH, 0], [ROUND, ROUND]);\nconst BEZEL_RING = rounded([-HALF_WIDTH + RIM, -DEPTH + RIM, HALF_WIDTH - RIM, -RIM], [ROUND - RIM, ROUND - RIM]);\n\nconst cut = (value: number) => Math.round(value * 100) / 100 || 0;\n\n// The base's model, in the laptop's space, which no pose changes: its\n// outline at the deck and at the bottom, the deck's top with the slot cut\n// into its front edge and the hinge's cut into its back edge, and the slot's\n// dip down the front face.\nconst SLAB: Box = [-HALF_WIDTH, -HALF_DEPTH, HALF_WIDTH, HALF_DEPTH];\nconst PLAIN = rounded(SLAB, [ROUND, ROUND]);\nconst RIMS: Solid[] = [...PLAIN.map(([x, y]): Solid => [x, y, 0]), ...PLAIN.map(([x, y]): Solid => [x, y, -BASE])];\nconst SCOOP_EDGE = slot(NOTCH, SCOOP, SCOOP - FILLET, FILLET).map(([x, y]): Flat => [x, HALF_DEPTH - y]);\nconst DIP_LINE: Solid[] = slot(NOTCH, DIP, END, FILLET).map(([x, w]) => [x, HALF_DEPTH, -w]);\nconst CUT_EDGE = slot(CUT, CUT_DEPTH, CUT_ROUND, FILLET).map(([x, y]): Flat => [x, y - HALF_DEPTH]);\nconst DECK_OUTLINE = rounded(SLAB, [ROUND, ROUND], [...SCOOP_EDGE].reverse(), CUT_EDGE);\n\n/** A rounded rectangle on the deck, as its outline. */\nconst ring = (box: Box, radius: number) => rounded(box, [radius, radius]);\n\n/** The speaker grilles' holes on the deck, `columns` across and `rows` down beside the keyboard. */\nfunction holes(columns: number, rows: number): Flat[] {\n  return [-1, 1].flatMap((side) =>\n    Array.from({ length: rows * columns }, (_, index): Flat => {\n      const across = WELL[2] + GRILLE_GAP + ((HALF_WIDTH - WELL[2] - 2 * GRILLE_GAP) * (index % columns)) / (columns - 1);\n      const along = WELL[1] + GRILLE_END + ((WELL[3] - WELL[1] - 2 * GRILLE_END) * Math.floor(index / columns)) / (rows - 1);\n      return [side * across, along];\n    }),\n  );\n}\n\n// The shadow on the ground: the base's footprint, a little in from its edge.\nconst footprint = (spread: number, ahead: number) =>\n  ring([-HALF_WIDTH + 1.32 - spread, -HALF_DEPTH + 0.93 - spread + ahead, HALF_WIDTH - 1.32 + spread, HALF_DEPTH - 0.93 + spread + ahead], ROUND);\nconst GROUND = -BASE - 0.1;\n\n/** Everything on the base, as outlines on the deck. */\nconst MODEL = {\n  well: ring(WELL, 0.8),\n  keys: [...KEYS.filter((key) => key !== LOCK_KEY), ...ARROWS].map((key) => ring(key, key[3] - key[1] < 2 ? 0.25 : 0.38)),\n  lockKey: ring(LOCK_KEY, 0.38),\n  lockKeyRing: ring([LOCK_KEY[0] + 0.45, LOCK_KEY[1] + 0.45, LOCK_KEY[2] - 0.45, LOCK_KEY[3] - 0.45], 0.3),\n  // The same field of holes at full, half and quarter density, for scenes\n  // too small to keep the finer ones apart.\n  holes: [holes(15, 47), holes(8, 24), holes(4, 12)],\n  trackpad: ring(TRACKPAD, 1),\n  // The hinge in its cut, a little past the cut's edges so the deck's top and\n  // the lid overlap it.\n  hinge: ring([-CUT / 2, -HALF_DEPTH - 0.15, CUT / 2, -HALF_DEPTH + CUT_DEPTH + 0.3], 0),\n  barrels: [-CUT / 2, CUT / 2 - BARREL].map((left) => ring([left, -HALF_DEPTH - 0.15, left + BARREL, -HALF_DEPTH + CUT_DEPTH + 0.3], 0)),\n  contact: footprint(0, 0.3),\n  ground: footprint(0.8, 1.2),\n};\n\n/** The lid's joint, which opens to `OPEN` degrees: where it is for each frame, whatever the camera. */\nfunction joint(OPEN: number) {\n  /**\n   * The lid's place for one frame, `closed` from 0 (open) to 1 (shut on the\n   * base): its angle in degrees, from OPEN to -180 when shut, about the back\n   * edge of the shut lid's face, then how far it moves along the deck and up\n   * for the hinge's real axis.\n   */\n  function lidPose(closed: number) {\n    const angle = OPEN - (180 + OPEN) * closed;\n    const turn = (angle + 180) * RADIAN;\n    const [y, z] = PIVOT;\n    return {\n      angle,\n      along: y * (1 - Math.cos(turn)) + z * Math.sin(turn),\n      up: z * (1 - Math.cos(turn)) - y * Math.sin(turn),\n    };\n  }\n\n  /**\n   * The lid as a pose in the laptop's space, for `Screen`: x across, y from\n   * the lid's top edge down to the hinge and z out of the display's face, as\n   * `GLASS` is given.\n   */\n  function lidWorld(closed: number): Pose {\n    const { angle, along, up } = lidPose(closed);\n    return { position: [0, -HALF_DEPTH + along, LIFT + up], rotation: rotateX(angle) };\n  }\n\n  return { lidPose, lidWorld };\n}\n\n/**\n * The laptop as one camera sees it: the base, and the lid for each frame.\n * `OPEN` is the open lid's angle, which stays the eye's own whatever the pose.\n */\nfunction look(eye: Camera, OPEN: number) {\n  const { project } = eye;\n  const { lidPose, lidWorld } = joint(OPEN);\n  const onDeck = ([x, y]: Flat, z = 0) => project([x, y, z]);\n  const outline = (points: readonly Flat[], z = 0) => shape(points.map((point) => onDeck(point, z)));\n\n  const DIP_EDGE = DIP_LINE.map(project);\n  const DECK_RING = DECK_OUTLINE.map((point) => onDeck(point));\n  const BASE_HULL = hull(RIMS.map(project));\n  // The base's outline without its back edge, which the deck's top draws with\n  // the cut: the edge through the middle of the deck's back.\n  const back = onDeck([0, -HALF_DEPTH]);\n  const BACK_EDGE = BASE_HULL.reduce(\n    (best, point, index, all) => {\n      const far = nearness(back, point, all[(index + 1) % all.length] ?? point);\n      return far < best.far ? { index, far } : best;\n    },\n    { index: 0, far: Infinity },\n  ).index;\n  const BASE_LINE = [...BASE_HULL.slice(BACK_EDGE + 1), ...BASE_HULL.slice(0, BACK_EDGE + 1)];\n  const top = shape(DECK_RING);\n\n  const field = (points: readonly Flat[]) =>\n    points\n      .map((point) => {\n        const [x, y] = onDeck(point);\n        return `M${cut(x)} ${cut(y)}h0`;\n      })\n      .join(\"\");\n\n  /** Everything that never moves as the lid does, as SVG path data in the scene's units. */\n  const DECK = {\n    hull: shape(BASE_HULL),\n    top,\n    lines: `${line(BASE_LINE)}${top}${line(DIP_EDGE)}`,\n    scoop: shape([...SCOOP_EDGE.map((point) => onDeck(point)), ...[...DIP_EDGE].reverse()]),\n    well: outline(MODEL.well),\n    keys: MODEL.keys.map((key) => outline(key)).join(\"\"),\n    lockKey: outline(MODEL.lockKey),\n    lockKeyRing: outline(MODEL.lockKeyRing),\n    holes: MODEL.holes.map(field),\n    trackpad: outline(MODEL.trackpad),\n    hinge: outline(MODEL.hinge),\n    barrels: MODEL.barrels.map((barrel) => outline(barrel)).join(\"\"),\n    contact: outline(MODEL.contact, GROUND),\n    ground: outline(MODEL.ground, GROUND),\n  };\n\n  // Which way round `hull` runs, as the sign of its outlines' area.\n  const TURN = winding(BASE_HULL);\n\n  // The base's outline on screen grown by half a hairline, so the lid stops at\n  // the deck's line instead of covering half of it.\n  const GROWN_HULL = grow(BASE_HULL, 0.06);\n\n  /** What of a hull on screen, grown past its hairline, the base covers. */\n  function behindBase(points: readonly Flat[]): Flat[] {\n    return GROWN_HULL.reduce<Flat[]>((kept, [ax, ay], index) => {\n      const [bx, by] = GROWN_HULL[(index + 1) % GROWN_HULL.length] ?? [ax, ay];\n      return keep(kept, ([x, y]) => -TURN * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)), toward);\n    }, grow(points, 0.2));\n  }\n\n  // The way toward the deck's front on screen, square to its sides' line, so\n  // the lid's shadow lies across the deck however it has turned.\n  const [ax, ay] = onDeck([-1, 0]);\n  const [bx, by] = onDeck([1, 0]);\n  const across = Math.hypot(bx - ax, by - ay);\n  const ahead: Flat = [-(by - ay) / across, (bx - ax) / across];\n  const along = ([x, y]: Flat) => x * ahead[0] + y * ahead[1];\n\n  /** The lid's paths for one frame, `closed` from 0 (open) to 1 (shut on the base). */\n  function lidArt(closed: number) {\n    const pose = lidPose(closed);\n    const angle = pose.angle * RADIAN;\n    const [cos, sin] = [Math.cos(angle), Math.sin(angle)];\n    // A point `behind` the display's face, at (x, -d) in the lid's plane.\n    const at =\n      (behind: number) =>\n      ([x, up]: Flat): Solid => [x, -HALF_DEPTH + pose.along + up * cos + behind * sin, LIFT + pose.up + up * sin - behind * cos];\n    const flat = (behind: number, ring: readonly Flat[]) => ring.map((point) => project(at(behind)(point)));\n    // Shut, the lid's sides reach down to the deck, so the seam is one line.\n    const skirt = LIFT * Math.min(1, Math.max(0, (closed - 0.9) / 0.1));\n    const front = flat(0, LID_RING);\n    const back = flat(LID, LID_RING);\n    const outline = hull([...front, ...back, ...(skirt > 0 ? flat(-skirt, LID_RING) : [])]);\n    const facing = eye.facesEye(at(0)([0, -DEPTH / 2]), [0, -sin, cos]);\n    const face = facing ? front : back;\n    // The chin below the deck drops behind the base, which hides it where the\n    // rest of the lid does not stand in front.\n    const part = (side: number) => [0, LID].flatMap((behind) => keep(LID_RING.map(at(behind)), ([, , z]) => side * z, toward).map(project));\n    const sunk = part(1);\n    const hidden = sunk.length > 2 ? behindBase(hull(sunk)) : [];\n    // How far over the deck the lid reaches, as a share of its depth, for its\n    // shadow, which runs from `from` to `to` along `ahead`.\n    const reach = Math.max(0, -cos);\n    return {\n      facing,\n      hull: shape(outline),\n      face: shape(face),\n      bezel: facing ? shape(flat(0, BEZEL_RING)) : null,\n      cut: hidden.length > 2 ? { hidden: shape(hidden), clear: shape(grow(hull(part(-1)), 0.08)) } : null,\n      lines: `${shape(outline)}${shape(face)}`,\n      shadow:\n        closed > 0\n          ? {\n              from: cut(along(onDeck([0, -HALF_DEPTH + DEPTH * (reach - 0.1)]))),\n              to: cut(along(onDeck([0, -HALF_DEPTH + DEPTH * (reach + 0.15)]))),\n            }\n          : null,\n    };\n  }\n\n  return { camera: eye, DECK, ahead, lidPose, lidWorld, lidArt };\n}\n\n/** One pose of the laptop: the lid's open share from 0 (shut) to 1, its turn and the eye's height, in degrees. */\nexport type LaptopPose = { lid: number; yaw: number; pitch: number };\n\n/** How far the laptop turns either way, and how high the eye goes, in degrees. */\nexport const YAW = 35;\nexport const PITCH: readonly [number, number] = [5, 30];\n\n// The points that bound the laptop on screen in any pose: the corners of the\n// base's box at the deck and the bottom, its shadow's on the ground, and the\n// lid's at both its faces.\nconst CORNERS = (box: Box, z: number): Solid[] => [\n  [box[0], box[1], z],\n  [box[2], box[1], z],\n  [box[2], box[3], z],\n  [box[0], box[3], z],\n];\nconst GROUND_BOX: Box = [-HALF_WIDTH + 0.52, -HALF_DEPTH + 1.33, HALF_WIDTH - 0.52, HALF_DEPTH + 1.07];\n// How far the ground shadow's blur reaches past it, at two of its deviations, and the margin round all.\nconst BLUR: Flat = [4, 1.8];\nconst MARGIN = 1.5;\nconst up = (value: number) => Math.ceil(value * 100) / 100;\n\n/**\n * An eye's camera turned to a pose. Boxed, the scene is the one fixed box\n * that holds the laptop in every pose, with it in the middle: the same box\n * moved, so a pose draws the same in it, only shifted.\n */\nconst turned = (options: CameraOptions, { yaw, pitch }: Omit<LaptopPose, \"lid\">, box?: Boxed) => aimed(options, { yaw, pitch: -pitch }, box);\n\n/** The fixed box round every pose an eye can take, found by trying poses across the ranges. */\nfunction boxFor(options: CameraOptions, OPEN: number) {\n  const { lidWorld } = joint(OPEN);\n  const bounds = { left: Infinity, right: -Infinity, top: Infinity, bottom: -Infinity };\n  const take = ([x, y]: Flat, [spreadX, spreadY]: Flat = [0, 0]) => {\n    bounds.left = Math.min(bounds.left, x - spreadX);\n    bounds.right = Math.max(bounds.right, x + spreadX);\n    bounds.top = Math.min(bounds.top, y - spreadY);\n    bounds.bottom = Math.max(bounds.bottom, y + spreadY);\n  };\n  for (let yaw = -YAW; yaw <= YAW; yaw += 5) {\n    for (let pitch = PITCH[0]; pitch <= PITCH[1]; pitch += 5) {\n      const eye = turned(options, { yaw, pitch });\n      for (const z of [0, -BASE]) for (const point of CORNERS(SLAB, z)) take(eye.project(point));\n      for (const point of CORNERS(GROUND_BOX, GROUND)) take(eye.project(point), BLUR);\n      for (let step = 0; step <= 10; step++) {\n        const pose = lidWorld(step / 10);\n        for (const z of [0, -LID]) for (const point of CORNERS([-HALF_WIDTH, -DEPTH, HALF_WIDTH, 0], z)) take(eye.project(place(pose, point)));\n      }\n    }\n  }\n  const anchorX = options.anchorX ?? (options.width ?? 100) / 2;\n  // Even about the laptop's middle across, and rounded up.\n  const half = Math.max(anchorX - bounds.left, bounds.right - anchorX) + MARGIN;\n  return { width: up(2 * half), height: up(bounds.bottom - bounds.top + 2 * MARGIN), x: half - anchorX, y: MARGIN - bounds.top };\n}\n\n/** The laptop as one eye sees it: what never moves, and the lid for each frame, at the eye's own pose or any other. */\nfunction seen<const Id extends string>({ id, ...options }: CameraOptions & { id: Id }) {\n  const rest = camera(options);\n  // The open lid faces the eye at its own pitch, so its glass is a plain\n  // rectangle on screen there; it opens as far in every pose.\n  const OPEN = -90 - rest.pitch;\n  const own = look(rest, OPEN);\n  // Found on first use, so a page that never turns the laptop never pays for it.\n  let found: ReturnType<typeof boxFor> | undefined;\n  const fixedBox = () => (found ??= boxFor(options, OPEN));\n  return {\n    id,\n    ...own,\n    /** The eye's own height, the pitch of `LaptopPose`. */\n    pitch: -rest.pitch,\n    /** The fixed box that holds every pose, in scene units, and where the eye's own scene sits in it. */\n    get box() {\n      const { width, height, x, y } = fixedBox();\n      return { width, height, x: fixed(x), y: fixed(y) };\n    },\n    /** The laptop turned to `yaw` and seen from `pitch`, boxed in the fixed box that holds every pose, or in the eye's own scene. */\n    at({ yaw, pitch }: Omit<LaptopPose, \"lid\">, boxed = false) {\n      return !boxed && yaw === 0 && pitch === -rest.pitch ? own : look(turned(options, { yaw, pitch }, boxed ? fixedBox() : undefined), OPEN);\n    },\n    /** The laptop in a free pose, boxed as `at` boxes it: every path, tone and place a frame of it draws, `real` as the realistic look draws it. */\n    trick(pose: TrickPose, boxed = false, real = false) {\n      return free(options, OPEN, pose, boxed ? fixedBox() : undefined, real);\n    },\n  };\n}\n\n/** An eye that sees the laptop in perspective, with a lid that opens and closes. */\nexport type Perspective = ReturnType<typeof seen>;\n\n/** The laptop as an eye sees it in one pose: its camera, the base's paths and the lid's for each frame. */\nexport type Posed = ReturnType<typeof look>;\n\n/**\n * Beside the words, on a wide page: the eye is near, so the deck comes\n * forward and spreads. The lid opens to 105 degrees. An eye sits\n * `perspective` away, level with `originY`; the laptop stands `back` behind\n * its focus at `anchorY` and turns toward it by `pitch` degrees, in a scene\n * 100 by `height`.\n */\nexport const DESK = seen({ id: \"desk\", height: 70, perspective: 200, pitch: -15, back: 14, originY: 0.22 * 70, anchorY: 0.78 * 70 });\n\n/**\n * Over the words, on a phone: the eye is three times as far and a little\n * lower, so the whole laptop fits a narrow page. `back` and `anchorY` put the\n * open lid where DESK has it, so one glass serves both. The lid opens to 102 degrees.\n */\nexport const PHONE = seen({ id: \"phone\", height: 59, perspective: 600, pitch: -12, back: 88.07, originY: 0.22 * 70, anchorY: 53.43 });\n\n/**\n * The fixed box, in scene units, that holds the laptop and its shadow in\n * every pose an eye can take: yaw from -35 to 35, pitch from 5 to 30, the\n * lid shut to open, the laptop in the middle. A `Laptop` passed `turning`\n * draws in it, so its stage keeps one size; its aspect ratio is `width /\n * height`. A point of the eye's own scene lands `x` to the right and `y`\n * down in the box, so an overlay placed on the still laptop can follow it\n * into a turning one.\n */\nexport const LAPTOP_BOX = {\n  get desk() {\n    return DESK.box;\n  },\n  get phone() {\n    return PHONE.box;\n  },\n};\n\n\n// Straight on, as product shots draw the machine, in hundredths of the lid's\n// width: the near base shows 1.214 times as wide as the lid, and the lid\n// 1.465 times as wide as it is tall, leaning back over a hidden foot.\nconst share = (hundredths: number) => (hundredths * WIDTH) / 100;\nconst OVERHANG = 10.7;\nconst SHOWN = 100 / 1.465;\n// From the lid's edge in: an aluminum rim, a dark gasket with a lighter\n// inner line, then black glass. The display stands BORDER in from the edge at\n// the top and the sides, with top corners round about the lid's.\nconst LID_ROUND = 3.05;\nconst RIM_LINE = 0.07;\nconst GASKET = 0.2;\nconst GASKET_LINE = 0.44;\nconst BLACK = 0.54;\nconst BORDER = 1.75;\nconst DISPLAY: Box = [BORDER, BORDER, 100 - BORDER, BORDER + 62.67];\nconst DISPLAY_ROUND = 1.27;\n// Under the display, black glass to the hinge's hairline and band, which stop\n// at the base with corners a little round.\nconst HINGE_LINE = 65.54;\nconst HINGE = 65.64;\nconst HINGE_ROUND = 0.6;\n// The base's front face, with round bottom corners, the thumb's scoop cut\n// down into its top edge, and two feet under the lid's sides.\nconst FACE = 4.37;\nconst FACE_ROUND = 2.4;\nconst SCOOP_HALF = 10.6;\nconst SCOOP_DEPTH = 1.53;\nconst SCOOP_END = 1.9;\nconst SCOOP_WALL = 0.75;\nconst FOOT_HALF = 4;\nconst FOOT_TAPER = 0.3;\nconst FOOT_HIGH = 0.6;\n// The notch is NOTCH_ACROSS wide and NOTCH_HIGH tall from the display's top edge, with\n// a flat floor, round bottom corners and small curved shoulders that flare\n// FLARE out along that edge; the camera is centered in its top part.\nconst NOTCH_ACROSS = 11.79;\nconst NOTCH_HIGH = 2.03;\nconst NOTCH_ROUND = 0.7;\nconst SHOULDER = 0.29;\nconst FLARE = 0.19;\nconst LENS_AT = 0.54;\n\n/** The laptop straight on, drawn flat: its paths, the display's place and the notch. */\nfunction straight() {\n  const x = (hundredths: number) => share(OVERHANG + hundredths);\n  const at = ([left, top, right, bottom]: Box): Box => [x(left), share(top), x(right), share(bottom)];\n  const plate = (outline: Box, high: number, low = 0) => panel(at(outline), share(high), share(low));\n  const BASE_TOP = SHOWN;\n  const BASE_BOTTOM = BASE_TOP + FACE;\n  const FEET = BASE_BOTTOM + FOOT_HIGH;\n  const eye = camera({ width: fixed(share(100 + 2 * OVERHANG)), height: fixed(share(FEET)), perspective: 1000, anchorX: 0, anchorY: 0 });\n\n  /** The scoop's outline, its walls quarter ellipses `end` across down to a flat floor. */\n  function scoop(end: number, inset: number) {\n    const [left, right, top, floor] = [x(50 - SCOOP_HALF + inset), x(50 + SCOOP_HALF - inset), share(BASE_TOP), share(BASE_TOP + SCOOP_DEPTH)];\n    const [across, down] = [fixed(share(end)), fixed(share(SCOOP_DEPTH))];\n    return `M${fixed(left)} ${fixed(top)}A${across} ${down} 0 0 0 ${fixed(left + share(end))} ${fixed(floor)}H${fixed(right - share(end))}A${across} ${down} 0 0 0 ${fixed(right)} ${fixed(top)}Z`;\n  }\n\n  /** A foot centered under `middle`, from under the base's face to the ground, a little narrower at the bottom. */\n  function foot(middle: number) {\n    const [top, face, ground, round] = [share(BASE_BOTTOM - 0.3), share(BASE_BOTTOM), share(FEET), share(0.2)];\n    const [wide, narrow] = [share(FOOT_HALF), share(FOOT_HALF - FOOT_TAPER)];\n    const center = x(middle);\n    const [l, r, ll, rr] = [center - wide, center + wide, center - narrow, center + narrow];\n    return [\n      `M${fixed(l)} ${fixed(top)}H${fixed(r)}V${fixed(face)}L${fixed(rr)} ${fixed(ground - round)}`,\n      `Q${fixed(rr)} ${fixed(ground)} ${fixed(rr - round)} ${fixed(ground)}H${fixed(ll + round)}`,\n      `Q${fixed(ll)} ${fixed(ground)} ${fixed(ll)} ${fixed(ground - round)}L${fixed(l)} ${fixed(face)}Z`,\n    ].join(\"\");\n  }\n\n  // The notch in its own box, in hundredths of the lid's width, from the\n  // display's top edge down; a little past that edge so no seam shows.\n  const NOTCH_WIDE = NOTCH_ACROSS + 2 * FLARE;\n  const [side, far, floor] = [FLARE, FLARE + NOTCH_ACROSS, NOTCH_HIGH];\n  const notch = {\n    width: NOTCH_WIDE,\n    height: NOTCH_HIGH,\n    path: [\n      `M0 -0.1V0Q${side} 0 ${side} ${SHOULDER}V${fixed(floor - NOTCH_ROUND)}`,\n      `A${NOTCH_ROUND} ${NOTCH_ROUND} 0 0 0 ${fixed(side + NOTCH_ROUND)} ${floor}H${fixed(far - NOTCH_ROUND)}`,\n      `A${NOTCH_ROUND} ${NOTCH_ROUND} 0 0 0 ${fixed(far)} ${fixed(floor - NOTCH_ROUND)}V${SHOULDER}Q${fixed(far)} 0 ${fixed(NOTCH_WIDE)} 0V-0.1Z`,\n    ].join(\"\"),\n    lens: { x: fixed(NOTCH_WIDE / 2), y: LENS_AT },\n    // Its place on the display, as shares of the display's width and height.\n    left: (50 - BORDER - NOTCH_WIDE / 2) / (DISPLAY[2] - DISPLAY[0]),\n    across: NOTCH_WIDE / (DISPLAY[2] - DISPLAY[0]),\n    down: NOTCH_HIGH / (DISPLAY[3] - DISPLAY[1]),\n  };\n\n  const [left, top, right, bottom] = at(DISPLAY);\n  return {\n    id: \"front\",\n    camera: eye,\n    /** The display in the scene, for laying live content on it with `Screen`. */\n    glass: { x: left, y: top, width: right - left, height: bottom - top } satisfies ScreenRect,\n    /** The display's top corners' radius in the scene. */\n    round: share(DISPLAY_ROUND),\n    /** The display's plane, facing the eye, where a point of `glass` lands on the scene as it is. */\n    pose: { position: [0, 0, 0], rotation: rotateX(-90) } satisfies Pose,\n    notch,\n    art: {\n      edge: plate([0, 0, 100, SHOWN + 0.2], LID_ROUND),\n      rim: plate([RIM_LINE, RIM_LINE, 100 - RIM_LINE, SHOWN + 0.2], LID_ROUND - RIM_LINE),\n      gasket: plate([GASKET, GASKET, 100 - GASKET, SHOWN], LID_ROUND - GASKET, HINGE_ROUND + BLACK - GASKET),\n      gasketLine: plate([GASKET_LINE, GASKET_LINE, 100 - GASKET_LINE, SHOWN], LID_ROUND - GASKET_LINE, HINGE_ROUND + BLACK - GASKET_LINE),\n      glass: plate([BLACK, BLACK, 100 - BLACK, SHOWN], LID_ROUND - BLACK, HINGE_ROUND),\n      hingeLine: plate([BLACK, HINGE_LINE, 100 - BLACK, SHOWN], 0, HINGE_ROUND),\n      hinge: plate([BLACK, HINGE, 100 - BLACK, SHOWN], 0, HINGE_ROUND),\n      base: panel([0, share(BASE_TOP), share(100 + 2 * OVERHANG), share(BASE_BOTTOM)], 0, share(FACE_ROUND)),\n      scoop: scoop(SCOOP_END, 0),\n      // The scoop's walls, from its edge to a narrower scoop inside it, to fill even-odd.\n      walls: scoop(SCOOP_END, 0) + scoop(SCOOP_END - SCOOP_WALL, SCOOP_WALL),\n      feet: [foot(0), foot(100)],\n      /** Where the rim's top darkens, and where the feet start and end, as heights in the scene for their gradients. */\n      rimFade: fixed(share(LID_ROUND)),\n      feetTop: fixed(share(BASE_BOTTOM)),\n      feetBottom: fixed(share(FEET)),\n    },\n  };\n}\n\nexport type Straight = ReturnType<typeof straight>;\n\n/** An eye on the laptop: `DESK`, `PHONE` or `FRONT`. */\nexport type View = Perspective | Straight;\n\n/**\n * Straight on, as a product shot: the open lid upright with its display a\n * plain rectangle, over the base's front face with the thumb's scoop and two\n * feet, drawn flat to sizes measured on product renders. The lid is always\n * open, so `closed` does nothing. Its aluminum is the page's\n * `--vectoys-finish`, silver by default; #3d3b3e reads as space black. The\n * scene is the laptop's own bounds, the base's width across and the lid's top\n * to the feet down, and the lid is 82.4% of its width. Laptop takes no style\n * or className, so to let a frame cut it, put it in a div with `width:\n * 121.4%; margin-inline: -10.7%; margin-bottom: -3%` inside a wrapper with\n * `overflow: hidden`: the lid then fills the wrapper's width, the base's ends\n * are cut flush with the lid's sides, and the base's face just under the\n * thumb's scoop.\n */\nexport const FRONT = straight();\n\n// A laptop free to turn any way, as a trick takes it. At `flat` 1 the eye is\n// FRONT's and sees FRONT's shapes, fitted once on first use; toward 0 they\n// ease into the eye's own, so nothing jumps. It rolls and turns round the\n// middle of the shut slab, then moves and grows on screen.\n\n/** A pose of a laptop free to turn: `flat` from the eye's own look (0) to FRONT's (1), a move `x` and `y` and a `scale` on screen, and a `roll` about its long axis. */\nexport type TrickPose = LaptopPose & { flat: number; x: number; y: number; scale: number; roll: number };\n\nconst MIDDLE: Solid = [0, 0, (LIFT + LID - BASE) / 2];\n// The side faces' corners stand this far apart, as `rounded` takes it.\nconst COARSE = 0.4;\n/** The tones a face takes, as shares of the finish, from the darkest to the finish itself. */\nexport const TONES = Array.from({ length: 12 }, (_, step) => fixed(84 + (16 * step) / 11));\n// A lamp over the eye, a little to the left, in the scene's axes.\nconst LAMP: Solid = [-0.2, -1, 0.8];\n/** Which of `TONES` a face turned along `normal` takes, as the lamp falls on it. */\nconst lit = (eye: Camera, normal: Solid) => {\n  const turned = rotate(eye.rotation, normal);\n  return Math.round(((dot(turned, LAMP) / Math.hypot(...turned) / Math.hypot(...LAMP) + 1) / 2) * (TONES.length - 1));\n};\n// The realistic look's shades, from the darkest turn of a side to a highlight:\n// the lamp's light over an even share of light all round, and a highlight\n// where a face mirrors the lamp toward the eye.\nexport const SHADES = 16;\nconst SHINE: Solid = [-0.2, -1, 2.1];\nconst glint = (eye: Camera, normal: Solid) => {\n  const turned = rotate(eye.rotation, normal);\n  const length = Math.hypot(...turned);\n  const light = dot(turned, LAMP) / length / Math.hypot(...LAMP);\n  const shine = Math.max(0, dot(turned, SHINE) / length / Math.hypot(...SHINE)) ** 10;\n  return Math.round(clamp(0.4 + 0.4 * light + 0.3 * shine, 0, 1) * (SHADES - 1));\n};\nconst at = (ring: readonly Flat[], z: number) => ring.map(([x, y]): Solid => [x, y, z]);\nconst inset = (by: number, step?: number) => rounded([-HALF_WIDTH + by, -HALF_DEPTH + by, HALF_WIDTH - by, HALF_DEPTH - by], [ROUND - by, ROUND - by], [], [], step);\nconst DEEP = (DEPTH * 100) / WIDTH;\n\nlet fitted: ReturnType<typeof fit> | undefined;\n/** FRONT's sizes as the model's, for the eye level with the deck and the lid upright with its face square to it. */\nfunction fit() {\n  const plane = -HALF_DEPTH + PIVOT[0] + PIVOT[1];\n  const halve = (lo: number, hi: number, short: (value: number) => boolean) => {\n    for (let step = 0; step < 48; step++) [lo, hi] = short((lo + hi) / 2) ? [(lo + hi) / 2, hi] : [lo, (lo + hi) / 2];\n    return lo;\n  };\n  // How much larger than the lid's face a depth `y` draws, with the eye `far` from the deck's middle.\n  const near = (far: number) => (y: number) => (far - plane) / (far - y);\n  // As far as puts the base's ends where FRONT has them, the lid drawn at its own width.\n  const far = halve(60, 4000, (far) => Math.max(...PLAIN.map(([x, y]) => Math.abs(x) * near(far)(y))) > share(50 + OVERHANG));\n  const k = near(far);\n  const round = share(FACE_ROUND) / k(HALF_DEPTH);\n  const arc = [0, 1, 2, 3, 4, 5, 6].map((step) => (Math.PI / 12) * step);\n  const thick = halve(1, 5, (thick) => Math.max(...arc.map((turn) => (thick - round + round * Math.sin(turn)) * k(HALF_DEPTH - round + round * Math.cos(turn)))) < share(FACE));\n  const [front, deep] = [HALF_DEPTH - round - 0.3, 1.2];\n  return {\n    far,\n    plane,\n    thick,\n    round,\n    face: k(HALF_DEPTH),\n    // How far the lid slides toward its hinge so its top edge stands where FRONT has it.\n    slide: LIFT + PIVOT[1] - PIVOT[0] + DEPTH - share(SHOWN),\n    foot: {\n      x: HALF_WIDTH / k(front),\n      half: share(FOOT_HALF) / k(front),\n      front,\n      deep,\n      low: share(FACE + FOOT_HIGH) / k(front) - thick,\n      high: thick - share(FACE - 0.3) / k(front - deep),\n      taper: share(FOOT_TAPER) / k(front),\n    },\n  };\n}\n\n/**\n * The eye's camera at `flat`: FRONT's at 1, level with the deck and as far\n * as `fit` found, drawing FRONT's scene across the eye's own; the eye's own\n * at 0. Its distance and its scale ease by ratio, so the laptop's size on\n * screen changes evenly.\n */\nfunction eased(options: CameraOptions, pitch: number, flat: number, box?: Boxed) {\n  const eye = turned(options, { yaw: 0, pitch }, box);\n  const { far, plane } = (fitted ??= fit());\n  const width = options.width ?? 100;\n  const scale = width / share(100 + 2 * OVERHANG);\n  const ratio = (from: number, to: number) => from ** (1 - flat) * to ** flat;\n  const distance = ratio(eye.perspective + eye.back, far);\n  const lens = distance * ratio(eye.perspective / (eye.perspective + eye.back), (scale * (far - plane)) / far);\n  const [middle, level] = [width / 2 + (box?.x ?? 0), share(SHOWN) * scale + (box?.y ?? 0)];\n  return camera({\n    width: eye.width,\n    height: eye.height,\n    perspective: lens,\n    back: distance - lens,\n    originX: lerp(eye.originX, middle, flat),\n    originY: lerp(eye.originY, level, flat),\n    anchorX: lerp(eye.anchorX, middle, flat),\n    anchorY: lerp(eye.anchorY, level, flat),\n    pitch: eye.pitch * (1 - flat),\n  });\n}\n\n/** A convex slab between `rings`, from one face to the other, as the eye sees it: its outline, and the side faces that face the eye as path data for each of `TONES`, or of `SHADES` when `real`. */\nconst slab = (rings: readonly (readonly Solid[])[], pose: Pose, eye: Camera, middle: Solid, real: boolean) =>\n  banded(rings, pose, eye, middle, real ? SHADES : TONES.length, (normal) => (real ? glint : lit)(eye, normal));\n\n/** A box in FRONT's hundredths of the lid's width, from the lid's top edge down, in the lid's plane. */\nconst onLid = ([left, top, right, bottom]: Box): Box => [share(left) - HALF_WIDTH, share(top) - DEPTH, share(right) - HALF_WIDTH, share(bottom) - DEPTH];\nconst BEZEL_BOX: Box = [-HALF_WIDTH + RIM, -DEPTH + RIM, HALF_WIDTH - RIM, -RIM];\n\n/** Everything a frame of a free laptop draws: the paths by name, the tones and weights that change, the order of its parts and the glass's pose. */\nfunction free(options: CameraOptions, OPEN: number, pose: TrickPose, box?: Boxed, real = false) {\n  const { thick: fat, round: fillet, slide, foot, face: kh } = (fitted ??= fit());\n  const flat = pose.flat;\n  // Realistic, the lid is always the machine's own, as FRONT measures it, whatever the camera.\n  const trim = real ? 1 : flat;\n  const shade = real ? glint : lit;\n  const ground = eased(options, pose.pitch, flat, box);\n  const turn = combine(rotateX(-90), rotateY(pose.yaw), rotateX(90), rotateX(pose.roll));\n  const eye = moved(ground, turn, pose.scale, [pose.x * WIDTH, -pose.y * WIDTH], MIDDLE);\n  const project = (points: readonly Solid[]) => shape(points.map(eye.project));\n  // Facing the eye by a margin, so a face seen edge on, as the deck is level with FRONT's eye, does not draw.\n  const shows = (point: Solid, normal: Solid) => {\n    const toward = subtract([eye.originX, eye.originY, eye.perspective], eye.toView(point));\n    return dot(rotate(eye.rotation, normal), toward) > 1e-4 * Math.hypot(...normal) * Math.hypot(...toward);\n  };\n  const mix = (a: Box, b: Box): Box => [lerp(a[0], b[0], trim), lerp(a[1], b[1], trim), lerp(a[2], b[2], trim), lerp(a[3], b[3], trim)];\n\n  // The base: its thickness and the round of its underside's edge are FRONT's at flat 1.\n  const thick = lerp(BASE, fat, flat);\n  // Realistic, the underside's edge is round in every look, about 4 mm.\n  const round = real ? lerp(0.85, fillet, flat) : fillet * flat;\n  const arc = round > 0.01 ? (real ? [0, 1, 2, 3, 4] : [0, 1, 2, 3, 4, 5, 6]).map((step, _, all) => (Math.PI / 2 / (all.length - 1)) * step) : [Math.PI / 2];\n  const rings = [at(inset(0, COARSE), 0), ...arc.map((turn) => at(inset(round * (1 - Math.cos(turn)), COARSE), round - thick - round * Math.sin(turn)))];\n  const base = slab(rings, { position: [0, 0, 0], rotation: IDENTITY }, eye, [0, 0, -thick / 2], real);\n  const top = shows([0, 0, 0], [0, 0, 1]);\n  const under = shows([0, 0, -thick], [0, 0, -1]);\n  const back = shows([0, -HALF_DEPTH, -thick / 2], [0, -1, 0]);\n  const deck = top ? look(eye, OPEN).DECK : null;\n  const bottom = (ring: readonly Flat[]) => (under ? project(at(ring, -thick)) : \"\");\n  // FRONT's thumb scoop on the base's front face, its walls quarter ellipses `end` across down to a flat floor.\n  const QUARTER = [0, 1, 2, 3, 4, 5, 6].map((step) => (Math.PI / 12) * step);\n  const scoop = (end: number, inset: number) => {\n    const [left, right, across, down] = [share(inset - SCOOP_HALF), share(SCOOP_HALF - inset), share(end), share(SCOOP_DEPTH)];\n    const wall = (x: number, v: number): Solid => [x / kh, HALF_DEPTH, -v / kh];\n    return project([\n      ...QUARTER.map((turn) => wall(left + across - across * Math.cos(turn), down * Math.sin(turn))),\n      ...QUARTER.map((turn) => wall(right - across + across * Math.sin(turn), down * Math.cos(turn))),\n    ]);\n  };\n  const rear = (left: number, right: number) => project([left, right, right, left].map((x, index): Solid => [x, -HALF_DEPTH, index < 2 ? 0 : -1.1]));\n\n  // The lid opens as far as the eye's own lid, or upright at flat 1, and its\n  // corners and its face's rings go from the eye's own to FRONT's.\n  const { lidWorld } = joint(lerp(OPEN, -90, flat));\n  const closed = 1 - pose.lid;\n  const lid = compose(lidWorld(closed), { position: [0, slide * flat, 0], rotation: IDENTITY });\n  const radii: Flat = [lerp(ROUND, share(LID_ROUND), trim), lerp(ROUND, 1.2, trim)];\n  const ring = (step?: number) => rounded([-HALF_WIDTH, -DEPTH, HALF_WIDTH, 0], radii, [], [], step);\n  const coarse = ring(COARSE);\n  const skirt = LIFT * clamp((closed - 0.9) / 0.1, 0, 1);\n  const shell = slab([at(coarse, skirt), at(coarse, 0), at(coarse, -LID)], lid, eye, [0, -DEPTH / 2, -LID / 2], real);\n  const normal = rotate(lid.rotation, [0, 0, 1]);\n  const facing = shows(place(lid, [0, -DEPTH / 2, 0]), normal);\n  const outer = shows(place(lid, [0, -DEPTH / 2, -LID]), [-normal[0], -normal[1], -normal[2]]);\n  const plate = (box: Box, [high, low]: Flat, z = 0) => (facing || z ? project(at(rounded(box, [high, low]), z).map((point) => place(lid, point))) : \"\");\n  const ringed = (inset: number, bottom: number, low: number) =>\n    plate(mix(BEZEL_BOX, onLid([inset, inset, 100 - inset, bottom])), [lerp(ROUND - RIM, share(LID_ROUND - inset), trim), lerp(ROUND - RIM, share(low), trim)]);\n  const face = plate([-HALF_WIDTH, -DEPTH, HALF_WIDTH, 0], radii);\n  const lidPoint = (y: number) => eye.project(place(lid, [0, y, 0]));\n\n  // The glass, FRONT's display at flat 1: the eye's own glass mapped onto it, so the content lays out the same.\n  const [left, high, right, low] = onLid(DISPLAY);\n  const want = { x: lerp(GLASS.x, left, trim), y: lerp(GLASS.y, high, trim), width: lerp(GLASS.width, right - left, trim), height: lerp(GLASS.height, low - high, trim) };\n  // Shut over the deck, or seen past the base from under the deck, the\n  // display is hidden even where it faces the eye, so the glass goes with it.\n  const [sx, sy] = facing && !under && closed < 0.98 ? [want.width / GLASS.width, want.height / GLASS.height] : [0, 0];\n\n  // The feet: under the base's corners, the front two where FRONT draws them.\n  // Realistic, they are round, each in a bright ring on the underside.\n  const deep = real ? 2 * foot.half : foot.deep;\n  const soles: string[] = [];\n  const bezels: string[] = [];\n  const feet = [-1, 1].flatMap((side) =>\n    [foot.front - deep, -foot.front].map((near) => {\n      const pad: Box = [side * foot.x - foot.half, near, side * foot.x + foot.half, near + deep];\n      const sole: Box = [pad[0] + foot.taper, pad[1] + foot.taper, pad[2] - foot.taper, pad[3] - foot.taper];\n      const [high, low] = real ? [foot.half, foot.half - foot.taper] : [0.6, 0.5];\n      const ground = at(rounded(sole, [low, low], [], [], COARSE), -thick - foot.low);\n      if (real) {\n        soles.push(under ? project(ground) : \"\");\n        bezels.push(under ? project(at(rounded([pad[0] - 0.3, pad[1] - 0.3, pad[2] + 0.3, pad[3] + 0.3], [high + 0.3, high + 0.3], [], [], COARSE), -thick)) : \"\");\n      }\n      return shape(hull([...at(rounded(pad, [high, high], [], [], COARSE), foot.high - thick), ...ground].map(eye.project)));\n    }),\n  );\n\n  // The shadow lies on the ground, so it turns with the laptop but moves only across.\n  const shadow = (outline: readonly Flat[]) =>\n    shape(\n      hull(\n        outline.map(([x, y]) => {\n          const [gx, gy] = add(rotate(turn, subtract([x, y, -BASE], MIDDLE)), MIDDLE);\n          const [sx, sy] = ground.project([gx, gy, GROUND]);\n          return [sx + pose.x * WIDTH, sy];\n        }),\n      ),\n    );\n\n  const ahead = rotate(eye.rotation, [0, 1, 0]);\n  const parts = under ? [\"base\", \"feet\"] : [\"feet\", \"base\"];\n  const outerFace = outer ? plate([-HALF_WIDTH, -DEPTH, HALF_WIDTH, 0], radii, -LID) : \"\";\n  const span = (from: Solid, to: Solid) => {\n    const [[x1, y1], [x2, y2]] = [eye.project(from), eye.project(to)];\n    return { x1: fixed(x1), y1: fixed(y1), x2: fixed(x2), y2: fixed(y2) };\n  };\n  const sheet = (from: Solid, to: Solid) => gradientSpace(eye, from, to);\n  // The realistic look's extra parts: the underside's intake slots along its\n  // sides and its eight screws, the feet's soles and rings, and copies of the\n  // faces that carry a sheen or a highlight.\n  const slit = (side: number) => {\n    const x = side * (HALF_WIDTH - 0.43 * round);\n    return project(at(rounded([x - 0.12, -0.2 * DEPTH, x + 0.12, 0.25 * DEPTH], [0.12, 0.12], [], [], COARSE), 0.18 * round - thick));\n  };\n  const screw = ([x, y]: Flat) => {\n    const [sx, sy] = eye.project([x, y, -thick]);\n    return `M${cut(sx)} ${cut(sy)}h0`;\n  };\n  const extra = real\n    ? {\n        \"base.sheen\": deck?.top ?? \"\",\n        \"base.edge\": deck?.top ?? \"\",\n        \"base.slits\": under ? slit(-1) + slit(1) : \"\",\n        \"base.screws\": under ? [-29.6, -11.9, 11.9, 29.6].flatMap((x) => [screw([x, 1.4 - HALF_DEPTH]), screw([x, HALF_DEPTH - 1.8])]).join(\"\") : \"\",\n        \"base.underSheen\": bottom(inset(round)),\n        \"lid.sheen\": outerFace,\n        \"lid.edge\": outerFace,\n        ...Object.fromEntries(soles.map((d, index) => [`sole.${index}`, d])),\n        ...Object.fromEntries(bezels.map((d, index) => [`bezel.${index}`, d])),\n      }\n    : {};\n  return {\n    camera: eye,\n    glass: compose(lid, { position: [want.x - sx * GLASS.x, want.y - sy * GLASS.y, 0], rotation: [sx, 0, 0, 0, sy, 0, 0, 0, 1] }),\n    /** The parts far to near: the lid in front once its back faces the eye, the feet once the underside does. */\n    order: outer ? [...parts, \"lid\"] : [\"lid\", ...parts],\n    /** How much FRONT's tones show on the base's front: all at flat 1 while it faces the eye. */\n    front: flat * clamp((ahead[2] / Math.hypot(...ahead) - 0.6) / 0.4, 0, 1),\n    /** The tones of the deck and the underside, as indexes into `TONES`, or `SHADES` when realistic. */\n    deckTone: shade(eye, [0, 0, 1]),\n    underTone: shade(eye, [0, 0, -1]),\n    outerTone: shade(eye, [-normal[0], -normal[1], -normal[2]]),\n    /** How far the keys' bevel highlight stands from them on screen, toward the deck's back. */\n    bevel: span([0, -7.7, 0], [0, -7.8, 0]),\n    /** The realistic look's sheens, each as its own space on screen: on the deck, the underside and down the thumb's scoop from back to front, on the lid's back and its glass from its top edge to its hinge. */\n    sheen: {\n      top: sheet([0, -HALF_DEPTH, 0], [0, HALF_DEPTH, 0]),\n      under: sheet([0, -HALF_DEPTH, -thick], [0, HALF_DEPTH, -thick]),\n      outer: sheet(place(lid, [0, -DEPTH, -LID]), place(lid, [0, 0, -LID])),\n      dish: sheet([0, HALF_DEPTH - SCOOP, 0], [0, HALF_DEPTH, -DIP]),\n      pane: sheet(place(lid, [0, -DEPTH, 0]), place(lid, [0, 0, 0])),\n    },\n    /** Where FRONT's lid edge gradient runs, from the lid's top edge down. */\n    edge: { x1: lidPoint(-DEPTH)[0], y1: lidPoint(-DEPTH)[1], x2: lidPoint(share(LID_ROUND) - DEPTH)[0], y2: lidPoint(share(LID_ROUND) - DEPTH)[1] },\n    d: {\n      \"shadow.far\": shadow(MODEL.ground),\n      \"shadow.near\": shadow(MODEL.contact),\n      \"base.hull\": base.hull,\n      // The base's front, for FRONT's tones: straight on, the whole of its outline.\n      \"base.front\": shape(hull(rings.flatMap((ring) => ring.filter(([, y]) => y > HALF_DEPTH - ROUND - 0.01)).map(eye.project))),\n      ...Object.fromEntries(base.tones.map((d, step) => [`base.${step}`, d])),\n      \"base.top\": deck?.top ?? \"\",\n      \"base.under\": bottom(inset(round)),\n      \"base.seam\": bottom(inset(round + 1.1)),\n      \"base.vent\": bottom(rounded([-18, 2.6 - HALF_DEPTH, 18, 3.2 - HALF_DEPTH], [0.3, 0.3])),\n      \"base.cover\": back ? rear(BARREL - CUT / 2, CUT / 2 - BARREL) : \"\",\n      \"base.ends\": back ? rear(-CUT / 2, BARREL - CUT / 2) + rear(CUT / 2 - BARREL, CUT / 2) : \"\",\n      \"base.lines\": (deck?.lines ?? base.hull) + bottom(inset(round)),\n      \"base.scoop\": scoop(SCOOP_END, 0),\n      \"base.walls\": scoop(SCOOP_END, 0) + scoop(SCOOP_END - SCOOP_WALL, SCOOP_WALL),\n      \"deck.well\": deck?.well ?? \"\",\n      \"deck.keys\": deck?.keys ?? \"\",\n      \"deck.lockKey\": deck?.lockKey ?? \"\",\n      \"deck.lockKeyRing\": deck?.lockKeyRing ?? \"\",\n      \"deck.trackpad\": deck?.trackpad ?? \"\",\n      \"deck.scoop\": deck?.scoop ?? \"\",\n      \"deck.hinge\": deck?.hinge ?? \"\",\n      \"deck.barrels\": deck?.barrels ?? \"\",\n      ...Object.fromEntries([0, 1, 2].map((coarser) => [`holes.${coarser}`, deck?.holes[coarser] ?? \"\"])),\n      ...Object.fromEntries(feet.map((d, index) => [`foot.${index}`, d])),\n      \"lid.hull\": shell.hull,\n      ...Object.fromEntries(shell.tones.map((d, step) => [`lid.${step}`, d])),\n      \"lid.face\": face,\n      \"lid.outer\": outerFace,\n      \"lid.rim\": plate(onLid([RIM_LINE, RIM_LINE, 100 - RIM_LINE, DEEP]), [share(LID_ROUND - RIM_LINE), radii[1]]),\n      \"lid.gasket\": ringed(GASKET, SHOWN, HINGE_ROUND + BLACK - GASKET),\n      \"lid.gasketLine\": ringed(GASKET_LINE, SHOWN, HINGE_ROUND + BLACK - GASKET_LINE),\n      \"lid.glass\": ringed(BLACK, SHOWN, HINGE_ROUND),\n      \"lid.hingeLine\": plate(onLid([BLACK, HINGE_LINE, 100 - BLACK, SHOWN]), [0, share(HINGE_ROUND)]),\n      \"lid.hinge\": plate(onLid([BLACK, HINGE, 100 - BLACK, SHOWN]), [0, share(HINGE_ROUND)]),\n      \"lid.lines\": shell.hull + (facing ? face : \"\"),\n      ...extra,\n    },\n  };\n}\n\n/** A frame of a free laptop, as `trick` gives it. */\nexport type Trick = ReturnType<typeof free>;\n\n// The kickflip's moves, one track a field, each eased its own way: the lid\n// shuts, the laptop comes down to the ground, crouches with its front drawn\n// back, pops up flipping and turning, hangs at the top, falls, catches the\n// flip just before the ground, lands with a small bounce, and opens. The eye\n// starts to rise as it comes down, so the shut slab shows its top before the\n// pop instead of an edge level with the eye.\nconst LID_TRACK = [{ at: 0, lid: 1 }, { at: 0.12, lid: 0, ease: \"ease\" }, { at: 0.84, lid: 0 }, { at: 1, lid: 1, ease: [0.4, 0, 0.2, 1] }] as const;\nconst Y_TRACK = [\n  { at: 0.12, y: 0.6 },\n  { at: 0.3, y: 0, ease: [0.5, 0, 0.3, 1] },\n  { at: 0.36, y: -0.03, ease: [0.3, 0, 0.3, 1] },\n  { at: 0.5, y: 0.5, ease: [0.1, 0.6, 0.35, 1] },\n  { at: 0.62, y: 0, ease: [0.6, 0, 0.85, 0.5] },\n  { at: 0.655, y: 0.035, ease: [0.2, 0.6, 0.4, 1] },\n  { at: 0.78, y: 0, ease: \"settle\" },\n] as const;\nconst ROLL_TRACK = [{ at: 0.3, roll: 0 }, { at: 0.37, roll: -12, ease: [0.3, 0, 0.4, 1] }, { at: 0.6, roll: 360, ease: [0.3, 0.15, 0.35, 1.04] }] as const;\nconst YAW_TRACK = [{ at: 0.37, yaw: 0 }, { at: 0.6, yaw: 360, ease: [0.35, 0.1, 0.45, 1] }] as const;\nconst SCALE_TRACK = [\n  { at: 0.3, scale: 1 },\n  { at: 0.36, scale: 0.97, ease: \"ease\" },\n  { at: 0.5, scale: 1.1, ease: [0.2, 0.6, 0.4, 1] },\n  { at: 0.62, scale: 1, ease: [0.5, 0, 0.7, 1] },\n] as const;\nconst FLAT_TRACK = [{ at: 0.12, flat: 1 }, { at: 0.37, flat: 0.65, ease: [0.4, 0, 0.6, 1] }, { at: 0.62, flat: 0, ease: [0.45, 0, 0.4, 1] }] as const;\n\n/**\n * A kickflip with a shove-it for a free laptop, from FRONT's look high over\n * the ground to the eye's own, open on the ground, over a progress from 0 to\n * 1: pass `scrub(t, KICKFLIP)` to the laptop's handle. Each field moves on\n * its own eased track, baked here into short straight keyframes, so one\n * `scrub` reads them all. It leaves `pitch` to the laptop, and `y` starts at\n * 0.6, so give the stage room above it.\n */\nexport const KICKFLIP: readonly ({ at: number; ease: Easing } & Pick<TrickPose, \"lid\" | \"flat\" | \"x\" | \"y\" | \"scale\" | \"roll\" | \"yaw\">)[] = [\n  ...new Set([...Array.from({ length: 241 }, (_, step) => step / 240), ...[LID_TRACK, Y_TRACK, ROLL_TRACK, YAW_TRACK, SCALE_TRACK, FLAT_TRACK].flat().map(({ at }) => at)]),\n]\n  .sort((a, b) => a - b)\n  .map((at) => ({\n    at,\n    ease: \"linear\",\n    lid: fixed(scrub(at, LID_TRACK).lid),\n    flat: fixed(scrub(at, FLAT_TRACK).flat),\n    x: 0,\n    y: fixed(scrub(at, Y_TRACK).y),\n    scale: fixed(scrub(at, SCALE_TRACK).scale),\n    roll: fixed(scrub(at, ROLL_TRACK).roll),\n    yaw: fixed(scrub(at, YAW_TRACK).yaw),\n  }));\n"
    },
    {
      "path": "registry/laptop/src/laptop.tsx",
      "type": "registry:component",
      "target": "@components/vectoys/laptop/laptop.tsx",
      "content": "// vectoys laptop, from https://vectoys.com/r/laptop.json\n// License: MIT. Credit: vectoys (https://vectoys.com)\nimport { clamp, fixed, lerp } from \"@vinena/vectoys\";\nimport { Glare, Gradient, Grain, Screen, Stage, Turntable, mirrored, placeScreen, useNodes, type Nodes, type Stop, type TurnLimits, type TurnOptions } from \"@vinena/vectoys-react\";\nimport { memo, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, type CSSProperties, type ReactNode, type Ref, type SVGProps } from \"react\";\nimport { BEZEL, DEPTH, DESK, FRONT, GLASS, HOLE, PITCH, ROUND, SHADES, TONES, WIDTH, YAW, type LaptopPose, type Perspective, type Posed, type Straight, type Trick, type TrickPose, type View } from \"./geometry\";\n\n// The page's colors, as CSS variables, each with the light tone of the site\n// this laptop was first drawn for, so it shows as meant before they are set.\nconst BG = \"var(--vectoys-bg, #ffffff)\";\nconst HAIRLINE = \"var(--vectoys-hairline, #8a8a8a)\";\nconst BORDER = \"var(--vectoys-border, #d4d4d4)\";\nconst SURFACE_TONE = \"var(--vectoys-surface, #f5f5f5)\";\nconst SHADOW = \"var(--vectoys-shadow, rgba(0, 0, 0, 0.08))\";\n\n// Aluminum lit from above: tops in the page's surface, sides a step darker,\n// the glass's rim a step toward the border. Glass, hinge cover and keys are\n// near black, the hinge's barrels a darker aluminum.\nconst SURFACE: CSSProperties = { fill: SURFACE_TONE };\nconst shade = (share: number): CSSProperties => ({ fill: SHADOW, fillOpacity: share });\nconst SIDE = 0.45;\nconst RIM_TONE = `color-mix(in srgb, ${SURFACE_TONE} 60%, ${BORDER})`;\nconst LENS = `color-mix(in srgb, ${HAIRLINE} 15%, black)`;\nconst HINGE = `color-mix(in srgb, ${HAIRLINE} 6%, black)`;\nconst BARREL = `color-mix(in srgb, ${SURFACE_TONE} 78%, black)`;\nconst WELL = `color-mix(in srgb, ${SURFACE_TONE} 10%, black)`;\nconst KEY = `color-mix(in srgb, ${HAIRLINE} 22%, ${WELL})`;\nconst LOCK_KEY = `color-mix(in srgb, ${HAIRLINE} 12%, ${WELL})`;\n\nconst hairline = (color: string): CSSProperties => ({ stroke: color, fill: \"none\" });\nconst stroke = {\n  vectorEffect: \"non-scaling-stroke\",\n  strokeWidth: 1,\n  strokeLinejoin: \"round\",\n  strokeLinecap: \"round\",\n} as const;\n\n// Which field of holes shows: the full one where a hole is about a device\n// pixel (a scene 560px wide at 2dppx), the half one down to half that, the\n// quarter one below. In a wider `turning` scene the widths grow and the\n// fields take names of their own.\nconst HOLES = \"vectoys-laptop-holes\";\nconst holesShown = (name: string, scene: number) => {\n  const [fine, coarse] = [fixed((560 * scene) / 100), fixed((280 * scene) / 100)];\n  return `\n.${name}-0, .${name}-2 { display: none; }\n@media (min-resolution: 2dppx) { @container (min-width: ${fine}px) { .${name}-0 { display: inline; } .${name}-1 { display: none; } } }\n@media (max-resolution: 1.99dppx) { @container (max-width: ${fixed(fine - 0.02)}px) { .${name}-1 { display: none; } .${name}-2 { display: inline; } } }\n@container (max-width: ${fixed(coarse - 0.02)}px) { .${name}-1 { display: none; } .${name}-2 { display: inline; } }\n`;\n};\n\n/** The base, which never changes as the lid moves. */\nconst Base = memo(function Base({ id, deck: DECK, holes: name, node }: { id: string; deck: Posed[\"DECK\"]; holes: string; node: Nodes[\"ref\"] }) {\n  return (\n    <>\n      <path ref={node(\"deck.ground\")} d={DECK.ground} filter={`url(#${id}-far)`} style={shade(1)} />\n      <path ref={node(\"deck.contact\")} d={DECK.contact} filter={`url(#${id}-near)`} style={shade(1)} />\n      <path ref={node(\"deck.hull\")} d={DECK.hull} style={SURFACE} />\n      <path ref={node(\"deck.hull\")} d={DECK.hull} style={shade(SIDE)} />\n      <path ref={node(\"deck.hinge\")} d={DECK.hinge} style={{ fill: HINGE }} />\n      <path ref={node(\"deck.barrels\")} d={DECK.barrels} style={{ fill: BARREL }} />\n      <path ref={node(\"deck.top\")} d={DECK.top} style={SURFACE} />\n      <g clipPath={`url(#${id}-deck)`}>\n        {DECK.holes.map((holes, coarser) => (\n          <path\n            key={coarser}\n            ref={node(`holes.${coarser}`)}\n            d={holes}\n            className={`${name}-${coarser}`}\n            strokeWidth={HOLE * 2 ** coarser}\n            strokeLinecap=\"round\"\n            style={{ stroke: WELL, fill: \"none\" }}\n          />\n        ))}\n      </g>\n      <path ref={node(\"deck.well\")} d={DECK.well} style={{ fill: WELL }} />\n      <path ref={node(\"deck.keys\")} d={DECK.keys} style={{ fill: KEY }} />\n      <path ref={node(\"deck.lockKey\")} d={DECK.lockKey} style={{ fill: LOCK_KEY }} />\n      <path ref={node(\"deck.lockKeyRing\")} d={DECK.lockKeyRing} {...stroke} style={hairline(KEY)} />\n      <path ref={node(\"deck.trackpad\")} d={DECK.trackpad} style={shade(0.2)} />\n      <path ref={node(\"deck.trackpad\")} d={DECK.trackpad} {...stroke} style={hairline(BORDER)} />\n      <path ref={node(\"deck.scoop\")} d={DECK.scoop} style={shade(1)} />\n      <path ref={node(\"deck.lines\")} d={DECK.lines} {...stroke} style={hairline(HAIRLINE)} />\n    </>\n  );\n});\n\n/**\n * What the drawing refers to, under names of its own so any number of\n * laptops share a page. A free laptop's shadows blur past the stage, the far\n * one by `blur`, which a pose sets.\n */\nconst defs = (id: string, { width, height }: Perspective[\"camera\"], holes: string, blur?: SVGProps<SVGFEGaussianBlurElement>) => {\n  const region = blur ? { x: -width, y: -height, width: 3 * width, height: 3 * height } : { x: \"0\", y: \"0\", width, height };\n  return (\n    <>\n      <style>{holes}</style>\n      <filter id={`${id}-near`} filterUnits=\"userSpaceOnUse\" {...region}>\n        <feGaussianBlur stdDeviation=\"0.4 0.2\" />\n      </filter>\n      <filter id={`${id}-far`} filterUnits=\"userSpaceOnUse\" {...region}>\n        <feGaussianBlur stdDeviation=\"2 0.9\" {...blur} />\n      </filter>\n      <linearGradient id={`${id}-sheen`} x1=\"0\" y1=\"0\" x2=\"0.35\" y2=\"1\">\n        <stop offset=\"0\" style={{ stopColor: `color-mix(in srgb, ${SURFACE_TONE} 70%, ${BG})` }} />\n        <stop offset=\"1\" style={{ stopColor: SURFACE_TONE }} />\n      </linearGradient>\n    </>\n  );\n};\n\n// The glass's top corners follow the lid's, inside the bezel, as shares of\n// the glass so they hold at any size the content lays out at.\nconst CORNER = ROUND - BEZEL;\nconst ACROSS = `${fixed((CORNER / GLASS.width) * 100)}%`;\nconst DOWN = `${fixed((CORNER / GLASS.height) * 100)}%`;\nconst GLASS_STYLE: CSSProperties = {\n  overflow: \"hidden\",\n  background: \"#0d0d0d\",\n  borderRadius: `${ACROSS} ${ACROSS} 0 0 / ${DOWN} ${DOWN} 0 0`,\n  clipPath: `inset(0 round ${ACROSS} ${ACROSS} 0 0 / ${DOWN} ${DOWN} 0 0)`,\n  containerType: \"inline-size\",\n};\nconst FILL: CSSProperties = { position: \"absolute\", inset: 0 };\n\n/** Moves a laptop to a new pose at once, without a render; any field left out stays as it is. */\nexport type LaptopHandle = { set(pose: Partial<TrickPose>): void };\n\n/** How the laptop is drawn: `simple` in the page's own tones, or `realistic`, the machine's own silver with its edges, keys and feet. */\nexport type LaptopLook = \"simple\" | \"realistic\";\n\ntype LaptopProps = TurnOptions & {\n  /** How far the lid is open, from 0 (shut on the base) to 1 (open); open by default. `FRONT` is always open. */\n  lid?: number;\n  /** @deprecated Use `lid`. How far the lid has closed, from 0 (open) to 1 (shut); `lid` wins when both are given. */\n  closed?: number;\n  /** How far the laptop turns about its own upright axis, from -35 to 35 degrees; positive turns its display toward the page's right. */\n  yaw?: number;\n  /** How high the eye looks down from, from 5 to 30 degrees; the eye's own by default, 15 for `DESK` and 12 for `PHONE`. */\n  pitch?: number;\n  /**\n   * Draws in the eye's fixed box that holds every pose, `LAPTOP_BOX`, in\n   * place of its own scene, so the stage keeps one size while the laptop\n   * turns, opens and closes. Off by default, which keeps the eye's own view\n   * and crop.\n   */\n  turning?: boolean;\n  /**\n   * How much the laptop looks as `FRONT` draws it, from 0, the eye's own\n   * look, to 1, FRONT's straight-on shot: there, open and with every angle\n   * 0, it draws as `<Laptop eye={FRONT}>` does with FRONT's scene across the\n   * eye's own, from its top. Between, the camera, the shapes and the tones\n   * ease from one to the other in any pose. At 1 a drag up or down rolls it.\n   */\n  flat?: number;\n  /** A move across the drawing, in shares of the laptop's width, `WIDTH`; positive is to the right. */\n  x?: number;\n  /** A move up the drawing in shares of the laptop's width: its height over the ground, where its shadow stays. */\n  y?: number;\n  /** How large it draws about its middle, 1 at rest. */\n  scale?: number;\n  /** A turn about the laptop's long axis, from left to right, in degrees; positive lifts its front. */\n  roll?: number;\n  /**\n   * Lets the laptop turn any way: no range holds `yaw`, `pitch` or `roll`,\n   * and the drawing may leave its box, so size the box round where it goes.\n   * With this or any of `flat`, `x`, `y`, `scale` and `roll`, the laptop is\n   * drawn as a closed slab from every side, its underside and back too.\n   */\n  free?: boolean;\n  /** The eye that sees it: `DESK`, `PHONE` or `FRONT`; `DESK` by default. */\n  eye?: View;\n  /**\n   * `simple` by default, in the page's tones. `realistic` draws the machine\n   * as it is, silver or as --vectoys-tint filters it, with its keys, grilles, feet and underside.\n   * It turns and moves as `free` does, and changing it lets go of the\n   * handle's pose. `FRONT` draws the same in both.\n   */\n  look?: LaptopLook;\n  /** The laptop's name for assistive technology. */\n  label?: string;\n  /** Live content on the display, laid out as on a screen about 1280 pixels wide. */\n  children?: ReactNode;\n  /**\n   * A handle whose `set({ lid, yaw, pitch })` redraws the laptop in a new\n   * pose without a render, for a pose that moves every frame, as a scroll\n   * does, with a drag's turn added. It owns the pose from its first `set`\n   * until a pose prop, `free`, `look` or a drag changes it. `flat`, `x`, `y`,\n   * `scale` and `roll` move only a laptop given `free` or one of them.\n   * `FRONT` does not move.\n   */\n  ref?: Ref<LaptopHandle>;\n};\n\n/**\n * A 14 inch laptop seen from the eye you pass (DESK, PHONE or FRONT): one SVG\n * of its projected outline, and the display's glass laid onto the lid with\n * live content on it, hidden while the lid faces away. A drag or the arrow\n * keys turn it, but for FRONT, and a button shows it full screen.\n */\nexport const Laptop = memo(function Laptop({ draggable, fullscreen = true, limits, release = \"stay\", ref, ...props }: LaptopProps) {\n  const { yaw = 0, pitch, free, eye = DESK, label = \"Laptop\" } = props;\n  const uid = useId();\n  const inner = useRef<LaptopHandle>(null);\n  const turned = useRef({ yaw: 0, pitch: 0 });\n  // A drag up looks from lower, so it takes from the eye's height, or at flat 1 adds to the roll.\n  useImperativeHandle(ref, () => ({\n    set: (next) => {\n      const { yaw, pitch } = turned.current;\n      const flat = props.flat === 1;\n      inner.current?.set({ ...next, ...(next.yaw !== undefined && { yaw: next.yaw + yaw }), ...(!flat && next.pitch !== undefined && { pitch: next.pitch - pitch }), ...(flat && next.roll !== undefined && { roll: next.roll + pitch }) });\n    },\n  }));\n  const front = \"art\" in eye;\n  // Held to a laptop's ranges unless free, so a drag stops where its pose does.\n  const rest = { lid: 0, yaw, pitch: pitch ?? (front ? 15 : eye.pitch) };\n  const at = free ? rest : held(rest);\n  const range: Required<TurnLimits> = free ? { yaw: [-180, 180], pitch: [-35, 35] } : { yaw: [-YAW - at.yaw, YAW - at.yaw], pitch: [at.pitch - PITCH[1], at.pitch - PITCH[0]] };\n  return (\n    <Turntable mode=\"camera\" label={label} draggable={draggable ?? !front} fullscreen={fullscreen} release={release} limits={{ ...range, ...limits }}>\n      {(turn) => (\n        <>\n          <Hold to={turned} turn={turn} />\n          <Drawn\n            {...props}\n            uid={uid}\n            {...(ref && { ref: inner })}\n            {...(turn.yaw !== 0 && { yaw: at.yaw + turn.yaw })}\n            {...(turn.pitch !== 0 && (props.flat === 1 ? { roll: (props.roll ?? 0) + turn.pitch } : { pitch: at.pitch - turn.pitch }))}\n          />\n        </>\n      )}\n    </Turntable>\n  );\n});\n\n// After the commit, so a render React drops leaves the handle's turn alone.\nconst Hold = ({ to, turn }: { to: { current: object }; turn: object }) => (useLayoutEffect(() => void (to.current = turn)), null);\n\n/** The laptop at one pose, under the ids `uid` makes. */\nfunction Drawn({ uid, ...props }: Omit<LaptopProps, keyof TurnOptions> & { uid: string }) {\n  const { lid, closed = 0, yaw, pitch, flat, x, y, scale, roll, free = false, turning = false, eye = DESK, look = \"simple\", children, label = \"Laptop\", ref } = props;\n  const real = look === \"realistic\";\n  if (\"art\" in eye) {\n    return (\n      <Front uid={uid} eye={eye} label={label} handle={ref}>\n        {children}\n      </Front>\n    );\n  }\n  if (real || free || [flat, x, y, scale, roll].some((value) => value !== undefined)) {\n    const pose = loose({ lid: lid ?? 1 - closed, yaw: yaw ?? 0, pitch: pitch ?? eye.pitch, flat: flat ?? 0, x: x ?? 0, y: y ?? 0, scale: scale ?? 1, roll: roll ?? 0 }, free);\n    return (\n      <Free uid={uid} pose={pose} free={free} real={real} turning={turning} eye={eye} label={label} handle={ref}>\n        {children}\n      </Free>\n    );\n  }\n  // With none of the pose's props, a handle or `turning`, the markup is the\n  // eye's own still drawing, as it always was; otherwise every part a pose\n  // can show or hide is drawn, so the handle has it to write to.\n  const live = turning || lid !== undefined || yaw !== undefined || pitch !== undefined || ref != null;\n  const pose = held({ lid: lid ?? 1 - closed, yaw: yaw ?? 0, pitch: pitch ?? eye.pitch });\n  return (\n    <Angled uid={uid} pose={pose} closed={lid === undefined ? closed : 1 - pose.lid} live={live} turning={turning} eye={eye} label={label} handle={ref}>\n      {children}\n    </Angled>\n  );\n}\n\n/** A pose held to the ranges a laptop takes. */\nconst held = ({ lid, yaw, pitch }: LaptopPose): LaptopPose => ({ lid: clamp(lid, 0, 1), yaw: clamp(yaw, -YAW, YAW), pitch: clamp(pitch, ...PITCH) });\n\nconst same = (a: LaptopPose, b: LaptopPose) => a.lid === b.lid && a.yaw === b.yaw && a.pitch === b.pitch;\n\n/** The lid shadow's gradient, along the way toward the deck's front. */\nconst along = ({ ahead }: Posed, shadow: { from: number; to: number } | null) => {\n  const [from, to] = shadow ? [shadow.from, shadow.to] : [0, 0];\n  return { x1: fixed(ahead[0] * from), x2: fixed(ahead[0] * to), y1: fixed(ahead[1] * from), y2: fixed(ahead[1] * to) };\n};\n\n/**\n * Writes a pose straight into the drawing, as a render of it would: the\n * paths, the lid's mask and shadow, which face shows, and the glass. The\n * base is written only when the turn or the eye's height changes.\n */\nfunction painter(eye: Perspective, turning: boolean, nodes: Nodes, id: string) {\n  let shown: { view: Posed; closed: number } | null = null;\n  const write = (name: string, attribute: string, value: string | null) => {\n    for (const node of nodes.all(name)) {\n      if (value === null) node.removeAttribute(attribute);\n      else node.setAttribute(attribute, value);\n    }\n  };\n  return {\n    /** What the page shows after a render, so a pose after it writes only what changed. */\n    rendered(view: Posed, closed: number) {\n      shown = { view, closed };\n    },\n    draw({ lid, yaw, pitch }: LaptopPose) {\n      const view = shown && shown.view.camera.yaw === yaw && shown.view.camera.pitch === -pitch ? shown.view : eye.at({ yaw, pitch }, turning);\n      const closed = 1 - lid;\n      if (shown && shown.view === view && shown.closed === closed) return;\n      if (view !== shown?.view) {\n        const { holes, ...paths } = view.DECK;\n        for (const [name, d] of Object.entries(paths)) write(`deck.${name}`, \"d\", d);\n        write(\"lid.shade\", \"d\", paths.top);\n        holes.forEach((d, coarser) => write(`holes.${coarser}`, \"d\", d));\n      }\n      const art = view.lidArt(closed);\n      write(\"lid.hull\", \"d\", art.hull);\n      write(\"lid.face\", \"d\", art.face);\n      write(\"lid.bezel\", \"d\", art.bezel ?? \"\");\n      write(\"lid.lines\", \"d\", art.lines);\n      for (const node of nodes.all(\"lid.face\")) if (node instanceof SVGElement) node.style.fill = art.facing ? RIM_TONE : `url(#${id}-sheen)`;\n      write(\"lid.group\", \"mask\", art.cut ? `url(#${id}-lid-cut)` : null);\n      write(\"cut.hidden\", \"d\", art.cut?.hidden ?? \"\");\n      write(\"cut.clear\", \"d\", art.cut?.clear ?? \"\");\n      for (const [name, value] of Object.entries(along(view, art.shadow))) write(\"lid.shadow\", name, String(value));\n      write(\"lid.shade\", \"opacity\", String(fixed(closed)));\n      for (const glass of nodes.all(\"glass\")) if (glass instanceof HTMLElement) placeScreen(glass, view.camera, view.lidWorld(closed), GLASS);\n      shown = { view, closed };\n    },\n  };\n}\n\n/** The laptop in perspective, in a pose. */\nfunction Angled({\n  uid,\n  pose,\n  closed,\n  live,\n  turning,\n  eye,\n  children,\n  label,\n  handle,\n}: {\n  uid: string;\n  pose: LaptopPose;\n  closed: number;\n  live: boolean;\n  turning: boolean;\n  eye: Perspective;\n  label: string;\n  children?: ReactNode;\n  handle?: Ref<LaptopHandle> | undefined;\n}) {\n  const id = `laptop-${eye.id}-${uid.replace(/[^\\w-]/g, \"\")}`;\n  const { yaw, pitch } = pose;\n  const view = useMemo(() => eye.at({ yaw, pitch }, turning), [eye, yaw, pitch, turning]);\n  const { DECK, camera } = view;\n  const lid = useMemo(() => view.lidArt(closed), [view, closed]);\n  const nodes = useNodes();\n  const paint = useMemo(() => painter(eye, turning, nodes, id), [eye, turning, nodes, id]);\n  // The handle's pose, the props' pose it was set over and the painter that\n  // drew it: when the props' pose changes, or the eye or `turning` does, the\n  // props win, drawn whole so nothing of the handle's pose is left behind.\n  const set = useRef<{ pose: LaptopPose; over: LaptopPose; paint: typeof paint } | null>(null);\n  const given = useRef(pose);\n\n  useLayoutEffect(() => {\n    given.current = pose;\n    const hold = set.current;\n    if (hold && (hold.paint !== paint || !same(hold.over, pose))) {\n      set.current = null;\n      paint.draw(pose);\n    } else if (!hold) paint.rendered(view, closed);\n  });\n\n  useImperativeHandle(\n    handle,\n    () => ({\n      set(next) {\n        const from = set.current?.pose ?? given.current;\n        const pose = held({ ...from, ...next });\n        set.current = { pose, over: given.current, paint };\n        paint.draw(pose);\n      },\n    }),\n    [paint],\n  );\n\n  const holes = turning ? `${HOLES}-${eye.id}` : HOLES;\n  const shadow = along(view, lid.shadow);\n  const glass = (\n    <Screen ref={nodes.ref(\"glass\")} camera={camera} pose={view.lidWorld(closed)} rect={GLASS} style={GLASS_STYLE}>\n      <div style={{ ...FILL, background: `linear-gradient(110deg, color-mix(in srgb, ${HAIRLINE} 9%, transparent), transparent 35%)` }} />\n      <div style={FILL}>{children}</div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: \"50%\",\n          top: 0,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          width: \"12.5%\",\n          height: \"3.6%\",\n          transform: \"translateX(-50%)\",\n          borderRadius: \"0 0 0.8cqw 0.8cqw\",\n          background: \"black\",\n          pointerEvents: \"none\",\n        }}\n      >\n        <div style={{ width: \"0.7cqw\", height: \"0.7cqw\", borderRadius: \"50%\", background: LENS }} />\n      </div>\n    </Screen>\n  );\n\n  // Empty, the device is a picture; with live content it is a group, so its\n  // links and buttons stay reachable inside it.\n  return (\n    <Stage role={children == null ? \"img\" : \"group\"} label={label} camera={camera} overlay={glass}>\n      <defs>\n        {defs(id, camera, holesShown(holes, camera.width))}\n        <clipPath id={`${id}-deck`}>\n          <path ref={nodes.ref(\"deck.top\")} d={DECK.top} />\n        </clipPath>\n        {(live || lid.shadow) && (\n          <linearGradient ref={nodes.ref(\"lid.shadow\")} id={`${id}-lid-shadow`} gradientUnits=\"userSpaceOnUse\" {...shadow}>\n            <stop offset=\"0\" style={{ stopColor: SHADOW }} />\n            <stop offset=\"1\" style={{ stopColor: SHADOW, stopOpacity: 0 }} />\n          </linearGradient>\n        )}\n        {/*\n          A mask and not a clip path: the three shapes are painted one over the other, so which way round each\n          outline runs does not matter. As two paths of one clip path the lid went missing for single frames of\n          its swing, at the angles where the outline that clears it happened to run the other way.\n        */}\n        {(live || lid.cut) && (\n          <mask id={`${id}-lid-cut`} maskUnits=\"userSpaceOnUse\" x=\"-50\" y=\"-50\" width={fixed(camera.width + 100)} height={fixed(Math.max(170, camera.height + 100))}>\n            <rect x=\"-50\" y=\"-50\" width={fixed(camera.width + 100)} height={fixed(Math.max(170, camera.height + 100))} fill=\"white\" />\n            <path ref={nodes.ref(\"cut.hidden\")} d={lid.cut?.hidden ?? \"\"} fill=\"black\" />\n            <path ref={nodes.ref(\"cut.clear\")} d={lid.cut?.clear ?? \"\"} fill=\"white\" />\n          </mask>\n        )}\n      </defs>\n      <Base id={id} deck={DECK} holes={holes} node={nodes.ref} />\n      {(live || lid.shadow) && <path ref={nodes.ref(\"lid.shade\")} d={DECK.top} fill={`url(#${id}-lid-shadow)`} opacity={fixed(closed)} />}\n      <g ref={nodes.ref(\"lid.group\")} mask={lid.cut ? `url(#${id}-lid-cut)` : undefined}>\n        <path ref={nodes.ref(\"lid.hull\")} d={lid.hull} style={SURFACE} />\n        <path ref={nodes.ref(\"lid.hull\")} d={lid.hull} style={shade(SIDE)} />\n        {live ? (\n          <>\n            <path ref={nodes.ref(\"lid.face\")} d={lid.face} style={{ fill: lid.facing ? RIM_TONE : `url(#${id}-sheen)` }} />\n            <path ref={nodes.ref(\"lid.bezel\")} d={lid.bezel ?? \"\"} fill=\"black\" />\n          </>\n        ) : lid.facing ? (\n          <>\n            <path d={lid.face} style={{ fill: RIM_TONE }} />\n            {lid.bezel && <path d={lid.bezel} fill=\"black\" />}\n          </>\n        ) : (\n          <path d={lid.face} fill={`url(#${id}-sheen)`} />\n        )}\n        <path ref={nodes.ref(\"lid.lines\")} d={lid.lines} {...stroke} style={hairline(HAIRLINE)} />\n      </g>\n    </Stage>\n  );\n}\n\n// Straight on, the aluminum is the page's finish, silver by default, and\n// every tone a share of it toward black, so a dark finish reads as space\n// black. The realistic look takes `SILVER` in its place.\nconst FINISH = \"var(--vectoys-finish, #f0f1f3)\";\nconst SILVER = \"#f0f1f3\";\nconst tone = (share: number, finish = FINISH) => (share >= 100 ? finish : `color-mix(in srgb, ${finish} ${share}%, black)`);\nconst GASKET = \"#232323\";\nconst GASKET_LINE = \"#2c2c2c\";\nconst HINGE_LINE = \"#1a1a1a\";\n// The base's face from its top down, in its middle: a bright top edge, light\n// over its upper half, darkest where its rounded underside turns away, then a\n// bright lip and a dark bottom edge.\nconst FACE: readonly (readonly [number, number])[] = [\n  [0, 99],\n  [0.012, 99],\n  [0.03, 92],\n  [0.29, 90],\n  [0.37, 88],\n  [0.42, 87],\n  [0.48, 83],\n  [0.5, 80],\n  [0.53, 74],\n  [0.56, 67],\n  [0.61, 60],\n  [0.66, 54],\n  [0.72, 54],\n  [0.77, 56],\n  [0.82, 64],\n  [0.88, 77],\n  [0.92, 89],\n  [0.935, 83],\n  [0.95, 89],\n  [0.96, 82],\n  [0.975, 70],\n  [1, 57],\n];\n// Toward its ends the face takes one even tone, as the light falls off, with\n// a bright streak where the end turns away; as shares of the base's width.\nconst ENDS: readonly (readonly [number, number])[] = [\n  [0, 1],\n  [0.04, 1],\n  [0.07, 0.75],\n  [0.11, 0.5],\n  [0.17, 0.3],\n  [0.25, 0.15],\n  [0.32, 0.05],\n  [0.38, 0],\n];\nconst STREAK: readonly (readonly [number, number])[] = [\n  [0, 0.2],\n  [0.0045, 0.2],\n  [0.008, 0.55],\n  [0.0105, 0.85],\n  [0.0135, 0.7],\n  [0.0195, 0.1],\n  [0.025, 0],\n];\n// The feet: an aluminum ring under the face, a dark line, then the dark pad,\n// round so each is lighter in its middle and at its very sides.\nconst FOOT: readonly (readonly [number, number])[] = [\n  [0, 87],\n  [0.45, 87],\n  [0.5, 13],\n  [0.56, 29],\n  [0.92, 29],\n  [1, 22],\n];\nconst FOOT_ROUND: readonly (readonly [number, number])[] = [\n  [0, 0.08],\n  [0.05, 0.08],\n  [0.14, 0.3],\n  [0.3, 0.12],\n  [0.5, 0],\n];\n\n/** Tones as a gradient's stops, shares of `finish`. */\nconst shares = (tones: readonly (readonly [number, number])[], finish: string) => tones.map(([offset, share]): Stop => [offset, tone(share, finish)]);\n/** One color's opacities as a gradient's stops, mirrored. */\nconst faded = (half: readonly (readonly [number, number])[], color: string) => mirrored(half).map(([offset, opacity]): Stop => [offset, color, opacity]);\n\nconst across = (share: number) => `${fixed(share * 100)}%`;\nconst frontGlass = ({ glass, round }: Straight): CSSProperties => {\n  const [x, y] = [across(round / glass.width), across(round / glass.height)];\n  return { ...GLASS_STYLE, borderRadius: `${x} ${x} 0 0 / ${y} ${y} 0 0`, clipPath: `inset(0 round ${x} ${x} 0 0 / ${y} ${y} 0 0)` };\n};\n\n/**\n * FRONT's tones, for `Front` and for a free laptop at `flat`: `edge` is where\n * the lid's edge darkens, `feet` where the feet's tones run with `foot` as\n * their stops, `soft` the blur on the scoop's walls, `scoop` the shape that\n * clips them and `finish` the aluminum every tone is a share of.\n */\nconst frontDefs = (\n  id: string,\n  edge: Omit<SVGProps<SVGLinearGradientElement>, \"id\">,\n  feet: Omit<SVGProps<SVGLinearGradientElement>, \"id\">,\n  foot: readonly (readonly [number, number])[],\n  soft: number,\n  scoop: ReactNode,\n  finish: string,\n) => (\n  <>\n    <Gradient id={`${id}-face`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\" stops={shares(FACE, finish)} />\n    <Gradient id={`${id}-ends`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\" stops={faded(ENDS, tone(62, finish))} />\n    <Gradient id={`${id}-streak`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\" stops={faded(STREAK, finish)} />\n    <linearGradient id={`${id}-hinge`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n      <stop offset=\"0\" stopColor=\"#161616\" />\n      <stop offset=\"1\" stopColor=\"#030303\" />\n    </linearGradient>\n    <Gradient id={`${id}-edge`} gradientUnits=\"userSpaceOnUse\" {...edge} stops={shares([[0, 66], [1, 73]], finish)} />\n    <Gradient id={`${id}-rim`} gradientUnits=\"userSpaceOnUse\" {...edge} stops={shares([[0, 60], [1, 67]], finish)} />\n    <Gradient id={`${id}-wall`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\" stops={shares([[0, 15], [0.5, 30], [0.85, 75], [1, 90]], finish)} />\n    <Gradient id={`${id}-floor`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\" stops={shares([[0, 96], [0.9, 100]], finish)} />\n    <Gradient id={`${id}-feet`} {...feet} stops={shares(foot, finish)} />\n    <Gradient id={`${id}-foot`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\" stops={faded(FOOT_ROUND, \"black\")} />\n    <filter id={`${id}-soft`}>\n      <feGaussianBlur stdDeviation={soft} />\n    </filter>\n    <clipPath id={`${id}-scoop`}>{scoop}</clipPath>\n  </>\n);\n\n/** The laptop straight on, drawn flat, with live content on its display under the notch. */\nfunction Front({ uid, eye, children, label, handle }: { uid: string; eye: Straight; label: string; children?: ReactNode; handle?: Ref<LaptopHandle> | undefined }) {\n  const id = `laptop-${eye.id}-${uid.replace(/[^\\w-]/g, \"\")}`;\n  useImperativeHandle(handle, () => ({ set() {} }), []);\n  const { art, notch, camera } = eye;\n  const url = (name: string) => `url(#${id}-${name})`;\n\n  const glass = (\n    <Screen camera={camera} pose={eye.pose} rect={eye.glass} style={frontGlass(eye)}>\n      <div style={FILL}>{children}</div>\n      <svg\n        viewBox={`0 0 ${fixed(notch.width)} ${notch.height}`}\n        preserveAspectRatio=\"none\"\n        aria-hidden=\"true\"\n        style={{\n          position: \"absolute\",\n          left: across(notch.left),\n          top: 0,\n          width: across(notch.across),\n          height: across(notch.down),\n          overflow: \"visible\",\n          pointerEvents: \"none\",\n        }}\n      >\n        <path d={notch.path} fill=\"black\" />\n        <circle cx={notch.lens.x} cy={notch.lens.y} r={0.35} fill=\"#111112\" />\n        <circle cx={notch.lens.x} cy={notch.lens.y} r={0.11} fill=\"#1b3048\" />\n        <circle cx={notch.lens.x} cy={fixed(notch.lens.y - 0.04)} r={0.04} fill=\"#2d5c86\" />\n      </svg>\n    </Screen>\n  );\n\n  return (\n    <Stage role={children == null ? \"img\" : \"group\"} label={label} camera={camera} overlay={glass}>\n      <defs>\n        {frontDefs(\n          id,\n          { x1: \"0\", y1: \"0\", x2: \"0\", y2: art.rimFade },\n          { gradientUnits: \"userSpaceOnUse\", x1: \"0\", y1: art.feetTop, x2: \"0\", y2: art.feetBottom },\n          FOOT,\n          0.12,\n          <path d={art.scoop} />,\n          FINISH,\n        )}\n      </defs>\n      {art.feet.map((foot) => (\n        <g key={foot}>\n          <path d={foot} fill={url(\"feet\")} />\n          <path d={foot} fill={url(\"foot\")} />\n        </g>\n      ))}\n      <path d={art.edge} fill={url(\"edge\")} />\n      <path d={art.rim} fill={url(\"rim\")} />\n      <path d={art.gasket} fill={GASKET} />\n      <path d={art.gasketLine} fill={GASKET_LINE} />\n      <path d={art.glass} fill=\"black\" />\n      <path d={art.hingeLine} fill={HINGE_LINE} />\n      <path d={art.hinge} fill={url(\"hinge\")} />\n      <path d={art.base} fill={url(\"face\")} />\n      <path d={art.base} fill={url(\"ends\")} />\n      <path d={art.base} fill={url(\"streak\")} />\n      <path d={art.scoop} fill={url(\"floor\")} />\n      <g clipPath={url(\"scoop\")}>\n        <path d={art.walls} fillRule=\"evenodd\" fill={url(\"wall\")} filter={url(\"soft\")} />\n      </g>\n    </Stage>\n  );\n}\n\n/** A free laptop's pose held to its shares, and a laptop that is not free held to the ranges it takes, its roll as its yaw. */\nconst loose = (pose: TrickPose, free: boolean): TrickPose => ({\n  ...pose,\n  ...(!free && { ...held(pose), roll: clamp(pose.roll, -YAW, YAW) }),\n  lid: clamp(pose.lid, 0, 1),\n  flat: clamp(pose.flat, 0, 1),\n  scale: Math.max(0, pose.scale),\n});\n\nconst FIELDS = [\"lid\", \"yaw\", \"pitch\", \"flat\", \"x\", \"y\", \"scale\", \"roll\"] as const;\n\n// A free laptop is silver whatever the page's colors, as FRONT is: its\n// faces take `TONES` of the finish by how the lamp falls on them, its\n// hairlines a darker share, and the parts that are not aluminum their own.\nconst LINE = hairline(tone(62));\nconst UNDER = tone(TONES[5] ?? 90);\nconst FOOT_PAD = tone(28);\n// The feet's tones run over the foot below the face, from 0.3 of FRONT's\n// foot, its top hidden under the face, so they fit the foot's own box.\nconst FOOT_BOX = FOOT.map(([offset, share]): readonly [number, number] => [fixed((0.3 + offset * 0.6) / 0.9), share]);\n\n// The realistic look's own colors, measured on photos of the silver machine:\n// the aluminum's `SHADES` from the darkest side to a highlight, then its\n// edges, keys, gasket, hinge, feet and underside.\nconst RGB = [\n  [0x6e, 0x6f, 0x71],\n  [0xf3, 0xf4, 0xf6],\n] as const;\nconst SHADE = Array.from({ length: SHADES }, (_, step) => `rgb(${RGB[0].map((from, channel) => Math.round(lerp(from, RGB[1][channel] ?? from, step / (SHADES - 1)))).join(\" \")})`);\nconst REAL = {\n  edge: \"#f6f7f9\",\n  rim: \"#a7a8aa\",\n  well: \"#1c1d1e\",\n  key: \"#29292b\",\n  bevel: \"#5b5c5e\",\n  ring: \"#3d3e40\",\n  holes: \"#8f9092\",\n  pad: \"rgba(0, 0, 0, 0.02)\",\n  padLine: \"#b3b4b6\",\n  hinge: \"#141414\",\n  barrel: \"#77787a\",\n  seam: \"#9d9ea0\",\n  slit: \"#2a2a2d\",\n  screw: \"#626365\",\n  rubber: \"#343536\",\n  sole: \"#454647\",\n  bezel: \"#c9cacc\",\n  turn: \"#6f7072\",\n  near: \"rgba(0, 0, 0, 0.5)\",\n  far: \"rgba(0, 0, 0, 0.2)\",\n};\n\n// The bezel's glass catching the light from above, down the lid from its top, none at flat 1 as on FRONT.\nconst [BRIGHT, DIM, AT] = [0.13, 0.04, 0.25];\nconst PANE: Stop[] = [\n  [0, \"white\", BRIGHT],\n  [AT, \"white\", DIM],\n  [0.6, \"white\", 0],\n];\n// The pane's light at the notch's middle height: some browsers scale a gradient there wrong in 3D.\nconst NOTCH_LIGHT = fixed(lerp(BRIGHT, DIM, (FRONT.glass.y + (FRONT.notch.height * WIDTH) / 200) / DEPTH / AT));\n\n/** The glass's top corners and the notch, from the eye's own at `flat` 0 to FRONT's at 1. */\nfunction glassLook(flat: number) {\n  const [x, y] = [lerp(CORNER / GLASS.width, FRONT.round / FRONT.glass.width, flat), lerp(CORNER / GLASS.height, FRONT.round / FRONT.glass.height, flat)];\n  const radius = `${across(x)} ${across(x)} 0 0 / ${across(y)} ${across(y)} 0 0`;\n  const notch = lerp(0.125, FRONT.notch.across, flat);\n  return {\n    glass: { borderRadius: radius, clipPath: `inset(0 round ${radius})` },\n    notch: { left: across(0.5 - notch / 2), width: across(notch), height: across(lerp(0.036, FRONT.notch.down, flat)) },\n    sheen: { opacity: String(fixed(1 - flat)) },\n  };\n}\n\n/**\n * What a frame of a free laptop writes, by node: path data, `fill` as a\n * style, and the attributes a pose sets on its groups, gradients and blur.\n */\nfunction looks(frame: Trick, { flat, y }: TrickPose, real = false) {\n  const values: Record<string, Record<string, string>> = Object.fromEntries(Object.entries(frame.d).map(([name, d]) => [name, { d }]));\n  const set = (name: string, more: Record<string, string>) => void (values[name] = { ...values[name], ...more });\n  const edge = (value: number) => String(fixed(value));\n  const high = Math.max(0, y);\n  const fill = (step: number) => (real ? (SHADE[step] ?? REAL.edge) : tone(TONES[step] ?? 100));\n  set(\"base.top\", { fill: fill(frame.deckTone) });\n  set(\"base.under\", { fill: fill(frame.underTone) });\n  set(\"lid.outer\", { fill: fill(frame.outerTone) });\n  set(\"lid.gasket\", { fill: real ? GASKET : `color-mix(in srgb, ${GASKET} ${fixed(flat * 100)}%, black)` });\n  set(\"lid.gasketLine\", { fill: real ? GASKET_LINE : `color-mix(in srgb, ${GASKET_LINE} ${fixed(flat * 100)}%, black)` });\n  if (real) {\n    for (const [name, span] of Object.entries(frame.sheen)) set(`sheen.${name}`, Object.fromEntries(Object.entries(span).map(([key, value]) => [key, String(value)])));\n    const { x1, y1, x2, y2 } = frame.bevel;\n    set(\"bevel\", { transform: `translate(${fixed(x2 - x1)} ${fixed(y2 - y1)})` });\n  }\n  set(\"edges\", { x1: edge(frame.edge.x1), y1: edge(frame.edge.y1), x2: edge(frame.edge.x2), y2: edge(frame.edge.y2) });\n  set(\"flat\", { opacity: String(fixed(flat)) });\n  set(\"front\", { opacity: String(fixed(frame.front)) });\n  set(\"lines\", { opacity: String(fixed(1 - flat)) });\n  // Off the ground the shadow fades and spreads, and the contact under the base goes first.\n  set(\"shadow\", { opacity: String(fixed((1 - flat) / (1 + 5 * high))) });\n  set(\"shadow.near\", { opacity: String(fixed(clamp(1 - high / 0.04, 0, 1))) });\n  set(\"blur\", { stdDeviation: `${fixed(2 + 30 * high)} ${fixed(0.9 + 14 * high)}` });\n  return values;\n}\n\n/** Writes a free laptop's frame straight into the drawing, as a render of it would, writing only what changed since the last. */\nfunction trickPainter(eye: Perspective, turning: boolean, nodes: Nodes, real: boolean) {\n  let written = new Map<string, string>();\n  return {\n    /** Forgets what it wrote, after a render that may have written over it. */\n    reset() {\n      written = new Map();\n    },\n    draw(pose: TrickPose) {\n      const frame = eye.trick(pose, turning, real);\n      for (const [name, attributes] of Object.entries(looks(frame, pose, real))) {\n        for (const [attribute, value] of Object.entries(attributes)) {\n          if (written.get(`${name} ${attribute}`) === value) continue;\n          written.set(`${name} ${attribute}`, value);\n          for (const node of nodes.all(name)) {\n            if (attribute === \"fill\" && node instanceof SVGElement) node.style.fill = value;\n            else node.setAttribute(attribute, value);\n          }\n        }\n      }\n      const order = frame.order.join();\n      if (written.get(\"order\") !== order) {\n        written.set(\"order\", order);\n        for (const part of frame.order) for (const node of nodes.all(`part.${part}`)) node.parentNode?.appendChild(node);\n      }\n      const look = glassLook(real ? 1 : pose.flat);\n      for (const glass of nodes.all(\"glass\")) {\n        if (!(glass instanceof HTMLElement)) continue;\n        placeScreen(glass, frame.camera, frame.glass, GLASS);\n        Object.assign(glass.style, look.glass);\n      }\n      for (const notch of nodes.all(\"notch\")) if (notch instanceof SVGElement) Object.assign(notch.style, look.notch);\n      for (const sheen of nodes.all(\"sheen\")) if (sheen instanceof HTMLElement) Object.assign(sheen.style, look.sheen);\n      for (const glare of nodes.all(\"glare\")) if (glare instanceof HTMLElement) glare.style.opacity = String(fixed(1 - pose.flat));\n    },\n  };\n}\n\n/** The laptop free to turn any way and to ease into FRONT's look, in a pose. */\nfunction Free({\n  uid,\n  pose,\n  free,\n  real,\n  turning,\n  eye,\n  children,\n  label,\n  handle,\n}: {\n  uid: string;\n  pose: TrickPose;\n  free: boolean;\n  real: boolean;\n  turning: boolean;\n  eye: Perspective;\n  label: string;\n  children?: ReactNode;\n  handle?: Ref<LaptopHandle> | undefined;\n}) {\n  const id = `laptop-${eye.id}-${uid.replace(/[^\\w-]/g, \"\")}`;\n  const key = FIELDS.map((field) => pose[field]).join();\n  // `key` is the pose's fields, so a new pose object with the same fields keeps the frame.\n  const frame = useMemo(() => eye.trick(pose, turning, real), [eye, turning, real, key]);\n  const values = looks(frame, pose, real);\n  const nodes = useNodes();\n  const paint = useMemo(() => trickPainter(eye, turning, nodes, real), [eye, turning, nodes, real]);\n  const set = useRef<{ pose: TrickPose; over: TrickPose; paint: typeof paint; free: boolean } | null>(null);\n  const given = useRef(pose);\n\n  // After every render the frame is drawn whole, the handle's if it still\n  // holds. Turning `free` on or off lets go of the handle's pose.\n  useLayoutEffect(() => {\n    given.current = pose;\n    const hold = set.current;\n    if (hold && (hold.paint !== paint || hold.free !== free || FIELDS.some((field) => hold.over[field] !== pose[field]))) set.current = null;\n    paint.reset();\n    paint.draw(set.current?.pose ?? pose);\n  });\n\n  useImperativeHandle(\n    handle,\n    () => ({\n      set(next) {\n        const pose = loose({ ...(set.current?.pose ?? given.current), ...next }, free);\n        set.current = { pose, over: given.current, paint, free };\n        paint.draw(pose);\n      },\n    }),\n    [paint, free],\n  );\n\n  const bind = (name: string, style?: CSSProperties) => {\n    const { fill, ...rest } = values[name] ?? {};\n    return { ref: nodes.ref(name), ...rest, style: fill ? { ...style, fill } : style };\n  };\n  const url = (name: string) => `url(#${id}-${name})`;\n  const sides = (part: string) =>\n    real ? SHADE.map((fill, step) => <path key={step} {...bind(`${part}.${step}`, { fill })} />) : TONES.map((share, step) => <path key={step} {...bind(`${part}.${step}`, { fill: tone(share) })} />);\n  const look = glassLook(real ? 1 : pose.flat);\n  const edge = hairline(REAL.edge);\n  const sheen = (name: string, stops: Stop[]) => <Gradient {...bind(`sheen.${name}`)} id={`${id}-${name}`} gradientUnits=\"userSpaceOnUse\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\" stops={stops} />;\n  const grain = url(\"grain\");\n  const flatFeet = (\n    <g {...bind(\"flat\")}>\n      {[0, 1, 2, 3].map((foot) => (\n        <g key={foot}>\n          <path {...bind(`foot.${foot}`)} fill={url(\"feet\")} />\n          <path {...bind(`foot.${foot}`)} fill={url(\"foot\")} />\n        </g>\n      ))}\n    </g>\n  );\n  const frontFace = (\n    <g {...bind(\"front\")}>\n      <path {...bind(\"base.front\")} fill={url(\"face\")} />\n      <path {...bind(\"base.front\")} fill={url(\"ends\")} />\n      <path {...bind(\"base.front\")} fill={url(\"streak\")} />\n      <path {...bind(\"base.scoop\")} fill={url(\"floor\")} />\n      <g clipPath={url(\"scoop\")}>\n        <path {...bind(\"base.walls\")} fillRule=\"evenodd\" fill={url(\"wall\")} filter={url(\"soft\")} />\n      </g>\n    </g>\n  );\n  const holeField = (color: string) => (\n    <g clipPath={url(\"deck\")}>\n      {[0, 1, 2].map((coarser) => (\n        <path key={coarser} {...bind(`holes.${coarser}`, { stroke: color, fill: \"none\" })} className={`${holes}-${coarser}`} strokeWidth={HOLE * 2 ** coarser} strokeLinecap=\"round\" />\n      ))}\n    </g>\n  );\n  const lidHinge = (\n    <g {...bind(real ? \"solid\" : \"flat\")}>\n      <path {...bind(\"lid.hingeLine\")} fill={HINGE_LINE} />\n      <path {...bind(\"lid.hinge\")} fill={url(\"hinge\")} />\n    </g>\n  );\n  const { notch } = FRONT;\n  const holes = turning ? `${HOLES}-${eye.id}` : HOLES;\n\n  const parts = {\n    lid: (\n      <>\n        <path {...bind(\"lid.hull\", { fill: UNDER })} />\n        {sides(\"lid\")}\n        <path {...bind(\"lid.outer\")} />\n        <path {...bind(\"lid.face\", { fill: tone(95) })} />\n        <g {...bind(\"flat\")}>\n          <path {...bind(\"lid.face\")} fill={url(\"edge\")} />\n          <path {...bind(\"lid.rim\")} fill={url(\"rim\")} />\n        </g>\n        <path {...bind(\"lid.gasket\")} />\n        <path {...bind(\"lid.gasketLine\")} />\n        <path {...bind(\"lid.glass\")} fill=\"black\" />\n        {lidHinge}\n        <g {...bind(\"lines\")}>\n          <path {...bind(\"lid.lines\", LINE)} {...stroke} />\n        </g>\n      </>\n    ),\n    base: (\n      <>\n        <path {...bind(\"base.hull\", { fill: UNDER })} />\n        {sides(\"base\")}\n        <path {...bind(\"deck.hinge\", { fill: HINGE })} />\n        <path {...bind(\"deck.barrels\", { fill: tone(78) })} />\n        <path {...bind(\"base.top\")} />\n        {holeField(WELL)}\n        <path {...bind(\"deck.well\", { fill: WELL })} />\n        <path {...bind(\"deck.keys\", { fill: KEY })} />\n        <path {...bind(\"deck.lockKey\", { fill: LOCK_KEY })} />\n        <path {...bind(\"deck.lockKeyRing\", hairline(KEY))} {...stroke} />\n        <path {...bind(\"deck.trackpad\", shade(0.2))} />\n        <path {...bind(\"deck.trackpad\", hairline(tone(84)))} {...stroke} />\n        <path {...bind(\"deck.scoop\", shade(1))} />\n        <path {...bind(\"base.under\")} />\n        <path {...bind(\"base.vent\", { fill: WELL })} />\n        <path {...bind(\"base.seam\", hairline(tone(74)))} {...stroke} />\n        <path {...bind(\"base.cover\", { fill: HINGE })} />\n        <path {...bind(\"base.ends\", { fill: tone(78) })} />\n        {frontFace}\n        <g {...bind(\"lines\")}>\n          <path {...bind(\"base.lines\", LINE)} {...stroke} />\n        </g>\n      </>\n    ),\n    feet: (\n      <>\n        {[0, 1, 2, 3].map((foot) => (\n          <path key={foot} {...bind(`foot.${foot}`, { fill: FOOT_PAD })} />\n        ))}\n        {flatFeet}\n      </>\n    ),\n  };\n\n  // Realistic: the machine's own colors in every pose, the lid's true face\n  // and hinge band always, highlights on the edges and no outlines.\n  const realParts = {\n    lid: (\n      <>\n        <path {...bind(\"lid.hull\", { fill: SHADE[5] })} />\n        {sides(\"lid\")}\n        <path {...bind(\"lid.outer\")} />\n        <path {...bind(\"lid.sheen\")} fill={url(\"outer\")} />\n        <path {...bind(\"lid.sheen\")} fill={grain} />\n        <path {...bind(\"lid.edge\", edge)} {...stroke} />\n        {/* The display side's chamfer, gone at flat 1 as FRONT draws its own: a darker turn off the side, then a bright line on the edge. */}\n        <g {...bind(\"lines\")}>\n          <path {...bind(\"lid.face\", hairline(REAL.turn))} {...stroke} strokeWidth={1.6} />\n        </g>\n        <path {...bind(\"lid.face\", { fill: REAL.rim })} />\n        <g {...bind(\"lines\")}>\n          <path {...bind(\"lid.face\", edge)} {...stroke} strokeWidth={0.7} />\n        </g>\n        <g {...bind(\"flat\")}>\n          <path {...bind(\"lid.face\")} fill={url(\"edge\")} />\n          <path {...bind(\"lid.rim\")} fill={url(\"rim\")} />\n        </g>\n        <path {...bind(\"lid.gasket\")} />\n        <path {...bind(\"lid.gasketLine\")} />\n        <path {...bind(\"lid.glass\")} fill=\"black\" />\n        <g {...bind(\"lines\")}>\n          <path {...bind(\"lid.glass\")} fill={url(\"pane\")} />\n        </g>\n        {lidHinge}\n      </>\n    ),\n    base: (\n      <>\n        <path {...bind(\"base.hull\", { fill: SHADE[5] })} />\n        {sides(\"base\")}\n        <path {...bind(\"deck.hinge\", { fill: REAL.hinge })} />\n        <path {...bind(\"deck.barrels\", { fill: REAL.turn })} />\n        <path {...bind(\"base.top\")} />\n        <path {...bind(\"base.sheen\")} fill={url(\"top\")} />\n        <path {...bind(\"base.sheen\")} fill={grain} />\n        {holeField(REAL.holes)}\n        <path {...bind(\"deck.well\", { fill: REAL.well, stroke: \"black\" })} {...stroke} />\n        {/* Each key's bevel catches the light on its back edge: the keys again, a little toward the deck's back, under them. */}\n        <use {...bind(\"bevel\")} href={`#${id}-keys`} style={{ fill: REAL.bevel, stroke: \"none\" }} />\n        <g style={{ fill: REAL.key, stroke: \"black\" }} strokeWidth={0.07}>\n          <g id={`${id}-keys`}>\n            <path {...bind(\"deck.keys\")} />\n            <path {...bind(\"deck.lockKey\")} />\n          </g>\n        </g>\n        <path {...bind(\"deck.lockKeyRing\", hairline(REAL.ring))} {...stroke} />\n        <path {...bind(\"deck.trackpad\", { fill: REAL.pad })} />\n        <path {...bind(\"deck.trackpad\", hairline(REAL.padLine))} {...stroke} />\n        <path {...bind(\"deck.scoop\")} fill={url(\"dish\")} />\n        <path {...bind(\"base.edge\", edge)} {...stroke} strokeWidth={0.7} />\n        <path {...bind(\"base.under\")} />\n        <path {...bind(\"base.underSheen\")} fill={url(\"under\")} />\n        <path {...bind(\"base.underSheen\")} fill={grain} />\n        <path {...bind(\"base.seam\", hairline(REAL.seam))} {...stroke} />\n        <path {...bind(\"base.slits\", { fill: REAL.slit })} />\n        <path {...bind(\"base.screws\", { stroke: REAL.screw, fill: \"none\" })} strokeWidth={0.32} strokeLinecap=\"round\" />\n        <path {...bind(\"base.cover\", { fill: REAL.hinge })} />\n        <path {...bind(\"base.ends\", { fill: REAL.barrel })} />\n        {frontFace}\n      </>\n    ),\n    feet: (\n      <>\n        {[0, 1, 2, 3].map((foot) => (\n          <g key={foot}>\n            <path {...bind(`bezel.${foot}`, { fill: REAL.bezel })} />\n            <path {...bind(`foot.${foot}`, { fill: REAL.rubber })} />\n            <path {...bind(`sole.${foot}`, { fill: REAL.sole })} />\n          </g>\n        ))}\n        {flatFeet}\n      </>\n    ),\n  };\n  const drawn = real ? realParts : parts;\n\n  const glass = (\n    <Screen ref={nodes.ref(\"glass\")} camera={frame.camera} pose={frame.glass} rect={GLASS} style={{ ...GLASS_STYLE, ...look.glass }}>\n      <div ref={nodes.ref(\"sheen\")} style={{ ...FILL, ...look.sheen, background: `linear-gradient(110deg, color-mix(in srgb, ${HAIRLINE} 9%, transparent), transparent 35%)` }} />\n      <div style={FILL}>{children}</div>\n      {/* Realistic, the glass's glare, none at flat 1, as FRONT has none. */}\n      {real && <Glare ref={nodes.ref(\"glare\")} opacity={fixed(1 - pose.flat)} />}\n      <svg\n        ref={nodes.ref(\"notch\")}\n        viewBox={`0 0 ${fixed(notch.width)} ${notch.height}`}\n        preserveAspectRatio=\"none\"\n        aria-hidden=\"true\"\n        style={{ position: \"absolute\", top: 0, ...look.notch, overflow: \"visible\", pointerEvents: \"none\" }}\n      >\n        <path d={notch.path} fill=\"black\" />\n        {/* Realistic, the notch is the bezel's glass, under the same light down the lid. */}\n        {real && (\n          <g {...bind(\"lines\")}>\n            <path d={notch.path} fill=\"white\" fillOpacity={NOTCH_LIGHT} />\n          </g>\n        )}\n        <circle cx={notch.lens.x} cy={notch.lens.y} r={0.35} fill=\"#111112\" />\n        <circle cx={notch.lens.x} cy={notch.lens.y} r={0.11} fill=\"#1b3048\" />\n      </svg>\n    </Screen>\n  );\n\n  return (\n    <Stage role={children == null ? \"img\" : \"group\"} label={label} camera={frame.camera} overlay={glass}>\n      <defs>\n        {defs(id, frame.camera, holesShown(holes, frame.camera.width), bind(\"blur\"))}\n        {frontDefs(id, bind(\"edges\"), { x1: \"0\", y1: \"0\", x2: \"0\", y2: \"1\" }, FOOT_BOX, 0.15, <path {...bind(\"base.scoop\")} />, real ? SILVER : FINISH)}\n        {real && (\n          <>\n            {/* The deck: shade where the lid meets it at the hinge, light falling on toward the front, a bright band before the front edge turns away. */}\n            {sheen(\"top\", [\n              [0, \"black\", 0.42],\n              [0.025, \"black\", 0.2],\n              [0.07, \"black\", 0.07],\n              [0.4, \"black\", 0.02],\n              [0.62, \"white\", 0.04],\n              [0.86, \"white\", 0.24],\n              [0.95, \"white\", 0.12],\n              [1, \"black\", 0.05],\n            ])}\n            {/* The lid's back: a bright top edge, an even middle, falling off dark toward the hinge, away from the lamp. */}\n            {sheen(\"outer\", [\n              [0, \"white\", 0.3],\n              [0.06, \"white\", 0.14],\n              [0.4, \"white\", 0.03],\n              [0.62, \"black\", 0.04],\n              [0.85, \"black\", 0.2],\n              [1, \"black\", 0.36],\n            ])}\n            {sheen(\"under\", [\n              [0, \"black\", 0.14],\n              [0.5, \"black\", 0.02],\n              [0.9, \"white\", 0.12],\n              [1, \"white\", 0.04],\n            ])}\n            {/* The thumb's scoop, a soft shallow cut in shade: darkest at its back wall, lighter toward the lip. */}\n            {sheen(\"dish\", [\n              [0, \"#6e6f71\"],\n              [0.3, \"#8f9092\"],\n              [0.7, \"#abacae\"],\n              [1, \"#c6c7c9\"],\n            ])}\n            {sheen(\"pane\", PANE)}\n            <Grain id={`${id}-grain`} size={2.4} count={160} speck={0.05} strength={0.045} stretch={2} />\n          </>\n        )}\n        <clipPath id={`${id}-deck`}>\n          <path {...bind(\"base.top\")} />\n        </clipPath>\n      </defs>\n      <g {...bind(\"shadow\")}>\n        <path {...bind(\"shadow.far\", real ? { fill: REAL.far } : shade(1))} filter={url(\"far\")} />\n        <path {...bind(\"shadow.near\", real ? { fill: REAL.near } : shade(1))} filter={url(\"near\")} />\n      </g>\n      <g style={{ filter: \"var(--vectoys-tint)\" }}>\n        {frame.order.map((part) => (\n          <g key={part} ref={nodes.ref(`part.${part}`)}>\n            {part === \"lid\" ? drawn.lid : part === \"base\" ? drawn.base : drawn.feet}\n          </g>\n        ))}\n      </g>\n    </Stage>\n  );\n}\n"
    }
  ],
  "meta": {
    "license": "MIT",
    "source": "https://vectoys.com/r/laptop.json",
    "credit": "vectoys (https://vectoys.com)"
  },
  "docs": "This toy needs @vinena/vectoys-react 0.3.2 or newer. If your project has an older one, run: npm install @vinena/vectoys-react@latest"
}
