/* ============================================================
   DATA — projects + case-study content + about content
   Realistic placeholder cybersecurity portfolio for
   Dylan Jeppesen.
   ============================================================ */

// muted warm tints used for placeholder plates (paper-family)
const TINTS = {
  sand:   "oklch(0.88 0.024 76)",
  clay:   "oklch(0.74 0.05 48)",
  stone:  "oklch(0.80 0.012 70)",
  olive:  "oklch(0.80 0.03 120)",
  slate:  "oklch(0.66 0.02 250)",
  ink:    "oklch(0.40 0.015 60)",
  bone:   "oklch(0.92 0.012 84)",
  rust:   "oklch(0.62 0.07 42)",
  fog:    "oklch(0.84 0.014 230)",
};

const PROJECTS = [
  {
    slug: "meridian",
    title: "Meridian Bank",
    kind: "Client Work",
    year: 2024,
    role: "Red Team Engagement",
    services: "Adversary Simulation",
    client: "Meridian Bank",
    partner: "Internal SOC. Detection Eng.",
    tint: TINTS.ink,
    accent: "oklch(0.70 0.12 34)",
    visual: "killchain",
    cover: "ADVERSARY SIM / FULL-SCOPE",
    video: true,
    summary: "A six-week full-scope adversary simulation against a tier-one retail bank — from external footprint to the core payment switch.",
    lead: "We were asked one question: assume a motivated attacker has time and patience — how far do they get before anyone notices?",
    body: "The engagement began with no internal access and no prior knowledge beyond the bank's public footprint. Over six weeks the team moved from an exposed third-party portal to domain compromise, then laterally toward the payment authorisation environment. Every step was documented as a defendable narrative, mapped to detection opportunities the blue team had missed.\n\nThe value was never the proof of compromise. It was the timeline — a minute-by-minute reconstruction the SOC could replay against their own telemetry to find exactly where visibility broke down.",
    blocks: ["KILL-CHAIN TIMELINE", "INITIAL ACCESS / PHISH", "PRIV-ESC PATH", "DETECTION GAP MAP"],
  },
  {
    slug: "helios",
    title: "Helios Health",
    kind: "Client Work",
    year: 2024,
    role: "Cloud Security Architecture",
    services: "Architecture",
    client: "Helios Health",
    partner: "Platform Eng. Compliance.",
    tint: TINTS.fog,
    accent: "oklch(0.58 0.11 232)",
    visual: "trust",
    cover: "ZERO-TRUST / MULTI-CLOUD",
    video: false,
    summary: "Re-architecting identity and network boundaries for a healthcare platform spanning three cloud providers and forty million records.",
    lead: "Patient data does not get a second chance. The brief was a security architecture that assumed breach by default.",
    body: "Helios had grown by acquisition — three clouds, four identity providers, and a network that trusted itself far too much. We rebuilt the foundation around explicit identity, workload-level segmentation, and an audit trail regulators could read without a translator.\n\nThe hard part was never the technology. It was sequencing the migration so that ten thousand clinicians never noticed the ground moving beneath them.",
    blocks: ["TRUST BOUNDARY MAP", "IDENTITY FEDERATION", "SEGMENTATION MODEL", "AUDIT PIPELINE"],
  },
  {
    slug: "northwind",
    title: "Northwind Grid",
    kind: "Client Work",
    year: 2023,
    role: "OT / ICS Assessment",
    services: "Critical Infrastructure",
    client: "Northwind Energy",
    partner: "OT Engineering. Safety.",
    tint: TINTS.olive,
    accent: "oklch(0.52 0.10 145)",
    visual: "grid",
    cover: "OT / ICS — SUBSTATION",
    video: true,
    summary: "A safety-first security assessment of substation control systems for a regional energy operator.",
    lead: "In an environment where a wrong packet can trip a grid, the rules of engagement matter more than the findings.",
    body: "Operational technology does not forgive the habits of corporate pentesting. Working alongside the engineers who keep the lights on, we mapped the boundary between the business network and the control plane, then probed it with surgical care.\n\nThe deliverable read less like a vulnerability report and more like a field guide: what to fix, what to monitor, and what must never be touched while the turbines are spinning.",
    blocks: ["NETWORK PURDUE MODEL", "PROTOCOL ANALYSIS", "SAFETY ENVELOPE", "REMEDIATION ROADMAP"],
  },
  {
    slug: "vantage",
    title: "Vantage Pay",
    kind: "Client Work",
    year: 2023,
    role: "Application Pentest",
    services: "Offensive Security",
    client: "Vantage Pay",
    partner: "Product Eng.",
    tint: TINTS.clay,
    accent: "oklch(0.56 0.12 32)",
    visual: "api",
    cover: "PAYMENTS / API SURFACE",
    video: false,
    summary: "Continuous penetration testing across a fast-moving payments API handling millions of daily transactions.",
    lead: "Ship velocity and security are not opposites — but only if testing moves at the same speed the product does.",
    body: "Vantage shipped multiple times a day. A point-in-time pentest would have been stale before the report was signed. Instead we embedded testing into their release rhythm — every meaningful change met an adversary before it met a customer.\n\nWe found the flaws that scanners never will: the business-logic gaps where a legitimate sequence of requests becomes theft.",
    blocks: ["AUTH FLOW REVIEW", "BUSINESS-LOGIC ABUSE", "RATE-LIMIT BYPASS", "CI/CD GATE"],
  },
  {
    slug: "orbit",
    title: "Orbit Logistics",
    kind: "Client Work",
    year: 2022,
    role: "Zero-Trust Rollout",
    services: "Architecture",
    client: "Orbit Logistics",
    partner: "IT. Endpoint.",
    tint: TINTS.bone,
    accent: "oklch(0.55 0.09 185)",
    visual: "access",
    cover: "ZERO-TRUST / ENDPOINT",
    video: false,
    summary: "Retiring the corporate VPN for a global logistics workforce in favour of identity-aware access.",
    lead: "Fifteen thousand people, forty countries, and a VPN concentrator that was one outage away from stopping the trucks.",
    body: "The flat, trusted internal network was a liability that had quietly become load-bearing. We replaced it with identity-aware proxies and device posture checks, then decommissioned the VPN one application at a time.\n\nSuccess was invisible: nobody filed a ticket, and the attack surface quietly collapsed.",
    blocks: ["ACCESS POLICY MODEL", "DEVICE POSTURE", "APP-BY-APP CUTOVER", "VPN SUNSET"],
  },
  {
    slug: "tracker",
    title: "Tracker",
    kind: "Personal",
    year: 2024,
    role: "Personal Tooling",
    services: "Research & Tooling",
    client: "Personal",
    partner: "Solo.",
    tint: TINTS.stone,
    accent: "oklch(0.56 0.08 155)",
    visual: "mesh",
    cover: "JOB-SEARCH / INBOX",
    video: false,
    demo: "tracker.html",
    carouselWidth: "34vw",
    shots: {
      carousel: {
        src: "images/tracker-carousel.png",
        alt: "Composed Tracker demo sections showing the dashboard, application table, and status timeline.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      cover: {
        src: "images/tracker-carousel.png",
        alt: "Tracker demo overview with table and timeline sections.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      preview: {
        src: "images/tracker-table-detail.png",
        alt: "Tracker application table view.",
        fit: "cover",
        position: "left top",
        variant: "clean"
      },
      hero: {
        src: "images/tracker-case-hero.png",
        alt: "Tracker case-study hero composed from the real demo overview, table, and timeline.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block0: {
        src: "images/tracker-hero.png",
        alt: "Tracker overview dashboard with application status counts.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block1: {
        src: "images/tracker-timeline-detail.png",
        alt: "Tracker timeline detail showing applications and reply markers.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block2: {
        src: "images/tracker-controls-table.png",
        alt: "Tracker table section with companies, dates, statuses, and notes.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      block3: {
        src: "images/tracker-workflow.png",
        alt: "Tracker workflow composite showing stats, table, and timeline demo sections.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      extra: [
        {
          src: "images/tracker-stats.png",
          alt: "Tracker application status counters.",
          fit: "contain",
          position: "center center",
          pad: "clamp(0.75rem, 2vw, 1.25rem)",
          variant: "ui"
        },
        {
          src: "images/tracker-controls.png",
          alt: "Tracker table and timeline controls with reset, CSV, and add actions.",
          fit: "contain",
          position: "center center",
          pad: "clamp(0.75rem, 2vw, 1.25rem)",
          variant: "ui"
        },
        {
          src: "images/tracker-table-detail.png",
          alt: "Tracker table rows showing status, dates, and notes.",
          fit: "cover",
          position: "left top",
          variant: "clean"
        },
        {
          src: "images/tracker-modal.png",
          alt: "Tracker new application modal.",
          fit: "contain",
          position: "center center",
          pad: "clamp(0.75rem, 2vw, 1.25rem)",
          variant: "ui"
        },
      ],
    },
    summary: "Tracker is a browser-based job application webapp for logging companies, roles, sent dates, reply dates, statuses, and notes in one focused workspace.",
    lead: "The webapp replaces a scattered job-search spreadsheet with a table, status dashboard, timeline view, and export flow built around the way applications actually move.",
    body: "Each entry starts with the practical details: company, role, date sent, reply received, current status, and notes for context. The table view keeps that information scannable, with status filters and search for quickly finding the roles that need attention.\n\nThe timeline view turns the same data into a visual record of waiting time. Applications, replies, interviews, offers, and declines sit on a shared date axis so it is easier to see what is active, what has gone quiet, and where the next follow-up belongs. It runs locally in the browser with sample data, reset controls, and CSV export for moving the records somewhere else when needed.",
    outcomeTitle: "How the webapp works",
    outcome: "Tracker keeps the workflow deliberately small: add an application, update the status when something changes, switch between table and timeline views, then export the dataset when the search needs a more portable record. There is no account system or backend requirement, so the demo stays fast, inspectable, and easy to adapt.",
    blocks: ["APPLICATION INDEX", "STATUS TIMELINE", "FILTERS / ACTIONS", "WORKFLOW OVERVIEW"],
  },
  {
    slug: "counterstrafe",
    title: "Counterstrafe",
    kind: "Personal",
    year: 2026,
    role: "Browser Training Tool",
    services: "Game Mechanics",
    client: "Personal",
    partner: "Solo.",
    tint: TINTS.fog,
    accent: "oklch(0.55 0.09 155)",
    visual: "beacon",
    cover: "CS2 / MOVEMENT TIMING",
    video: false,
    demo: "counterstrafe.html",
    carouselWidth: "34vw",
    shots: {
      carousel: {
        src: "images/counterstrafe-carousel.png",
        alt: "Counterstrafe trainer live demo showing the timing target, velocity panels, and difficulty controls.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      cover: {
        src: "images/counterstrafe-carousel.png",
        alt: "Counterstrafe trainer overview with timing target and run state panels.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      preview: {
        src: "images/counterstrafe-click.png",
        alt: "Counterstrafe trainer click window state.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      hero: {
        src: "images/counterstrafe-case-hero.png",
        alt: "Counterstrafe trainer case-study hero showing a successful click timing state.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      block0: {
        src: "images/counterstrafe-build.png",
        alt: "Counterstrafe trainer build phase before reaching max velocity.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      block1: {
        src: "images/counterstrafe-counter.png",
        alt: "Counterstrafe trainer counter phase after max velocity is reached.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      block2: {
        src: "images/counterstrafe-click.png",
        alt: "Counterstrafe trainer click phase while under the accuracy threshold.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      block3: {
        src: "images/counterstrafe-late.png",
        alt: "Counterstrafe trainer late feedback state after natural slowdown.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      extra: [
        {
          src: "images/counterstrafe-build.png",
          alt: "Build phase panel and arena state.",
          fit: "cover",
          position: "center top",
          variant: "clean"
        },
        {
          src: "images/counterstrafe-counter.png",
          alt: "Counter-strafe window with difficulty controls visible.",
          fit: "cover",
          position: "center top",
          variant: "clean"
        },
        {
          src: "images/counterstrafe-click.png",
          alt: "Click window with successful timing feedback.",
          fit: "cover",
          position: "center top",
          variant: "clean"
        },
        {
          src: "images/counterstrafe-late.png",
          alt: "Late rep feedback after slowing without a counter-strafe.",
          fit: "cover",
          position: "center top",
          variant: "clean"
        },
      ],
    },
    summary: "Counterstrafe is a browser-based CS2 movement trainer for drilling full-speed A/D stops, click timing, and first-shot discipline against timed targets.",
    lead: "The useful part of counter-strafing is not the gesture by itself. It is knowing exactly when the rifle becomes accurate again.",
    body: "The trainer runs a fixed-step lateral movement simulation using public CS2-facing constants: 64 Hz cadence, Source-style ground acceleration and friction, AK-47 movement speed, and the 34 percent weapon-speed threshold commonly used for movement accuracy.\n\nEach rep requires the player to hit near max AK velocity first. The player then counter-strafes with the opposite key and clicks or presses Space only when the movement model says the shot is inside the accurate window. Natural slowdown is treated as late feedback, but the run keeps flowing into the next rep.",
    outcomeTitle: "How the trainer works",
    outcome: "The webapp keeps the loop narrow: move, counter-strafe, click, then read the velocity and shot log. It is intentionally isolated from the portfolio runtime, so the demo can evolve without adding a build system or changing the rest of the site.",
    blocks: ["A/D MOVEMENT MODEL", "VELOCITY ACCURACY GATE", "TARGET TIMER", "SHOT FEEDBACK"],
  },
  {
    slug: "clue-atlas",
    title: "Clue Atlas",
    kind: "Personal",
    year: 2026,
    role: "Browser Study Tool",
    services: "Research & Tooling",
    client: "Personal",
    partner: "Solo.",
    tint: TINTS.bone,
    accent: "oklch(0.47 0.075 188)",
    visual: "mesh",
    cover: "COUNTRY CLUES / VISUAL ATLAS",
    video: false,
    demo: "clue-atlas/index.html",
    demoNote: "Runs locally in your browser · source-backed clue data",
    carouselWidth: "34vw",
    shots: {
      carousel: {
        src: "images/clue-atlas-carousel.png",
        alt: "Clue Atlas globe view with Brazil selected for country clue study.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      cover: {
        src: "images/clue-atlas-carousel.png",
        alt: "Clue Atlas globe overview for country-identification clues.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      preview: {
        src: "images/clue-atlas-meta-atlas.png",
        alt: "Clue Atlas bollards meta atlas with source-backed country cards.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      hero: {
        src: "images/clue-atlas-case-hero.png",
        alt: "Clue Atlas case-study hero combining the globe, meta atlas, and Brazil source clue images.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block0: {
        src: "images/clue-atlas-globe.png",
        alt: "Clue Atlas clean globe navigation view with Brazil highlighted.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block1: {
        src: "images/clue-atlas-meta-atlas.png",
        alt: "Bollards atlas in Clue Atlas showing filter chips and country cards.",
        fit: "cover",
        position: "center top",
        variant: "clean"
      },
      block2: {
        src: "images/clue-atlas-country-panel.png",
        alt: "Clue Atlas country panel showing Japan clues beside the globe.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block3: {
        src: "images/clue-atlas-compare.png",
        alt: "Source-backed Brazil clue images for bollards, license plates, poles, and street signs.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      extra: [
        {
          src: "images/clue-atlas-mobile.png",
          alt: "Clue Atlas mobile layout with an internally scrolling country panel.",
          fit: "contain",
          position: "center center",
          pad: "clamp(0.75rem, 2vw, 1.25rem)",
          variant: "ui"
        },
        {
          src: "images/clue-atlas-language.png",
          alt: "Clue Atlas language lens detail view with source-backed examples.",
          fit: "cover",
          position: "center top",
          variant: "clean"
        },
        {
          src: "images/clue-atlas-meta-atlas.png",
          alt: "Clue Atlas meta atlas filters for bollards.",
          fit: "cover",
          position: "center top",
          variant: "clean"
        },
        {
          src: "images/clue-atlas-compare.png",
          alt: "Clue Atlas source image review composite for Brazil.",
          fit: "cover",
          position: "center center",
          variant: "clean"
        },
      ],
    },
    summary: "Clue Atlas is a browser-based study atlas for country-identification clues, combining a clean globe, country panels, and source-backed meta lenses for roads, signs, poles, plates, vegetation, and car meta.",
    lead: "GeoGuessr study is mostly pattern recognition. The useful interface is the one that lets a player move from place, to clue type, to source example without losing the thread.",
    body: "The app turns scattered guide and meta-reference material into a static, local-first browser tool. A clean Globe.GL view stays focused on country selection, while global meta atlas modes collect clue families into browsable card grids with filters and source imagery.\n\nCountry panels stay compact and source-aware: each one brings together clue summaries, local thumbnails, attribution links, and meta-lens examples without copying full guide prose. The portfolio build keeps the runtime self-contained, trims bulky raw image originals, and preserves the app as a standalone demo that can evolve without changing the main site router.",
    outcomeTitle: "How the atlas works",
    outcome: "Clue Atlas keeps the study loop visual: spin the globe, open a country, switch to a meta atlas, compare source-backed examples, then return to the country panel with the context still intact. It is intentionally static, inspectable, and attribution-conscious, with internal scroll regions and smoke tests covering the globe, atlas views, image loading, and mobile layout.",
    blocks: ["GLOBE NAVIGATION", "META LENS ATLAS", "COUNTRY CLUE PANEL", "SOURCE IMAGE REVIEW"],
  },
  {
    slug: "tft-pivot-dojo",
    title: "TFT Pivot Dojo",
    kind: "Personal",
    year: 2026,
    role: "Strategy Training Tool",
    services: "Game Systems",
    client: "Personal",
    partner: "Solo.",
    tint: TINTS.slate,
    accent: "oklch(0.72 0.10 235)",
    visual: "grid",
    cover: "TFT / PIVOT TRAINER",
    video: false,
    demo: "tft-pivot-dojo/index.html",
    demoNote: "Static export - local progress in your browser",
    carouselWidth: "34vw",
    shots: {
      carousel: {
        src: "images/tft-pivot-dojo-carousel.png",
        alt: "TFT Pivot Dojo immersive daily board with decision choices, bench, shop, and economy panels.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      cover: {
        src: "images/tft-pivot-dojo-carousel.png",
        alt: "TFT Pivot Dojo daily decision trainer board overview.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      preview: {
        src: "images/tft-pivot-dojo-pivot-flow.png",
        alt: "TFT Pivot Dojo board and coach-review decision panel.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      hero: {
        src: "images/tft-pivot-dojo-case-hero.png",
        alt: "TFT Pivot Dojo case-study hero showing a full board state and the decision panel.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block0: {
        src: "images/tft-pivot-dojo-board.png",
        alt: "TFT Pivot Dojo full tactical board before choosing a line.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block1: {
        src: "images/tft-pivot-dojo-planner.png",
        alt: "TFT Pivot Dojo synergies, item bench, and augment planning rail.",
        fit: "contain",
        position: "center center",
        pad: "clamp(0.75rem, 2vw, 1.25rem)",
        background: "oklch(0.23 0.012 60)",
        variant: "ui"
      },
      block2: {
        src: "images/tft-pivot-dojo-pivot-flow.png",
        alt: "TFT Pivot Dojo pivot flow with board state beside a coach-review panel.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      block3: {
        src: "images/tft-pivot-dojo-review.png",
        alt: "TFT Pivot Dojo coach review after locking the best line.",
        fit: "cover",
        position: "center center",
        variant: "clean"
      },
      extra: [
        {
          src: "images/tft-pivot-dojo-review.png",
          alt: "Coach review panel scoring the selected TFT decision five out of five.",
          fit: "cover",
          position: "right center",
          variant: "clean"
        },
        {
          src: "images/tft-pivot-dojo-shop.png",
          alt: "TFT Pivot Dojo shop and board detail with champion cards and review state.",
          fit: "cover",
          position: "center center",
          variant: "clean"
        },
        {
          src: "images/tft-pivot-dojo-planner.png",
          alt: "Planning rail with synergies, components, completed items, and augments.",
          fit: "contain",
          position: "center center",
          pad: "clamp(0.75rem, 2vw, 1.25rem)",
          background: "oklch(0.23 0.012 60)",
          variant: "ui"
        },
        {
          src: "images/tft-pivot-dojo-archive.png",
          alt: "TFT Pivot Dojo archive of authored decision-training spots.",
          fit: "cover",
          position: "center top",
          variant: "clean"
        },
      ],
    },
    summary: "TFT Pivot Dojo is a browser-based Teamfight Tactics decision trainer for practicing pivots, economy calls, board stabilization, and item direction against authored game states.",
    lead: "The hard part of TFT is rarely knowing the best comp. It is recognizing when the board in front of you has stopped agreeing with the plan.",
    body: "The app turns one board state into a focused coaching rep. Each spot presents the stage, lobby tempo, health, gold, shop, bench, items, augments, active traits, and a small set of plausible lines. The player picks one line, locks the answer, then gets a scored coach review explaining why the best choice fits the moment.\n\nThe interface keeps the game-state cockpit intact: hex board, trait rail, item inventory, bench, shop, economy, and decision panel all share one 16:9 training surface. Archive and practice routes let the same scenario model become a daily drill, a ladder by difficulty, or a searchable library of pivot patterns.",
    outcomeTitle: "How the dojo works",
    outcome: "Pivot Dojo keeps the loop narrow: read the board, identify the outs, choose the line, then compare the decision against the authored review. For the portfolio, the Next app exports as a static folder, so the live demo sits beside the other tools without adding a runtime server to the main site.",
    blocks: ["BOARD STATE READING", "SHOP / ECON DRILLS", "PIVOT DECISION FLOW", "COACH REVIEW LOOP"],
  },
  {
    slug: "ghostwire",
    title: "Ghostwire",
    kind: "Personal",
    year: 2024,
    role: "Open-Source Tooling",
    services: "Research & Tooling",
    client: "Open Source",
    partner: "Community.",
    tint: TINTS.slate,
    accent: "oklch(0.72 0.13 268)",
    visual: "beacon",
    cover: "C2 DETECTION / OSS",
    video: true,
    summary: "An open-source library that turns noisy network telemetry into legible command-and-control signals.",
    lead: "Defenders drown in logs. Ghostwire is an attempt to give them a quieter, sharper signal.",
    body: "Built in the open over a year of evenings, Ghostwire models the rhythm of beaconing malware and surfaces the patterns hidden inside ordinary traffic. It ships as a small, dependency-light library so a team can drop it into the pipeline they already have.\n\nThe project has become a small community — pull requests from defenders who found a new pattern in the wild and wanted to share it.",
    blocks: ["BEACON MODEL", "JITTER ANALYSIS", "DETECTION RULES", "PUBLIC BENCHMARK"],
  },
  {
    slug: "phantom",
    title: "Phantom",
    kind: "Personal",
    year: 2023,
    role: "Supply-Chain Research",
    services: "Research & Tooling",
    client: "Independent",
    partner: "Disclosure.",
    tint: TINTS.rust,
    accent: "oklch(0.72 0.13 42)",
    visual: "supply",
    cover: "SUPPLY-CHAIN / RESEARCH",
    video: false,
    summary: "A research project tracing how a single compromised build dependency can quietly reach thousands of downstream projects.",
    lead: "The most dangerous code is the code you never decided to trust — you simply inherited it.",
    body: "Phantom started as curiosity about how far a malicious package could travel before anyone noticed. It became a methodology for mapping trust in the modern software supply chain, and a set of disclosures handled quietly with the maintainers affected.\n\nThe work was eventually presented as a talk, with all proof-of-concept code held back until fixes had shipped.",
    blocks: ["DEPENDENCY GRAPH", "TRUST PROPAGATION", "POC SANDBOX", "COORDINATED DISCLOSURE"],
  },
  {
    slug: "nullroute",
    title: "Nullroute",
    kind: "Personal",
    year: 2022,
    role: "CTF Platform",
    services: "Research & Tooling",
    client: "Community",
    partner: "Volunteers.",
    tint: TINTS.sand,
    accent: "oklch(0.61 0.12 86)",
    visual: "ctf",
    cover: "CTF / PLATFORM",
    video: false,
    summary: "A capture-the-flag platform and a set of original challenges run for a community of learning hackers.",
    lead: "You learn to defend a system by spending real time inside one that wants to be broken.",
    body: "Nullroute is the platform I wish I had when I started — original challenges, clean infrastructure, and write-ups that explain the why rather than just the flag. It runs a few times a year for a growing community.\n\nDesigning challenges turned out to be the best security education I have ever had.",
    blocks: ["CHALLENGE DESIGN", "ISOLATED INFRA", "SCOREBOARD", "WRITE-UPS"],
  },
];

