/* ============================================================================
   MATRIA, shared UI: icons, decorative SVG, status bar, tab bar, portraits.
   Exposed on window for other babel scripts.
   ============================================================================ */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ── line icons ─────────────────────────────────────────── */
function Icon({name, size=22, stroke=1.3, ...p}){
  const paths = {
    home:<><path d="M3 11l9-7 9 7v8.5a1 1 0 0 1-1 1h-5.5V15h-5v5.5H4a1 1 0 0 1-1-1z"/></>,
    ask:<><circle cx="12" cy="12" r="8.4"/><path d="M12 8.2v7.6M8.2 12h7.6"/></>,
    explore:<><path d="M12 3l2.2 6.6L21 12l-6.8 2.4L12 21l-2.2-6.6L3 12l6.8-2.4z"/></>,
    paths:<><circle cx="6" cy="6" r="2.2"/><circle cx="18" cy="9" r="2.2"/><circle cx="9" cy="18" r="2.2"/><path d="M8 6.7l8 1.6M16.5 10.8L10.5 16"/></>,
    sanctuary:<><circle cx="12" cy="8" r="3.6"/><path d="M5.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6"/></>,
    bell:<><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M10.5 21a2 2 0 0 0 3 0"/></>,
    back:<><path d="M15 5l-7 7 7 7"/></>,
    chevron:<><path d="M9 6l6 6-6 6"/></>,
    arrow:<><path d="M5 12h13M13 6l6 6-6 6"/></>,
    save:<><path d="M6 4h12v17l-6-4-6 4z"/></>,
    journal:<><path d="M6 3h9l3 3v15H6z"/><path d="M9 9h6M9 13h6"/></>,
    share:<><path d="M9 4h11v11M20 4l-9 9M4 9v11h11"/></>,
    sources:<><path d="M5 5h9l3 3v11H5z"/><path d="M8 11h6M8 15h6"/></>,
    filter:<><circle cx="12" cy="12" r="8.5"/><path d="M12 7v10M7 12h10"/></>,
    legend:<><path d="M12 3v18M3 12h18M5.5 5.5l13 13M18.5 5.5l-13 13"/></>,
    star:<><path d="M12 4l2 5 5 .5-4 3.5 1.5 5L12 20l-4.5 3 1.5-5-4-3.5 5-.5z"/></>,
    leaf:<><path d="M12 21c-4.5-6.2-7.5-10.2-7.5-14.2a7.5 7.5 0 0 1 15 0c0 4-3 7.8-7.5 14.2z"/><path d="M12 10.5V21"/></>,
    more:<><circle cx="6" cy="12" r="1.3" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.3" fill="currentColor" stroke="none"/><circle cx="18" cy="12" r="1.3" fill="currentColor" stroke="none"/></>,
    bookmark:<><path d="M7 4h10v16l-5-3.5L7 20z"/></>,
    map:<><path d="M4 7l5-2 6 2 5-2v12l-5 2-6-2-5 2z"/><path d="M9 5v12M15 7v12"/></>,
    check:<><path d="M5 12l4 4 10-11"/></>,
    pen:<><path d="M5 19l1-4 9-9 3 3-9 9zM14 6l3 3"/></>,
    constellation:<><circle cx="6" cy="7" r="1.4"/><circle cx="17" cy="5" r="1.4"/><circle cx="12" cy="12" r="1.7"/><circle cx="19" cy="16" r="1.4"/><circle cx="8" cy="18" r="1.4"/><path d="M6 7l6 5 5-7M12 12l7 4M12 12l-4 6"/></>,
    letters:<><rect x="3" y="5.5" width="18" height="13" rx="1.5"/><path d="M3.5 6.5L12 13l8.5-6.5"/></>,
    plus:<><path d="M12 5v14M5 12h14"/></>,
    minus:<><path d="M5 12h14"/></>,
    lock:<><rect x="6" y="10" width="12" height="10" rx="2"/><path d="M8.5 10V8a3.5 3.5 0 0 1 7 0v2"/></>,
    search:<><circle cx="11" cy="11" r="6.2"/><path d="M16.2 16.2L20 20"/></>,
    heart:<><path d="M12 20.5s-6.8-4.2-6.8-9.4C5.2 7.8 7.4 6 9.8 6c1.4 0 2.2.7 3 1.6.8-.9 1.6-1.6 3-1.6 2.4 0 4.6 1.8 4.6 5.1 0 5.2-6.8 9.4-6.8 9.4z"/></>,
    sun:<><circle cx="12" cy="12" r="3.2"/><path d="M12 4.2v2.2M12 17.6v2.2M4.2 12h2.2M17.6 12h2.2M6.2 6.2l1.6 1.6M16.2 16.2l1.6 1.6M17.8 6.2l-1.6 1.6M6.2 17.8l-1.6 1.6"/></>,
  };
  return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" {...p}>{paths[name]}</svg>;
}

