// bouquet.jsx — bouquet preview SVG component
// Renders a hand-tied bouquet wrapped in paper/silk/twine, with stems splaying
// out the top and flower heads varying in height for a natural arrangement.
// All flowers are composed of basic shapes (circles + simple paths) per
// project SVG rules.

// Canonical flower vocab — the single source of truth for both the renderer
// (FLOWERS, below) and the two front-end pickers, which read window.FLOWER_VOCAB
// (c = dot fill = head, ic = dot ring = center). Listed in picker display order.
// NOTE: server.js keeps its own FLOWER_NAMES allow-list — a deliberate, runtime-
// independent validation mirror (Node can't load this JSX). Keep the two in sync.
const FLOWER_VOCAB = [
  { type: 'peony',       name: 'peony',         lat: 'paeonia',     head: '#c45c6e', center: '#f3d57a', size: 18 },
  { type: 'ranunculus',  name: 'ranunculus',    lat: 'ranunculus',  head: '#f0c8b8', center: '#c45c6e', size: 14 },
  { type: 'daisy',       name: 'daisy',         lat: 'bellis',      head: '#f6efd9', center: '#c8762a', size: 15, ringStroke: '#7a5a3a' },
  { type: 'cornflower',  name: 'cornflower',    lat: 'centaurea',   head: '#9cbed4', center: '#f3d57a', size: 13 },
  { type: 'lavender',    name: 'lavender',      lat: 'lavandula',   head: '#b8a4d0', center: '#7a5a8a', size: 12 },
  { type: 'marigold',    name: 'marigold',      lat: 'tagetes',     head: '#e8a36a', center: '#a8606a', size: 16 },
  { type: 'tulip',       name: 'tulip',         lat: 'tulipa',      head: '#e6cf83', center: '#bf9a52', size: 15 },
  // foliage / fillers
  { type: 'babysbreath', name: "baby's breath", lat: 'gypsophila',  head: '#fbf7ec', center: '#cdbf9b', size: 13 },
  { type: 'eucalyptus',  name: 'eucalyptus',    lat: 'eucalyptus',  head: '#9bb59a', center: '#6b7d5c', size: 14 },
  { type: 'fern',        name: 'fern',          lat: 'nephrolepis', head: '#7e9b6e', center: '#4a5d3e', size: 15 },
];
// Renderer lookup keyed by type. `label` kept as an alias of `name` for callers.
const FLOWERS = Object.fromEntries(
  FLOWER_VOCAB.map(f => [f.type, { ...f, label: f.name }])
);

// Deterministic jitter so the bouquet is repeatable.
function jitter(i, seed = 1, range = 1) {
  const x = Math.sin(i * 12.9898 + seed * 78.233) * 43758.5453;
  return ((x - Math.floor(x)) - 0.5) * 2 * range;
}

// Compute a stable position + height for the n-th stem. The gather differs by
// wrap: twine is a hand-tied bunch (stems pinch at the tie, width from the fan),
// while paper cones want the stems spread to fill the wrap mouth.
// `fullness` (default 1) scales how far the structured fan opens — the buyer's
// "tight ↔ full" slider. It multiplies only the (t-0.5)*spread/fan term, never the
// jitter, so a tight bouquet stays naturally varied instead of collapsing to a line.
function stemPlacement(i, total, wrap = 'kraft', fullness = 1) {
  const t = total === 1 ? 0.5 : i / (total - 1);
  let x, angle;
  if (wrap === 'twine') {
    const gather = Math.min(22, total * 1.8 + 6);    // base spread at the bind
    const fan = Math.min(34, total * 2.2 + 11);      // tighter splay — heads sit closer at the top
    x = (t - 0.5) * gather * fullness + jitter(i, 1, 3);
    angle = (t - 0.5) * fan * fullness + jitter(i, 2, 4);
  } else if (wrap === 'vase') {
    // standing in a vessel: a tight, upright column. Both the bind spread and the
    // splay barely grow with count and cap low, so a full bouquet stays gathered
    // and vertical (stems lean out only slightly) rather than fanning open.
    const gather = Math.min(15, total * 1.1 + 5);    // narrow bind, grows slowly
    const fan = Math.min(16, total * 1.2 + 5);        // near-upright, gentle lean
    x = (t - 0.5) * gather * fullness + jitter(i, 1, 2.4);
    angle = (t - 0.5) * fan * fullness + jitter(i, 2, 3);
  } else if (wrap === 'silk') {
    // refined compact posy — a tight gather held nearly upright, for a polished
    // "presented" nosegay that's distinctly narrower than kraft's open cone.
    const spread = Math.min(46, total * 4 + 16);
    x = (t - 0.5) * spread * fullness + jitter(i, 1, 2.5);
    angle = (t - 0.5) * 9 * fullness + jitter(i, 2, 2.5);  // close to vertical
  } else {
    // kraft — a full, relaxed cone: wider and splayed more open than silk, so it
    // reads as a generous casual hand-wrap.
    const spread = Math.min(96, total * 10 + 30);     // fill the cone mouth, wide
    x = (t - 0.5) * spread * fullness + jitter(i, 1, 4);
    angle = (t - 0.5) * 30 * fullness + jitter(i, 2, 5);   // pronounced outward tilt
  }
  // height: outer stems shorter so the crown reads as a rounded dome. In a vase
  // the heads sit looser — a much smaller dome bias and a wide random stagger, so
  // blooms land at varied heights instead of a tidy mound.
  let height;
  if (wrap === 'vase') {
    // shorter, looser column (~30% lower than a wrapped bouquet): gentle dome bias
    // + wide random stagger, scaled down so blooms sit over the vase, not towering.
    height = (236 - Math.abs(t - 0.5) * 22 + jitter(i, 3, 30)) * 0.7;
  } else if (wrap === 'silk') {
    // tidy, even posy crown — shallow dome, little variation, sat lower so the
    // compact nosegay doesn't tower over the ribbon wrap
    height = 200 - Math.abs(t - 0.5) * 28 + jitter(i, 3, 8);
  } else if (wrap === 'twine') {
    const heightBase = 216 - Math.abs(t - 0.5) * 48;  // a touch shorter
    height = heightBase + jitter(i, 3, 14);
  } else {
    // kraft — deeper cascade + more variation, so the cone reads full and casual
    height = 228 - Math.abs(t - 0.5) * 60 + jitter(i, 3, 18);
  }
  return { x, angle, height };
}

