// Variant: JUMPYARD — reworked.
// HERO: Mount Everest group challenge (left). Spotlight (right, compact).
// Scales to 150+ jumpers via height-distribution strip + Groups grid + Top10.

const jyStyles = {
  red: "#E63329",
  black: "#0A0A0A",
  charcoal: "#141414",
  panel: "#1A1A1A",
  white: "#FFFFFF",
  yellow: "#FFD23F",
  green: "#3DDC97",
  ice: "#B8E0FF",
  textDim: "rgba(255,255,255,0.55)",
  textMute: "rgba(255,255,255,0.32)",
};

const jyFonts = {
  display: {
    fontFamily: '"Barlow Condensed", "Oswald", Impact, sans-serif',
    fontWeight: 900, fontStyle: "italic",
    textTransform: "uppercase", letterSpacing: -0.5,
  },
  label: {
    fontFamily: '"Barlow Condensed", "Oswald", Impact, sans-serif',
    fontWeight: 700, fontStyle: "italic",
    textTransform: "uppercase", letterSpacing: 1.5,
  },
  num: {
    fontFamily: '"Barlow Condensed", "Oswald", Impact, sans-serif',
    fontWeight: 900, fontStyle: "italic",
    fontVariantNumeric: "tabular-nums",
    letterSpacing: -2,
  },
  body: {
    fontFamily: '"Inter", system-ui, sans-serif', fontWeight: 500,
  },
};

function hexA(hex, a) {
  const h = hex.replace("#","");
  const r = parseInt(h.slice(0,2),16), g = parseInt(h.slice(2,4),16), b = parseInt(h.slice(4,6),16);
  return `rgba(${r},${g},${b},${a})`;
}

// ─────────────────────────────────────────────
// 3D Logo (extracted vector shapes, extruded)
// ─────────────────────────────────────────────
const JY_LOGO_SHAPES = [{"outline":[[0.6374,0.3411],[0.5634,0.4425],[0.4581,0.5361],[0.3411,0.6023],[0.1501,0.6803],[0.0214,0.7466],[0.0721,0.6842],[0.0916,0.6374],[0.0916,0.6062],[0.0721,0.5789],[0.0331,0.5673],[0.0097,0.5712],[-0.0604,0.6023],[-0.1072,0.6335],[-0.2164,0.7349],[-0.2593,0.8168],[-0.2593,0.9025],[-0.2359,0.9571],[-0.2047,0.9961],[-0.2827,0.9649],[-0.3333,0.9298],[-0.3957,0.8674],[-0.4308,0.8168],[-0.462,0.7544],[-0.4893,0.6491],[-0.4893,0.5595],[-0.4698,0.4815],[-0.4191,0.3957],[-0.3606,0.3411],[-0.2086,0.2476],[-0.1423,0.1852],[-0.1111,0.115],[-0.1111,0.0682],[-0.1228,0.0292],[-0.1579,-0.0175],[-0.1735,-0.0292],[-0.1852,-0.0253],[-0.1657,0.0058],[-0.1579,0.037],[-0.1696,0.0916],[-0.2008,0.1267],[-0.2242,0.1384],[-0.2593,0.1384],[-0.2982,0.1111],[-0.3138,0.076],[-0.3138,0.0253],[-0.306,0.0019],[-0.2827,-0.0292],[-0.2904,-0.0331],[-0.306,-0.0253],[-0.3489,0.0292],[-0.3879,0.2047],[-0.4191,0.2749],[-0.4503,0.306],[-0.4776,0.3216],[-0.5439,0.3333],[-0.5088,0.2943],[-0.501,0.2554],[-0.5088,0.2281],[-0.5244,0.2047],[-0.5945,0.1462],[-0.6725,0.0604],[-0.6998,-0.0097],[-0.6998,-0.1033],[-0.6608,-0.1735],[-0.5867,-0.2398],[-0.5984,-0.2125],[-0.5984,-0.1774],[-0.5906,-0.1579],[-0.5789,-0.1579],[-0.5478,-0.193],[-0.4698,-0.2398],[-0.3957,-0.2632],[-0.3216,-0.2749],[-0.232,-0.2788],[-0.0955,-0.2671],[0.0721,-0.2359],[0.1852,-0.2047],[0.271,-0.1735],[0.3684,-0.1267],[0.4737,-0.0565],[0.5673,0.0409],[0.6023,0.0994],[0.6179,0.154],[0.6335,0.1657],[0.6491,0.1345],[0.6491,0.0994],[0.6725,0.1618],[0.6725,0.232]],"holes":[[[0.2086,0.3489],[0.2593,0.3567],[0.3021,0.3411],[0.345,0.3021],[0.3684,0.2593],[0.3762,0.2281],[0.3723,0.1618],[0.3528,0.1228],[0.3177,0.0916],[0.3138,0.0994],[0.3333,0.1345],[0.3333,0.1813],[0.3216,0.2086],[0.2865,0.2398],[0.2437,0.2437],[0.2281,0.2359],[0.2008,0.2008],[0.1969,0.1501],[0.2086,0.1189],[0.2281,0.0994],[0.2203,0.0916],[0.1852,0.1189],[0.154,0.1657],[0.1423,0.2086],[0.1462,0.271],[0.1696,0.3177]]]},{"outline":[[0.6998,0.1033],[0.6452,0.0058],[0.5517,-0.0955],[0.4776,-0.154],[0.3957,-0.2047],[0.2982,-0.2515],[0.1462,-0.3099],[0.1189,-0.3372],[0.0994,-0.4152],[0.076,-0.3606],[0.0487,-0.3372],[0.0253,-0.3294],[-0.1579,-0.3489],[-0.3294,-0.3528],[-0.4113,-0.345],[-0.501,-0.3255],[-0.5673,-0.3021],[-0.6179,-0.2749],[-0.6647,-0.2398],[-0.7037,-0.1969],[-0.6842,-0.8596],[-0.6686,-0.8947],[-0.6257,-0.922],[-0.5906,-0.922],[-0.5478,-0.8986],[-0.5283,-0.8558],[-0.5205,-0.6764],[-0.4347,-0.7193],[-0.3099,-0.7544],[-0.1774,-0.77],[-0.0604,-0.7661],[-0.0526,-0.7739],[-0.0526,-0.9376],[-0.0214,-0.9805],[0.0253,-0.9961],[0.076,-0.9805],[0.1033,-0.9454],[0.1072,-0.7505],[0.115,-0.7427],[0.193,-0.7271],[0.3138,-0.6842],[0.3957,-0.6452],[0.5205,-0.5673],[0.5205,-0.7037],[0.5322,-0.731],[0.575,-0.7622],[0.6218,-0.7622],[0.6491,-0.7466],[0.6647,-0.7271],[0.6764,-0.692]],"holes":[]}];