/* Matria pillar glyphs: shared by splash + onboarding */
function SplashGlyph({kind, size=28}){
  const g={
    stories:<><path d="M6.5 5.5h4.8a2.2 2.2 0 0 1 2.2 2.2V19"/><path d="M17.5 5.5H12.7a2.2 2.2 0 0 0-2.2 2.2V19"/><path d="M12 7.7v11.3"/></>,
    women:<><path d="M12 8.5c1.8 0 3 1.2 3 2.8S13.8 14 12 14s-3-1.2-3-2.7S10.2 8.5 12 8.5z"/><path d="M12 14v2"/><path d="M9.5 19.5c.6-2 1.6-3 2.5-3s1.9 1 2.5 3"/><path d="M8 12.5c-1.2-.4-2-1.2-2-2.2s1.2-1.8 3-2"/><path d="M16 12.5c1.2-.4 2-1.2 2-2.2s-1.2-1.8-3-2"/></>,
    knowledge:<><path d="M12 4.5l1.2 3.4 3.5.3-2.7 2 1 3.4-2.9-1.9-2.9 1.9 1-3.4-2.7-2 3.5-.3z"/><circle cx="6.5" cy="7" r=".8"/><circle cx="17.5" cy="8" r=".8"/><circle cx="8" cy="17" r=".8"/></>,
    sources:<><path d="M12 4.2l4.2 7.3-4.2 7.3-4.2-7.3z"/><path d="M12 10.8v7.7"/></>,
  };
  return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">{g[kind]}</svg>;
}

const MATRIA_PILLARS=[
  {key:'stories', label:'Ancient stories', subtitle:'Recovered writings and oral traditions.'},
  {key:'women', label:'Radical women', subtitle:'Voices that challenged and endured.'},
  {key:'knowledge', label:'Embodied knowledge', subtitle:'Body, spirit, and cosmos.'},
  {key:'sources', label:'Real sources', subtitle:'Every source named in full.'},
];

