// Vapor — the shared-layer visual language for the site.
// Pigment word-art (ported verbatim from the design reference's drawPigment)
// + inked moons for the Circles feed + a tiny affect lexicon so the site's
// words render deterministically, exactly as the app would.

const WA_TAU = Math.PI * 2;
function waMulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296;};}
function waFnv1a(s){let h=2166136261>>>0;for(let i=0;i<s.length;i++)h=Math.imul(h^s.toLowerCase().charCodeAt(i),16777619);return h>>>0;}
const waLerp=(a,b,t)=>a+(b-a)*t;
const waLerp3=(a,b,t)=>[Math.round(waLerp(a[0],b[0],t)),Math.round(waLerp(a[1],b[1],t)),Math.round(waLerp(a[2],b[2],t))];
const waRgba=(c,a)=>'rgba('+c[0]+','+c[1]+','+c[2]+','+a+')';

// temperature: cool (negative) -> warm (positive). default-dark reference endpoints.
function waTemp(pol){const w=(pol+1)/2;return {
  hi:[Math.round(waLerp(168,250,w)),Math.round(waLerp(202,234,w)),Math.round(waLerp(196,198,w))],
  mid:[Math.round(waLerp(92,214,w)),Math.round(waLerp(148,178,w)),Math.round(waLerp(146,106,w))]};}

function waBaseSurface(ctx,W,H){
  const g=ctx.createRadialGradient(W/2,H*0.46,0,W/2,H*0.46,Math.hypot(W,H)*0.62);
  g.addColorStop(0,'#201d17');g.addColorStop(0.5,'#181613');g.addColorStop(1,'#100e0b');
  ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
}
function waStamp(ctx,x,y,r,col,a){
  const g=ctx.createRadialGradient(x,y,0,x,y,r);
  g.addColorStop(0,waRgba(col,a));g.addColorStop(0.4,waRgba(col,a*0.45));
  g.addColorStop(0.75,waRgba(col,a*0.12));g.addColorStop(1,waRgba(col,0));
  ctx.fillStyle=g;ctx.beginPath();ctx.arc(x,y,r,0,WA_TAU);ctx.fill();
}
let _waGrain=null;
function waGrainTile(){if(_waGrain)return _waGrain;const cell=3,cells=120,s=cell*cells,c=document.createElement('canvas');c.width=s;c.height=s;const g=c.getContext('2d');for(let y=0;y<cells;y++)for(let x=0;x<cells;x++){const v=Math.round(Math.random()*255);g.fillStyle='rgb('+v+','+v+','+v+')';g.fillRect(x*cell,y*cell,cell,cell);}_waGrain=c;return c;}
function waFinish(ctx,W,H,ground){
  const mn=Math.min(W,H),cx=W/2,cy=H*0.46;
  if(ground){
    const fo=ctx.createRadialGradient(cx,cy,mn*0.36,cx,cy,mn*0.82);
    fo.addColorStop(0,'rgba(16,14,11,0)');fo.addColorStop(1,'rgba(16,14,11,1)');
    ctx.fillStyle=fo;ctx.fillRect(0,0,W,H);
  }
  if(ground){
    ctx.save();ctx.globalCompositeOperation='overlay';ctx.globalAlpha=0.06;
    ctx.fillStyle=ctx.createPattern(waGrainTile(),'repeat');ctx.fillRect(0,0,W,H);ctx.restore();
    const vg=ctx.createRadialGradient(cx,cy,mn*0.32,cx,cy,Math.hypot(W,H)*0.6);
    vg.addColorStop(0,'rgba(0,0,0,0)');vg.addColorStop(1,'rgba(0,0,0,0.5)');
    ctx.fillStyle=vg;ctx.fillRect(0,0,W,H);
  }
}

const WA_GEST={
  highPositive:{cy:0.44,upBias:-0.62,sx:0.24,sy:0.36,sat:3,satUp:true},
  lowPositive: {cy:0.46,upBias:0.0, sx:0.20,sy:0.20,sat:1,satUp:false},
  neutral:     {cy:0.50,upBias:0.0, sx:0.32,sy:0.24,sat:2,satUp:false},
  lowNegative: {cy:0.66,upBias:0.42,sx:0.36,sy:0.24,sat:2,satUp:false},
  highNegative:{cy:0.47,upBias:0.0, sx:0.17,sy:0.17,sat:2,satUp:false},
};

