/* Bundled components — concatenated to avoid in-browser Babel multi-fetch races */


/* ===== galaxy.jsx ===== */
/* ------------------------------------------------------------------
   Galaxy3D — Three.js cluster of app-icon sprites + starfield.
   Mounts into a full-bleed canvas behind the hero.
   Props: intensity (0..1.5), density ("stars"|"nebula"|"min"), accent (hex)
------------------------------------------------------------------- */
function Galaxy3D({ intensity = 1, density = "stars", accent = "#8B5CF6", paused = false }) {
  const mountRef = React.useRef(null);
  const stateRef = React.useRef({});

  // Build once: scene, icons, stars.
  React.useEffect(function () {
    const THREE = window.THREE;
    const mount = mountRef.current;
    if (!THREE || !mount) return;

    const W = () => mount.clientWidth;
    const H = () => mount.clientHeight;

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(50, W() / H(), 0.1, 100);
    camera.position.set(0, 0, 15);

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(W(), H());
    if ("outputColorSpace" in renderer && THREE.SRGBColorSpace) renderer.outputColorSpace = THREE.SRGBColorSpace;
    else if (THREE.sRGBEncoding) renderer.outputEncoding = THREE.sRGBEncoding;
    mount.appendChild(renderer.domElement);

    // ---- icon sprites on a fibonacci sphere ----
    const apps = window.PORTFOLIO.APPS;
    const iconGroup = new THREE.Group();
    scene.add(iconGroup);
    const loader = new THREE.TextureLoader();
    const sprites = [];
    const N = apps.length;
    const R = 6.4;

    function setCS(t) {
      if ("colorSpace" in t && THREE.SRGBColorSpace) t.colorSpace = THREE.SRGBColorSpace;
      else if (THREE.sRGBEncoding) t.encoding = THREE.sRGBEncoding;
      t.anisotropy = 4;
    }
    function roundRectPath(ctx, x, y, w, h, r) {
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + w, y, x + w, y + h, r);
      ctx.arcTo(x + w, y + h, x, y + h, r);
      ctx.arcTo(x, y + h, x, y, r);
      ctx.arcTo(x, y, x + w, y, r);
      ctx.closePath();
    }
    function makeTex(app) {
      const url = window.VISUALS.getIcon(app);
      if (!url) { const t = loader.load(window.VISUALS.iconDataUrl(app)); setCS(t); return t; }
      const sz = 256;
      const canvas = document.createElement("canvas");
      canvas.width = canvas.height = sz;
      const ctx = canvas.getContext("2d");
      const tex = new THREE.CanvasTexture(canvas);
      setCS(tex);
      const img = new Image();
      img.crossOrigin = "anonymous";
      img.onload = function () {
        ctx.clearRect(0, 0, sz, sz);
        ctx.save();
        roundRectPath(ctx, 0, 0, sz, sz, sz * 0.225);
        ctx.clip();
        const s = Math.max(sz / img.width, sz / img.height);
        const w = img.width * s, h = img.height * s;
        ctx.drawImage(img, (sz - w) / 2, (sz - h) / 2, w, h);
        ctx.restore();
        tex.needsUpdate = true;
      };
      img.src = url;
      return tex;
    }

    for (let i = 0; i < N; i++) {
      const y = 1 - (i / (N - 1)) * 2;
      const rad = Math.sqrt(1 - y * y);
      const theta = i * 2.399963; // golden angle
      const pos = new THREE.Vector3(Math.cos(theta) * rad, y, Math.sin(theta) * rad).multiplyScalar(R);

      const tex = makeTex(apps[i]);
      const mat = new THREE.SpriteMaterial({ map: tex, transparent: true, depthWrite: false });
      const sp = new THREE.Sprite(mat);
      sp.position.copy(pos);
      const s = 1.5;
      sp.scale.set(s, s, s);
      sp.userData.base = s;
      iconGroup.add(sp);
      sprites.push(sp);
    }

    // ---- starfield ----
    function makeStars(count, spread, size, color, opacity) {
      const geo = new THREE.BufferGeometry();
      const arr = new Float32Array(count * 3);
      for (let i = 0; i < count; i++) {
        const r = spread * (0.4 + Math.random() * 0.6);
        const t = Math.random() * Math.PI * 2;
        const p = Math.acos(2 * Math.random() - 1);
        arr[i * 3] = r * Math.sin(p) * Math.cos(t);
        arr[i * 3 + 1] = r * Math.sin(p) * Math.sin(t);
        arr[i * 3 + 2] = r * Math.cos(p);
      }
      geo.setAttribute("position", new THREE.BufferAttribute(arr, 3));
      const mat = new THREE.PointsMaterial({
        color: new THREE.Color(color), size: size, sizeAttenuation: true,
        transparent: true, opacity: opacity, depthWrite: false,
        blending: THREE.AdditiveBlending,
      });
      return new THREE.Points(geo, mat);
    }
    const starsFar = makeStars(1400, 34, 0.09, "#cdd6ff", 0.7);
    const starsNear = makeStars(500, 20, 0.16, accent, 0.55);
    scene.add(starsFar);
    scene.add(starsNear);

    // ---- interaction ----
    let rotX = 0.1, rotY = 0, targetRX = 0.1, targetRY = 0;
    let velY = 0.0016, velX = 0;
    let dragging = false, lastX = 0, lastY = 0;
    let mx = 0, my = 0;

    function onDown(e) {
      dragging = true;
      const p = e.touches ? e.touches[0] : e;
      lastX = p.clientX; lastY = p.clientY;
      velX = velY = 0;
    }
    function onMove(e) {
      const p = e.touches ? e.touches[0] : e;
      const rect = mount.getBoundingClientRect();
      mx = ((p.clientX - rect.left) / rect.width - 0.5);
      my = ((p.clientY - rect.top) / rect.height - 0.5);
      if (!dragging) return;
      const dx = p.clientX - lastX, dy = p.clientY - lastY;
      lastX = p.clientX; lastY = p.clientY;
      targetRY += dx * 0.005;
      targetRX += dy * 0.005;
      targetRX = Math.max(-0.9, Math.min(0.9, targetRX));
      velY = dx * 0.00025;
      velX = dy * 0.00025;
    }
    function onUp() { dragging = false; }

    const el = renderer.domElement;
    el.addEventListener("mousedown", onDown);
    el.addEventListener("touchstart", onDown, { passive: true });
    window.addEventListener("mousemove", onMove);
    window.addEventListener("touchmove", onMove, { passive: true });
    window.addEventListener("mouseup", onUp);
    window.addEventListener("touchend", onUp);

    function onResize() {
      camera.aspect = W() / H();
      camera.updateProjectionMatrix();
      renderer.setSize(W(), H());
    }
    window.addEventListener("resize", onResize);

    stateRef.current = {
      get intensity() { return stateRef.current._i; },
      get paused() { return stateRef.current._p; },
      starsNear, sprites, _i: intensity, _p: paused,
    };

    let raf;
    const clock = new THREE.Clock();
    function tick() {
      raf = requestAnimationFrame(tick);
      const dt = clock.getDelta();
      const I = stateRef.current._i;
      if (!stateRef.current._p) {
        if (!dragging) {
          targetRY += velY * I;
          targetRX += velX;
          velX *= 0.96;
        }
      }
      rotY += (targetRY - rotY) * 0.08;
      rotX += (targetRX - rotX) * 0.08;
      // parallax from mouse
      camera.position.x += (mx * 2.2 - camera.position.x) * 0.04;
      camera.position.y += (-my * 1.6 - camera.position.y) * 0.04;
      camera.lookAt(0, 0, 0);

      iconGroup.rotation.y = rotY;
      iconGroup.rotation.x = rotX;
      starsFar.rotation.y = rotY * 0.3;
      starsNear.rotation.y = -rotY * 0.18 + clock.elapsedTime * 0.01;

      // billboard already (sprites). subtle pulse on near side
      for (let i = 0; i < sprites.length; i++) {
        const sp = sprites[i];
        const wp = sp.getWorldPosition(new THREE.Vector3());
        const depth = (wp.z + R) / (2 * R); // 0 back .. 1 front
        const o = 0.35 + depth * 0.65;
        sp.material.opacity = o;
        const sc = sp.userData.base * (0.78 + depth * 0.32);
        sp.scale.set(sc, sc, sc);
      }
      renderer.render(scene, camera);
    }
    tick();

    return function cleanup() {
      cancelAnimationFrame(raf);
      el.removeEventListener("mousedown", onDown);
      el.removeEventListener("touchstart", onDown);
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("touchmove", onMove);
      window.removeEventListener("mouseup", onUp);
      window.removeEventListener("touchend", onUp);
      window.removeEventListener("resize", onResize);
      renderer.dispose();
      if (el.parentNode) el.parentNode.removeChild(el);
    };
  }, []);

  // react to prop changes
  React.useEffect(function () {
    if (stateRef.current) {
      stateRef.current._i = intensity;
      stateRef.current._p = paused;
      if (stateRef.current.starsNear) {
        stateRef.current.starsNear.material.color.set(accent);
      }
    }
  }, [intensity, paused, accent]);

  return React.createElement("div", { ref: mountRef, className: "galaxy-canvas" });
}

