// TrialNexus website — shared layout components (Babel/JSX)
// Reused by every page. Exposes nav, footer, modal, consent banner,
// tweaks panel, motion primitives, and the brand mark SVG.

// SVG icon system — stroke-based, 24×24, currentColor
const TN_ICONS = {
  narratives: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></>,
  cdisc:       <><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/><line x1="12" y1="2" x2="12" y2="22"/><line x1="2" y1="8.5" x2="22" y2="8.5"/></>,
  audit:       <><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 11 14 15 10"/></>,
  dial:        <><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></>,
  integrations:<><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></>,
  explainable: <><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></>,
  open:        <><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></>,
  clinops:     <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>,
  medical:     <><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></>,
  biostats:    <><line x1="4" y1="4" x2="4" y2="20"/><line x1="4" y1="20" x2="20" y2="20"/><circle cx="8" cy="16" r="1.5" fill="currentColor" stroke="none"/><circle cx="11.5" cy="11.5" r="1.5" fill="currentColor" stroke="none"/><circle cx="15" cy="8" r="1.5" fill="currentColor" stroke="none"/><circle cx="18.5" cy="5.5" r="1.5" fill="currentColor" stroke="none"/><line x1="5.5" y1="18.5" x2="20" y2="4.5" strokeDasharray="2 1.5"/></>,
  regulatory:  <><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1" ry="1"/><polyline points="9,12 11,14 15,10"/></>,
  safety:      <><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></>,
  executive:   <><rect x="2" y="3" width="20" height="18" rx="2"/><path d="M2 9h20"/><path d="M12 9v12"/><polyline points="5.5,17 7.5,14.5 9.5,15.5 11,12.5"/><line x1="14.5" y1="13" x2="18.5" y2="13"/><line x1="14.5" y1="16.5" x2="17" y2="16.5"/></>,
  weekly:      <><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></>,
  ask:         <><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></>,
  provenance:  <><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></>,
  roleaware:   <><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></>,
  queue:       <><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></>,
  econsent:    <><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></>,
  offline:     <><line x1="1" y1="1" x2="23" y2="23"/><path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"/><path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"/><path d="M10.71 5.05A16 16 0 0 1 22.56 9"/><path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/></>,
  sourcedoc:   <><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></>,
  sdv:         <><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></>,
  voice:       <><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></>,
  token:       <><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></>,
  context:     <><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></>,
  compliance:  <><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></>,
  build:       <><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></>,

  // Product brand icons
  core:        <><rect x="2" y="4.5" width="20" height="3.5" rx="1.75"/><rect x="5" y="10.25" width="14" height="3.5" rx="1.75"/><rect x="8" y="16" width="8" height="3.5" rx="1.75"/></>,
  predict:     <><line x1="3" y1="21" x2="21" y2="21" strokeOpacity=".25"/><polyline points="3,17 7,14 10,16 14,10" fill="none" strokeLinejoin="round"/><circle cx="14" cy="10" r="2" fill="currentColor" stroke="none"/><line x1="14" y1="10" x2="20" y2="5" strokeDasharray="2 1.5"/><circle cx="20" cy="5" r="1.75" fill="none"/></>,
  studio:      <><rect x="2" y="3" width="20" height="18" rx="3"/><path d="M2 8.5h20" strokeOpacity=".4"/><circle cx="5.5" cy="5.75" r="1" fill="currentColor" stroke="none"/><circle cx="8.5" cy="5.75" r="1" fill="currentColor" stroke="none"/><circle cx="11.5" cy="5.75" r="1" fill="currentColor" stroke="none"/><polyline points="6,12.5 9.5,15.5 6,18.5" fill="none"/><line x1="12" y1="18.5" x2="16" y2="18.5"/></>,
};

function TnIcon({ name, size = 22, color = 'currentColor', strokeWidth = 1.5, style: extraStyle }) {
  const paths = TN_ICONS[name];
  if (!paths) return null;
  return (
    <svg
      width={size} height={size} viewBox="0 0 24 24"
      fill="none" stroke={color} strokeWidth={strokeWidth}
      strokeLinecap="round" strokeLinejoin="round"
      aria-hidden="true"
      style={extraStyle}
    >
      {paths}
    </svg>
  );
}