// ground=true  → full 4:3 composition on dark ground (the app's word panel).
// ground=false → additive pigment only, transparent surround (floats on the page).
function waDrawPigment(ctx,W,H,quad,seed,pol,en,ground){
  const rng=waMulberry32(seed),pal=waTemp(pol),e=(en+1)/2,mn=Math.min(W,H),g=WA_GEST[quad]||WA_GEST.neutral;
  ctx.clearRect(0,0,W,H);
  if(ground) waBaseSurface(ctx,W,H);
  const cx=W*0.5, cy=H*g.cy;
  ctx.globalCompositeOperation='lighter';
  const boost=ground?1:1.5; // floats need a touch more presence over the page
  const bodyN=Math.round(waLerp(22,40,e));
  const bodyR=mn*waLerp(0.20,0.12,e);
  for(let i=0;i<bodyN;i++){
    const gx=(rng()+rng()-1),gy=(rng()+rng()-1);
    const x=cx+gx*W*g.sx*0.7, y=cy+gy*H*g.sy*0.7+g.upBias*Math.abs(gy)*H*0.28;
    const r=bodyR*(0.6+rng()*0.8);
    const d=Math.hypot((x-cx)/(W*g.sx+1),(y-cy)/(H*g.sy+1));
    waStamp(ctx,x,y,r,waLerp3(pal.hi,pal.mid,Math.min(1,d*0.7)),0.035*boost*(1-Math.min(0.8,d*0.4)));
  }
  const mottN=Math.round(waLerp(120,360,e));
  const mottR=mn*waLerp(0.11,0.045,e);
  for(let i=0;i<mottN;i++){
    const gx=(rng()+rng()+rng()-1.5)/1.5, gy=(rng()+rng()+rng()-1.5)/1.5;
    const x=cx+gx*W*g.sx, y=cy+gy*H*g.sy+g.upBias*Math.abs(gy)*H*0.32;
    const r=mottR*(0.3+rng()*1.2);
    const d=Math.hypot((x-cx)/(W*g.sx+1),(y-cy)/(H*g.sy+1));
    const col=waLerp3(pal.hi,pal.mid,Math.min(1,0.15+d*0.85));
    const a=waLerp(0.035,0.068,e)*(1-Math.min(0.9,d*0.5))*(0.4+rng()*0.9)*boost;
    waStamp(ctx,x,y,r,col,Math.max(0.006,a));
  }
  for(let i=0;i<g.sat;i++){
    const ang=g.satUp?(-Math.PI/2+(rng()-0.5)*1.4):rng()*WA_TAU;
    const dist=mn*(0.18+rng()*0.22);
    const x=cx+Math.cos(ang)*dist*(0.8+rng()*0.6), y=cy+Math.sin(ang)*dist;
    const r=mn*waLerp(0.09,0.05,e)*(0.7+rng()*0.7);
    waStamp(ctx,x,y,r,waLerp3(pal.hi,pal.mid,0.6),waLerp(0.03,0.055,e)*boost);
  }
  waStamp(ctx,cx+(rng()-0.5)*W*0.03,cy+g.upBias*H*0.04,mn*0.075,waLerp3(pal.hi,pal.mid,0.5),waLerp(0.07,0.11,e)*boost);
  ctx.globalCompositeOperation='source-over';
  waFinish(ctx,W,H,ground);
}

// ── affect lexicon — the polarity/energy/quadrant the app's scorer would give
// the handful of words the site shows. deterministic, matches the spec anchors.
const WA_AFFECT = {
  alive:     { pol: 0.85, en: 0.90, quad: 'highPositive' },
  lifting:   { pol: 0.75, en: 0.55, quad: 'highPositive' },
  hopeful:   { pol: 0.62, en: 0.20, quad: 'highPositive' },
  grateful:  { pol: 0.80, en: 0.10, quad: 'highPositive' },
  radiant:   { pol: 0.82, en: 0.70, quad: 'highPositive' },
  open:      { pol: 0.55, en: -0.10, quad: 'lowPositive' },
  calm:      { pol: 0.78, en: -0.65, quad: 'lowPositive' },
  tender:    { pol: 0.70, en: -0.50, quad: 'lowPositive' },
  healing:   { pol: 0.48, en: -0.22, quad: 'lowPositive' },
  still:     { pol: 0.25, en: -0.70, quad: 'lowPositive' },
  grounded:  { pol: 0.58, en: -0.42, quad: 'lowPositive' },
  home:      { pol: 0.72, en: -0.55, quad: 'lowPositive' },
  safe:      { pol: 0.70, en: -0.45, quad: 'lowPositive' },
  pensive:   { pol: -0.06, en: -0.28, quad: 'neutral' },
  monday:    { pol: 0.00, en: -0.12, quad: 'neutral' },
  quiet:     { pol: 0.10, en: -0.40, quad: 'neutral' },
  ambivalent:{ pol: -0.05, en: -0.05, quad: 'neutral' },
  tired:     { pol: -0.42, en: -0.62, quad: 'lowNegative' },
  heavy:     { pol: -0.80, en: -0.68, quad: 'lowNegative' },
  lonely:    { pol: -0.62, en: -0.45, quad: 'lowNegative' },
  numb:      { pol: -0.55, en: -0.75, quad: 'lowNegative' },
  grief:     { pol: -0.78, en: -0.50, quad: 'lowNegative' },
  anxious:   { pol: -0.60, en: 0.72, quad: 'highNegative' },
  angry:     { pol: -0.82, en: 0.90, quad: 'highNegative' },
  afraid:    { pol: -0.70, en: 0.78, quad: 'highNegative' },
};
function waResolveAffect(raw){
  const word = String(raw||'').toLowerCase().replace(/[^a-z]/g,'');
  const a = WA_AFFECT[word] || { pol: 0, en: -0.1, quad: 'neutral' };
  return { seed: waFnv1a(word || 'monday'), pol: a.pol, en: a.en, quad: a.quad, word };
}

