// dthas — Work page. Selected engagements, development-led and ordered by weight.
// Clients/employers are kept discreet where appropriate; the studio's own projects
// are named. Shares the design system, Section scaffolding, shared nav and Footer.

const { Overline, Tag, Button } = window.DesignSystem_aa2f04;

function WorkReveal({ children }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('is-in'); io.unobserve(e.target); } });
    }, { threshold: 0.12 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return <div ref={ref} className="reveal">{children}</div>;
}

// Inverted page header — bookends with the footer.
function WorkHeader() {
  return (
    <header id="top" style={{
      background: 'var(--c-black)', color: 'var(--c-white)',
      borderBottom: '1px solid var(--c-line-invert)',
    }}>
      <div style={{
        maxWidth: 'var(--container-max)', margin: '0 auto',
        padding: 'clamp(56px, 10vh, 128px) var(--container-pad) clamp(48px, 8vh, 96px)',
      }}>
        <div style={{
          fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-overline)',
          letterSpacing: 'var(--ls-wide)', textTransform: 'uppercase',
          color: 'var(--c-paper-500)', marginBottom: 'clamp(32px, 6vh, 72px)',
          display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: '12px',
        }}>
          <span>Selected work</span>
        </div>

        <h1 style={{
          margin: 0, fontWeight: 400,
          fontSize: 'var(--fs-h1)', lineHeight: 1.04,
          letterSpacing: '-0.04em', maxWidth: '16ch',
        }}>Shipped, in production.</h1>

        <p style={{
          margin: 'clamp(28px, 5vh, 48px) 0 0', maxWidth: '54ch',
          fontSize: 'var(--fs-lead)', lineHeight: 1.45,
          letterSpacing: '-0.01em', color: 'var(--c-paper-700)',
        }}>
          A sample of delivered engagements across trading, identity, deployment and
          the web. Some detail is kept deliberately light; the work is real.
        </p>
      </div>
    </header>
  );
}