/* type glyphs (for graph nodes / explore) */
function TypeGlyph({type, size=16}){
  const g={
    person:<><path d="M8.4 18.8c.9-2.7 2.1-4 3.6-4s2.7 1.3 3.6 4"/><path d="M9.2 9.4c.7-2.4 4.9-2.4 5.6 0 .4 1.6-.8 3.2-2.8 3.2s-3.2-1.6-2.8-3.2z"/><path d="M5.2 15.5c1.8-4.8 3.9-7.8 6.8-10.8 2.9 3 5 6 6.8 10.8"/><path d="M6.2 19.8c2.1-1 4-1.5 5.8-1.5s3.7.5 5.8 1.5"/></>,
    myth:<><path d="M16.8 4.4a7.8 7.8 0 1 0 2.8 11.2 6 6 0 0 1-5.5-10.3"/><path d="M8.2 16.2c2.7-4.9 5.4-7.1 8.2-6.6"/><circle cx="8.2" cy="8.2" r="1"/></>,
    political:<><path d="M12 3.8l6.8 3.5v4.8c0 4-2.7 6.8-6.8 8.1-4.1-1.3-6.8-4.1-6.8-8.1V7.3z"/><path d="M12 7.3v9.4M8.5 11.2h7"/></>,
    power:<><path d="M12 3.8l6.8 3.5v4.8c0 4-2.7 6.8-6.8 8.1-4.1-1.3-6.8-4.1-6.8-8.1V7.3z"/><path d="M12 7.3v9.4M8.5 11.2h7"/></>,
    history:<><path d="M5.5 6.2h13M5.5 12h13M5.5 17.8h13"/><path d="M8.2 4.2v15.6M15.8 4.2v15.6"/><circle cx="12" cy="12" r="1.7"/></>,
    science:<><circle cx="12" cy="12" r="2.2"/><ellipse cx="12" cy="12" rx="8.6" ry="3.2" transform="rotate(24 12 12)"/><ellipse cx="12" cy="12" rx="8.6" ry="3.2" transform="rotate(-24 12 12)"/><circle cx="18.2" cy="8.1" r="1.1"/></>,
    cosmos:<><circle cx="12" cy="12" r="2.2"/><ellipse cx="12" cy="12" rx="8.6" ry="3.2" transform="rotate(24 12 12)"/><ellipse cx="12" cy="12" rx="8.6" ry="3.2" transform="rotate(-24 12 12)"/><circle cx="18.2" cy="8.1" r="1.1"/></>,
    tradition:<><path d="M6.2 9.2c2.1-3.2 9.5-3.2 11.6 0"/><path d="M7.4 9.1v4.7a4.6 4.6 0 0 0 9.2 0V9.1"/><path d="M9.2 18.7h5.6M8 12.2h8"/></>,
    idea:<><path d="M6 16.5c3.5-4.8 7.5-7.8 12-9"/><path d="M7.2 7.5c3.9 2.9 6.8 6.4 8.6 10.5"/><circle cx="7" cy="16.5" r="1.2"/><circle cx="12" cy="12" r="1.4"/><circle cx="18" cy="7.5" r="1.2"/></>,
  };
  return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="1.15" strokeLinecap="round" strokeLinejoin="round">{g[type]||g.idea}</svg>;
}

