// app.jsx — Budget Engineer

function timeAgo(iso) {
  const diff = (Date.now() - new Date(iso)) / 1000;
  if (diff < 60) return 'just now';
  if (diff < 3600) return `${Math.floor(diff / 60)}m`;
  if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
  if (diff < 604800) return `${Math.floor(diff / 86400)}d`;
  return `${Math.floor(diff / 604800)}w`;
}

const PALETTES = {
  'purple-green': {
    name: 'Purple + Neon (default)',
    bg: '#0b0612', bg2: '#110a1f', surface: '#170d2b', surface2: '#1d1235',
    border: '#2a1a4a', borderSoft: '#1f1437',
    purple: '#7c2dc7', purpleDeep: '#4a1284', purpleGlow: '#b466ff',
    neon: '#39ff14', neonSoft: '#6cff52', neonDim: '#1a8d09',
    text: '#e8e6f0', textMuted: '#8a85a3', textDim: '#5a557a',
  },
  'amber-crt': {
    name: 'Amber CRT',
    bg: '#0d0903', bg2: '#150e04', surface: '#1a1106', surface2: '#22170a',
    border: '#3a2710', borderSoft: '#251a0c',
    purple: '#b25e10', purpleDeep: '#6b3a08', purpleGlow: '#ff9a3c',
    neon: '#ffba49', neonSoft: '#ffd07a', neonDim: '#8a5e1a',
    text: '#f5e8cc', textMuted: '#a08a66', textDim: '#5a4a30',
  },
  'mono-green': {
    name: 'Mono Green',
    bg: '#031006', bg2: '#06180a', surface: '#0a200f', surface2: '#0e2814',
    border: '#1a4524', borderSoft: '#13321b',
    purple: '#2e7045', purpleDeep: '#1a4a2a', purpleGlow: '#76d29a',
    neon: '#5bff8b', neonSoft: '#9affb6', neonDim: '#1f6b35',
    text: '#dfffe6', textMuted: '#7aa085', textDim: '#446b55',
  },
};

function applyPalette(p) {
  const root = document.documentElement;
  root.style.setProperty('--bg', p.bg);
  root.style.setProperty('--bg-2', p.bg2);
  root.style.setProperty('--surface', p.surface);
  root.style.setProperty('--surface-2', p.surface2);
  root.style.setProperty('--border', p.border);
  root.style.setProperty('--border-soft', p.borderSoft);
  root.style.setProperty('--purple', p.purple);
  root.style.setProperty('--purple-deep', p.purpleDeep);
  root.style.setProperty('--purple-glow', p.purpleGlow);
  root.style.setProperty('--neon', p.neon);
  root.style.setProperty('--neon-soft', p.neonSoft);
  root.style.setProperty('--neon-dim', p.neonDim);
  root.style.setProperty('--text', p.text);
  root.style.setProperty('--text-muted', p.textMuted);
  root.style.setProperty('--text-dim', p.textDim);
}

const PROJECTS = [
  {
    id: 'PRJ-001', title: 'D&D Battle Map', status: 'live',
    desc: 'Browser-based battle map tool for tabletop D&D. Multi-size tokens, real-time multiplayer sync via SocketIO. Flask + SQLite backend.',
    tags: ['Vanilla JS', 'Flask', 'SocketIO', 'SQLite'], year: '2025',
    href: 'https://dnd.budget-engineer.com',
  },
  {
    id: 'PRJ-002', title: 'Weather Station', status: 'live',
    desc: 'ESP32-S3 based outdoor weather station. SparkFun Weather Meter Kit, BME280, solar powered. Publishes to MQTT, feeds Home Assistant and Weather Underground.',
    tags: ['ESP32-S3', 'C++', 'MQTT', 'PlatformIO'], year: '2025',
    href: '#',
  },
  {
    id: 'PRJ-003', title: 'Workshop Dash Mobile', status: 'wip',
    desc: 'React Native / Expo app for battery tracking, parts inventory, project management, and flip tracking. Drizzle ORM + SQLite.',
    tags: ['React Native', 'Expo', 'Drizzle', 'SQLite'], year: '2025',
    href: '#',
  },
  {
    id: 'PRJ-004', title: 'BlissDeck Cyberdeck', status: 'wip',
    desc: 'AMD A12 laptop motherboard in a vintage leather briefcase. DietPi/LXDE, ESP32-S3 running Marauder, RTL-SDR for P25, offline Wikipedia via Kiwix.',
    tags: ['DietPi', 'ESP32-S3', 'RTL-SDR', 'Hardware'], year: '2025',
    href: '#',
  },
  {
    id: 'PRJ-005', title: 'Wireless Macropad', status: 'wip',
    desc: 'BLE-only conversion of existing ESP32-S3 macropad. NimBLE / ESP32-BLE-Keyboard, swappable 18650 pack, 3D-printed enclosure.',
    tags: ['ESP32-S3', 'NimBLE', 'C++', '3D Printing'], year: '2025',
    href: '#',
  },
  {
    id: 'PRJ-006', title: 'Sauron Network Dash', status: 'wip',
    desc: 'React/Vite frontend + FastAPI backend proxying NetAlertX, AdGuard Home, and Uptime Kuma into a single homelab network dashboard.',
    tags: ['React', 'FastAPI', 'Docker', 'Python'], year: '2025',
    href: '#',
  },
];

