// app.jsx — PM Portfolio for Ionut "Johnny" Pogacean
// NASA Graphics Standards Manual (1976) × Linear restraint.

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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "auto",
  "displayFont": "helvetica",
  "grain": 0.05,
  "marqueeSpeed": 100
} /*EDITMODE-END*/;

// Resolve "auto" → "light"/"dark" based on local hour.
// Day: 07:00–18:59. Night: 17:00–06:59.
function resolveTheme(theme) {
  if (theme === "light" || theme === "dark") return theme;
  const h = new Date().getHours();
  return h >= 7 && h < 17 ? "light" : "dark";
}

// ── Content ──────────────────────────────────────────────────────────────────

const NAME = "Ionut “Johnny” Pogacean";
const NAME_DISPLAY = ["IONUT", "POGACEAN"];
const YEAR_EST = "2016";
const EMAIL = "ionut.pogacean@gmail.com";

const POSITIONING = [
"Twenty years inside media and entertainment tech. Started as an engineer, became a product manager, and the engineering instinct never really left.",
"Currently shipping at Fonn Group; before that, Sky and Vizrt."
];


const TOOLS = [
"AHA", "JIRA", "CONFLUENCE", "FIGMA", "NOTION",
"LOOKER", "SQL", "UNREAL ENGINE", "VIZ ENGINE", "AWS", "ANSIBLE"];


const PROJECTS = [
{ no: "01", title: "Workflow Delivery", company: "Fonn Group", year: "2026",
  outcome: "Owning end-to-end delivery for PLS and Sky News UK 2030 workflow deployments.",
  detail: "Translating complex broadcast requirements into structured delivery plans and measurable milestones. Acting as the primary bridge between client stakeholders and engineering, ensuring editorial priorities drive solution design over purely technical constraints." },
{ no: "02", title: "Editorial & Control Solutions", company: "Vizrt", year: "2025",
  outcome: "Grew ARR 35% and the active customer base from 60 to 180+ enterprise users (3×).",
  detail: "Senior PM for the newsroom and control room tools used daily by broadcasters worldwide. Drove the strategy and vision for the portfolio, prioritising features through direct customer feedback loops and adoption analytics. Led cross-functional teams across product, engineering, design, QA, and marketing." },
{ no: "03", title: "Virtual Productions on AWS", company: "Sky UK", year: "2021",
  outcome: "Spearheaded cloud and on-prem virtual production deployments; reduced support calls 25% via playbooks.",
  detail: "Designed automated workflows via Ansible, significantly reducing spin-up time and manual setup. Built technical playbooks and SOPs that freed teams to focus on product improvement over incident response." },
{ no: "04", title: "Sky News US Election 2020", company: "Sky UK", year: "2020",
  outcome: "Shaped the on-air experience watched by millions, integrating real-time data, on-screen graphics, and AR.",
  detail: "Led the project delivery team responsible for architecture and deployment of real-time broadcast graphics solutions. Ensured reliability at scale during peak audience moments, working alongside editorial, design, and data teams.",
  links: [
    { label: "Watch on Vimeo", href: "https://vimeo.com/480808527" },
    { label: "Watch on YouTube", href: "https://youtu.be/gUYMKHTaPMA" }
  ] },
{ no: "05", title: "Premier League Matchday On-site Virtual Set", company: "Sky UK", year: "2019",
  outcome: "Delivered the matchday visual experience for millions of Sky Sports viewers.",
  detail: "Integrated Unreal Engine, Viz Engine, and optical tracking to enhance storytelling and engagement. Deployable virtual set, on-screen graphics, and live data integration for the Premier League season.",
  links: [{ label: "Watch on YouTube", href: "https://www.youtube.com/watch?v=SmiRoUEOPAk" }] },
{ no: "06", title: "Sky News UK General Election 2019", company: "Sky UK", year: "2019",
  outcome: "Delivered the on-air graphics system for the Sky News UK General Election, watched by millions.",
  detail: "Real-time AR graphics integrated with Technocrane and Spidercam, alongside live data feeds for election results coverage. End-to-end delivery for a high-stakes overnight broadcast.",
  links: [{ label: "Watch on YouTube", href: "https://www.youtube.com/watch?v=nBRkC6wAkFU" }] }];