/* concept glyphs: feminist literary idea marks, not universal app icons */
function ConceptGlyph({id, size=18}){
  const g={
    idea:<><path d="M6 16.5c3.5-4.8 7.5-7.8 12-9"/><path d="M7.2 7.5c3.9 2.9 6.8 6.4 8.6 10.5"/><circle cx="7" cy="16.5" r="1.2"/><circle cx="12" cy="12" r="1.4"/><circle cx="18" cy="7.5" r="1.2"/></>,
    care_ethics:<><path d="M7 12.5c0 3 2.1 5.5 5 6.6 2.9-1.1 5-3.6 5-6.6V8.8"/><path d="M7 8.8c1.5-1.8 3.1-2.7 5-2.7s3.5.9 5 2.7"/><path d="M9.2 12.5c1.6 1.2 4 1.2 5.6 0"/></>,
    rest_resistance:<><path d="M5.5 14.5h13"/><path d="M7.5 11.2c2.6-2.8 6.4-2.8 9 0"/><path d="M8 17.4c2.7 1 5.3 1 8 0"/><circle cx="12" cy="14.5" r="1.2"/></>,
    motherhood_inst:<><path d="M6 19V8.5l6-4.2 6 4.2V19"/><path d="M9 19v-6h6v6"/><path d="M8.5 10.3h7"/><path d="M12 5.2v13.5"/></>,
    invisible_labour:<><path d="M5 16c3-4 6-5.8 9-5.4 2 .3 3.7 1.6 5 3.9"/><path d="M5 19c3.8-2.2 8.5-2.2 14 0" opacity=".72"/><path d="M7.5 10.2h9" strokeDasharray="1.5 2"/></>,
    recognition:<><path d="M4.8 12s2.7-4.4 7.2-4.4 7.2 4.4 7.2 4.4-2.7 4.4-7.2 4.4S4.8 12 4.8 12z"/><circle cx="12" cy="12" r="2.2"/><path d="M15.8 7.2l2.2-2.2M8.2 16.8L6 19"/></>,
    reciprocity:<><path d="M7.2 8.4c1.3-1.5 2.9-2.2 4.8-2.2 2.6 0 4.8 1.6 5.8 3.8"/><path d="M16.8 15.6c-1.3 1.5-2.9 2.2-4.8 2.2-2.6 0-4.8-1.6-5.8-3.8"/><path d="M17.8 6.8v3.2h-3.2M6.2 17.2V14h3.2"/></>,
    interiority:<><path d="M7 19V8.2l5-3.7 5 3.7V19"/><path d="M10 19v-5.3a2 2 0 0 1 4 0V19"/><circle cx="12" cy="10.4" r="1.2"/></>,
    combahee:<><path d="M5.2 8.2c3.6 2.2 5.4 5.5 6.8 9.6"/><path d="M18.8 8.2c-3.6 2.2-5.4 5.5-6.8 9.6"/><path d="M6.5 14.8c3.2-1.7 7.8-1.7 11 0"/><circle cx="12" cy="17.8" r="1.1"/></>,
    embodiment:<><path d="M12 4.5c2.8 2.4 4.2 5 4.2 7.7 0 3-1.4 5.4-4.2 7.3-2.8-1.9-4.2-4.3-4.2-7.3 0-2.7 1.4-5.3 4.2-7.7z"/><path d="M9 12h2l1-2.4 1.5 5 1-2.6H16"/></>,
    oral_memory:<><path d="M6 12c2-2.4 4-3.6 6-3.6s4 1.2 6 3.6c-2 2.4-4 3.6-6 3.6s-4-1.2-6-3.6z"/><path d="M8.5 17.4c2.2 1.4 4.8 1.4 7 0"/><path d="M9 11.8c2 .8 4 .8 6 0"/></>,
    wages_housework:<><path d="M5.5 9.5h13v9h-13z"/><path d="M8 9.5V7.2h8v2.3"/><path d="M8.2 13h7.6M8.2 15.8h5.2"/><circle cx="17" cy="16" r="1.1"/></>,
    pachamama:<><path d="M12 20c4.6-2.6 6.2-7.2 4.6-13.8-3.3 1.8-6.6 4.2-6.6 8.1 0 2.1.8 3.9 2 5.7z"/><path d="M12 20c-2.3-2-3.9-4.5-4.8-7.4"/><path d="M12 20V9"/></>,
    ecofeminism:<><path d="M12 20c4.4-2.8 5.8-7 4.2-12.6-2.8 1.3-5.8 3.6-5.8 7.2"/><path d="M7.2 9.2c3.2.2 5.4 1.8 6.6 4.8"/><path d="M12 20V8.5"/></>,
    daoist_yin:<><path d="M12 4.5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15z"/><path d="M12 4.5c2 2 2 5 0 7.5s-2 5.5 0 7.5"/><circle cx="10.2" cy="8.8" r=".9"/><circle cx="13.8" cy="15.2" r=".9"/></>,
    ubuntu:<><circle cx="8" cy="9" r="2"/><circle cx="16" cy="9" r="2"/><path d="M5.2 18c.4-3 2-4.5 4.8-4.5"/><path d="M18.8 18c-.4-3-2-4.5-4.8-4.5"/><path d="M9.5 16.2c1.7 1 3.3 1 5 0"/></>,
    great_migration:<><path d="M5 17.5c4.2-5.4 8.8-8.8 14-10.2"/><path d="M15.8 6.4l3.2.9-1.5 3"/><path d="M6.2 12h5.2M5 17.5h5.8"/></>,
    ancestor_veneration:<><path d="M12 4.5v15"/><path d="M7 9.5c2.4-2.1 7.6-2.1 10 0"/><path d="M8.2 15.2c1.8 1.4 5.8 1.4 7.6 0"/><circle cx="12" cy="4.5" r="1.2"/></>,
    weaving:<><path d="M6 6v12M10 6v12M14 6v12M18 6v12"/><path d="M5 9.5c4 2.2 9.9 2.2 14 0"/><path d="M5 14.5c4-2.2 9.9-2.2 14 0"/></>,
    midwifery:<><path d="M7 16.8c1.6-3.8 3.3-5.8 5-5.8s3.4 2 5 5.8"/><path d="M8.8 9.2c1.1-2.4 5.3-2.4 6.4 0"/><path d="M6.2 18.8c3.4 1 8.2 1 11.6 0"/></>,
    green_belt:<><path d="M12 20V8"/><path d="M12 11c-3.6-.6-5.8-2.4-6.6-5.4 3.4.1 5.6 1.9 6.6 5.4z"/><path d="M12 13c3.6-.6 5.8-2.4 6.6-5.4-3.4.1-5.6 1.9-6.6 5.4z"/></>,
  };
  const mark = g[id] || g.idea;
  return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="1.15" strokeLinecap="round" strokeLinejoin="round">{mark}</svg>;
}

