
// ===== src/data.jsx =====
// Product, program, faculty, etc. data for SBDA Research

const PRODUCTS = [
{
  id: 'virdb',
  name: 'VIRdb',
  version: 'v1.0',
  category: 'Database',
  subtitle: 'Vitiligo Interactive Resource',
  color: 'var(--c-teal)',
  glyph: 'V',
  short: 'A comprehensive database for interactive analysis of genes and proteins involved in the pathogenesis of vitiligo.',
  long: 'VIRdb is a curated and queryable resource that consolidates over a decade of vitiligo-related molecular research. It enables researchers to interactively explore gene and protein networks, biomarkers, and pathways implicated in vitiligo pathogenesis — accelerating hypothesis generation and translational discovery.',
  tags: ['Genomics', 'Proteomics', 'Vitiligo', 'Pathways'],
  stats: [
  { v: '4,800+', l: 'Curated entries' },
  { v: '120+', l: 'Pathways' },
  { v: 'Open', l: 'Access' }],

  features: [
  'Searchable catalogue of vitiligo-related genes and proteins',
  'Visualisation of interaction networks and signaling pathways',
  'Cross-references to UniProt, KEGG and PubMed',
  'Downloadable datasets for reproducible research']

},
{
  id: 'virdb2',
  name: 'VIRdb 2.0',
  version: 'v2.0',
  category: 'Database',
  subtitle: 'Comorbidity Network Resource',
  color: 'var(--c-pink)',
  glyph: 'V²',
  short: 'Interactive analysis of comorbidity conditions associated with vitiligo pathogenesis using a co-expression network-based approach.',
  long: 'The next-generation expansion of VIRdb, extending the resource into the realm of comorbidities. VIRdb 2.0 integrates co-expression networks to model how vitiligo shares molecular signatures with associated disorders — from autoimmune conditions to metabolic syndromes — empowering systems-level inquiry.',
  tags: ['Co-expression', 'Networks', 'Comorbidity', 'Systems Biology'],
  stats: [
  { v: '11,200+', l: 'Network edges' },
  { v: '34', l: 'Comorbidities' },
  { v: '2024', l: 'Latest release' }],

  features: [
  'Co-expression network exploration across comorbid conditions',
  'Cross-disease module detection and visualization',
  'Interactive heatmaps, force-directed graphs, and Sankey views',
  'Programmatic API access for batch queries']

},
{
  id: 'net2align',
  name: 'Net2Align',
  version: 'Tool',
  category: 'Algorithm',
  subtitle: 'Pairwise Network Alignment',
  color: 'var(--c-blue)',
  glyph: 'N',
  short: 'An algorithm for pairwise global alignment of biological networks.',
  long: 'Net2Align is an alignment algorithm designed for the global comparison of two biological networks. It identifies conserved structural and functional correspondences across species or conditions, helping researchers transfer knowledge between model organisms and understand evolutionary conservation of molecular machinery.',
  tags: ['Alignment', 'Graph theory', 'Cross-species', 'CLI'],
  stats: [
  { v: '8x', l: 'Faster than baseline' },
  { v: 'CLI', l: 'Reproducible runs' },
  { v: 'MIT', l: 'License' }],

  features: [
  'Global pairwise network alignment with tunable scoring',
  'Supports weighted, directed, and multigraph inputs',
  'Outputs alignment matrices and conserved subgraphs',
  'Python bindings + command-line interface']

},
{
  id: 'plurimet',
  name: 'PluriMetNet',
  version: 'Model',
  category: 'Model',
  subtitle: 'hESC Metabolic Dynamics',
  color: 'var(--c-violet)',
  glyph: 'P',
  short: 'A dynamic electronic model decrypting the metabolic variations in human embryonic stem cells at fluctuating oxygen concentrations.',
  long: 'PluriMetNet is a dynamic computational model that simulates the metabolic rewiring of human embryonic stem cells (hESCs) under varying oxygen tensions. By linking glycolysis, oxidative phosphorylation, and pluripotency markers, it offers a quantitative lens on how environment shapes stem-cell identity.',
  tags: ['Metabolism', 'hESC', 'Hypoxia', 'Dynamic model'],
  stats: [
  { v: '180+', l: 'Reactions modelled' },
  { v: '24h', l: 'Simulated window' },
  { v: 'Open', l: 'Source code' }],

  features: [
  'Time-resolved metabolic flux predictions',
  'Oxygen-tension parameter sweeps with phase plots',
  'Coupled to pluripotency gene expression signals',
  'Reproducible Jupyter notebooks for every figure']

},
{
  id: 'tfis',
  name: 'TFIS',
  version: 'Tool',
  category: 'Tool',
  subtitle: 'Transcription Factor Information System',
  color: 'var(--c-yellow)',
  glyph: 'T',
  short: 'Transcription Factor Information System — a tool for detection of transcription factor binding sites.',
  long: 'TFIS is a sequence-analysis tool for the identification of transcription factor binding sites (TFBS) across promoter regions. With customizable PWM libraries and statistical scoring, it streamlines the discovery of regulatory motifs that orchestrate gene expression programs.',
  tags: ['TFBS', 'Regulation', 'PWM', 'Motifs'],
  stats: [
  { v: '650+', l: 'PWM library' },
  { v: 'p<10⁻⁵', l: 'Default cutoff' },
  { v: 'Web + CLI', l: 'Interfaces' }],

  features: [
  'PWM-based scanning with thresholded scoring',
  'Cross-species comparisons via aligned promoters',
  'Batch upload and downloadable BED/CSV exports',
  'Interactive genome-browser style visualisation']

},
{
  id: 'hepnet',
  name: 'HEPNet',
  version: 'Model',
  category: 'Model',
  subtitle: 'Human Energy Pool Network',
  color: 'var(--c-lime)',
  glyph: 'H',
  short: 'A knowledge-base model of the Human Energy Pool Network for predicting the energy availability status of an individual.',
  long: 'HEPNet is a knowledge-base model that integrates physiological, biochemical, and behavioral signals into a unified network describing human energy flux. By predicting energy availability status, it lays groundwork for personalised diagnostics in metabolic and lifestyle disorders.',
  tags: ['Energy metabolism', 'Knowledge base', 'Predictive', 'Personalised'],
  stats: [
  { v: '2,400+', l: 'Curated relations' },
  { v: '6', l: 'Organ systems' },
  { v: 'β', l: 'Release stage' }],

  features: [
  'Knowledge graph linking nutrients, hormones, and organ systems',
  'Predicts individualised energy availability status',
  'API integration with wearable and clinical datasets',
  'Interpretable, evidence-linked predictions']

}];


const PROGRAMS = [
{
  id: 'p1',
  cat: 'Internship',
  title: 'Systems Biology Internship',
  duration: '8 weeks',
  desc: 'Hands-on mentorship in network analysis, omics data wrangling, and reproducible research practice. Cohort-based with weekly journal clubs.',
  price: 'Stipend-based',
  seats: '12 seats'
},
{
  id: 'p2',
  cat: 'Workshop',
  title: 'Bioinformatics Bootcamp',
  duration: '4 weekends',
  desc: 'From Linux fundamentals to RNA-seq pipelines. Live coding, real datasets, certificate of completion.',
  price: '₹14,500',
  seats: '24 seats'
},
{
  id: 'p3',
  cat: 'Course',
  title: 'Network Biology Masterclass',
  duration: '12 weeks',
  desc: 'Advanced graph algorithms applied to biological networks — pathway enrichment, alignment, and dynamics.',
  price: '₹32,000',
  seats: '30 seats'
},
{
  id: 'p4',
  cat: 'Fellowship',
  title: 'Research Fellowship',
  duration: '6–12 months',
  desc: 'Funded research positions for graduates interested in pursuing publication-grade work in systems biology.',
  price: 'Funded',
  seats: '4 seats'
},
{
  id: 'p5',
  cat: 'Course',
  title: 'ML for Life Sciences',
  duration: '10 weeks',
  desc: 'Practical machine learning curriculum tailored for biologists — feature engineering, model interpretability, ethics.',
  price: '₹28,000',
  seats: '20 seats'
},
{
  id: 'p6',
  cat: 'Workshop',
  title: 'Scientific Writing Lab',
  duration: '2 weekends',
  desc: 'Crafting a publishable manuscript: structure, figures, and the art of the persuasive abstract.',
  price: '₹6,500',
  seats: '40 seats'
}];


