// app.jsx — main Astral Project mobile app

// Admin email allowlist — mirrored in functions/index.js + firestore.rules +
// storage.rules. Adding a teammate? Update all four. The Admin.exe desktop
// icon and window only render when the current user's verified email is here.
const ADMIN_EMAILS = ['astralprojectco@gmail.com'];
function isAdmin(user) {
  return !!(user && user.email && user.emailVerified && ADMIN_EMAILS.includes(user.email));
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "wallpaper": "car",
  "iconStyle": "pixel",
  "theme": "dusk",
  "density": "regular",
  "shippingThreshold": 200,
  "showMusicPlayer": false,
  "showNewsletter": false,
  "showRecycleBin": false,
  "showPromo": false,
  "showVideo": false,
  "showMarquee": false,
  "scanlines": true,
  "boot": true,
  "sound": true
}/*EDITMODE-END*/;

// ── desktop icon definitions ───────────────────────────────────
const DESKTOP_ICONS = [
  { id: 'shop', label: 'Shop.exe', icon: 'shop', opens: 'shop' },
  { id: 'collections', label: 'Collections', icon: 'folder-pink', opens: 'collections', hidden: true },
  { id: 'lookbook', label: 'Lookbook', icon: 'camera', opens: 'lookbook', hidden: true },
  { id: 'about', label: 'About.txt', icon: 'notepad', opens: 'about' },
  { id: 'portfolio', label: 'Portfolio.exe', icon: 'portfolio', opens: 'portfolio', hidden: true },
  { id: 'contact', label: 'Chat.exe', icon: 'mail', opens: 'contact' },
  { id: 'track', label: 'Track Order', icon: 'globe', opens: 'track' },
  { id: 'soon', label: 'Coming Soon', icon: 'cd', opens: 'soon', hidden: true },
  { id: 'help', label: 'Help?', icon: 'help', opens: 'help' },
  { id: 'cart', label: 'Cart', icon: 'cart', opens: 'cart' },
  { id: 'recycle', label: 'Recycle Bin', icon: 'recycle-full', opens: 'recycle', hidden: true },
  { id: 'computer', label: 'My Computer', icon: 'computer', opens: 'file-manager' },
  // Admin.exe — only rendered for admin emails (filtered in the icon grid + drawer).
  { id: 'admin', label: 'Admin.exe', icon: 'computer', opens: 'admin', adminOnly: true },
];

// ── boot screen ────────────────────────────────────────────────
// Progress is rendered as a block-character bar below the BIOS lines.
// 20 cells; each cell is 5% of the load. step label maps to the current
// loadCatalog phase ('connecting' / 'products' / 'collections' / 'details'
// / 'shared' / 'done').
function BootProgressBar({ pct, step }) {
  const cells = 20;
  const filled = Math.max(0, Math.min(cells, Math.round((pct / 100) * cells)));
  const bar = '█'.repeat(filled) + '░'.repeat(cells - filled);
  const labelMap = {
    connecting: 'connecting to ASTRAL.NET',
    products: 'decoding products',
    collections: 'mapping collections',
    details: 'loading product details',
    shared: 'syncing shared info',
    done: 'ready',
  };
  return (
    <div style={{ marginTop: 8, color: '#5fff5f' }}>
      <div>[{bar}] {String(pct).padStart(3, ' ')}%</div>
      <div style={{ color: '#9cf09c', fontSize: 12 }}>&gt; {labelMap[step] || 'buffering…'}</div>
    </div>
  );
}

function BootScreen({ onDone, ready = true, progress = null }) {
  const [step, setStep] = React.useState(0);
  const lines = [
    'Astral BIOS v1.95.04',
    'Copyright (C) 1995-2026 Astral Systems Inc.',
    '',
    'CPU: PinkPentium™ 200MHz ............... [OK]',
    'RAM: 64 MB EDO ......................... [OK]',
    'Cache: 512K ............................ [OK]',
    'Floppy A: 1.44 MB ...................... [OK]',
    'Detecting IDE devices ..................',
    '  Primary: ASTRAL_DRIVE_C 8.4GB',
    '  Secondary: CD-ROM 24x',
    'Connecting to ASTRAL.NET ...............',
    'Loading vaporwave.dll ..................',
    'Loading kawaii.sys .....................',
    '',
    'Starting ＡＳＴＲＡＬ ＰＲＯＪＥＣＴ ...',
  ];

  React.useEffect(() => {
    if (step >= lines.length) {
      // Hold the final frame until the catalog has loaded.
      if (!ready) return;
      const t = setTimeout(onDone, 600);
      return () => clearTimeout(t);
    }
    const t = setTimeout(() => setStep(s => s + 1), step < 4 ? 80 : 60);
    return () => clearTimeout(t);
  }, [step, ready]);

  return (
    <div className="boot-screen">
      <div style={{ fontSize: 20, color: '#5fff5f', marginBottom: 8 }}>
        ┌──────────────────────────────────┐
      </div>
      {lines.slice(0, step).map((l, i) => (
        <div key={i} className="boot-line" style={{
          color: l.includes('[OK]') ? '#5fff5f' : l.includes('ＡＳＴＲＡＬ') ? '#ff7ad9' : '#fff',
        }}>
          {l}
        </div>
      ))}
      {step < lines.length && (
        <div className="blink-cursor" style={{ display: 'inline-block', width: 8, height: 12, background: '#fff' }} />
      )}
      {step >= lines.length && progress && !ready && (
        <BootProgressBar pct={progress.pct} step={progress.step} />
      )}
    </div>
  );
}

// Catalog-load failure screen — replaces a silent error toast with a CRT
// "no signal" panel and a retry that re-runs window.loadCatalog().
function NoSignalScreen({ onRetry }) {
  return (
    <div className="boot-screen" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
      <div style={{ fontSize: 28, color: '#ff3eb5', letterSpacing: '0.2em', fontWeight: 'bold', marginBottom: 12 }}>
        ＮＯ ＳＩＧＮＡＬ
      </div>
      <pre style={{ color: '#9cf09c', fontSize: 11, lineHeight: 1.4, margin: 0, marginBottom: 16, textAlign: 'left' }}>
{`> ASTRAL.NET handshake failed
> retry connection? [Y/n]`}
      </pre>
      <button className="w95-btn" onClick={onRetry}
        style={{ padding: '6px 18px', fontSize: 12, fontWeight: 'bold', background: '#ff3eb5', color: '#fff', cursor: 'pointer' }}>
        RECONNECT →
      </button>
      <div style={{ color: '#666', fontSize: 9, marginTop: 12 }}>
        if this keeps happening, check your connection or refresh the page
      </div>
    </div>
  );
}