function TnMark({ size = 30 }) {
  const id = React.useId();
  return (
    <svg width={size} height={size} viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
      <defs>
        <linearGradient id={id} x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stopColor="#ff6b9d"/>
          <stop offset="100%" stopColor="#12c2e9"/>
        </linearGradient>
      </defs>
      <line x1="16" y1="16" x2="32" y2="32" stroke={`url(#${id})`} strokeWidth="2.5" strokeLinecap="round"/>
      <line x1="48" y1="16" x2="32" y2="32" stroke={`url(#${id})`} strokeWidth="2.5" strokeLinecap="round"/>
      <line x1="16" y1="48" x2="32" y2="32" stroke={`url(#${id})`} strokeWidth="2.5" strokeLinecap="round"/>
      <line x1="48" y1="48" x2="32" y2="32" stroke={`url(#${id})`} strokeWidth="2.5" strokeLinecap="round"/>
      <circle cx="16" cy="16" r="5" fill="#12c2e9"/>
      <circle cx="48" cy="16" r="5" fill="#12c2e9"/>
      <circle cx="16" cy="48" r="5" fill="#ff6b9d"/>
      <circle cx="48" cy="48" r="5" fill="#ff6b9d"/>
      <circle cx="32" cy="32" r="9" fill={`url(#${id})`}/>
    </svg>
  );
}

const NAV_LINKS = [
  { href: 'index.html', label: 'Platform' },
  { href: 'data-management-agent.html', label: 'Core' },
  { href: 'signal.html', label: 'Signal' },
  { href: 'agent-studio.html', label: 'Agent Studio' },
  { href: 'about.html', label: 'About' },
  { label: 'Resources', dropdown: [
    { href: 'resources.html',  label: 'Field Notes',   sub: 'Articles and perspectives' },
    { href: 'pricing.html',    label: 'Pricing',        sub: 'Pilot, production, enterprise' },
    { href: 'security.html',   label: 'Trust Center',   sub: 'Security, compliance, CSV' },
    { href: 'customers.html',  label: 'Customers',      sub: 'Who we work with' },
  ]},
];

function TnNavDropdown({ item, isActive }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, []);
  return (
    <div ref={ref} className={`tn-nav-dd ${open ? 'open' : ''}`} onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
      <button className={`tn-nav-dd-btn ${isActive ? 'active' : ''}`} onClick={() => setOpen(o => !o)} aria-expanded={open}>
        {item.label}
        <svg className="tn-nav-dd-caret" viewBox="0 0 10 6" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M1 1l4 4 4-4"/></svg>
      </button>
      <div className="tn-nav-dd-menu" role="menu">
        {item.dropdown.map(d => (
          <a key={d.href} href={d.href} className="tn-nav-dd-item" role="menuitem">
            <strong>{d.label}</strong>
            <span>{d.sub}</span>
          </a>
        ))}
      </div>
    </div>
  );
}

function TnNav({ onCtaClick, currentPath = '', onDark = false }) {
  const [scrolled, setScrolled] = React.useState(false);
  const [drawer, setDrawer] = React.useState(false);
  React.useEffect(() => {
    const on = () => setScrolled(window.scrollY > 20);
    window.addEventListener('scroll', on, { passive: true });
    on();
    return () => window.removeEventListener('scroll', on);
  }, []);
  const is = (href) => currentPath === href;
  return (
    <>
      <nav className={`tn-nav ${scrolled ? 'scrolled' : ''} ${onDark ? 'on-dark' : ''}`}>
        <a href="index.html" className="tn-logo" onClick={() => window.tnTrack?.('nav_logo_click')}>
          <TnMark size={30}/>
          <span>TrialNexus</span>
        </a>
        <div className="tn-nav-links">
          {NAV_LINKS.map(l =>
            l.dropdown
              ? <TnNavDropdown key={l.label} item={l} isActive={l.dropdown.some(d => is(d.href))}/>
              : <a key={l.href} href={l.href} className={is(l.href) ? 'active' : ''}>{l.label}</a>
          )}
        </div>
        <div className="tn-nav-right">
          <a className="tn-nav-ghost" href="contact.html">Contact</a>
          <button className="tn-nav-cta" onClick={() => { window.tnTrack?.('nav_cta_click'); onCtaClick?.(); }}>
            Book a demo <span className="tn-arrow">→</span>
          </button>
          <button className="tn-menu-btn" aria-label="Menu" onClick={() => setDrawer(true)}>
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
              <line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/>
            </svg>
          </button>
        </div>
      </nav>

      <div className={`tn-drawer-scrim ${drawer ? 'open' : ''}`} onClick={() => setDrawer(false)}/>
      <aside className={`tn-drawer ${drawer ? 'open' : ''}`} aria-hidden={!drawer}>
        <div className="tn-drawer-top">
          <a href="index.html" className="tn-logo"><TnMark size={28}/><span>TrialNexus</span></a>
          <button className="tn-menu-btn" onClick={() => setDrawer(false)} aria-label="Close">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
          </button>
        </div>
        {NAV_LINKS.map(l =>
          l.dropdown
            ? <React.Fragment key={l.label}>
                <span className="tn-drawer-section">{l.label}</span>
                {l.dropdown.map(d => <a key={d.href} href={d.href} className="tn-drawer-sub">{d.label}</a>)}
              </React.Fragment>
            : <a key={l.href} href={l.href}>{l.label}</a>
        )}
        <a href="contact.html">Contact</a>
        <div className="tn-drawer-cta">
          <button className="tn-btn tn-btn-primary" style={{width:'100%'}} onClick={() => { setDrawer(false); onCtaClick?.(); }}>
            Book a demo <span className="tn-arrow">→</span>
          </button>
        </div>
      </aside>
    </>
  );
}