function Flower({ type, x = 0, y = 0, scale = 1 }) {
  const f = FLOWERS[type] || FLOWERS.peony;
  const r = f.size;
  if (type === 'babysbreath') {
    // airy spray of tiny white blossoms on hair-thin stems
    const pts = [[0,-13],[5,-9],[-5,-9],[8,-3],[-8,-3],[3,-4],[-3,-4],[6,2],[-6,2],[0,3],[2,-1]];
    return (
      <g transform={`translate(${x} ${y}) scale(${scale})`}>
        {pts.map(([px,py],i) => (
          <line key={'s'+i} x1="0" y1="4" x2={px} y2={py} stroke="#9fae7e" strokeWidth="0.6" opacity="0.85" />
        ))}
        {pts.map(([px,py],i) => (
          <circle key={'d'+i} cx={px} cy={py} r="1.85" fill={f.head} stroke="#a7ab83" strokeWidth="0.7" />
        ))}
      </g>
    );
  }
  if (type === 'eucalyptus') {
    // rounded silver-sage leaves in opposite pairs up a short sprig
    const leaves = [];
    [-2,-7,-12].forEach((ly,i) => {
      leaves.push(<ellipse key={'r'+i} cx="4.5" cy={ly} rx="5" ry="3.1" fill={f.head} stroke={f.center} strokeWidth="0.5" transform={`rotate(38 4.5 ${ly})`} />);
      leaves.push(<ellipse key={'l'+i} cx="-4.5" cy={ly} rx="5" ry="3.1" fill={f.head} stroke={f.center} strokeWidth="0.5" transform={`rotate(-38 -4.5 ${ly})`} />);
    });
    return (
      <g transform={`translate(${x} ${y}) scale(${scale})`}>
        <path d="M0 6 Q 1 -8 0 -20" stroke={f.center} strokeWidth="0.8" fill="none" />
        {leaves}
        <ellipse cx="0" cy="-20" rx="3.6" ry="2.7" fill={f.head} stroke={f.center} strokeWidth="0.5" />
      </g>
    );
  }
  if (type === 'fern') {
    // a single green frond — central rib with paired leaflets
    const parts = [<path key="rib" d="M0 6 Q 1 -10 0 -24" stroke={f.center} strokeWidth="1" fill="none" />];
    for (let i = 0; i < 6; i++) {
      const ly = 3 - i * 4.4;
      const len = 7.5 - i * 0.9;
      parts.push(<line key={'r'+i} x1="0" y1={ly} x2={len} y2={ly - 4.5} stroke={f.head} strokeWidth="1.4" strokeLinecap="round" />);
      parts.push(<line key={'l'+i} x1="0" y1={ly} x2={-len} y2={ly - 4.5} stroke={f.head} strokeWidth="1.4" strokeLinecap="round" />);
    }
    return <g transform={`translate(${x} ${y}) scale(${scale})`}>{parts}</g>;
  }
  if (type === 'tulip') {
    // a closed tulip cup — three petals, center tallest
    return (
      <g transform={`translate(${x} ${y}) scale(${scale})`}>
        <path d={`M ${-r*0.6} ${-r*0.18} Q ${-r*0.74} ${r*0.5} 0 ${r*0.72} Q ${r*0.74} ${r*0.5} ${r*0.6} ${-r*0.18} Q ${r*0.52} ${-r*0.46} ${r*0.4} ${-r*0.52} Q ${r*0.3} ${-r*0.28} ${r*0.2} ${-r*0.34} Q ${r*0.1} ${-r*0.64} 0 ${-r*0.74} Q ${-r*0.1} ${-r*0.64} ${-r*0.2} ${-r*0.34} Q ${-r*0.3} ${-r*0.28} ${-r*0.4} ${-r*0.52} Q ${-r*0.52} ${-r*0.46} ${-r*0.6} ${-r*0.18} Z`}
              fill={f.head} stroke={f.center} strokeWidth="0.6" />
        {/* soft petal seams */}
        <path d={`M 0 ${r*0.58} Q ${r*0.04} ${-r*0.1} 0 ${-r*0.7}`} stroke={f.center} strokeWidth="0.5" fill="none" opacity=".4" />
        <path d={`M ${-r*0.32} ${r*0.18} Q ${-r*0.42} ${-r*0.12} ${-r*0.4} ${-r*0.5}`} stroke={f.center} strokeWidth="0.4" fill="none" opacity=".3" />
        <path d={`M ${r*0.32} ${r*0.18} Q ${r*0.42} ${-r*0.12} ${r*0.4} ${-r*0.5}`} stroke={f.center} strokeWidth="0.4" fill="none" opacity=".3" />
      </g>
    );
  }
  if (type === 'lavender') {
    // small bud spike
    return (
      <g transform={`translate(${x} ${y}) scale(${scale})`}>
        <ellipse cx="0" cy="0" rx="3" ry="3.6" fill={f.head} />
        <ellipse cx="-3" cy="-5" rx="2.6" ry="3" fill={f.head} />
        <ellipse cx="3" cy="-5" rx="2.6" ry="3" fill={f.head} />
        <ellipse cx="0" cy="-10" rx="2.4" ry="2.8" fill={f.head} />
        <ellipse cx="-2" cy="-14" rx="2" ry="2.5" fill={f.head} />
        <ellipse cx="2" cy="-14" rx="2" ry="2.5" fill={f.head} />
        <ellipse cx="0" cy="-18" rx="1.7" ry="2.2" fill={f.center} />
      </g>
    );
  }
  if (type === 'daisy') {
    // 8 petals around a center
    const petals = [];
    for (let i = 0; i < 8; i++) {
      const a = (i / 8) * Math.PI * 2;
      const px = Math.cos(a) * r * 0.7;
      const py = Math.sin(a) * r * 0.7;
      petals.push(
        <ellipse key={i} cx={px} cy={py} rx={r * 0.55} ry={r * 0.35}
          transform={`rotate(${(a * 180 / Math.PI)} ${px} ${py})`}
          fill={f.head} stroke={f.ringStroke} strokeWidth="0.5" />
      );
    }
    return (
      <g transform={`translate(${x} ${y}) scale(${scale})`}>
        {petals}
        <circle cx="0" cy="0" r={r * 0.42} fill={f.center} />
      </g>
    );
  }
  if (type === 'cornflower') {
    // jagged round
    const petals = [];
    for (let i = 0; i < 6; i++) {
      const a = (i / 6) * Math.PI * 2;
      const px = Math.cos(a) * r * 0.65;
      const py = Math.sin(a) * r * 0.65;
      petals.push(<circle key={i} cx={px} cy={py} r={r * 0.55} fill={f.head} />);
    }
    return (
      <g transform={`translate(${x} ${y}) scale(${scale})`}>
        {petals}
        <circle cx="0" cy="0" r={r * 0.36} fill={f.center} />
      </g>
    );
  }
  // peony / marigold / ranunculus — layered round
  const outer = [];
  for (let i = 0; i < 6; i++) {
    const a = (i / 6) * Math.PI * 2;
    outer.push(<circle key={i} cx={Math.cos(a) * r * 0.55} cy={Math.sin(a) * r * 0.55} r={r * 0.55} fill={f.head} />);
  }
  const inner = [];
  for (let i = 0; i < 5; i++) {
    const a = (i / 5) * Math.PI * 2 + 0.3;
    inner.push(<circle key={i} cx={Math.cos(a) * r * 0.30} cy={Math.sin(a) * r * 0.30} r={r * 0.35} fill={f.head} />);
  }
  return (
    <g transform={`translate(${x} ${y}) scale(${scale})`}>
      <g opacity="0.95">{outer}</g>
      <g>{inner}</g>
      <circle cx="0" cy="0" r={r * 0.24} fill={f.center} />
    </g>
  );
}

