{"spec_id":"feynman-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// feynman-basic: Feynman Diagram for Particle Interactions\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-20\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst SIZE = window.ANYPLOT_SIZE;\nconst TITLE = \"feynman-basic · javascript · muix · anyplot.ai\";\nconst SUBTITLE = \"Electron–positron annihilation → virtual photon → muon pair (QED)\";\n\nconst FONT =\n  '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\n\n// Imprint palette — data colors identical across themes; first series is\n// always brand green.\nconst FERMION = t.palette[0]; // #009E73 — every fermion leg (e-, e+, mu-, mu+)\nconst PHOTON = t.palette[1]; // #C475FD — photon propagator\nconst GLUON = t.palette[2]; // #4467A3 — gluon (shown in the style key only)\nconst BOSON = t.palette[3]; // #BD8233 — scalar boson, e.g. Higgs (style key only)\n\n// Data-space domain. x runs left→right as the time axis; y is plain vertical\n// space. 100×60 roughly matches the 1600×900 CSS mount aspect ratio so pixel\n// shapes (computed after scaling) aren't stretched.\nconst X = [0, 100];\nconst Y = [-20, 40];\n\n// --- Data model: particles / vertices / propagators (per specification.md) --\nconst vertices = {\n  v1: [34, 12], // e- + e+ annihilate here\n  v2: [66, 12], // gamma* pair-produces mu- + mu+ here\n  eIn: [8, 24], // incoming e-\n  posIn: [8, 0], // incoming e+\n  muOut: [92, 24], // outgoing mu-\n  antiMuOut: [92, 0], // outgoing mu+\n};\n\nconst particles = [\n  { id: \"e-\", type: \"fermion\", label: \"e⁻\", anti: false },\n  { id: \"e+\", type: \"fermion\", label: \"e⁺\", anti: true },\n  { id: \"gamma\", type: \"photon\", label: \"γ*\", anti: false },\n  { id: \"mu-\", type: \"fermion\", label: \"μ⁻\", anti: false },\n  { id: \"mu+\", type: \"fermion\", label: \"μ⁺\", anti: true },\n];\nconst particleById = Object.fromEntries(particles.map((p) => [p.id, p]));\n\nconst propagators = [\n  { from_vertex: \"eIn\", to_vertex: \"v1\", particle_id: \"e-\" },\n  { from_vertex: \"posIn\", to_vertex: \"v1\", particle_id: \"e+\" },\n  { from_vertex: \"v1\", to_vertex: \"v2\", particle_id: \"gamma\" },\n  { from_vertex: \"v2\", to_vertex: \"muOut\", particle_id: \"mu-\" },\n  { from_vertex: \"v2\", to_vertex: \"antiMuOut\", particle_id: \"mu+\" },\n];\n\n// --- Geometry helpers (pixel space, after scaling) --------------------------\nfunction wavyPath(x1, y1, x2, y2, waves, amp) {\n  const dx = x2 - x1;\n  const dy = y2 - y1;\n  const len = Math.hypot(dx, dy);\n  const ux = dx / len;\n  const uy = dy / len;\n  const px = -uy;\n  const py = ux;\n  const steps = Math.max(24, Math.round(waves * 22));\n  let d = \"\";\n  for (let i = 0; i <= steps; i++) {\n    const u = i / steps;\n    const offset = amp * Math.sin(u * waves * 2 * Math.PI);\n    const x = x1 + ux * u * len + px * offset;\n    const y = y1 + uy * u * len + py * offset;\n    d += `${i === 0 ? \"M\" : \"L\"} ${x.toFixed(1)} ${y.toFixed(1)} `;\n  }\n  return d;\n}\n\n// Coiled-spring look for a gluon: a chain of overlapping cubic-bezier bumps\n// alternating above/below the path.\nfunction curlyPath(x1, y1, x2, y2, loops, radius) {\n  const dx = x2 - x1;\n  const dy = y2 - y1;\n  const len = Math.hypot(dx, dy);\n  const ux = dx / len;\n  const uy = dy / len;\n  const px = -uy;\n  const py = ux;\n  const loopLen = len / loops;\n  let d = `M ${x1.toFixed(1)} ${y1.toFixed(1)} `;\n  for (let i = 0; i < loops; i++) {\n    const s0 = i * loopLen;\n    const c1x = x1 + ux * (s0 + loopLen * 0.15) - px * radius;\n    const c1y = y1 + uy * (s0 + loopLen * 0.15) - py * radius;\n    const c2x = x1 + ux * (s0 + loopLen * 0.85) + px * radius;\n    const c2y = y1 + uy * (s0 + loopLen * 0.85) + py * radius;\n    const ex = x1 + ux * (s0 + loopLen);\n    const ey = y1 + uy * (s0 + loopLen);\n    d += `C ${c1x.toFixed(1)} ${c1y.toFixed(1)}, ${c2x.toFixed(1)} ${c2y.toFixed(1)}, ${ex.toFixed(1)} ${ey.toFixed(1)} `;\n  }\n  return d;\n}\n\n// Auto-oriented SVG markers give a reliable arrowhead: the browser places and\n// rotates the triangle exactly at the path's endpoint, unlike a hand-rolled\n// polygon overlaid on a full-length line (which reads by which side has the\n// longer unbroken run of line, not by the triangle's own geometry).\nfunction ArrowDefs() {\n  return (\n    <defs>\n      <marker\n        id=\"arrow-fermion\"\n        markerWidth=\"11\"\n        markerHeight=\"11\"\n        refX=\"8\"\n        refY=\"5.5\"\n        orient=\"auto\"\n        markerUnits=\"userSpaceOnUse\"\n      >\n        <path d=\"M0,0 L11,5.5 L0,11 Z\" fill={FERMION} />\n      </marker>\n      <marker\n        id=\"arrow-axis\"\n        markerWidth=\"9\"\n        markerHeight=\"9\"\n        refX=\"7\"\n        refY=\"4.5\"\n        orient=\"auto\"\n        markerUnits=\"userSpaceOnUse\"\n      >\n        <path d=\"M0,0 L9,4.5 L0,9 Z\" fill={t.inkSoft} />\n      </marker>\n    </defs>\n  );\n}\n\n// Fermion propagator drawn as two half-segments so the arrowhead marker sits\n// exactly at the path midpoint. Particles flow forward in time (arrow toward\n// `to`); antiparticles are drawn flowing backward, so the marker-carrying\n// half is the one nearer `from` instead.\nfunction FermionLine({ x1, y1, x2, y2, anti }) {\n  const mx = (x1 + x2) / 2;\n  const my = (y1 + y2) / 2;\n  const marked = anti\n    ? { ax: x2, ay: y2, bx: x1, by: y1 }\n    : { ax: x1, ay: y1, bx: x2, by: y2 };\n  return (\n    <g stroke={FERMION} strokeWidth={4.5} strokeLinecap=\"round\">\n      <line x1={marked.ax} y1={marked.ay} x2={mx} y2={my} markerEnd=\"url(#arrow-fermion)\" />\n      <line x1={marked.bx} y1={marked.by} x2={mx} y2={my} />\n    </g>\n  );\n}\n\n// --- Overlay layers -----------------------------------------------------------\nfunction Propagators() {\n  const xs = useXScale();\n  const ys = useYScale();\n  return (\n    <g>\n      <ArrowDefs />\n      {propagators.map((prop, i) => {\n        const particle = particleById[prop.particle_id];\n        const [x1d, y1d] = vertices[prop.from_vertex];\n        const [x2d, y2d] = vertices[prop.to_vertex];\n        const x1 = xs(x1d);\n        const y1 = ys(y1d);\n        const x2 = xs(x2d);\n        const y2 = ys(y2d);\n        if (particle.type === \"photon\") {\n          return (\n            <path\n              key={i}\n              d={wavyPath(x1, y1, x2, y2, 6, 12)}\n              stroke={PHOTON}\n              strokeWidth={4.5}\n              fill=\"none\"\n            />\n          );\n        }\n        return <FermionLine key={i} x1={x1} y1={y1} x2={x2} y2={y2} anti={particle.anti} />;\n      })}\n    </g>\n  );\n}\n\nfunction VertexDots() {\n  const xs = useXScale();\n  const ys = useYScale();\n  return (\n    <g>\n      {[\"v1\", \"v2\"].map((id) => (\n        <circle key={id} cx={xs(vertices[id][0])} cy={ys(vertices[id][1])} r={9} fill={t.ink} />\n      ))}\n    </g>\n  );\n}\n\nfunction Labels() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const entries = [\n    { at: vertices.eIn, dx: -14, dy: -10, anchor: \"end\", particle: \"e-\" },\n    { at: vertices.posIn, dx: -14, dy: 24, anchor: \"end\", particle: \"e+\" },\n    { at: vertices.muOut, dx: 14, dy: -10, anchor: \"start\", particle: \"mu-\" },\n    { at: vertices.antiMuOut, dx: 14, dy: 24, anchor: \"start\", particle: \"mu+\" },\n  ];\n  const [mx, my] = [(vertices.v1[0] + vertices.v2[0]) / 2, vertices.v1[1]];\n  return (\n    <g fontFamily={FONT} fontWeight={700}>\n      {entries.map(({ at, dx, dy, anchor, particle }) => (\n        <text\n          key={particle}\n          x={xs(at[0]) + dx}\n          y={ys(at[1]) + dy}\n          textAnchor={anchor}\n          fontSize={24}\n          fill={FERMION}\n        >\n          {particleById[particle].label}\n        </text>\n      ))}\n      <text x={xs(mx)} y={ys(my) - 22} textAnchor=\"middle\" fontSize={24} fill={PHOTON}>\n        {particleById.gamma.label}\n      </text>\n    </g>\n  );\n}\n\nfunction TimeAxis() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const y = -6;\n  const x1 = xs(vertices.eIn[0]);\n  const x2 = xs(92);\n  const py = ys(y);\n  return (\n    <g fontFamily={FONT}>\n      <line\n        x1={x1}\n        y1={py}\n        x2={x2}\n        y2={py}\n        stroke={t.inkSoft}\n        strokeWidth={1.5}\n        markerEnd=\"url(#arrow-axis)\"\n      />\n      <text x={x2 + 14} y={py + 5} fontSize={16} fill={t.inkSoft}>\n        time\n      </text>\n    </g>\n  );\n}\n\n// Style key covering all four line conventions from the spec, independent of\n// which types this particular process happens to use.\nfunction StyleKey() {\n  const xs = useXScale();\n  const ys = useYScale();\n  const y = -13;\n  const swatchW = 60;\n  const items = [\n    { x: 10, color: FERMION, kind: \"fermion\", label: \"Fermion (e⁻, q, …)\" },\n    { x: 34, color: PHOTON, kind: \"photon\", label: \"Photon (γ)\" },\n    { x: 58, color: GLUON, kind: \"gluon\", label: \"Gluon (g)\" },\n    { x: 82, color: BOSON, kind: \"boson\", label: \"Scalar boson (H)\" },\n  ];\n  return (\n    <g fontFamily={FONT}>\n      {items.map(({ x, color, kind, label }) => {\n        const px = xs(x);\n        const py = ys(y);\n        const x2 = px + swatchW;\n        return (\n          <g key={kind}>\n            {kind === \"fermion\" && (\n              <line\n                x1={px}\n                y1={py}\n                x2={x2}\n                y2={py}\n                stroke={color}\n                strokeWidth={4}\n                markerEnd=\"url(#arrow-fermion)\"\n              />\n            )}\n            {kind === \"photon\" && (\n              <path d={wavyPath(px, py, x2, py, 2.5, 8)} stroke={color} strokeWidth={4} fill=\"none\" />\n            )}\n            {kind === \"gluon\" && (\n              <path d={curlyPath(px, py, x2, py, 2.5, 8)} stroke={color} strokeWidth={4} fill=\"none\" />\n            )}\n            {kind === \"boson\" && (\n              <line\n                x1={px}\n                y1={py}\n                x2={x2}\n                y2={py}\n                stroke={color}\n                strokeWidth={4}\n                strokeDasharray=\"12,7\"\n              />\n            )}\n            <text x={px} y={py + 26} fontSize={15} fill={t.inkSoft}>\n              {label}\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\nfunction Frame() {\n  const xs = useXScale();\n  const ys = useYScale();\n  return (\n    <g fontFamily={FONT}>\n      <text x={xs(50)} y={ys(36)} textAnchor=\"middle\" fontSize={30} fontWeight={600} fill={t.ink}>\n        {TITLE}\n      </text>\n      <text x={xs(50)} y={ys(31)} textAnchor=\"middle\" fontSize={17} fill={t.inkSoft}>\n        {SUBTITLE}\n      </text>\n      <text x={xs(50)} y={ys(-18)} textAnchor=\"middle\" fontSize={14} fill={t.inkSoft}>\n        Vertex dots mark interaction points · arrows show particle (forward) vs antiparticle\n        (backward) flow in time\n      </text>\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  return (\n    <ChartContainer\n      width={SIZE.width}\n      height={SIZE.height}\n      margin={{ top: 12, right: 12, bottom: 12, left: 12 }}\n      series={[]}\n      xAxis={[{ id: \"x\", scaleType: \"linear\", min: X[0], max: X[1] }]}\n      yAxis={[{ id: \"y\", scaleType: \"linear\", min: Y[0], max: Y[1] }]}\n      skipAnimation\n    >\n      <Propagators />\n      <VertexDots />\n      <Labels />\n      <TimeAxis />\n      <StyleKey />\n      <Frame />\n    </ChartContainer>\n  );\n}\n"}