// JPJ Solutions — App
// All content lives here. Three direction palettes + hero variant + density driven by Tweaks.

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "direction": "quant",
  "hero": "split",
  "density": "regular",
  "fonts": "serif-mono",
  "headline_lead": "Planning, solved as math.",
  "headline_em": "Not guesswork.",
  "cta_label": "Book a discovery call",
  "color_bg": "#0d0d0e",
  "color_bg_2": "#16161a",
  "color_fg": "#f3f1ec",
  "color_accent": "#f4b740",
  "color_accent_2": "#e8e3d4"
}/*EDITMODE-END*/;

// ── Direction palettes ─────────────────────────────────────────────────
// Each direction is a complete visual world. Applied by writing CSS variables
// to the <body> element so the entire stylesheet reflows without re-render.
const DIRECTIONS = {
  quant: {
    label: 'Quant — editorial dark',
    vars: {
      '--bg': '#0d0d0e',
      '--bg-2': '#16161a',
      '--fg': '#f3f1ec',
      '--fg-2': 'rgba(243,241,236,.62)',
      '--fg-3': 'rgba(243,241,236,.4)',
      '--line': 'rgba(243,241,236,.14)',
      '--line-2': 'rgba(243,241,236,.08)',
      '--accent': '#f4b740',
      '--accent-2': '#e8e3d4',
      '--card': 'rgba(255,255,255,.03)',
    }
  },
  operator: {
    label: 'Operator — swiss bright',
    vars: {
      '--bg': '#fafaf7',
      '--bg-2': '#f1f1ec',
      '--fg': '#0c0d10',
      '--fg-2': 'rgba(12,13,16,.66)',
      '--fg-3': 'rgba(12,13,16,.42)',
      '--line': 'rgba(12,13,16,.16)',
      '--line-2': 'rgba(12,13,16,.08)',
      '--accent': '#1d4ed8',
      '--accent-2': '#0c0d10',
      '--card': 'rgba(12,13,16,.025)',
    }
  },
  specialist: {
    label: 'Specialist — warm boutique',
    vars: {
      '--bg': '#f6f1e8',
      '--bg-2': '#ebe4d4',
      '--fg': '#1f2922',
      '--fg-2': 'rgba(31,41,34,.66)',
      '--fg-3': 'rgba(31,41,34,.42)',
      '--line': 'rgba(31,41,34,.18)',
      '--line-2': 'rgba(31,41,34,.1)',
      '--accent': '#a4521b',
      '--accent-2': '#1f3a2c',
      '--card': 'rgba(31,41,34,.04)',
    }
  },
  brand: {
    label: 'Brand — JPJ navy',
    vars: {
      '--bg': '#f7f7f4',
      '--bg-2': '#eeeee9',
      '--fg': '#1a1d22',
      '--fg-2': 'rgba(26,29,34,.68)',
      '--fg-3': 'rgba(26,29,34,.44)',
      '--line': 'rgba(26,29,34,.16)',
      '--line-2': 'rgba(26,29,34,.08)',
      '--accent': '#1f3a68',
      '--accent-2': '#1a1d22',
      '--card': 'rgba(26,29,34,.03)',
    }
  },
};

const FONTS = {
  'serif-mono':  { display:"'Instrument Serif', Georgia, serif",  body:"'Inter', system-ui, sans-serif", mono:"'JetBrains Mono', ui-monospace, monospace" },
  'newsreader':  { display:"'Newsreader', Georgia, serif",        body:"'Manrope', system-ui, sans-serif", mono:"'JetBrains Mono', ui-monospace, monospace" },
  'geist-mono':  { display:"'Geist', system-ui, sans-serif",      body:"'Geist', system-ui, sans-serif",   mono:"'Geist Mono', ui-monospace, monospace" },
  'brand':       { display:"'Manrope', system-ui, sans-serif",    body:"'Manrope', system-ui, sans-serif", mono:"'JetBrains Mono', ui-monospace, monospace" },
};

function hexToRgba(hex, a) {
  const h = (hex || '#000000').replace('#', '').trim();
  const full = h.length === 3 ? h.split('').map(c => c + c).join('') : h;
  const r = parseInt(full.slice(0, 2), 16);
  const g = parseInt(full.slice(2, 4), 16);
  const b = parseInt(full.slice(4, 6), 16);
  return `rgba(${r},${g},${b},${a})`;
}