// ---- About page content ----
const DISCIPLINES = [
  {
    n: "01",
    title: "Offensive Security",
    tint: TINTS.ink,
    label: "RED TEAM / ADVERSARY SIM",
    body: "Full-scope adversary simulation, red-teaming and application penetration testing. The goal is never a list of vulnerabilities — it is a credible story about how a real attacker would move, and exactly where your defenders would catch them.",
  },
  {
    n: "02",
    title: "Security Architecture",
    tint: TINTS.fog,
    label: "ZERO-TRUST / CLOUD",
    body: "Designing identity, network and cloud foundations that assume breach by default. I work with platform teams to make the secure path the easy path, then sequence the migration so nobody has to notice the ground move beneath them.",
  },
  {
    n: "03",
    title: "Research & Tooling",
    tint: TINTS.slate,
    label: "OSS / DETECTION",
    body: "Open-source detection tooling, supply-chain research and coordinated disclosure. The work that does not bill by the hour but quietly raises the floor for everyone — and keeps my own instincts sharp.",
  },
];

const CLIENTS = ["Meridian Bank", "Helios Health", "Northwind Energy", "Vantage Pay", "Orbit Logistics"];
const PARTNERS = ["OWASP", "BSides", "Hack The Box", "First.org", "Disclose.io"];

const ABOUT_STORY = "I did not set out to break things for a living. I started as the person who kept the servers running, drifted into writing the software that ran on them, and slowly realised the part I cared about most was the part everyone else was trying not to think about: how it all falls apart.\n\nFifteen years later I still chase that feeling — the quiet moment when a system reveals an assumption nobody meant to make. The difference now is that I get to hand that moment back to the people who can fix it.";

Object.assign(window, { PROJECTS, DISCIPLINES, CLIENTS, PARTNERS, ABOUT_STORY, TINTS });