window.Galaxy3D = Galaxy3D;


/* ===== ui.jsx ===== */
/* Shared UI atoms: AppIcon, store buttons, platform badges, category pill, Reveal */

function AppIcon({ app, size = 64, radius = 22, glow = true }) {
  const c = window.VISUALS.catColors(app);
  const icon = window.VISUALS.getIcon(app);
  const ref = React.useRef(null);
  React.useEffect(function () {
    if (!icon && ref.current) ref.current.innerHTML = window.VISUALS.iconSvg(app, { radius: (radius / size) * 100 });
  }, [app, size, radius, icon]);
  const boxStyle = {
    width: size, height: size, borderRadius: radius,
    boxShadow: glow ? ("0 8px 26px -8px " + c[0] + "88") : "none",
  };
  if (icon) {
    return (
      <div className="app-icon" style={boxStyle}>
        <img src={icon} alt={app.name + " icon"} decoding="async"
          style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
      </div>
    );
  }
  return React.createElement("div", { ref: ref, className: "app-icon", style: boxStyle });
}

function AppleMark({ s = 16 }) {
  return (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M17.05 12.6c-.03-2.6 2.12-3.85 2.22-3.91-1.21-1.77-3.1-2.01-3.77-2.04-1.6-.16-3.13.94-3.94.94-.82 0-2.07-.92-3.4-.9-1.75.03-3.36 1.02-4.26 2.58-1.82 3.16-.47 7.83 1.3 10.39.86 1.25 1.89 2.66 3.24 2.6 1.3-.05 1.79-.84 3.36-.84 1.56 0 2 .84 3.37.81 1.39-.02 2.27-1.27 3.12-2.53.98-1.45 1.39-2.86 1.41-2.93-.03-.01-2.71-1.04-2.74-4.13zM14.6 4.84c.72-.87 1.2-2.08 1.07-3.28-1.03.04-2.28.68-3.02 1.55-.66.77-1.24 2-1.08 3.18 1.15.09 2.32-.58 3.03-1.45z" />
    </svg>
  );
}