function JYLogo3D({ size = 88, color = "#ffffff" }) {
  const canvasRef = React.useRef(null);
  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas || typeof THREE === "undefined") return;

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 50);
    camera.position.set(0, 0, 7);

    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
    const effectiveDpr = () => {
      const stageScale = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
      return Math.min(Math.min(window.devicePixelRatio, 2) * stageScale, 4);
    };
    renderer.setPixelRatio(effectiveDpr());
    renderer.setSize(size, size, false);
    renderer.setClearColor(0x000000, 0);
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.05;

    scene.add(new THREE.AmbientLight(0xffffff, 0.3));
    const key = new THREE.DirectionalLight(0xffffff, 1.8);
    key.position.set(5, 6, 6);
    scene.add(key);
    const fill = new THREE.DirectionalLight(0xb39eff, 0.55);
    fill.position.set(-4, 2, 3);
    scene.add(fill);
    const rim = new THREE.DirectionalLight(0xffffff, 1.4);
    rim.position.set(0, 1, -8);
    scene.add(rim);
    const redBounce = new THREE.PointLight(0xe4002b, 3.5, 12, 1.8);
    redBounce.position.set(0, -3.5, 2);
    scene.add(redBounce);
    const topGlow = new THREE.PointLight(0xff4040, 1.2, 8, 2);
    topGlow.position.set(0, 3, 2);
    scene.add(topGlow);

    function buildShape(sd) {
      const shape = new THREE.Shape();
      const o = sd.outline;
      shape.moveTo(o[0][0], o[0][1]);
      for (let i = 1; i < o.length; i++) shape.lineTo(o[i][0], o[i][1]);
      shape.closePath();
      for (const hole of sd.holes) {
        const h = new THREE.Path();
        h.moveTo(hole[0][0], hole[0][1]);
        for (let i = 1; i < hole.length; i++) h.lineTo(hole[i][0], hole[i][1]);
        h.closePath();
        shape.holes.push(h);
      }
      return shape;
    }

    const depth = 0.35;
    const extrudeSettings = {
      depth, bevelEnabled: true,
      bevelThickness: 0.02, bevelSize: 0.015, bevelOffset: 0,
      bevelSegments: 5, curveSegments: 16,
    };

    const material = new THREE.MeshPhysicalMaterial({
      color: new THREE.Color(color),
      roughness: 0.28,
      metalness: 1.0,
      clearcoat: 0.5,
      clearcoatRoughness: 0.15,
      reflectivity: 0.8,
    });

    let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
    for (const sd of JY_LOGO_SHAPES) {
      for (const p of sd.outline) {
        if (p[0] < minX) minX = p[0];
        if (p[0] > maxX) maxX = p[0];
        if (p[1] < minY) minY = p[1];
        if (p[1] > maxY) maxY = p[1];
      }
    }
    const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;

    const pivot = new THREE.Group();
    scene.add(pivot);
    const geos = [];
    for (const sd of JY_LOGO_SHAPES) {
      const shape = buildShape(sd);
      const geo = new THREE.ExtrudeGeometry(shape, extrudeSettings);
      geo.translate(-cx, -cy, -depth / 2);
      const mesh = new THREE.Mesh(geo, material);
      pivot.add(mesh);
      geos.push(geo);
    }
    pivot.scale.set(1.9, 1.9, 1.9);

    let rafId = 0, destroyed = false;
    const tick = (t) => {
      if (destroyed) return;
      pivot.rotation.y += 0.008;
      pivot.rotation.x = -0.22 + Math.sin(t * 0.0008) * 0.05;
      renderer.render(scene, camera);
      rafId = requestAnimationFrame(tick);
    };
    rafId = requestAnimationFrame(tick);

    const onResize = () => {
      renderer.setPixelRatio(effectiveDpr());
      renderer.setSize(size, size, false);
    };
    window.addEventListener("resize", onResize);

    return () => {
      destroyed = true;
      cancelAnimationFrame(rafId);
      window.removeEventListener("resize", onResize);
      for (const g of geos) g.dispose();
      material.dispose();
      renderer.dispose();
    };
  }, [size, color]);

  return <canvas ref={canvasRef} style={{
    width: size, height: size, display: "block",
  }}/>;
}

// ─────────────────────────────────────────────
// Header
// ─────────────────────────────────────────────
function JYHeader() {
  const { clock, tweaks } = useJY();
  const time = clock.toLocaleTimeString("sv", { hour: "2-digit", minute: "2-digit" });
  return (
    <div style={{
      display: "flex", alignItems: "stretch", justifyContent: "space-between",
      height: 148, borderBottom: `2px solid ${jyStyles.red}`,
      position: "relative", zIndex: 2,
    }}>
      <div style={{ display: "flex", alignItems: "stretch" }}>
        <div style={{
          padding: "0 24px", display: "grid", placeItems: "center",
        }}>
          <JYLogo3D size={140} color={jyStyles.red}/>
        </div>
      </div>

      <div style={{ display: "flex", alignItems: "stretch" }}>
        <JYHeaderStat label="Hoppar nu" value={YARD_STATS.jumpersNow} color={jyStyles.green} pulse/>
        <JYHeaderStat label="Hopp idag" value={YARD_STATS.totalJumps.toLocaleString("sv")}/>
        <JYHeaderStat label="Hoppare" value={YARD_STATS.jumpersToday}/>
        <JYHeaderStat label="Airtime" value={YARD_STATS.totalAirtime}/>
        <JYHeaderStat label="Högsta" value={`${YARD_STATS.highestToday}cm`} accent/>
      </div>
    </div>
  );
}

function JYHeaderStat({ label, value, accent, color, pulse }) {
  return (
    <div style={{
      padding: "0 26px", minWidth: 140,
      display: "flex", flexDirection: "column", justifyContent: "center",
      borderLeft: `1px solid ${hexA("#fff", 0.08)}`,
      background: accent ? jyStyles.red : "transparent",
      position: "relative",
    }}>
      <div style={{
        ...jyFonts.label, fontSize: 11,
        color: color || (accent ? hexA("#fff", 0.85) : jyStyles.textDim),
      }}>
        {pulse && <span style={{
          display: "inline-block", width: 6, height: 6, borderRadius: "50%",
          background: color, marginRight: 6, verticalAlign: 1,
          animation: "jy-pulse 1.2s ease-in-out infinite",
        }}/>}
        {label}
      </div>
      <div style={{ ...jyFonts.num, fontSize: 34, color: "#fff", lineHeight: 1 }}>{value}</div>
      <style>{`@keyframes jy-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }`}</style>
    </div>
  );
}