// ── PigmentBloom — deterministic still art for a word. Rendered once (static).
function PigmentBloom({ word = 'monday', ground = false, ar = 4/3, style = {}, className = '' }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const c = ref.current; if (!c) return;
    // render at a modest internal resolution and let CSS scale it up (spec §6).
    const IW = 640, IH = Math.round(IW / ar);
    c.width = IW; c.height = IH;
    const ctx = c.getContext('2d');
    const a = waResolveAffect(word);
    waDrawPigment(ctx, IW, IH, a.quad, a.seed, a.pol, a.en, ground);
  }, [word, ground, ar]);
  return <canvas ref={ref} className={className} style={{ display: 'block', width: '100%', height: '100%', ...style }} />;
}

// ── InkedMoon — a member's daily word as a lunar phase. weight (-10..+10):
// heavy/negative -> crescent, neutral -> half, light/positive -> full.
function InkedMoon({ weight = 0, size = 48, lit = '#7EA882', dark = 'rgba(232,226,217,0.10)', rim = 'rgba(232,226,217,0.22)' }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const c = ref.current; if (!c) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    c.width = size * dpr; c.height = size * dpr;
    const ctx = c.getContext('2d'); ctx.scale(dpr, dpr);
    ctx.clearRect(0, 0, size, size);
    const cx = size / 2, cy = size / 2, r = size / 2 - 1.5;
    const f = Math.max(0.06, Math.min(0.96, (weight + 10) / 20)); // illuminated fraction, waxing (lit right)
    // dark disc
    ctx.beginPath(); ctx.arc(cx, cy, r, 0, WA_TAU); ctx.fillStyle = dark; ctx.fill();
    // lit region (clipped), filled + softly grained like hand-printed ink
    ctx.save();
    ctx.beginPath();
    ctx.arc(cx, cy, r, -Math.PI / 2, Math.PI / 2, false);
    const b = r * Math.abs(1 - 2 * f);
    ctx.ellipse(cx, cy, b, r, 0, Math.PI / 2, -Math.PI / 2, f < 0.5);
    ctx.closePath();
    ctx.clip();
    ctx.fillStyle = lit; ctx.fillRect(cx - r, cy - r, r * 2, r * 2);
    const hl = ctx.createRadialGradient(cx + r * 0.28, cy - r * 0.3, r * 0.1, cx, cy, r * 1.2);
    hl.addColorStop(0, 'rgba(255,255,255,0.16)'); hl.addColorStop(1, 'rgba(255,255,255,0)');
    ctx.fillStyle = hl; ctx.fillRect(cx - r, cy - r, r * 2, r * 2);
    // grain: deterministic sage mottle
    const gr = waMulberry32((Math.round((weight + 10) * 97) + 7) >>> 0 || 1);
    const speckles = Math.round(r * r * 2.2);
    for (let i = 0; i < speckles; i++) {
      const px = cx - r + gr() * r * 2, py = cy - r + gr() * r * 2;
      const v = gr(), a = 0.05 + gr() * 0.16, s = 0.5 + gr() * 1.0;
      ctx.fillStyle = v > 0.52 ? `rgba(238,244,236,${a})` : `rgba(14,26,18,${a})`;
      ctx.fillRect(px, py, s, s);
    }
    ctx.restore();
    // rim
    ctx.beginPath(); ctx.arc(cx, cy, r, 0, WA_TAU); ctx.strokeStyle = rim; ctx.lineWidth = 0.75; ctx.stroke();
  }, [weight, size, lit, dark, rim]);
  return <canvas ref={ref} style={{ width: size, height: size, display: 'block' }} />;
}

Object.assign(window, { PigmentBloom, InkedMoon, waResolveAffect });