function hashSeed(str){
  let h=0; const s=String(str||'Matria');
  for(let i=0;i<s.length;i++) h=(h*31+s.charCodeAt(i))%9973;
  return h;
}

function TypeMedallion({type='idea', conceptId, size=40, label}){
  return (
    <span className={'type-medallion type-medallion-'+type} style={{width:size,height:size}} aria-hidden={label?undefined:true} aria-label={label}>
      <span className="type-medallion__halo"/>
      {type==='idea'
        ? <ConceptGlyph id={conceptId} size={Math.round(size*.48)}/>
        : <TypeGlyph type={type} size={Math.round(size*.48)}/>}
    </span>
  );
}

/* ── status bar ─────────────────────────────────────────── */
function StatusBar(){
  return (
    <div className="status">
      <div className="clock">9:41</div>
      <div className="sig">
        <svg width="17" height="11" viewBox="0 0 17 11"><g fill="currentColor">
          <rect x="0" y="7" width="3" height="4" rx="1"/><rect x="4.5" y="5" width="3" height="6" rx="1"/>
          <rect x="9" y="2.5" width="3" height="8.5" rx="1"/><rect x="13.5" y="0" width="3" height="11" rx="1"/>
        </g></svg>
        <svg width="16" height="11" viewBox="0 0 16 11" fill="none" stroke="currentColor" strokeWidth="1.3">
          <path d="M1 4.2C3 2.4 5.4 1.4 8 1.4s5 1 7 2.8"/><path d="M3.4 6.6C4.7 5.5 6.3 4.9 8 4.9s3.3.6 4.6 1.7"/><path d="M5.8 9c.6-.5 1.4-.8 2.2-.8s1.6.3 2.2.8"/>
        </svg>
        <svg width="25" height="12" viewBox="0 0 25 12">
          <rect x="1" y="1" width="20" height="10" rx="3" fill="none" stroke="currentColor" strokeOpacity=".5" strokeWidth="1"/>
          <rect x="2.6" y="2.6" width="16" height="6.8" rx="1.6" fill="currentColor"/>
          <rect x="22" y="4" width="1.8" height="4" rx="1" fill="currentColor" fillOpacity=".5"/>
        </svg>
      </div>
    </div>
  );
}

/* ── tab bar ─────────────────────────────────────────────── */
const TABS = [['Today','home'],['Letters','letters'],['Ask','ask'],['Explore','explore'],['Sanctuary','sanctuary']];
function TabBar({active, onNav}){
  const S=useMStore();
  const unread = S.unreadInbox().length;
  return (
    <nav className="tabbar">
      {TABS.map(([label,icon])=>{
        const on = active===label;
        const badge = label==='Letters' ? unread : 0;
        return (
          <button key={label} className={'tab'+(on?' on':'')} onClick={()=>onNav&&onNav(label)}
            aria-label={badge>0?label+', '+badge+' new':label} aria-current={on?'page':undefined}>
            <span className="tab__icon">
              <Icon name={icon} size={21} stroke={on?1.45:1.2}/>
              {badge>0 && <span className="tab-badge">{badge}</span>}
            </span>
            <span>{label}</span>
          </button>
        );
      })}
    </nav>
  );
}