const BIO = [
"My strongest work has always been close to the engineering and creative teams. Specs that hold up when the deadline is a live broadcast. No take twos. Comfortable when the answer is months of unglamorous work followed by one week of visible impact.",
"I have a pretty good sense of when to be firm and when to be flexible. I found collaboration and being heard gets the most traction and buy-in over dictating, because what matters is shipping the best product. Best idea should win.",
"Based in London. Spent years doing improv on stage outside work, which turned out to be better PM training than any course. Listen first, commit to the choice, work with whatever your scene partner gives you."];


const SKILLS = [
["DOMAIN", "Broadcast, Media and Entertainment, live media, B2B SaaS"],
["STACK", "Aha! · Jira · SQL · Figma · Notion"],
["METHOD", "Discovery, JTBD, OKRs, roadmapping"],
["SHIPPED", "35% ARR · 200% customer growth"],
["TEAMS", "Cross-functional product, Marketing, Engineering, Design, QA"],
["LANGUAGES", "EN · RO"],
["BASED", "London, UK"],
["EST.", "2016"]];


const NOW = [
{ label: "WORKING ON", text: "Premier League and Sky News UK 2030 workflow deployments at Fonn Group." },
{ label: "BUILDING", text: "A terminal app for running commands across multiple servers in parallel." },
{ label: "READING", text: "Working Backwards: by Colin Bryar, Bill Carr"},
{ label: "EXPLORING", text: "Learning more about custom Claude Skills: how to develop, maintain and improve them." }];

const POSTS = [
{ no: "01",
  slug: "hello-and-welcome",
  date: "MAY 2026",
  title: "Hello, and welcome",
  excerpt: "A short note to kick things off." },
{ no: "02",
  slug: "improv-and-pm",
  date: "MAY 2026",
  title: "Improv and Product Management",
  excerpt: "How improv comedy has influenced my approach to product management." }
]


const LINKS = [
{ label: "LINKEDIN", href: "https://linkedin.com/in/ionutpogacean" },
{ label: "EMAIL", href: "mailto:ionut.pogacean@gmail.com?subject=Hello%2C%20Johnny" }
];


const LAST_UPDATED = "MAY 2026";

const FONTS = {
  helvetica: '"Helvetica Neue", "Helvetica", "Arial", system-ui, sans-serif',
  inter: '"Inter Tight", "Inter", system-ui, sans-serif',
  sohne: '"Space Grotesk", "Inter Tight", system-ui, sans-serif'
};

// ── Helpers ──────────────────────────────────────────────────────────────────

function CornerBrackets({ size = 14, inset = 0 }) {
  const s = size;
  const off = inset;
  const armV = { position: "absolute", background: "currentColor", width: 1, height: s };
  const armH = { position: "absolute", background: "currentColor", width: s, height: 1 };
  const wrap = (pos) => ({ position: "absolute", width: s, height: s, pointerEvents: "none", ...pos });
  return (
    <React.Fragment>
      <div style={wrap({ top: off, left: off })}>
        <div style={{ ...armV, top: 0, left: 0 }} />
        <div style={{ ...armH, top: 0, left: 0 }} />
      </div>
      <div style={wrap({ top: off, right: off })}>
        <div style={{ ...armV, top: 0, right: 0 }} />
        <div style={{ ...armH, top: 0, right: 0 }} />
      </div>
      <div style={wrap({ bottom: off, left: off })}>
        <div style={{ ...armV, bottom: 0, left: 0 }} />
        <div style={{ ...armH, bottom: 0, left: 0 }} />
      </div>
      <div style={wrap({ bottom: off, right: off })}>
        <div style={{ ...armV, bottom: 0, right: 0 }} />
        <div style={{ ...armH, bottom: 0, right: 0 }} />
      </div>
    </React.Fragment>);

}