const FACULTY = [
{ id: 'f1', name: 'Dr. A. Mehrotra', role: 'Principal Investigator', tags: ['Systems Biology', 'Vitiligo'], glyph: 'AM' },
{ id: 'f2', name: 'Dr. R. Kapoor', role: 'Senior Researcher', tags: ['Networks', 'Algorithms'], glyph: 'RK' },
{ id: 'f3', name: 'Dr. S. Iyer', role: 'Computational Biologist', tags: ['Metabolism', 'hESC'], glyph: 'SI' },
{ id: 'f4', name: 'Dr. M. Banerjee', role: 'Bioinformatics Lead', tags: ['TFBS', 'Regulation'], glyph: 'MB' }];


const TESTIMONIALS = [
{
  quote: 'My internship at SBDA changed the way I think about biology. I went in writing scripts; I came out designing studies.',
  name: 'Anika Verma',
  role: 'MSc → PhD candidate, IISc',
  av: 'AV'
},
{
  quote: 'VIRdb 2.0 has become a regular stop in our comorbidity analyses. The network views are exactly the lens we needed.',
  name: 'Dr. P. Krishnan',
  role: 'Clinical Researcher, AIIMS',
  av: 'PK'
},
{
  quote: 'The Net2Align workshop replaced two weeks of trial and error with two days of clarity. Thoughtful, rigorous teaching.',
  name: 'Rishi Datta',
  role: 'Senior Engineer, Strand Life Sci.',
  av: 'RD'
},
{
  quote: 'A rare lab that takes mentorship seriously. I left with a publication and, more importantly, a research compass.',
  name: 'Tanvi Rao',
  role: 'Fellow, 2024 cohort',
  av: 'TR'
}];


const POSTS = [
{
  id: 'b1',
  cat: 'Research',
  date: 'May 02, 2026',
  title: 'Comorbidity networks: when shared genes mean shared destinies',
  excerpt: 'A look at how VIRdb 2.0 surfaces molecular crosstalk between vitiligo and adjacent autoimmune conditions — and what it implies for therapy.',
  color: 'var(--c-pink)',
  featured: true
},
{
  id: 'b2',
  cat: 'Tutorial',
  date: 'Apr 18, 2026',
  title: 'Five rules for aligning biological networks well',
  excerpt: 'Pragmatic guidance from the Net2Align team on scoring, normalisation, and validation.',
  color: 'var(--c-blue)'
},
{
  id: 'b3',
  cat: 'Lab Notes',
  date: 'Apr 04, 2026',
  title: 'Hypoxia, glycolysis, and pluripotency — modelling the trio',
  excerpt: 'A walkthrough of PluriMetNet’s parameter sweep methodology and why oxygen still matters.',
  color: 'var(--c-violet)'
}];


const STATS = [
{ v: 142, suffix: '+', l: 'Peer-reviewed papers' },
{ v: 6, suffix: '', l: 'Hero products shipped' },
{ v: 2400, suffix: '+', l: 'Researchers served' },
{ v: 18, suffix: '', l: 'Years of inquiry' }];


Object.assign(window, { PRODUCTS, PROGRAMS, FACULTY, TESTIMONIALS, POSTS, STATS });


// ===== src/atoms.jsx =====
// Small atoms: icons, scroll spy, counter, etc.

const { useState, useEffect, useRef, useCallback } = React;

// Minimal stroke icons — original line set
function Icon({ name, size = 16, stroke = 1.6 }) {
  const s = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (name) {
    case 'arrow-right':return <svg {...s}><path d="M5 12h14" /><path d="M13 5l7 7-7 7" /></svg>;
    case 'arrow-up-right':return <svg {...s}><path d="M7 17L17 7" /><path d="M8 7h9v9" /></svg>;
    case 'arrow-left':return <svg {...s}><path d="M19 12H5" /><path d="M11 5l-7 7 7 7" /></svg>;
    case 'close':return <svg {...s}><path d="M6 6l12 12" /><path d="M18 6L6 18" /></svg>;
    case 'sun':return <svg {...s}><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5L19 19M5 19l1.5-1.5M17.5 6.5L19 5" /></svg>;
    case 'moon':return <svg {...s}><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" /></svg>;
    case 'user':return <svg {...s}><circle cx="12" cy="8" r="4" /><path d="M4 21c0-4 4-7 8-7s8 3 8 7" /></svg>;
    case 'mail':return <svg {...s}><rect x="3" y="5" width="18" height="14" rx="2" /><path d="M3 7l9 6 9-6" /></svg>;
    case 'phone':return <svg {...s}><path d="M22 16.92V21a1 1 0 0 1-1.1 1 19 19 0 0 1-8.3-3 19 19 0 0 1-6-6 19 19 0 0 1-3-8.3A1 1 0 0 1 4.6 3H8.5a1 1 0 0 1 1 .75l1 4a1 1 0 0 1-.3 1L8.5 10.5a16 16 0 0 0 5 5l1.75-1.7a1 1 0 0 1 1-.3l4 1a1 1 0 0 1 .75 1z" /></svg>;
    case 'pin':return <svg {...s}><path d="M12 22s7-7 7-13a7 7 0 0 0-14 0c0 6 7 13 7 13z" /><circle cx="12" cy="9" r="2.5" /></svg>;
    case 'check':return <svg {...s}><path d="M5 13l4 4L19 7" /></svg>;
    case 'home':return <svg {...s}><path d="M3 11l9-8 9 8" /><path d="M5 10v10h14V10" /></svg>;
    case 'grid':return <svg {...s}><rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect x="3" y="14" width="7" height="7" /><rect x="14" y="14" width="7" height="7" /></svg>;
    case 'book':return <svg {...s}><path d="M4 4h11a4 4 0 0 1 4 4v12H8a4 4 0 0 1-4-4z" /><path d="M4 16a4 4 0 0 1 4-4h11" /></svg>;
    case 'spark':return <svg {...s}><path d="M12 3v6" /><path d="M12 15v6" /><path d="M3 12h6" /><path d="M15 12h6" /></svg>;
    case 'logout':return <svg {...s}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><path d="M16 17l5-5-5-5" /><path d="M21 12H9" /></svg>;
    default:return null;
  }
}

// Animated counter (intersection observer)
function Counter({ to, suffix = '', duration = 1400 }) {
  const ref = useRef(null);
  const [val, setVal] = useState(0);
  const fired = useRef(false);
  useEffect(() => {
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !fired.current) {
          fired.current = true;
          const start = performance.now();
          const tick = (now) => {
            const t = Math.min(1, (now - start) / duration);
            const eased = 1 - Math.pow(1 - t, 3);
            setVal(Math.round(eased * to));
            if (t < 1) requestAnimationFrame(tick);
          };
          requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.25 });
    if (ref.current) io.observe(ref.current);
    return () => io.disconnect();
  }, [to, duration]);
  return <span ref={ref}>{val.toLocaleString()}{suffix}</span>;
}

// Scroll lock for modal
function useBodyLock(active) {
  useEffect(() => {
    if (!active) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => {document.body.style.overflow = prev;};
  }, [active]);
}

// Theme controller
function applyTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
}

Object.assign(window, { Icon, Counter, useBodyLock, applyTheme });


// ===== src/nav-hero.jsx =====
// Nav + Hero

const { useState: useStateNH, useEffect: useEffectNH } = React;