/* ── portrait ───────────────────────────────────────────── */
function Portrait({pic, name, id, size=48, round=true, radius=10, collage=false}){
  const collageSrc=id?collagePic(id):null;
  const resolved=pic||collageSrc;
  const [src,setSrc]=useState(resolved);
  useEffect(()=>{ setSrc(resolved); },[resolved]);
  const initials = (name||'?').split(' ').map(w=>w[0]).slice(0,2).join('');
  const style={width:size,height:size,borderRadius:round?'50%':radius};
  if(src) return <img className={'portrait'+(round?' round':'')+(collage?' portrait--collage':'')} src={src} alt={name||''} style={style}
    onError={()=>{
      if(src!==collageSrc && collageSrc) setSrc(collageSrc);
      else setSrc(null);
    }}/>;
  const seed=hashSeed(name);
  const x1=18+(seed%42), y1=16+((seed>>2)%38), x2=38+((seed>>4)%36), y2=42+((seed>>6)%30);
  const bgHue=seed%5;
  return (
    <div className={'abstract-portrait abstract-portrait-'+bgHue+(round?' round':'')} style={{...style, '--ap-size':size+'px', '--ap-x1':x1+'%', '--ap-y1':y1+'%', '--ap-x2':x2+'%', '--ap-y2':y2+'%', flex:'none'}} aria-label={name||'Archive figure'}>
      <span className="abstract-portrait__veil"/>
      <span className="abstract-portrait__thread"/>
      <span className="abstract-portrait__star s1"/>
      <span className="abstract-portrait__star s2"/>
      <span className="abstract-portrait__mark">{initials}</span>
    </div>
  );
}

/* ── decorative: constellation (splash / loading) ───────── */
function Constellation({w=240, h=240, seed=7, dense=false}){
  const data = useMemo(()=>{
    let s=seed; const rnd=()=>{s=(s*9301+49297)%233280;return s/233280;};
    const cx=w/2, cy=h*0.42, pts=[];
    const n = dense? 60 : 34;
    for(let i=0;i<n;i++){const a=rnd()*Math.PI*2, r=rnd()*Math.min(w,h)*0.46; pts.push([cx+Math.cos(a)*r, cy+Math.sin(a)*r, rnd()*1.4+0.5]);}
    const edges=[];
    for(let k=0;k<5;k++){const b=Math.floor(rnd()*(n-6)); for(let j=0;j<4;j++){if(pts[b+j]&&pts[b+j+1])edges.push([b+j,b+j+1]);}}
    let rings='';
    if(!dense) for(let r=Math.min(w,h)*0.14; r<Math.min(w,h)*0.46; r+=Math.min(w,h)*0.09) rings+=`M ${cx-r} ${cy} a ${r} ${r} 0 1 0 ${r*2} 0 a ${r} ${r} 0 1 0 ${-r*2} 0`;
    return {pts,edges,rings,cx,cy};
  },[w,h,seed,dense]);
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} style={{display:'block'}}>
      {data.rings && <path d={data.rings} fill="none" stroke="rgba(200,160,100,.16)" strokeWidth=".7"/>}
      {data.edges.map(([a,b],i)=><line key={i} x1={data.pts[a][0]} y1={data.pts[a][1]} x2={data.pts[b][0]} y2={data.pts[b][1]} stroke="rgba(200,160,100,.4)" strokeWidth=".7"/>)}
      {data.pts.map(([x,y,r],i)=><circle key={i} cx={x} cy={y} r={r} fill="#dcba84" opacity={0.5+r/3}/>)}
      <circle cx={data.cx} cy={data.cy} r="2.4" fill="#f0d9a8"/>
    </svg>
  );
}

/* cinematic constellation field: photographic base + canvas particles.
   No SVG graphics. Organic drift, twinkle, soft nebula glow, film grain. */