// Where stems gather. Paper wraps (kraft/silk) form a cone whose front fold
// covers from ~y296 down, so stems originate at PAPER and their cut ends hide
// inside the cone. Twine is a bare hand-tied bunch — there's no vase, so the
// stems pass through the tie (~y320) and their cut ends show BELOW it.
const STEM_BASE_PAPER = 318;
const STEM_BASE_TWINE = 350;
// Vase stems run down into the water so they read through the translucent glass.
const STEM_BASE_VASE = 384;
// Flower heads are anchored to this line regardless of wrap, so switching wrap
// only changes how the stems are gathered at the bottom, not the silhouette.
const HEAD_ANCHOR = STEM_BASE_PAPER;

function Stem({ type, index, total, isNew, wrap, fullness }) {
  const { x, angle, height } = stemPlacement(index, total, wrap, fullness);
  const baseY = wrap === 'twine' ? STEM_BASE_TWINE
              : wrap === 'vase' ? STEM_BASE_VASE
              : STEM_BASE_PAPER;
  // extend the drawn length downward for twine so the head stays put
  const h = height + (baseY - HEAD_ANCHOR);
  const fadeRef = React.useRef(null);
  React.useEffect(() => {
    if (!fadeRef.current) return;
    if (isNew) {
      fadeRef.current.style.transformOrigin = `${x + 160}px ${baseY}px`;
      fadeRef.current.animate(
        [
          { transform: 'translateY(20px) scale(.7) rotate(-2deg)', opacity: 0 },
          { transform: 'translateY(0) scale(1) rotate(0)', opacity: 1 },
        ],
        { duration: 460, easing: 'cubic-bezier(.2,.8,.2,1)', fill: 'both' }
      );
    }
  }, []);
  return (
    <g ref={fadeRef}>
      <g transform={`translate(${x + 160} ${baseY}) rotate(${angle})`}>
        {/* stem */}
        <path d={`M0 0 Q ${jitter(index, 5, 3)} ${-h / 2} ${jitter(index, 6, 2)} ${-h}`}
              stroke="#6e8c63" strokeWidth="1.8" fill="none" strokeLinecap="round" />
        {/* leaf */}
        {Math.abs(jitter(index, 7, 1)) > .3 && (
          <ellipse cx={jitter(index, 8, 6) - 4} cy={-h / 2 - 10} rx="7" ry="3" fill="#6e8c63"
                   transform={`rotate(${jitter(index, 9, 30) - 25} ${jitter(index, 8, 6) - 4} ${-h / 2 - 10})`} />
        )}
        {/* flower head */}
        <Flower type={type} x={jitter(index, 6, 2)} y={-h}
                scale={0.9 + Math.abs(jitter(index, 10, 0.15))} />
      </g>
    </g>
  );
}