function Nav({ onLogin, onDashboard, isAuthed, theme, onToggleTheme, onNavigate, view }) {
  const [scrolled, setScrolled] = useStateNH(false);
  const [menuOpen, setMenuOpen] = useStateNH(false);
  useEffectNH(() => {
    const onScroll = () => setScrolled(window.scrollY > 16);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  const links = [
  ['Products', '#products'],
  ['About', '#about'],
  ['Programs', '#programs'],
  ['Faculty', '#faculty'],
  ['Resources', '#blog'],
  ['Contact', '#contact']];

  const closeMenu = () => setMenuOpen(false);

  return (
    <React.Fragment>
    <header className={`nav ${scrolled ? 'scrolled' : ''}`}>
      <div className="container nav-row">
        <a href="#top" className="brand" onClick={(e) => {e.preventDefault();onNavigate('home');closeMenu();}}>
          <span className="brand-mark">S</span>
          <span>SBDA<span style={{ color: 'var(--muted)', fontWeight: 400, marginLeft: 6 }}>Research</span></span>
        </a>
        {view === 'home' &&
        <nav className="nav-links">
            {links.map(([label, href]) =>
          <a key={href} href={href}>{label}</a>
          )}
          </nav>
        }
        <div className="nav-actions">
          <button className="nav-icon-btn" onClick={onToggleTheme} aria-label="Toggle theme">
            <Icon name={theme === 'dark' ? 'sun' : 'moon'} size={16} />
          </button>
          {view === 'home' &&
            <button className="nav-icon-btn nav-hamburger" onClick={() => setMenuOpen(v => !v)} aria-label="Menu">
              {menuOpen
                ? <Icon name="close" size={16} />
                : <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect y="2" width="16" height="2" rx="1" fill="currentColor"/><rect y="7" width="16" height="2" rx="1" fill="currentColor"/><rect y="12" width="16" height="2" rx="1" fill="currentColor"/></svg>
              }
            </button>
          }
          {isAuthed ?
          <button className="btn btn-ghost btn-sm" onClick={onDashboard}>
              <Icon name="grid" size={14} /> My dashboard
            </button> :

          <button className="btn btn-ghost btn-sm" onClick={onLogin}>
              <Icon name="user" size={14} /> Log in
            </button>
          }
          <a href="#contact" className="btn btn-primary btn-sm">
            <span>Get in touch</span> <Icon name="arrow-right" size={14} />
          </a>
        </div>
      </div>
    </header>
    {menuOpen && view === 'home' && (
      <div className="mobile-menu">
        {links.map(([label, href]) =>
          <a key={href} href={href} onClick={closeMenu}>{label}</a>
        )}
      </div>
    )}
    {view === 'home' && (
      <nav className="mobile-bottom-nav">
        <a href="#top" onClick={(e) => { e.preventDefault(); window.scrollTo({ top: 0, behavior: 'smooth' }); }}>
          <span className="mbn-icon">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
          </span>
          Home
        </a>
        <a href="#products">
          <span className="mbn-icon">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="7" height="7"/><rect x="15" y="3" width="7" height="7"/><rect x="15" y="14" width="7" height="7"/><rect x="2" y="14" width="7" height="7"/></svg>
          </span>
          Products
        </a>
        <a href="#programs">
          <span className="mbn-icon">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c3 3 9 3 12 0v-5"/></svg>
          </span>
          Programs
        </a>
        <a
          href="#profile"
          onClick={(e) => {
            e.preventDefault();
            if (isAuthed) { onDashboard && onDashboard(); }
            else { onLogin && onLogin(); }
          }}
        >
          <span className="mbn-icon">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
          </span>
          {isAuthed ? 'Dashboard' : 'Profile'}
        </a>
        <a href="#contact">
          <span className="mbn-icon">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
          </span>
          Contact
        </a>
      </nav>
    )}
    </React.Fragment>);

}

function HeroViz() {
  // Render a network of dots + lines + 3 floating tags
  const nodes = [
  { x: 50, y: 50, r: 26, c: 'var(--accent)' },
  { x: 18, y: 22, r: 9, c: 'var(--c-teal)' },
  { x: 82, y: 18, r: 7, c: 'var(--c-yellow)' },
  { x: 90, y: 60, r: 11, c: 'var(--c-pink)' },
  { x: 70, y: 88, r: 8, c: 'var(--c-blue)' },
  { x: 22, y: 82, r: 10, c: 'var(--c-lime)' },
  { x: 8, y: 55, r: 6, c: 'var(--c-violet)' },
  { x: 38, y: 12, r: 5, c: 'var(--ink-2)' },
  { x: 62, y: 38, r: 5, c: 'var(--ink-2)' },
  { x: 40, y: 70, r: 5, c: 'var(--ink-2)' },
  { x: 78, y: 78, r: 4, c: 'var(--ink-2)' },
  { x: 30, y: 40, r: 4, c: 'var(--ink-2)' }];

  const edges = [
  [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6],
  [1, 7], [2, 7], [2, 8], [3, 8], [4, 9], [5, 9],
  [5, 10], [4, 10], [6, 11], [1, 11], [0, 7], [0, 8], [0, 9], [0, 10], [0, 11]];

  return (
    <div className="hero-viz">
      <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
        <defs>
          <radialGradient id="coreGlow" cx="50%" cy="50%" r="50%">
            <stop offset="0%" stopColor="rgba(255,107,53,0.4)" />
            <stop offset="100%" stopColor="rgba(255,107,53,0)" />
          </radialGradient>
        </defs>
        <circle cx="50" cy="50" r="44" fill="url(#coreGlow)" />
        <circle cx="50" cy="50" r="38" fill="none" stroke="var(--line)" strokeWidth="0.2" strokeDasharray="0.5 0.8" />
        <circle cx="50" cy="50" r="28" fill="none" stroke="var(--line)" strokeWidth="0.2" />
        {edges.map(([a, b], i) => {
          const A = nodes[a],B = nodes[b];
          return <line key={i} x1={A.x} y1={A.y} x2={B.x} y2={B.y} stroke="var(--line-2)" strokeWidth="0.25" />;
        })}
        {nodes.map((n, i) =>
        <g key={i}>
            {i === 0 &&
          <circle cx={n.x} cy={n.y} r={n.r + 4} fill="none" stroke={n.c} strokeWidth="0.3" opacity="0.4">
                <animate attributeName="r" values={`${n.r + 2};${n.r + 6};${n.r + 2}`} dur="3.2s" repeatCount="indefinite" />
                <animate attributeName="opacity" values="0.5;0.1;0.5" dur="3.2s" repeatCount="indefinite" />
              </circle>
          }
            <circle cx={n.x} cy={n.y} r={n.r / 6} fill={n.c} opacity={i === 0 ? 1 : 0.9}>
              {i > 0 && i < 7 && <animate attributeName="opacity" values="0.4;1;0.4" dur={`${2 + i * 0.3}s`} repeatCount="indefinite" />}
            </circle>
          </g>
        )}
        <text x="50" y="51" textAnchor="middle" fontFamily="JetBrains Mono" fontSize="2.3" fill="#fff" fontWeight="600">SBDA</text>
        <text x="50" y="55" textAnchor="middle" fontFamily="JetBrains Mono" fontSize="1.4" fill="rgba(255,255,255,0.7)" letterSpacing="0.05em">CORE</text>
      </svg>
      <div className="viz-tag" style={{ top: '8%', left: '-2%' }}>
        <span className="d" style={{ background: 'var(--c-teal)' }}></span> NETWORK · 11,200 edges
      </div>
      <div className="viz-tag" style={{ top: '46%', right: '-6%' }}>
        <span className="d" style={{ background: 'var(--c-pink)' }}></span> VIRdb · 4,800 genes
      </div>
      <div className="viz-tag" style={{ bottom: '6%', left: '12%' }}>
        <span className="d" style={{ background: 'var(--c-lime)' }}></span> HEPNet · live
      </div>
    </div>);

}

function Hero() {
  return (
    <section className="hero" id="top">
      <div className="glow a"></div>
      <div className="glow b"></div>
      <div className="container hero-grid">
        <div>
          <span className="eyebrow">Systems Biology · Data Analytics</span>
          <h1 className="h1 hero-title" style={{ marginTop: 28 }}>
            Where <span className="grad">biology</span><br />
            meets <em>data.</em>
          </h1>
          <p className="lede" style={{ marginTop: 28 }}>
            SBDA Research builds open scientific instruments — databases, algorithms, and models — that let researchers see disease, metabolism, and regulation as the connected systems they truly are.
          </p>
          <div className="hero-cta">
            <a href="#products" className="btn btn-primary">
              Explore products <Icon name="arrow-right" size={14} />
            </a>
            <a href="#programs" className="btn btn-ghost">
              Join a program
            </a>
          </div>
          <div className="hero-meta">
            <div>
              <div className="num"><Counter to={6} /></div>
              <div className="lbl">Hero products</div>
            </div>
            <div>
              <div className="num"><Counter to={142} suffix="+" /></div>
              <div className="lbl">Publications</div>
            </div>
            <div>
              <div className="num"><Counter to={2400} suffix="+" /></div>
              <div className="lbl">Researchers served</div>
            </div>
          </div>
        </div>
        <HeroViz />
      </div>
    </section>);

}

function Strip() {
  const items = ['VIRdb', 'Net2Align', 'PluriMetNet', 'TFIS', 'HEPNet', 'VIRdb 2.0', 'Open Science', 'Reproducible by default'];
  const doubled = [...items, ...items];
  return (
    <div className="strip">
      <div className="strip-track">
        {doubled.map((t, i) => <span key={i} className="strip-item">{t}</span>)}
      </div>
    </div>);

}

Object.assign(window, { Nav, Hero, Strip });


// ===== src/products.jsx =====
// Products carousel + modal

const { useState: useStateP, useRef: useRefP, useEffect: useEffectP } = React;

function ProductCard({ p, onOpen }) {
  return (
    <div className="product-card" onClick={() => onOpen(p)}>
      <div className="pc-head">
        <div className="pc-glyph" style={{ background: p.color }}>{p.glyph}</div>
        <span className="pc-ver">{p.version}</span>
      </div>
      <div>
        <div className="pc-sub">{p.subtitle}</div>
        <h3>{p.name}</h3>
      </div>
      <div className="pc-tags">
        {p.tags.slice(0, 3).map((t) => <span key={t} className="pc-tag">{t}</span>)}
      </div>
      <p className="pc-desc">{p.short}</p>
      <div className="pc-foot">
        <span className="pc-stat"><b>{p.stats[0].v}</b> · {p.stats[0].l}</span>
        <span className="pc-arrow"><Icon name="arrow-up-right" size={14} /></span>
      </div>
    </div>);

}

function ProductModal({ p, onClose, onEnquire }) {
  useBodyLock(!!p);
  useEffectP(() => {
    if (!p) return;
    const onKey = (e) => {if (e.key === 'Escape') onClose();};
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [p, onClose]);
  if (!p) return null;
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div className="modal-glyph" style={{ background: p.color }}>{p.glyph}</div>
          <button className="modal-close" onClick={onClose} aria-label="Close">
            <Icon name="close" size={14} />
          </button>
        </div>
        <div className="modal-body">
          <div className="pc-sub">{p.subtitle} · {p.version}</div>
          <h3>{p.name}</h3>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 12 }}>
            {p.tags.map((t) => <span key={t} className="pc-tag">{t}</span>)}
          </div>
          <p style={{ color: 'var(--ink-2)', fontSize: 15, lineHeight: 1.6, marginTop: 20 }}>{p.long}</p>
          <div className="modal-stats">
            {p.stats.map((s, i) =>
            <div key={i} className="modal-stat">
                <div className="v">{s.v}</div>
                <div className="l">{s.l}</div>
              </div>
            )}
          </div>
          <h5 className="mono" style={{ margin: '0 0 8px' }}>What's inside</h5>
          <ul className="modal-features">
            {p.features.map((f, i) => <li key={i}>{f}</li>)}
          </ul>
          <div className="modal-foot">
            <a href="#" className="btn btn-primary" onClick={(e) => e.preventDefault()}>
              Launch tool <Icon name="arrow-up-right" size={14} />
            </a>
            <button className="btn btn-ghost" onClick={() => { onClose(); onEnquire && onEnquire('Product enquiry — ' + p.name); }}>
              Get in touch <Icon name="arrow-right" size={14} />
            </button>
          </div>
        </div>
      </div>
    </div>);

}