function isHexLight(hex) {
  const h = (hex || '#000').replace('#', '').trim();
  const full = h.length === 3 ? h.split('').map(c => c + c).join('') : h;
  if (full.length !== 6) return false;
  const r = parseInt(full.slice(0, 2), 16);
  const g = parseInt(full.slice(2, 4), 16);
  const b = parseInt(full.slice(4, 6), 16);
  return (r * 299 + g * 587 + b * 114) / 1000 > 140;
}

function applyDirection(t) {
  const dir = DIRECTIONS[t.direction] || DIRECTIONS.quant;
  const v = dir.vars;
  const fonts = FONTS[t.fonts] || FONTS['serif-mono'];
  const root = document.body;

  // Custom colors take precedence; fall back to the named palette so older
  // persisted tweak blocks (without color_* keys) still render correctly.
  const bg      = t.color_bg       || v['--bg'];
  const bg2     = t.color_bg_2     || v['--bg-2'];
  const fg      = t.color_fg       || v['--fg'];
  const accent  = t.color_accent   || v['--accent'];
  const accent2 = t.color_accent_2 || v['--accent-2'];

  root.style.setProperty('--bg', bg);
  root.style.setProperty('--bg-2', bg2);
  root.style.setProperty('--fg', fg);
  root.style.setProperty('--fg-2',   hexToRgba(fg, 0.65));
  root.style.setProperty('--fg-3',   hexToRgba(fg, 0.42));
  root.style.setProperty('--line',   hexToRgba(fg, 0.16));
  root.style.setProperty('--line-2', hexToRgba(fg, 0.08));
  root.style.setProperty('--accent', accent);
  root.style.setProperty('--accent-2', accent2);
  root.style.setProperty('--card',   hexToRgba(fg, 0.04));
  // Banner is white-on-transparent — invert it on light backgrounds.
  root.style.setProperty('--logo-filter', isHexLight(bg) ? 'invert(1)' : 'none');

  root.style.setProperty('--display', fonts.display);
  root.style.setProperty('--body', fonts.body);
  root.style.setProperty('--mono', fonts.mono);
  root.dataset.dir = t.direction;
  root.dataset.hero = t.hero;
  root.dataset.density = t.density;
}

// ── Service icons (line, monoweight) ────────────────────────────────────
const Icons = {
  algo: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/>
      <circle cx="12" cy="14" r="2"/><circle cx="5" cy="20" r="2"/><circle cx="19" cy="20" r="2"/>
      <path d="M7 6h10M6 8l5 4M18 8l-5 4M6 18l5-3M18 18l-5-3"/>
    </svg>
  ),
  improve: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      <path d="M3 17l4-4 4 3 7-8"/><path d="M14 8h6v6"/>
    </svg>
  ),
  tools: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      <rect x="3" y="4" width="18" height="14" rx="2"/>
      <path d="M3 9h18M7 14h3M14 14h3"/>
    </svg>
  ),
  support: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1"/>
      <circle cx="12" cy="12" r="3"/>
    </svg>
  ),
};

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

function Nav({ ctaLabel }) {
  const [open, setOpen] = React.useState(false);
  const links = [
    { href: '#problem',      label: 'Problem' },
    { href: '#services',     label: 'Services' },
    { href: '#proof',        label: 'Results' },
    { href: '#about',        label: 'About' },
    { href: '#packages',     label: 'Packages' },
    { href: '#contact-info', label: 'Contact' },
  ];
  return (
    <nav className="nav">
      <div className="wrap nav-row">
        <a href="#top" className="brand" onClick={() => setOpen(false)}>
          <img src="assets/banner_white_transparent.png" alt="JPJ Solutions" className="brand-banner"/>
        </a>
        <div className="nav-links">
          {links.map(l => <a key={l.href} href={l.href}>{l.label}</a>)}
        </div>
        <a href="#contact" className="btn btn-primary nav-cta">
          {ctaLabel} <span className="arr">↗</span>
        </a>
        <button
          type="button"
          className={open ? 'nav-burger open' : 'nav-burger'}
          aria-label="Menu"
          aria-expanded={open}
          onClick={() => setOpen(o => !o)}
        >
          <span/><span/><span/>
        </button>
      </div>
      <div className={open ? 'nav-mobile open' : 'nav-mobile'}>
        {links.map(l => (
          <a key={l.href} href={l.href} onClick={() => setOpen(false)}>{l.label}</a>
        ))}
      </div>
    </nav>
  );
}

function Hero({ ctaLabel }) {
  return (
    <header className="section hero" id="top">
      <HeroVisual ctaLabel={ctaLabel}/>
    </header>
  );
}