function TnFooter() {
  const [email, setEmail] = React.useState('');
  const [sent, setSent] = React.useState(false);
  return (
    <footer className="tn-footer">
      <div className="tn-footer-grid">
        <div className="tn-footer-brand">
          <a href="index.html" className="tn-logo" style={{color:'#fff'}}><TnMark size={28}/><span>TrialNexus</span></a>
          <p>The AI operating system for clinical trials. CDISC-native. USDM-centric. Audit-ready.</p>
          {!sent ? (
            <form className="tn-footer-news" onSubmit={async e => {
              e.preventDefault();
              window.tnTrack?.('newsletter_signup', { email });
              try {
                await fetch('/api/newsletter', {
                  method: 'POST',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({ email, source: 'footer', page_url: window.location.href, subscribed_at: new Date().toISOString() }),
                });
              } catch (_) {}
              setSent(true);
            }}>
              <input type="email" required placeholder="Work email for updates" value={email} onChange={e => setEmail(e.target.value)}/>
              <button type="submit">Subscribe</button>
            </form>
          ) : (
            <p style={{color:'rgba(255,255,255,.85)',fontSize:13}}>Subscribed. You'll hear from us.</p>
          )}
        </div>
        <div className="tn-footer-col">
          <h4>Platform</h4>
          <a href="index.html">Overview</a>
          <a href="data-management-agent.html">Core</a>
          <a href="signal.html">Signal</a>
          <a href="agent-studio.html">Agent Studio</a>
          <a href="mobile-app.html">Mobile App</a>
          <a href="security.html">Trust Center</a>
          <a href="pricing.html">Pricing</a>
        </div>
        <div className="tn-footer-col">
          <h4>Company</h4>
          <a href="about.html">About</a>
          <a href="customers.html">Customers</a>
          <a href="resources.html">Resources</a>
          <a href="contact.html">Contact</a>
        </div>
        <div className="tn-footer-col">
          <h4>Legal</h4>
          <a href="legal.html#terms">Terms</a>
          <a href="legal.html#privacy">Privacy</a>
          <a href="legal.html#dpa">DPA</a>
        </div>
        <div className="tn-footer-col">
          <h4>Connect</h4>
          <a href="mailto:hello@trialnexus.ai">hello@trialnexus.ai</a>
          <a href="https://www.linkedin.com/company/trialnexus" target="_blank" rel="noreferrer">LinkedIn</a>
          <div className="tn-footer-social" style={{marginTop:14}}>
            <a href="mailto:hello@trialnexus.ai" aria-label="Email"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M22 7l-10 6L2 7"/></svg></a>
            <a href="https://www.linkedin.com/company/trialnexus" target="_blank" rel="noreferrer" aria-label="LinkedIn"><svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.063 2.063 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
          </div>
        </div>
      </div>
      <div className="tn-footer-bot">
        <span>© 2026 TrialNexus, Inc. Built for the people who actually run clinical trials.</span>
        <span><a href="legal.html#terms">Terms</a> · <a href="legal.html#privacy">Privacy</a> · <a href="legal.html#dpa">DPA</a></span>
      </div>
    </footer>
  );
}