function Products({ onOpen }) {
  const [tab, setTab] = useStateP('all');
  const trackRef = useRefP(null);

  const filtered = tab === 'all' ? PRODUCTS : PRODUCTS.filter((p) => p.category.toLowerCase() === tab);
  const tabs = [
  ['all', 'All'],
  ['database', 'Databases'],
  ['algorithm', 'Algorithms'],
  ['tool', 'Tools'],
  ['model', 'Models']];


  const scroll = (dir) => {
    const el = trackRef.current;
    if (!el) return;
    const card = el.querySelector('.product-card');
    const step = card ? card.offsetWidth + 24 : 360;
    const half = el.scrollWidth / 2;
    // If going backwards from the start, jump silently to the end of the first set
    if (dir < 0 && el.scrollLeft <= 1) {
      el.scrollTo({ left: half, behavior: 'auto' });
      // next frame, smooth-step back
      requestAnimationFrame(() => el.scrollBy({ left: -step, behavior: 'smooth' }));
      return;
    }
    el.scrollBy({ left: dir * step, behavior: 'smooth' });
  };

  // Continuous infinite scroll — duplicate set + RAF translates the track
  // smoothly left→right. When scrollLeft crosses the duplicate boundary we
  // silently rewind, so the loop is seamless.
  const [paused, setPaused] = React.useState(false);
  React.useEffect(() => {
    const el = trackRef.current;
    if (!el) return;
    let raf = 0;
    let last = performance.now();
    const SPEED = 70; // pixels per second
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      if (!paused) {
        el.scrollLeft += SPEED * dt;
        const half = el.scrollWidth / 2;
        if (half && el.scrollLeft >= half) el.scrollLeft -= half;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [paused, tab]);

  return (
    <section className="section" id="products">
      <div className="container">
        <div className="products-head">
          <div>
            <span className="eyebrow">Hero products</span>
            <h2 className="h2" style={{ marginTop: 18 }}>
              Six instruments<br />for systems-level inquiry.
            </h2>
          </div>
          <div className="products-tabs">
            {tabs.map(([id, label]) =>
            <button
              key={id}
              className={`products-tab ${tab === id ? 'active' : ''}`}
              onClick={() => setTab(id)}>
              
                {label}
              </button>
            )}
          </div>
        </div>
        <div className="products-track-wrap" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
          <div className="products-track" ref={trackRef}>
            {filtered.map((p) => <ProductCard key={p.id} p={p} onOpen={onOpen} />)}
            {filtered.map((p) => <ProductCard key={p.id + '-dup'} p={p} onOpen={onOpen} />)}
          </div>
          <div className="track-nav">
            <button onClick={() => scroll(-1)} aria-label="Previous"><Icon name="arrow-left" size={16} /></button>
            <button onClick={() => scroll(1)} aria-label="Next"><Icon name="arrow-right" size={16} /></button>
          </div>
        </div>
      </div>
    </section>);

}

Object.assign(window, { Products, ProductModal });


// ===== src/about-results.jsx =====
// About + Results (stats)

function About() {
  const pillars = [
  { ico: 'spark', color: 'var(--c-teal)', title: 'Curate', desc: 'We hand-curate research-grade datasets and knowledge bases for vitiligo, metabolism, and regulation.' },
  { ico: 'grid', color: 'var(--c-yellow)', title: 'Compute', desc: 'We design algorithms — alignment, network analysis, dynamic models — that move the field forward.' },
  { ico: 'book', color: 'var(--c-pink)', title: 'Publish', desc: 'Open access, reproducible methods, and shared code. Science worth standing on.' },
  { ico: 'user', color: 'var(--c-blue)', title: 'Mentor', desc: 'Interns and fellows ship real research. We optimise for graduates with research compass.' }];

  return (
    <section className="section" id="about" style={{ paddingTop: 80 }}>
      <div className="container about-grid">
        <div>
          <span className="eyebrow">About the lab</span>
          <h2 className="h2" style={{ marginTop: 18 }}>
            Our commitment lies in <em style={{ fontStyle: 'normal', color: 'var(--accent)' }}>advancing</em> the languages biology speaks.
          </h2>
          <p className="lede" style={{ marginTop: 22 }}>
            SBDA — Systems Biology and Data Analytics — was built on the conviction that biology is best understood as a connected system. We make the instruments researchers need to ask better questions, faster.
          </p>
          <p style={{ color: 'var(--ink-2)', fontSize: 15, lineHeight: 1.6, marginTop: 18, maxWidth: '56ch' }}>
            From curating disease-specific knowledge bases to algorithmic breakthroughs in network alignment, our work threads through publications, products, and the next generation of computational biologists trained here.
          </p>
          <div style={{ display: 'flex', gap: 12, marginTop: 28 }}>
            <a href="#programs" className="btn btn-primary">Work with us</a>
            <a href="#blog" className="btn btn-ghost">Read the journal</a>
          </div>
        </div>
        <div className="about-cards">
          {pillars.map((p) =>
          <div className="about-card" key={p.title}>
              <div className="ico" style={{ background: p.color }}><Icon name={p.ico} size={18} /></div>
              <h4>{p.title}</h4>
              <p>{p.desc}</p>
            </div>
          )}
        </div>
      </div>
    </section>);

}

function Results() {
  const palette = ['var(--accent)', 'var(--c-teal)', 'var(--c-pink)', 'var(--c-yellow)'];
  return (
    <section className="section" id="results" style={{ paddingTop: 60 }}>
      <div className="container">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'end', marginBottom: 40, gap: 24, flexWrap: 'wrap' }}>
          <div>
            <span className="eyebrow">Results · impact</span>
            <h2 className="h2" style={{ marginTop: 18 }}>Numbers that earn the work.</h2>
          </div>
          <p className="lede" style={{ maxWidth: '40ch' }}>
            A snapshot of what eighteen years of disciplined inquiry, mentorship, and open tooling adds up to.
          </p>
        </div>
        <div className="stats">
          {STATS.map((s, i) =>
          <div key={i} className="stat">
              <div className="stat-num" style={{ color: palette[i] }}>
                <Counter to={s.v} suffix={s.suffix} />
              </div>
              <div className="stat-lbl">{s.l}</div>
              <div className="stat-bar"><i style={{ background: palette[i], width: '78%' }} /></div>
            </div>
          )}
        </div>
      </div>
    </section>);

}

Object.assign(window, { About, Results });


// ===== src/programs-faculty.jsx =====
// Programs + Faculty