// Wraps: kraft cone, silk ribbon band, twine knot
function Wrap({ style = 'kraft', count = 1, fullness = 1 }) {
  if (style === 'kraft') {
    return (
      <g>
        {/* back layer — curved cone */}
        <path d="M 70 282 Q 60 320 100 380 Q 130 396 160 396 Q 190 396 220 380 Q 260 320 250 282 Q 245 286 230 290 Q 195 296 160 296 Q 125 296 90 290 Q 75 286 70 282 Z"
              fill="#d4ac80" stroke="#8d6238" strokeWidth="1.2" />
        {/* front overlap — paper folded in front */}
        <path d="M 100 296 Q 90 330 125 392 Q 142 400 160 400 Q 178 400 195 392 Q 230 330 220 296 Q 210 308 195 320 Q 178 330 160 330 Q 142 330 125 320 Q 110 308 100 296 Z"
              fill="#c8a070" stroke="#8d6238" strokeWidth="1" opacity="0.95" />
        {/* paper crease lines — gentle */}
        <path d="M 110 300 Q 130 350 145 392" stroke="#8d6238" strokeWidth=".5" fill="none" opacity=".4" />
        <path d="M 210 300 Q 190 350 175 392" stroke="#8d6238" strokeWidth=".5" fill="none" opacity=".4" />
        <path d="M 160 300 V 392" stroke="#8d6238" strokeWidth=".4" opacity=".25" />
        {/* twine tie */}
        <ellipse cx="160" cy="320" rx="62" ry="5" fill="#7a5a3a" />
        <path d="M 160 320 L 150 338 M 160 320 L 170 338" stroke="#7a5a3a" strokeWidth="1.5" strokeLinecap="round" />
      </g>
    );
  }
  if (style === 'silk') {
    return (
      <g>
        {/* curved silk wrap, back layer */}
        <path d="M 80 282 Q 70 322 110 380 Q 135 396 160 396 Q 185 396 210 380 Q 250 322 240 282 Q 235 286 220 290 Q 188 296 160 296 Q 132 296 100 290 Q 85 286 80 282 Z"
              fill="#f1e9d4" stroke="#a8606a" strokeWidth="1" opacity=".95" />
        {/* front overlap */}
        <path d="M 105 296 Q 95 332 130 390 Q 145 398 160 398 Q 175 398 190 390 Q 225 332 215 296 Q 205 308 190 318 Q 175 326 160 326 Q 145 326 130 318 Q 115 308 105 296 Z"
              fill="#eddfc6" stroke="#a8606a" strokeWidth=".8" opacity=".95" />
        {/* silk ribbon band */}
        <rect x="90" y="310" width="140" height="18" fill="#c45c6e" rx="2" />
        <path d="M 90 310 L 80 304 L 78 326 L 90 328 Z" fill="#a8606a" />
        <path d="M 230 310 L 240 304 L 242 326 L 230 328 Z" fill="#a8606a" />
        {/* ribbon shine */}
        <rect x="92" y="313" width="136" height="3" fill="#e88394" opacity=".5" />
      </g>
    );
  }
  if (style === 'vase') {
    // a clear glass vessel — translucent so the gathered stems read through it,
    // with a waterline and a couple of soft glass highlights. Drawn over the
    // stems (Wrap renders last) so the tint falls across everything inside.
    return (
      <g>
        {/* water — fills the lower interior, below the waterline */}
        <path d="M 131 322 Q 122 352 136 394 L 184 394 Q 198 352 189 322 Z"
              fill="#9bb8c4" opacity=".30" />
        {/* glass body — translucent; doubles the tint below the waterline */}
        <path d="M 128 300 Q 132 320 122 352 Q 116 380 135 396 L 185 396 Q 204 380 198 352 Q 188 320 192 300 Z"
              fill="#cfdee2" opacity=".30" stroke="#a9bcc0" strokeWidth="1.3" />
        {/* waterline */}
        <path d="M 131 322 Q 160 327 189 322" stroke="#cfe0e4" strokeWidth="1.1" fill="none" opacity=".75" />
        <path d="M 135 326 Q 160 330 185 326" stroke="#bcd3d8" strokeWidth=".6" fill="none" opacity=".5" />
        {/* mouth rim */}
        <ellipse cx="160" cy="300" rx="32" ry="4.4" fill="#ffffff" opacity=".10" />
        <ellipse cx="160" cy="300" rx="32" ry="4.4" fill="none" stroke="#a9bcc0" strokeWidth="1.3" />
        {/* glass highlights */}
        <path d="M 138 312 Q 130 352 143 388" stroke="#ffffff" strokeWidth="3" fill="none" strokeLinecap="round" opacity=".5" />
        <path d="M 182 316 Q 188 352 179 386" stroke="#ffffff" strokeWidth="1.6" fill="none" strokeLinecap="round" opacity=".3" />
      </g>
    );
  }
  // twine — bare hand-tied bunch, cinched with a single wrap of jute.
  // Width tracks how far the outer stems sit from center at the tie (~y321),
  // mirroring stemPlacement's twine gather+fan, so the cuff grows with the bunch.
  const baseHalf = Math.min(22, count * 1.8 + 6) * fullness / 2;
  const fanHalf = Math.min(34, count * 2.2 + 11) * fullness / 2; // mirror stemPlacement's twine fan
  const half = baseHalf + 39 * Math.sin(fanHalf * Math.PI / 180) + 4; // +margin
  const L = 160 - half, R = 160 + half;
  return (
    <g>
      {/* twine wrap — one clean cuff hugging the gathered stems (tied a touch higher) */}
      <path d={`M ${L} 308 Q 160 304 ${R} 308 L ${R} 318 Q 160 322 ${L} 318 Z`}
            fill="#b69968" stroke="#7a5a3a" strokeWidth="1" />
      {/* wound-cord shading across the front of the cuff */}
      <path d={`M ${L + 1} 313 Q 160 309 ${R - 1} 313`} stroke="#9d8350" strokeWidth="2.2" fill="none" opacity=".55" />
      <path d={`M ${L + 3} 310 Q 160 307 ${R - 3} 310`} stroke="#e7d4a8" strokeWidth=".7" fill="none" opacity=".55" />
      {/* two trailing ends splaying out from under the tie */}
      <path d={`M ${L + 10} 321 Q ${L + 5} 335 ${L + 12} 346`} stroke="#7a5a3a" strokeWidth="1.6" fill="none" strokeLinecap="round" />
      <path d={`M ${R - 10} 321 Q ${R - 5} 335 ${R - 12} 346`} stroke="#7a5a3a" strokeWidth="1.6" fill="none" strokeLinecap="round" />
    </g>
  );
}