function TnPilotModal({ open, onClose }) {
  const [sent, setSent] = React.useState(false);
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);
  React.useEffect(() => { if (open) { setSent(false); setError(null); } }, [open]);
  if (!open) return null;
  return (
    <div className="tn-modal-scrim" onClick={onClose}>
      <div className="tn-modal" onClick={e => e.stopPropagation()} role="dialog" aria-labelledby="pilot-title">
        {!sent ? (
          <>
            <span className="tn-pre-dot"><span className="tn-dot"/>Book a demo</span>
            <h2 id="pilot-title" style={{fontFamily:'var(--tn-font-heading)',fontSize:28,fontWeight:600,letterSpacing:'-.02em',margin:'0 0 8px'}}>
              See your protocol in USDM.
            </h2>
            <p style={{color:'var(--tn-fg-2)',fontSize:14.5,margin:'0 0 24px',lineHeight:1.6}}>
              30-minute working session. No slideware. We start with your CRF and show you exactly what the agents do.
            </p>
            <form className="tn-form" onSubmit={async e => {
              e.preventDefault();
              setSubmitting(true);
              setError(null);
              const data = Object.fromEntries(new FormData(e.target).entries());
              window.tnTrack?.('demo_request_submitted', { company: data.company });
              try {
                const res = await fetch('/api/demo', {
                  method: 'POST',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({
                    ...data,
                    source: 'demo-modal',
                    page: document.title,
                    page_url: window.location.href,
                    submitted_at: new Date().toISOString(),
                  }),
                });
                if (!res.ok) throw new Error('server error');
                setSent(true);
              } catch (_) {
                setError('Something went wrong. Email us at hello@trialnexus.ai');
              } finally {
                setSubmitting(false);
              }
            }}>
              <input name="name" placeholder="Full name" required/>
              <input name="email" type="email" placeholder="Work email" required/>
              <input name="company" placeholder="Company" required/>
              <textarea name="notes" placeholder="Anything we should know? (optional)" rows="2"/>
              {error && <p style={{color:'#f87171',fontSize:13,margin:'0 0 8px'}}>{error}</p>}
              <small>We reply within one business day. No sales reps, just engineers.</small>
              <button type="submit" className="tn-btn tn-btn-primary" style={{marginTop:8,width:'100%'}} disabled={submitting}>
                {submitting ? 'Sending…' : <><span>Book the session</span> <span className="tn-arrow">→</span></>}
              </button>
            </form>
          </>
        ) : (
          <div style={{textAlign:'center',padding:'12px 0'}}>
            <div style={{width:56,height:56,borderRadius:'50%',background:'var(--tn-gradient-primary)',display:'inline-flex',alignItems:'center',justifyContent:'center',color:'#fff',margin:'0 auto 20px'}}>
              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
            </div>
            <h2 style={{fontFamily:'var(--tn-font-heading)',fontSize:24,fontWeight:600,margin:'0 0 8px',letterSpacing:'-.02em'}}>You're in the queue.</h2>
            <p style={{color:'var(--tn-fg-2)',margin:'0 0 24px',fontSize:14.5}}>Expect a reply within one business day. We'll send a calendar link and a short pre-read.</p>
            <button onClick={onClose} className="tn-btn tn-btn-secondary">Close</button>
          </div>
        )}
      </div>
    </div>
  );
}

// Consent banner + PostHog loader
function TnConsentBanner() {
  const [show, setShow] = React.useState(false);
  const getConsent = () => {
    try {
      const cookie = document.cookie.split(';').map(c => c.trim()).find(c => c.startsWith('tn-consent='));
      if (cookie) return cookie.split('=')[1];
      return localStorage.getItem('tn-consent');
    } catch { return null; }
  };
  const setConsent = (val) => {
    try { document.cookie = `tn-consent=${val};max-age=31536000;path=/;SameSite=Lax`; } catch {}
    try { localStorage.setItem('tn-consent', val); } catch {}
  };
  React.useEffect(() => {
    const existing = getConsent();
    if (!existing) setTimeout(() => setShow(true), 900);
    else if (existing === 'accepted') window.tnEnableAnalytics?.();
  }, []);
  if (!show) return null;
  const accept = () => { setConsent('accepted'); window.tnEnableAnalytics?.(); setShow(false); };
  const decline = () => { setConsent('declined'); setShow(false); };
  return (
    <div className={`tn-consent ${show ? 'show' : ''}`}>
      <div style={{flex:1}}>
        <p><strong>We use PostHog for product analytics.</strong></p>
        <p className="sm">No third-party ad trackers. No session replay without consent. <a href="legal.html#privacy" style={{color:'var(--tn-pink)',textDecoration:'underline'}}>Read our privacy policy</a>.</p>
      </div>
      <div className="tn-consent-actions">
        <button className="decline" onClick={decline}>Decline</button>
        <button className="accept" onClick={accept}>Accept</button>
      </div>
    </div>
  );
}