function Programs({ onEnquire, user, onOpenDashboard }) {
  const dotColor = { Internship: 'var(--c-teal)', Workshop: 'var(--c-yellow)', Course: 'var(--c-blue)', Fellowship: 'var(--c-pink)' };
  const [grantedIds, setGrantedIds] = React.useState(new Set());
  const [moduleCounts, setModuleCounts] = React.useState({});

  React.useEffect(() => {
    const client = getSupa && getSupa();
    if (!client || !user) { setGrantedIds(new Set()); setModuleCounts({}); return; }
    (async () => {
      const { data: gs } = await client.from('access_grants')
        .select('item_id, item_kind').eq('status', 'active').eq('item_kind', 'course');
      const ids = new Set((gs || []).map((g) => g.item_id));
      setGrantedIds(ids);
      if (ids.size === 0) return;
      const { data: rows } = await client.from('course_content')
        .select('item_id').eq('item_kind', 'course').in('item_id', Array.from(ids));
      const counts = {};
      (rows || []).forEach((r) => { counts[r.item_id] = (counts[r.item_id] || 0) + 1; });
      setModuleCounts(counts);
    })();
  }, [user]);

  return (
    <section className="section" id="programs" style={{ paddingTop: 80 }}>
      <div className="container">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'end', marginBottom: 40, gap: 24, flexWrap: 'wrap' }}>
          <div>
            <span className="eyebrow">Programs · for researchers & interns</span>
            <h2 className="h2" style={{ marginTop: 18 }}>Learn the craft of<br />computational biology.</h2>
          </div>
          <a href="#contact" className="btn btn-ghost">View full catalogue <Icon name="arrow-right" size={14} /></a>
        </div>
        <div className="programs-grid">
          {PROGRAMS.map((p) => {
            const granted = grantedIds.has(p.id);
            const count = moduleCounts[p.id] || 0;
            return (
              <div className="program" key={p.id}>
                <div className="program-head">
                  <span className="program-cat" style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                    <span style={{ width: 6, height: 6, borderRadius: 999, background: dotColor[p.cat] || 'var(--accent)' }}></span>
                    {p.cat}
                  </span>
                  <span className="program-dur">{p.duration}</span>
                </div>
                <div>
                  <h4>{p.title}</h4>
                  <p style={{ marginTop: 10 }}>{p.desc}</p>
                </div>
                {granted && (
                  <span className="program-modules">
                    <Icon name="book" size={11} />
                    {count > 0 ? `${count} module${count === 1 ? '' : 's'} available` : 'Enrolled · content coming'}
                  </span>
                )}
                <div className="program-foot">
                  <span className="price">{p.price}<small>· {p.seats}</small></span>
                  {granted ? (
                    <button className="program-view" onClick={() => onOpenDashboard && onOpenDashboard()}>
                      View materials <Icon name="arrow-right" size={12} />
                    </button>
                  ) : (
                    <button className="pc-arrow" onClick={() => onEnquire && onEnquire(p.title)} style={{ border: 0, cursor: 'pointer' }}>
                      <Icon name="arrow-up-right" size={14} />
                    </button>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </section>);

}

function Faculty() {
  const tagColors = ['var(--c-teal)', 'var(--c-pink)', 'var(--c-yellow)', 'var(--c-blue)', 'var(--c-lime)', 'var(--c-violet)'];
  return (
    <section className="section" id="faculty" style={{ paddingTop: 60 }}>
      <div className="container">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'end', marginBottom: 40, gap: 24, flexWrap: 'wrap' }}>
          <div>
            <span className="eyebrow">Faculty · mentors</span>
            <h2 className="h2" style={{ marginTop: 18 }}>The people behind the instruments.</h2>
          </div>
          <p style={{ maxWidth: '36ch', color: 'var(--ink-2)' }}>
            A small team. Published, opinionated, generous with time. Each card opens a research profile.
          </p>
        </div>
        <div className="faculty-grid">
          {FACULTY.map((f, i) =>
          <div className="face" key={f.id}>
              <div className="face-img placeholder">
                <div className="mark">{f.glyph}</div>
              </div>
              <h4 className="face-name">{f.name}</h4>
              <p className="face-role">{f.role}</p>
              <div className="face-tags">
                {f.tags.map((t, j) =>
              <span key={t} className="pc-tag" style={{ borderColor: tagColors[(i + j) % tagColors.length], color: tagColors[(i + j) % tagColors.length] }}>{t}</span>
              )}
              </div>
            </div>
          )}
        </div>
      </div>
    </section>);

}

Object.assign(window, { Programs, Faculty });


// ===== src/testimonials-blog.jsx =====
// Testimonials + Blog

function Testimonials() {
  return (
    <section className="section" id="testimonials" style={{ paddingTop: 60 }}>
      <div className="container">
        <div style={{ marginBottom: 40 }}>
          <span className="eyebrow">Testimonials</span>
          <h2 className="h2" style={{ marginTop: 18, maxWidth: '24ch' }}>Words from the people who used the instruments.</h2>
        </div>
        <div className="testimonials-track">
          {TESTIMONIALS.map((t, i) =>
          <div className="testimonial" key={i}>
              <blockquote>{t.quote}</blockquote>
              <div className="who">
                <div className="av" style={{ background: ['var(--c-teal)', 'var(--c-pink)', 'var(--c-yellow)', 'var(--c-blue)'][i % 4], color: '#0a0d14' }}>{t.av}</div>
                <div>
                  <div className="nm">{t.name}</div>
                  <div className="ro">{t.role}</div>
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
    </section>);

}

function Blog() {
  return (
    <section className="section" id="blog" style={{ paddingTop: 80 }}>
      <div className="container">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'end', marginBottom: 40, gap: 24, flexWrap: 'wrap' }}>
          <div>
            <span className="eyebrow">Journal · resources</span>
            <h2 className="h2" style={{ marginTop: 18 }}>From the lab notebook.</h2>
          </div>
          <a href="#" className="btn btn-ghost" onClick={(e) => e.preventDefault()}>All posts <Icon name="arrow-right" size={14} /></a>
        </div>
        <div className="blog-grid">
          {POSTS.map((p) =>
          <article key={p.id} className={`blog-card ${p.featured ? 'featured' : ''}`}>
              <div className="img placeholder" style={{ background: `linear-gradient(135deg, ${p.color}, transparent 70%), var(--bg-2)` }}>
                <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center' }}>
                  <div className="mark" style={{ opacity: 0.4 }}>{p.cat}</div>
                </div>
              </div>
              <div className="meta">
                <span className="pc-tag" style={{ borderColor: p.color, color: p.color }}>{p.cat}</span>
                <span className="mono" style={{ fontSize: 11 }}>{p.date}</span>
              </div>
              <h4>{p.title}</h4>
              <p className="exc">{p.excerpt}</p>
            </article>
          )}
        </div>
      </div>
    </section>);

}

Object.assign(window, { Testimonials, Blog });


// ===== src/contact-footer.jsx =====
// Contact form (with validation) + Footer
// Submits directly to Supabase using the anon key (no Pages Function needed).

const { useState: useStateCF, useEffect: useEffectCF } = React;

function getSupaAnon() {
  const cfg = window.__SBDA_CONFIG;
  if (!cfg || !cfg.supabase_url || !cfg.supabase_anon_key) return null;
  return { url: cfg.supabase_url, key: cfg.supabase_anon_key };
}

const INTEREST_OPTIONS = [
  'Internship', 'Fellowship', 'Workshop / Bootcamp', 'Collaboration',
  'Product enquiry — VIRdb', 'Product enquiry — VIRdb 2.0',
  'Product enquiry — Net2Align', 'Product enquiry — PluriMetNet',
  'Product enquiry — TFIS', 'Product enquiry — HEPNet',
  'Systems Biology Internship', 'Bioinformatics Bootcamp',
  'Network Biology Masterclass', 'Research Fellowship',
  'ML for Life Sciences', 'Scientific Writing Lab',
  'Press / Media', 'Other',
];

function Contact({ initialInterest }) {
  const [form, setForm] = useStateCF({ name: '', email: '', interest: initialInterest || 'Internship', message: '' });

  useEffectCF(() => {
    if (initialInterest) setForm((f) => ({ ...f, interest: initialInterest }));
  }, [initialInterest]);
  const [errors, setErrors] = useStateCF({});
  const [sent, setSent] = useStateCF(false);
  const [busy, setBusy] = useStateCF(false);

  const validate = () => {
    const e = {};
    if (!form.name.trim()) e.name = 'Please enter your name';
    if (!form.email.trim()) e.email = 'We need an email to reply';
    else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) e.email = 'Hmm, that email looks off';
    if (!form.message.trim() || form.message.trim().length < 12) e.message = 'A sentence or two would help';
    return e;
  };

  const onSubmit = async (ev) => {
    ev.preventDefault();
    const e = validate();
    setErrors(e);
    if (Object.keys(e).length > 0) return;
    setBusy(true);
    try {
      const cfg = getSupaAnon();
      if (!cfg) throw new Error('Service not ready — please try again in a moment.');

      const payload = {
        name: form.name.trim(),
        email: form.email.trim().toLowerCase(),
        interest: form.interest,
        message: form.message.trim(),
        source: 'website',
      };

      const res = await fetch(`${cfg.url}/rest/v1/enquiries`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'apikey': cfg.key,
          'Authorization': `Bearer ${cfg.key}`,
          'Prefer': 'return=minimal',
        },
        body: JSON.stringify(payload),
      });

      if (!res.ok) {
        const detail = await res.json().catch(() => ({}));
        throw new Error(detail.message || detail.error || 'Could not save enquiry.');
      }

      setSent(true);
      setTimeout(() => { setSent(false); setForm({ name: '', email: '', interest: 'Internship', message: '' }); }, 4000);
    } catch (err) {
      setErrors({ message: err.message || 'Network error — please try again.' });
    } finally {
      setBusy(false);
    }
  };

  const update = (k) => (ev) => setForm({ ...form, [k]: ev.target.value });

  return (
    <section className="section" id="contact" style={{ paddingTop: 80 }}>
      <div className="container contact-grid">
        <div>
          <span className="eyebrow">Contact · enquiry</span>
          <h2 className="h2" style={{ marginTop: 18 }}>Reach out.<br />We reply within 48 hours.</h2>
          <p className="lede" style={{ marginTop: 22 }}>
            Whether you're a prospective intern, a clinician with a dataset, or a collaborator with a question — we'd be glad to hear what you're working on.
          </p>
          <div className="contact-meta">
            <div className="contact-meta-row">
              <div className="ico"><Icon name="mail" size={16} /></div>
              <div>
                <div className="lbl">Email</div>
                <div className="val">research@sbdaresearch.in</div>
              </div>
            </div>
            <div className="contact-meta-row">
              <div className="ico"><Icon name="phone" size={16} /></div>
              <div>
                <div className="lbl">Phone</div>
                <div className="val">+91 11 4000 0000</div>
              </div>
            </div>
            <div className="contact-meta-row">
              <div className="ico"><Icon name="pin" size={16} /></div>
              <div>
                <div className="lbl">Lab address</div>
                <div className="val">Block C, Research Park, New Delhi 110016, India</div>
              </div>
            </div>
          </div>
        </div>

        <form className="form" onSubmit={onSubmit} noValidate>
          {sent &&
          <div className="success"><Icon name="check" size={14} /> Thanks — your enquiry is on its way. We'll reply within 48 hours.</div>
          }
          <div className="field-row">
            <div className="field">
              <label>Full name</label>
              <input value={form.name} onChange={update('name')} placeholder="Ada Lovelace" />
              {errors.name && <span className="err">{errors.name}</span>}
            </div>
            <div className="field">
              <label>Email</label>
              <input type="email" value={form.email} onChange={update('email')} placeholder="you@institution.edu" />
              {errors.email && <span className="err">{errors.email}</span>}
            </div>
          </div>
          <div className="field">
            <label>I'm interested in</label>
            <select value={form.interest} onChange={update('interest')}>
              {INTEREST_OPTIONS.map((o) => <option key={o}>{o}</option>)}
              {!INTEREST_OPTIONS.includes(form.interest) && (
                <option value={form.interest}>{form.interest}</option>
              )}
            </select>
          </div>
          <div className="field">
            <label>Your message</label>
            <textarea rows={5} value={form.message} onChange={update('message')} placeholder="Tell us a little about your work and what you're looking for…"></textarea>
            {errors.message && <span className="err">{errors.message}</span>}
          </div>
          <button type="submit" className="btn btn-primary" disabled={busy} style={{ alignSelf: 'flex-start' }}>
            {busy ? 'Sending…' : 'Send enquiry'} <Icon name="arrow-right" size={14} />
          </button>
        </form>
      </div>
    </section>);

}

