{"version":3,"file":"Ruler-BJWQU2TJ.cjs","sources":["../app/components/ruler/Ruler.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useWindowStore } from \"../windows/windowStore\";\nimport RotateHandle from \"../ui/RotateHandle\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\n\ntype RulerProps = {\n  orientation?: Orientation;\n  lengthPx?: number; // visual length in pixels\n  windowId?: string;\n};\n\n// Generates tick marks for both cm and inches scales\nfunction useTicks(lengthPx: number) {\n  // CSS px is 1/96 inch. For typical displays, using 96 keeps geometry consistent.\n  const pxPerInch = 96; // CSS pixel baseline\n  const pxPerCm = pxPerInch / 2.54;\n\n  // Create ticks up to given length\n  const inchTicks = useMemo(() => {\n    type InchKind = \"major\" | \"half\" | \"quarter\" | \"eighth\" | \"sixteenth\";\n    const count = Math.floor(lengthPx / pxPerInch) + 1;\n    const ticks: { x: number; label?: string; kind: InchKind }[] = [];\n    for (let i = 0; i <= count * 16; i++) {\n      const sixteenth = i / 16;\n      const x = Math.round(sixteenth * pxPerInch);\n      const isWhole = i % 16 === 0;\n      const isHalf = !isWhole && i % 8 === 0;\n      const isQuarter = !isWhole && !isHalf && i % 4 === 0;\n      const isEighth = !isWhole && !isHalf && !isQuarter && i % 2 === 0;\n      const kind: InchKind = isWhole\n        ? \"major\"\n        : isHalf\n        ? \"half\"\n        : isQuarter\n        ? \"quarter\"\n        : isEighth\n        ? \"eighth\"\n        : \"sixteenth\";\n      ticks.push({\n        x,\n        label: isWhole ? `${sixteenth}`.replace(/\\.0$/, \"\") : undefined,\n        kind,\n      });\n    }\n    return ticks.filter((t) => t.x <= lengthPx);\n  }, [lengthPx, pxPerInch]);\n\n  const cmTicks = useMemo(() => {\n    type CmKind = \"cm\" | \"halfCm\" | \"mm\";\n    const count = Math.floor(lengthPx / pxPerCm) + 1;\n    const ticks: { x: number; label?: string; kind: CmKind }[] = [];\n    for (let i = 0; i <= count * 10; i++) {\n      const tenth = i / 10; // millimeters as tenths of a centimeter\n      const x = Math.round(tenth * pxPerCm);\n      const isWhole = i % 10 === 0; // 1 cm\n      const isHalf = !isWhole && i % 5 === 0; // 5 mm\n      const kind: CmKind = isWhole ? \"cm\" : isHalf ? \"halfCm\" : \"mm\";\n      ticks.push({\n        x,\n        label: isWhole ? `${tenth}`.replace(/\\.0$/, \"\") : undefined,\n        kind,\n      });\n    }\n    return ticks.filter((t) => t.x <= lengthPx);\n  }, [lengthPx, pxPerCm]);\n\n  return { inchTicks, cmTicks, pxPerInch, pxPerCm };\n}\n\nexport default function Ruler({ lengthPx = 800, windowId }: RulerProps) {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const [size, setSize] = useState({ width: lengthPx, height: 80 });\n  const rotationDeg = useWindowStore((s) =>\n    windowId ? s.windows[windowId]?.rotationDeg ?? 0 : 0\n  );\n  const updateRotation = useWindowStore((s) => s.updateRotation);\n\n  useEffect(() => {\n    const el = containerRef.current;\n    if (!el) return;\n    const obs = new ResizeObserver((entries) => {\n      const cr = entries[0]?.contentRect;\n      if (!cr) return;\n      setSize((s) => ({ width: Math.max(cr.width, 200), height: s.height }));\n    });\n    obs.observe(el);\n    return () => obs.disconnect();\n  }, []);\n\n  const { inchTicks, cmTicks } = useTicks(size.width);\n\n  return (\n    <div\n      ref={containerRef}\n      className=\"relative h-full w-full select-none text-foreground\"\n      style={{\n        background: \"transparent\",\n        WebkitUserSelect: \"none\",\n        userSelect: \"none\",\n      }}\n      aria-label=\"Ruler\"\n    >\n      {windowId && (\n        <RotateHandle\n          className=\"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 h-6 w-6 rounded-full bg-card/70 border border-border shadow no-drag\"\n          title=\"Rotate\"\n          initialDeg={rotationDeg}\n          getCenter={() => {\n            const root = containerRef.current;\n            if (!root) return null;\n            const rect = root.getBoundingClientRect();\n            return {\n              x: rect.left + rect.width / 2,\n              y: rect.top + rect.height / 2,\n            };\n          }}\n          onChange={(deg) => updateRotation(windowId, deg)}\n        />\n      )}\n      <svg\n        width={size.width}\n        height={80}\n        role=\"img\"\n        aria-label=\"Measurement ruler with inches and centimeters\"\n        style={{ display: \"block\" }}\n      >\n        {(() => {\n          // ratio-based measures\n          const baseH = 80;\n          const inMajor = Math.max(16, baseH * 0.24);\n          const inHalf = Math.max(13, inMajor * 0.8);\n          const inQuarter = Math.max(11, inMajor * 0.68);\n          const inEighth = Math.max(9, inMajor * 0.55);\n          const inSixteenth = Math.max(7, inMajor * 0.42);\n          const cmMajor = inMajor;\n          const cmHalf = Math.max(11, inMajor * 0.66);\n          const cmMinor = Math.max(8, inMajor * 0.5);\n          const swMajor = 1.2;\n          const swMedium = 1.0;\n          const swMinor = 0.8;\n          const labelFont = 10;\n          return (\n            <g>\n              <defs>\n                <pattern\n                  id=\"ruler-backing\"\n                  width={8}\n                  height={8}\n                  patternUnits=\"userSpaceOnUse\"\n                >\n                  <rect width=\"8\" height=\"8\" fill=\"rgba(255,255,255,0.08)\" />\n                </pattern>\n              </defs>\n              <rect\n                x=\"0\"\n                y=\"0\"\n                width=\"100%\"\n                height=\"100%\"\n                fill=\"rgba(255,255,255,0.05)\"\n              />\n              {/* Subtle background pattern overlay */}\n              <rect\n                x=\"0\"\n                y=\"0\"\n                width=\"100%\"\n                height=\"100%\"\n                fill=\"url(#ruler-backing)\"\n                fillOpacity={0.18}\n              />\n\n              {/* Center baseline */}\n              <line\n                x1={0.5}\n                y1={baseH / 2}\n                x2={size.width - 0.5}\n                y2={baseH / 2}\n                stroke=\"currentColor\"\n                strokeOpacity={0.18}\n                strokeWidth={0.75}\n              />\n\n              {/* Inches scale - top */}\n              <g transform=\"translate(0,0)\">\n                {inchTicks.map((t, idx) => {\n                  const h =\n                    t.kind === \"major\"\n                      ? inMajor\n                      : t.kind === \"half\"\n                      ? inHalf\n                      : t.kind === \"quarter\"\n                      ? inQuarter\n                      : t.kind === \"eighth\"\n                      ? inEighth\n                      : inSixteenth;\n                  const sw =\n                    t.kind === \"major\"\n                      ? swMajor\n                      : t.kind === \"half\" || t.kind === \"quarter\"\n                      ? swMedium\n                      : swMinor;\n                  return (\n                    <g key={`in-${idx}`}>\n                      <line\n                        x1={t.x}\n                        y1={0}\n                        x2={t.x}\n                        y2={h}\n                        stroke=\"currentColor\"\n                        strokeOpacity={0.8}\n                        strokeWidth={sw}\n                      />\n                      {t.label && t.label !== \"0\" && (\n                        <text\n                          x={t.x}\n                          y={h + 10}\n                          fontSize={labelFont}\n                          textAnchor=\"middle\"\n                          fill=\"currentColor\"\n                          fillOpacity={0.85}\n                        >\n                          {t.label}\n                        </text>\n                      )}\n                    </g>\n                  );\n                })}\n                <text\n                  x={6}\n                  y={inMajor + 20}\n                  fontSize={10}\n                  fill=\"currentColor\"\n                  fillOpacity={0.65}\n                >\n                  in\n                </text>\n              </g>\n\n              {/* Centimeters scale - bottom */}\n              <g transform=\"translate(0,80)\">\n                {cmTicks.map((t, idx) => {\n                  const h =\n                    t.kind === \"cm\"\n                      ? cmMajor\n                      : t.kind === \"halfCm\"\n                      ? cmHalf\n                      : cmMinor;\n                  const sw =\n                    t.kind === \"cm\"\n                      ? swMajor\n                      : t.kind === \"halfCm\"\n                      ? swMedium\n                      : swMinor;\n                  return (\n                    <g key={`cm-${idx}`}>\n                      <line\n                        x1={t.x}\n                        y1={0}\n                        x2={t.x}\n                        y2={-h}\n                        stroke=\"currentColor\"\n                        strokeOpacity={0.8}\n                        strokeWidth={sw}\n                      />\n                      {t.label && t.label !== \"0\" && (\n                        <text\n                          x={t.x}\n                          y={-h - 6}\n                          fontSize={labelFont}\n                          textAnchor=\"middle\"\n                          fill=\"currentColor\"\n                          fillOpacity={0.85}\n                        >\n                          {t.label}\n                        </text>\n                      )}\n                    </g>\n                  );\n                })}\n                <text\n                  x={6}\n                  y={-cmHalf - 12}\n                  fontSize={10}\n                  fill=\"currentColor\"\n                  fillOpacity={0.65}\n                >\n                  cm\n                </text>\n              </g>\n\n              {/* Outline border so user can match edges to content */}\n              <rect\n                x={0.5}\n                y={0.5}\n                width={size.width - 1}\n                height={79}\n                rx={6}\n                ry={6}\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeOpacity={0.6}\n              />\n            </g>\n          );\n        })()}\n      </svg>\n    </div>\n  );\n}\n"],"names":["useTicks","lengthPx","pxPerCm","inchTicks","useMemo","count","ticks","i","sixteenth","x","isWhole","isHalf","isQuarter","isEighth","kind","t","cmTicks","tenth","Ruler","windowId","containerRef","useRef","size","setSize","useState","rotationDeg","useWindowStore","s","updateRotation","useEffect","el","obs","entries","cr","jsxs","jsx","RotateHandle","root","rect","deg","inMajor","inHalf","inQuarter","inEighth","inSixteenth","cmMajor","cmHalf","cmMinor","swMajor","swMedium","swMinor","labelFont","idx","h","sw"],"mappings":"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"}