function NoiseOverlay({ opacity }) {
  return (
    <svg aria-hidden="true" style={{
      position: "fixed", inset: 0, width: "100%", height: "100%",
      pointerEvents: "none", opacity, mixBlendMode: "multiply", zIndex: 1
    }}>
      <filter id="pmnoise">
        <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" stitchTiles="stitch" />
        <feColorMatrix values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.55 0" />
      </filter>
      <rect width="100%" height="100%" filter="url(#pmnoise)" />
    </svg>);

}

function SectionNo({ n, label }) {
  return (
    <div className="sectno mono">
      <span>{n}</span>
      <span className="sectno-slash">/</span>
      <span>{label}</span>
    </div>);

}

// ── Sections ─────────────────────────────────────────────────────────────────

function Hero({ marqueeSpeed }) {
  return (
    <section id="hero" className="band hero" data-screen-label="01 Hero">
      <CornerBrackets size={16} inset={24} />
      <SectionNo n="00" label="INDEX" />
      <div className="hero-mono mono">
        PRODUCT MANAGER &middot; LONDON &middot; EST. {YEAR_EST}
      </div>
      <h1 className="hero-name">
        <span className="hero-line">{NAME_DISPLAY[0]}</span>
        <span className="hero-line hero-accent">{NAME_DISPLAY[1]}</span>
      </h1>
      <div className="hero-positioning">
        <p>{POSITIONING[0]}</p>
        <p>{POSITIONING[1]}</p>
      </div>
      <div className="hero-meta mono">
        <div><span className="dim">FILE</span>&nbsp;&nbsp;PORTFOLIO / 2026</div>
        <div><span className="dim">REV.</span>&nbsp;&nbsp;14</div>
        <div><span className="dim">SHEET</span>&nbsp;&nbsp;01 OF 05</div>
      </div>
      <Marquee items={TOOLS} speed={marqueeSpeed} />
    </section>);

}

function Marquee({ items, speed }) {
  const duration = Math.max(15, 9000 / speed);
  const repeated = [...items, ...items, ...items];
  return (
    <div className="marquee" aria-hidden="true">
      <div className="marquee-track mono" style={{ animationDuration: `${duration}s` }}>
        {repeated.map((t, i) =>
        <span key={i} className="marquee-item">
            <span>{t}</span>
            <span className="marquee-dot">&middot;</span>
          </span>
        )}
      </div>
    </div>);

}

function Work() {
  const [hover, setHover] = useState(null);
  return (
    <section id="work" className="band work" data-screen-label="04 Work">
      <SectionNo n="03" label="WORK" />
      <h2 className="band-title">Selected work</h2>
      <p className="band-deck">Six projects, 2019&ndash;2025. One-line outcomes; ask for the long version.</p>
      <ol className="work-list">
        {PROJECTS.map((p, i) =>
        <li key={p.no}
        className={`work-row ${hover === i ? "is-hover" : ""}`}
        onMouseEnter={() => setHover(i)}
        onMouseLeave={() => setHover(null)}
        onFocus={() => setHover(i)}
        onBlur={() => setHover(null)}
        tabIndex={0}>
            <CornerBrackets size={10} inset={0} />
            <div className="work-no mono">{p.no}</div>
            <div className="work-main">
              <div className="work-head">
                <h3 className="work-title">{p.title}</h3>
                <div className="work-company mono">{p.company}</div>
              </div>
              <div className="work-outcome">{p.outcome}</div>
              <div className="work-detail">{p.detail}</div>
              {p.links && p.links.map(l =>
                <a key={l.href} className="work-link mono" href={l.href} target="_blank" rel="noopener noreferrer">{l.label} &rarr;</a>
              )}
            </div>
            <div className="work-thumb" aria-hidden={p.links ? undefined : "true"}>
              {p.links ? <VideoThumb links={p.links} /> : <Thumb i={i} />}
            </div>
            <div className="work-year mono">{p.year}</div>
          </li>
        )}
      </ol>
    </section>);

}