// ─────────────────────────────────────────────
// MOUNT EVEREST HERO — procedural 3D mountain (Three.js) that fills with JY red
// ─────────────────────────────────────────────
const EVEREST_PEAK_HEIGHT = 10.0;

function EverestMountain3D({ filled, goalMeters, spinBoost }) {
  const canvasRef = React.useRef(null);
  const uniformsRef = React.useRef(null);
  const spinBoostRef = React.useRef(1);

  React.useEffect(() => {
    if (!spinBoost) return;
    const startT = performance.now();
    const duration = 3500;
    let id;
    const step = () => {
      const elapsed = performance.now() - startT;
      if (elapsed >= duration) { spinBoostRef.current = 1; return; }
      const t = elapsed / duration;
      spinBoostRef.current = 1 + 14 * (1 - t);
      id = requestAnimationFrame(step);
    };
    id = requestAnimationFrame(step);
    return () => { if (id) cancelAnimationFrame(id); };
  }, [spinBoost]);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas || typeof THREE === "undefined") return;
    const parent = canvas.parentElement;

    let w = Math.max(1, parent.clientWidth);
    let h = Math.max(1, parent.clientHeight);

    const MOUNTAIN_RADIUS = 7.5;
    const PLANE_SIZE = 22;
    const SEGMENTS = 240;

    const scene = new THREE.Scene();
    scene.fog = new THREE.Fog(0x0a0a0c, 24, 60);
    const camera = new THREE.PerspectiveCamera(32, w / h, 0.1, 200);
    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
    const effectiveDpr = () => {
      const stageScale = Math.min(window.innerWidth / 1920, window.innerHeight / 1080);
      return Math.min(Math.min(window.devicePixelRatio, 2) * stageScale, 4);
    };
    renderer.setPixelRatio(effectiveDpr());
    renderer.setSize(w, h, false);
    renderer.setClearColor(0x000000, 0);

    function hash(x, y) {
      const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
      return s - Math.floor(s);
    }
    function smoothNoise(x, y) {
      const xi = Math.floor(x), yi = Math.floor(y);
      const xf = x - xi, yf = y - yi;
      const a = hash(xi, yi), b = hash(xi + 1, yi);
      const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
      const u = xf * xf * (3 - 2 * xf);
      const v = yf * yf * (3 - 2 * yf);
      return a * (1 - u) * (1 - v) + b * u * (1 - v) + c * (1 - u) * v + d * u * v;
    }
    function fbm(x, y, octaves = 5) {
      let amp = 1, freq = 1, sum = 0, max = 0;
      for (let i = 0; i < octaves; i++) {
        sum += amp * smoothNoise(x * freq, y * freq);
        max += amp;
        amp *= 0.5;
        freq *= 2.13;
      }
      return sum / max;
    }
    function warpedFbm(x, y) {
      const qx = fbm(x + 5.2, y + 1.3, 4);
      const qy = fbm(x + 8.3, y + 2.8, 4);
      return fbm(x + 4.0 * qx, y + 4.0 * qy, 5);
    }
    function ridge(x, y, octaves = 4) {
      let amp = 1, freq = 1, sum = 0, max = 0;
      for (let i = 0; i < octaves; i++) {
        const n = 1.0 - Math.abs(smoothNoise(x * freq, y * freq) * 2.0 - 1.0);
        sum += amp * n * n;
        max += amp;
        amp *= 0.5;
        freq *= 2.0;
      }
      return sum / max;
    }

    const geometry = new THREE.PlaneGeometry(PLANE_SIZE, PLANE_SIZE, SEGMENTS, SEGMENTS);
    geometry.rotateX(-Math.PI / 2);
    const positions = geometry.attributes.position;
    for (let i = 0; i < positions.count; i++) {
      const x = positions.getX(i);
      const z = positions.getZ(i);
      const dist = Math.sqrt(x * x + z * z);
      const nDist = dist / MOUNTAIN_RADIUS;
      let hy;
      if (nDist > 1.0) {
        hy = -1.5;
      } else {
        hy = EVEREST_PEAK_HEIGHT * Math.pow(Math.max(0, 1 - nDist), 1.4);
        const edgeFalloff = Math.max(0, 1 - Math.pow(nDist, 3));
        const midBand = 1 - Math.abs(nDist - 0.35) * 2.8;
        const ridgeStrength = Math.max(0, midBand) * 2.2;
        const r = ridge(x * 0.4 + 7, z * 0.4 - 3, 4) * ridgeStrength;
        const mid = (warpedFbm(x * 0.45 + 2, z * 0.45 + 6) - 0.5) * 2.0 * edgeFalloff;
        const fine = (fbm(x * 2.8, z * 2.8, 4) - 0.5) * 0.45 * edgeFalloff;
        const rough = (fbm(x * 6.5, z * 6.5, 3) - 0.5) * 0.12 * edgeFalloff;
        hy = hy + r + mid + fine + rough;
        if (nDist > 0.82) {
          const t = (nDist - 0.82) / 0.18;
          hy = hy * (1 - t) + (-0.1) * t;
        }
        hy = Math.max(-0.1, hy);
      }
      positions.setY(i, hy);
    }
    geometry.computeVertexNormals();

    const mountainMat = new THREE.ShaderMaterial({
      uniforms: {
        uFillLevel:  { value: 0.0 },
        uFillColor:  { value: new THREE.Color(0xE63329) },
        uFillGlow:   { value: new THREE.Color(0xff4055) },
        uRockDark:   { value: new THREE.Color(0x15131a) },
        uRockMid:    { value: new THREE.Color(0x3e3740) },
        uRockLight:  { value: new THREE.Color(0x7a7078) },
        uSnow:       { value: new THREE.Color(0xf6f3ec) },
        uPeakHeight: { value: EVEREST_PEAK_HEIGHT },
        uTime:       { value: 0 },
        uFogColor:   { value: new THREE.Color(0x0a0a0c) },
        uFogNear:    { value: 24.0 },
        uFogFar:     { value: 60.0 },
      },
      vertexShader: `
        varying vec3 vPos;
        varying vec3 vNormal;
        varying vec3 vWorldPos;
        varying float vFogDepth;
        void main() {
          vPos = position;
          vNormal = normalize(normalMatrix * normal);
          vec4 wp = modelMatrix * vec4(position, 1.0);
          vWorldPos = wp.xyz;
          vec4 mvPos = viewMatrix * wp;
          vFogDepth = -mvPos.z;
          gl_Position = projectionMatrix * mvPos;
        }
      `,
      fragmentShader: `
        uniform float uFillLevel;
        uniform vec3 uFillColor;
        uniform vec3 uFillGlow;
        uniform vec3 uRockDark;
        uniform vec3 uRockMid;
        uniform vec3 uRockLight;
        uniform vec3 uSnow;
        uniform float uPeakHeight;
        uniform float uTime;
        uniform vec3 uFogColor;
        uniform float uFogNear;
        uniform float uFogFar;
        varying vec3 vPos;
        varying vec3 vNormal;
        varying vec3 vWorldPos;
        varying float vFogDepth;
        float h2(vec2 p) {
          return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
        }
        float vn(vec2 p) {
          vec2 i = floor(p); vec2 f = fract(p);
          float a = h2(i);
          float b = h2(i + vec2(1.0, 0.0));
          float c = h2(i + vec2(0.0, 1.0));
          float d = h2(i + vec2(1.0, 1.0));
          vec2 u = f * f * (3.0 - 2.0 * f);
          return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
        }
        void main() {
          vec3 N = normalize(vNormal);
          vec3 L1 = normalize(vec3(0.7, 0.85, 0.5));
          vec3 L2 = normalize(vec3(-0.5, 0.4, -0.4));
          float d1 = max(0.0, dot(N, L1));
          float d2 = max(0.0, dot(N, L2)) * 0.3;
          float skyLight = 0.28 + 0.22 * max(0.0, N.y);
          float lighting = skyLight + d1 * 0.6 + d2;
          float slope = clamp(1.0 - N.y, 0.0, 1.0);
          float elev = clamp(vPos.y / uPeakHeight, 0.0, 1.0);

          // Irregular, mountain-following fill boundary (ridges, not a flat waterline)
          float ridgeBreak = (vn(vWorldPos.xz * 0.55) - 0.5) * 1.1
                           + (vn(vWorldPos.xz * 1.4 + 3.7) - 0.5) * 0.45
                           + sin(vWorldPos.x * 1.8 + uTime * 0.4) * 0.08
                           + sin(vWorldPos.z * 2.1 - uTime * 0.35) * 0.08;
          float fillBoundary = uFillLevel + ridgeBreak;

          // Hide red on any vertex that sits at or below the mountain's natural base.
          // (mountain edge blends toward -0.1, skirt vertices sit at -1.5 — both stay rock/dark.)
          bool belowFill = (vPos.y <= fillBoundary) && (vPos.y > -0.05);

          if (belowFill) {
            vec3 c = uFillColor;
            float depth = clamp((fillBoundary - vPos.y) / max(uFillLevel, 0.01), 0.0, 1.0);
            c *= (1.0 - depth * 0.32);
            float edgeDist = fillBoundary - vPos.y;
            float glow = exp(-edgeDist * 6.0);
            c = mix(c, uFillGlow, glow * 0.55);
            float ring = exp(-edgeDist * 24.0);
            c += vec3(1.0, 0.88, 0.75) * ring * 0.45;
            c *= lighting;
            float fogFactor = smoothstep(uFogNear, uFogFar, vFogDepth);
            c = mix(c, uFogColor, fogFactor * 0.5);
            gl_FragColor = vec4(c, 1.0);
          } else {
            // Multi-scale rock texture — weathering, fractures, coarse grain
            float n1 = vn(vWorldPos.xz * 2.4);
            float n2 = vn(vWorldPos.xz * 6.8);
            float n3 = vn(vWorldPos.xz * 17.0);
            float rockNoise = n1 * 0.55 + n2 * 0.3 + n3 * 0.15;
            // Horizontal stratification bands (sedimentary rock layers)
            float strata = sin(vPos.y * 5.4 + n1 * 3.5) * 0.18 + 0.82;
            vec3 rock = mix(uRockDark, uRockMid, smoothstep(0.0, 0.55, (elev + rockNoise * 0.25) * strata));
            rock = mix(rock, uRockLight, smoothstep(0.62, 1.05, elev * strata + rockNoise * 0.12));
            rock += (rockNoise - 0.5) * 0.13;
            // Ambient occlusion deepens valleys
            float ao = smoothstep(-0.5, 2.5, vPos.y) * 0.35 + 0.65;
            rock *= ao;
            // Snow — falls off on steep faces, heavy on flatter slopes, patchy
            float snowAlt = smoothstep(uPeakHeight * 0.08, uPeakHeight * 0.22, vPos.y);
            float snowSlope = smoothstep(0.72, 0.08, slope);
            float snowPatch = clamp(0.72 + vn(vWorldPos.xz * 2.1) * 0.45, 0.0, 1.0);
            float snowAmt = snowAlt * snowSlope * snowPatch;
            // Guaranteed summit snow cap — covers summit pyramid except on cliffs
            float summitCap = smoothstep(uPeakHeight * 0.58, uPeakHeight * 0.88, vPos.y);
            snowAmt = max(snowAmt, summitCap * (1.0 - smoothstep(0.85, 1.05, slope)));
            // Snow in couloirs/gullies — noise-driven streaks that survive on steeper slopes
            float couloir = vn(vWorldPos.xz * vec2(4.0, 1.2) + vec2(1.7, 0.3));
            couloir = smoothstep(0.58, 0.85, couloir) * smoothstep(0.95, 0.35, slope);
            snowAmt = max(snowAmt, couloir * snowAlt);
            snowAmt = clamp(snowAmt, 0.0, 1.0);
            vec3 snowColor = uSnow + h2(floor(vWorldPos.xz * 14.0)) * 0.06 - 0.03;
            // Cool blue tint in shadowed snow (atmospheric scatter)
            float shadowed = 1.0 - max(0.0, dot(N, L1));
            snowColor = mix(snowColor, snowColor * vec3(0.88, 0.94, 1.12), shadowed * 0.4);
            vec3 color = mix(rock, snowColor, snowAmt);
            // Subtle warm tint on rock just above the fill (magma glow)
            float belowEdge = smoothstep(0.0, 0.5, fillBoundary - vPos.y + 0.5);
            color = mix(color, color * vec3(1.15, 0.9, 0.85), belowEdge * 0.22);
            color *= lighting;
            // Cool high-altitude rim light
            vec3 V = normalize(cameraPosition - vWorldPos);
            float rim = pow(1.0 - max(0.0, dot(N, V)), 3.5) * 0.20;
            color += vec3(0.70, 0.82, 1.0) * rim;
            float fogFactor = smoothstep(uFogNear, uFogFar, vFogDepth);
            color = mix(color, uFogColor, fogFactor);
            gl_FragColor = vec4(color, 1.0);
          }
        }
      `,
    });

    uniformsRef.current = mountainMat.uniforms;

    const mountain = new THREE.Mesh(geometry, mountainMat);
    mountain.position.y = 0.02;
    scene.add(mountain);

    // Dark ground plane — hides the skirt vertices (nDist > 1) so no "red ground"
    // appears around the base when fill rises.
    const groundGeo = new THREE.PlaneGeometry(120, 120);
    const groundMat = new THREE.MeshBasicMaterial({ color: 0x07070a, fog: true });
    const ground = new THREE.Mesh(groundGeo, groundMat);
    ground.rotation.x = -Math.PI / 2;
    ground.position.y = -0.12;
    scene.add(ground);

    // Camera orbit (auto-rotate, no drag — this is a big-screen dashboard)
    let theta = Math.PI * 0.28;
    const phi = Math.PI * 0.36;
    const radius = 30;
    const rotateSpeed = 0.0028;

    let rafId = 0;
    let destroyed = false;
    const tick = (time) => {
      if (destroyed) return;
      theta += rotateSpeed * spinBoostRef.current;
      camera.position.x = radius * Math.sin(phi) * Math.cos(theta);
      camera.position.z = radius * Math.sin(phi) * Math.sin(theta);
      camera.position.y = radius * Math.cos(phi);
      camera.lookAt(0, EVEREST_PEAK_HEIGHT * 0.3, 0);
      mountainMat.uniforms.uTime.value = time * 0.001;
      renderer.render(scene, camera);
      rafId = requestAnimationFrame(tick);
    };
    rafId = requestAnimationFrame(tick);

    const ro = new ResizeObserver(() => {
      const nw = Math.max(1, parent.clientWidth);
      const nh = Math.max(1, parent.clientHeight);
      if (nw === w && nh === h) return;
      w = nw; h = nh;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setPixelRatio(effectiveDpr());
      renderer.setSize(w, h, false);
    });
    ro.observe(parent);

    const onWinResize = () => {
      renderer.setPixelRatio(effectiveDpr());
      renderer.setSize(w, h, false);
    };
    window.addEventListener("resize", onWinResize);

    return () => {
      destroyed = true;
      cancelAnimationFrame(rafId);
      ro.disconnect();
      window.removeEventListener("resize", onWinResize);
      geometry.dispose();
      mountainMat.dispose();
      groundGeo.dispose();
      groundMat.dispose();
      renderer.dispose();
      uniformsRef.current = null;
    };
  }, []);

  React.useEffect(() => {
    if (!uniformsRef.current) return;
    const pct = Math.max(0, Math.min(1, filled / goalMeters));
    uniformsRef.current.uFillLevel.value = pct * EVEREST_PEAK_HEIGHT;
  }, [filled, goalMeters]);

  return <canvas ref={canvasRef} style={{
    position: "absolute", inset: 0, width: "100%", height: "100%", display: "block",
  }}/>;
}