function ToolsMarquee() {
  const items = ['Python', 'Pyomo', 'OR-Tools', 'Gurobi', 'CPLEX', 'HiGHS', 'CP-SAT', 'Custom heuristics', 'Mixed-integer programming', 'Constraint programming', 'Robust Optimization', 'Stochastic Optimization', 'Machine Learning'];
  const doubled = [...items, ...items];
  return (
    <div className="tools">
      <div className="tools-track">
        {doubled.map((t,i) => <span key={i}>{t}</span>)}
      </div>
    </div>
  );
}

function Pain() {
  const items = [
    { n: 'Symptom 01', t: 'Your planning process is too manual.', d: 'Excel sheets, tribal knowledge, and weekly firefights. Every plan starts from scratch and quality depends on whoever is on shift.' },
    { n: 'Symptom 02', t: 'Routes, schedules, allocations aren\'t good enough.', d: 'You\'re leaving capacity on the table. Vehicles half-full, shifts misaligned, resources sitting idle while bottlenecks pile up elsewhere.' },
    { n: 'Symptom 03', t: 'Your current solution doesn\'t scale to reality.', d: 'The tool you bought ignores constraints that actually matter. The output looks clean — until dispatch overrides 30% of it.' },
  ];
  return (
    <section className="section pain" id="problem">
      <div className="wrap">
        {/* <PlanningVisual/> */}
        <div className="sec-head">
          <div>
            <div className="eyebrow">The problem</div>
            <h2>If one of these sounds&nbsp;familiar, you have a planning&nbsp;problem.</h2>
          </div>
          <p>
            Operations teams don't fail because they're slow. They fail because
            their planning process is built for a smaller, simpler version of
            the business they've already become.
          </p>
        </div>
        <div className="pain-grid">
          {items.map((it,i) => (
            <article key={i} className="pain-card">
              <div className="pain-num">{it.n}</div>
              <h3>{it.t}</h3>
              <p>{it.d}</p>
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

function Services() {
  const items = [
    { I: Icons.algo, t: 'Custom optimization algorithms',
      d: 'Routing, scheduling, allocation, network design. Built around your real constraints — load, time windows, skills, regulations — not a generic textbook model.',
      tags: ['MILP', 'CP-SAT', 'Metaheuristics', 'Column generation'] },
    { I: Icons.improve, t: 'Improve software you already own',
      d: 'When the system is too slow, too rigid, or producing plans dispatch keeps overriding — I diagnose the model, fix the bottleneck, and quantify the win.',
      tags: ['Performance audit', 'Model rewrite', 'Solver tuning'] },
    { I: Icons.tools, t: 'Decision-support tools teams will actually use',
      d: 'What-if simulations, scenario planners, interactive dashboards. The math runs underneath — your planners stay in control on top.',
      tags: ['Scenario engine', 'KPI dashboard', 'Planner UI'] },
    { I: Icons.support, t: 'Project rescue & strategic support',
      d: 'Independent review of stalled optimization projects. Where it went wrong, what to keep, what to cut, and a credible path back to delivery.',
      tags: ['Project review', 'Vendor assessment', 'Roadmap'] },
  ];
  return (
    <section className="section" id="services">
      <div className="wrap">
        <div className="sec-head">
          <div>
            <div className="eyebrow">What I do</div>
            <h2>Math you can deploy. Operations you can trust.</h2>
          </div>
          <p>Four ways I work with operations and supply-chain teams. Most engagements blend two or three — a custom model paired with a UI your planners can run on Monday morning.</p>
        </div>
        <div className="services">
          {items.map((s,i) => (
            <article key={i} className="svc">
              <div className="svc-icon"><s.I/></div>
              <h3>{s.t}</h3>
              <p>{s.d}</p>
              <ul>{s.tags.map((tg,j) => <li key={j}>{tg}</li>)}</ul>
            </article>
          ))}
        </div>
        <TruckVisual/>
      </div>
    </section>
  );
}

function Proof() {
  const pinRef   = React.useRef(null);
  const stageRef = React.useRef(null);
  const trackRef = React.useRef(null);
  const [swipeOp, setSwipeOp] = React.useState(1);
  const recommendationsUrl = 'https://www.linkedin.com/in/jens-peter-joost/details/recommendations/';

  // Mobile swipe hint: fades out as the user swipes from the first testimonial
  // toward the next. Inactive on desktop (pin-and-slide drives the same track
  // via transform, scrollLeft stays at 0).
  React.useEffect(() => {
    const stage = stageRef.current;
    if (!stage) return;
    const onScroll = () => {
      const firstCard = stage.querySelector('.quote');
      if (!firstCard) return;
      const cardW = firstCard.clientWidth;
      const fadeRange = cardW * 0.6;
      const op = Math.max(0, 1 - stage.scrollLeft / fadeRange);
      setSwipeOp(op);
    };
    stage.addEventListener('scroll', onScroll, { passive: true });
    return () => stage.removeEventListener('scroll', onScroll);
  }, []);

  // Pin-and-slide: while the proof section's testimonials block intersects
  // the viewport, vertical wheel scroll translates the horizontal track.
  // Active only on desktop (>880px). Mobile keeps native horizontal swipe.
  React.useEffect(() => {
    const desktop = window.matchMedia('(min-width: 881px)');
    let raf = null;
    let travel = 0;
    let pinHeight = 0;
    let ro = null;
    let active = false;
    const scrollDensity = 0.72;

    const measure = () => {
      const pin = pinRef.current;
      const stage = stageRef.current;
      const track = trackRef.current;
      if (!pin || !stage || !track) return;
      travel = Math.max(0, track.scrollWidth - stage.clientWidth);
      pinHeight = window.innerHeight + travel * scrollDensity;
      pin.style.height = pinHeight + 'px';
    };

    const update = () => {
      raf = null;
      const pin = pinRef.current;
      const track = trackRef.current;
      if (!pin || !track || !active) return;
      const rect = pin.getBoundingClientRect();
      const total = pinHeight - window.innerHeight;
      const progress = total <= 0 ? 0 : Math.max(0, Math.min(1, -rect.top / total));
      track.style.transform = `translate3d(${(-progress * travel).toFixed(2)}px, 0, 0)`;
    };

    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(update);
    };
    const onResize = () => { measure(); update(); };

    const enable = () => {
      if (active) return;
      active = true;
      measure();
      update();
      window.addEventListener('scroll', onScroll, { passive: true });
      window.addEventListener('resize', onResize);
      if (typeof ResizeObserver !== 'undefined' && trackRef.current) {
        ro = new ResizeObserver(onResize);
        ro.observe(trackRef.current);
      }
    };

    const disable = () => {
      if (!active) return;
      active = false;
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onResize);
      if (ro) { ro.disconnect(); ro = null; }
      if (pinRef.current) pinRef.current.style.height = '';
      if (trackRef.current) trackRef.current.style.transform = '';
    };

    if (desktop.matches) enable();
    const onMql = (e) => { e.matches ? enable() : disable(); };
    desktop.addEventListener('change', onMql);

    return () => {
      desktop.removeEventListener('change', onMql);
      disable();
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  // LinkedIn testimonials, matched to each client profile image.
  const stats = [
    { v: <>−<em>8</em>%</>, l: 'Daily last-mile routing cost saved at DHL — 3D loading constraints' },
    { v: <>$<em>1</em>B</>, l: 'Global express airfreight network simulated end-to-end' },
    { v: <><em>6</em>+</>, l: 'Years inside DHL Global HQ as a data scientist' },
    { v: <><em>15</em>+</>, l: 'Optimization projects shipped — happy clients on LinkedIn' },
  ];
  const quotes = [
    { q: 'I would like to acknowledge Jens-Peter\'s expertise and invaluable contributions, particularly during the initial development of our new digital products and data solutions. As a highly knowledgeable expert and discussion partner, he has a broad understanding of methods and trends in mathematical optimisation, machine learning and AI. He can explain interrelationships and develop targeted approaches for our specific requirements. He familiarised himself quickly and thoroughly with our specific consulting area, enabling him to build bridges to meaningful application products. He works very transparently and collaboratively with our team, ensuring that we all feel we have jointly made the right development decisions on our way to POC/MVP. Additionally, he is a very pleasant and sociable person who integrates well into existing international company structures.',
      n: 'Paul Beck', r: 'Founding Partner / Managing Director at planeground airport consulting', img: 'assets/testimonials/paul.png' },
    { q: 'I can strongly recommend Peter. He brings deep AI and OR expertise, works in a very structured and proactive way, and you can rely on him to get things done-done. In our collaboration at Redcare, he helped shape the initial POC and then took real ownership of turning it into robust, production-ready code. What I particularly like about him is how well he explains complex concepts and model behaviour in a way that non-technical stakeholders can actually use for decisions. He also actively supported upskilling the team along the way, which made a tangible difference. Overall, Peter combines high standards with a very positive, hands-on attitude — and he\'s someone I would gladly work with again.',
      n: 'Ole Vollertsen', r: 'Driving Data & AI Innovation Through Exceptional Talent', img: 'assets/testimonials/ole.jpeg' },
    { q: 'I had the pleasure of working with Peter as a freelancer on a data science project, and he exceeded expectations in every aspect. He is highly motivated, proactive, and brings a great combination of deep technical expertise and strong structure to his work. Peter was strongly involved in developing the initial proof of concept and later carried significant responsibility in turning it into robust, production-ready code. He is also excellent at explaining data science concepts and models to non-technical stakeholders, which was crucial for alignment and successful delivery. With his positive attitude, ownership mindset, and strong delivery focus, Peter is someone I would gladly work with again and can highly recommend to any team looking for real impact from data science.',
      n: 'Dr. Alexandra Fleischmann', r: 'Senior Manager, Data Science @Redcare Pharmacy', img: 'assets/testimonials/alexandra.jpeg' },
    { q: 'I had the pleasure of working with Peter on a project, where he proved to be highly professional, reliable and easy to work with. His strong hands-on mentality and flexibility make him very effective in solving complex challenges. Peter combines deep expertise in machine learning, operations research and software engineering with a practical, results-driven approach. He quickly understands business needs, communicates clearly with stakeholders and delivers robust, well-designed solutions. On top of his technical strength, Peter is a great teammate with a positive attitude and a collaborative mindset. Working with him is both productive and enjoyable. I would gladly work with him again.',
      n: 'Dr. Maximilian Wagner', r: 'Netzwerken für Innovation und Technologietransfer im Bereich Batterie', img: 'assets/testimonials/max.jpeg' },
    { q: 'Peter and I have closely cooperated for multiple years on multiple projects. I had the opportunity to witness both his ability to understand, develop and implement ML and OR solutions to complex business problems, as well as connect with business partners, understand their needs, take feedback from them and explain technical solutions. In short Peter is the one-stop-shop to understand and solve problems with AI and mathematical optimization. And he is just a great human being to be around and to work with. He shares freely his compassion, empathy and positive attitude. And if it gets tough he shows grit and determination. If I will have the chance to work with him again, I\'ll gladly do it!',
      n: 'Lutz Venhofen', r: 'Staff Machine Learning Engineer', img: 'assets/testimonials/lutz.png' },
    { q: 'It has been a joy working with Peter! Peter is not just an outstanding programmer with great experience in algorithmic optimization and GenAI applications, but most importantly driven by the impact he can make on processes and systems. While working in my department, he has gone to great length to understand business processes, talk to stakeholders on both working- and management level and try to dial in his solution exactly on their needs. His broad expertise allows him to both quickly design Proof-of-Concept implementations as well as drive the development of industry-grade, deployed analytics applications. He is also a great teamplayer also to his colleagues and project members.',
      n: 'Henning Blunck', r: 'Head of Data Platforms & Engineering @ DHL Data & Analytics', img: 'assets/testimonials/henning.png' },
    { q: 'Peter is a highly skilled data scientist and operations research expert. He combines deep technical knowledge with a practical, solution-oriented mindset, delivering results that drive real business value to the customer. Peter is also a very skilled and passionate software engineer and software architect. I was very impressed by how he quickly became proficient in the Rust programming language, and by the soundness of the software he designed. Beyond his technical skills, Peter is a wonderful teammate. He shares knowledge freely, and is always open to feedback and new ideas. His positive energy and professionalism make him a pleasure to work with.',
      n: 'Matteo Biggio', r: 'Software Engineer at DHL', img: 'assets/testimonials/matteo.png' },
    { q: 'Peter is a highly motivated Operations Research practitioner with a strong background in algorithms, problem modeling & solving and great energy to learn more to always keep up with state of the art. He approaches the problem as a whole which let him become versatile going beyond the maths, and designing data flow, processes and user interfaces. Last but not least, he is a great teammate and it is joy to work with him.',
      n: 'Uğur Arikan', r: 'Principal Operation Research Scientist, DHL Data & Analytics', img: 'assets/testimonials/ugur.png' },
  ];
  return (
    <section className="section" id="proof">
      <div className="wrap">
        <div className="sec-head">
          <div>
            <div className="eyebrow">The proof</div>
            <h2>Math that survived contact with real operations.</h2>
          </div>
          <p>I worked six years inside DHL Global Headquarters on the kinds of problems most consultants only model in slides. I know the math and the supply-chain reality — and so do my clients.</p>
        </div>
        <div className="proof-strip">
          {stats.map((s,i) => (
            <div className="stat" key={i}>
              <div className="v">{s.v}</div>
              <div className="l">{s.l}</div>
            </div>
          ))}
        </div>
      </div>
      <div className="testimonials-pin" ref={pinRef}>
        <div className="testimonials-stage" ref={stageRef}>
          <div className="testimonials-track" ref={trackRef}>
            {quotes.map((q,i) => (
              <article className="quote" key={i}>
                <div className="quote-mark">"</div>
                <p>{q.q}</p>
                <div className="quote-by">
                  <a className="quote-person" href={recommendationsUrl} target="_blank" rel="noreferrer">
                    <img className="quote-av" src={q.img} alt={q.n} loading="lazy"/>
                    <span>
                      <span className="n">{q.n}</span>
                      <span className="r">{q.r}</span>
                    </span>
                  </a>
                </div>
              </article>
            ))}
          </div>
        </div>
        <div className="swipe-hint" style={{ opacity: swipeOp }} aria-hidden="true">
          <span>Swipe</span>
          <svg viewBox="0 0 24 24" width="22" height="22" fill="none"
            stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
            <path d="M9 6l6 6-6 6"/>
          </svg>
        </div>
      </div>
    </section>
  );
}

function About() {
  return (
    <section className="section" id="about">
      <div className="wrap about-grid">
        <div className="about-photo">
          <img src="assets/headshot_rectangle.jpg" alt="Jens-Peter Joost"/>
          <div className="about-photo-tag">Jens-Peter Joost · Bonn, GER</div>
        </div>
        <div>
          <div className="eyebrow">About</div>
          <h2 className="about-h">I know the math <em>and</em> the supply-chain reality.</h2>
          <p className="about-lead">
            I turn messy planning problems into models, algorithms, and decision tools that teams can trust.
            My work combines applied mathematics, production software, and the translation layer between operations,
            data, and management.
          </p>
          <ul className="about-list">
            <li><strong>M.Sc. Mathematics</strong> with focus on optimization</li>
            <li><strong>11+ years</strong> building mathematical optimization solutions</li>
            <li><strong>Hands-on</strong> with Gurobi, CPLEX, HiGHS, OR-Tools, CP-SAT, Rust, Python</li>
          </ul>
        </div>
      </div>
    </section>
  );
}

function Packages({ ctaLabel }) {
  const tiers = [
    { name: 'Diagnostic', h: 'Pinpoint the win.', flag: null,
      d: 'A focused two-week review. I quantify the savings opportunity in your routing, scheduling, or allocation problem — and tell you what it would take to capture it.',
      bullets: ['On-site or remote workshop','Data sample + constraint review','Quantified savings estimate','Implementation roadmap','30-day follow-up call'] },
    { name: 'Build', h: 'Deploy the algorithm.', flag: 'Most chosen',
      d: 'Custom optimization engine, designed and shipped. From mathematical model through deployment, with the decision-support UI your planners need.',
      bullets: ['Custom MILP / CP / heuristic','Solver-agnostic — Gurobi, OR-Tools, HiGHS and more','Decision-support tool included','Integration with your data','3 months post-launch support'] },
    { name: 'Embedded', h: 'Become the OR team.', flag: null,
      d: 'Fractional optimization lead. I sit inside your team — running models, mentoring engineers, defending the plan when the business pushes back.',
      bullets: ['2 days / week, minimum 3 months','Model ownership & maintenance','Vendor & tooling decisions','Internal training & handover','Direct line, all hours'] },
  ];
  return (
    <section className="section" id="packages">
      <div className="wrap">
        <div className="sec-head">
          <div>
            <div className="eyebrow">How we work together</div>
            <h2>Three ways in. All start with a&nbsp;conversation.</h2>
          </div>
          <p>Pick the engagement that matches the stage you're at. Most clients begin with a Diagnostic — it pays for itself before you ever commit to a build.</p>
        </div>
        <div className="tiers">
          {tiers.map((t,i) => (
            <article key={i} className={t.flag ? 'tier featured' : 'tier'}>
              {t.flag && <div className="tier-flag">{t.flag}</div>}
              <div className="tier-name">{t.name}</div>
              <h3 className="tier-h">{t.h}</h3>
              <p className="tier-desc">{t.d}</p>
              <ul>{t.bullets.map((b,j) => <li key={j}>{b}</li>)}</ul>
              <a href="#contact" className={t.flag ? 'btn btn-primary' : 'btn btn-ghost'}>
                {ctaLabel} <span className="arr">↗</span>
              </a>
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

function CTAFinal() {
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (!ref.current) return;
    const cs = getComputedStyle(document.body);
    const strip = (s) => (s || '').trim().replace('#', '').replace(/[^a-f0-9]/gi, '').slice(0, 6);
    const bg     = strip(cs.getPropertyValue('--bg'))     || '0d0d0e';
    const fg     = strip(cs.getPropertyValue('--fg'))     || 'f3f1ec';
    const accent = strip(cs.getPropertyValue('--accent')) || 'f4b740';
    const url = `https://calendly.com/jenspeter-joost/30min?background_color=${bg}&text_color=${fg}&primary_color=${accent}`;

    const init = () => {
      if (!window.Calendly || !ref.current) return;
      ref.current.innerHTML = '';
      window.Calendly.initInlineWidget({ url, parentElement: ref.current });
    };

    if (window.Calendly) { init(); return; }

    const existing = document.querySelector('script[data-calendly-widget]');
    if (existing) {
      existing.addEventListener('load', init, { once: true });
      return;
    }
    const script = document.createElement('script');
    script.src = 'https://assets.calendly.com/assets/external/widget.js';
    script.async = true;
    script.dataset.calendlyWidget = 'true';
    script.onload = init;
    document.body.appendChild(script);
  }, []);

  // Calendly posts cross-origin messages on major state transitions.
  // Cross-origin sandbox blocks reading the iframe's content height directly,
  // so we use these events to shrink the iframe on the (short) thank-you
  // screen. The base CSS height (900 / 1100) covers the tallest state
  // (calendar + time slots) so the inner scrollbar never appears.
  React.useEffect(() => {
    const onMessage = (e) => {
      if (e.origin !== 'https://calendly.com') return;
      const data = e.data;
      if (!data || typeof data !== 'object') return;
      if (typeof data.event !== 'string' || !data.event.startsWith('calendly.')) return;
      if (!ref.current) return;

      if (data.event === 'calendly.event_scheduled') {
        const mobile = window.matchMedia('(max-width: 880px)').matches;
        ref.current.style.height = (mobile ? 660 : 520) + 'px';
      }
    };
    window.addEventListener('message', onMessage);
    return () => window.removeEventListener('message', onMessage);
  }, []);

  return (
    <section className="section" id="contact">
      <div className="wrap">
        <div className="cta-final">
          <div>
            <div className="eyebrow">Next step</div>
            <h2>Tell me where the planning&nbsp;hurts.</h2>
            <p>30-minute call. No pitch deck. I'll ask sharp questions about your routing, scheduling or allocation problem and tell you honestly whether I can help.</p>
          </div>
          <div className="cta-calendly" ref={ref}/>
        </div>
      </div>
    </section>
  );
}

function ContactInfo() {
  // Official Lucide icons (lucide.dev) — mail, phone, linkedin.
  const ContactIcons = {
    email: () => (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
        <rect width="20" height="16" x="2" y="4" rx="2"/>
        <path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>
      </svg>
    ),
    phone: () => (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
        <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
      </svg>
    ),
    linkedin: () => (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
        <path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/>
        <rect width="4" height="12" x="2" y="9"/>
        <circle cx="4" cy="4" r="2"/>
      </svg>
    ),
  };
  const cards = [
    { I: ContactIcons.email,    l: 'Email',    v: 'jenspeter.joost@jpjsolutions.com',
      href: 'mailto:jenspeter.joost@jpjsolutions.com' },
    { I: ContactIcons.phone,    l: 'Phone',    v: '+49 176 9588 1951',
      href: 'tel:+4917695881951' },
    { I: ContactIcons.linkedin, l: 'LinkedIn', v: 'jens-peter-joost',
      href: 'https://www.linkedin.com/in/jens-peter-joost/', external: true },
  ];
  return (
    <section className="section" id="contact-info">
      <div className="wrap">
        <div className="sec-head">
          <div>
            <div className="eyebrow">Contact</div>
            <h2>Reach me directly.</h2>
          </div>
          <p>Prefer email or a quick message over booking a slot? Here's how to get in touch — I usually reply within a day.</p>
        </div>
        <div className="contact-cards">
          {cards.map((c,i) => (
            <a
              key={i}
              className="contact-card"
              href={c.href}
              {...(c.external ? { target: '_blank', rel: 'noreferrer' } : {})}
            >
              <div className="contact-card-ico"><c.I/></div>
              <div className="contact-card-l">{c.l}</div>
              <div className="contact-card-v">{c.v}</div>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer>
      <div className="wrap foot-row">
        <div className="brand">
          <img src="assets/banner_white_transparent.png" alt="JPJ Solutions" className="brand-banner"/>
        </div>
        <div className="foot-links">
          <a href="https://www.linkedin.com/in/jens-peter-joost/" target="_blank" rel="noreferrer">LinkedIn</a>
          <a href="#contact">Contact</a>
          <a href="impressum.html">Imprint</a>
          <a href="datenschutz.html">Privacy</a>
        </div>
        <div>© 2026 JPJ Solutions · Optimization consulting</div>
      </div>
    </footer>
  );
}

// ── App root ────────────────────────────────────────────────────────────

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  React.useEffect(() => { applyDirection(t); }, [
    t.direction, t.hero, t.density, t.fonts,
    t.color_bg, t.color_bg_2, t.color_fg, t.color_accent, t.color_accent_2,
  ]);

  // Picking a palette acts as a preset: snap all 5 custom colors to it.
  const applyPreset = (v) => {
    const preset = (DIRECTIONS[v] || DIRECTIONS.quant).vars;
    setTweak({
      direction: v,
      color_bg:       preset['--bg'],
      color_bg_2:     preset['--bg-2'],
      color_fg:       preset['--fg'],
      color_accent:   preset['--accent'],
      color_accent_2: preset['--accent-2'],
    });
  };

  return (
    <>
      <Nav ctaLabel={t.cta_label}/>
      <Hero ctaLabel={t.cta_label}/>
      <ToolsMarquee/>
      <Pain/>
      <Services/>
      <Proof/>
      <About/>
      <Packages ctaLabel={t.cta_label}/>
      <CTAFinal/>
      <ContactInfo/>
      <Footer/>

      <TweaksPanel title="Design tweaks">
        <TweakSection label="Direction"/>
        <TweakSelect label="Palette preset" value={t.direction}
          options={[
            { value:'quant', label:'Quant — editorial dark' },
            { value:'operator', label:'Operator — swiss bright' },
            { value:'specialist', label:'Specialist — warm boutique' },
            { value:'brand', label:'Brand — JPJ navy' },
          ]}
          onChange={applyPreset}/>
        <TweakSelect label="Fonts" value={t.fonts}
          options={[
            { value:'serif-mono', label:'Instrument Serif + Inter' },
            { value:'newsreader', label:'Newsreader + Manrope' },
            { value:'geist-mono', label:'Geist + Geist Mono' },
            { value:'brand', label:'Brand — Manrope (matches logo)' },
          ]}
          onChange={(v) => setTweak('fonts', v)}/>

        <TweakSection label="Custom colors"/>
        <TweakColor label="Background"       value={t.color_bg}
          onChange={(v) => setTweak('color_bg', v)}/>
        <TweakColor label="Surface (cards)"  value={t.color_bg_2}
          onChange={(v) => setTweak('color_bg_2', v)}/>
        <TweakColor label="Foreground (text)" value={t.color_fg}
          onChange={(v) => setTweak('color_fg', v)}/>
        <TweakColor label="Accent"           value={t.color_accent}
          onChange={(v) => setTweak('color_accent', v)}/>
        <TweakColor label="Accent · alt"     value={t.color_accent_2}
          onChange={(v) => setTweak('color_accent_2', v)}/>

        <TweakSection label="Hero"/>
        <TweakRadio label="Layout" value={t.hero}
          options={[{value:'split', label:'Split'}, {value:'editorial', label:'Editorial'}]}
          onChange={(v) => setTweak('hero', v)}/>
        <TweakText label="Headline lead" value={t.headline_lead}
          onChange={(v) => setTweak('headline_lead', v)}/>
        <TweakText label="Headline emphasis" value={t.headline_em}
          onChange={(v) => setTweak('headline_em', v)}/>

        <TweakSection label="Conversion"/>
        <TweakText label="CTA label" value={t.cta_label}
          onChange={(v) => setTweak('cta_label', v)}/>

        <TweakSection label="Layout"/>
        <TweakRadio label="Density" value={t.density}
          options={[{value:'compact',label:'Compact'},{value:'regular',label:'Regular'},{value:'comfy',label:'Comfy'}]}
          onChange={(v) => setTweak('density', v)}/>
      </TweaksPanel>
    </>
  );
}

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