function Thumb({ i }) {
  const variants = [
  <svg viewBox="0 0 80 60" key={0}>
      <circle cx="40" cy="30" r="22" fill="none" stroke="currentColor" strokeWidth="0.6" />
      <circle cx="40" cy="30" r="14" fill="none" stroke="currentColor" strokeWidth="0.6" />
      <circle cx="40" cy="30" r="6" fill="currentColor" />
      <line x1="4" y1="30" x2="76" y2="30" stroke="currentColor" strokeWidth="0.4" strokeDasharray="2 2" />
    </svg>,
  <svg viewBox="0 0 80 60" key={1}>
      <polygon points="8,8 72,8 50,32 50,52 30,52 30,32" fill="none" stroke="currentColor" strokeWidth="0.6" />
      <line x1="8" y1="20" x2="72" y2="20" stroke="currentColor" strokeWidth="0.4" />
      <line x1="14" y1="14" x2="66" y2="14" stroke="currentColor" strokeWidth="0.3" />
    </svg>,
  <svg viewBox="0 0 80 60" key={2}>
      <rect x="6" y="14" width="14" height="32" fill="none" stroke="currentColor" strokeWidth="0.6" />
      <rect x="22" y="20" width="14" height="26" fill="none" stroke="currentColor" strokeWidth="0.6" />
      <rect x="38" y="10" width="14" height="36" fill="currentColor" />
      <rect x="54" y="22" width="14" height="24" fill="none" stroke="currentColor" strokeWidth="0.6" />
      <line x1="4" y1="46" x2="76" y2="46" stroke="currentColor" strokeWidth="0.4" />
    </svg>,
  <svg viewBox="0 0 80 60" key={3}>
      <rect x="6" y="8" width="68" height="44" fill="none" stroke="currentColor" strokeWidth="0.6" />
      {[0, 1, 2].map((r) =>
    [0, 1, 2, 3].map((c) =>
    <rect key={`${r}-${c}`} x={10 + c * 16} y={12 + r * 12} width="12" height="8"
    fill={r === 1 && c === 2 ? "currentColor" : "none"}
    stroke="currentColor" strokeWidth="0.3" />
    )
    )}
    </svg>,
  <svg viewBox="0 0 80 60" key={4}>
      <line x1="8" y1="50" x2="72" y2="50" stroke="currentColor" strokeWidth="0.4" />
      <line x1="8" y1="50" x2="8" y2="8" stroke="currentColor" strokeWidth="0.4" />
      {[[14, 42], [20, 36], [28, 30], [36, 38], [42, 22], [48, 26], [54, 16], [60, 20], [66, 12]].map(([x, y], j) =>
    <circle key={j} cx={x} cy={y} r="1.2" fill="currentColor" />
    )}
      <line x1="10" y1="48" x2="70" y2="14" stroke="currentColor" strokeWidth="0.4" strokeDasharray="2 2" />
    </svg>,
  <svg viewBox="0 0 80 60" key={5}>
      {[0, 1, 2, 3].map((j) =>
    <React.Fragment key={j}>
          <rect x={6 + j * 18} y="22" width="14" height="16"
      fill={j === 2 ? "currentColor" : "none"} stroke="currentColor" strokeWidth="0.6" />
          {j < 3 && <line x1={20 + j * 18} y1="30" x2={24 + j * 18} y2="30" stroke="currentColor" strokeWidth="0.4" />}
        </React.Fragment>
    )}
    </svg>];

  return variants[i % variants.length];
}

function resolveThumbnail(url) {
  const yt = url.match(/[?&]v=([^&]+)/) || url.match(/youtu\.be\/([^?&]+)/);
  if (yt) return Promise.resolve(`https://img.youtube.com/vi/${yt[1]}/mqdefault.jpg`);
  const vimeo = url.match(/vimeo\.com\/(\d+)/);
  if (vimeo) {
    return fetch(`https://vimeo.com/api/oembed.json?url=${encodeURIComponent(url)}`)
      .then(r => r.json()).then(d => d.thumbnail_url).catch(() => null);
  }
  return Promise.resolve(null);
}

