{"mappings":"AS2EA;;;;;GAKG;AACH,sCAA+B,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,UAoM1D;AAED;;GAEG;AACH,gCACE,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,GAAG,EAAE,EACvB,KAAK,oBAAK,EACV,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,GAAG,OAeZ","sources":["src/src/lib/utils.ts","src/src/lib/canvas/colors.ts","src/src/lib/canvas/shapes/basic.ts","src/src/lib/constants.ts","src/src/lib/canvas/shapes/utils.ts","src/src/lib/canvas/shapes/complex.ts","src/src/lib/canvas/shapes/sacred.ts","src/src/lib/canvas/shapes/index.ts","src/src/lib/canvas/draw.ts","src/src/index.ts","src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,"import { createCanvas } from \"@napi-rs/canvas\";\nimport {\n    SacredColorScheme,\n    hexWithAlpha,\n    jitterColor,\n} from \"./lib/canvas/colors\";\nimport { enhanceShapeGeneration } from \"./lib/canvas/draw\";\nimport { shapes } from \"./lib/canvas/shapes\";\nimport { createRng, seedFromHash } from \"./lib/utils\";\n\n// Shape categories for weighted selection per layer\nconst BASIC_SHAPES = [\n  \"circle\",\n  \"square\",\n  \"triangle\",\n  \"hexagon\",\n  \"diamond\",\n  \"cube\",\n];\nconst COMPLEX_SHAPES = [\n  \"star\",\n  \"jacked-star\",\n  \"heart\",\n  \"platonicSolid\",\n  \"fibonacciSpiral\",\n  \"islamicPattern\",\n  \"celticKnot\",\n  \"merkaba\",\n  \"fractal\",\n];\nconst SACRED_SHAPES = [\n  \"mandala\",\n  \"flowerOfLife\",\n  \"treeOfLife\",\n  \"metatronsCube\",\n  \"sriYantra\",\n  \"seedOfLife\",\n  \"vesicaPiscis\",\n  \"torus\",\n  \"eggOfLife\",\n];\n\n/**\n * Pick a shape name using layer-weighted selection.\n * Early layers favor basic shapes; later layers favor complex/sacred.\n */\nfunction pickShape(\n  rng: () => number,\n  layerRatio: number,\n  shapeNames: string[],\n): string {\n  // layerRatio: 0 = first layer, 1 = last layer\n  const basicWeight = 1 - layerRatio * 0.6;\n  const complexWeight = 0.3 + layerRatio * 0.3;\n  const sacredWeight = 0.1 + layerRatio * 0.4;\n  const total = basicWeight + complexWeight + sacredWeight;\n\n  const roll = rng() * total;\n  let pool: string[];\n  if (roll < basicWeight) {\n    pool = BASIC_SHAPES;\n  } else if (roll < basicWeight + complexWeight) {\n    pool = COMPLEX_SHAPES;\n  } else {\n    pool = SACRED_SHAPES;\n  }\n\n  // Filter to shapes that actually exist in the registry\n  const available = pool.filter((s) => shapeNames.includes(s));\n  if (available.length === 0) {\n    return shapeNames[Math.floor(rng() * shapeNames.length)];\n  }\n  return available[Math.floor(rng() * available.length)];\n}\n\n/**\n * Generate an abstract art image from a git hash with custom configuration\n * @param {string} gitHash - The git hash to use as a seed\n * @param {object} [config={}] - Configuration options\n * @returns {Buffer} PNG buffer of the generated image\n */\nfunction generateImageFromHash(gitHash: string, config = {}) {\n  const defaultConfig = {\n    width: 2048,\n    height: 2048,\n    gridSize: 5,\n    layers: 4,\n    minShapeSize: 30,\n    maxShapeSize: 400,\n    baseOpacity: 0.7,\n    opacityReduction: 0.12,\n    shapesPerLayer: 0,\n  };\n\n  const finalConfig = { ...defaultConfig, ...config };\n  const {\n    width,\n    height,\n    gridSize,\n    layers,\n    minShapeSize,\n    maxShapeSize,\n    baseOpacity,\n    opacityReduction,\n  } = finalConfig;\n\n  finalConfig.shapesPerLayer =\n    finalConfig.shapesPerLayer || Math.floor(gridSize * gridSize * 1.5);\n\n  const canvas = createCanvas(width, height);\n  const ctx = canvas.getContext(\"2d\") as unknown as CanvasRenderingContext2D;\n\n  // --- Color scheme derived from hash ---\n  const colorScheme = new SacredColorScheme(gitHash);\n  const colors = colorScheme.getColors();\n  const [bgStart, bgEnd] = colorScheme.getBackgroundColors();\n\n  // --- Radial gradient background for depth ---\n  const cx = width / 2;\n  const cy = height / 2;\n  const bgRadius = Math.hypot(cx, cy);\n  const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, bgRadius);\n  gradient.addColorStop(0, bgStart);\n  gradient.addColorStop(1, bgEnd);\n  ctx.fillStyle = gradient;\n  ctx.fillRect(0, 0, width, height);\n\n  const shapeNames = Object.keys(shapes);\n  const scaleFactor = Math.min(width, height) / 1024;\n  const adjustedMinSize = minShapeSize * scaleFactor;\n  const adjustedMaxSize = maxShapeSize * scaleFactor;\n\n  // One master RNG seeded from the full hash\n  const rng = createRng(seedFromHash(gitHash));\n\n  // --- Focal points: 1-3 hash-derived attractors that bias shape placement ---\n  const numFocal = 1 + Math.floor(rng() * 2); // 1-2 focal points\n  const focalPoints: Array<{ x: number; y: number; strength: number }> = [];\n  for (let f = 0; f < numFocal; f++) {\n    focalPoints.push({\n      x: width * (0.2 + rng() * 0.6), // keep away from edges\n      y: height * (0.2 + rng() * 0.6),\n      strength: 0.3 + rng() * 0.4,\n    });\n  }\n\n  /**\n   * Bias a position toward the nearest focal point.\n   * Returns a blended position between the random point and the focal point.\n   */\n  function applyFocalBias(rx: number, ry: number): [number, number] {\n    // Find nearest focal point\n    let nearest = focalPoints[0];\n    let minDist = Infinity;\n    for (const fp of focalPoints) {\n      const d = Math.hypot(rx - fp.x, ry - fp.y);\n      if (d < minDist) {\n        minDist = d;\n        nearest = fp;\n      }\n    }\n    // Blend toward focal point based on strength and a random factor\n    const pull = nearest.strength * rng() * 0.5;\n    return [\n      rx + (nearest.x - rx) * pull,\n      ry + (nearest.y - ry) * pull,\n    ];\n  }\n\n  const shapePositions: Array<{ x: number; y: number }> = [];\n\n  for (let layer = 0; layer < layers; layer++) {\n    const layerRatio = layers > 1 ? layer / (layers - 1) : 0;\n    const numShapes =\n      finalConfig.shapesPerLayer +\n      Math.floor(rng() * finalConfig.shapesPerLayer * 0.3);\n\n    const layerOpacity = Math.max(0.15, baseOpacity - layer * opacityReduction);\n    const layerSizeScale = 1 - layer * 0.15;\n\n    for (let i = 0; i < numShapes; i++) {\n      // Random position biased toward focal points\n      const rawX = rng() * width;\n      const rawY = rng() * height;\n      const [x, y] = applyFocalBias(rawX, rawY);\n\n      // Weighted shape selection: basic early, complex/sacred later\n      const shape = pickShape(rng, layerRatio, shapeNames);\n\n      const sizeT = Math.pow(rng(), 1.8);\n      const size =\n        (adjustedMinSize + sizeT * (adjustedMaxSize - adjustedMinSize)) *\n        layerSizeScale;\n\n      const rotation = rng() * 360;\n\n      // Color jitter: slight variation on each pick for organic feel\n      const baseFill = colors[Math.floor(rng() * colors.length)];\n      const baseStroke = colors[Math.floor(rng() * colors.length)];\n      const fillColor = jitterColor(baseFill, rng, 0.08);\n      const strokeColor = jitterColor(baseStroke, rng, 0.06);\n\n      // Semi-transparent fill for watercolor layering effect\n      const fillAlpha = 0.25 + rng() * 0.5;\n      const transparentFill = hexWithAlpha(fillColor, fillAlpha);\n\n      const strokeWidth = (0.5 + rng() * 2.0) * scaleFactor;\n\n      ctx.globalAlpha = layerOpacity * (0.5 + rng() * 0.5);\n\n      // Glow: ~25% of shapes get a soft glow, more likely on sacred shapes\n      const isSacred = SACRED_SHAPES.includes(shape);\n      const glowChance = isSacred ? 0.45 : 0.2;\n      const hasGlow = rng() < glowChance;\n      const glowRadius = hasGlow ? (8 + rng() * 20) * scaleFactor : 0;\n\n      // Gradient fill: ~30% of shapes get a radial gradient\n      const hasGradient = rng() < 0.3;\n      const gradientEnd = hasGradient\n        ? jitterColor(colors[Math.floor(rng() * colors.length)], rng, 0.1)\n        : undefined;\n\n      enhanceShapeGeneration(ctx, shape, x, y, {\n        fillColor: transparentFill,\n        strokeColor,\n        strokeWidth,\n        size,\n        rotation,\n        proportionType: \"GOLDEN_RATIO\",\n        glowRadius,\n        glowColor: hasGlow ? hexWithAlpha(fillColor, 0.6) : undefined,\n        gradientFillEnd: gradientEnd,\n      });\n\n      shapePositions.push({ x, y });\n    }\n  }\n\n  // --- Organic connecting curves between nearby shapes ---\n  if (shapePositions.length > 1) {\n    const numCurves = Math.floor((8 * (width * height)) / (1024 * 1024));\n    ctx.lineWidth = 0.8 * scaleFactor;\n\n    for (let i = 0; i < numCurves; i++) {\n      const idxA = Math.floor(rng() * shapePositions.length);\n      const offset =\n        1 + Math.floor(rng() * Math.min(5, shapePositions.length - 1));\n      const idxB = (idxA + offset) % shapePositions.length;\n\n      const a = shapePositions[idxA];\n      const b = shapePositions[idxB];\n\n      const mx = (a.x + b.x) / 2;\n      const my = (a.y + b.y) / 2;\n      const dx = b.x - a.x;\n      const dy = b.y - a.y;\n      const dist = Math.hypot(dx, dy);\n      const bulge = (rng() - 0.5) * dist * 0.4;\n\n      const cpx = mx + (-dy / (dist || 1)) * bulge;\n      const cpy = my + (dx / (dist || 1)) * bulge;\n\n      ctx.globalAlpha = 0.08 + rng() * 0.12;\n      ctx.strokeStyle = hexWithAlpha(\n        colors[Math.floor(rng() * colors.length)],\n        0.3,\n      );\n\n      ctx.beginPath();\n      ctx.moveTo(a.x, a.y);\n      ctx.quadraticCurveTo(cpx, cpy, b.x, b.y);\n      ctx.stroke();\n    }\n  }\n\n  ctx.globalAlpha = 1;\n  return canvas.toBuffer(\"image/png\");\n}\n\n/**\n * Save the generated image to a file\n */\nfunction saveImageToFile(\n  imageBuffer: string,\n  outputDir: string,\n  gitHash: string | any[],\n  label = \"\",\n  width: any,\n  height: any,\n) {\n  if (!fs.existsSync(outputDir)) {\n    fs.mkdirSync(outputDir, { recursive: true });\n  }\n\n  const filename = label\n    ? `${label}-${width}x${height}-${gitHash.slice(0, 8)}.png`\n    : `${gitHash.slice(0, 8)}-${width}x${height}.png`;\n\n  const outputPath = path.join(outputDir, filename);\n  fs.writeFileSync(outputPath, imageBuffer);\n  console.log(`Generated: ${outputPath}`);\n\n  return outputPath;\n}\n\nexport { generateImageFromHash, saveImageToFile };\n"],"names":[],"version":3,"file":"types.d.ts.map"}