// ── start menu ────────────────────────────────────────────────
function StartMenu({ open, onClose, onItem }) {
  if (!open) return null;
  const items = [
    { icon: 'shop', label: 'Shop All', target: 'shop' },
    { icon: 'folder-pink', label: 'Collections', target: 'collections' },
    { icon: 'camera', label: 'Lookbook', target: 'lookbook' },
    null,
    { icon: 'cart', label: 'My Cart', target: 'cart' },
    { icon: 'globe', label: 'Track Order', target: 'track' },
    null,
    { icon: 'mail', label: 'Chat.exe', target: 'contact' },
    { icon: 'notepad', label: 'About', target: 'about' },
    { icon: 'help', label: 'Help', target: 'help' },
  ];
  return (
    <>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, zIndex: 990 }} />
      <div style={{
        position: 'absolute', bottom: 30, left: 2,
        width: 200, background: 'var(--w95-bg)',
        border: '1px solid', borderColor: '#fff #000 #000 #fff',
        boxShadow: '2px 2px 0 #000', zIndex: 999,
      }}>
        <div style={{
          width: 24, position: 'absolute', top: 0, bottom: 0, left: 0,
          background: 'linear-gradient(180deg, #6a2bd9 0%, #ff3eb5 100%)',
          color: '#fff', fontWeight: 'bold', writingMode: 'vertical-rl',
          transform: 'rotate(180deg)', textAlign: 'center', fontSize: 16,
          padding: '8px 4px', letterSpacing: '0.2em',
        }}>
          ＡＳＴＲＡＬ★95
        </div>
        <div style={{ paddingLeft: 28, paddingRight: 2, paddingTop: 2, paddingBottom: 2 }}>
          {items.map((it, i) => it === null ? (
            <div key={i} style={{ height: 1, background: '#888', margin: '2px 0', boxShadow: '0 1px 0 #fff' }} />
          ) : (
            <button key={i} onClick={() => { onItem(it.target); onClose(); }} style={{
              display: 'flex', alignItems: 'center', gap: 8,
              padding: '4px 6px', width: '100%',
              border: 0, background: 'transparent', cursor: 'pointer',
              fontFamily: 'inherit', fontSize: 12, textAlign: 'left',
            }} onMouseOver={e => { e.currentTarget.style.background = 'var(--w95-title)'; e.currentTarget.style.color = '#fff'; }}
               onMouseOut={e => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = '#000'; }}
            >
              <Icon name={it.icon} size={20} style="pixel" />
              <span>{it.label}</span>
            </button>
          ))}
        </div>
      </div>
    </>
  );
}

// ── ToastNudge ────────────────────────────────────────────────
function ToastNudge({ message, onDone }) {
  React.useEffect(() => {
    if (!message) return;
    const t = setTimeout(onDone, 2200);
    return () => clearTimeout(t);
  }, [message]);
  if (!message) return null;
  return (
    <div style={{
      position: 'absolute', top: 60, left: '50%', transform: 'translateX(-50%)',
      background: '#000080', color: '#fff', padding: '6px 12px',
      border: '2px solid #fff', boxShadow: '3px 3px 0 #000',
      fontSize: 11, fontWeight: 'bold', zIndex: 2000, whiteSpace: 'nowrap',
      animation: 'shake 0.4s',
    }}>
      ★ {message} ★
    </div>
  );
}