const DEMOS = [
  {
    name: 'D&D Battle Map COMING SOON ',
    tag: 'MAP',
    desc: 'Browser-based tabletop battle map. Drag tokens, place torches. Try it out.',
    href: 'https://.com',
  },
];

const STATIC_POSTS = [
  { time: '1d', body: 'Making things. Breaking things. Occasionally on purpose.\n\nFollow along for embedded systems, homelab builds, and whatever rabbit hole I fell into this week.', likes: 0, reposts: 0, replies: 0 },
];

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "purple-green",
  "background": "dots",
  "scanlines": false,
  "glitchHover": true,
  "cursorBlink": true,
  "density": "cozy"
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const palette = PALETTES[t.palette] || PALETTES['purple-green'];

  React.useEffect(() => { applyPalette(palette); }, [palette]);
  React.useEffect(() => {
    document.body.classList.toggle('scanlines', !!t.scanlines);
    document.body.classList.toggle('glitch-on', !!t.glitchHover);
    document.body.classList.toggle('density-compact', t.density === 'compact');
  }, [t.scanlines, t.glitchHover, t.density]);

  const [konami, setKonami] = React.useState(false);
  React.useEffect(() => {
    const seq = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];
    let pos = 0;
    function onKey(e) {
  const k = e.key.length === 1 ? e.key.toLowerCase() : e.key;
  if (k === seq[pos]) {
    pos++;
    if (pos === seq.length) {
      pos = 0;
      setKonami(true);
      document.documentElement.style.filter = 'hue-rotate(180deg)';
      console.log('%c✓ konami confirmed. you know your stuff.', 'color:#39ff14;font-family:monospace;font-size:13px');
      setTimeout(() => setKonami(false), 3000);
      setTimeout(() => { document.documentElement.style.filter = ''; }, 4500);
    }
  } else {
    pos = k === seq[0] ? 1 : 0;
  }

    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  return (
    <>
      <BgCanvas mode={t.background} palette={palette} />
      <div className="page">
        <Nav />
        <Hero cursorBlink={t.cursorBlink} />
        <Now />
        <Projects />
        <LiveDemos />
        <Feed />
        <About />
        <Contact />
        <Footer />
      </div>
      {konami && (
        <div className="konami show">
          <div className="title">BUDGET ENGINEER UNLOCKED</div>
          <div>check the console. konami confirmed.</div>
        </div>
      )}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Look" />
        <TweakSelect label="Palette" value={t.palette}
          options={Object.keys(PALETTES).map(k => ({ value: k, label: PALETTES[k].name }))}
          onChange={(v) => setTweak('palette', v)} />
        <TweakSelect label="Background" value={t.background}
          options={[
            { value: 'traces', label: 'PCB traces' },
            { value: 'dots', label: 'Pulse grid' },
            { value: 'hex', label: 'Hex grid' },
            { value: 'mesh', label: 'Mesh + sparks' },
            { value: 'topo', label: 'Topography' },
            { value: 'constellation', label: 'Constellation' },
            { value: 'circuit', label: 'Circuit web' },
            { value: 'matrix', label: 'Matrix rain' },
            { value: 'off', label: 'Off' },
          ]}
          onChange={(v) => setTweak('background', v)} />
        <TweakRadio label="Density" value={t.density} options={['cozy', 'compact']}
          onChange={(v) => setTweak('density', v)} />
        <TweakSection label="Effects" />
        <TweakToggle label="Scanline overlay" value={t.scanlines} onChange={(v) => setTweak('scanlines', v)} />
        <TweakToggle label="Glitch on hover" value={t.glitchHover} onChange={(v) => setTweak('glitchHover', v)} />
        <TweakToggle label="Hero cursor blink" value={t.cursorBlink} onChange={(v) => setTweak('cursorBlink', v)} />
      </TweaksPanel>
    </>
  );
}

function Nav() {
  return (
    <nav className="nav">
      <div className="nav-brand">
        <img src="assets/logo.png" alt="Budget Engineer"
             onError={(e) => { e.target.style.display='none'; }} />
        <span className="b-mark">budget<span>_engineer</span></span>
      </div>
      <div className="nav-links">
        <a href="#now">now</a>
        <a href="#projects">projects</a>
        <a href="#demos">demos</a>
        <a href="#feed">feed</a>
        <a href="#about">about</a>
      </div>
      <div className="nav-status">
        <span className="dot"></span>
        <span>online · building</span>
      </div>
    </nav>
  );
}

function Hero({ cursorBlink }) {
  return (
    <section className="hero container">
      <div className="hero-tag">
        <span className="dot"></span>
        <span> somewhere tinkering · embedded systems · homelab </span>
      </div>
      <h1>
        Making things.<br/>
        <span className="purple glitchable">Breaking</span> things.<br/>
        <span className="accent glitchable">Occasionally</span> on purpose
        {cursorBlink && <span className="crsr" />}
      </h1>
      <p className="hero-sub">
        Builder of things that probably shouldn't work but some how do.
        ESP32 firmware, Docker stacks, DIY electronics, and whatever rabbit hole I fell into this week.
      </p>
      <div className="hero-actions">
        <a className="btn primary" href="#projects">
          See the builds <span className="arrow">→</span>
        </a>
        <a className="btn" href="https://bsky.app/profile/budgetengineer.bsky.social" target="_blank" rel="noreferrer">
          @budgetengineer.bsky.social
        </a>
      </div>
      <div className="hero-meta">
        <div>
          <div className="lbl">Currently</div>
          <div className="val">building <span className="accent">something cool</span></div>
        </div>
      
        <div>
          <div className="lbl">Stack</div>
          <div className="val">C++, Python, React, regret</div>
        </div>
        <div>
          <div className="lbl">Session</div>
          <div className="val"><LiveUptime /></div>
        </div>
      </div>
    </section>
  );
}

function LiveUptime() {
  const [t, setT] = React.useState(0);
  React.useEffect(() => {
    const start = Date.now();
    const i = setInterval(() => setT(Date.now() - start), 1000);
    return () => clearInterval(i);
  }, []);
  const s = Math.floor(t / 1000);
  const m = Math.floor(s / 60);
  return <span>{String(m).padStart(2,'0')}:{String(s % 60).padStart(2,'0')} <span style={{color:'var(--text-dim)'}}>this session</span></span>;
}

function Now() {
  return (
    <section className="sect container" id="now">
      <div className="sect-head">
        <span className="num">01</span>
        <span>now.log</span>
        <span className="line"></span>
        <span>$ tail -f</span>
      </div>
      <h2 className="sect-title glitchable">What I'm doing right now</h2>
      <p className="sect-sub">A live tail of what's on the bench, what's broken, and what's almost working.</p>
      <div className="terminal">
        <div className="term-bar">
          <span className="path">~/<b>now</b>/status.log</span>
          <span style={{marginLeft:'auto'}}>● recording</span>
        </div>
        <div className="term-body">
          <div className="term-line cmd">
            <span className="prompt">be@bench $</span>
            <span className="out">cat status.md</span>
          </div>
          <div className="term-line">
            <span className="prompt">›</span>
            <span className="out"><b>this week:</b> landing page, D&D map static demo, Cloudflare Pages deploy. <span className="ok">ship it.</span></span>
          </div>
          <div className="term-line">
            <span className="prompt">›</span>
            <span className="out"><b>on deck:</b> Workshop Dash Mobile remediation sessions, wireless macropad BLE conversion. <span className="warn">[wip]</span></span>
          </div>
          <div className="term-line">
            <span className="prompt">›</span>
            <span className="out"><b>long game:</b> Weather Station V2 PCB, BlissDeck cyberdeck enclosure, offset smoker build.</span>
          </div>
          <div className="term-line cmd" style={{marginTop:14}}>
            <span className="prompt">be@bench $</span>
            <span className="out" style={{borderRight:'0.5em solid var(--neon)', paddingRight:0, animation:'blink 1.05s steps(1) infinite'}}> </span>
          </div>
        </div>
      </div>
    </section>
  );
}

function Projects() {
  const onMove = (e) => {
    const r = e.currentTarget.getBoundingClientRect();
    e.currentTarget.style.setProperty('--mx', `${e.clientX - r.left}px`);
    e.currentTarget.style.setProperty('--my', `${e.clientY - r.top}px`);
  };
  return (
    <section className="sect container" id="projects">
      <div className="sect-head">
        <span className="num">02</span>
        <span>projects.idx</span>
        <span className="line"></span>
        <span>{PROJECTS.length} entries</span>
      </div>
      <h2 className="sect-title glitchable">Things I've built</h2>
      <p className="sect-sub">Mostly working. Some held together with solder flux and optimism. Live entries link to demos.</p>
      <div className="proj-grid">
        {PROJECTS.map((p) => (
          <a key={p.id} className="proj-card" href={p.href || '#'} onMouseMove={onMove}
             target={p.href && p.href !== '#' ? '_blank' : undefined} rel="noreferrer">
            <div className="proj-head">
              <div>
                <div className="proj-id">{p.id} · {p.year}</div>
                <h3 className="proj-title">{p.title}</h3>
              </div>
              <span className={`proj-status ${p.status}`}>
                <span className="dot"></span>
                {p.status}
              </span>
            </div>
            <p className="proj-desc">{p.desc}</p>
            <div className="proj-tags">
              {p.tags.map(tag => <span key={tag} className="tag">{tag}</span>)}
            </div>
            <div className="proj-foot">
              <span>{p.href && p.href !== '#' ? 'launch demo' : 'in progress'}</span>
              <span className="arrow">{p.href && p.href !== '#' ? '↗' : '…'}</span>
            </div>
          </a>
        ))}
      </div>
    </section>
  );
}

function LiveDemos() {
  return (
    <section className="sect container" id="demos">
      <div className="sect-head">
        <span className="num">03</span>
        <span>demos.live</span>
        <span className="line"></span>
        <span>open in new tab</span>
      </div>
      <h2 className="sect-title glitchable">Live demos</h2>
      <p className="sect-sub">Real running things you can poke at. More coming as builds ship.</p>
      <div className="demo-grid">
        {DEMOS.map((d) => (
          <a key={d.name} className="demo-card" href={d.href} target="_blank" rel="noreferrer">
            <div className="demo-preview">
              <span className="ico">{d.tag}</span>
            </div>
            <div className="demo-meta">
              <span className="demo-name">{d.name}</span>
              <span className="demo-link">launch ↗</span>
            </div>
            <p className="demo-desc">{d.desc}</p>
          </a>
        ))}
      </div>
    </section>
  );
}

function Feed() {
  const [posts, setPosts] = React.useState(null);

  React.useEffect(() => {
    fetch('https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=budgetengineer.bsky.social&limit=4')
      .then(r => r.json())
      .then(data => {
        if (!data.feed || !data.feed.length) { setPosts(STATIC_POSTS); return; }
        const mapped = data.feed
          .filter(item => !item.post.record.reply)
          .slice(0, 4)
          .map(item => ({
            time: timeAgo(item.post.indexedAt),
            body: item.post.record.text,
            likes: item.post.likeCount || 0,
            reposts: item.post.repostCount || 0,
            replies: item.post.replyCount || 0,
            uri: item.post.uri,
          }));
        setPosts(mapped.length ? mapped : STATIC_POSTS);
      })
      .catch(() => setPosts(STATIC_POSTS));
  }, []);

  const postUrl = (uri) => {
    if (!uri) return 'https://bsky.app/profile/budgetengineer.bsky.social';
    const rkey = uri.split('/').pop();
    return `https://bsky.app/profile/budgetengineer.bsky.social/post/${rkey}`;
  };

  return (
    <section className="sect container" id="feed">
      <div className="sect-head">
        <span className="num">04</span>
        <span>bsky.feed</span>
        <span className="line"></span>
        <a href="https://bsky.app/profile/budgetengineer.bsky.social" target="_blank" rel="noreferrer" style={{color:'var(--neon)'}}>
          follow on bluesky →
        </a>
      </div>
      <h2 className="sect-title glitchable">Latest from the build log</h2>
      <p className="sect-sub">Build updates, half-finished thoughts, and the occasional thing that actually worked.</p>
      {!posts ? (
        <div style={{color:'var(--text-dim)',fontFamily:'var(--font-mono)',fontSize:13}}>loading feed...</div>
      ) : (
        <div className="feed-wrap">
          {posts.map((p, i) => (
            <a key={i} className="post" href={postUrl(p.uri)} target="_blank" rel="noreferrer" style={{textDecoration:'none'}}>
              <div className="post-head">
                <div className="post-av">BE</div>
                <div className="post-handle">
                  <div><b>Budget Engineer</b></div>
                  <div><span>@budgetengineer.bsky.social</span></div>
                </div>
                <span className="post-time">{p.time}</span>
              </div>
              <p className="post-body">{p.body}</p>
              <div className="post-stats">
                <span>↺ {p.reposts}</span>
                <span>♡ {p.likes}</span>
                <span>↩ {p.replies}</span>
              </div>
            </a>
          ))}
        </div>
      )}
    </section>
  );
}

function DaysSinceBreaking() {
  const [days, setDays] = React.useState(() => Math.floor(Math.random() * 12) + 1);
  const [secs, setSecs] = React.useState(0);
  React.useEffect(() => {
    const i = setInterval(() => {
      setSecs(s => {
        if (s >= 86400) {
          setDays(d => d + 1);
          return 0;
        }
        // small random chance of reset each second
        if (Math.random() < 0.0003) {
          setDays(0);
          return 0;
        }
        return s + 1;
      });
    }, 1000);
    return () => clearInterval(i);
  }, []);
  return <span>{days}d</span>;
}

function About() {
  return (
    <section className="sect container" id="about">
      <div className="sect-head">
        <span className="num">05</span>
        <span>about.me</span>
        <span className="line"></span>
        <span>cat ~/who</span>
      </div>
      <div className="about-grid">
        <div>
          <h2 className="sect-title glitchable" style={{marginBottom:24}}>About</h2>
          <div className="about-prose">
            <p>
              I'm Budget Engineer — builder of things that probably shouldn't work but do.
              Embedded systems, homelabs, DIY electronics, and whatever rabbit hole I fell into this week.
            </p>
            <p>
              Most of my projects start as "what if I just" and end up as a tangle of ESP32s
              publishing MQTT data to a Home Assistant dashboard at 3am.
            </p>
            <p>
              I share builds on Bluesky and run a Women in Tech history series on the side.
              If you want to follow along, argue about firmware, or just say hi — that's the place.
            </p>
          </div>
        </div>
        <div className="stats">
          <div className="stat"><span className="k">Primary MCU</span><span className="v ok">ESP32-S3</span></div>
          <div className="stat"><span className="k">Homelab nodes</span><span className="v">3 machines</span></div>
          <div className="stat"><span className="k">Containers running</span><span className="v ok">always too many</span></div>
          <div className="stat"><span className="k">Firmware stack</span><span className="v">PlatformIO + Arduino</span></div>
          <div className="stat"><span className="k">Web stack</span><span className="v">React, Flask, FastAPI</span></div>
          <div className="stat"><span className="k">SDR</span><span className="v">RTL-SDR V4 · P25</span></div>
          <div className="stat"><span className="k">Days since breaking something</span><span className="v ok"><DaysSinceBreaking /></span></div>
          
        </div>
      </div>
    </section>
  );
}

function Contact() {
  return (
    <section id="contact">
      <div className="contact">
        <h2><span className="accent glitchable">Say hi.</span> break something.</h2>
        <p>
          Got a cursed ESP32 idea? Want to argue about firmware? Just want to say hi?
          Drop a line — I read everything.
        </p>
        <div className="contact-channels">
          <a className="chan" href="https://bsky.app/profile/budgetengineer.bsky.social" target="_blank" rel="noreferrer">
            <span className="lbl">Bluesky · primary</span>
            <span className="val">@budgetengineer.bsky.social</span>
          </a>
          <a className="chan" href="mailto:hello@budget-engineer.com">
            <span className="lbl">Email</span>
            <span className="val">hello@budget-engineer.com</span>
          </a>
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer className="foot">
      <span>© budget_engineer · built by hand</span>
      <span className="mono-blink">no trackers · no cookies · no nonsense</span>
    </footer>
  );
}

(function bootConsole() {
  const neon = '#39ff14'; const purple = '#b466ff'; const dim = '#8a85a3';
  const banner = [
    '', '   ██████╗ ███████╗', '   ██╔══██╗██╔════╝',
    '   ██████╔╝█████╗  ', '   ██╔══██╗██╔══╝  ',
    '   ██████╔╝███████╗', '   ╚═════╝ ╚══════╝',
    '   budget _ engineer', ''
  ].join('\n');
  try {
    console.log(`%c${banner}`, `color:${neon};font-family:monospace;font-size:11px;line-height:1.1`);
    console.log('%cwell well well.', `color:${purple};font-weight:bold;font-size:14px`);
    console.log('%cif you\'re reading this, you probably build things too.', `color:${dim}`);
    console.log('%c→ try the konami code. ↑↑↓↓←→←→ b a', `color:${neon}`);
    console.log('%c→ say hi: @budgetengineer.bsky.social', `color:${neon}`);
  } catch(e) {}
})();

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