// ── Accents: little fauna + ambient touches sprinkled over the arrangement ──
// Each critter is authored around (0,0) and placed by the caller. Kept small and
// muted to stay tasteful — these still print on a real card. All composed of basic
// shapes per the SVG rules, so they rasterize crisply into the print front.
function Bee({ x, y, rot = -12 }) {
  return (
    <g transform={`translate(${x} ${y}) rotate(${rot})`}>
      {/* dashed flight path trailing in */}
      <path d="M -30 -14 Q -18 -20 -8 -10 Q -2 -4 4 -7" fill="none" stroke="#c7b48f"
            strokeWidth="1" strokeDasharray="2 3" strokeLinecap="round" opacity=".7" />
      {/* wings */}
      <ellipse cx="-1" cy="-5" rx="5" ry="3.4" fill="#eaf1f4" stroke="#bcd0d6" strokeWidth=".5" opacity=".85" transform="rotate(-24 -1 -5)" />
      <ellipse cx="3"  cy="-5" rx="5" ry="3.4" fill="#eaf1f4" stroke="#bcd0d6" strokeWidth=".5" opacity=".85" transform="rotate(20 3 -5)" />
      {/* amber body + soft-dark stripes */}
      <ellipse cx="0" cy="0" rx="6.5" ry="4.6" fill="#e3a93c" />
      <path d="M -3 -3.6 Q -3.5 0 -2.2 3.8" stroke="#46402f" strokeWidth="1.6" fill="none" />
      <path d="M 0.6 -4.2 Q 0.2 0 1.4 4"   stroke="#46402f" strokeWidth="1.6" fill="none" />
      <path d="M 4 -3 Q 4.6 0 3.6 2.8"     stroke="#46402f" strokeWidth="1.4" fill="none" />
      <circle cx="6.4" cy="0" r="2.4" fill="#46402f" />
    </g>
  );
}
function Butterfly({ x, y, rot = -8 }) {
  return (
    <g transform={`translate(${x} ${y}) rotate(${rot})`}>
      <path d="M0 0 C -12 -14 -18 -8 -14 -1 C -12 3 -5 3 0 0 Z" fill="#c45c6e" opacity=".92" />
      <path d="M0 0 C 12 -14 18 -8 14 -1 C 12 3 5 3 0 0 Z"      fill="#c45c6e" opacity=".92" />
      <path d="M0 0 C -10 8 -12 13 -7 14 C -3 14 -1 7 0 1 Z"    fill="#f0c8b8" opacity=".92" />
      <path d="M0 0 C 10 8 12 13 7 14 C 3 14 1 7 0 1 Z"        fill="#f0c8b8" opacity=".92" />
      <circle cx="-9" cy="-5" r="1.5" fill="#f9f4ea" opacity=".85" />
      <circle cx="9"  cy="-5" r="1.5" fill="#f9f4ea" opacity=".85" />
      <ellipse cx="0" cy="2" rx="1.5" ry="7" fill="#46402f" />
      <path d="M0 -5 Q -3 -12 -6 -13 M0 -5 Q 3 -12 6 -13" stroke="#46402f" strokeWidth=".8" fill="none" strokeLinecap="round" />
    </g>
  );
}
function Ladybug({ x, y, rot = 14 }) {
  return (
    <g transform={`translate(${x} ${y}) rotate(${rot})`}>
      {/* perched on a small leaf */}
      <path d="M -10 8 Q 2 14 14 6 Q 4 2 -10 8 Z" fill="#7e9b6e" opacity=".9" />
      <ellipse cx="0" cy="0" rx="6.5" ry="5.6" fill="#b5524e" />
      <path d="M0 -5.4 L0 5.4" stroke="#3c352a" strokeWidth=".8" />
      <circle cx="0" cy="-5" r="2.4" fill="#3c352a" />
      <circle cx="-3"   cy="-1" r="1.3" fill="#3c352a" />
      <circle cx="3"    cy="-1" r="1.3" fill="#3c352a" />
      <circle cx="-2.6" cy="3"  r="1.1" fill="#3c352a" />
      <circle cx="2.6"  cy="3"  r="1.1" fill="#3c352a" />
    </g>
  );
}
function Dragonfly({ x, y, rot = -18 }) {
  return (
    <g transform={`translate(${x} ${y}) rotate(${rot})`}>
      <ellipse cx="-7" cy="-3" rx="9" ry="2.6" fill="#cdd9e0" stroke="#aebfc4" strokeWidth=".4" opacity=".7" transform="rotate(-18 -7 -3)" />
      <ellipse cx="7"  cy="-3" rx="9" ry="2.6" fill="#cdd9e0" stroke="#aebfc4" strokeWidth=".4" opacity=".7" transform="rotate(18 7 -3)" />
      <ellipse cx="-7" cy="1"  rx="8" ry="2.4" fill="#cdd9e0" stroke="#aebfc4" strokeWidth=".4" opacity=".6" transform="rotate(-8 -7 1)" />
      <ellipse cx="7"  cy="1"  rx="8" ry="2.4" fill="#cdd9e0" stroke="#aebfc4" strokeWidth=".4" opacity=".6" transform="rotate(8 7 1)" />
      <circle cx="0" cy="-3" r="2.4" fill="#8a7fb0" />
      <path d="M0 -3 Q 1 8 0 17" stroke="#8a7fb0" strokeWidth="2.2" fill="none" strokeLinecap="round" />
    </g>
  );
}
function Hummingbird({ x, y, rot = -12 }) {
  return (
    <g transform={`translate(${x} ${y}) rotate(${rot})`}>
      {/* dashed darting path trailing in from behind */}
      <path d="M 30 9 Q 20 3 12 7 Q 7 9 4 5" fill="none" stroke="#c7b48f"
            strokeWidth="1" strokeDasharray="2 3" strokeLinecap="round" opacity=".6" />
      {/* needle beak */}
      <path d="M -6 -1 L -17 -4" stroke="#5b5142" strokeWidth="1.1" strokeLinecap="round" />
      {/* fanned tail */}
      <path d="M 8 1 L 18 3 L 16 7 L 8 4 Z" fill="#5e9079" opacity=".9" />
      {/* teal body, head to tail */}
      <path d="M -6 -1 Q 1 -6 10 -1 Q 4 4 -3 3 Q -6 2 -6 -1 Z" fill="#4f8f78" />
      {/* rose throat (ties to the brand rose) */}
      <path d="M -5 0.5 Q -1 3.5 4 2 Q -0.5 4.5 -5 3 Z" fill="#bf5f6e" opacity=".95" />
      {/* eye */}
      <circle cx="-3" cy="-1.5" r=".9" fill="#2f2a22" />
      {/* blurred beating wing */}
      <ellipse cx="3" cy="-3" rx="11" ry="4.2" fill="#cfe0d6" stroke="#a9c4b6" strokeWidth=".4"
               opacity=".5" transform="rotate(-38 3 -3)" />
    </g>
  );
}
function Petals({ seed = 1 }) {
  // a few blossom petals drifting near the edges, away from the note/crown center.
  // Each is nudged by the seed so the scatter differs from card to card.
  const drift = [[44,86,-20,'#f0c8b8'],[270,150,30,'#e8b4bf'],[58,300,12,'#f0c8b8'],[258,312,-24,'#e8b4bf'],[214,58,40,'#f3d9c4']];
  return (
    <g opacity=".88">
      {drift.map(([px,py,rot,col],i) => {
        const dx = (hashRand(seed, i*2 + 1) - 0.5) * 30;
        const dy = (hashRand(seed, i*2 + 2) - 0.5) * 26;
        const dr = (hashRand(seed, i + 40) - 0.5) * 44;
        return (
          <path key={i} d="M0 0 Q 5 -3 7 -9 Q 2 -7 0 0 Z" fill={col}
                transform={`translate(${px+dx} ${py+dy}) rotate(${rot+dr}) scale(1.5)`} />
        );
      })}
    </g>
  );
}
function Sparkles({ seed = 1 }) {
  // soft pollen glints around the crown, lightly scattered per seed
  const glints = [[120,118],[206,150],[150,88],[224,210],[96,182],[176,236]];
  return (
    <g opacity=".9">
      {glints.map(([px,py],i) => {
        const dx = (hashRand(seed, i*2 + 7) - 0.5) * 24;
        const dy = (hashRand(seed, i*2 + 8) - 0.5) * 24;
        const sc = 0.8 + hashRand(seed, i + 20) * 0.6;
        return (
          <path key={i} d="M0 -4.4 L1 -1 L4.4 0 L1 1 L0 4.4 L-1 1 L-4.4 0 L-1 -1 Z"
                fill="#f3d57a" transform={`translate(${px+dx} ${py+dy}) scale(${sc})`} />
        );
      })}
    </g>
  );
}
function Fireflies({ seed = 1 }) {
  // a few warm evening glows drifting near the crown — sparser + softer than pollen,
  // each a layered halo → core → bright center so it reads as a quiet light.
  const spots = [[110,128],[214,108],[150,210],[92,170]];
  return (
    <g>
      {spots.map(([px,py],i) => {
        const dx = (hashRand(seed, i*2 + 11) - 0.5) * 30;
        const dy = (hashRand(seed, i*2 + 12) - 0.5) * 28;
        const sc = 0.85 + hashRand(seed, i + 30) * 0.5;
        return (
          <g key={i} transform={`translate(${px+dx} ${py+dy}) scale(${sc})`}>
            <circle r="7"   fill="#f6d98a" opacity=".22" />
            <circle r="3.4" fill="#f4cf6e" opacity=".4" />
            <circle r="1.6" fill="#fff3cf" />
          </g>
        );
      })}
    </g>
  );
}
// Each critter roams within its own box (kept roughly disjoint so they spread
// around the crown). A position is picked from a seeded RNG and re-rolled a few
// times if it lands too close to an already-placed critter — so every card gets a
// slightly different arrangement, yet stays deterministic in (seed, selection) so
// the live preview and the rasterized print agree.
const CRITTER_BOX = {
  bee:       { x0: 178, y0: 92,  x1: 248, y1: 158, rot: -12 },
  butterfly: { x0: 72,  y0: 84,  x1: 138, y1: 150, rot: -8  },
  ladybug:   { x0: 104, y0: 198, x1: 214, y1: 250, rot: 14  },
  dragonfly: { x0: 118, y0: 54,  x1: 206, y1: 96,  rot: -18 },
  hummingbird: { x0: 196, y0: 56, x1: 254, y1: 100, rot: -12 }, // darts in upper-right, facing the crown
};
const CRITTER_ORDER = ['bee', 'butterfly', 'ladybug', 'dragonfly', 'hummingbird']; // canonical placement order (toggle-order independent)