function Footer() {
  return (
    <footer className="footer">
      <div className="container">
        <div className="footer-grid">
          <div className="footer-tag">
            <div className="brand">
              <span className="brand-mark">S</span>
              <span>SBDA<span style={{ color: 'var(--muted)', fontWeight: 400, marginLeft: 6 }}>Research</span></span>
            </div>
            <p>Systems Biology and Data Analytics Research Lab. Building open instruments for a more connected science.</p>
            <span className="mono">sbdaresearch.in</span>
          </div>
          <div>
            <h5>Products</h5>
            <ul>
              <li><a href="#products">VIRdb</a></li>
              <li><a href="#products">VIRdb 2.0</a></li>
              <li><a href="#products">Net2Align</a></li>
              <li><a href="#products">PluriMetNet</a></li>
              <li><a href="#products">TFIS</a></li>
              <li><a href="#products">HEPNet</a></li>
            </ul>
          </div>
          <div>
            <h5>Lab</h5>
            <ul>
              <li><a href="#about">About</a></li>
              <li><a href="#programs">Programs</a></li>
              <li><a href="#faculty">Faculty</a></li>
              <li><a href="#blog">Journal</a></li>
              <li><a href="#contact">Contact</a></li>
            </ul>
          </div>
          <div>
            <h5>Resources</h5>
            <ul>
              <li><a href="#">Documentation</a></li>
              <li><a href="#">Datasets</a></li>
              <li><a href="#">API reference</a></li>
              <li><a href="#">Citation policy</a></li>
              <li><a href="#">Press kit</a></li>
            </ul>
          </div>
        </div>
        <div className="footer-bot">
          <span>© 2026 SBDA Research · All rights reserved</span>
          <span>Where biology meets data</span>
        </div>
      </div>
    </footer>);

}

Object.assign(window, { Contact, Footer });


// ===== src/login-dashboard.jsx =====
// Login modal + Dashboard view — wired to Supabase auth

const { useState: useStateLD, useEffect: useEffectLD } = React;

let _supa = null;
function getSupa() {
  if (_supa) return _supa;
  const cfg = window.__SBDA_CONFIG;
  if (!cfg || !cfg.supabase_url || !cfg.supabase_anon_key) return null;
  _supa = window.supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
    auth: { persistSession: true, autoRefreshToken: true },
  });
  return _supa;
}

function LoginModal({ open, onClose, onAuth }) {
  const [mode, setMode] = useStateLD('login');
  const [form, setForm] = useStateLD({ email: '', password: '', name: '' });
  const [error, setError] = useStateLD('');
  const [busy, setBusy] = useStateLD(false);
  useBodyLock(open);

  if (!open) return null;

  const submit = async (e) => {
    e.preventDefault();
    setError('');
    if (!form.email.includes('@')) { setError('Please enter a valid email'); return; }
    if (form.password.length < 6) { setError('Password must be at least 6 characters'); return; }
    if (mode === 'signup' && !form.name.trim()) { setError('Please enter your name'); return; }

    const client = getSupa();
    if (!client) { setError('Auth service is not configured yet.'); return; }

    setBusy(true);
    try {
      if (mode === 'login') {
        const { data, error: err } = await client.auth.signInWithPassword({ email: form.email, password: form.password });
        if (err) throw err;
        const { data: profile } = await client.from('profiles').select('full_name, role').eq('id', data.user.id).single();
        onAuth({ id: data.user.id, name: profile?.full_name || form.email.split('@')[0], email: data.user.email, role: profile?.role || 'researcher' });
      } else {
        const { data, error: err } = await client.auth.signUp({
          email: form.email,
          password: form.password,
          options: { data: { full_name: form.name.trim() } },
        });
        if (err) throw err;
        if (data.session) {
          onAuth({ id: data.user.id, name: form.name.trim(), email: data.user.email, role: 'researcher' });
        } else {
          setError('Account created — please check your email to confirm, then log in.');
          setMode('login');
        }
      }
    } catch (err) {
      setError(err.message || 'Something went wrong. Please try again.');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 460 }} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div className="modal-glyph" style={{ background: 'var(--accent)', color: '#1a1207' }}>S</div>
          <button className="modal-close" onClick={onClose}><Icon name="close" size={14} /></button>
        </div>
        <div className="modal-body">
          <h3 style={{ fontSize: 28 }}>{mode === 'login' ? 'Welcome back.' : 'Create an account.'}</h3>
          <p style={{ color: 'var(--ink-2)', fontSize: 14, lineHeight: 1.5, margin: '6px 0 24px' }}>
            {mode === 'login' ? 'Access your dashboard, grants, and program enrolments.' : 'Researchers and interns — set up your SBDA profile in under a minute.'}
          </p>
          <form className="form" style={{ background: 'transparent', border: 0, padding: 0, gap: 14 }} onSubmit={submit}>
            {mode === 'signup' &&
            <div className="field">
                <label>Full name</label>
                <input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="Your name" />
              </div>
            }
            <div className="field">
              <label>Email</label>
              <input type="email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} placeholder="you@institution.edu" />
            </div>
            <div className="field">
              <label>Password</label>
              <input type="password" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} placeholder="••••••••" />
            </div>
            {error && <span className="err" style={{ color: '#ff8a5c', fontFamily: 'var(--font-mono)', fontSize: 12 }}>{error}</span>}
            <button type="submit" className="btn btn-primary" disabled={busy} style={{ justifyContent: 'center' }}>
              {busy ? 'Please wait…' : mode === 'login' ? 'Log in' : 'Create account'}
            </button>
          </form>
          <div style={{ marginTop: 18, fontSize: 13, color: 'var(--muted)', textAlign: 'center' }}>
            {mode === 'login' ?
            <>New to SBDA? <a href="#" onClick={(e) => { e.preventDefault(); setMode('signup'); setError(''); }} style={{ color: 'var(--accent)' }}>Create an account</a></> :
            <>Already have an account? <a href="#" onClick={(e) => { e.preventDefault(); setMode('login'); setError(''); }} style={{ color: 'var(--accent)' }}>Log in</a></>
            }
          </div>
        </div>
      </div>
    </div>);
}