function PlayMark({ s = 15 }) {
  return (
    <svg width={s} height={s} viewBox="0 0 24 24" aria-hidden="true">
      <path d="M3.6 2.3 13.4 12 3.6 21.7c-.3-.2-.5-.6-.5-1.1V3.4c0-.5.2-.9.5-1.1z" fill="#5BE0A6" />
      <path d="M16.8 8.6 13.4 12l3.4 3.4 3.5-2c.9-.5.9-1.8 0-2.3l-3.5-2.5z" fill="#FFD24D" />
      <path d="M13.4 12 3.6 2.3c.1-.1.3-.1.5-.1.3 0 .6.1.9.3l8 4.6-0 .1z" fill="#46C2FF" />
      <path d="M13.4 12 5 16.9l-1.4.8 9.8-5.7z" fill="#FF6B6B" />
    </svg>
  );
}

function StoreButton({ kind, href, compact = false }) {
  if (!href) return null;
  const apple = kind === "ios";
  return (
    <a className={"store-btn" + (compact ? " compact" : "")} href={href} target="_blank" rel="noopener noreferrer">
      <span className="store-mark">{apple ? <AppleMark s={compact ? 15 : 19} /> : <PlayMark s={compact ? 14 : 18} />}</span>
      <span className="store-txt">
        <span className="store-top">{apple ? "Download on the" : "Get it on"}</span>
        <span className="store-name">{apple ? "App Store" : "Google Play"}</span>
      </span>
    </a>
  );
}