function hashRand(a, b) {
  const x = Math.sin(a * 127.1 + b * 311.7) * 43758.5453;
  return x - Math.floor(x); // 0..1
}
const idNum = (id) => { let s = 0; for (let i = 0; i < id.length; i++) s += id.charCodeAt(i) * (i + 1); return s; };

function placeCritters(selected, seed) {
  const ids = CRITTER_ORDER.filter(id => selected.includes(id));
  const placed = [];
  ids.forEach(id => {
    const b = CRITTER_BOX[id];
    let pick = null;
    for (let a = 0; a < 6; a++) {
      const x = b.x0 + hashRand(seed + idNum(id), a * 2 + 1) * (b.x1 - b.x0);
      const y = b.y0 + hashRand(seed + idNum(id), a * 2 + 2) * (b.y1 - b.y0);
      pick = { id, x, y, rot: b.rot + (hashRand(seed + idNum(id), 99) - 0.5) * 18 };
      if (placed.every(p => Math.hypot(p.x - x, p.y - y) > 48)) break; // clear of others → keep
    }
    placed.push(pick);
  });
  return placed;
}

function Accents({ accents = [], seed = 1 }) {
  if (!accents.length) return null;
  const crit = placeCritters(accents, seed);
  return (
    <g>
      {crit.map(({ id, x, y, rot }) => {
        switch (id) {
          case 'bee':       return <Bee key={id} x={x} y={y} rot={rot} />;
          case 'butterfly': return <Butterfly key={id} x={x} y={y} rot={rot} />;
          case 'ladybug':   return <Ladybug key={id} x={x} y={y} rot={rot} />;
          case 'dragonfly': return <Dragonfly key={id} x={x} y={y} rot={rot} />;
          case 'hummingbird': return <Hummingbird key={id} x={x} y={y} rot={rot} />;
          default:          return null;
        }
      })}
      {accents.includes('petals')    && <Petals seed={seed} />}
      {accents.includes('sparkles')  && <Sparkles seed={seed} />}
      {accents.includes('fireflies') && <Fireflies seed={seed} />}
    </g>
  );
}