const PRODUCT_COLORS_D = { virdb: 'var(--c-teal)', virdb2: 'var(--c-pink)', net2align: 'var(--c-blue)', plurimet: 'var(--c-violet)', tfis: 'var(--c-yellow)', hepnet: 'var(--c-lime)' };

function MaterialItem({ c }) {
  const kind = c.file_url ? 'file' : c.link_url ? 'link' : 'text';
  const icon = kind === 'file'
    ? <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><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"/></svg>
    : kind === 'link'
    ? <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></svg>
    : <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="14" y2="18"/></svg>;

  return (
    <div className="material">
      <div className={`mat-ico ${kind}`}>{icon}</div>
      <div className="mat-body">
        <div className="mat-head">
          <span className="mat-title">{c.title}</span>
          <span className={`mat-badge ${kind}`}>{kind === 'file' ? 'File' : kind === 'link' ? 'Link' : 'Note'}</span>
        </div>
        {c.body && <p className="mat-text">{c.body}</p>}
        {c.file_url && (
          <a href={c.file_url} target="_blank" rel="noopener noreferrer" className="mat-action">
            Download {c.file_name ? `· ${c.file_name}` : 'file'} <Icon name="arrow-up-right" size={12} />
          </a>
        )}
        {c.link_url && (
          <a href={c.link_url} target="_blank" rel="noopener noreferrer" className="mat-action">
            Open link <Icon name="arrow-up-right" size={12} />
          </a>
        )}
      </div>
    </div>
  );
}

function GrantCard({ g, defaultOpen }) {
  const [open, setOpen] = useStateLD(!!defaultOpen);
  const [contents, setContents] = useStateLD(null);
  const [loading, setLoading] = useStateLD(false);

  const load = async () => {
    setLoading(true);
    const client = getSupa();
    if (client) {
      const { data } = await client.from('course_content').select('*')
        .eq('item_kind', g.item_kind).eq('item_id', g.item_id)
        .order('sort_order').order('created_at');
      setContents(data || []);
    }
    setLoading(false);
  };

  useEffectLD(() => { if (open && contents === null) load(); }, [open]);

  const toggle = () => setOpen((v) => !v);

  const glyphBg = PRODUCT_COLORS_D[g.item_id] || 'var(--accent)';

  return (
    <div className={`grant-card${open ? ' open' : ''}`}>
      <div className="gc-head" onClick={toggle}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, minWidth: 0 }}>
          <div className="gc-glyph" style={{ background: glyphBg }}>{g.item_label[0]}</div>
          <div className="gc-info">
            <div className="gc-title">{g.item_label}</div>
            <div className="gc-meta">
              <span className="kind">{g.item_kind}</span>
              <span className="date">Granted {new Date(g.granted_at).toLocaleDateString()}</span>
            </div>
          </div>
        </div>
        <div className="gc-right">
          {contents !== null && (
            <span className="gc-count">{contents.length} {contents.length === 1 ? 'item' : 'items'}</span>
          )}
          <span className="gc-chev">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
          </span>
        </div>
      </div>

      {open && (
        <div className="gc-body">
          {loading && <p className="mono" style={{ fontSize: 11, margin: 0 }}>Loading content…</p>}
          {!loading && contents && contents.length === 0 && (
            <div className="dash-empty" style={{ padding: '20px 16px' }}>
              <p style={{ margin: 0 }}>No materials published yet — the admin will upload soon.</p>
            </div>
          )}
          {!loading && contents && contents.map((c) => <MaterialItem key={c.id} c={c} />)}
        </div>
      )}
    </div>
  );
}

function AccountEditor({ user, onUpdated }) {
  const [fullName, setFullName] = useStateLD(user?.name || '');
  const [newsletter, setNewsletter] = useStateLD(false);
  const [busy, setBusy] = useStateLD(false);
  const [savedAt, setSavedAt] = useStateLD(null);
  const [err, setErr] = useStateLD('');

  useEffectLD(() => {
    const client = getSupa();
    if (!client || !user) return;
    client.from('profiles').select('newsletter_optin').eq('id', user.id).single()
      .then(({ data }) => { if (data) setNewsletter(!!data.newsletter_optin); });
  }, [user]);

  const save = async (e) => {
    e.preventDefault();
    const client = getSupa();
    if (!client || !user) return;
    setBusy(true); setErr('');
    const { error } = await client.from('profiles')
      .update({ full_name: fullName.trim(), newsletter_optin: newsletter })
      .eq('id', user.id);
    setBusy(false);
    if (error) { setErr(error.message); return; }
    setSavedAt(Date.now());
    onUpdated && onUpdated({ ...user, name: fullName.trim() });
    setTimeout(() => setSavedAt(null), 2500);
  };

  return (
    <form className="dash-edit" onSubmit={save}>
      <div className="field">
        <label>Full name</label>
        <input type="text" value={fullName} onChange={(e) => setFullName(e.target.value)} placeholder="Your name" />
      </div>
      <div className="field">
        <label>Email</label>
        <input type="email" value={user?.email || ''} disabled />
      </div>
      <div className="field">
        <label>Role</label>
        <input type="text" value={user?.role || 'researcher'} disabled style={{ textTransform: 'capitalize' }} />
      </div>
      <label className="opt-row">
        <input type="checkbox" checked={newsletter} onChange={(e) => setNewsletter(e.target.checked)} />
        <span style={{ fontSize: 14 }}>Email me with new programs, research updates, and product releases.</span>
      </label>
      {err && <div className="field"><span className="err" style={{ color: '#ff8a5c', fontSize: 12 }}>{err}</span></div>}
      <div className="save-row">
        <button type="submit" className="btn btn-primary btn-sm" disabled={busy}>
          {busy ? 'Saving…' : 'Save changes'} <Icon name="check" size={14} />
        </button>
        {savedAt && <span className="save-ok">✓ Saved</span>}
      </div>
    </form>
  );
}