// Tweaks panel — generic; each page passes its own schema
function TnTweaks({ schema, state, setState }) {
  const [ready, setReady] = React.useState(false);
  const [open, setOpen] = React.useState(false);
  React.useEffect(() => {
    const onMsg = e => {
      if (e.data?.type === '__activate_edit_mode') setOpen(true);
      if (e.data?.type === '__deactivate_edit_mode') setOpen(false);
    };
    window.addEventListener('message', onMsg);
    setReady(true);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', onMsg);
  }, []);
  if (!ready || !open || !schema) return null;
  return (
    <div className="tn-tweaks show">
      <div className="tn-tweaks-head">
        <strong>Tweaks</strong>
        <button onClick={() => setOpen(false)} aria-label="Close" style={{color:'var(--tn-fg-3)',fontSize:18}}>×</button>
      </div>
      <div className="tn-tweaks-body">
        {schema.map(field => {
          const val = state[field.key];
          if (field.type === 'segmented') {
            return (
              <div key={field.key} className="tn-tweaks-field">
                <label>{field.label}</label>
                <div className="tn-tweaks-seg">
                  {field.options.map(o => (
                    <button key={o.value} className={val === o.value ? 'on' : ''}
                      onClick={() => setState(s => ({ ...s, [field.key]: o.value }))}>{o.label}</button>
                  ))}
                </div>
              </div>
            );
          }
          if (field.type === 'select') {
            return (
              <div key={field.key} className="tn-tweaks-field">
                <label>{field.label}</label>
                <select value={val} onChange={e => setState(s => ({ ...s, [field.key]: e.target.value }))}>
                  {field.options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
                </select>
              </div>
            );
          }
          if (field.type === 'text') {
            return (
              <div key={field.key} className="tn-tweaks-field">
                <label>{field.label}</label>
                <input type="text" value={val} onChange={e => setState(s => ({ ...s, [field.key]: e.target.value }))}/>
              </div>
            );
          }
          return null;
        })}
      </div>
    </div>
  );
}

// Reveal observer — call once per page
function useReveal(deps = []) {
  React.useEffect(() => {
    const obs = new IntersectionObserver(es => es.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); obs.unobserve(e.target); } }), { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
    document.querySelectorAll('.tn-reveal:not(.visible)').forEach(el => obs.observe(el));
    return () => obs.disconnect();
  }, deps);
}

// Simple orbs + constellation canvas backdrop
// subtle=true reduces opacity for interior page headers
function TnOrbs({ subtle = false }) {
  return <div className="tn-hero-orbs" aria-hidden="true" style={subtle ? { opacity: 0.3 } : undefined}/>;
}