// ── Drawer (mobile menu) ──────────────────────────────────────
function Drawer({ open, onClose, onItem, cartCount, user }) {
  if (!open) return null;
  const items = DESKTOP_ICONS.filter(i => !i.hidden && i.id !== 'recycle' && i.id !== 'computer' && (!i.adminOnly || isAdmin(user)));
  return (
    <>
      <div className="drawer-backdrop" onClick={onClose} />
      <div className="drawer">
        <div style={{
          background: 'linear-gradient(135deg, #ff3eb5, #6a2bd9)',
          color: '#fff', padding: 16, fontWeight: 'bold',
          fontSize: 18, letterSpacing: '0.1em',
          textShadow: '2px 2px 0 #000', borderBottom: '2px solid #000',
        }}>
          ＡＳＴＲＡＬ ★ 95
          <div style={{ fontSize: 10, fontWeight: 'normal', marginTop: 2, letterSpacing: 0 }}>
            navigation panel
          </div>
        </div>
        <div style={{ flex: 1, overflow: 'auto' }} className="w95-scroll">
          {items.map(it => (
            <div key={it.id} className="drawer-item" onClick={() => { onItem(it.opens); onClose(); }}>
              <Icon name={it.icon} size={24} style="pixel" />
              <div style={{ flex: 1 }}>{it.label}</div>
              {it.id === 'cart' && cartCount > 0 && (
                <div style={{
                  background: '#ff3a3a', color: '#fff', borderRadius: '50%',
                  width: 18, height: 18, fontSize: 10, fontWeight: 'bold',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>{cartCount}</div>
              )}
              <div>›</div>
            </div>
          ))}
        </div>
        <div style={{
          padding: 12, background: '#000', color: '#5fff5f',
          fontFamily: 'monospace', fontSize: 9, borderTop: '2px solid #888',
        }}>
          ＡＳＴＲＡＬ.95 v4.26<br/>
          uptime: 31y 4m 12d<br/>
          status: <span style={{ color: '#ffe14d' }}>★ ONLINE ★</span>
        </div>
      </div>
    </>
  );
}

// ── App ───────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [booted, setBooted] = React.useState(!t.boot);
  const [windows, setWindows] = React.useState([]);
  const [maximized, setMaximized] = React.useState(() => new Set());
  const [minimized, setMinimized] = React.useState(() => new Set());
  const [zCounter, setZCounter] = React.useState(20);
  const [drawerOpen, setDrawerOpen] = React.useState(false);
  const [startOpen, setStartOpen] = React.useState(false);
  const [cart, setCart] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('ap-cart') || '[]'); } catch { return []; }
  });
  const [wishlist, setWishlist] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('ap-wishlist') || '[]'); } catch { return []; }
  });
  const [toast, setToast] = React.useState('');
  const [now, setNow] = React.useState(new Date());
  const [ready, setReady] = React.useState(false);
  const [loadProgress, setLoadProgress] = React.useState({ step: 'connecting', pct: 0 });
  const [loadFailed, setLoadFailed] = React.useState(false);
  const [currentUser, setCurrentUser] = React.useState(null);
  const [achievements, setAchievements] = React.useState([]);
  const [unlockBanner, setUnlockBanner] = React.useState(null);
  // Mirror state into a ref so the award queue (which lives outside any
  // single render) always reads the freshest list. Callers can fire
  // awards in parallel; the queue serializes them and uses the ref as
  // the source of truth, avoiding the lost-update race where two
  // concurrent .then(setAchievements) calls overwrite each other.
  const achievementsRef = React.useRef([]);
  React.useEffect(() => { achievementsRef.current = achievements; }, [achievements]);
  const awardQueueRef = React.useRef(Promise.resolve());

  // Persist cart + wishlist to localStorage on every change.
  React.useEffect(() => {
    try { localStorage.setItem('ap-cart', JSON.stringify(cart)); } catch {}
  }, [cart]);
  React.useEffect(() => {
    try { localStorage.setItem('ap-wishlist', JSON.stringify(wishlist)); } catch {}
  }, [wishlist]);

  React.useEffect(() => {
    if (!window.auth) return;
    const unsub = window.auth.onAuthStateChanged((u) => setCurrentUser(u));
    return () => unsub();
  }, []);

  // Pull wishlist + achievements from the user's Firestore doc once we know
  // the uid. Anonymous users keep using localStorage only; a Google-linked
  // user gets their cross-device wishlist merged with whatever is on the
  // device. After loading we also evaluate any achievements that newly
  // qualify (e.g. early-adopter, wishlist-10) and award them.
  React.useEffect(() => {
    if (!currentUser || currentUser.isAnonymous) {
      setAchievements([]);
      return;
    }
    if (typeof window.loadUserPrefs !== 'function') return;
    let cancelled = false;
    // Backfill historical Wix-import orders to this uid (idempotent — server
    // marks the user once and short-circuits on subsequent sign-ins). When
    // it links something, prefs are reloaded so the achievement evaluator
    // sees the updated lifetimeSpend / orderCount.
    const linkPromise = typeof window.linkImportedOrders === 'function'
      ? window.linkImportedOrders()
      : Promise.resolve(null);
    linkPromise.then(async (link) => {
      if (cancelled) return;
      const reload = link && link.linked > 0;
      const prefs = await window.loadUserPrefs(currentUser.uid);
      if (cancelled) return;
      // If we just bumped stats, the first prefs read may have raced the
      // transaction write. Re-read once to grab the canonical values.
      const fresh = reload ? await window.loadUserPrefs(currentUser.uid) : prefs;
      if (cancelled) return;
      const remote = Array.isArray(fresh.wishlist) ? fresh.wishlist : [];
      setWishlist((local) => {
        const merged = Array.from(new Set([...(local || []), ...remote]));
        if (typeof window.saveUserPrefs === 'function' && merged.length !== remote.length) {
          window.saveUserPrefs(currentUser.uid, { wishlist: merged });
        }
        return merged;
      });
      const remoteAch = Array.isArray(fresh.achievements) ? fresh.achievements : [];
      achievementsRef.current = remoteAch;
      setAchievements(remoteAch);
      // Evaluate achievements that depend purely on user state at sign-in
      // and route each new id through awardAchievement so it shares the
      // queue with any concurrent wishlist / order / minesweeper awards.
      if (window.apAchievements) {
        const newOnes = window.apAchievements.evaluate({
          user: currentUser,
          current: remoteAch,
          orderCount: fresh.orderCount || 0,
          lifetimeSpend: fresh.lifetimeSpend || 0,
          wishlistSize: remote.length,
        });
        for (const id of newOnes) {
          // eslint-disable-next-line no-await-in-loop
          await awardAchievement(id);
        }
      }
    });
    return () => { cancelled = true; };
  }, [currentUser && currentUser.uid]);

  // Awarder wrapper. Serializes through awardQueueRef so concurrent
  // callers (e.g. wishlist-10 firing simultaneously with a post-order
  // evaluator) can't race their state updates. Always reads the latest
  // achievements list from the ref — never the stale closure copy.
  const awardAchievement = React.useCallback((id) => {
    if (!window.apAchievements) return Promise.resolve();
    const job = awardQueueRef.current.then(async () => {
      const before = achievementsRef.current;
      const next = await window.apAchievements.award(currentUser, before, id);
      if (next !== before) {
        achievementsRef.current = next;
        setAchievements(next);
      }
      return next;
    });
    awardQueueRef.current = job.catch(() => {});
    return job;
  }, [currentUser]);

  // Listen for achievement unlock events emitted by apAchievements.award
  // and surface a 6s banner toast (longer than the standard 2.2s toast).
  React.useEffect(() => {
    const handler = (e) => {
      const a = e && e.detail && e.detail.achievement;
      if (!a) return;
      if (window.apSound) window.apSound.success();
      setUnlockBanner(a);
      setTimeout(() => setUnlockBanner(b => b && b.id === a.id ? null : b), 6000);
    };
    window.addEventListener('ap-achievement', handler);
    return () => window.removeEventListener('ap-achievement', handler);
  }, []);

  // Hidden mini-game unlock paths:
  //   1. Konami code anywhere (↑ ↑ ↓ ↓ ← → ← → b a)
  //   2. Typing WINMINE while on the boot/no-signal screen (or anywhere — easier to discover)
  //   3. Recycle Bin right-click → Properties (handled in the recycle window)
  // All three call openWindow('minesweeper').
  React.useEffect(() => {
    const KONAMI = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
    let buf = [];
    let typeBuf = '';
    let typeTimer = null;
    const onKey = (e) => {
      const k = e.key;
      // konami
      buf.push(k.length === 1 ? k.toLowerCase() : k);
      if (buf.length > KONAMI.length) buf = buf.slice(-KONAMI.length);
      if (buf.length === KONAMI.length && KONAMI.every((x, i) => x === buf[i])) {
        buf = [];
        if (window.apSound) window.apSound.startup();
        openWindow('minesweeper');
        return;
      }
      // WINMINE typed (case-insensitive). Resets after 2s of inactivity.
      if (k.length === 1 && /^[a-zA-Z]$/.test(k)) {
        typeBuf = (typeBuf + k).toLowerCase().slice(-7);
        if (typeTimer) clearTimeout(typeTimer);
        typeTimer = setTimeout(() => { typeBuf = ''; }, 2000);
        if (typeBuf.endsWith('winmine')) {
          typeBuf = '';
          if (window.apSound) window.apSound.startup();
          openWindow('minesweeper');
        }
      }
    };
    window.addEventListener('keydown', onKey);
    return () => {
      window.removeEventListener('keydown', onKey);
      if (typeTimer) clearTimeout(typeTimer);
    };
  }, []);

  // Esc closes the topmost visible window. Skipped while the user is
  // typing in an input/textarea so it doesn't fight with form clears.
  React.useEffect(() => {
    const onEsc = (e) => {
      if (e.key !== 'Escape') return;
      const t = e.target;
      const tag = t && t.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || (t && t.isContentEditable)) return;
      const visible = windows.filter((w) => !minimized.has(w.id));
      if (visible.length === 0) return;
      const top = visible.reduce((a, b) => (a.z > b.z ? a : b));
      closeWindow(top.id);
      e.preventDefault();
    };
    window.addEventListener('keydown', onEsc);
    return () => window.removeEventListener('keydown', onEsc);
  }, [windows, minimized]);

  // Win handler — calls the claimMinesweeperReward callable, awards the
  // 'minesweeper' achievement, and surfaces a toast with the coupon code.
  // Idempotent server-side; safe to call again on subsequent wins.
  const handleMinesweeperWin = React.useCallback(async () => {
    // Achievement first — works for anonymous users too.
    awardAchievement('minesweeper');
    if (!currentUser || currentUser.isAnonymous) {
      setToast('★ field cleared — sign in to claim 50 ★');
      return;
    }
    if (typeof window.callFn !== 'function') return;
    try {
      const res = await window.callFn('claimMinesweeperReward', {});
      if (res && res.alreadyClaimed) {
        setToast('★ field cleared — reward already collected');
      } else if (res && res.coupon) {
        setToast(`★ +50 ★ + coupon ${res.coupon}`);
      } else {
        setToast('★ field cleared');
      }
    } catch (e) {
      console.warn('[minesweeper] claim failed:', e);
      setToast('⚠ could not claim reward — try again later');
    }
  }, [currentUser, awardAchievement]);

  const toggleWishlist = (slug) => {
    if (!slug) return;
    setWishlist((w) => {
      const has = w.includes(slug);
      const next = has ? w.filter((s) => s !== slug) : [...w, slug];
      if (currentUser && !currentUser.isAnonymous && typeof window.saveUserPrefs === 'function') {
        window.saveUserPrefs(currentUser.uid, { wishlist: next });
      }
      if (window.apSound) window.apSound.click();
      setToast(has ? '✗ removed from wishlist' : '♥ saved to wishlist');
      // wishlist-10 achievement: triggered when adding pushes count to 10+
      if (!has && next.length >= 10) awardAchievement('wishlist-10');
      // Track adds only — removes aren't a meaningful conversion signal.
      if (!has && typeof window.apTrack === 'function') {
        const product = (window.PRODUCTS || []).find((p) => p.slug === slug)
          || (window.ARCHIVED_PRODUCTS || []).find((p) => p.slug === slug);
        const u = product && typeof window.upsellInfo === 'function' ? window.upsellInfo(product) : null;
        const value = u ? u.sale : (product && product.price) || 0;
        window.apTrack('add_to_wishlist', {
          currency: 'INR',
          value,
          items: [{ item_id: slug, item_name: product ? product.name : slug, price: value, quantity: 1 }],
        });
      }
      return next;
    });
  };

  React.useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 30000);
    return () => clearInterval(id);
  }, []);

  // Keep the sound module in sync with the tweak. Default-on; user can mute.
  React.useEffect(() => {
    if (window.apSound) window.apSound.setMuted(!t.sound);
  }, [t.sound]);

  // One-shot startup chime when the desktop is fully booted.
  React.useEffect(() => {
    if (booted && ready && window.apSound) window.apSound.startup();
  }, [booted, ready]);

  // Pull catalog from Firestore on mount. The boot screen holds until this
  // resolves so the desktop never renders against an empty PRODUCTS array.
  // The NO_SIGNAL screen offers a retry on failure instead of a silent toast.
  const runCatalogLoad = React.useCallback(() => {
    if (typeof window.loadCatalog !== 'function') {
      console.error('[App] window.loadCatalog missing — Firebase init likely failed');
      if (window.apSound) window.apSound.error();
      setLoadFailed(true);
      return;
    }
    setLoadFailed(false);
    setLoadProgress({ step: 'connecting', pct: 5 });
    window.loadCatalog((p) => setLoadProgress(p))
      .then(() => setReady(true))
      .catch((err) => {
        console.error('[App] catalog load failed:', err);
        if (window.apSound) window.apSound.error();
        setLoadFailed(true);
      });
  }, []);
  React.useEffect(() => { runCatalogLoad(); }, [runCatalogLoad]);

  // Open default popups on first boot
  React.useEffect(() => {
    if (!booted) return;
    const initial = [];
    // First-visit welcome popup. localStorage flag → shows once per device.
    // Wrapped in try/catch because storage can be disabled (private mode, etc.) —
    // we'd rather over-show than crash the boot effect.
    let firstVisit = false;
    try {
      if (!localStorage.getItem('ap-welcome-seen')) {
        firstVisit = true;
        localStorage.setItem('ap-welcome-seen', '1');
      }
    } catch (e) { /* storage blocked — skip welcome */ }
    if (firstVisit) initial.push('welcome');
    if (t.showMusicPlayer) initial.push('music');
    if (t.showNewsletter) initial.push('newsletter');
    if (t.showRecycleBin) initial.push('recycle');
    if (t.showPromo) initial.push('promo');
    if (t.showVideo) initial.push('video');
    if (t.showMarquee) initial.push('marquee');
    initial.forEach((id, i) => {
      setTimeout(() => openWindow(id), i * 150);
    });
    // eslint-disable-next-line
  }, [booted]);

  const openWindow = (id) => {
    setMinimized(s => {
      if (!s.has(id)) return s;
      const n = new Set(s);
      n.delete(id);
      return n;
    });
    setWindows(ws => {
      if (ws.find(w => w.id === id)) return ws.map(w => w.id === id ? { ...w, z: zCounter + 1 } : w);
      const meta = WINDOW_META[id] || {};
      const next = {
        id,
        z: zCounter + 1,
        x: meta.x ?? 8 + Math.random() * 20,
        y: meta.y ?? 70 + Math.random() * 40,
      };
      if (window.apSound) {
        if (id === 'order-success') window.apSound.mail();
        else window.apSound.open();
      }
      return [...ws, next];
    });
    setZCounter(z => z + 1);
  };

  const toggleMaximize = (id) => {
    setMaximized(s => {
      const n = new Set(s);
      if (n.has(id)) n.delete(id); else n.add(id);
      return n;
    });
    activate(id);
  };

  // `silent` skips the close SFX — used when the close is triggered by a
  // tweak toggle (or any non-user-initiated path) so the user doesn't get
  // a phantom whoosh from flipping a checkbox.
  const closeWindow = (id, { silent = false } = {}) => {
    if (!silent && window.apSound) window.apSound.close();
    setWindows(ws => ws.filter(w => w.id !== id));
    setMaximized(s => {
      if (!s.has(id)) return s;
      const n = new Set(s);
      n.delete(id);
      return n;
    });
    setMinimized(s => {
      if (!s.has(id)) return s;
      const n = new Set(s);
      n.delete(id);
      return n;
    });
    // also reflect popup tweaks if they came from initial state
    if (id === 'music') setTweak('showMusicPlayer', false);
    if (id === 'newsletter') setTweak('showNewsletter', false);
    if (id === 'recycle') setTweak('showRecycleBin', false);
    if (id === 'promo') setTweak('showPromo', false);
    if (id === 'video') setTweak('showVideo', false);
    if (id === 'marquee') setTweak('showMarquee', false);
  };

  const activate = (id) => {
    setWindows(ws => ws.map(w => w.id === id ? { ...w, z: zCounter + 1 } : w));
    setZCounter(z => z + 1);
    setMinimized(s => {
      if (!s.has(id)) return s;
      const n = new Set(s);
      n.delete(id);
      return n;
    });
  };

  const minimizeWindow = (id) => {
    if (window.apSound) window.apSound.minimize();
    setMinimized(s => {
      const n = new Set(s);
      n.add(id);
      return n;
    });
  };

  const toggleTaskWindow = (id) => {
    if (minimized.has(id)) { activate(id); return; }
    const top = windows.length > 0 ? Math.max(...windows.filter(w => !minimized.has(w.id)).map(x => x.z)) : -Infinity;
    const w = windows.find(x => x.id === id);
    if (w && w.z === top) { minimizeWindow(id); return; }
    activate(id);
  };

  const addToCart = (product) => {
    setCart(c => {
      const existing = c.find(it => it.id === product.id);
      if (existing) return c.map(it => it.id === product.id ? { ...it, qty: it.qty + 1 } : it);
      return [...c, { ...product, qty: 1 }];
    });
    if (window.apSound) window.apSound.success();
    setToast(`★ ${product.name} → cart`);
    if (typeof window.apTrack === 'function') {
      const u = typeof window.upsellInfo === 'function' ? window.upsellInfo(product) : null;
      const value = u ? u.sale : product.price;
      window.apTrack('add_to_cart', {
        currency: 'INR',
        value,
        items: [{
          item_id: product.slug,
          item_name: product.name,
          price: value,
          quantity: 1,
        }],
      });
    }
  };

  const removeFromCart = (id) => {
    setCart(c => c.filter(it => it.id !== id));
  };

  const updateCartQty = (id, delta) => {
    setCart(c => c
      .map(it => it.id === id ? { ...it, qty: Math.max(0, it.qty + delta) } : it)
      .filter(it => it.qty > 0));
  };

  const cartCount = cart.reduce((s, it) => s + it.qty, 0);
  const cartTotal = cart.reduce((s, it) => s + it.price * it.qty, 0);
  const shipProgress = Math.min(1, cartTotal / t.shippingThreshold);
  const shipRemaining = Math.max(0, t.shippingThreshold - cartTotal);

  const [musicPlaying, setMusicPlaying] = React.useState(true);

  // Rotating ticker for the top bar — witty cosmic "social proof" + scarcity.
  // Real shipping nudge takes over once the cart is close enough to matter.
  const tickerLines = React.useMemo(() => [
    "✶ 1,247 stargazers in orbit right now",
    "♄ Saturn says: black tees flatter every chart",
    "★★★★★ 'best fit i own' — Riya, BLR",
    "🌙 Aanya in Mumbai just copped NIRVANA tee",
    "⚡ Drop 03 → only 12 left in this dimension",
    "✶ Mercury retrograde cancelled. cop now",
    "★★★★★ 'fits like a prophecy' — Devansh, DEL",
    "♆ Neptune favors hoodies past sundown",
    "🪐 9 souls added to cart in the last hour",
    "✶ rated 4.9 / 5 across the multiverse",
  ], []);
  const [tickerIdx, setTickerIdx] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setTickerIdx(i => (i + 1) % tickerLines.length), 3500);
    return () => clearInterval(id);
  }, [tickerLines.length]);
  const showShipNudge = cartTotal > 0 && shipRemaining > 0 && shipProgress >= 0.5;

  const renderWindow = (w) => {
    const meta = WINDOW_META[w.id];
    if (!meta) return null;
    const visibleWindows = windows.filter(x => !minimized.has(x.id));
    const isActive = !minimized.has(w.id) && visibleWindows.length > 0 && Math.max(...visibleWindows.map(x => x.z)) === w.z;

    let content, title = meta.title, icon = meta.icon, marquee = meta.marquee, width = meta.width || 280, height = meta.height;

    switch (w.id) {
      case 'music':
        content = <MusicPlayerContent playing={musicPlaying} onTogglePlay={() => setMusicPlaying(p => !p)} />;
        break;
      case 'welcome':
        content = <WelcomeContent
          onClose={() => closeWindow('welcome')}
          onShop={() => openWindow('shop')}
          onNewsletter={() => openWindow('newsletter')}
        />;
        break;
      case 'newsletter':
        content = <NewsletterContent
          onClose={() => closeWindow('newsletter')}
          onSubmit={(email) => { setToast(`Subscribed: ${email}`); closeWindow('newsletter'); }}
        />;
        break;
      case 'recycle':
        content = <RecycleBinContent onSecret={() => openWindow('minesweeper')} />;
        break;
      case 'promo':
        content = <PromoSidebarContent onShop={() => openWindow('shop')} />;
        break;
      case 'video':
        content = <VideoPopupContent />;
        break;
      case 'marquee':
        content = <MarqueeContent />;
        break;
      case 'shop':
        content = <ShopGridContent onAdd={addToCart}
          wishlist={wishlist} onToggleWish={toggleWishlist}
          onView={(p) => { setActiveProduct(p); openWindow('product-view'); }} />;
        break;
      case 'collections':
        content = <CollectionsContent onOpen={(c) => { setCollectionFilter(c); openWindow('collection-view'); }} />;
        break;
      case 'collection-view':
        content = <ShopGridContent onAdd={addToCart} collection={collectionFilter}
          wishlist={wishlist} onToggleWish={toggleWishlist}
          onView={(p) => { setActiveProduct(p); openWindow('product-view'); }}
          products={collectionFilter ? PRODUCTS.filter(p => (p.cols || []).includes(collectionFilter.id)) : PRODUCTS} />;
        title = collectionFilter ? `${collectionFilter.name}` : 'Collection';
        break;
      case 'product-view':
        content = activeProduct
          ? <ProductDetailContent product={activeProduct}
              onAdd={addToCart}
              wishlist={wishlist} onToggleWish={toggleWishlist}
              onView={(p) => setActiveProduct(p)}
              onBack={() => closeWindow('product-view')} />
          : <div style={{ padding: 12 }}>No product selected.</div>;
        title = activeProduct ? activeProduct.name : 'Product';
        break;
      case 'lookbook': content = <LookbookContent
        onViewImage={(img) => { setLookbookImage(img); openWindow('lookbook-image'); }} />; break;
      case 'lookbook-image':
        content = lookbookImage
          ? <LookbookImageContent image={lookbookImage} />
          : <div style={{ padding: 12 }}>No image selected.</div>;
        title = lookbookImage ? `lookbook / ${lookbookImage.caption}` : 'Lookbook';
        break;
      case 'about': content = <AboutContent />; break;
      case 'portfolio':
        content = <PortfolioContent onToast={(m) => setToast(m)} onOpenWindow={openWindow} />;
        break;
      case 'contact': content = <ChatExeContent user={currentUser} onToast={(m) => setToast(m)} />; break;
      case 'cart': content = <CartContent items={cart} onRemove={removeFromCart}
        onUpdateQty={updateCartQty} threshold={t.shippingThreshold}
        onCheckout={() => {
          if (typeof window.apTrack === 'function' && cart.length) {
            window.apTrack('begin_checkout', {
              currency: 'INR',
              value: cartTotal,
              items: cart.map((it) => ({
                item_id: it.slug || it.id, item_name: it.name,
                price: it.price, quantity: it.qty,
              })),
            });
          }
          closeWindow('cart'); openWindow('checkout');
        }}
        onShop={() => { closeWindow('cart'); openWindow('shop'); }} />; break;
      case 'checkout':
        content = <CheckoutContent items={cart} threshold={t.shippingThreshold} user={currentUser}
          onToast={(m) => setToast(m)}
          onPaid={({ orderId, total }) => {
            // Capture the cart contents BEFORE we clear it for the purchase event.
            const purchaseItems = cart.map((it) => ({
              item_id: it.slug || it.id, item_name: it.name,
              price: it.price, quantity: it.qty,
            }));
            setLastOrder({ orderId, total });
            setCart([]);
            closeWindow('checkout');
            openWindow('order-success');
            // event_id = razorpay order id so the server-side Meta CAPI call
            // dedups against this browser-side Pixel event.
            if (typeof window.apTrack === 'function') {
              window.apTrack('purchase', {
                currency: 'INR',
                value: total,
                transaction_id: orderId,
                event_id: orderId,
                items: purchaseItems,
              });
            }
            // Award post-order achievements (first-order + tier bumps + night-owl).
            // Re-fetches the user's prefs because verifyPayment on the server
            // updates lifetimeSpend/orderCount before we land here.
            if (currentUser && !currentUser.isAnonymous && window.apAchievements && window.loadUserPrefs) {
              window.loadUserPrefs(currentUser.uid).then(async (prefs) => {
                // Trust server-managed prefs (lifetimeSpend / orderCount /
                // achievements). Sync the ref to whatever the server has so
                // the queue starts from fresh state.
                const remoteAch = Array.isArray(prefs.achievements) ? prefs.achievements : [];
                achievementsRef.current = remoteAch;
                setAchievements(remoteAch);
                const newOnes = window.apAchievements.evaluate({
                  user: currentUser, current: remoteAch,
                  orderCount: prefs.orderCount || 1,
                  lifetimeSpend: prefs.lifetimeSpend || total,
                  wishlistSize: (prefs.wishlist || []).length,
                  justOrdered: true,
                });
                for (const id of newOnes) {
                  // eslint-disable-next-line no-await-in-loop
                  await awardAchievement(id);
                }
              });
            }
          }} />;
        break;
      case 'order-success':
        content = lastOrder
          ? <OrderSuccessContent orderId={lastOrder.orderId} total={lastOrder.total}
              onClose={() => closeWindow('order-success')}
              onTrack={() => { closeWindow('order-success'); openWindow('track'); }} />
          : <div style={{ padding: 12 }}>No recent order.</div>;
        break;
      case 'track': content = <TrackOrderContent user={currentUser} />; break;
      case 'soon': content = <ComingSoonContent user={currentUser} onToast={(m) => setToast(m)} />; break;
      case 'file-manager':
        content = <FileManagerContent
          onView={(p) => { setActiveProduct(p); openWindow('product-view'); }}
          onOpen={(target) => openWindow(target)} />;
        break;
      case 'minesweeper':
        content = <MinesweeperContent user={currentUser} onWin={handleMinesweeperWin}
          onToast={(m) => setToast(m)} />;
        break;
      case 'help': content = <HelpContent />; break;
      case 'admin':
        if (isAdmin(currentUser)) {
          content = <AdminContent user={currentUser}
            onToast={(m) => setToast(m)}
            onView={(p) => { setActiveProduct(p); openWindow('product-view'); }} />;
        } else {
          content = (
            <div className="w95-inset" style={{ background: '#fff', padding: 12, fontSize: 11, textAlign: 'center' }}>
              <div style={{ fontWeight: 'bold', marginBottom: 4 }}>★ Access denied ★</div>
              <div style={{ fontSize: 10, color: '#444' }}>
                Sign in with the admin Google account to view this dashboard.
              </div>
            </div>
          );
        }
        break;
      case 'login':
        if (currentUser) {
          content = <AccountContent user={currentUser}
            wishlist={wishlist} onToggleWish={toggleWishlist}
            achievements={achievements}
            onClose={() => closeWindow('login')}
            onToast={(m) => setToast(m)}
            onOpen={(target, payload) => {
              if (target === 'product-view' && payload) setActiveProduct(payload);
              openWindow(target);
            }} />;
          title = 'My Account';
          icon = <Icon name="folder-pink" size={14} />;
          width = 350;
          height = 560;
        } else {
          content = <LoginContent user={currentUser}
            onClose={() => closeWindow('login')}
            onToast={(m) => setToast(m)} />;
          title = 'Sign in';
        }
        break;
    }

    return (
      <Win95Window
        key={w.id}
        title={title}
        icon={icon}
        marquee={marquee}
        x={w.x}
        y={w.y}
        width={width}
        height={height}
        zIndex={w.z}
        active={isActive}
        onActivate={() => activate(w.id)}
        onClose={() => closeWindow(w.id)}
        onMinimize={() => minimizeWindow(w.id)}
        onMaximize={() => toggleMaximize(w.id)}
        maximized={maximized.has(w.id)}
        hidden={minimized.has(w.id)}
        statusbar={meta.statusbar}
      >
        {content}
      </Win95Window>
    );
  };

  const [collectionFilter, setCollectionFilter] = React.useState(null);
  const [activeProduct, setActiveProduct] = React.useState(null);
  const [lookbookImage, setLookbookImage] = React.useState(null);
  const [lastOrder, setLastOrder] = React.useState(null);

  // Fire view_item whenever a product is shown in the detail view. Captures
  // both the initial open and sideways navigation via "more from this
  // collection". Sale price (upsell) wins over list price for value reporting.
  React.useEffect(() => {
    if (!activeProduct || typeof window.apTrack !== 'function') return;
    const u = typeof window.upsellInfo === 'function' ? window.upsellInfo(activeProduct) : null;
    const value = u ? u.sale : activeProduct.price;
    window.apTrack('view_item', {
      currency: 'INR',
      value,
      items: [{
        item_id: activeProduct.slug,
        item_name: activeProduct.name,
        price: value,
        quantity: 1,
      }],
    });
  }, [activeProduct && activeProduct.slug]);

  const screenW = 360;
  const screenH = 720;

  const timeStr = now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });

  return (
    <div style={{
      position: 'fixed', inset: 0,
      paddingTop: 'env(safe-area-inset-top)',
      paddingRight: 'env(safe-area-inset-right)',
      paddingBottom: 'env(safe-area-inset-bottom)',
      paddingLeft: 'env(safe-area-inset-left)',
      background: '#000',
      overflow: 'hidden',
      boxSizing: 'border-box',
    }}>
      <a className="ap-skip-link" href="#main-content"
        onClick={(e) => {
          e.preventDefault();
          openWindow('shop');
        }}>
        Skip to shop
      </a>
      <div style={{
        width: '100%', height: '100%',
        overflow: 'hidden',
        display: 'flex', flexDirection: 'column',
        background: '#000',
      }}
      data-density={t.density}
      data-theme={t.theme}>
        {loadFailed ? (
          <NoSignalScreen onRetry={runCatalogLoad} />
        ) : (!booted || !ready) ? (
          <BootScreen ready={ready} progress={loadProgress} onDone={() => setBooted(true)} />
        ) : (
          <>
            {/* Top header */}
            <div className="ap-topbar">
              <button onClick={() => setDrawerOpen(true)} style={{
                background: 'transparent', border: 0, color: '#fff',
                cursor: 'pointer', padding: 4, fontSize: 18, lineHeight: 1,
              }} aria-label="Menu">≡</button>
              <div className="ap-logo" style={{ flexDirection: 'column', gap: 1, alignItems: 'center', lineHeight: 1 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
                  <span className="star">★</span>
                  <span style={{ fontFamily: 'serif', letterSpacing: '0.08em' }}>ASTRAL</span>
                  <span style={{ color: 'var(--ap-cyan)' }}>·</span>
                  <span style={{ fontFamily: 'serif', letterSpacing: '0.08em' }}>PROJECT</span>
                </div>
                <div style={{
                  fontSize: 7, fontWeight: 'normal', letterSpacing: '0.12em',
                  color: 'var(--ap-cyan)', textTransform: 'uppercase', opacity: 0.85,
                  marginTop: 1,
                }}>
                  Goa, Ind · Gen Z tees · musicians · dreamers · artists
                </div>
              </div>
              <Tooltip text={currentUser ? `Signed in${currentUser.email ? ` as ${currentUser.email}` : ''}` : 'Sign in'} placement="bottom">
              <button onClick={() => openWindow('login')} style={{
                background: 'transparent', border: 0, color: '#fff', cursor: 'pointer',
                padding: 4, marginRight: 2, display: 'flex', alignItems: 'center', gap: 4,
                fontSize: 11,
              }} aria-label={currentUser ? 'Account' : 'Sign in'}>
                {currentUser && currentUser.photoURL ? (
                  <img src={currentUser.photoURL} alt="" aria-hidden="true"
                    style={{ width: 18, height: 18, borderRadius: '50%', border: '1px solid #fff' }} />
                ) : currentUser ? (
                  <span style={{
                    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                    width: 18, height: 18, border: '1px solid #fff',
                    background: '#5fff5f', color: '#000', fontWeight: 'bold', fontSize: 10,
                  }}>{((currentUser.displayName || currentUser.email || 'G')[0] || 'G').toUpperCase()}</span>
                ) : (
                  <Icon name="user" size={20} />
                )}
              </button>
              </Tooltip>
              <Tooltip text={cartCount > 0 ? `Cart — ${cartCount} item${cartCount === 1 ? '' : 's'}` : 'Cart'} placement="bottom">
              <button onClick={() => openWindow('cart')} style={{
                background: 'transparent', border: 0, color: '#fff', cursor: 'pointer',
                padding: 4, position: 'relative',
              }} aria-label="Cart">
                <Icon name="cart-light" size={22} />
                {cartCount > 0 && (
                  <div style={{
                    position: 'absolute', top: 0, right: 0,
                    background: '#ff3a3a', borderRadius: '50%',
                    width: 14, height: 14, fontSize: 9, fontWeight: 'bold',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    color: '#fff',
                  }}>{cartCount}</div>
                )}
              </button>
              </Tooltip>
            </div>

            {/* Sticky shipping bar — only visible once free shipping is unlocked */}
            {shipRemaining === 0 && cartTotal > 0 && (
              <div className="ship-bar">
                <div className="progress" style={{ width: '100%' }} />
                <div className="ship-text">
                  <>✓ FREE SHIPPING UNLOCKED ★</>
                </div>
              </div>
            )}

            {/* Desktop area */}
            <div id="main-content" style={{
              position: 'relative', flex: 1, overflow: 'hidden',
              backgroundImage: 'url("wallpaperflare.com_wallpaper.jpg")',
              backgroundSize: 'cover',
              backgroundPosition: 'center',
              backgroundRepeat: 'no-repeat',
            }}>

              {/* Icon grid */}
              <div style={{
                position: 'absolute', inset: 0, padding: 10,
                display: 'grid',
                gridTemplateColumns: `repeat(auto-fill, var(--grid-cell))`,
                gap: 'var(--grid-gap)',
                alignContent: 'flex-start', justifyContent: 'flex-start',
                pointerEvents: windows.length > 0 ? 'none' : 'auto',
              }}>
                {DESKTOP_ICONS
                  .filter(ic => !ic.hidden && ic.id !== 'computer' && (!ic.adminOnly || isAdmin(currentUser)))
                  .map(ic => (
                  <DesktopIcon
                    key={ic.id}
                    label={ic.label}
                    iconName={ic.icon}
                    iconStyle={t.iconStyle}
                    onOpen={() => openWindow(ic.opens)}
                    style={{ pointerEvents: 'auto' }}
                  />
                ))}
              </div>

              {/* Windows */}
              {windows.map(renderWindow)}

              {/* Toast */}
              <ToastNudge message={toast} onDone={() => setToast('')} />

              {/* Achievement unlock banner — sits above the standard toast. */}
              {unlockBanner && (
                <div style={{
                  position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)',
                  zIndex: 9999, background: '#000', color: '#fff',
                  border: `2px solid ${unlockBanner.color || '#ff3eb5'}`,
                  boxShadow: `4px 4px 0 ${unlockBanner.color || '#ff3eb5'}`,
                  padding: '6px 10px', display: 'flex', gap: 10, alignItems: 'center',
                  fontFamily: 'monospace', fontSize: 11, minWidth: 240,
                }}>
                  <pre style={{
                    margin: 0, fontSize: 11, lineHeight: 1.05,
                    color: unlockBanner.color || '#ff3eb5', fontFamily: 'monospace',
                  }}>{unlockBanner.pixel}</pre>
                  <div style={{ flex: 1 }}>
                    <div style={{ color: unlockBanner.color || '#ff3eb5', fontWeight: 'bold', fontSize: 9 }}>
                      ★ ACHIEVEMENT UNLOCKED ★
                    </div>
                    <div style={{ fontWeight: 'bold' }}>{unlockBanner.title}</div>
                    <div style={{ fontSize: 9, color: '#ccc' }}>{unlockBanner.desc}</div>
                  </div>
                  <button onClick={() => setUnlockBanner(null)}
                    style={{
                      background: 'transparent', border: 0, color: '#fff',
                      cursor: 'pointer', fontSize: 14, padding: 0, lineHeight: 1,
                    }} aria-label="Dismiss">×</button>
                </div>
              )}

              {/* Drawer */}
              <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)}
                      onItem={openWindow} cartCount={cartCount} user={currentUser} />

              {/* Start menu */}
              <StartMenu open={startOpen} onClose={() => setStartOpen(false)} onItem={openWindow} />

              {/* Desktop pet — sits above the taskbar and wanders around */}
              <CatPet />

              {/* CRT scanlines */}
              {t.scanlines && <div className="crt-overlay" />}
            </div>

            {/* Taskbar */}
            <div className="taskbar">
              <button className="w95-btn start-btn" aria-pressed={startOpen}
                      onClick={() => setStartOpen(o => !o)}>
                <span className="star">★</span>
                <span className={startOpen ? '' : 'blink-cursor'}>Start</span>
              </button>
              <div className="taskbar-divider" />
              <div style={{ display: 'flex', gap: 2, flex: 1, overflow: 'hidden' }}>
                {(() => {
                  const visible = windows.filter(x => !minimized.has(x.id));
                  const topZ = visible.length > 0 ? Math.max(...visible.map(x => x.z)) : -Infinity;
                  return windows.slice(-3).map(w => (
                    <button key={w.id} className="w95-btn task-window"
                            aria-pressed={!minimized.has(w.id) && w.z === topZ}
                            onClick={() => toggleTaskWindow(w.id)}>
                      {WINDOW_META[w.id]?.title || w.id}
                    </button>
                  ));
                })()}
              </div>
              <div className="taskbar-tray">
                <Tooltip text="AstralFM" placement="top"><span style={{ color: '#ff3eb5' }}>♪</span></Tooltip>
                <Tooltip text="Connected to ASTRAL.NET" placement="top"><span style={{ color: '#0080ff' }}>📶</span></Tooltip>
                <Tooltip text={now.toLocaleString('en-IN', { weekday: 'short', day: 'numeric', month: 'short' })} placement="top">
                  <span style={{ fontVariantNumeric: 'tabular-nums' }}>{timeStr}</span>
                </Tooltip>
              </div>
            </div>

            {/* PWA install prompt — Android Chrome (one-tap) / iOS Safari (instructions) */}
            <InstallPrompt />
          </>
        )}
      </div>

      {/* Tweaks panel */}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Wallpaper" />
        <TweakRadio label="Variant" value={t.wallpaper}
          options={['car', 'statue', 'grid']}
          onChange={v => setTweak('wallpaper', v)} />
        <TweakSection label="Icons" />
        <TweakRadio label="Style" value={t.iconStyle}
          options={['pixel', 'flat', 'emoji']}
          onChange={v => setTweak('iconStyle', v)} />
        <TweakRadio label="Density" value={t.density}
          options={['compact', 'regular', 'roomy']}
          onChange={v => setTweak('density', v)} />
        <TweakSection label="Theme" />
        <TweakRadio label="Color" value={t.theme}
          options={['dusk', 'teal', 'sunset']}
          onChange={v => setTweak('theme', v)} />
        <TweakSection label="Cart" />
        <TweakRadio label="Free ship" value={String(t.shippingThreshold)}
          options={[{ value: '100', label: '₹100' }, { value: '200', label: '₹200' }, { value: '500', label: '₹500' }]}
          onChange={v => setTweak('shippingThreshold', Number(v))} />
        <TweakSection label="Popups" />
        <TweakToggle label="Music Player" value={t.showMusicPlayer}
          onChange={v => { setTweak('showMusicPlayer', v); v ? openWindow('music') : closeWindow('music', { silent: true }); }} />
        <TweakToggle label="Newsletter" value={t.showNewsletter}
          onChange={v => { setTweak('showNewsletter', v); v ? openWindow('newsletter') : closeWindow('newsletter', { silent: true }); }} />
        <TweakToggle label="Recycle Bin" value={t.showRecycleBin}
          onChange={v => { setTweak('showRecycleBin', v); v ? openWindow('recycle') : closeWindow('recycle', { silent: true }); }} />
        <TweakToggle label="Promo Tiles" value={t.showPromo}
          onChange={v => { setTweak('showPromo', v); v ? openWindow('promo') : closeWindow('promo', { silent: true }); }} />
        <TweakToggle label="Video" value={t.showVideo}
          onChange={v => { setTweak('showVideo', v); v ? openWindow('video') : closeWindow('video', { silent: true }); }} />
        <TweakToggle label="Marquee" value={t.showMarquee}
          onChange={v => { setTweak('showMarquee', v); v ? openWindow('marquee') : closeWindow('marquee', { silent: true }); }} />
        <TweakSection label="FX" />
        <TweakToggle label="CRT scanlines" value={t.scanlines} onChange={v => setTweak('scanlines', v)} />
        <TweakToggle label="Sound" value={t.sound} onChange={v => setTweak('sound', v)} />
        <TweakToggle label="Boot sequence" value={t.boot} onChange={v => setTweak('boot', v)} />
        <TweakButton label="Reboot" onClick={() => { setBooted(false); setWindows([]); }} />
      </TweaksPanel>
    </div>
  );
}