function Confetti({ count = 110 }) {
  const pieces = React.useMemo(() => (
    Array.from({ length: count }, (_, i) => {
      const r = Math.random();
      const color = r < 0.7 ? jyStyles.red : r < 0.88 ? "#ff6a5b" : jyStyles.yellow;
      return {
        id: i,
        left: Math.random() * 100,
        delay: Math.random() * 0.9,
        duration: 1.8 + Math.random() * 1.8,
        w: 6 + Math.random() * 8,
        h: 10 + Math.random() * 8,
        color,
      };
    })
  ), [count]);
  return (
    <div style={{ position: "absolute", inset: 0, pointerEvents: "none", overflow: "hidden", zIndex: 10 }}>
      {pieces.map(p => (
        <div key={p.id} style={{
          position: "absolute", top: -20, left: `${p.left}%`,
          width: p.w, height: p.h, background: p.color,
          animation: `jy-confetti-fall ${p.duration}s cubic-bezier(0.22, 0.6, 0.4, 1) ${p.delay}s forwards`,
          boxShadow: `0 0 6px ${hexA(p.color, 0.5)}`,
        }}/>
      ))}
    </div>
  );
}

function EverestHero() {
  const m = MOUNTAIN;
  const [filled, setFilled] = React.useState(m.currentMeters);
  const [summit, setSummit] = React.useState(false);
  React.useEffect(() => {
    const id = setInterval(() => {
      setFilled(f => Math.min(m.goalMeters, f + Math.random() * 100));
    }, 350);
    return () => clearInterval(id);
  }, []);
  React.useEffect(() => {
    if (filled >= m.goalMeters && !summit) {
      setSummit(true);
      const t = setTimeout(() => {
        setSummit(false);
        setFilled(400);
      }, 5000);
      return () => clearTimeout(t);
    }
  }, [filled, summit, m.goalMeters]);
  const pct = filled / m.goalMeters;
  const hoursLeft = Math.max(0.1, (m.goalMeters - filled) / (m.metersPerMinute * 60));

  return (
    <div style={{
      position: "relative", height: "100%",
      background: `linear-gradient(180deg, #0a0a12 0%, #1a0a0a 60%, ${jyStyles.black} 100%)`,
      border: `2px solid ${jyStyles.red}`,
      overflow: "hidden",
      display: "flex", flexDirection: "column",
    }}>
      {/* Header slab */}
      <div style={{ padding: "20px 28px", position: "relative", zIndex: 3 }}>
        <div style={{ ...jyFonts.label, fontSize: 13, color: jyStyles.yellow }}>
          ⚡ DAGENS GEMENSAMMA UTMANING
        </div>
        <div style={{
          ...jyFonts.display, fontSize: 72, lineHeight: 0.88, color: "#fff",
          textShadow: `4px 4px 0 ${jyStyles.red}`,
        }}>
          Mount Everest
        </div>
        <div style={{ ...jyFonts.body, fontSize: 14, color: jyStyles.textDim, marginTop: 6, lineHeight: 1.4, maxWidth: 440 }}>
          Hela hallen hoppar tillsammans. Varje centimeter ni flyger räknas — nå toppen innan stängning.
        </div>
      </div>

      {/* 3D hero + overlays */}
      <div style={{ flex: 1, position: "relative", minHeight: 0 }}>
        <style>{`@keyframes jy-confetti-fall {
          0% { transform: translateY(-30px) rotate(0deg); opacity: 1; }
          100% { transform: translateY(920px) rotate(720deg); opacity: 0.85; }
        }`}</style>
        <EverestMountain3D filled={filled} goalMeters={m.goalMeters} spinBoost={summit}/>
        {summit && <Confetti/>}
        {summit && (
          <div style={{
            position: "absolute", top: "38%", left: 0, right: 0, zIndex: 11,
            textAlign: "center", pointerEvents: "none",
            ...jyFonts.display, fontSize: 96, color: "#fff",
            textShadow: `0 0 24px ${jyStyles.red}, 6px 6px 0 ${jyStyles.red}`,
            animation: "jy-celeb 1.2s ease-out",
          }}>TOPPEN NÅDD!</div>
        )}

        {/* Milestones overlay (right edge, vertically distributed by altitude) */}
        <div style={{
          position: "absolute", top: 14, right: 14, bottom: 14, width: 140,
          pointerEvents: "none", zIndex: 3,
        }}>
          {m.milestones.map((mi, i) => {
            const reached = filled >= mi.m;
            const top = `${(1 - mi.m / m.goalMeters) * 100}%`;
            return (
              <div key={i} style={{
                position: "absolute", right: 0, top,
                transform: "translateY(-50%)",
                display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 8,
                opacity: reached ? 1 : 0.55,
                transition: "opacity 0.3s",
              }}>
                <div style={{ textAlign: "right" }}>
                  <div style={{
                    ...jyFonts.label, fontSize: 10,
                    color: reached ? jyStyles.yellow : "#fff",
                    textShadow: "0 1px 3px rgba(0,0,0,0.8)",
                  }}>
                    {mi.icon} {mi.name}
                  </div>
                  <div style={{
                    ...jyFonts.num, fontSize: 12, letterSpacing: 0,
                    color: reached ? jyStyles.yellow : jyStyles.textDim,
                    textShadow: "0 1px 3px rgba(0,0,0,0.8)",
                  }}>
                    {mi.m}m
                  </div>
                </div>
                <div style={{
                  width: 14, height: 1,
                  background: reached ? jyStyles.yellow : "rgba(255,255,255,0.5)",
                }}/>
              </div>
            );
          })}
        </div>

        {/* Current altitude callout */}
        <div style={{
          position: "absolute", left: 20, bottom: 20, zIndex: 3,
          background: jyStyles.red, padding: "10px 28px 10px 16px",
          clipPath: "polygon(0 0, 100% 0, 94% 100%, 0 100%)",
        }}>
          <div style={{ ...jyFonts.label, fontSize: 11, color: "rgba(255,255,255,0.75)" }}>Just nu</div>
          <div style={{ ...jyFonts.num, fontSize: 40, color: "#fff", lineHeight: 0.9, letterSpacing: -1 }}>
            {Math.round(filled).toLocaleString("sv")}<span style={{ fontSize: 20 }}>m</span>
          </div>
        </div>
      </div>

      {/* Bottom strip — progress + ETA + reward */}
      <div style={{
        background: jyStyles.black, borderTop: `1px solid ${hexA("#fff", 0.08)}`,
        padding: "16px 24px",
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
          <div style={{ ...jyFonts.label, fontSize: 12, color: jyStyles.textDim }}>
            {Math.round(pct*100)}% TILL TOPPEN · {m.contributorsToday} HOPPARE BIDRAR
          </div>
          <div style={{ ...jyFonts.label, fontSize: 12, color: jyStyles.yellow }}>
            ↑ {m.metersPerMinute} m/min · ~{hoursLeft.toFixed(1)}h KVAR
          </div>
        </div>
        <div style={{ height: 12, background: hexA("#fff", 0.08), position: "relative", overflow: "hidden" }}>
          <div style={{
            position: "absolute", inset: 0, width: `${pct*100}%`,
            background: `linear-gradient(90deg, ${jyStyles.yellow} 0%, ${jyStyles.red} 100%)`,
            transition: "width 1s linear",
          }}/>
        </div>
        <div style={{
          marginTop: 10, background: jyStyles.charcoal, padding: "8px 12px",
          ...jyFonts.label, fontSize: 12, color: jyStyles.yellow,
          borderLeft: `3px solid ${jyStyles.yellow}`,
        }}>
          🏅 BELÖNING · {m.reward.toUpperCase()}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Spotlight — compact, top right
// ─────────────────────────────────────────────
function JYSpotlight() {
  const { spotlight: s, celebrate } = useJY();
  const gain = s.best - s.previousBest;
  const [k, setK] = React.useState(0);
  React.useEffect(() => { if (celebrate) setK(x => x+1); }, [celebrate]);
  return (
    <div key={k} style={{
      position: "relative",
      background: jyStyles.charcoal, border: `2px solid ${hexA("#fff", 0.1)}`,
      padding: "18px 20px", overflow: "hidden",
      animation: celebrate ? "jy-celeb 1.2s ease-out" : "none",
    }}>
      {/* corner wedge */}
      <div style={{
        position: "absolute", top: 0, right: 0, width: 100, height: 100,
        background: jyStyles.red, clipPath: "polygon(100% 0, 100% 100%, 0 0)",
        opacity: 0.9,
      }}/>
      <div style={{ ...jyFonts.label, fontSize: 11, color: "#fff", position: "absolute", top: 10, right: 12, zIndex: 2 }}>
        PR
      </div>

      <div style={{ ...jyFonts.label, fontSize: 12, color: jyStyles.red }}>★ SPOTLIGHT</div>
      <div style={{ display: "flex", alignItems: "center", gap: 14, marginTop: 8 }}>
        <div style={{
          width: 60, height: 60, background: s.color,
          border: `2px solid #fff`, display: "grid", placeItems: "center", fontSize: 32, flexShrink: 0,
        }}>{s.avatar}</div>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ ...jyFonts.display, fontSize: 28, color: "#fff", lineHeight: 0.95, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
            {s.name}
          </div>
          <div style={{ ...jyFonts.label, fontSize: 11, color: jyStyles.textDim, marginTop: 2 }}>
            LVL {s.level} · {s.age || 10} ÅR
          </div>
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "flex-end", gap: 8, marginTop: 10 }}>
        <div style={{
          ...jyFonts.num, fontSize: 86, color: jyStyles.yellow, lineHeight: 0.85,
          textShadow: `4px 4px 0 ${jyStyles.red}`,
        }}>{s.best}</div>
        <div style={{ paddingBottom: 8 }}>
          <div style={{ ...jyFonts.display, fontSize: 24, color: "#fff" }}>cm</div>
          <div style={{
            background: jyStyles.green, color: jyStyles.black,
            padding: "2px 8px", ...jyFonts.label, fontSize: 11, marginTop: 4,
          }}>▲ +{gain} CM</div>
        </div>
      </div>
      <style>{`@keyframes jy-celeb { 0% { box-shadow: 0 0 0 0 ${hexA(jyStyles.red, 0.6)}; } 50% { box-shadow: 0 0 60px 8px ${hexA(jyStyles.red, 0.4)}; } 100% { box-shadow: none; } }`}</style>
    </div>
  );
}

// ─────────────────────────────────────────────
// Leaderboard — top 10 + your-position-ish footer
// ─────────────────────────────────────────────
function JYLeaderboard() {
  const { leaderboard } = useJY();
  return (
    <div style={{
      background: jyStyles.charcoal, border: `2px solid ${hexA("#fff", 0.1)}`,
      display: "flex", flexDirection: "column", height: "100%",
    }}>
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "stretch",
        borderBottom: `2px solid ${jyStyles.red}`,
      }}>
        <div style={{ padding: "14px 22px" }}>
          <div style={{ ...jyFonts.label, fontSize: 14, color: jyStyles.red }}>TOPP 10 · HÖJD</div>
          <div style={{ ...jyFonts.display, fontSize: 40, color: "#fff", lineHeight: 0.9 }}>Höjdligan</div>
        </div>
        <div style={{ display: "grid", placeItems: "center", padding: "0 20px" }}>
          <div style={{
            background: jyStyles.red, color: "#fff", padding: "6px 14px",
            ...jyFonts.label, fontSize: 14,
            clipPath: "polygon(0 0, 100% 0, 90% 100%, 0 100%)",
            paddingRight: 22,
          }}>● LIVE</div>
        </div>
      </div>

      <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
        {leaderboard.map((p, i) => {
          const isMedal = i < 3;
          const medalColors = [jyStyles.yellow, "#D6D6D6", "#CD7F32"];
          return (
            <div key={p.name} style={{
              display: "grid",
              gridTemplateColumns: "60px 52px 1fr 130px 40px",
              alignItems: "center", gap: 14,
              padding: "0 22px", flex: 1,
              background: i === 0 ? hexA(jyStyles.red, 0.18) :
                          i % 2 === 0 ? hexA("#fff", 0.02) : "transparent",
              borderLeft: isMedal ? `4px solid ${medalColors[i]}` : "4px solid transparent",
              borderTop: `1px solid ${hexA("#fff", 0.04)}`,
            }}>
              <div style={{
                ...jyFonts.num, fontSize: isMedal ? 44 : 34,
                color: isMedal ? medalColors[i] : jyStyles.textMute, letterSpacing: -1,
              }}>
                {String(p.rank).padStart(2,"0")}
              </div>
              <div style={{
                width: 44, height: 44, background: p.color,
                border: "2px solid #fff", display: "grid", placeItems: "center", fontSize: 24,
              }}>{p.avatar}</div>
              <div style={{ minWidth: 0 }}>
                <div style={{ ...jyFonts.display, fontSize: 26, color: "#fff", lineHeight: 0.95, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                  {p.name}
                </div>
                <div style={{ ...jyFonts.label, fontSize: 13, color: jyStyles.textDim, marginTop: 2 }}>
                  LVL {p.level} · {p.jumps} HOPP
                </div>
              </div>
              <div style={{
                ...jyFonts.num, fontSize: 42, textAlign: "right",
                color: i === 0 ? jyStyles.yellow : "#fff", letterSpacing: -1,
              }}>
                {p.best}<span style={{ ...jyFonts.label, fontSize: 15, color: jyStyles.textMute, marginLeft: 3, fontWeight: 700 }}>CM</span>
              </div>
              <div style={{ fontSize: 22, textAlign: "center" }}>
                {p.trend === "up" ? <span style={{ color: jyStyles.green }}>▲</span> :
                 p.trend === "down" ? <span style={{ color: jyStyles.red }}>▼</span> :
                 <span style={{ color: jyStyles.textMute }}>—</span>}
              </div>
            </div>
          );
        })}
      </div>

      {/* Footer: "du är placering X av Y" */}
      <div style={{
        background: jyStyles.black, padding: "12px 20px",
        borderTop: `1px solid ${hexA("#fff", 0.08)}`,
        display: "flex", justifyContent: "space-between", alignItems: "center",
      }}>
        <div style={{ ...jyFonts.label, fontSize: 14, color: jyStyles.textDim }}>
          + {YARD_STATS.jumpersToday - 10} TILL HOPPARE IDAG
        </div>
        <div style={{ ...jyFonts.label, fontSize: 14, color: jyStyles.yellow }}>
          SE DIN PLACERING PÅ APPEN →
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Groups — team leaderboard
// ─────────────────────────────────────────────
function JYGroups() {
  return (
    <div style={{
      background: jyStyles.charcoal, border: `2px solid ${hexA("#fff", 0.1)}`,
    }}>
      <div style={{
        borderBottom: `2px solid ${jyStyles.red}`, padding: "12px 18px",
        display: "flex", justifyContent: "space-between", alignItems: "center",
      }}>
        <div>
          <div style={{ ...jyFonts.label, fontSize: 11, color: jyStyles.red }}>GRUPPER</div>
          <div style={{ ...jyFonts.display, fontSize: 26, color: "#fff", lineHeight: 0.9 }}>Kompisgäng</div>
        </div>
        <div style={{ ...jyFonts.label, fontSize: 11, color: jyStyles.textDim }}>
          {GROUPS.length} AKTIVA
        </div>
      </div>
      <div style={{ display: "flex", flexDirection: "column" }}>
        {GROUPS.map((g, i) => {
          const max = Math.max(...GROUPS.map(x => x.totalMeters));
          const pct = g.totalMeters / max;
          return (
            <div key={g.id} style={{
              display: "grid",
              gridTemplateColumns: "28px 1fr 72px",
              gap: 12, alignItems: "center",
              padding: "10px 18px",
              borderTop: i === 0 ? "none" : `1px solid ${hexA("#fff", 0.04)}`,
              position: "relative",
            }}>
              <div style={{
                ...jyFonts.num, fontSize: 24, color: g.leader ? jyStyles.yellow : jyStyles.textMute,
                letterSpacing: -1,
              }}>{i+1}</div>
              <div style={{ minWidth: 0 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <div style={{ width: 14, height: 14, background: g.color, border: "1.5px solid #fff" }}/>
                  <div style={{ ...jyFonts.display, fontSize: 20, color: "#fff", lineHeight: 1 }}>
                    {g.name}
                  </div>
                  <div style={{ ...jyFonts.label, fontSize: 10, color: jyStyles.textDim }}>
                    {g.members}👥 · {g.bestBumper} {g.bestHeight}cm
                  </div>
                </div>
                <div style={{ height: 4, background: hexA("#fff", 0.08), marginTop: 6, overflow: "hidden" }}>
                  <div style={{ width: `${pct*100}%`, height: "100%", background: g.color }}/>
                </div>
              </div>
              <div style={{ ...jyFonts.num, fontSize: 22, color: "#fff", textAlign: "right", letterSpacing: -1 }}>
                {g.totalMeters}<span style={{ ...jyFonts.label, fontSize: 10, color: jyStyles.textMute, marginLeft: 2 }}>M</span>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Crowd height distribution — shows all 150+ jumpers at a glance
// ─────────────────────────────────────────────
function JYCrowdDist() {
  const total = HEIGHT_DIST.reduce((s, x) => s + x.count, 0);
  const max = Math.max(...HEIGHT_DIST.map(x => x.count));
  return (
    <div style={{
      background: jyStyles.charcoal, border: `2px solid ${hexA("#fff", 0.1)}`,
      padding: "14px 18px",
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 10 }}>
        <div>
          <div style={{ ...jyFonts.label, fontSize: 11, color: jyStyles.red }}>HOPPARE JUST NU</div>
          <div style={{ ...jyFonts.display, fontSize: 22, color: "#fff", lineHeight: 0.9 }}>Höjdfördelning</div>
        </div>
        <div style={{ ...jyFonts.num, fontSize: 36, color: jyStyles.yellow, letterSpacing: -1 }}>
          {total}<span style={{ ...jyFonts.label, fontSize: 12, color: jyStyles.textDim, marginLeft: 4 }}>STYCKEN</span>
        </div>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 6, alignItems: "end", height: 90 }}>
        {HEIGHT_DIST.map((b, i) => {
          const h = (b.count / max) * 100;
          return (
            <div key={i} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
              <div style={{ ...jyFonts.num, fontSize: 18, color: "#fff", textAlign: "center", letterSpacing: -0.5 }}>
                {b.count}
              </div>
              <div style={{
                height: `${h}%`, minHeight: 8,
                background: b.color, position: "relative",
              }}>
                {/* dot cluster inside bar (one dot per ~5 jumpers, sampled) */}
                {Array.from({length: Math.min(12, Math.round(b.count / 5))}).map((_, j) => (
                  <div key={j} style={{
                    position: "absolute", width: 4, height: 4, borderRadius: "50%",
                    background: "rgba(0,0,0,0.35)",
                    left: `${((j*13) % 70) + 10}%`, top: `${((j*17) % 70) + 15}%`,
                  }}/>
                ))}
              </div>
              <div style={{ ...jyFonts.label, fontSize: 10, color: jyStyles.textDim, textAlign: "center" }}>
                {b.band}cm
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Ticker
// ─────────────────────────────────────────────
function JYTicker() {
  const { recent } = useJY();
  const recentRef = React.useRef(recent);
  recentRef.current = recent;
  const [snapshot, setSnapshot] = React.useState(recent);
  const loop = [...snapshot, ...snapshot];
  return (
    <div style={{
      background: jyStyles.black, color: "#fff",
      position: "relative", overflow: "hidden",
      borderTop: `2px solid ${jyStyles.red}`,
      display: "flex", alignItems: "center", height: "100%",
    }}>
      <style>{`@keyframes jy-ticker { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } }`}</style>
      <div style={{
        background: jyStyles.red, color: "#fff", padding: "0 28px 0 22px", height: "100%",
        display: "grid", placeItems: "center",
        ...jyFonts.display, fontSize: 22, flexShrink: 0,
        clipPath: "polygon(0 0, 100% 0, calc(100% - 16px) 100%, 0 100%)",
        zIndex: 2,
      }}>📡 LIVE</div>
      <div style={{
        flex: 1, overflow: "hidden", position: "relative",
        paddingLeft: 28, paddingRight: 28,
      }}>
        <div
          onAnimationIteration={() => setSnapshot(recentRef.current)}
          style={{
            display: "flex", gap: 36, whiteSpace: "nowrap",
            width: "max-content", alignItems: "center",
            animation: "jy-ticker 60s linear infinite",
          }}>
          {loop.map((j, i) => (
            <div key={i} style={{ display: "flex", alignItems: "center", gap: 12, flexShrink: 0 }}>
              <span style={{ fontSize: 22 }}>{j.avatar}</span>
              <span style={{ ...jyFonts.display, fontSize: 20, color: "#fff" }}>{j.name}</span>
              <span style={{ ...jyFonts.num, fontSize: 24, color: jyStyles.yellow, letterSpacing: -0.5 }}>
                {j.height}<span style={{ ...jyFonts.label, fontSize: 12, marginLeft: 2 }}>CM</span>
              </span>
              {j.isPR && (
                <span style={{
                  background: jyStyles.red, color: "#fff",
                  padding: "2px 8px", ...jyFonts.label, fontSize: 11,
                }}>PR!</span>
              )}
              <span style={{ color: jyStyles.textMute }}>●</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// House record mini
// ─────────────────────────────────────────────
function JYHouseMini() {
  return (
    <div style={{
      background: jyStyles.yellow, color: jyStyles.black,
      padding: "12px 16px", display: "flex", alignItems: "center", gap: 14,
    }}>
      <div style={{ ...jyFonts.display, fontSize: 14, color: jyStyles.black }}>👑 HUSETS</div>
      <div style={{ ...jyFonts.num, fontSize: 42, color: jyStyles.black, letterSpacing: -1, lineHeight: 0.9 }}>
        {HOUSE_RECORD.height}<span style={{ ...jyFonts.display, fontSize: 16, marginLeft: 2 }}>cm</span>
      </div>
      <div style={{ flex: 1, textAlign: "right" }}>
        <div style={{ ...jyFonts.display, fontSize: 18, lineHeight: 1 }}>{HOUSE_RECORD.holder}</div>
        <div style={{ ...jyFonts.label, fontSize: 10, opacity: 0.7 }}>{HOUSE_RECORD.when}</div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Root
// ─────────────────────────────────────────────
function JumpYardDashboard() {
  const { tweaks } = useJY();
  return (
    <>
      <link rel="preconnect" href="https://fonts.googleapis.com"/>
      <link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,700;0,900;1,700;1,900&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
      <div style={{
        width: 1920, height: 1080,
        background: jyStyles.black,
        position: "relative", overflow: "hidden",
        display: "grid",
        gridTemplateRows: tweaks.ticker ? "148px 1fr 64px" : "148px 1fr",
      }}>
        <JYHeader/>

        {/* Main grid: Everest hero | Leaderboard | Right column */}
        <div style={{
          display: "grid",
          gridTemplateColumns: "680px 1fr 400px",
          gap: 14, padding: 14,
          position: "relative", zIndex: 2,
          minHeight: 0,
        }}>
          {/* LEFT — Mount Everest hero */}
          <div style={{ minHeight: 0 }}>
            <EverestHero/>
          </div>

          {/* CENTER — Leaderboard */}
          <div style={{ minHeight: 0 }}>
            <JYLeaderboard/>
          </div>

          {/* RIGHT — Spotlight + Groups + CrowdDist + House */}
          <div style={{ display: "grid", gridTemplateRows: "auto auto auto auto", gap: 12, minHeight: 0 }}>
            <JYSpotlight/>
            <JYGroups/>
            <JYCrowdDist/>
            <JYHouseMini/>
          </div>
        </div>

        {tweaks.ticker && <JYTicker/>}
      </div>
    </>
  );
}

Object.assign(window, { JumpYardDashboard });