function ConstellationBackground({src='uploads/matria-constellation-bg.png', dim=0.72}){
  const canvasRef = useRef(null);
  const frameRef = useRef(null);
  const reduce = useMemo(()=>{
    try{return window.matchMedia('(prefers-reduced-motion: reduce)').matches;}catch(e){return false;}
  },[]);

  useEffect(()=>{
    const canvas = canvasRef.current;
    if(!canvas) return;
    const ctx = canvas.getContext('2d');
    const host = canvas.parentElement;
    let w=0, h=0, t0=performance.now();

    function resize(){
      const dpr = Math.min(window.devicePixelRatio||1, 2);
      w = host.clientWidth; h = host.clientHeight;
      canvas.width = Math.round(w*dpr); canvas.height = Math.round(h*dpr);
      canvas.style.width = w+'px'; canvas.style.height = h+'px';
      ctx.setTransform(dpr,0,0,dpr,0,0);
    }
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(host);

    let s=42;
    const rnd=()=>{s=(s*9301+49297)%233280;return s/233280;};

    const stars = Array.from({length: reduce? 48 : 120}, (_,i)=>{
      const depth = rnd();
      return {
        x:rnd(), y:rnd(), r:(0.4+depth*2.2)*(i%9===0?1.6:1),
        base:0.18+depth*0.55, tw: rnd()*Math.PI*2, speed:0.35+rnd()*1.1,
        driftX:(rnd()-.5)*0.00008, driftY:(rnd()-.5)*0.00006, depth
      };
    });

    const arcs = Array.from({length: reduce? 2 : 5}, ()=>{
      const cx=rnd(), cy=rnd(), rad=0.12+rnd()*0.28;
      const a0=rnd()*Math.PI*2, span=0.8+rnd()*1.6;
      return {cx,cy,rad,a0,span,phase:rnd()*Math.PI*2,speed:0.15+rnd()*0.25};
    });

    function drawArc(a, alpha, time){
      ctx.beginPath();
      ctx.arc(a.cx*w, a.cy*h, a.rad*Math.min(w,h), a.a0, a.a0+a.span);
      ctx.strokeStyle=`rgba(224,176,108,${alpha})`;
      ctx.lineWidth=0.7;
      ctx.stroke();
    }

    function tick(now){
      const t = (now-t0)*0.001;
      ctx.clearRect(0,0,w,h);

      // soft nebula pools
      stars.filter((_,i)=>i%17===0).forEach((st,i)=>{
        const pulse = 0.04+Math.sin(t*0.35+st.tw)*0.025;
        const gx = st.x*w, gy = st.y*h, gr = 36+st.depth*48;
        const g = ctx.createRadialGradient(gx,gy,0,gx,gy,gr);
        g.addColorStop(0,`rgba(212,168,98,${pulse})`);
        g.addColorStop(1,'rgba(212,168,98,0)');
        ctx.fillStyle=g; ctx.fillRect(gx-gr,gy-gr,gr*2,gr*2);
      });

      // organic orbital arcs
      arcs.forEach(a=>{
        const alpha = 0.08+Math.sin(t*a.speed+a.phase)*0.05;
        drawArc(a, alpha, t);
      });

      // star field
      stars.forEach(st=>{
        st.x += st.driftX; st.y += st.driftY;
        if(st.x<0) st.x=1; if(st.x>1) st.x=0;
        if(st.y<0) st.y=1; if(st.y>1) st.y=0;
        const tw = 0.55+0.45*Math.sin(t*st.speed+st.tw);
        const alpha = st.base*tw;
        const x=st.x*w, y=st.y*h;
        if(st.r>2.2){
          const g = ctx.createRadialGradient(x,y,0,x,y,st.r*3.5);
          g.addColorStop(0,`rgba(240,210,160,${alpha*0.55})`);
          g.addColorStop(1,'rgba(240,210,160,0)');
          ctx.fillStyle=g; ctx.beginPath(); ctx.arc(x,y,st.r*3.5,0,Math.PI*2); ctx.fill();
        }
        ctx.fillStyle=`rgba(232,196,133,${alpha})`;
        ctx.beginPath(); ctx.arc(x,y,st.r,0,Math.PI*2); ctx.fill();
      });

      if(!reduce) frameRef.current = requestAnimationFrame(tick);
    }

    if(reduce){
      tick(performance.now());
    } else {
      frameRef.current = requestAnimationFrame(tick);
    }
    return ()=>{
      ro.disconnect();
      if(frameRef.current) cancelAnimationFrame(frameRef.current);
    };
  },[src, reduce]);

  return (
    <div className="constellation-bg" aria-hidden="true" style={{'--constellation-dim':dim}}>
      <div className="constellation-bg__photo" style={{backgroundImage:`url(${src})`}}/>
      <canvas ref={canvasRef} className="constellation-bg__canvas"/>
      <div className="constellation-bg__veil"/>
      <div className="constellation-bg__vignette"/>
      <div className="constellation-bg__grain"/>
    </div>
  );
}