function VideoThumb({ links }) {
  const [srcs, setSrcs] = useState([]);
  useEffect(() => {
    Promise.all(links.map(l => resolveThumbnail(l.href))).then(setSrcs);
  }, [links]);
  if (!srcs.length) return <Thumb i={0} />;
  return (
    <div className="work-video-thumbs">
      {links.map((link, i) => srcs[i] &&
        <a key={link.href} href={link.href} target="_blank" rel="noopener noreferrer"
           className="work-video-thumb" aria-label={link.label}>
          <img src={srcs[i]} alt="" />
          <div className="work-video-play" aria-hidden="true">
            <svg viewBox="0 0 24 24" width="14" height="14">
              <polygon points="6,4 20,12 6,20" fill="currentColor" />
            </svg>
          </div>
        </a>
      )}
    </div>);
}

function About() {
  return (
    <section id="about" className="band about alt" data-screen-label="05 About">
      <SectionNo n="04" label="ABOUT" />
      <h2 className="band-title">About</h2>
      <div className="about-grid">
        <div className="portrait">
          <CornerBrackets size={12} inset={0} />
          <img
            src="portrait.jpg"
            alt="Ionut Pogacean"
            className="portrait-img" />

          <div className="portrait-cap mono">JOHNNY</div>
        </div>
        <div className="bio">
          {BIO.map((p, i) =>
          <p key={i} className={i === 1 ? "bio-emph" : ""}>{p}</p>
          )}
          <div className="skills">
            <div className="skills-head mono">
              <span>SPECIFICATIONS</span>
              <span className="dim">REV. 14 / {LAST_UPDATED}</span>
            </div>
            <dl className="skills-list mono">
              {SKILLS.map(([k, v]) =>
              <div key={k} className="skills-row">
                  <dt>{k}</dt>
                  <dd>{v}</dd>
                </div>
              )}
            </dl>
          </div>
        </div>
      </div>
    </section>);

}

function NowSection() {
  return (
    <section id="now" className="band now" data-screen-label="02 Now">
      <SectionNo n="01" label="NOW" />
      <h2 className="band-title">Now</h2>
      <p className="band-deck">As of {LAST_UPDATED}.</p>
      <ul className="now-list">
        {NOW.map((row) =>
        <li key={row.label} className="now-row">
            <span className="now-label mono">{row.label}</span>
            <span className="now-text">{row.text}</span>
          </li>
        )}
      </ul>
    </section>);

}

function Posts() {
  const visible = POSTS.slice(0, 3);
  if (visible.length === 0) return null;
  return (
    <section id="posts" className="band posts" data-screen-label="03 Posts">
      <SectionNo n="02" label="POSTS" />
      <h2 className="band-title">Posts</h2>
      <p className="band-deck">Notes and thoughts on product, broadcast, and what catches my attention. Latest first.</p>
      <ol className="posts-list">
        {visible.map((p) =>
        <li key={p.no} className="posts-row">
            <CornerBrackets size={10} inset={0} />
            <a className="posts-toggle" href={`posts/${p.slug}.html`}>
              <div className="posts-no mono">{p.no}</div>
              <div className="posts-main">
                <div className="posts-head">
                  <h3 className="posts-title">{p.title}</h3>
                  <div className="posts-date mono">{p.date}</div>
                </div>
                <div className="posts-excerpt">{p.excerpt}</div>
              </div>
              <div className="posts-chevron mono" aria-hidden="true">&#8599;</div>
            </a>
          </li>
        )}
      </ol>
    </section>);

}

function Contact() {
  return (
    <section id="contact" className="band contact alt" data-screen-label="05 Contact">
      <SectionNo n="05" label="CONTACT" />
      <h2 className="band-title">Get in touch</h2>
{/*       <a className="email" href={`mailto:${EMAIL}`}>
        <span>{EMAIL}</span>
        <span className="email-arrow">&#8599;</span>
      </a> */}
      <div className="links mono">
        {LINKS.map((l, i) =>
        <a key={l.label} href={l.href} className="link-pill">
            <span className="link-no">{String(i + 1).padStart(2, "0")}</span>
            <span>{l.label}</span>
            <span className="link-arrow">&#8599;</span>
          </a>
        )}
      </div>
      <footer className="footer mono">
        <div>&copy; {NAME.toUpperCase()} &middot; LONDON</div>
        <div>LAST UPDATED {LAST_UPDATED}</div>
        <div className="footer-meta">SHEET 05 OF 05 &middot; END</div>
      </footer>
    </section>);

}

