/* AI Edge v2 — GlyphHero: particles assemble into the hex logo ("知识星图").
   Requires THREE + React. Exposes window.GlyphHero. */
(function(){
const { useEffect, useRef } = React;
const motionOff = ()=> document.documentElement.dataset.motion==="off";

/* Pause WebGL rendering after 60s without user input. WebGL frames drawn
   right up to an idle-tab reload leave the compositor in a state Chrome
   blends to white at commit (the intermittent white flash on refresh);
   a stopped loop is proven safe — and saves battery. Any input resumes. */
const IDLE_MS = 60000;
let lastActive = performance.now();
const idleResume = new Set();
["pointermove","pointerdown","wheel","keydown","touchstart","scroll"].forEach(ev=>
  addEventListener(ev, ()=>{ lastActive = performance.now(); idleResume.forEach(fn=>fn()); }, { passive:true })
);
const userIdle = ()=> performance.now() - lastActive > IDLE_MS;

function accentRGB(){
  const s=(getComputedStyle(document.documentElement).getPropertyValue("--accent")||"").trim();
  const ok=s.match(/oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)/i);
  if(ok){
    const L=+ok[1],C=+ok[2],H=+ok[3];
    const hr=H*Math.PI/180,a=C*Math.cos(hr),b=C*Math.sin(hr);
    const l_=L+0.3963377774*a+0.2158037573*b,m_=L-0.1055613458*a-0.0638541728*b,s_=L-0.0894841775*a-1.2914855480*b;
    const l=l_**3,m=m_**3,ss=s_**3;
    const r=4.0767416621*l-3.3077115913*m+0.2309699292*ss;
    const g=-1.2684380046*l+2.6097574011*m-0.3413193965*ss;
    const bl=-0.0041960863*l-0.7034186147*m+1.7076147010*ss;
    const cl=(x)=>Math.max(0,Math.min(1,x));
    return { r:cl(r), g:cl(g), b:cl(bl) };
  }
  const m=s.match(/[\d.]+/g);
  if(m&&m.length>=3) return { r:+m[0]/255, g:+m[1]/255, b:+m[2]/255 };
  return { r:0.31, g:0.89, b:0.60 };
}

/* Rasterize the hex logo, return sample points in px (centered coords). */
function sampleGlyph(){
  const S=520, c=document.createElement("canvas"); c.width=c.height=S;
  const g=c.getContext("2d",{willReadFrequently:true});
  g.translate(S/2,S/2); g.strokeStyle="#fff"; g.lineJoin="round"; g.lineCap="round";
  const R=205, hex=[];
  for(let i=0;i<6;i++){ const a=-Math.PI/2+i*Math.PI/3; hex.push([Math.cos(a)*R,Math.sin(a)*R]); }
  g.lineWidth=30; g.beginPath();
  hex.forEach((p,i)=> i?g.lineTo(p[0],p[1]):g.moveTo(p[0],p[1]));
  g.closePath(); g.stroke();
  g.lineWidth=22;
  g.beginPath(); g.moveTo(0,-R); g.lineTo(0,R); g.stroke();
  g.beginPath(); g.moveTo(hex[5][0],hex[5][1]); g.lineTo(0,0); g.lineTo(hex[1][0],hex[1][1]); g.stroke();
  const data=g.getImageData(0,0,S,S).data, pts=[];
  for(let y=0;y<S;y+=3) for(let x=0;x<S;x+=3){
    if(data[(y*S+x)*4+3]>110) pts.push([x-S/2+(Math.random()*3-1.5), S/2-y+(Math.random()*3-1.5)]);
  }
  return { pts, R };
}

function GlyphHero({ booted }){
  const cv=useRef(null);
  const bootedRef=useRef(booted);
  useEffect(()=>{ bootedRef.current=booted; },[booted]);

  useEffect(()=>{
    const THREE=window.THREE, canvas=cv.current;
    if(!THREE||!canvas) return;
    const host=canvas.parentElement;
    let w=host.clientWidth, h=host.clientHeight;
    const renderer=new THREE.WebGLRenderer({ canvas, alpha:true, antialias:false });
    renderer.setPixelRatio(Math.min(1.75, devicePixelRatio||1));
    renderer.setSize(w,h);
    const scene=new THREE.Scene();
    const camera=new THREE.PerspectiveCamera(58, w/h, 0.1, 100);
    camera.position.z=26;

    const isMobile=w<700;
    const { pts, R }=sampleGlyph();
    const scale=5.2/R;
    const NG=Math.min(isMobile?2200:4200, pts.length);
    const AMB=Math.round(NG*0.35);
    const N=NG+AMB;
    // shuffle-pick glyph samples
    for(let i=pts.length-1;i>0;i--){ const j=(Math.random()*(i+1))|0; const t=pts[i]; pts[i]=pts[j]; pts[j]=t; }

    const scatter=new Float32Array(N*3), target=new Float32Array(N*3);
    const scl=new Float32Array(N), mix=new Float32Array(N), amb=new Float32Array(N), ang=new Float32Array(N);
    for(let i=0;i<N;i++){
      // scatter: wide gaussian-ish cloud
      const r=7+Math.random()*11, a=Math.random()*Math.PI*2, b=Math.acos(2*Math.random()-1);
      scatter[i*3]=Math.sin(b)*Math.cos(a)*r; scatter[i*3+1]=Math.sin(b)*Math.sin(a)*r*0.75; scatter[i*3+2]=Math.cos(b)*r*0.6;
      if(i<NG){
        const p=pts[i];
        target[i*3]=p[0]*scale; target[i*3+1]=p[1]*scale; target[i*3+2]=(Math.random()-0.5)*0.6;
      }else{
        const rr=6.4+Math.random()*4.5, aa=Math.random()*Math.PI*2;
        target[i*3]=Math.cos(aa)*rr; target[i*3+1]=Math.sin(aa)*rr*0.8; target[i*3+2]=(Math.random()-0.5)*3.5;
      }
      scl[i]=0.5+Math.random()*Math.random()*1.7;
      mix[i]=Math.pow(Math.random(),3);
      amb[i]=i<NG?0:1;
      ang[i]=Math.atan2(target[i*3+1],target[i*3]);
    }
    const geo=new THREE.BufferGeometry();
    geo.setAttribute("position", new THREE.BufferAttribute(scatter,3));
    geo.setAttribute("aTarget", new THREE.BufferAttribute(target,3));
    geo.setAttribute("aScale", new THREE.BufferAttribute(scl,1));
    geo.setAttribute("aMix", new THREE.BufferAttribute(mix,1));
    geo.setAttribute("aAmb", new THREE.BufferAttribute(amb,1));
    geo.setAttribute("aAngle", new THREE.BufferAttribute(ang,1));

    const ac=accentRGB();
    const uniforms={
      uTime:{value:0}, uProgress:{value:0}, uOpacity:{value:0},
      uSize:{value:isMobile?66:88}, uMouse:{value:new THREE.Vector3(99,99,0)}, uMouseF:{value:0},
      uColor:{value:new THREE.Color(ac.r,ac.g,ac.b)},
      uColor2:{value:new THREE.Color(ac.r,ac.g,ac.b).lerp(new THREE.Color(1,1,1),0.45)},
    };
    const mat=new THREE.ShaderMaterial({
      uniforms, transparent:true, depthWrite:false, blending:THREE.AdditiveBlending,
      vertexShader:`
        attribute vec3 aTarget; attribute float aScale, aMix, aAmb, aAngle;
        uniform float uTime,uProgress,uSize,uMouseF; uniform vec3 uMouse;
        varying float vMix,vTw,vGlow;
        void main(){
          float prog=uProgress*mix(1.0,0.55,aAmb);
          // per-particle stagger so assembly ripples instead of snapping
          float pp=clamp(prog*1.35-aScale*0.22,0.0,1.0);
          pp=pp*pp*(3.0-2.0*pp);
          vec3 p=mix(position,aTarget,pp);
          float amp=mix(1.6,mix(0.10,0.6,aAmb),pp);
          float t=uTime*0.6;
          p.x+=sin(p.y*0.9+t+aScale*17.0)*0.35*amp;
          p.y+=cos(p.x*0.8+t*0.83+aScale*23.0)*0.35*amp;
          p.z+=sin((p.x+p.y)*0.7+t*0.9)*0.3*amp;
          vec2 dm=p.xy-uMouse.xy; float md=length(dm);
          p.xy+=normalize(dm+vec2(0.0001))*smoothstep(2.6,0.0,md)*1.4*uMouseF;
          float sweep=fract(aAngle/6.28318-uTime*0.05);
          float pulse=smoothstep(0.09,0.0,sweep)*pp*(1.0-aAmb);
          vGlow=pulse; vMix=aMix;
          float tw=0.6+0.4*sin(uTime*1.6+aScale*31.0+aAngle*3.0);
          vTw=tw;
          vec4 mv=modelViewMatrix*vec4(p,1.0);
          gl_PointSize=uSize*aScale*(0.5+0.55*tw+pulse*0.5)*(1.0/-mv.z);
          gl_Position=projectionMatrix*mv;
        }`,
      fragmentShader:`
        varying float vMix,vTw,vGlow;
        uniform vec3 uColor,uColor2; uniform float uOpacity;
        void main(){
          float d=length(gl_PointCoord-vec2(0.5));
          if(d>0.5) discard;
          float a=smoothstep(0.5,0.04,d);
          vec3 c=mix(uColor,uColor2,vMix);
          c=mix(c,vec3(1.0),vGlow*0.35);
          gl_FragColor=vec4(c, a*uOpacity*(0.3+0.6*vTw+vGlow*0.45));
        }`,
    });
    const points=new THREE.Points(geo,mat);

    // constellation edges between nearby glyph points
    const lp=[], LNTRY=isMobile?900:2200, MAXL=isMobile?220:520;
    for(let i=0;i<LNTRY && lp.length<MAXL*6;i++){
      const a=(Math.random()*NG)|0; let best=-1,bd=1e9;
      for(let k=0;k<7;k++){ const b=(Math.random()*NG)|0; if(b===a)continue;
        const dx=target[a*3]-target[b*3],dy=target[a*3+1]-target[b*3+1],dz=target[a*3+2]-target[b*3+2];
        const dd=dx*dx+dy*dy+dz*dz; if(dd<bd){bd=dd;best=b;} }
      if(best>=0&&bd>0.09&&bd<1.7) lp.push(target[a*3],target[a*3+1],target[a*3+2],target[best*3],target[best*3+1],target[best*3+2]);
    }
    const lgeo=new THREE.BufferGeometry();
    lgeo.setAttribute("position", new THREE.BufferAttribute(new Float32Array(lp),3));
    const lmat=new THREE.LineBasicMaterial({ color:new THREE.Color(ac.r,ac.g,ac.b), transparent:true, opacity:0, blending:THREE.AdditiveBlending, depthWrite:false });
    const lines=new THREE.LineSegments(lgeo,lmat);

    const group=new THREE.Group(); group.add(points); group.add(lines); scene.add(group);
    const groupX=isMobile?0:7.2;
    group.position.x=groupX;

    const fine=matchMedia("(pointer:fine)").matches;
    let mx=0,my=0, prog=0, raf, t0=performance.now(), fr=0, vis=true;
    const onMove=(e)=>{ mx=e.clientX/innerWidth*2-1; my=-(e.clientY/innerHeight*2-1); };
    if(fine) addEventListener("pointermove",onMove,{passive:true});
    const io=new IntersectionObserver(([en])=>{ vis=en.isIntersecting; });
    io.observe(host);
    const resize=()=>{ w=host.clientWidth; h=host.clientHeight; camera.aspect=w/h; camera.updateProjectionMatrix(); renderer.setSize(w,h);
      group.position.x = w<700?0:7.2; };
    const ro=new ResizeObserver(resize); ro.observe(host);

    function loop(now){
      if(userIdle()){ raf=0; return; }
      raf=requestAnimationFrame(loop);
      if(!vis||document.hidden){ t0=now; return; }
      const dt=Math.min(0.05,(now-t0)/1000); t0=now; fr++;
      const mo=motionOff();
      if(!mo) uniforms.uTime.value+=dt;
      // progress: assemble after boot, dissolve on scroll
      const sc=Math.max(0,Math.min(1, 1.18-scrollY/(h*0.72)));
      const tgt=(bootedRef.current?1:0)*sc;
      prog+=( (mo?tgt:tgt)-prog )*(mo?1:0.028);
      uniforms.uProgress.value=prog;
      uniforms.uOpacity.value+=((bootedRef.current?1:0)-uniforms.uOpacity.value)*0.05;
      lmat.opacity+=(prog*prog*prog*(isMobile?0.10:0.17)-lmat.opacity)*0.06;
      camera.position.z+=((bootedRef.current?18:26)-camera.position.z)*0.035;
      // parallax sway (glyph is planar — oscillate, don't spin)
      const sway=mo?0:1;
      group.rotation.y=(Math.sin(uniforms.uTime.value*0.12)*0.14+mx*0.22)*sway;
      group.rotation.x=(Math.cos(uniforms.uTime.value*0.09)*0.08-my*0.16)*sway;
      camera.lookAt(0,0,0);
      // mouse → world (approx, camera on axis), local to group
      const fov=camera.fov*Math.PI/180, hh=Math.tan(fov/2)*camera.position.z;
      uniforms.uMouse.value.set(mx*hh*camera.aspect-groupX*(w<700?0:1), my*hh, 0);
      uniforms.uMouseF.value+=( (fine&&!mo?prog:0)-uniforms.uMouseF.value )*0.08;
      if(fr%45===0){ const a=accentRGB();
        uniforms.uColor.value.setRGB(a.r,a.g,a.b);
        uniforms.uColor2.value.setRGB(a.r,a.g,a.b).lerp(new THREE.Color(1,1,1),0.45);
        lmat.color.setRGB(a.r,a.g,a.b); }
      renderer.render(scene,camera);
    }
    const resume=()=>{ if(!raf){ t0=performance.now(); raf=requestAnimationFrame(loop); } };
    idleResume.add(resume);
    raf=requestAnimationFrame(loop);
    return ()=>{ idleResume.delete(resume); cancelAnimationFrame(raf); if(fine) removeEventListener("pointermove",onMove);
      io.disconnect(); ro.disconnect(); geo.dispose(); mat.dispose(); lgeo.dispose(); lmat.dispose(); renderer.dispose(); };
  },[]);
  return <canvas ref={cv} className="phero-canvas"></canvas>;
}

window.GlyphHero=GlyphHero;

/* ---------------- SilkHero: domain-warped silk shader (丝绸流光) ---------------- */
function SilkHero({ booted }){
  const cv=useRef(null);
  const bootedRef=useRef(booted);
  useEffect(()=>{ bootedRef.current=booted; },[booted]);
  useEffect(()=>{
    const THREE=window.THREE, canvas=cv.current;
    if(!THREE||!canvas) return;
    const host=canvas.parentElement;
    let w=host.clientWidth, h=host.clientHeight;
    const renderer=new THREE.WebGLRenderer({ canvas, alpha:true, antialias:false });
    renderer.setPixelRatio(Math.min(1.5, devicePixelRatio||1));
    renderer.setSize(w,h);
    const scene=new THREE.Scene();
    const cam=new THREE.OrthographicCamera(-1,1,1,-1,0,1);
    const ac=accentRGB();
    const uniforms={
      uTime:{value:0}, uOpacity:{value:0},
      uRes:{value:new THREE.Vector2(w*renderer.getPixelRatio(),h*renderer.getPixelRatio())},
      uMouse:{value:new THREE.Vector2(0.5,0.5)},
      uColor:{value:new THREE.Color(ac.r,ac.g,ac.b)},
    };
    const mat=new THREE.ShaderMaterial({
      uniforms, transparent:true, depthWrite:false,
      vertexShader:`void main(){ gl_Position=vec4(position.xy,0.0,1.0); }`,
      fragmentShader:`
        precision highp float;
        uniform float uTime,uOpacity; uniform vec2 uRes,uMouse; uniform vec3 uColor;
        void main(){
          vec2 p=(gl_FragCoord.xy*2.0-uRes)/uRes.y;
          float t=uTime*0.32;
          vec2 m=(uMouse-0.5)*0.5;
          vec2 q=p*1.12+m;
          for(float i=1.0;i<6.0;i++){
            q.x+=(0.62/i)*sin(i*2.3*q.y+t+i*1.7);
            q.y+=(0.62/i)*cos(i*1.9*q.x-t*0.8+i);
          }
          float v=0.5+0.5*sin(q.x*1.4+q.y*1.8);
          float ridge=0.70+0.30*sin((q.x+q.y)*11.0-t*2.0);
          v=pow(v,3.2)*ridge;
          /* keep copy zone (left) calm, let the right side glow */
          float side=smoothstep(-0.9,1.5,p.x);
          float vert=1.0-smoothstep(0.25,1.3,abs(p.y));
          float k=v*(0.10+0.36*side)*max(vert,0.10);
          vec3 col=uColor*k+vec3(1.0)*k*k*0.15;
          gl_FragColor=vec4(col, uOpacity*min(0.85,k*2.0));
        }`,
    });
    scene.add(new THREE.Mesh(new THREE.PlaneGeometry(2,2), mat));
    let mx=0.5,my=0.5, raf, t0=performance.now(), fr=0, vis=true;
    const fine=matchMedia("(pointer:fine)").matches;
    const onMove=(e)=>{ mx=e.clientX/innerWidth; my=1-e.clientY/innerHeight; };
    if(fine) addEventListener("pointermove",onMove,{passive:true});
    const io=new IntersectionObserver(([en])=>{ vis=en.isIntersecting; }); io.observe(host);
    const resize=()=>{ w=host.clientWidth; h=host.clientHeight; renderer.setSize(w,h);
      uniforms.uRes.value.set(w*renderer.getPixelRatio(),h*renderer.getPixelRatio()); };
    const ro=new ResizeObserver(resize); ro.observe(host);
    function loop(now){
      if(userIdle()){ raf=0; return; }
      raf=requestAnimationFrame(loop);
      if(!vis||document.hidden){ t0=now; return; }
      const dt=Math.min(0.05,(now-t0)/1000); t0=now; fr++;
      if(!motionOff()) uniforms.uTime.value+=dt;
      uniforms.uOpacity.value+=((bootedRef.current?1:0)-uniforms.uOpacity.value)*0.04;
      uniforms.uMouse.value.x+=(mx-uniforms.uMouse.value.x)*0.03;
      uniforms.uMouse.value.y+=(my-uniforms.uMouse.value.y)*0.03;
      if(fr%45===0){ const a=accentRGB(); uniforms.uColor.value.setRGB(a.r,a.g,a.b); }
      renderer.render(scene,cam);
    }
    const resume=()=>{ if(!raf){ t0=performance.now(); raf=requestAnimationFrame(loop); } };
    idleResume.add(resume);
    raf=requestAnimationFrame(loop);
    return ()=>{ idleResume.delete(resume); cancelAnimationFrame(raf); if(fine) removeEventListener("pointermove",onMove);
      io.disconnect(); ro.disconnect(); mat.dispose(); renderer.dispose(); };
  },[]);
  return <canvas ref={cv} className="phero-canvas"></canvas>;
}
window.SilkHero=SilkHero;
})();