const WINDOW_META = {
  music:       { title: 'AstralFM', icon: <Icon name="music" size={14} />, x: 60, y: 80, width: 240 },
  welcome:     { title: '★ welcome.exe ★', marquee: true, icon: <Icon name="star" size={14} />, x: 16, y: 60, width: 320 },
  newsletter:  { title: '★ 5% OFF — subscribe ★', marquee: true, icon: <Icon name="mail" size={14} />, x: 30, y: 200, width: 280 },
  recycle:     { title: 'Recycle Bin', icon: <Icon name="recycle-full" size={14} />, x: 40, y: 120, width: 290, height: 240 },
  promo:       { title: '★ NEW DROPS ★', icon: <Icon name="star" size={14} />, x: 50, y: 90, width: 220 },
  video:       { title: 'lookbook.avi', icon: <Icon name="cd" size={14} />, x: 25, y: 140, width: 280 },
  marquee:     { title: 'system.exe', marquee: true, x: 8, y: 100, width: 350 },
  shop:        { title: 'Shop — All Products', icon: <Icon name="shop" size={14} />, x: 12, y: 70, width: 340, height: 480, statusbar: ['ONLINE', `${PRODUCTS.length} items in orbit`, '★'] },
  collections: { title: 'Collections', icon: <Icon name="folder-pink" size={14} />, x: 25, y: 80, width: 320, height: 360, statusbar: [`${COLLECTIONS.length} folders`, 'My Computer'] },
  'collection-view': { title: 'Collection', icon: <Icon name="folder-pink" size={14} />, x: 18, y: 90, width: 330, height: 460 },
  'product-view': { title: 'Product', icon: <Icon name="tshirt" size={14} />, x: 22, y: 60, width: 360, height: 540, statusbar: ['Product detail', '★'] },
  lookbook:    { title: 'Lookbook — Spring 95', icon: <Icon name="camera" size={14} />, x: 18, y: 90, width: 330, height: 420 },
  'lookbook-image': { title: 'Image Viewer', icon: <Icon name="camera" size={14} />, x: 24, y: 70, width: 340, height: 480 },
  about:       { title: 'About.txt — Notepad', icon: <Icon name="notepad" size={14} />, x: 30, y: 100, width: 310, height: 380 },
  portfolio:   { title: 'Portfolio — kushagra.exe', icon: <Icon name="portfolio" size={14} />, x: 16, y: 60, width: 420, height: 540, statusbar: ['ARCADE MODE', '6 YEARS', '★ press start ★'] },
  contact:     { title: 'CHAT.EXE — #help', icon: <Icon name="mail" size={14} />, x: 28, y: 80, width: 340, height: 440, statusbar: ['#help', '2 online', '★'] },
  cart:        { title: 'Shopping Cart', icon: <Icon name="cart" size={14} />, x: 20, y: 90, width: 320, height: 440 },
  checkout:    { title: 'Checkout', icon: <Icon name="cart" size={14} />, x: 18, y: 70, width: 340, height: 580, statusbar: ['SECURE LINK', '★ Razorpay ★'] },
  'order-success': { title: 'Order Confirmed', icon: <Icon name="star" size={14} />, x: 30, y: 120, width: 300, height: 280 },
  track:       { title: 'Track Order', icon: <Icon name="globe" size={14} />, x: 25, y: 100, width: 320, height: 360 },
  soon:        { title: 'coming_soon.exe', icon: <Icon name="cd" size={14} />, x: 40, y: 130, width: 280, height: 280 },
  help:        { title: 'Help — Knowledge Base', icon: <Icon name="help" size={14} />, x: 18, y: 70, width: 340, height: 520, statusbar: ['HELP', 'FAQ', 'SHIPPING', 'RETURNS', 'SIZES'] },
  login:       { title: 'Sign in', icon: <Icon name="help" size={14} />, x: 30, y: 110, width: 280, height: 280 },
  admin:       { title: 'Admin.exe', icon: <Icon name="computer" size={14} />, x: 6, y: 50, width: 350, height: 580, statusbar: ['ADMIN PANEL', 'apwebsite-54469'] },
  'file-manager': { title: 'My Computer — C:\\', icon: <Icon name="computer" size={14} />, x: 14, y: 60, width: 340, height: 460 },
  minesweeper: { title: 'WINMINE.EXE', icon: <Icon name="cd" size={14} />, x: 30, y: 60, width: 240, height: 360, statusbar: ['9×9', '10 mines', '★'] },
};

function DesktopIcon({ label, iconName, iconStyle, onOpen, style }) {
  const [selected, setSelected] = React.useState(false);
  const lastTap = React.useRef(0);

  const handleClick = (e) => {
    e.stopPropagation();
    const now = Date.now();
    if (now - lastTap.current < 400) {
      onOpen();
      setSelected(false);
    } else {
      setSelected(true);
      setTimeout(() => setSelected(false), 1500);
      // mobile: single tap opens too for usability
      onOpen();
    }
    lastTap.current = now;
  };

  return (
    <Tooltip text={`Open ${label}`} placement="bottom">
      <div className={`desktop-icon ${selected ? 'selected' : ''}`}
           style={style} onClick={handleClick}>
        <div className="icon-img">
          <Icon name={iconName} size={36} style={iconStyle} />
        </div>
        <div className="icon-label">{label}</div>
      </div>
    </Tooltip>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(
  <ErrorBoundary><App /></ErrorBoundary>
);