function collagePic(id){
  return id ? 'uploads/matria-collage-'+id+'.png' : null;
}

/* Ethereal womb/constellation field for cream sanctuary pages */
function WombField({variant='banner', src='uploads/matria-onboarding-sanctuary.png', kicker, title, copy, children, className=''}){
  const isStrip=variant==='strip';
  return (
    <div className={'womb-field womb-field--'+variant+(className?' '+className:'')} aria-hidden={isStrip||(!title&&!kicker&&!copy&&!children)?true:undefined}>
      <div className="womb-field__photo" style={{backgroundImage:'url('+src+')'}}/>
      <div className="womb-field__veil"/>
      <span className="womb-field__signal"/>
      <div className="womb-field__grain"/>
      {!isStrip&&(title||kicker||copy||children)&&(
        <div className="womb-field__inner">
          {kicker && <div className="womb-field__kicker">{kicker}</div>}
          {title && <div className="womb-field__title">{title}</div>}
          {copy && <div className="womb-field__copy">{copy}</div>}
          {children}
        </div>
      )}
    </div>
  );
}

function LineageCollage({ids=[]}){
  const D=window.MATRIA;
  const people=ids.slice(0,3).map(id=>D.get(id)).filter(n=>n&&n.type==='person');
  if(!people.length) return <Constellation w={130} h={72} seed={11} dense/>;
  return (
    <div className="lineage-collage" aria-hidden="true">
      {people.map(n=>(
        <div key={n.id} className="lineage-collage__cell">
          <CollageFace id={n.id} name={n.title} pic={n.pic}/>
        </div>
      ))}
    </div>
  );
}

function CollageFace({id, name, pic}){
  const [failed,setFailed]=useState(false);
  const src=pic || collagePic(id);
  if(!src || failed){
    return (
      <div className="lineage-collage__fallback">
        <TypeMedallion type="person" size={28}/>
      </div>
    );
  }
  return <img src={src} alt="" onError={()=>setFailed(true)}/>;
}

function CreamRow({title, meta, icon, medallion, onClick, action, className=''}){
  return (
    <div className={'cream-row'+(className?' '+className:'')} onClick={onClick} role={onClick?'button':undefined} tabIndex={onClick?0:undefined}
      onKeyDown={onClick?e=>{(e.key==='Enter'||e.key===' ')&&onClick(e);}:undefined}>
      <div className="cream-row__icon">
        {medallion || (icon && <Icon name={icon} size={18}/>)}
      </div>
      <div className="cream-row__body">
        <div className="cream-row__title">{title}</div>
        {meta && <div className="cream-row__meta">{meta}</div>}
      </div>
      {action || (onClick && <span className="cream-row__chevron"><Icon name="chevron" size={16}/></span>)}
    </div>
  );
}

/* botanical sprig (cream onboarding) */
function Sprig({w=120, h=300, color='#c7a25e', opacity=.8}){
  const leaves=[];
  for(let i=0;i<8;i++){const y=270-i*30; const s=i%2?1:-1;
    leaves.push(<path key={i} d={`M46 ${y} C${46+s*14} ${y-3} ${50+s*24} ${y-12} ${52+s*30} ${y-26}`} />);
    leaves.push(<path key={'b'+i} d={`M46 ${y} C${46+s*16} ${y+1} ${50+s*26} ${y-4} ${54+s*32} ${y-10}`} />);
  }
  return (
    <svg width={w} height={h} viewBox="0 0 90 300" fill="none" stroke={color} strokeWidth="1.1" strokeLinecap="round" style={{opacity}}>
      <path d="M48 298 C48 230 42 150 50 80 C53 50 60 28 70 10"/>
      {leaves}
      <circle cx="70" cy="10" r="2.4" fill={color} stroke="none"/>
    </svg>
  );
}

Object.assign(window, {Icon, SplashGlyph, MATRIA_PILLARS, TypeGlyph, TypeMedallion, ConceptGlyph, StatusBar, TabBar, TABS, Portrait, Constellation, ConstellationBackground, Sprig,
  WombField, LineageCollage, CreamRow, collagePic,
  useState, useEffect, useRef, useMemo, useCallback});