function Bouquet({ stems, wrap, accent, accents = [], accentSeed = 1, fullness = 1 }) {
  // sort stems so taller flowers render behind (smaller t = center)
  const ordered = stems.map((s, i) => ({ ...s, i })).sort((a, b) => {
    const ai = Math.abs(a.i / Math.max(1, stems.length - 1) - 0.5);
    const bi = Math.abs(b.i / Math.max(1, stems.length - 1) - 0.5);
    return bi - ai;
  });
  return (
    <svg viewBox="0 0 320 420" width="100%" style={{ display: 'block' }}>
      <defs>
        <radialGradient id="bouquet-shadow" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="rgba(60,40,20,.22)" />
          <stop offset="100%" stopColor="rgba(60,40,20,0)" />
        </radialGradient>
      </defs>
      {/* #bouquet-frame is the croppable content: the live preview + print both
          crop the viewBox to this group's getBBox, so only the bouquet defines
          the framing. */}
      <g id="bouquet-frame">
        {/* ground shadow */}
        <ellipse cx="160" cy="402" rx="100" ry="10" fill="url(#bouquet-shadow)" />
        {/* back stems first (sorted: outer first) */}
        {ordered.map(s => (
          <Stem key={s.id} type={s.type} index={s.i} total={stems.length} isNew={s.isNew} wrap={wrap} fullness={fullness} />
        ))}
        <Wrap style={wrap} count={stems.length} fullness={fullness} />
        {/* critters + petals + sparkles perch on top */}
        <Accents accents={accents} seed={accentSeed} />
      </g>
    </svg>
  );
}

// Export to global
Object.assign(window, { Bouquet, FLOWERS, FLOWER_VOCAB });
