{"spec_id":"area-mountain-panorama","library":"muix","language":"javascript","code":"// anyplot.ai\n// area-mountain-panorama: Mountain Panorama Profile with Labeled Peaks\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-08-20\n//# anyplot-orientation: landscape\n// anyplot.ai\n// area-mountain-panorama: Mountain Panorama Profile with Labeled Peaks\n// Library: muix 7.29.1 | JavaScript 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-08-20\n\nimport { LineChart } from \"@mui/x-charts/LineChart\";\n\nconst t = window.ANYPLOT_TOKENS;\n// Tertiary/\"muted\" theme anchor (default-style-guide.md) — the harness's\n// ANYPLOT_TOKENS doesn't carry it, so it's derived the same way as t.inkSoft.\nconst MUTED = t.theme === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n\n// --- Deterministic PRNG (mulberry32) — the browser has no seeded Math.random ---\nfunction mulberry32(seed) {\n  let a = seed;\n  return function random() {\n    a |= 0;\n    a = (a + 0x6d2b79f5) | 0;\n    let x = Math.imul(a ^ (a >>> 15), 1 | a);\n    x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;\n    return ((x ^ (x >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\n// --- Wallis (Valais, Switzerland) panorama anchored on the Matterhorn — the\n// classic Zermatt / Gornergrat view across the 4000 m Wallis summits ---\nconst PEAKS = [\n  { name: \"Weisshorn\", angle: -62, elev: 4506 },\n  { name: \"Zinalrothorn\", angle: -54, elev: 4221 },\n  { name: \"Ober Gabelhorn\", angle: -47, elev: 4063 },\n  { name: \"Dent Blanche\", angle: -39, elev: 4358 },\n  { name: \"Matterhorn\", angle: -28, elev: 4478 },\n  { name: \"Dom\", angle: -14, elev: 4545 },\n  { name: \"Täschhorn\", angle: -6, elev: 4491 },\n  { name: \"Alphubel\", angle: 3, elev: 4206 },\n  { name: \"Allalinhorn\", angle: 11, elev: 4027 },\n  { name: \"Rimpfischhorn\", angle: 19, elev: 4199 },\n  { name: \"Strahlhorn\", angle: 27, elev: 4190 },\n  { name: \"Breithorn\", angle: 36, elev: 4164 },\n  { name: \"Pollux\", angle: 44, elev: 4092 },\n  { name: \"Castor\", angle: 50, elev: 4223 },\n  { name: \"Liskamm\", angle: 57, elev: 4527 },\n  { name: \"Monte Rosa\", angle: 64, elev: 4634 },\n];\n\nconst BASELINE = 2650; // valley-floor elevation at the panorama's flat edges\nconst EDGE_PAD = 11;\nconst X_MIN = PEAKS[0].angle - EDGE_PAD;\nconst X_MAX = PEAKS[PEAKS.length - 1].angle + EDGE_PAD;\nconst Y_MIN = 2500; // sensible lower bound — keeps the ridge in the upper plot\nconst Y_MAX = 5600; // headroom above the tallest summit for staggered labels\n\n// Coarse control-point skyline: baseline -> (col, apex, col) per summit -> baseline.\n// Flank widths and saddle depths vary per peak (steep/gentle asymmetry) but are\n// always clamped below the gap to the next summit, so shoulders never cross.\nfunction buildControlPoints(peaks, rng, prominenceRange) {\n  const pts = [{ angle: X_MIN, elev: BASELINE }];\n  for (let i = 0; i < peaks.length; i++) {\n    const p = peaks[i];\n    const prevAngle = i === 0 ? X_MIN : peaks[i - 1].angle;\n    const nextAngle = i === peaks.length - 1 ? X_MAX : peaks[i + 1].angle;\n    const widthBefore = Math.min(1.4 + rng() * 3.8, (p.angle - prevAngle) * 0.42);\n    const widthAfter = Math.min(1.4 + rng() * 3.8, (nextAngle - p.angle) * 0.42);\n    const prominence = prominenceRange[0] + rng() * (prominenceRange[1] - prominenceRange[0]);\n    const colElev = Math.max(BASELINE, p.elev - prominence);\n    pts.push({ angle: p.angle - widthBefore, elev: colElev + (rng() - 0.5) * 90 });\n    pts.push({ angle: p.angle, elev: p.elev });\n    pts.push({ angle: p.angle + widthAfter, elev: colElev + (rng() - 0.5) * 90 });\n  }\n  pts.push({ angle: X_MAX, elev: BASELINE });\n  return pts;\n}\n\n// Midpoint-displacement roughening (1-D terrain fractal). Jitter amplitude scales\n// with each segment's own span, so the short flanks right next to a summit stay\n// sharp while long saddle stretches pick up rugged sub-peaks and rocky notches.\n// Original vertices (incl. every peak apex) are carried through untouched.\nfunction roughen(points, rng, depth, ampPerDegree, persistence) {\n  let pts = points;\n  let amp = ampPerDegree;\n  for (let d = 0; d < depth; d++) {\n    const next = [pts[0]];\n    for (let i = 0; i < pts.length - 1; i++) {\n      const a = pts[i];\n      const b = pts[i + 1];\n      const span = b.angle - a.angle;\n      const jitter = (rng() - 0.5) * 2 * amp * span;\n      const midElev = Math.max(BASELINE - 40, (a.elev + b.elev) / 2 + jitter);\n      next.push({ angle: (a.angle + b.angle) / 2, elev: midElev });\n      next.push(b);\n    }\n    pts = next;\n    amp *= persistence;\n  }\n  return pts;\n}\n\nconst rngFg = mulberry32(20260820);\n// Prominence capped so cols between neighboring 4000ers stay well above the\n// valley floor (real Wallis cols sit ~3400-3900 m) — a connected ridge, not\n// isolated witch's-hat spikes.\nconst fgControl = buildControlPoints(PEAKS, rngFg, [380, 620]);\nconst fgRidge = roughen(fgControl, rngFg, 5, 40, 0.58);\nconst fgAngles = fgRidge.map((p) => p.angle);\nconst fgElevs = fgRidge.map((p) => Math.round(p.elev));\n\n// Background haze ridge: a lower, hazier receding row of sub-summits, shifted a\n// few degrees east — same angular span, independent shape, no labels — for the\n// \"layered depth toward the sky\" cue from a classic panorama photograph.\nconst HAZE_PEAKS = PEAKS.map((p) => ({ angle: p.angle + 4, elev: BASELINE + (p.elev - BASELINE) * 0.5 }));\nconst rngBg = mulberry32(777);\nconst bgControl = buildControlPoints(HAZE_PEAKS, rngBg, [200, 520]);\nconst bgRidge = roughen(bgControl, rngBg, 4, 26, 0.55);\nconst bgAngles = bgRidge.map((p) => p.angle);\nconst bgElevs = bgRidge.map((p) => Math.round(p.elev));\n\nconst TITLE = \"Wallis Panorama · area-mountain-panorama · javascript · muix · anyplot.ai\";\nconst TITLE_FS = Math.max(16, Math.round(22 * 67 / TITLE.length));\n\n// Chart coordinate space constants — shared by LineChart's own scale and the\n// hand-placed peak-label/leader-line overlay below.\nconst MARGIN = { top: 26, right: 26, bottom: 16, left: 96 };\n\nfunction toPixel(angle, elev, chartW, chartH) {\n  const pw = chartW - MARGIN.left - MARGIN.right;\n  const ph = chartH - MARGIN.top - MARGIN.bottom;\n  return {\n    left: MARGIN.left + ((angle - X_MIN) / (X_MAX - X_MIN)) * pw,\n    top: MARGIN.top + (1 - (elev - Y_MIN) / (Y_MAX - Y_MIN)) * ph,\n  };\n}\n\n// Label staggering: round-robin through 4 fixed label \"shelves\" near the top\n// of the chart, in angle order — leader lines run from each shelf down to the\n// summit's own (varying) elevation, so shelf assignment is a pure horizontal\n// problem. PEAKS are fairly evenly spaced (6-14 deg apart), so any two summits\n// sharing a shelf are always 3 summits apart — far more horizontal room than\n// a label ever needs — which keeps neighboring names from colliding.\nconst LABEL_ROWS = [0, 46, 92, 138];\n\nfunction assignTiers(peaksPx) {\n  return peaksPx.map((p, i) => ({ ...p, tier: i % LABEL_ROWS.length }));\n}\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n\n  const PAD_TOP = 16;\n  const HEADER_H = 58;\n  const FOOTER_H = 30;\n  const PAD_SIDE = 24;\n  const chartW = W - PAD_SIDE * 2;\n  const chartH = H - PAD_TOP - HEADER_H - FOOTER_H;\n\n  const peaksPx = assignTiers(\n    PEAKS.map((p) => ({ ...p, ...toPixel(p.angle, p.elev, chartW, chartH) }))\n  );\n\n  return (\n    <div\n      style={{\n        width: W,\n        height: H,\n        backgroundColor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        padding: `${PAD_TOP}px ${PAD_SIDE}px 0`,\n        boxSizing: \"border-box\",\n        fontFamily: \"sans-serif\",\n      }}\n    >\n      <div\n        style={{\n          color: t.ink,\n          fontSize: TITLE_FS,\n          fontWeight: 600,\n          lineHeight: `${HEADER_H}px`,\n          flexShrink: 0,\n          whiteSpace: \"nowrap\",\n          overflow: \"hidden\",\n          textOverflow: \"ellipsis\",\n        }}\n      >\n        {TITLE}\n      </div>\n\n      <div style={{ position: \"relative\", width: chartW, height: chartH, flexShrink: 0 }}>\n        <LineChart\n          width={chartW}\n          height={chartH}\n          margin={MARGIN}\n          skipAnimation\n          bottomAxis={null}\n          grid={{ horizontal: true }}\n          xAxis={[\n            { id: \"x\", data: fgAngles, min: X_MIN, max: X_MAX, scaleType: \"linear\", domainLimit: \"strict\" },\n            { id: \"xBg\", data: bgAngles, min: X_MIN, max: X_MAX, scaleType: \"linear\", domainLimit: \"strict\" },\n          ]}\n          yAxis={[\n            {\n              min: Y_MIN,\n              max: Y_MAX,\n              domainLimit: \"strict\",\n              tickMinStep: 500,\n              tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n            },\n          ]}\n          series={[\n            { id: \"haze\", xAxisId: \"xBg\", data: bgElevs, area: true, showMark: false, curve: \"linear\", color: MUTED },\n            { id: \"ridge\", data: fgElevs, area: true, showMark: false, curve: \"linear\", color: t.palette[0] },\n          ]}\n          sx={{\n            \"& .MuiAreaElement-series-haze\": { fillOpacity: 0.32 },\n            \"& .MuiLineElement-series-haze\": { strokeWidth: 1.5, strokeOpacity: 0.45 },\n            \"& .MuiAreaElement-series-ridge\": { fillOpacity: 0.94 },\n            \"& .MuiLineElement-series-ridge\": { strokeWidth: 2.5 },\n            \"& .MuiChartsGrid-line\": { stroke: t.grid, strokeDasharray: \"4 3\" },\n            \"& .MuiChartsAxis-line\": { stroke: t.inkSoft, strokeOpacity: 0.4 },\n            \"& .MuiChartsAxis-tick\": { stroke: t.inkSoft, strokeOpacity: 0.4 },\n          }}\n        />\n\n        {/* Hand-placed y-axis title — MUI X's built-in axis label sits too close\n            to wide tick text at this font size, so it's positioned manually. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            top: 0,\n            width: 28,\n            height: chartH,\n            display: \"flex\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            pointerEvents: \"none\",\n          }}\n        >\n          <div style={{ transform: \"rotate(-90deg)\", whiteSpace: \"nowrap\", color: t.ink, fontSize: 15, fontWeight: 500 }}>\n            Elevation (m)\n          </div>\n        </div>\n\n        {peaksPx.map((p) => {\n          // The Matterhorn is the panorama's stated anchor summit — give it a\n          // distinct highlight (bigger ink-stroked apex, soft brand-green glow,\n          // bolder label) so it reads as the composition's focal point, using\n          // only the existing brand green + ink tokens (no new hue).\n          const isFocal = p.name === \"Matterhorn\";\n          const labelTop = MARGIN.top + 14 + LABEL_ROWS[p.tier];\n          const lineTop = labelTop + 32;\n          const lineHeight = Math.max(0, p.top - lineTop - 5);\n          return (\n            <div key={p.name}>\n              {isFocal && (\n                <div\n                  style={{\n                    position: \"absolute\",\n                    left: p.left - 55,\n                    top: p.top - 55,\n                    width: 110,\n                    height: 110,\n                    borderRadius: \"50%\",\n                    background: `radial-gradient(circle, ${t.palette[0]}33 0%, ${t.palette[0]}00 70%)`,\n                    pointerEvents: \"none\",\n                  }}\n                />\n              )}\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: p.left - 0.5,\n                  top: lineTop,\n                  width: isFocal ? 2 : 1,\n                  height: lineHeight,\n                  backgroundColor: isFocal ? t.ink : t.inkSoft,\n                  opacity: isFocal ? 0.8 : 0.5,\n                  pointerEvents: \"none\",\n                }}\n              />\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: p.left - (isFocal ? 5 : 3),\n                  top: p.top - (isFocal ? 5 : 3),\n                  width: isFocal ? 10 : 6,\n                  height: isFocal ? 10 : 6,\n                  borderRadius: \"50%\",\n                  backgroundColor: t.palette[0],\n                  border: `${isFocal ? 2 : 1.5}px solid ${isFocal ? t.ink : t.pageBg}`,\n                  pointerEvents: \"none\",\n                }}\n              />\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: p.left,\n                  top: labelTop,\n                  transform: \"translateX(-50%)\",\n                  textAlign: \"center\",\n                  whiteSpace: \"nowrap\",\n                  pointerEvents: \"none\",\n                }}\n              >\n                <div style={{ color: t.ink, fontSize: isFocal ? 17 : 14, fontWeight: isFocal ? 800 : 700 }}>\n                  {p.name}\n                </div>\n                <div style={{ color: t.inkSoft, fontSize: isFocal ? 13 : 12, fontWeight: isFocal ? 700 : 400 }}>\n                  {p.elev} m\n                </div>\n              </div>\n            </div>\n          );\n        })}\n      </div>\n\n      <div\n        style={{\n          color: t.inkSoft,\n          fontSize: 13,\n          textAlign: \"center\",\n          lineHeight: `${FOOTER_H}px`,\n          flexShrink: 0,\n        }}\n      >\n        Wallis Alps, Switzerland · view toward the Matterhorn from the Gornergrat ridge\n      </div>\n    </div>\n  );\n}\n"}