function Dashboard({ user, onBack, onLogout, onUserUpdate }) {
  const [tab, setTab] = useStateLD('overview');
  const [grants, setGrants] = useStateLD([]);
  const [loadingGrants, setLoadingGrants] = useStateLD(true);
  const [materialCount, setMaterialCount] = useStateLD(0);
  const initial = user?.name ? user.name[0].toUpperCase() : 'R';

  useEffectLD(() => {
    const client = getSupa();
    if (!client || !user) { setLoadingGrants(false); return; }
    client.from('access_grants').select('*').eq('status', 'active').order('granted_at', { ascending: false })
      .then(({ data }) => { setGrants(data || []); setLoadingGrants(false); });
    client.from('course_content').select('id', { count: 'exact', head: true })
      .then(({ count }) => { setMaterialCount(count || 0); });
  }, [user]);

  const handleLogout = async () => {
    const client = getSupa();
    if (client) await client.auth.signOut();
    onLogout();
  };

  const productGrants = grants.filter((g) => g.item_kind === 'product');
  const courseGrants  = grants.filter((g) => g.item_kind === 'course');
  const visibleGrants = tab === 'products' ? productGrants : tab === 'courses' ? courseGrants : grants;

  const lastGranted = grants[0]?.granted_at ? new Date(grants[0].granted_at).toLocaleDateString() : '—';

  const grantsBlock = loadingGrants
    ? <p className="mono" style={{ fontSize: 11 }}>Loading…</p>
    : visibleGrants.length === 0
      ? <div className="dash-empty">
          <div className="em-ico"><Icon name="spark" size={20} /></div>
          <h4>No active grants yet</h4>
          <p>Reach out via the contact form to request access to a product or enrol in a program.</p>
          <button className="btn btn-primary btn-sm" onClick={onBack}><Icon name="mail" size={14} /> Contact us</button>
        </div>
      : <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {visibleGrants.map((g, i) => <GrantCard key={g.id} g={g} defaultOpen={i === 0 && visibleGrants.length <= 2} />)}
        </div>;

  return (
    <div className="dash">
      <div className="container">
        <button className="dash-back" onClick={onBack} style={{ marginBottom: 24 }}>
          <Icon name="arrow-left" size={14} /> Back to site
        </button>
        <div className="dash-grid">
          <aside className="dash-side">
            <div className="user">
              <div className="av">{initial}</div>
              <div>
                <div className="nm">{user?.name || 'Researcher'}</div>
                <div className="em">{user?.email || ''}</div>
              </div>
            </div>
            <nav className="dash-nav">
              {[
              ['overview', 'home',  'Overview'],
              ['products', 'grid',  'Products'],
              ['courses',  'book',  'Courses'],
              ['account',  'user',  'Account']].
              map(([id, ico, label]) =>
              <button key={id} className={tab === id ? 'active' : ''} onClick={() => setTab(id)}>
                  <Icon name={ico} size={14} /> {label}
                </button>
              )}
              <button onClick={handleLogout} style={{ marginTop: 16, color: 'var(--muted)' }}>
                <Icon name="logout" size={14} /> Log out
              </button>
            </nav>
          </aside>
          <main className="dash-main">
            <div className="dash-hero">
              <div className="av-lg">{initial}</div>
              <div className="meta">
                <span className="role-chip"><i></i>{user?.role || 'researcher'}</span>
                <div className="nm">Welcome back, {user?.name?.split(' ')[0] || 'Researcher'}.</div>
                <div className="em">{user?.email || ''}</div>
              </div>
            </div>

            <div className="dash-stat-row">
              <div className="dash-stat">
                <div className="ico"><Icon name="grid" size={16} /></div>
                <div className="v">{loadingGrants ? '—' : grants.length}</div>
                <div className="l">Active grants</div>
              </div>
              <div className="dash-stat">
                <div className="ico"><Icon name="spark" size={16} /></div>
                <div className="v">{loadingGrants ? '—' : productGrants.length}</div>
                <div className="l">Products</div>
              </div>
              <div className="dash-stat">
                <div className="ico"><Icon name="book" size={16} /></div>
                <div className="v">{loadingGrants ? '—' : courseGrants.length}</div>
                <div className="l">Courses</div>
              </div>
              <div className="dash-stat">
                <div className="ico"><Icon name="check" size={16} /></div>
                <div className="v" style={{ fontSize: 16, fontFamily: 'var(--font-mono)', letterSpacing: 0 }}>{lastGranted}</div>
                <div className="l">Last granted</div>
              </div>
            </div>

            <span className="eyebrow">{tab}</span>
            <h2 className="h2" style={{ marginTop: 10, fontSize: 26 }}>
              {tab === 'overview' && 'Your library'}
              {tab === 'products' && 'Your products'}
              {tab === 'courses' && 'Your courses'}
              {tab === 'account' && 'Account settings'}
            </h2>
            <p style={{ color: 'var(--ink-2)', fontSize: 14, marginTop: 6, marginBottom: 22 }}>
              {tab === 'overview' && 'All your granted products and courses. Expand any card to read notes, download files, or open external links uploaded by the admin.'}
              {tab === 'products' && 'Tools and databases you have access to.'}
              {tab === 'courses' && 'Programs and courses you are enrolled in.'}
              {tab === 'account' && 'Update your name and email preferences.'}
            </p>

            {(tab === 'overview' || tab === 'products' || tab === 'courses') && grantsBlock}
            {tab === 'account' && <AccountEditor user={user} onUpdated={onUserUpdate} />}
          </main>
        </div>
      </div>
    </div>);
}

Object.assign(window, { LoginModal, Dashboard });


// ===== src/app.jsx =====
// Root App — composes everything, manages global state, wires Tweaks

const { useState: useStateApp, useEffect: useEffectApp } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "accent": "#ff6b35",
  "showStrip": true,
  "density": "comfortable"
} /*EDITMODE-END*/;

function App() {
  const [view, setView] = useStateApp('home'); // 'home' | 'dashboard'
  const [user, setUser] = useStateApp(null);
  const [loginOpen, setLoginOpen] = useStateApp(false);
  const [modalProduct, setModalProduct] = useStateApp(null);
  const [enquiryInterest, setEnquiryInterest] = useStateApp(null);
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  const handleEnquire = (interest) => {
    setEnquiryInterest(interest);
    setView('home');
    setTimeout(() => {
      const el = document.getElementById('contact');
      if (el) el.scrollIntoView({ behavior: 'smooth' });
    }, 50);
  };

  // Restore Supabase session on mount (after config loads)
  useEffectApp(() => {
    const restore = async () => {
      const client = getSupa();
      if (!client) return;
      const { data } = await client.auth.getSession();
      if (data?.session?.user) {
        const u = data.session.user;
        const { data: profile } = await client.from('profiles').select('full_name, role').eq('id', u.id).single();
        setUser({ id: u.id, name: profile?.full_name || u.email.split('@')[0], email: u.email, role: profile?.role || 'researcher' });
      }
    };
    if (window.__SBDA_CONFIG) { restore(); return; }
    const fn = () => restore();
    window.addEventListener('sbda-config', fn);
    return () => window.removeEventListener('sbda-config', fn);
  }, []);

  // Apply theme + accent
  useEffectApp(() => {
    applyTheme(t.theme);
    document.documentElement.style.setProperty('--accent', t.accent);
    document.documentElement.style.setProperty('--accent-2', shade(t.accent, 12));
  }, [t.theme, t.accent]);

  // Density tweak — adjusts section padding
  useEffectApp(() => {
    document.documentElement.style.setProperty('--section-pad', t.density === 'compact' ? '80px' : '120px');
  }, [t.density]);

  const handleAuth = (u) => {
    setUser(u);
    setLoginOpen(false);
    setView('dashboard');
  };

  const handleLogout = () => {
    setUser(null);
    setView('home');
  };

  return (
    <>
      <Nav
        view={view}
        theme={t.theme}
        onToggleTheme={() => setTweak('theme', t.theme === 'dark' ? 'light' : 'dark')}
        isAuthed={!!user}
        onLogin={() => setLoginOpen(true)}
        onDashboard={() => setView('dashboard')}
        onNavigate={(v) => setView(v)} />
      

      {view === 'home' ?
      <>
          <Hero />
          {t.showStrip && <Strip />}
          <Products onOpen={setModalProduct} />
          <About />
          <Results />
          <Programs onEnquire={handleEnquire} user={user} onOpenDashboard={() => setView('dashboard')} />
          <Faculty />
          <Testimonials />
          <Blog />
          <Contact initialInterest={enquiryInterest} />
          <Footer />
        </> :

      <Dashboard user={user} onBack={() => setView('home')} onLogout={handleLogout} onUserUpdate={setUser} />
      }

      <ProductModal p={modalProduct} onClose={() => setModalProduct(null)} onEnquire={handleEnquire} />
      <LoginModal open={loginOpen} onClose={() => setLoginOpen(false)} onAuth={handleAuth} />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Theme">
          <TweakRadio
            label="Mode"
            value={t.theme}
            onChange={(v) => setTweak('theme', v)}
            options={[['dark', 'Dark'], ['light', 'Light']]} />
          
          <TweakColor
            label="Accent color"
            value={t.accent}
            onChange={(v) => setTweak('accent', v)}
            options={['#ff6b35', '#2dd4bf', '#3b82f6', '#ec4899']} />
          
        </TweakSection>
        <TweakSection label="Layout">
          <TweakRadio
            label="Density"
            value={t.density}
            onChange={(v) => setTweak('density', v)}
            options={[['comfortable', 'Comfortable'], ['compact', 'Compact']]} />
          
          <TweakToggle
            label="Marquee strip"
            value={t.showStrip}
            onChange={(v) => setTweak('showStrip', v)} />
          
        </TweakSection>
      </TweaksPanel>
    </>);

}

// Tiny color helper — shift hex by a lightness delta
function shade(hex, deltaPct) {
  const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex || '');
  if (!m) return hex;
  const adj = (v) => Math.max(0, Math.min(255, parseInt(v, 16) + Math.round(255 * (deltaPct / 100))));
  return '#' + [m[1], m[2], m[3]].map((c) => adj(c).toString(16).padStart(2, '0')).join('');
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);