function PlatformBadges({ app }) {
  return (
    <div className="plat-badges">
      {app.ios && <span className="plat ios">iOS</span>}
      {app.android && <span className="plat android">Android</span>}
    </div>
  );
}

function CategoryPill({ cat, active, onClick, count }) {
  const meta = window.PORTFOLIO.CATEGORIES[cat];
  const style = meta ? { "--c1": meta.g[0], "--c2": meta.g[1] } : {};
  return (
    <button className={"cat-pill" + (active ? " active" : "")} style={style} onClick={onClick}>
      {cat}{count != null && <span className="cat-count">{count}</span>}
    </button>
  );
}

function Reveal({ children, delay = 0, as = "div", className = "" }) {
  const ref = React.useRef(null);
  const [seen, setSeen] = React.useState(false);
  React.useEffect(function () {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(function (entries) {
      entries.forEach(function (e) { if (e.isIntersecting) { setSeen(true); io.disconnect(); } });
    }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
    io.observe(el);
    return function () { io.disconnect(); };
  }, []);
  return React.createElement(as, {
    ref: ref,
    className: "reveal " + (seen ? "in " : "") + className,
    style: { transitionDelay: delay + "ms" },
  }, children);
}

Object.assign(window, { AppIcon, StoreButton, PlatformBadges, CategoryPill, Reveal, AppleMark, PlayMark });


/* ===== hero.jsx ===== */
/* Hero — 3D galaxy backdrop, name, value line, store CTAs, proof stats */

function NavBar({ accent }) {
  const [solid, setSolid] = React.useState(false);
  React.useEffect(function () {
    const onScroll = () => setSolid(window.scrollY > 40);
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  const links = [["Featured", "#featured"], ["Apps", "#apps"], ["Approach", "#approach"], ["About", "#about"]];
  return (
    <header className={"nav" + (solid ? " solid" : "")}>
      <a className="brand" href="#top">
        <span className="brand-name">AppSurge</span>
      </a>
      <nav className="nav-links">
        {links.map(([l, h]) => <a key={h} href={h}>{l}</a>)}
      </nav>
      <a className="nav-cta" href="#contact">Get in touch</a>
    </header>
  );
}

function Hero({ t }) {
  const apps = window.PORTFOLIO.APPS;
  const links = window.PORTFOLIO.LINKS;
  const cats = Object.keys(window.PORTFOLIO.CATEGORIES).length;
  const stats = [
    { n: apps.length + "+", l: "Published apps" },
    { n: "2", l: "Platforms" },
    { n: cats, l: "Categories" },
    { n: "AI", l: "Core focus" },
  ];
  return (
    <section className="hero" id="top">
      <div className="galaxy-wrap">
        <Galaxy3D intensity={t.intensity} accent={t.accent} density={t.bg} paused={false} />
        <div className="galaxy-vignette" />
      </div>

      <div className="hero-inner">
        <div className="hero-eyebrow">
          <span className="pulse-dot" /> Mobile App Studio · iOS &amp; Android
        </div>
        <h1 className="hero-title">AppSurge</h1>
        <p className="hero-lead">
          AppSurge is a mobile app studio building iOS and Android apps since 2019.
          What started as a fascination with putting software in people’s pockets has
          grown into <strong>{apps.length}+ published apps</strong> across the App Store
          and Google Play.
        </p>
        <div className="hero-cta">
          <StoreButton kind="ios" href={links.appleDev} />
          <StoreButton kind="android" href={links.androidDev} />
        </div>

        <div className="hero-stats">
          {stats.map((s, i) => (
            <div className="stat" key={i}>
              <div className="stat-n">{s.n}</div>
              <div className="stat-l">{s.l}</div>
            </div>
          ))}
        </div>
      </div>

      <a className="hero-scroll" href="#featured" aria-label="Scroll to work">
        <span>Drag the galaxy · scroll to explore</span>
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 5v14M6 13l6 6 6-6" /></svg>
      </a>
    </section>
  );
}

Object.assign(window, { NavBar, Hero });


/* ===== featured.jsx ===== */
/* Featured apps — 6 headline cards */

function FeatCard({ app, i }) {
  const grad = window.VISUALS.gradientCss(app);
  const meta = window.PORTFOLIO.CATEGORIES[app.cat];
  return (
    <Reveal as="article" className="feat-card" delay={i * 70}>
      <div className="feat-media" style={{ backgroundImage: grad }}>
        <div className="feat-watermark" dangerouslySetInnerHTML={{
          __html: '<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">' + window.PORTFOLIO.GLYPHS[app.glyph] + '</svg>'
        }} />
        <div className="feat-media-row">
          <AppIcon app={app} size={66} radius={18} glow={false} />
          <div className="feat-media-meta">
            <span className="feat-media-name">{app.short}</span>
            <span className="feat-media-cat">{app.cat}</span>
          </div>
        </div>
      </div>
      <div className="feat-body">
        <h3 className="feat-name">{app.name}</h3>
        <p className="feat-blurb">{app.blurb}</p>
        <div className="feat-foot">
          <PlatformBadges app={app} />
          <div className="feat-stores">
            <StoreButton kind="ios" href={app.ios} compact />
            <StoreButton kind="android" href={app.android} compact />
          </div>
        </div>
      </div>
    </Reveal>
  );
}

function Featured() {
  const apps = window.PORTFOLIO.APPS.filter((a) => a.featured);
  return (
    <section className="section featured" id="featured">
      <div className="section-head">
        <Reveal as="div" className="eyebrow">Featured work</Reveal>
        <Reveal as="h2" className="section-title" delay={60}>
          Apps that put AI to work
        </Reveal>
        <Reveal as="p" className="section-sub" delay={120}>
          A selection of recent builds — each one shipped end to end, from concept and
          UI to publishing and store optimization.
        </Reveal>
      </div>
      <div className="feat-grid">
        {apps.map((a, i) => <FeatCard key={a.name} app={a} i={i} />)}
      </div>
    </section>
  );
}

window.Featured = Featured;


/* ===== work.jsx ===== */
/* Approach — qualitative case studies + skills */

const CASES = [
  {
    short: "MelonNote", appName: "MelonNote — AI Note Taker", glyph: "note", cat: "Productivity",
    problem: "Notes from meetings and lectures pile up faster than anyone can organize them.",
    built: "An AI note taker that records, transcribes and restructures audio into clean, shareable notes — shipped on both iOS and Android.",
    features: ["Audio capture", "AI transcription", "Smart summaries", "Cross-platform"],
    result: "Raw recordings become readable, structured notes in seconds.",
    ios: "https://apps.apple.com/us/app/melonnote-ai-note-taker/id6754255911?uo=4",
    android: "https://play.google.com/store/apps/details?id=com.akhandaf.melonnote&hl=nl",
  },
  {
    short: "Zwintji", appName: "AI Calorie scanner — Zwintji", glyph: "flame", cat: "AI",
    problem: "Manual calorie logging is tedious enough that most people give up within a week.",
    built: "A camera-first scanner that estimates calories and macros from a single photo of a meal.",
    features: ["Camera-first", "AI food recognition", "Macro breakdown", "Daily log"],
    result: "Logging a meal becomes a photo instead of a form.",
    ios: "https://apps.apple.com/us/app/ai-calorie-scanner-zwintji/id6747187048?uo=4",
  },
  {
    short: "Baytee", appName: "AI Room Planner — Baytee", glyph: "home", cat: "AI",
    problem: "It's hard to picture a room redesign before committing real time or money.",
    built: "Snap a photo of a space and let generative AI restyle it into new looks instantly.",
    features: ["Photo input", "Generative restyle", "Multiple styles", "Share & save"],
    result: "Interior inspiration that's personal to your actual room.",
    ios: "https://apps.apple.com/us/app/ai-room-planner-baytee/id6743873862?uo=4",
  },
];

function CaseCard({ c, i }) {
  const fakeApp = { cat: c.cat, glyph: c.glyph, short: c.short, name: c.appName };
  const grad = window.VISUALS.gradientCss(fakeApp);
  return (
    <Reveal as="article" className="case-card" delay={i * 90}>
      <div className="case-media" style={{ backgroundImage: grad }}>
        <div className="case-icon"><AppIcon app={fakeApp} size={76} radius={20} glow={false} /></div>
        <span className="case-num">{String(i + 1).padStart(2, "0")}</span>
      </div>
      <div className="case-body">
        <div className="case-tag">{c.cat}</div>
        <h3 className="case-name">{c.appName}</h3>
        <div className="case-block">
          <span className="case-label">The problem</span>
          <p>{c.problem}</p>
        </div>
        <div className="case-block">
          <span className="case-label">What we built</span>
          <p>{c.built}</p>
        </div>
        <div className="case-features">
          {c.features.map((f) => <span className="chip" key={f}>{f}</span>)}
        </div>
        <div className="case-result">
          <span className="case-label">Outcome</span>
          <p>{c.result}</p>
        </div>
        <div className="case-stores">
          <StoreButton kind="ios" href={c.ios} compact />
          <StoreButton kind="android" href={c.android} compact />
        </div>
      </div>
    </Reveal>
  );
}

function Approach() {
  return (
    <section className="section approach" id="approach">
      <div className="section-head">
        <Reveal as="div" className="eyebrow">How it comes together</Reveal>
        <Reveal as="h2" className="section-title" delay={60}>From a real problem to a shipped app</Reveal>
        <Reveal as="p" className="section-sub" delay={120}>
          A few builds, broken down — the problem they solve, what went into them, and the
          experience they deliver.
        </Reveal>
      </div>
      <div className="case-grid">
        {CASES.map((c, i) => <CaseCard key={c.short} c={c} i={i} />)}
      </div>
    </section>
  );
}

const SKILLS = [
  { t: "Platforms", items: ["iOS development", "Android development", "Cross-platform releases"] },
  { t: "Product & design", items: ["UI / UX design", "Product design", "Rapid prototyping"] },
  { t: "AI features", items: ["AI app workflows", "Camera & vision features", "Generative media"] },
  { t: "Launch & growth", items: ["App publishing", "Store optimization (ASO)", "Monetization"] },
];

function Skills() {
  return (
    <section className="section skills">
      <div className="section-head">
        <Reveal as="div" className="eyebrow">What we do</Reveal>
        <Reveal as="h2" className="section-title" delay={60}>End to end, idea to store</Reveal>
      </div>
      <div className="skills-grid">
        {SKILLS.map((s, i) => (
          <Reveal as="div" className="skill-col" delay={i * 80} key={s.t}>
            <h3 className="skill-t">{s.t}</h3>
            <ul>{s.items.map((it) => <li key={it}>{it}</li>)}</ul>
          </Reveal>
        ))}
      </div>
    </section>
  );
}

Object.assign(window, { Approach, Skills });


/* ===== directory.jsx ===== */
/* Full app directory — searchable + filterable */

function DirCard({ app }) {
  const c = window.VISUALS.catColors(app);
  return (
    <div className="dir-card" style={{ "--c1": c[0], "--c2": c[1] }}>
      <AppIcon app={app} size={50} radius={14} glow={false} />
      <div className="dir-info">
        <span className="dir-name">{app.name}</span>
        <span className="dir-cat">{app.cat}</span>
      </div>
      <div className="dir-links">
        {app.ios && (
          <a href={app.ios} target="_blank" rel="noopener noreferrer" className="dir-link" title="App Store"><AppleMark s={15} /></a>
        )}
        {app.android && (
          <a href={app.android} target="_blank" rel="noopener noreferrer" className="dir-link" title="Google Play"><PlayMark s={15} /></a>
        )}
      </div>
    </div>
  );
}

function Directory() {
  const allApps = window.PORTFOLIO.APPS;
  const cats = window.PORTFOLIO.CATEGORIES;
  const [q, setQ] = React.useState("");
  const [cat, setCat] = React.useState("All");

  const counts = React.useMemo(function () {
    const m = {};
    allApps.forEach((a) => { m[a.cat] = (m[a.cat] || 0) + 1; });
    return m;
  }, [allApps]);

  const filtered = allApps.filter(function (a) {
    const okCat = cat === "All" || a.cat === cat;
    const okQ = !q || (a.name + " " + a.cat + " " + a.blurb).toLowerCase().includes(q.toLowerCase());
    return okCat && okQ;
  });

  return (
    <section className="section directory" id="apps">
      <div className="section-head">
        <Reveal as="div" className="eyebrow">Full catalog</Reveal>
        <Reveal as="h2" className="section-title" delay={60}>Every app, in one place</Reveal>
        <Reveal as="p" className="section-sub" delay={120}>
          {allApps.length} published apps across {Object.keys(cats).length} categories. Search by name or
          filter by what they do.
        </Reveal>
      </div>

      <div className="dir-controls">
        <div className="dir-search">
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="11" cy="11" r="7" /><path d="M21 21l-4-4" /></svg>
          <input
            type="text" placeholder="Search apps…" value={q}
            onChange={(e) => setQ(e.target.value)}
          />
          {q && <button className="dir-clear" onClick={() => setQ("")} aria-label="Clear">×</button>}
        </div>
        <div className="dir-pills">
          <CategoryPill cat="All" active={cat === "All"} onClick={() => setCat("All")} count={allApps.length} />
          {Object.keys(cats).map((k) => (
            <CategoryPill key={k} cat={k} active={cat === k} onClick={() => setCat(k)} count={counts[k] || 0} />
          ))}
        </div>
      </div>

      <div className="dir-grid">
        {filtered.map((a) => <DirCard key={a.name} app={a} />)}
      </div>
      {filtered.length === 0 && <p className="dir-empty">No apps match “{q}”.</p>}
    </section>
  );
}

window.Directory = Directory;


/* ===== about.jsx ===== */
/* About + Contact + Footer */

function About() {
  const apps = window.PORTFOLIO.APPS;
  return (
    <section className="section about" id="about">
      <div className="about-grid">
        <Reveal as="div" className="about-portrait-wrap">
          <div className="about-portrait">
            <img src="img/marwan.jpg" alt="Marwan Akhandaf, founder of AppSurge" />
          </div>
          <div className="about-portrait-glow" />
        </Reveal>
        <div className="about-text">
          <Reveal as="div" className="eyebrow">About</Reveal>
          <Reveal as="h2" className="section-title" delay={50}>
            We ship small, useful apps — fast.
          </Reveal>
          <Reveal as="p" className="about-p" delay={110}>
            AppSurge is a mobile app studio founded by Marwan Akhandaf, focused on building
            useful products from idea to release. We started creating apps in 2019 out of a
            fascination with how software could live in someone’s pocket and become part of
            their everyday routine.
          </Reveal>
          <Reveal as="p" className="about-p" delay={160}>
            Since then, we’ve published <strong>{apps.length}+ apps</strong> across the App Store and
            Google Play, covering AI tools, productivity, education, health, utilities and
            lifestyle apps. Our work is not just about generating ideas. We care about the full
            engineering and product process: designing clear interfaces, building reliable app
            experiences, publishing, store optimization, iteration and monetization.
          </Reveal>
          <Reveal as="p" className="about-p" delay={210}>
            Our focus is simple: build apps that solve a specific problem well, feel good to use,
            and are polished enough for real users.
          </Reveal>
          <Reveal as="div" className="about-facts" delay={260}>
            <div><span className="af-n">{apps.length}+</span><span className="af-l">apps shipped</span></div>
            <div><span className="af-n">iOS</span><span className="af-l">+ Android</span></div>
            <div><span className="af-n">AI</span><span className="af-l">first focus</span></div>
          </Reveal>
        </div>
      </div>
    </section>
  );
}

function Contact() {
  const links = window.PORTFOLIO.LINKS;
  return (
    <section className="section contact" id="contact">
      <Reveal as="div" className="contact-card">
        <div className="contact-orb" />
        <div className="eyebrow">Contact</div>
        <h2 className="contact-title">Have an app idea?<br />Let's build it.</h2>
        <p className="contact-sub">
          AppSurge is open to freelance builds, collaborations and new app concepts — for iOS, Android, or both.
        </p>
        <div className="contact-actions">
          <a className="btn-primary" href={"mailto:" + links.email}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="2" /><path d="m3 7 9 6 9-6" /></svg>
            {links.email}
          </a>
          <a className="btn-ghost" href={links.linkedin} target="_blank" rel="noopener noreferrer">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.35V9h3.42v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.07 2.07 0 1 1 0-4.14 2.07 2.07 0 0 1 0 4.14zM7.12 20.45H3.55V9h3.57v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.73v20.54C0 23.22.79 24 1.77 24h20.45c.98 0 1.78-.78 1.78-1.73V1.73C24 .77 23.2 0 22.22 0z" /></svg>
            LinkedIn
          </a>
        </div>
      </Reveal>
    </section>
  );
}

function Footer() {
  const links = window.PORTFOLIO.LINKS;
  return (
    <footer className="footer">
      <div className="footer-inner">
        <div className="footer-brand">
          <div>
            <div className="footer-name">AppSurge</div>
            <div className="footer-role">Mobile App Studio</div>
          </div>
        </div>
        <div className="footer-cols">
          <div className="footer-col">
            <span className="footer-h">Stores</span>
            <a href={links.appleDev} target="_blank" rel="noopener noreferrer">App Store profile</a>
            <a href={links.androidDev} target="_blank" rel="noopener noreferrer">Google Play</a>
          </div>
          <div className="footer-col">
            <span className="footer-h">Connect</span>
            <a href={"mailto:" + links.email}>Email</a>
            <a href={links.linkedin} target="_blank" rel="noopener noreferrer">LinkedIn</a>
          </div>
          <div className="footer-col">
            <span className="footer-h">Explore</span>
            <a href="#featured">Featured</a>
            <a href="#apps">All apps</a>
          </div>
        </div>
      </div>
      <div className="footer-base">
        <span>© 2026 AppSurge. All rights reserved.</span>
        <span>Built app-first · iOS &amp; Android</span>
      </div>
    </footer>
  );
}

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


/* ===== main.jsx ===== */
/* App root — assembles sections, wires tweaks */

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#8B5CF6",
  "intensity": 1,
  "motion": true
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const effIntensity = t.motion ? t.intensity : 0;

  return (
    <div className="page" style={{ "--accent": t.accent }}>
      <NavBar accent={t.accent} />
      <Hero t={{ accent: t.accent, intensity: effIntensity, bg: "stars" }} />
      <main>
        <Featured />
        <Approach />
        <Directory />
        <Skills />
        <About />
        <Contact />
      </main>
      <Footer />

      <TweaksPanel>
        <TweakSection label="Look" />
        <TweakColor label="Accent" value={t.accent}
          options={["#8B5CF6", "#22D3EE", "#34D399", "#F59E0B", "#FB7185"]}
          onChange={(v) => setTweak("accent", v)} />
        <TweakSection label="3D galaxy" />
        <TweakSlider label="Motion intensity" value={t.intensity} min={0} max={1.6} step={0.1}
          onChange={(v) => setTweak("intensity", v)} />
        <TweakToggle label="Animate galaxy" value={t.motion}
          onChange={(v) => setTweak("motion", v)} />
      </TweaksPanel>
    </div>
  );
}

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