// ── Shared bits for the per-project monoline diagrams ──────────────────────
function diagReduce() {
  return typeof window !== 'undefined' && window.matchMedia &&
    window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
const DIAG_LABEL = { fontFamily: 'var(--font-mono)', fontSize: '8px', letterSpacing: '0.06em', fill: 'var(--text-secondary)', stroke: 'none' };
function DiagramFrame({ ariaLabel, children }) {
  return (
    <div style={{ width: 'min(100%, 320px)', marginTop: 'clamp(28px, 4vh, 44px)' }}>
      <svg viewBox="0 0 250 150" fill="none" stroke="currentColor" strokeWidth="1.4"
        strokeLinecap="square" strokeLinejoin="miter"
        style={{ width: '100%', height: 'auto', display: 'block', color: 'var(--text-primary)' }}
        role="img" aria-label={ariaLabel}>
        {children}
      </svg>
    </div>
  );
}

// 02 — Identity & login: one broker surface over many eID provider cards; new ones drop in by config.
function IdentityDiagram() {
  const reduce = diagReduce();
  const DUR = '3.4s';
  return (
    <DiagramFrame ariaLabel="A broker gives apps one integration surface over OAuth, with eID provider cards registered behind it over SAML; a new provider drops into the stack by configuration.">
      {/* app + OAuth link */}
      <rect x="12" y="62" width="24" height="26" />
      <path d="M12 70 L36 70" />
      <path d="M36 75 L82 75" />
      <text x="59" y="70" textAnchor="middle" style={DIAG_LABEL}>OAuth</text>
      {/* a new provider card dropping into the back of the stack */}
      <rect x="116" y="38" width="54" height="46" fill="var(--c-white)" opacity={reduce ? 1 : 0}>
        {!reduce && <animate attributeName="opacity" values="0;1;1;0" keyTimes="0;0.3;0.9;1" dur={DUR} repeatCount="indefinite" />}
        {!reduce && <animate attributeName="y" values="18;38;38;38" keyTimes="0;0.3;0.9;1" dur={DUR} repeatCount="indefinite" />}
      </rect>
      {/* registered provider cards, peeking behind the broker */}
      <rect x="106" y="44" width="54" height="46" fill="var(--c-white)" />
      <rect x="96" y="50" width="54" height="46" fill="var(--c-white)" />
      {/* broker — the single integration surface (front) */}
      <rect x="86" y="56" width="54" height="46" fill="var(--c-white)" />
      {/* key glyph on the broker */}
      <circle cx="104" cy="76" r="4" />
      <path d="M107 79 L120 92 M116 88 L120 84" />
      {/* check on the freshly registered provider */}
      <path d="M156 46 L160 50 L168 41" opacity={reduce ? 1 : 0}>
        {!reduce && <animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.35;0.45;0.9;1" dur={DUR} repeatCount="indefinite" />}
      </path>
      <text x="150" y="32" textAnchor="middle" style={DIAG_LABEL}>SAML · eID</text>
      <text x="24" y="140" textAnchor="middle" style={DIAG_LABEL}>APP</text>
      <text x="113" y="140" textAnchor="middle" style={DIAG_LABEL}>BROKER</text>
    </DiagramFrame>
  );
}

// 03 — Deployment platform: a staged rollout bar advancing DEV → TEST → PROD.
function DeployDiagram() {
  const reduce = diagReduce();
  const DUR = '3.6s';
  const stages = [
    { label: 'DEV', x: 62, kt: '0;0.12;0.18;0.95;1' },
    { label: 'TEST', x: 125, kt: '0;0.4;0.46;0.95;1' },
    { label: 'PROD', x: 188, kt: '0;0.68;0.74;0.95;1' },
  ];
  return (
    <DiagramFrame ariaLabel="A staged deployment rolls a version out from dev to test to production.">
      <text x="30" y="46" textAnchor="start" style={DIAG_LABEL}>ROLLOUT → v1.3</text>
      {/* progress track */}
      <rect x="30" y="62" width="190" height="16" />
      {/* progress fill */}
      {reduce ? (
        <rect x="30" y="62" width="190" height="16" fill="var(--text-faint)" stroke="none" />
      ) : (
        <rect x="30" y="62" height="16" fill="var(--text-faint)" stroke="none">
          <animate attributeName="width" values="0;190;190;0" keyTimes="0;0.85;0.95;1" dur={DUR} repeatCount="indefinite" />
        </rect>
      )}
      {/* stage dividers */}
      <path d="M93 62 L93 78 M157 62 L157 78" stroke="var(--border-hairline)" />
      {/* stage labels light up as the rollout passes */}
      {stages.map((s) => (
        <text key={s.label} x={s.x} y="98" textAnchor="middle" style={DIAG_LABEL} fill="var(--text-primary)" opacity={reduce ? 1 : 0.35}>
          {s.label}
          {!reduce && <animate attributeName="opacity" values="0.35;0.35;1;1;0.35" keyTimes={s.kt} dur={DUR} repeatCount="indefinite" />}
        </text>
      ))}
      {/* deployed confirmation */}
      <g opacity={reduce ? 1 : 0}>
        <path d="M150 116 L156 122 L167 110" />
        <text x="173" y="121" textAnchor="start" style={DIAG_LABEL}>DEPLOYED</text>
        {!reduce && <animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.85;0.9;0.97;1" dur={DUR} repeatCount="indefinite" />}
      </g>
    </DiagramFrame>
  );
}

// 04 — Lecture scheduling: a timetable that fills itself slot by slot.
function ScheduleDiagram() {
  const reduce = diagReduce();
  return (
    <DiagramFrame ariaLabel="A timetable schedules lectures into room and time slots, filling slot by slot.">
      <rect x="40" y="28" width="168" height="88" />
      <path d="M82 28 L82 116 M124 28 L124 116 M166 28 L166 116" />
      <path d="M40 57 L208 57 M40 86 L208 86" />
      <rect x="45" y="33" width="32" height="18" fill="var(--surface-wash)" stroke="var(--border-hairline)" />
      <rect x="129" y="62" width="32" height="18" fill="var(--surface-wash)" stroke="var(--border-hairline)" />
      <rect x="171" y="91" width="32" height="18" fill="var(--surface-wash)" stroke="var(--border-hairline)" />
      {!reduce && (
        <rect x="45" y="33" width="32" height="18" fill="none" stroke="currentColor">
          <animateTransform attributeName="transform" type="translate" calcMode="discrete"
            values="84 0;42 29;0 58;126 0;84 58;0 0" keyTimes="0;0.18;0.36;0.54;0.72;0.88" dur="5s" repeatCount="indefinite" />
        </rect>
      )}
      <text x="124" y="140" textAnchor="middle" style={DIAG_LABEL}>TIMETABLE</text>
    </DiagramFrame>
  );
}

// 05 — Campus management: bookable resources on a campus map, each pinged as it's reserved.
function CampusDiagram() {
  const reduce = diagReduce();
  const DUR = '3.6s';
  const ripples = [
    { cx: 52, cy: 54, begin: '0s' },
    { cx: 97, cy: 100, begin: '1.2s' },
    { cx: 174, cy: 50, begin: '2.4s' },
  ];
  return (
    <DiagramFrame ariaLabel="Bookable campus resources, a room, a bike and a meal, on a campus map, each pinged as it is reserved.">
      {/* campus blocks + walkways */}
      <rect x="30" y="38" width="44" height="30" stroke="var(--border-hairline)" />
      <rect x="150" y="32" width="46" height="34" stroke="var(--border-hairline)" />
      <rect x="74" y="84" width="52" height="30" stroke="var(--border-hairline)" />
      <rect x="158" y="86" width="40" height="28" stroke="var(--border-hairline)" />
      <path d="M56 68 L96 84 M126 99 L158 99 M173 66 L176 86 M74 50 L150 46" stroke="var(--border-hairline)" />
      {/* room */}
      <rect x="46" y="46" width="12" height="16" /><circle cx="55" cy="54" r="1" fill="currentColor" stroke="none" />
      {/* bike */}
      <circle cx="90" cy="101" r="5" /><circle cx="104" cy="101" r="5" /><path d="M90 101 L97 92 L104 101 M97 92 L99 101" />
      {/* meal */}
      <path d="M167 44 L181 44 L179 56 L169 56 Z" /><path d="M181 46 L185 46 L185 50 L181 50" />
      {/* booking pings */}
      {!reduce && ripples.map((r, i) => (
        <circle key={i} cx={r.cx} cy={r.cy} fill="none" stroke="currentColor">
          <animate attributeName="r" values="3;18;18" keyTimes="0;0.34;1" dur={DUR} begin={r.begin} repeatCount="indefinite" />
          <animate attributeName="opacity" values="0.9;0;0" keyTimes="0;0.34;1" dur={DUR} begin={r.begin} repeatCount="indefinite" />
        </circle>
      ))}
      <text x="125" y="140" textAnchor="middle" style={DIAG_LABEL}>ROOM · BIKE · MEAL</text>
    </DiagramFrame>
  );
}

// 06 — Tailored projects: bespoke layout blocks assembling inside a window frame.
function CraftDiagram() {
  const reduce = diagReduce();
  const DUR = '4.4s';
  const blocks = [
    { x: 50, y: 50, w: 150, h: 12, begin: '0s' },
    { x: 50, y: 68, w: 68, h: 44, begin: '0.5s' },
    { x: 126, y: 68, w: 74, h: 20, begin: '1s' },
    { x: 126, y: 92, w: 74, h: 20, begin: '1.5s' },
  ];
  return (
    <DiagramFrame ariaLabel="Bespoke layout blocks assemble inside a browser window — tailored builds.">
      <rect x="40" y="28" width="170" height="92" />
      <path d="M40 42 L210 42" />
      <circle cx="48" cy="35" r="1.6" fill="currentColor" stroke="none" />
      <circle cx="55" cy="35" r="1.6" fill="currentColor" stroke="none" />
      <circle cx="62" cy="35" r="1.6" fill="currentColor" stroke="none" />
      {blocks.map((b, i) => (
        <rect key={i} x={b.x} y={b.y} width={b.w} height={b.h} fill="var(--surface-wash)" stroke="var(--border-hairline)" opacity={reduce ? 1 : 0}>
          {!reduce && <animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.12;0.3;0.9;1" dur={DUR} begin={b.begin} repeatCount="indefinite" />}
        </rect>
      ))}
      <text x="125" y="140" textAnchor="middle" style={DIAG_LABEL}>BESPOKE BUILD</text>
    </DiagramFrame>
  );
}

// 01 — KapiPalpiBot: signals → risk gate (rejects some) → order → broker, with a money ping.
function BotDiagram() {
  const reduce = diagReduce();
  const DUR = '2.4s';
  const inbound = [
    { d: 'M26 38 L88 75', delay: '0s' },
    { d: 'M26 75 L88 75', delay: '0.5s' },
    { d: 'M26 112 L88 75', delay: '1s' },
  ];
  return (
    <DiagramFrame ariaLabel="Three signal sources feed a central risk gate, which rejects some and turns the rest into an order sent to the broker.">
      <circle cx="20" cy="38" r="6" />
      <circle cx="20" cy="75" r="6" />
      <circle cx="20" cy="112" r="6" />
      <path d="M26 38 L88 75" />
      <path d="M26 75 L88 75" />
      <path d="M26 112 L88 75" />
      <path d="M104 58 L120 75 L104 92 L88 75 Z" />
      <path d="M104 92 L104 103" />
      <path d="M100 99 L108 107 M108 99 L100 107" />
      <path d="M120 75 L150 75" />
      <rect x="150" y="62" width="28" height="26" fill="var(--surface-wash)" stroke="var(--border-hairline)" />
      <path d="M155 71 L173 71 M155 79 L167 79" stroke="var(--border-hairline)" />
      <path d="M178 75 L208 75" />
      <rect x="208" y="58" width="34" height="34" />
      <path d="M217 84 L217 66 M213 70 L217 66 L221 70" />
      <path d="M229 66 L229 84 M225 80 L229 84 L233 80" />
      {!reduce && inbound.map((p, i) => (
        <circle key={i} r="3" fill="currentColor" stroke="none">
          <animateMotion dur={DUR} begin={p.delay} repeatCount="indefinite" path={p.d} />
          <animate attributeName="opacity" values="0;1;1;0" keyTimes="0;0.18;0.82;1" dur={DUR} begin={p.delay} repeatCount="indefinite" />
        </circle>
      ))}
      {!reduce && (
        <circle r="3.4" fill="none" stroke="currentColor" strokeWidth="1.4">
          <animateMotion dur={DUR} begin="0.9s" repeatCount="indefinite" path="M120 75 L242 75" />
          <animate attributeName="opacity" values="0;1;1;0" keyTimes="0;0.18;0.82;1" dur={DUR} begin="0.9s" repeatCount="indefinite" />
        </circle>
      )}
      {!reduce && (
        <text x="225" y="54" textAnchor="middle" fontFamily="var(--font-sans)" fontSize="14" fontWeight="500" fill="currentColor" stroke="none" opacity="0">$
          <animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.8;0.88;0.97;1" dur={DUR} begin="0.9s" repeatCount="indefinite" />
          <animate attributeName="y" values="54;54;46;46;46" keyTimes="0;0.8;0.88;0.97;1" dur={DUR} begin="0.9s" repeatCount="indefinite" />
        </text>
      )}
      <text x="24" y="140" textAnchor="middle" style={DIAG_LABEL}>SIGNALS</text>
      <text x="104" y="140" textAnchor="middle" style={DIAG_LABEL}>RISK GATE</text>
      <text x="164" y="140" textAnchor="middle" style={DIAG_LABEL}>ORDER</text>
      <text x="225" y="140" textAnchor="middle" style={DIAG_LABEL}>BROKER</text>
    </DiagramFrame>
  );
}

const DIAGRAMS = {
  bot: BotDiagram, identity: IdentityDiagram, deploy: DeployDiagram,
  schedule: ScheduleDiagram, campus: CampusDiagram, craft: CraftDiagram,
};

// One project — index + meta on the left, write-up + tech tags on the right.
function WorkEntry({ index, title, meta, description, tags, diagram }) {
  return (
    <article style={{
      display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1.5fr)',
      gap: 'clamp(24px, 5vw, 80px)', alignItems: 'start',
      padding: 'clamp(40px, 7vh, 88px) 0',
      borderTop: '1px solid var(--border-hairline)',
    }} className="dthas-svc-row">
      <div>
        <div style={{
          fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-mono)',
          letterSpacing: 'var(--ls-mono)', color: 'var(--text-secondary)',
        }}>
          <span>{index}</span>
        </div>
        <h2 style={{
          margin: '20px 0 0', fontWeight: 400,
          fontSize: 'var(--fs-h2)', lineHeight: 1.1, letterSpacing: '-0.03em',
        }}>{title}</h2>
        <div style={{
          marginTop: '14px',
          fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-caption)',
          letterSpacing: 'var(--ls-mono)', textTransform: 'uppercase',
          color: 'var(--text-secondary)',
        }}>{meta}</div>
        {diagram && DIAGRAMS[diagram] && React.createElement(DIAGRAMS[diagram])}
      </div>
      <div>
        <p style={{
          margin: 0, fontSize: 'var(--fs-lead)', lineHeight: 1.5,
          letterSpacing: '-0.01em', color: 'var(--text-secondary)', maxWidth: '56ch',
        }}>{description}</p>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', marginTop: 'clamp(20px, 3vh, 32px)' }}>
          {tags.map((t) => <Tag key={t} interactive>{t}</Tag>)}
        </div>
      </div>
    </article>
  );
}

function WorkList() {
  const { Section, SectionHead } = window;

  const work = [
    {
      index: '01', meta: 'Full-stack · Trading · 24/7', diagram: 'bot',
      title: 'KapiPalpiBot',
      description: 'An automated crypto trading bot that filters three independent signal sources through one central risk gate and executes on Binance futures and spot. Roughly 45k lines of Python with 2,300+ tests, engineered observe-first: every change ships flag-gated and shadow-logged until out-of-sample validation proves its edge. Runs 24/7 as a systemd service on Oracle Cloud, behind a TLS-secured dashboard and read-only JSON API, with a multi-provider AI classification layer, and a parity-proven Python 3.9 to 3.12 migration.',
      tags: ['Python', 'API Management', 'Risk engine', 'Backtesting', 'AI classification', 'systemd', 'Oracle Cloud'],
    },
    {
      index: '02', meta: 'Security · Back-end', diagram: 'identity',
      title: 'Identity & login framework',
      description: 'A generic authentication broker giving applications one integration surface to multiple Dutch and European electronic identity (eID) schemes, delegating each login over SAML while applications connect via OAuth 2.0. Built in C# on ASP.NET Core around a provider model, so new identity providers drop in by configuration, and shipped with a full testbed that mocks the providers to make authentication verifiable outside production, packaged for reuse via NuGet. Delivered for Netcompany as a TU Delft software project.',
      tags: ['.NET', 'C#', 'ASP.NET Core', 'SAML', 'OAuth 2.0', 'Identity Provider', 'Scrum'],
    },
    {
      index: '03', meta: 'DevOps · Tooling', diagram: 'deploy',
      title: 'Software deployment platform',
      description: 'Software for deploying and managing different versions of an application across hardware. A Django back end drives the orchestration; an Angular front end gives operators a control surface over what runs where.',
      tags: ['Python', 'Django', 'Angular', 'Versioning', 'Deployment'],
    },
    {
      index: '04', meta: 'Back-end · Microservices', diagram: 'schedule',
      title: 'Lecture scheduling platform',
      description: 'A pandemic-era lecture-scheduling system that assigns lectures to halls by capacity and time-slot, and students to online or on-campus seats under a rule that everyone attends in person at least once a fortnight, with every threshold left dynamically configurable as government rules shifted. A headless, API-only microservice build: Java 11 / Spring Boot services (gateway, schedulers and room/lecture/course/user managers) assembled with Gradle, secured with Spring Security and NetID auth, over a MySQL database on AWS. Hardened with unit and mutation testing plus static analysis. A TU Delft CSE2115 team project.',
      tags: ['Java', 'Spring Boot', 'Microservices', 'MySQL', 'AWS', 'Spring Security', 'Mutation testing'],
    },
    {
      index: '05', meta: 'Full-stack · Desktop', diagram: 'campus',
      title: 'Campus management platform',
      description: 'A campus-management desktop application for booking rooms and bikes and ordering food across the TU Delft campus, with role-based access for students, lecturers and administrators. A Java client-server build: Spring (MVC, Security, JPA/Hibernate) over a MySQL database on the back end, a JavaFX desktop client on the front, talking over a standardised JSON protocol, with token-authenticated login and rich filtering across rooms, facilities and restaurants. Built by a seven-person team for the Object-Oriented Programming Project.',
      tags: ['Java', 'Spring', 'Spring Security', 'Hibernate', 'MySQL', 'JavaFX', 'REST API'],
    },
    {
      index: '06', meta: 'Full-stack · Web', diagram: 'craft',
      title: 'Tailored Projects',
      description: (
        <>
          Bespoke applications, websites and projects, designed and built end to end: fast,
          hand-built, with no template underneath. Not everything is listed here; feel free to{' '}
          <a href="contact.html"
            style={{ color: 'var(--text-primary)', borderBottom: '1px solid var(--border-hairline)' }}
            onMouseEnter={(e)=>e.currentTarget.style.borderColor='var(--c-black)'}
            onMouseLeave={(e)=>e.currentTarget.style.borderColor='var(--border-hairline)'}
          >get in touch</a>{' '}
          to explore and discuss the rest.
        </>
      ),
      tags: ['Full-stack', 'Web development', 'Responsive', 'Database Management', 'Design'],
    },
  ];

  return (
    <Section id="work">
      <SectionHead
        eyebrow="Selected work"
        title="A sample of delivered engagements."
        intro="Development-led, ordered by weight. Some clients are confidential; the studio's own projects are named."
      />
      <div>
        {work.map((w) => (
          <WorkReveal key={w.index}><WorkEntry {...w} /></WorkReveal>
        ))}
      </div>
    </Section>
  );
}

function WorkCTA() {
  const { Section } = window;
  return (
    <Section id="cta">
      <div style={{
        display: 'grid', gridTemplateColumns: 'minmax(0, 1.4fr) auto',
        gap: 'clamp(32px, 6vw, 96px)', alignItems: 'end',
      }} className="dthas-hero-foot">
        <h2 style={{
          margin: 0, fontWeight: 400,
          fontSize: 'var(--fs-h1)', lineHeight: 1.04, letterSpacing: '-0.03em',
          maxWidth: '18ch',
        }}>Want something built to the same standard?</h2>
        <Button variant="primary" size="lg" withArrow as="a" href="contact.html">
          Start a project
        </Button>
      </div>
    </Section>
  );
}

function WorkPage() {
  return (
    <React.Fragment>
      <SiteNav current="work" />
      <WorkHeader />
      <main>
        <WorkList />
        <WorkCTA />
      </main>
      <Footer />
    </React.Fragment>
  );
}

Object.assign(window, { WorkPage });