function Nav() {
  return (
    <nav className="nav mono">
      <a href="#hero" className="nav-name">{NAME.toUpperCase()}</a>
      <ul>
        <li><a href="#now">01 NOW</a></li>
        <li><a href="#posts">02 POSTS</a></li>
        <li><a href="#work">03 WORK</a></li>
        <li><a href="#about">04 ABOUT</a></li>
        <li><a href="#contact">05 CONTACT</a></li>
      </ul>
    </nav>);

}

// ── App ──────────────────────────────────────────────────────────────────────

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [resolved, setResolved] = useState(() => resolveTheme(t.theme));
useEffect(() => {
  const hash = window.location.hash.slice(1);
  if (!hash) return;
  setTimeout(() => {
    const el = document.getElementById(hash);
    if (el) el.scrollIntoView({ block: "start" });
  }, 50);
}, []);
  useEffect(() => {
    const update = () => setResolved(resolveTheme(t.theme));
    update();
    const id = setInterval(update, 60_000);
    return () => clearInterval(id);
  }, [t.theme]);

  useEffect(() => {
    const root = document.documentElement;
    if (resolved === "dark") {
      root.style.setProperty("--bg", "#10131A");
      root.style.setProperty("--bg-alt", "#171B23");
      root.style.setProperty("--ink", "#DDE2EA");
      root.style.setProperty("--ink-mute", "#7A7E86");
      root.style.setProperty("--rule", "rgba(221,226,234,0.14)");
    } else {
      root.style.setProperty("--bg", "#E2E2DA");
      root.style.setProperty("--bg-alt", "#D6D6CD");
      root.style.setProperty("--ink", "#0A0A0C");
      root.style.setProperty("--ink-mute", "#6A6A66");
      root.style.setProperty("--rule", "rgba(10,10,12,0.14)");
    }
    root.style.setProperty("--font-display", FONTS[t.displayFont] || FONTS.helvetica);
  }, [resolved, t.displayFont]);

  const autoLabel = (() => {
    if (t.theme !== "auto") return null;
    const h = new Date().getHours();
    return `Auto — ${resolved} (local ${String(h).padStart(2, "0")}:00)`;
  })();

  return (
    <div className="page">
      <NoiseOverlay opacity={t.grain} />
      <Nav />
      <main>
        <Hero marqueeSpeed={t.marqueeSpeed} />
        <NowSection />
        <Posts />
        <Work />
        <About />
        <Contact />
      </main>
      <TweaksPanel>
        <TweakSection label="Theme" />
        <TweakRadio label="Mode" value={t.theme}
        options={[
        { value: "auto", label: "Auto" },
        { value: "light", label: "Day" },
        { value: "dark", label: "Night" }]
        }
        onChange={(v) => setTweak("theme", v)} />
        {autoLabel &&
        <div className="mono" style={{
          fontSize: 9, letterSpacing: "0.1em",
          color: "rgba(41,38,27,.55)", marginTop: -2
        }}>{autoLabel}</div>
        }
        <TweakSection label="Type" />
        <TweakRadio label="Display" value={t.displayFont}
        options={[
        { value: "helvetica", label: "Helv." },
        { value: "inter", label: "Inter" },
        { value: "sohne", label: "Söhne" }]
        }
        onChange={(v) => setTweak("displayFont", v)} />
        <TweakSection label="Texture" />
        <TweakSlider label="Grain" value={t.grain} min={0} max={0.18} step={0.01}
        format={(v) => `${Math.round(v * 100)}%`}
        onChange={(v) => setTweak("grain", v)} />
        <TweakSection label="Motion" />
        <TweakSlider label="Marquee speed" value={t.marqueeSpeed}
        min={20} max={140} step={5}
        format={(v) => `${v}`}
        onChange={(v) => setTweak("marqueeSpeed", v)} />
      </TweaksPanel>
    </div>);

}

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