// Tour lightbox — renders children in a centered modal overlay
function TnTourModal({ open, onClose, children }) {
  React.useEffect(() => {
    if (!open) return;
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div
      style={{position:'fixed',inset:0,zIndex:2000,display:'flex',alignItems:'center',justifyContent:'center',padding:'24px 16px'}}
      onClick={onClose}
    >
      <div style={{position:'absolute',inset:0,background:'rgba(10,10,26,.82)',backdropFilter:'blur(8px)'}}/>
      <div
        style={{position:'relative',width:'100%',maxWidth:920,background:'var(--tn-bg-dark)',borderRadius:24,border:'1px solid rgba(255,255,255,.1)',overflow:'hidden',boxShadow:'0 40px 100px rgba(0,0,0,.5)'}}
        onClick={e => e.stopPropagation()}
        role="dialog"
        aria-label="Platform tour"
      >
        <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',padding:'16px 20px',borderBottom:'1px solid rgba(255,255,255,.08)'}}>
          <span style={{fontSize:11,fontWeight:600,letterSpacing:'.12em',textTransform:'uppercase',color:'rgba(255,255,255,.5)'}}>Platform tour · Protocol → Submission</span>
          <button
            onClick={onClose}
            style={{width:32,height:32,borderRadius:'50%',background:'rgba(255,255,255,.06)',border:'1px solid rgba(255,255,255,.1)',color:'rgba(255,255,255,.7)',fontSize:18,display:'flex',alignItems:'center',justifyContent:'center',cursor:'pointer',lineHeight:1}}
            aria-label="Close tour"
          >×</button>
        </div>
        <div style={{padding:'24px 20px 20px'}}>
          {children}
        </div>
      </div>
    </div>
  );
}

function TnConstellation() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const canvas = ref.current; if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let raf, nodes = [];
    const resize = () => {
      const dpr = window.devicePixelRatio || 1;
      const r = canvas.getBoundingClientRect();
      canvas.width = r.width * dpr; canvas.height = r.height * dpr;
      ctx.scale(dpr, dpr);
    };
    const MARGIN = 60, DIST = 120, DIST2 = DIST * DIST;
    const seed = () => {
      const r = canvas.getBoundingClientRect();
      nodes = Array.from({ length: 38 }, () => ({
        x: MARGIN + Math.random() * (r.width - MARGIN * 2),
        y: MARGIN + Math.random() * (r.height - MARGIN * 2),
        vx: (Math.random() - .5) * .12, vy: (Math.random() - .5) * .12,
        pink: Math.random() < .5,
      }));
    };
    const loop = () => {
      const r = canvas.getBoundingClientRect();
      ctx.clearRect(0, 0, r.width, r.height);
      for (const n of nodes) {
        n.x += n.vx; n.y += n.vy;
        if (n.x < MARGIN || n.x > r.width - MARGIN) n.vx *= -1;
        if (n.y < MARGIN || n.y > r.height - MARGIN) n.vy *= -1;
      }
      const FADE = 300;
      const edgeFade = (n) => Math.min(n.x / FADE, (r.width - n.x) / FADE, n.y / FADE, (r.height - n.y) / FADE, 1);
      ctx.lineWidth = 1;
      for (let i = 0; i < nodes.length; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
          const a = nodes[i], b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y, d2 = dx*dx + dy*dy;
          if (d2 < DIST2 && d2 > 9) { // d2>9 prevents degenerate gradient when nodes overlap
            const fade = Math.min(edgeFade(a), edgeFade(b));
            ctx.globalAlpha = (1 - d2 / DIST2) * 0.5 * fade;
            const g = ctx.createLinearGradient(a.x, a.y, b.x, b.y);
            g.addColorStop(0, 'rgb(255,107,157)');
            g.addColorStop(1, 'rgb(18,194,233)');
            ctx.strokeStyle = g;
            ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
          }
        }
      }
      ctx.globalAlpha = 1;
      for (const n of nodes) {
        ctx.globalAlpha = edgeFade(n) * 0.6;
        ctx.fillStyle = n.pink ? 'rgb(255,107,157)' : 'rgb(18,194,233)';
        ctx.beginPath(); ctx.arc(n.x, n.y, 1.5, 0, Math.PI*2); ctx.fill();
      }
      raf = requestAnimationFrame(loop);
    };
    resize(); seed(); loop();
    const onResize = () => { resize(); seed(); };
    window.addEventListener('resize', onResize);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', onResize); };
  }, []);
  return <canvas ref={ref} className="tn-constellation" aria-hidden="true"/>;
}

// Page shell — nav + main + footer + modal + consent + tweaks
function TnPage({ path, onDarkNav = false, tweakSchema, tweakInitial, children }) {
  const [pilot, setPilot] = React.useState(false);
  const [tweaks, setTweaks] = React.useState(() => {
    try { const saved = JSON.parse(localStorage.getItem(`tn-tweaks-${path}`) || 'null'); return { ...(tweakInitial||{}), ...(saved||{}) }; }
    catch { return tweakInitial || {}; }
  });
  React.useEffect(() => {
    if (tweakInitial) localStorage.setItem(`tn-tweaks-${path}`, JSON.stringify(tweaks));
  }, [tweaks, path]);
  useReveal([tweaks]);
  return (
    <>
      <TnNav onCtaClick={() => setPilot(true)} currentPath={path} onDark={onDarkNav}/>
      {typeof children === 'function' ? children({ tweaks, openPilot: () => setPilot(true) }) : children}
      <TnFooter/>
      <TnPilotModal open={pilot} onClose={() => setPilot(false)}/>
      <TnConsentBanner/>
      {tweakSchema && <TnTweaks schema={tweakSchema} state={tweaks} setState={setTweaks}/>}
    </>
  );
}

Object.assign(window, {
  TnIcon, TnMark, TnNav, TnFooter, TnPilotModal, TnConsentBanner, TnTweaks,
  TnPage, TnOrbs, TnConstellation, TnTourModal, useReveal, NAV_LINKS,
});
