{
  "directories": [
    {
      "path": "/",
      "name": "/",
      "isDirectory": true,
      "type": "directory",
      "status": "active"
    },
    {
      "path": "/Applications",
      "name": "Applications",
      "isDirectory": true,
      "type": "directory-virtual",
      "icon": "/icons/default/applications.png",
      "status": "active"
    },
    {
      "path": "/Projects",
      "name": "Projects",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/documents.png",
      "status": "active"
    },
    {
      "path": "/Images",
      "name": "Images",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/images.png",
      "status": "active"
    },
    {
      "path": "/Images/CampusKey",
      "name": "CampusKey",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/images.png",
      "status": "active"
    },
    {
      "path": "/Music",
      "name": "Music",
      "isDirectory": true,
      "type": "directory-virtual",
      "icon": "/icons/default/sounds.png",
      "status": "active"
    },
    {
      "path": "/Sites",
      "name": "Sites",
      "isDirectory": true,
      "type": "directory-virtual",
      "icon": "/icons/default/sites.png",
      "status": "active"
    },
    {
      "path": "/Trash",
      "name": "Trash",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/trash-empty.png",
      "status": "active"
    },
    {
      "path": "/Desktop",
      "name": "Desktop",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/desktop.png",
      "status": "active"
    },
    {
      "path": "/Desktop/CampusKey",
      "name": "CampusKey",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/documents.png",
      "status": "active"
    },
    {
      "path": "/Desktop/Experimental",
      "name": "Experimental",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/experimental.png",
      "status": "active"
    },
    {
      "path": "/Games",
      "name": "Games",
      "isDirectory": true,
      "icon": "/icons/macosx/chess.png",
      "status": "active",
      "type": "directory"
    },
    {
      "path": "/Desktop/Freelance",
      "name": "Freelance",
      "isDirectory": true,
      "type": "directory",
      "icon": "/icons/default/documents.png",
      "status": "active"
    }
  ],
  "files": [
    {
      "path": "/Documents/Indexx.app.md",
      "name": "Indexx.app.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "content": "# Indexx.app\n\n*Boutique link-in-bio platform — concept, brand, design + full-stack development*\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/indexx-app/hero.png\" alt=\"Indexx.app marketing site\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n## The idea\n\nMost link-in-bio tools are identical. Everyone gets the same template, the same layout, the same generic feel — just with a different profile photo. Creators with real brands were being forced into tools that made them look like everyone else.\n\nIndexx is my answer to that. A boutique service that designs and hand-builds fully custom link-in-bio microsites — treated like a small branding project, not a SaaS signup. Every creator page is built individually. No templates. No drag-and-drop builders.\n\n---\n\n## What I built\n\nThe full product from scratch — the Indexx brand itself, the marketing website, and the technical platform that powers every client microsite.\n\nUnder the hood it's a Next.js App Router system with a dynamic `/{creator}` routing architecture. Each route maps to a completely custom-built frontend — not a theme applied to a template, but a bespoke component set built for that creator's brand. Per-creator social metadata and dynamic OG image generation mean every microsite behaves like a properly configured web property, not just a link page.\n\nCreator data and content are backed by Supabase with creator-scoped APIs, and every microsite ships with analytics instrumentation — page views, link clicks, and geo tracking on selected builds — so clients can actually see how their audience is engaging.\n\n---\n\n## The tech\n\n<div style=\"background:#f0f0f0;border:1px solid #ccc;box-shadow:2px 2px 0 #999;padding:12px 16px;margin:12px 0;\">\n  <div style=\"display:flex;justify-content:space-between;align-items:flex-start;\">\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/nextdotjs/333333\" alt=\"Next.js\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Next.js</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/react/333333\" alt=\"React\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">React</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/typescript/333333\" alt=\"TypeScript\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">TypeScript</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/tailwindcss/333333\" alt=\"Tailwind CSS\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Tailwind</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/framer/333333\" alt=\"Framer Motion\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Framer</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/threedotjs/333333\" alt=\"Three.js\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Three.js</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/supabase/333333\" alt=\"Supabase\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Supabase</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/vercel/333333\" alt=\"Vercel\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Vercel</span>\n    </div>\n  </div>\n</div>\n\n---\n\n## Every build is different\n\n<div style=\"margin:16px 0;\">\n  <img src=\"/projects/indexx-app/microsites.png\" alt=\"Three different creator microsites\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Nightshift Cam · Charne · Fleetwood Mac — no two builds share a template</p>\n</div>\n\n---\n\n## Live clients\n\n<table style=\"width:100%;border-collapse:collapse;margin:16px 0;\"><tr><td style=\"width:40%;vertical-align:top;padding-right:20px;\"><img src=\"/projects/indexx-app/phone-in-hand.png\" alt=\"Live microsite on a phone\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" /></td><td style=\"vertical-align:top;padding-top:4px;\"><p style=\"font-family:Geneva,system-ui;font-size:12px;line-height:1.8;color:#222;margin:0 0 16px 0;\">The platform is live with a growing roster of clients — each one a different type of creator, each one built completely differently.</p><p style=\"font-family:Geneva,system-ui;font-size:12px;line-height:1.8;color:#222;margin:0 0 10px 0;\"><strong>Nightshift Cam</strong> is a freelance designer's personal brand — the microsite doubles as a portfolio link and a social hub, built to feel as considered as the work itself.</p><p style=\"font-family:Geneva,system-ui;font-size:12px;line-height:1.8;color:#222;margin:0 0 10px 0;\"><strong>Charne</strong> is a UGC creator who needed a clean, brand-forward page that could sit in a social bio and convert brand inquiries — no clutter, just what matters.</p><p style=\"font-family:Geneva,system-ui;font-size:12px;line-height:1.8;color:#222;margin:0 0 10px 0;\"><strong>Xavier Dragner</strong> is a DJ and music producer — his microsite is built for the music space, linking out to sets, bookings, and platforms in a way that actually matches his sound and visual identity.</p><p style=\"font-family:Geneva,system-ui;font-size:12px;line-height:1.8;color:#222;margin:0 0 10px 0;\"><strong>CampusKey</strong> is one of the largest private student accommodation companies in South Africa — a brand at a completely different scale, with a microsite built to match.</p><p style=\"font-size:11px;color:#888;font-family:Geneva,system-ui;margin:16px 0 0 0;\">Indexx lives in a social bio — tapped on a phone, every time.</p></td></tr></table>\n---\n\n## My role\n\nEverything. Concept, positioning, brand identity, UX, design, and the entire codebase. Built in Next.js 16, React 19, TypeScript, Tailwind v4, and Framer Motion, deployed on Vercel.\n\n---\n\n## Where it's at\n\nIndexx is live and early stage — the platform is built, clients are on it, and the service is open. It's a real business, not a side project that never shipped.\n\n---\n\n<div style=\"margin-top: 8px; display: flex; gap: 10px; align-items: center;\">\n  <a href=\"https://www.indexx.app\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:inline-flex;align-items:center;gap:6px;background:#0f766e;color:white;padding:8px 18px;border-radius:6px;text-decoration:none;font-size:13px;font-weight:600;font-family:system-ui;\">Open Indexx.app ↗</a>\n</div>",
      "displayType": "UI / UX & Web",
      "subtitle": "Creator microsite platform"
    },
    {
      "path": "/Documents/CampusKey Brand Bubble.md",
      "name": "CampusKey Brand Bubble.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "content": "# CampusKey Brand Bubble\n\n*Internal brand enablement platform — UI/UX design + full-stack development*\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/campuskey/brand-bubble/brand-bubble-hero.png\" alt=\"Brand Bubble dashboard\" style=\"width:100%;display:block;max-height:480px;object-fit:cover;object-position:top;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n## The problem\n\nCampusKey had a 145-page brand guide. Like most brand guides, it lived in a folder, got shared once, and was never opened again. With over 100 employees spread across six campuses in South Africa, brand knowledge wasn't reaching the people who needed it — and there was no way to know what anyone actually understood.\n\nA PDF wasn't going to fix that. Something had to be built.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-before-pdf.png\" alt=\"The 145-page PDF\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Before — 145-page PDF</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-after-platform.png\" alt=\"Brand Bubble platform\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">After — Brand Bubble</p>\n  </div>\n</div>\n\n---\n\n## What I built\n\nBrand Bubble is a secure internal learning platform that replaces the brand guide entirely. Employees log in, work through structured brand training modules, test their knowledge through quizzes, and compete on a live leaderboard. Brand assets — logos, fonts, colours, guidelines — all live in one place, accessible to everyone, always up to date.\n\nIt's now the company's single source of truth for all things brand.\n\n---\n\n## The design\n\nFull UI/UX ownership from the ground up. The platform needed to feel like a CampusKey product — on-brand, polished, and engaging enough that people would actually want to use it. The content architecture spans 30+ pages of structured brand education, designed to guide employees through the material progressively rather than dumping everything at once.\n\nThe interface had to work for a wide range of users across different campuses and roles, so clarity and ease of navigation were non-negotiable.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-module.png?v=1\" alt=\"Brand module\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Brand training module</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-asset-library.png?v=1\" alt=\"Asset library\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Asset library — logos, fonts, colours with signed URL access</p>\n  </div>\n</div>\n\n---\n\n## The gamification layer\n\nThe quiz and leaderboard system was the key to driving adoption. Employees complete category and difficulty-based quizzes, earn points, and see how they rank against colleagues across campuses. That competitive element turned brand training from a passive exercise into something people actually engaged with — and gave the business a measurable signal of where brand knowledge was strong and where it needed work.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-quiz.png\" alt=\"Quiz\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Quiz — category + difficulty based</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-leaderboard.png\" alt=\"Leaderboard\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Live leaderboard across campuses</p>\n  </div>\n</div>\n\n---\n\n## The engineering\n\nBuilt in React and TypeScript with Supabase handling auth, data, and file storage. The login pipeline alone is worth calling out — it chains form validation, bot detection, Cloudflare Turnstile CAPTCHA with server-side verification, and rate limiting before a session is ever established. For an internal platform handling company-wide rollout, that level of security rigour mattered.\n\n<div style=\"margin:16px 0;\">\n  <img src=\"/projects/campuskey/brand-bubble/brand-bubble-login.png\" alt=\"Login screen\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Login — with Turnstile CAPTCHA</p>\n</div>\n\n---\n\n## Shipped across South Africa\n\nThis wasn't a prototype. Rolled out to 100+ employees across six campuses and running in production since launch. Along the way I diagnosed and resolved real production issues — CORS misconfigurations, Cloudflare Turnstile hostname authorisation failures, environment variable mismatches causing deploy failures. The kind of problems you only encounter when something is actually live and being used.\n\n<iframe src=\"/maps/campuskey-campuses.html\" scrolling=\"no\" style=\"width:100%;height:465px;border:1px solid #ccc;box-shadow:2px 2px 0 #999;display:block;margin:16px 0;\" title=\"CampusKey campus locations\"></iframe>\n\n---\n\n## The outcome\n\nA 145-page document that nobody read became a platform that the whole company uses. Brand Bubble is now the canonical home for CampusKey's brand — every asset, every guideline, every training module — used daily across South Africa.\n\n---\n\n*Internal platform — not publicly accessible.*",
      "displayType": "UI / UX & Web",
      "subtitle": "Brand identity & visual direction"
    },
    {
      "path": "/Images/steve-jobs.png",
      "name": "steve-jobs.png",
      "isDirectory": false,
      "type": "png",
      "icon": "/icons/default/image.png",
      "status": "active",
      "assetPath": "/assets/images/steve-jobs.png"
    },
    {
      "path": "/Desktop/CampusKey/CampusKey Brand Bubble.md",
      "name": "CampusKey Brand Bubble.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "content": "# CampusKey Brand Bubble\n\n*Internal brand enablement platform — UI/UX design + full-stack development*\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/campuskey/brand-bubble/brand-bubble-hero.png\" alt=\"Brand Bubble dashboard\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n## The problem\n\nCampusKey had a 145-page brand guide. Like most brand guides, it lived in a folder, got shared once, and was never opened again. With over 100 employees spread across six campuses in South Africa, brand knowledge wasn't reaching the people who needed it — and there was no way to know what anyone actually understood.\n\nA PDF wasn't going to fix that. Something had to be built.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-before-pdf.png\" alt=\"The 145-page PDF\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Before — 145-page PDF</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-after-platform.png\" alt=\"Brand Bubble platform\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">After — Brand Bubble</p>\n  </div>\n</div>\n\n---\n\n## What I built\n\nBrand Bubble is a secure internal learning platform that replaces the brand guide entirely. Employees log in, work through structured brand training modules, test their knowledge through quizzes, and compete on a live leaderboard. Brand assets — logos, fonts, colours, guidelines — all live in one place, accessible to everyone, always up to date.\n\nIt's now the company's single source of truth for all things brand.\n\n---\n\n## The design\n\nFull UI/UX ownership from the ground up. The platform needed to feel like a CampusKey product — on-brand, polished, and engaging enough that people would actually want to use it. The content architecture spans 30+ pages of structured brand education, designed to guide employees through the material progressively rather than dumping everything at once.\n\nThe interface had to work for a wide range of users across different campuses and roles, so clarity and ease of navigation were non-negotiable.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-module.png?v=1\" alt=\"Brand module\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Brand training module</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-asset-library.png?v=1\" alt=\"Asset library\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Asset library — logos, fonts, colours with signed URL access</p>\n  </div>\n</div>\n\n---\n\n## The gamification layer\n\nThe quiz and leaderboard system was the key to driving adoption. Employees complete category and difficulty-based quizzes, earn points, and see how they rank against colleagues across campuses. That competitive element turned brand training from a passive exercise into something people actually engaged with — and gave the business a measurable signal of where brand knowledge was strong and where it needed work.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-quiz.png\" alt=\"Quiz\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Quiz — category + difficulty based</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/brand-bubble-leaderboard.png\" alt=\"Leaderboard\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Live leaderboard across campuses</p>\n  </div>\n</div>\n\n---\n\n## The engineering\n\nBuilt in React and TypeScript with Supabase handling auth, data, and file storage. The login pipeline alone is worth calling out — it chains form validation, bot detection, Cloudflare Turnstile CAPTCHA with server-side verification, and rate limiting before a session is ever established. For an internal platform handling company-wide rollout, that level of security rigour mattered.\n\n<div style=\"margin:16px 0;\">\n  <img src=\"/projects/campuskey/brand-bubble/brand-bubble-login.png\" alt=\"Login screen\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Login — with Turnstile CAPTCHA</p>\n</div>\n\n---\n\n## Shipped across South Africa\n\nThis wasn't a prototype. Rolled out to 100+ employees across six campuses and running in production since launch. Along the way I diagnosed and resolved real production issues — CORS misconfigurations, Cloudflare Turnstile hostname authorisation failures, environment variable mismatches causing deploy failures. The kind of problems you only encounter when something is actually live and being used.\n\n<iframe src=\"/maps/campuskey-campuses.html\" scrolling=\"no\" style=\"width:100%;height:465px;border:1px solid #ccc;box-shadow:2px 2px 0 #999;display:block;margin:16px 0;\" title=\"CampusKey campus locations\"></iframe>\n\n---\n\n## The outcome\n\nA 145-page document that nobody read became a platform that the whole company uses. Brand Bubble is now the canonical home for CampusKey's brand — every asset, every guideline, every training module — used daily across South Africa.\n\n---\n\n*Internal platform — not publicly accessible.*",
      "displayType": "UI / UX & Web",
      "subtitle": "Brand identity & visual direction"
    },
    {
      "path": "/Desktop/CampusKey/Rebooker Webapp.md",
      "name": "Rebooker Webapp.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "content": "# CampusKey Rebook 2026\n\n*Campaign microsite — UI/UX design + full-stack development*\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/campuskey/brand-bubble/rebooker-hero.png\" alt=\"Countdown timer hero\" style=\"width:100%;display:block;max-height:480px;object-fit:cover;object-position:top;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:8px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Countdown timer — big, bold, campaign energy</p>\n</div>\n\n---\n\n## The idea\n\nCampusKey's annual rebooking campaign was running on printed flyers. The target market is 18–25 year old students — people who live on their phones. A flyer wasn't going to cut it.\n\nI replaced it with a digital campaign hub: same energy as a flyer, but live, interactive, and built for the way students actually consume information.\n\n---\n\n## The problem vs the solution\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/brand-bubble/rebooker-print-and-digital.png\" alt=\"Printed flyer vs microsite\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Printed flyer → mobile microsite</p>\n  </div>\n  <div style=\"display:flex;flex-direction:column;justify-content:center;\">\n    <p style=\"font-size:13px;font-family:Geneva,system-ui;margin:0;\">Replaced printed rebooking flyers with a mobile-first campaign microsite — built for students, not inboxes.</p>\n  </div>\n</div>\n\n---\n\n## What I built\n\nA mobile-first campaign microsite that served as the single digital touchpoint for the entire 2026 VIP rebooking window across all six campuses. Students could get all the information they needed, find their campus-specific rebooking link, and participate in a gamified community challenge — all in one place.\n\nThe app combined a live countdown to the rebooking deadline, campus-specific routing, a prize and rewards mechanic to drive urgency, a student photo gallery for community engagement, and an admin dashboard so the marketing team could update campaign content without touching code.\n\n<div style=\"margin:16px 0;\">\n  <img src=\"/projects/campuskey/brand-bubble/rebooker-screens.png\" alt=\"App screens\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:8px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Homepage · Campus selector · Prizes & rewards</p>\n</div>\n\n---\n\n## The approach\n\nThe design brief I set myself was: what if a flyer could have a deadline ticking down, know which campus you're at, and reward you for acting early? Every decision came from that — the countdown timer creates urgency, the gamification layer drives repeat visits, and the mobile-first layout means it works just as well shared as a WhatsApp link as it does on a QR code poster.\n\n<div style=\"margin:16px 0;\">\n  <img src=\"/projects/campuskey/brand-bubble/rebooker-gallery.png\" alt=\"Student photo gallery\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:8px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Student photo gallery — community mechanic live and real</p>\n</div>\n\n---\n\n## My role\n\nEverything. Campaign concept, UX structure, visual design, build, admin tooling, and all supporting campaign assets.\n\n---\n\n## The outcome\n\nReplaced printed campaign collateral entirely. One URL handled the full rebooking campaign across six campuses, with campus-level click tracking giving the team visibility into where engagement was coming from.\n\n---\n\n*Campaign microsite — CampusKey internal.*",
      "displayType": "UI / UX & Web",
      "subtitle": "Re-booking flow web app"
    },
    {
      "path": "/Desktop/Experimental/Indexx.app.md",
      "name": "Indexx.app.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/indexx-app.png",
      "status": "active",
      "content": "# Indexx.app\n\n*Boutique link-in-bio platform — concept, brand, design + full-stack development*\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/indexx-app/hero.png\" alt=\"Indexx.app marketing site\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n## The idea\n\nMost link-in-bio tools are identical. Everyone gets the same template, the same layout, the same generic feel — just with a different profile photo. Creators with real brands were being forced into tools that made them look like everyone else.\n\nIndexx is my answer to that. A boutique service that designs and hand-builds fully custom link-in-bio microsites — treated like a small branding project, not a SaaS signup. Every creator page is built individually. No templates. No drag-and-drop builders.\n\n---\n\n## What I built\n\nThe full product from scratch — the Indexx brand itself, the marketing website, and the technical platform that powers every client microsite.\n\nUnder the hood it's a Next.js App Router system with a dynamic `/{creator}` routing architecture. Each route maps to a completely custom-built frontend — not a theme applied to a template, but a bespoke component set built for that creator's brand. Per-creator social metadata and dynamic OG image generation mean every microsite behaves like a properly configured web property, not just a link page.\n\nCreator data and content are backed by Supabase with creator-scoped APIs, and every microsite ships with analytics instrumentation — page views, link clicks, and geo tracking on selected builds — so clients can actually see how their audience is engaging.\n\n---\n\n## The tech\n\n<div style=\"background:#f0f0f0;border:1px solid #ccc;box-shadow:2px 2px 0 #999;padding:12px 16px;margin:12px 0;\">\n  <div style=\"display:flex;justify-content:space-between;align-items:flex-start;\">\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/nextdotjs/333333\" alt=\"Next.js\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Next.js</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/react/333333\" alt=\"React\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">React</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/typescript/333333\" alt=\"TypeScript\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">TypeScript</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/tailwindcss/333333\" alt=\"Tailwind CSS\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Tailwind</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/framer/333333\" alt=\"Framer Motion\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Framer</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/threedotjs/333333\" alt=\"Three.js\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Three.js</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/supabase/333333\" alt=\"Supabase\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Supabase</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/vercel/333333\" alt=\"Vercel\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Vercel</span>\n    </div>\n  </div>\n</div>\n\n---\n\n## Every build is different\n\n<div style=\"margin:16px 0;\">\n  <img src=\"/projects/indexx-app/microsites.png\" alt=\"Three different creator microsites\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Nightshift Cam · Charne · Fleetwood Mac — no two builds share a template</p>\n</div>\n\n---\n\n## Live clients\n\n<table style=\"width:100%;border-collapse:collapse;margin:16px 0;\"><tr><td style=\"width:40%;vertical-align:top;padding-right:20px;\"><img src=\"/projects/indexx-app/phone-in-hand.png\" alt=\"Live microsite on a phone\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" /></td><td style=\"vertical-align:top;padding-top:4px;\"><p style=\"font-family:Geneva,system-ui;font-size:12px;line-height:1.6;color:#222;margin:0 0 12px 0;\">The platform is live and growing. Current microsites include Nightshift Cam, Rosectra, Rook Cycles, Palm Black, and CampusKey — each one built and designed individually to match the creator's brand, not a house style.</p><p style=\"font-size:11px;color:#888;font-family:Geneva,system-ui;margin:0;\">Indexx lives in a social bio — tapped on a phone, every time.</p></td></tr></table>\n---\n\n## My role\n\nEverything. Concept, positioning, brand identity, UX, design, and the entire codebase. Built in Next.js 16, React 19, TypeScript, Tailwind v4, and Framer Motion, deployed on Vercel.\n\n---\n\n## Where it's at\n\nIndexx is live and early stage — the platform is built, clients are on it, and the service is open. It's a real business, not a side project that never shipped.\n\n---\n\n<div style=\"margin-top: 8px; display: flex; gap: 10px; align-items: center;\">\n  <a href=\"https://www.indexx.app\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display:inline-flex;align-items:center;gap:6px;background:#0f766e;color:white;padding:8px 18px;border-radius:6px;text-decoration:none;font-size:13px;font-weight:600;font-family:system-ui;\">Open Indexx.app ↗</a>\n</div>",
      "displayType": "UI / UX & Web",
      "subtitle": "Creator microsite platform"
    },
    {
      "path": "/Games/Pac-Man.html",
      "name": "Pac-Man",
      "isDirectory": false,
      "type": "application",
      "icon": "/icons/default/pacman.png",
      "status": "active",
      "appId": "pacman"
    },
    {
      "path": "/Games/Doom.html",
      "name": "Doom",
      "isDirectory": false,
      "type": "application",
      "icon": "/assets/games/images/doom.webp",
      "status": "active",
      "appId": "pc",
      "appInitialData": { "gameId": "doom" }
    },
    {
      "path": "/Games/Oregon Trail.html",
      "name": "Oregon Trail",
      "isDirectory": false,
      "type": "application",
      "icon": "/assets/games/images/oregon-trail.webp",
      "status": "active",
      "appId": "pc",
      "appInitialData": { "gameId": "oregontrail" }
    },
    {
      "path": "/Desktop/CampusKey/Price Launch 2025.md",
      "name": "Price Launch 2025.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "displayType": "Graphic Design",
      "content": "# CampusKey 2025 Price Launch\n\n*#202Vibes campaign — graphic design · print · digital · large format · merchandise & activations*\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/campuskey/price-launch-hero.png\" alt=\"Students holding check-in boards\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n## The brief\n\nFill 4,000 student beds across six campuses. One campaign. Four to six months. One designer.\n\n---\n\n## The campaign\n\n#202Vibes was the creative platform for CampusKey's 2025 price launch — a bold, high-energy campaign identity that had to work everywhere simultaneously. Billboards on campus roads. Flagpoles and gazebos at activation events. Social content and digital ads. Posters and flyers. Mentor and staff t-shirts. Check-in day gift boards that students held on their first morning. A branded calendar. Event flyers for Kickstart Fest with a headline act.\n\nThe same visual world — lime green, hot pink, bold type, maximal energy — stretched across every single one of those touchpoints without losing its coherence or its punch.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/price-launch-billboard.png\" alt=\"202VIBES billboard\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Large format — billboard & banner</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/price-launch-posters.png\" alt=\"Poster range\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Print & digital — range within the system</p>\n  </div>\n</div>\n\n---\n\n## The print system\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/price-launch-corex.png\" alt=\"Corex boards\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Corex board sets of three — student portrait · student in space · copy board</p>\n  </div>\n  <div>\n    <div style=\"display:grid;grid-template-columns:repeat(4,1fr);gap:4px;padding:8px;background:#f9f9f9;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\">\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224102-1-1.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">WiFi</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224103-4.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Social Spaces</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224243.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Study Spaces</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224103-2-1.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Laundry</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224103-1-1.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Gym</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224239.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Security</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224103-8.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">All Inclusive</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224102-1.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Room Cleaning</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224101-2.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Furnished Rooms</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224101-1.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Events</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224103-3-1.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">iStore</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224102-2.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">24/7 Team</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224102-3.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Backup Power</span></div>\n      <div style=\"display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px;\"><img src=\"https://campuskey.co.za/wp-content/uploads/2024/07/Frame-2147224101-2-1.svg\" style=\"width:36px;height:36px;\" /><span style=\"font-size:8px;color:#555;font-family:Geneva,system-ui;text-align:center;\">Parking</span></div>\n    </div>\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">14 amenities · every one got a corex board set</p>\n    </div>\n  </div>\n</div>\n\n<p style=\"font-size:12px;color:#666;font-family:Geneva,system-ui;font-style:italic;margin:8px 0 0 0;\">Each CampusKey amenity got its own set of three corex boards — a close-up student portrait, the same student photographed in that space, and a copy board telling their story. Designed as a system, deployed across all six campuses.</p>\n\n---\n\n## Kickstart Fest\n\nKickstart Fest was the headline event that closed out check-in season — a live party with MgucciFab as the headline act. The brief called for something that could step away from the campaign's bright palette entirely: full bleed, dark, editorial. Same campaign world, completely different energy. The identity proved it could go anywhere.\n\n<div style=\"margin:16px 0;\">\n  <img src=\"/projects/campuskey/price-launch-kickstart-fest.png\" alt=\"Kickstart Fest\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Kickstart Fest — MgucciFab headline · full bleed · editorial contrast</p>\n</div>\n\n---\n\n## Merchandise & activation\n\nThe campaign identity extended into what the team wore. The t-shirt design brought the visual system into merch and workwear — this wasn't just a poster campaign, it was a world.\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:16px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/price-launch-merch.png\" alt=\"T-shirt mockup and mentors\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Merch — mockup vs mentors in the flesh</p>\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/price-launch-calendar.png\" alt=\"Campaign calendar\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n    <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">4,000+ printed and handed to every student on check-in day</p>\n  </div>\n</div>\n\n<p style=\"font-size:12px;color:#666;font-family:Geneva,system-ui;font-style:italic;margin:8px 0 0 0;\">Over 4,000 calendars were produced and given as welcome gifts to every student who checked into CampusKey in 2025 — a physical piece of the campaign that lived in their rooms all year.</p>\n\n---\n\n## My role\n\nI work as part of a wider marketing team, but the graphic design is entirely mine. Every asset you see — from the billboard to the back of a t-shirt — was designed, adapted, and delivered by me. The campaign ran across six campuses simultaneously, which meant every format had to be produced in multiple variations while keeping the identity locked in.\n\n---\n\n## The outcome\n\nThe campaign ran. The buildings filled. All 4,000 beds booked.\n\n---\n\n*One designer. One campaign identity. Billboards, merch, events, digital, and everything in between — across six campuses. 4,000 beds. Fully booked.*",
      "subtitle": "Annual pricing campaign"
    },
    {
      "path": "/Games/Duck Hunt.html",
      "name": "Duck Hunt.html",
      "isDirectory": false,
      "type": "application",
      "icon": "/icons/default/duckhunt.png",
      "status": "active",
      "appId": "duckhunt"
    },
    {
      "path": "/Desktop/CampusKey/Zapier Automations.md",
      "name": "Zapier Automations.md",
      "icon": "/icons/default/file-text.png",
      "content": "# CampusKey Zapier Automations\n\n*Workflow automation — Zapier, Shopify, Calendly & Asana*\n\n---\n\n## The context\n\nNot everything in a growing student accommodation business is a design problem. Sometimes the bottleneck is admin — repetitive manual tasks that eat into the time teams should be spending on bigger things. When I saw the opportunity to fix that, I figured it out and built it.\n\n---\n\n## What I built\n\nA suite of Zapier automations that connect CampusKey's external tools and pipe the right information to the right people automatically.\n\n**Shopify orders** trigger task creation in Asana so the team can action fulfilment without manually checking the store. **Calendly bookings** do the same — every booking lands in Asana as a task, ready to be picked up. **Supplier notifications** fire automatically when orders are placed, keeping partners like Jenny Africa in the loop without anyone having to send a manual email.\n\nAll three are live and running in production, used daily by the CK team.\n\n<div style=\"margin:24px 0;\">\n  <img src=\"/projects/campuskey/zapier-dashboard.png\" alt=\"Zapier dashboard showing active Zaps\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:6px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Active Zaps in production — Shopify, Calendly, and supplier notifications running daily</p>\n</div>\n\n---\n\n## How it's wired\n\n<div style=\"border:1px solid #ccc;box-shadow:2px 2px 0 #999;margin:24px 0;overflow:hidden;\"><div style=\"background:linear-gradient(to bottom,#e8e8e8,#d0d0d0);border-bottom:1px solid #bbb;padding:6px 10px;display:flex;align-items:center;gap:6px;\"><div style=\"width:10px;height:10px;border-radius:50%;background:#ff5f57;border:1px solid #d94f47;flex-shrink:0;\"></div><div style=\"width:10px;height:10px;border-radius:50%;background:#febc2e;border:1px solid #d4a017;flex-shrink:0;\"></div><div style=\"width:10px;height:10px;border-radius:50%;background:#28c840;border:1px solid #1aab29;flex-shrink:0;\"></div><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:11px;color:#444;font-weight:700;margin-left:10px;letter-spacing:0.02em;\">Shopify Order Automation</span></div><div style=\"background:#f5f5f5;overflow-x:auto;padding:20px;\"><div style=\"display:inline-flex;align-items:center;\"><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:8px 11px;min-width:115px;max-width:150px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:3px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#22c55e;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">SHOPIFY</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">New Paid Order</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:2px;\">1. Trigger</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Watches store — fires the moment an order is marked paid</div></div><div style=\"display:flex;align-items:center;width:22px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:8px 11px;min-width:115px;max-width:150px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:3px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#818cf8;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">FORMATTER</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Extract order data</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:2px;\">2–5.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Pulls name, contact, products bought + amounts paid</div></div><div style=\"display:flex;align-items:center;width:22px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:8px 11px;min-width:115px;max-width:150px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:3px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#fb923c;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">LOOPING</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Loop line items</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:2px;\">6.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Iterates through each product in the order</div></div><div style=\"display:flex;align-items:center;width:22px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:8px 11px;min-width:115px;max-width:150px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:3px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#818cf8;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">FORMATTER</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Format for Asana</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:2px;\">7.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Structures data to fit Asana custom field inputs</div></div><div style=\"display:flex;align-items:center;width:22px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:8px 11px;min-width:115px;max-width:150px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:3px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#818cf8;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">PATHS</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Route by product</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:2px;\">8. Branch</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Splits flow based on what was purchased</div></div><div style=\"display:flex;align-items:center;width:22px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"display:flex;flex-direction:column;gap:12px;\"><div style=\"display:flex;align-items:center;\"><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;background:#f0f0f0;padding:4px 9px;margin-right:8px;white-space:nowrap;line-height:1.4;\">Path A<br><span style=\"font-weight:400;color:#888;\">Always runs</span></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:7px 10px;min-width:110px;max-width:145px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:2px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#22c55e;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">PATHS</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Always — all orders</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:1px;\">9.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Every purchase triggers this path</div></div><div style=\"display:flex;align-items:center;width:16px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:7px 10px;min-width:110px;max-width:145px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:2px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#ef4444;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">ASANA</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Create task + assign</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:1px;\">10.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Purchase details inputted into custom fields, assigned to fulfilment team</div></div><div style=\"display:flex;align-items:center;width:16px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:7px 10px;min-width:110px;max-width:145px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:2px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#f97316;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">FILTER</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Product = Membership?</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:1px;\">11.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Gates Klaviyo step</div></div><div style=\"display:flex;align-items:center;width:16px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:7px 10px;min-width:110px;max-width:145px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:2px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#10b981;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">KLAVIYO</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Add to mailing list</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:1px;\">12.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Triggers onboarding email sequence</div></div></div><div style=\"display:flex;align-items:center;\"><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;background:#f0f0f0;padding:4px 9px;margin-right:8px;white-space:nowrap;line-height:1.4;\">Path B<br><span style=\"font-weight:400;color:#888;\">WIFI orders only</span></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:7px 10px;min-width:110px;max-width:145px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:2px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#fb923c;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">PATHS</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Product = “WIFI”</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:1px;\">13.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Only runs for WiFi voucher purchases</div></div><div style=\"display:flex;align-items:center;width:16px;flex-shrink:0;\"><div style=\"flex:1;height:1px;background:#999;\"></div><div style=\"width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid #999;\"></div></div><div style=\"background:#fff;border:1px solid #ccc;box-shadow:1px 1px 0 #aaa;padding:7px 10px;min-width:110px;max-width:145px;flex-shrink:0;\"><div style=\"display:flex;align-items:center;margin-bottom:2px;\"><span style=\"display:inline-block;width:7px;height:7px;border-radius:50%;background:#d97706;margin-right:4px;flex-shrink:0;vertical-align:middle;\"></span><span style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#555;font-weight:700;letter-spacing:0.04em;\">WEBHOOK</span></div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:10px;color:#1a1a1a;font-weight:600;line-height:1.3;\">Jenny WiFi API</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#999;margin-top:1px;\">14.</div><div style=\"font-family:Geneva,'Helvetica Neue',sans-serif;font-size:9px;color:#aaa;margin-top:3px;font-style:italic;line-height:1.3;\">Auto-issues + emails voucher to client</div></div></div></div></div></div></div>\n<p style=\"font-size:11px;color:#888;margin:-16px 0 24px 0;font-family:Geneva,'Helvetica Neue',sans-serif;text-align:center;\">Path A runs on every order. Path B fires only when the product is a WiFi voucher — no human involved.</p>\n\n---\n\n## Why it matters\n\nThis isn't graphic design. But it's the same instinct — see a problem, figure out the tool, build the solution. The automations removed a meaningful amount of manual admin from the team's day so they could focus on the work that actually needs a human.",
      "displayType": "Automation",
      "subtitle": "No-code automation workflows"
    },
    {
      "path": "/Desktop/CampusKey/Shopify Rollout.md",
      "name": "Shopify Rollout.md",
      "icon": "/icons/default/file-text.png",
      "content": "# CampusKey Shopify Rollout\n\n<div style=\"display:flex;align-items:center;gap:12px;margin:4px 0 20px 0;flex-wrap:wrap;\">\n  <em style=\"font-family:Geneva,system-ui;font-size:13px;color:#555;\">Ecommerce — store design, setup & rollout across 6 campuses</em>\n  <div style=\"display:inline-flex;align-items:center;gap:8px;background:#f0f0f0;border:1px solid #ccc;box-shadow:1px 1px 0 #999;padding:4px 10px;\">\n    <img src=\"https://cdn.simpleicons.org/shopify/96bf48\" style=\"width:16px;height:16px;display:block;\" />\n    <span style=\"font-size:10px;font-family:Geneva,system-ui;color:#333;\">6 stores · 6 campuses · est. 2024</span>\n  </div>\n</div>\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/campuskey/shopify/shopify-hero.png\" alt=\"CampusKey Shopify storefront\" style=\"width:100%;display:block;max-height:480px;object-fit:cover;object-position:top;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n## The context\n\nCampusKey had no ecommerce presence at all. No online store, no way for students to purchase anything digitally across any campus. The plan was to start with Cape Town, prove the concept, and then roll it out to the remaining five campuses.\n\nIt took a lot of back and forth to get it over the line. But we got there.\n\n---\n\n## What I did\n\nFull ownership of every store, end to end. That meant store design and branding, product setup, payment and shipping configuration, and launch — starting with Cape Town and then repeated for each of the five remaining campuses individually.\n\nEach campus got its own store, set up and tailored to that location's specific offering. Once live, I took on ongoing store management and reporting across all six — monitoring performance, maintaining products, and pulling analytics to track what was working.\n\n<div style=\"margin:24px 0;\">\n  <img src=\"/projects/campuskey/shopify/shopify-grid.png\" alt=\"All six CampusKey campus stores\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:6px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Six stores, six campuses — Cape Town, Pretoria, Johannesburg, Bloemfontein, Stellenbosch, Durban</p>\n</div>\n\n---\n\n## The result\n\nIn the first year of all six stores being live, CampusKey did R2,453,961.53 in total sales across the network.\n\nEvery rand of it was new revenue that didn't exist before the stores launched.\n\n<div style=\"margin:24px 0;background:#f8f8f8;border:1px solid #ccc;box-shadow:2px 2px 0 #999;padding:20px 20px 16px;border-radius:2px;\"><style>.ck-line{stroke-dasharray:620;stroke-dashoffset:620;animation:ck-draw 2s cubic-bezier(0.4,0,0.2,1) 0.3s forwards;}.ck-dot{opacity:0;animation:ck-pop 0.3s ease forwards;}.ck-d0{animation-delay:0.5s}.ck-d1{animation-delay:1.0s}.ck-d2{animation-delay:1.4s}.ck-d3{animation-delay:1.8s}.ck-num{opacity:0;animation:ck-pop 0.4s ease 2.1s forwards;}@keyframes ck-draw{to{stroke-dashoffset:0;}}@keyframes ck-pop{to{opacity:1;}}</style><div style=\"font-family:Geneva,system-ui;font-size:10px;color:#aaa;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:12px;\">Year One Revenue — All Six Campuses</div><svg viewBox=\"0 0 560 120\" style=\"width:100%;display:block;overflow:visible;\"><line x1=\"0\" y1=\"0\" x2=\"560\" y2=\"0\" stroke=\"#e8e8e8\" stroke-width=\"1\"/><line x1=\"0\" y1=\"40\" x2=\"560\" y2=\"40\" stroke=\"#e8e8e8\" stroke-width=\"1\"/><line x1=\"0\" y1=\"80\" x2=\"560\" y2=\"80\" stroke=\"#e8e8e8\" stroke-width=\"1\"/><line x1=\"0\" y1=\"120\" x2=\"560\" y2=\"120\" stroke=\"#e8e8e8\" stroke-width=\"1\"/><text x=\"-4\" y=\"4\" text-anchor=\"end\" font-family=\"Geneva,system-ui\" font-size=\"8\" fill=\"#bbb\">R2.4M</text><text x=\"-4\" y=\"44\" text-anchor=\"end\" font-family=\"Geneva,system-ui\" font-size=\"8\" fill=\"#bbb\">R1.8M</text><text x=\"-4\" y=\"84\" text-anchor=\"end\" font-family=\"Geneva,system-ui\" font-size=\"8\" fill=\"#bbb\">R1.2M</text><text x=\"-4\" y=\"124\" text-anchor=\"end\" font-family=\"Geneva,system-ui\" font-size=\"8\" fill=\"#bbb\">R0</text><path class=\"ck-line\" d=\"M0,90 L186,60 L373,30 L560,0\" fill=\"none\" stroke=\"#2d6a4f\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><circle class=\"ck-dot ck-d0\" cx=\"0\" cy=\"90\" r=\"3.5\" fill=\"#2d6a4f\"/><circle class=\"ck-dot ck-d1\" cx=\"186\" cy=\"60\" r=\"3.5\" fill=\"#2d6a4f\"/><circle class=\"ck-dot ck-d2\" cx=\"373\" cy=\"30\" r=\"3.5\" fill=\"#2d6a4f\"/><circle class=\"ck-dot ck-d3\" cx=\"560\" cy=\"0\" r=\"4.5\" fill=\"#2d6a4f\"/></svg><div style=\"display:flex;justify-content:space-between;font-family:Geneva,system-ui;font-size:9px;color:#bbb;margin-top:6px;padding:0 2px;\"><span>Q1</span><span>Q2</span><span>Q3</span><span>Q4</span></div><div style=\"border-top:1px solid #e0e0e0;margin-top:14px;padding-top:12px;display:flex;align-items:baseline;gap:8px;\"><span class=\"ck-num\" style=\"font-family:Geneva,system-ui;font-size:26px;font-weight:700;color:#2d6a4f;letter-spacing:-0.02em;\">R2,453,961.53</span><span style=\"font-family:Geneva,system-ui;font-size:11px;color:#999;\">total sales &#xB7; year one</span></div></div>",
      "displayType": "Ecommerce",
      "subtitle": "E-commerce merchandise rollout"
    },
    {
      "path": "/Desktop/CampusKey/Email Signatures.md",
      "name": "Email Signatures.md",
      "icon": "/icons/default/file-text.png",
      "displayType": "Graphic Design",
      "subtitle": "Seven animated email signatures",
      "content": "# CampusKey Email Signatures\n\n*Motion & graphic design — seven animated email signatures across three campaign years*\n\n<div style=\"margin:0 0 24px 0;\">\n  <img src=\"/projects/campuskey/email-signatures/sig-hero.gif\" alt=\"Most recent CampusKey email signature\" style=\"display:block;max-width:600px;width:100%;background:#f9f9f9;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:6px 0 0 0;font-family:Geneva,system-ui;\">2026 General Email Signature — animated GIF</p>\n</div>\n\n---\n\n## The idea\n\nAn email signature is the smallest piece of real estate in a campaign. It's also one of the most consistent — it goes out on every single email, from every staff member, for the entire duration of a campaign. Getting it right matters.\n\nOver three years and across multiple campaigns I've designed and built CampusKey's animated email signatures from scratch — each one a GIF, each one tied to the campaign running at the time, keeping the brand coherent all the way down to the bottom of an email.\n\n---\n\n## Seven signatures. Three years.\n\n<div style=\"position:relative;margin:8px 0 32px 0;\">\n  <div style=\"position:absolute;left:10px;top:24px;bottom:24px;width:1px;background:#ddd;\"></div>\n\n  <div style=\"margin-bottom:36px;\">\n    <div style=\"display:flex;align-items:center;gap:14px;margin-bottom:20px;\">\n      <div style=\"width:20px;height:20px;border-radius:50%;background:#222;flex-shrink:0;position:relative;z-index:1;\"></div>\n      <span style=\"font-size:13px;font-family:Geneva,system-ui;font-weight:bold;letter-spacing:0.12em;color:#111;text-transform:uppercase;\">2024</span>\n    </div>\n    <div style=\"margin-left:40px;\">\n      <p style=\"font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.1em;font-family:Geneva,system-ui;margin:0 0 10px 0;\">Application Campaign</p>\n      <div style=\"background:#f9f9f9;border:1px solid #ddd;padding:16px;display:block;max-width:600px;\">\n        <img src=\"/projects/campuskey/email-signatures/sig-2024-application.gif\" alt=\"2024 Application Campaign email signature\" style=\"display:block;width:100%;\" />\n      </div>\n    </div>\n  </div>\n\n  <div style=\"margin-bottom:36px;\">\n    <div style=\"display:flex;align-items:center;gap:14px;margin-bottom:20px;\">\n      <div style=\"width:20px;height:20px;border-radius:50%;background:#222;flex-shrink:0;position:relative;z-index:1;\"></div>\n      <span style=\"font-size:13px;font-family:Geneva,system-ui;font-weight:bold;letter-spacing:0.12em;color:#111;text-transform:uppercase;\">2025</span>\n    </div>\n    <div style=\"margin-left:40px;\">\n      <p style=\"font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.1em;font-family:Geneva,system-ui;margin:0 0 10px 0;\">Rebooker Application Campaign</p>\n      <div style=\"background:#f9f9f9;border:1px solid #ddd;padding:16px;display:inline-block;max-width:100%;\">\n        <img src=\"/projects/campuskey/email-signatures/sig-2025-rebooker.gif\" alt=\"2025 Rebooker Application Campaign email signature\" style=\"display:block;max-width:600px;width:100%;\" />\n      </div>\n      <div style=\"border-top:1px solid #eee;margin:20px 0;\"></div>\n      <p style=\"font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.1em;font-family:Geneva,system-ui;margin:0 0 10px 0;\">Public Application Campaign</p>\n      <div style=\"background:#f9f9f9;border:1px solid #ddd;padding:16px;display:inline-block;max-width:100%;\">\n        <img src=\"/projects/campuskey/email-signatures/sig-2025-public.gif\" alt=\"2025 Public Application Campaign email signature\" style=\"display:block;max-width:600px;width:100%;\" />\n      </div>\n      <div style=\"border-top:1px solid #eee;margin:20px 0;\"></div>\n      <p style=\"font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.1em;font-family:Geneva,system-ui;margin:0 0 10px 0;\">General Email Signature</p>\n      <div style=\"background:#f9f9f9;border:1px solid #ddd;padding:16px;display:inline-block;max-width:100%;\">\n        <img src=\"/projects/campuskey/email-signatures/sig-2025-general.gif\" alt=\"2025 General email signature\" style=\"display:block;max-width:600px;width:100%;\" />\n      </div>\n    </div>\n  </div>\n\n  <div style=\"margin-bottom:16px;\">\n    <div style=\"display:flex;align-items:center;gap:14px;margin-bottom:20px;\">\n      <div style=\"width:20px;height:20px;border-radius:50%;background:#222;flex-shrink:0;position:relative;z-index:1;\"></div>\n      <span style=\"font-size:13px;font-family:Geneva,system-ui;font-weight:bold;letter-spacing:0.12em;color:#111;text-transform:uppercase;\">2026</span>\n    </div>\n    <div style=\"margin-left:40px;\">\n      <p style=\"font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.1em;font-family:Geneva,system-ui;margin:0 0 10px 0;\">Rebooker Application Campaign</p>\n      <div style=\"background:#f9f9f9;border:1px solid #ddd;padding:16px;display:inline-block;max-width:100%;\">\n        <img src=\"/projects/campuskey/email-signatures/sig-2026-rebooker.gif\" alt=\"2026 Rebooker Application Campaign email signature\" style=\"display:block;max-width:600px;width:100%;\" />\n      </div>\n      <div style=\"border-top:1px solid #eee;margin:20px 0;\"></div>\n      <p style=\"font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.1em;font-family:Geneva,system-ui;margin:0 0 10px 0;\">Public Application Campaign</p>\n      <div style=\"background:#f9f9f9;border:1px solid #ddd;padding:16px;display:inline-block;max-width:100%;\">\n        <img src=\"/projects/campuskey/email-signatures/sig-2026-public.gif\" alt=\"2026 Public Application Campaign email signature\" style=\"display:block;max-width:600px;width:100%;\" />\n      </div>\n      <div style=\"border-top:1px solid #eee;margin:20px 0;\"></div>\n      <p style=\"font-size:10px;color:#888;text-transform:uppercase;letter-spacing:0.1em;font-family:Geneva,system-ui;margin:0 0 10px 0;\">General Email Signature</p>\n      <div style=\"background:#f9f9f9;border:1px solid #ddd;padding:16px;display:inline-block;max-width:100%;\">\n        <img src=\"/projects/campuskey/email-signatures/sig-2026-general.gif\" alt=\"2026 General email signature\" style=\"display:block;max-width:600px;width:100%;\" />\n      </div>\n    </div>\n  </div>\n\n</div>\n\n---\n\n## What this shows\n\nSeven signatures. Three years. Multiple campaign identities. Each one designed specifically for the moment it was meant to live in — not recycled, not resized from something else, built fresh each time.\n\nThis is the kind of detail that most designers don't think about. The email signature is the last thing to get designed and the first thing to get ignored. Treating it as a proper campaign deliverable — animated, considered, campaign-specific — is a small thing that adds up across thousands of emails sent."
    },
    {
      "path": "/Desktop/CampusKey/Print & Physical.md",
      "name": "Print & Physical.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "displayType": "Graphic Design",
      "subtitle": "Print collateral and physical branded environments",
      "content": "# CampusKey Print & Physical\n\n<p style=\"margin:-8px 0 24px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">Not everything lives on a screen. Here's what CampusKey looks like in the real world.</p>\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/campuskey/print-physical/print-hero.png\" alt=\"CampusKey print — expo stand in situ\" style=\"width:100%;display:block;max-height:480px;object-fit:cover;object-position:top;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\nI spend a lot of time building things that live in browsers. But a significant part of what I do at CampusKey exists in the physical world — things you can touch, stand next to, stick on a laptop, drink coffee from.\n\nEvery piece of print collateral that goes out under the CampusKey name comes through me. That means understanding not just how something looks on screen but how it behaves when it's three metres tall and bolted to a telescopic flag pole, or printed on a corrugated coffee cup, or stuck to the back window of a car as a parking disc. Different constraints. Different files. Same attention to detail.\n\n---\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Expo stand · Full branded environment</p>\n<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:0 0 28px 0;\">\n  <div>\n    <img src=\"/projects/campuskey/print-physical/print-expo-stand-1.jpg\" alt=\"CampusKey expo stand\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  </div>\n  <div>\n    <img src=\"/projects/campuskey/print-physical/print-expo-stand-2.jpg\" alt=\"CampusKey expo stand\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  </div>\n</div>\n\nThe expo stand work is probably the most technically involved — you're designing for a three dimensional branded environment, not a flat surface. Every panel, every angle, every sight line needs to work together. Get one element wrong and the whole thing reads wrong when it's assembled on the day.\n\n---\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Large format — flag poles · gazebo · billboard</p>\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/campuskey/print-physical/print-large-format.png\" alt=\"Large format print — flag poles, gazebo, billboard\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Small format — stickers · parking discs · coffee cups · packaging</p>\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/campuskey/print-physical/print-small-format.png\" alt=\"Laptop stickers, parking discs, coffee cups, custom packaging\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\nThe smaller format stuff is its own discipline. A parking disc has about four square centimetres of usable space and needs to communicate the brand, the campus, and a vehicle registration number clearly. A coffee cup wraps around a cylinder and has to account for the seam. A laptop sticker gets placed next to every other sticker someone owns and still needs to hold its own.\n\nNone of this is glamorous work. Nobody tweets about a parking disc. But it all adds up — every physical touchpoint is a moment where the brand either holds together or it doesn't. Making sure it holds together is the job."
    },
    {
      "path": "/Desktop/Game of Life.html",
      "name": "Game of Life.html",
      "isDirectory": false,
      "type": "application",
      "icon": "/icons/default/gameoflife.png",
      "status": "active",
      "appId": "gameoflife",
      "subtitle": "Conway's cellular automaton",
      "aliasType": "app",
      "aliasTarget": "gameoflife"
    },
    {
      "path": "/Desktop/Experimental/51DAYS.md",
      "name": "51DAYS.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/51days.png",
      "status": "active",
      "displayType": "UI / UX & Web",
      "subtitle": "Turn 15 days of leave into 51",
      "content": "# 51 DAYS\n\n<p style=\"margin:-8px 0 24px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">Work smarter, not longer.</p>\n\n**15 days of leave. 51 days off. That's not a hack — that's just maths your HR department hopes you never do.**\n\n---\n\n<div style=\"border-radius:12px;overflow:hidden;margin:28px 0;\">\n  <img src=\"/experiments/51days/calendar.png\" alt=\"51 Days — app mockup\" class=\"md-img\" style=\"width:100%;display:block;\" />\n</div>\n\nSo your company gives you 15 days of leave. Cool. Except if you just take random Fridays off like a normal person you're going to waste half of them. The system is designed for you to not think about this. I thought about it.\n\nIf you're smart about it — bridge days, public holidays, weekends — 15 days of actual leave can become 51 days off. That's not a hack. That's just maths that your HR department hopes you never do.\n\nSo I built a tool that does the maths for you.\n\n---\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin:28px 0;\">\n  <img src=\"/experiments/51days/state-1-input.png\" alt=\"51 Days — input screen\" style=\"width:100%;border-radius:8px;display:block;\" />\n  <img src=\"/experiments/51days/state-2-calendar.png\" alt=\"51 Days — calendar view\" style=\"width:100%;border-radius:8px;display:block;\" />\n  <img src=\"/experiments/51days/state-3-share.png\" alt=\"51 Days — share output\" style=\"width:100%;border-radius:8px;display:block;\" />\n</div>\n\n---\n\nYou put in your country, you put in how many leave days you have, and it maps out the entire year. Here's when to take off. Here's the bridge day you missed. Here's how to turn that long weekend into a 10 day break without using more than 3 days of leave. It even writes the HR email for you so you don't have to think about how to word it.\n\nThe share feature was important to me. You should be able to send this to a friend. Or a colleague. Or post it to your stories. Spread the knowledge. Everyone deserves to know this stuff.\n\nIt's not anti-work. I like working. I just think if the leave is yours you should actually use it properly.\n\n<a href=\"https://51days.app/\" style=\"display:none;\" aria-hidden=\"true\"></a>"
    },
    {
      "path": "/Desktop/Experimental/OHCRUMBS.md",
      "name": "OHCRUMBS.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/ohcrumbs.png",
      "status": "active",
      "displayType": "UI / UX & Web",
      "subtitle": "Tough love fortune cookie",
      "content": "# OH CRUMBS\n\n<p style=\"margin:-8px 0 24px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">Crack it. Face it. Fix it.</p>\n\n**A fortune cookie that actually says something.**\n\n---\n\n<div style=\"margin:28px 0;\">\n  <img src=\"/experiments/ohcrumbs/hero.png\" alt=\"Oh Crumbs\" style=\"width:100%;display:block;max-height:480px;object-fit:cover;object-position:center;border-radius:10px;\" />\n</div>\n\nI wanted to make a fortune cookie. But not the kind that tells you “good things are coming” or “you will find success in unexpected places.” That stuff is useless.\n\nI wanted one that actually says something. The kind of message you read and go oh. That one stings a little. Good.\n\nSo that’s what I built. You tap the cookie, it cracks open, you get a fortune. Except the fortunes are blunt and a bit uncomfortable and probably something you already knew but didn’t want to think about. Crack it. Face it. Fix it. That’s the whole idea.\n\n---\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin:28px 0;\">\n  <img src=\"/experiments/ohcrumbs/state-1-closed.png\" alt=\"Closed cookie\" style=\"width:100%;border-radius:8px;display:block;\" />\n  <img src=\"/experiments/ohcrumbs/state-2-crack.png\" alt=\"Cracking cookie\" style=\"width:100%;border-radius:8px;display:block;\" />\n  <img src=\"/experiments/ohcrumbs/state-3-open.png\" alt=\"Fortune revealed\" style=\"width:100%;border-radius:8px;display:block;\" />\n</div>\n\nThe animation took longer than I expected. Getting the crack to feel satisfying — the timing, the easing, the two halves splitting apart — that kind of thing matters more than people think. If the crack doesn’t feel right the whole thing falls apart.\n\nThere’s haptics too on devices that support it. A short vibration when it opens. Another small detail that just makes it feel more real.\n\nYou can copy the fortune and send it to someone. That was always part of it. Some of these are messages people need to receive, not just read.\n\n<a href=\"https://ohcrumbs.vercel.app/\" style=\"display:none;\" aria-hidden=\"true\"></a>"
    },
    {
      "path": "/Desktop/Experimental/ThisDay.md",
      "name": "ThisDay.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "displayType": "UI / UX & Web",
      "subtitle": "A newspaper that publishes itself every morning",
      "content": "# THIS DAY\n\n<p style=\"margin:-8px 0 24px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">A newspaper that publishes itself every morning.</p>\n\n**No CMS, no API, no backend. Just today.**\n\n---\n\n<div style=\"margin:28px 0;overflow:hidden;border-radius:6px;\">\n  <img src=\"/experiments/thisday/media/hero.png\" alt=\"This Day — full newspaper layout\" class=\"md-img\" style=\"width:100%;display:block;max-height:480px;object-fit:cover;object-position:top;\" />\n</div>\n\nI wanted to make something worth opening every single day. Not a widget. Not a dashboard. Something that felt like it had been made. Like someone sat down and put it together.\n\nSo I made a newspaper. Except it publishes itself.\n\nEvery day you open it, it reads your local date, looks it up in a dataset I curated, and builds the whole edition from scratch. Today in history. A strange but true fact. Word of the day. A number with context. A pull quote if there's a good one. All of it locked to that specific calendar day.\n\nThe design was the whole point honestly. Special Elite for the masthead. Playfair for the headlines. Paper and ink colour palette. Gold accents. Red kickers. Actual newspaper rules and bylines. I didn't want it to look like a web app pretending to be a newspaper. I wanted it to look like a newspaper that happened to be on a screen.\n\n---\n\nNo React. No bundler. No framework. Just an HTML file, some embedded CSS, and a JavaScript object with 300+ days of curated content. You open the URL and it works. That's it. That's the whole thing.\n\nThe data lives in a JSON file. There's a tiny Node script that compiles it into a variable the browser can load directly. Want to add a new day? Edit the JSON, run the script, redeploy. Done.\n\nIf there's no entry for today it shows a clean &ldquo;no edition&rdquo; state instead of breaking. Small detail but it felt important to get right.\n\nEvery day has a story worth knowing. That's the tagline. I genuinely believe it.\n\n---\n\n<div style=\"display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin:28px 0;\">\n  <img src=\"/experiments/thisday/media/state-1-masthead.png\" alt=\"This Day — masthead\" style=\"width:100%;border-radius:8px;display:block;\" />\n  <img src=\"/experiments/thisday/media/state-2-sections.png\" alt=\"This Day — sections\" style=\"width:100%;border-radius:8px;display:block;\" />\n  <img src=\"/experiments/thisday/media/state-3-mobile.png\" alt=\"This Day — mobile\" style=\"width:100%;border-radius:8px;display:block;\" />\n</div>\n\n<div style=\"margin:40px 0;padding:20px 28px;border-top:2px solid #1a1a1a;border-bottom:2px solid #1a1a1a;text-align:center;\">\n  <p style=\"font-family:Georgia,'Times New Roman',serif;font-size:17px;font-style:italic;line-height:1.5;color:#1a1a1a;margin:0;letter-spacing:0.01em;\">&ldquo;Every day has a story worth knowing.&rdquo;</p>\n</div>\n\n<a href=\"https://thisdaynews.vercel.app/\" style=\"display:none;\" aria-hidden=\"true\"></a>"
    },
    {
      "path": "/Desktop/Experimental/Depthfield.md",
      "name": "Depthfield.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/3dtracker.png",
      "status": "active",
      "displayType": "UI / UX & Web",
      "subtitle": "Your monitor as a window into another space",
      "content": "# DEPTHFIELD\n\n<p style=\"margin:-8px 0 24px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">Your face is the controller.</p>\n\n**Move your head — the 3D scene moves with you, like looking through a window into another space.**\n\n---\n\n<div style=\"margin:28px 0;border-radius:12px;overflow:hidden;background:#0a0a0a;\">\n  <img src=\"/experiments/3dtracker/media/demo.gif\" alt=\"DepthField — head-coupled 3D viewer\" style=\"width:100%;display:block;\" />\n</div>\n\nI saw a TikTok video. That's how this started. Someone had built this thing where their monitor looked like a window into another space and when they moved their head the 3D scene moved with them. I watched it probably four times. Then I closed the app and started figuring out how to build it myself.\n\nThe original idea isn't mine. Head-coupled perspective has been around since the 90s. But I wanted to see if I could get it running in a browser, in real time, with a webcam — and then add my own thing to it.\n\nMediaPipe reads your face. Thousands of landmarks tracked every single frame. I take that data, smooth it out, and feed it into the Three.js camera so the scene actually responds to where your head is in space. It's one of those things where when it works you just sit there moving your head back and forth like an idiot because it feels genuinely strange and cool.\n\n---\n\nThe calibration part took longer than the tracking part honestly. You have to enter your actual screen size and how far you're sitting from it so the maths lines up with reality. Without that it's just vibes. With it, it actually feels like the object exists behind the glass.\n\nMy version of it though — I wanted a real 3D object in the scene. Not something downloaded. So I modelled an Xbox controller in Blender myself, textured it, and exported it for the web. That was my contribution. That's the part that felt like mine.\n\n<p style=\"font-size:10px;color:#999;text-align:center;margin:-20px 0 32px;font-family:Geneva,system-ui;\">Built on an existing head-coupled viewer foundation — calibration system, UI, and the Blender-authored 3D assets are original work.</p>\n\n<a href=\"https://depthfield-vert.vercel.app/\" style=\"display:none;\" aria-hidden=\"true\"></a>"
    },
    {
      "path": "/Desktop/Freelance/EmailDesign.md",
      "name": "EmailDesign.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "displayType": "Email Design",
      "subtitle": "Three brands, three worlds, one consistent skill",
      "content": "# Email Design\n\n<p style=\"margin:-8px 0 16px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">Three completely different brands. Three completely different worlds. One consistent skill — making emails people actually want to open.</p>\n\n<div style=\"display:inline-flex;align-items:center;gap:8px;background:#f0f0f0;border:1px solid #ccc;box-shadow:1px 1px 0 #999;padding:4px 10px;margin:0 0 24px 0;\">\n  <img src=\"https://cdn.simpleicons.org/klaviyo/333333\" alt=\"Klaviyo\" style=\"width:16px;height:16px;display:block;\" />\n  <span style=\"font-size:10px;font-family:Geneva,system-ui;color:#333;\">Built in Klaviyo</span>\n</div>\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/email-design/email-hero.png\" alt=\"Email design hero\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\nEmail design is one of those disciplines that looks simple until you’re actually doing it. Every brand has a different voice, a different visual system, a different audience, and a different reason for landing in someone’s inbox that week. Your job is to make it feel like it belongs there.\n\nOver several months I designed regular marketing emails for three very different brands through an agency — all built and delivered in Klaviyo.\n\n---\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Squatchbox · Outdoor & Car Rooftop Tents</p>\n<div style=\"margin:0 0 16px 0;\">\n  <img src=\"/projects/email-design/squatchbox-emails.png\" alt=\"Squatchbox email examples\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\nSquatchbox is an outdoor camping brand specialising in car rooftop tents. The emails needed to feel adventurous and aspirational — big imagery, strong headlines, the kind of energy that makes you want to pack the car and leave on Friday afternoon. Outdoorsy without being try-hard.\n\n---\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">So Luxury · Natural Bath, Body & Cleaning Products</p>\n<div style=\"margin:0 0 16px 0;\">\n  <img src=\"/projects/email-design/so-luxury-emails.png\" alt=\"So Luxury email examples\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\nSo Luxury is a natural bath, body, and cleaning products brand. Completely different world — clean, soft, considered. The emails here needed to breathe. White space, elegant typography, the kind of layout that makes the product feel like a treat rather than a purchase.\n\n---\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Heal Faster · Health & Recovery</p>\n<div style=\"margin:0 0 16px 0;\">\n  <img src=\"/projects/email-design/heal-faster-emails.png\" alt=\"Heal Faster email examples\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\nHeal Faster is a health and recovery brand. Clinical enough to feel credible, approachable enough to not feel cold. The emails needed to communicate product benefits clearly without tipping into medical brochure territory.\n\n---\n\nThree brands, three completely different briefs, three completely different visual languages. The skill isn’t having one style — it’s being able to step fully into someone else’s brand world and make something that feels like it was always supposed to be there."
    },
    {
      "path": "/Desktop/Freelance/SYMBODI.md",
      "name": "SYMBODI.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "displayType": "Graphic Design",
      "subtitle": "Cheeky ad campaign for a massage ball brand",
      "content": "# SYMBODI\n\n<p style=\"margin:-8px 0 24px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">Cheeky enough to stop the scroll. Clean enough to actually run.</p>\n\n**When the product is a blue massage ball that mounts on a wall, you either play it safe or you have some fun with it. I had some fun with it.**\n\n---\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/symbodi/vertiball-row.png\" alt=\"Vertiball — six cheeky ad variants\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Vertiball · Single ball · Wall mounted — all six ad variants</p>\n</div>\n\n## The brief\n\nSymbodi make wall-mounted massage balls and massage guns. They needed social and display ad graphics for Google and Facebook and gave me fairly free rein on the creative direction.\n\nFree rein is dangerous. I used it.\n\n---\n\n## The idea\n\nI was thinking about Naked Insurance in South Africa. They built an entire brand on a word that could mean two things and leaned into it completely — *\"Hanna got NAKED at the gym\", \"Cameron got NAKED during a meeting.\"* Risky on paper. Brilliant in execution. Nobody forgets it.\n\nSymbodi had the same setup sitting right in front of them. The Vertiball is blue. It mounts on a wall. People rub it. The taglines practically wrote themselves.\n\n**The Vertiball:**\n\n- The blue balls you actually want.\n- Your pleasure with every mount.\n- It's not cheating when it's with Symbodi.\n\n**The Vertiroller — two balls, different energy:**\n\n- Two balls are always better than one.\n- Your muscles are begging for these balls.\n- Deep relief. No strings attached.\n\n**The Vertigun — a different flavour of cheeky:**\n\n- Feeling knotty? We'll fix that.\n- Unlock the pleasure you've been missing.\n- We'll work every inch until you're loose.\n\nCheeky enough to stop the scroll. Clean enough to actually run as an ad.\n\n---\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Vertiroller · Dual ball · Wall mounted</p>\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/symbodi/vertiroller-row.png\" alt=\"Vertiroller — six cheeky ad variants\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Vertigun · Percussion massage gun</p>\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/symbodi/vertigun-row.png\" alt=\"Vertigun — six cheeky ad variants\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n<p style=\"font-size:11px;color:#888;margin:0 0 4px 0;font-family:Geneva,system-ui;text-transform:uppercase;letter-spacing:0.08em;\">Alternate direction · Conventional variants</p>\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/symbodi/safe-variants-row.png\" alt=\"Alternate direction — conventional product variants\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\n---\n\n## What I delivered\n\nAd graphics for three products across the Symbodi range — the Vertiball (single blue wall-mounted ball), the Vertiroller (dual ball roller), and the Vertigun (massage gun) — each with multiple tagline variants for social and display use. Each product got its own set of cheeky taglines tailored to exactly what it does. The creative direction, copywriting, and design were all mine.\n\nAlongside the cheeky campaign, I also produced a set of straightforward product graphics for when the brief called for something more conventional. Same product, different energy. Knowing when to dial it up and when to pull it back is part of the job.\n\nSome of the ads ran. Some didn't. The ones that didn't were probably the most fun."
    },
    {
      "path": "/Desktop/Freelance/GEMS_AND_JUNIPER.md",
      "name": "GEMS_AND_JUNIPER.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "displayType": "UI / UX & Web",
      "subtitle": "Password-protected brand hub for a jewellery studio",
      "content": "# GEMS & JUNIPER\n\n<p style=\"margin:-8px 0 24px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">A brand hub that feels like the brand it's describing.</p>\n\n**A brand guide that actually feels like the brand it's describing. Password protected, editorially designed, and built to replace a folder full of scattered files.**\n\n---\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/gems-juniper/login.png\" alt=\"Gems & Juniper — password-protected login page\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">The login page — the password protection is part of the story</p>\n</div>\n\nMost brand guides are PDFs. Long, flat, forgettable PDFs that live in a Google Drive folder nobody remembers the name of. You need the logo? Good luck finding which version is the right one. You need to know the tone of voice for a condolence piece? Hope you saved the email from six months ago.\n\nGems & Juniper needed something better than that. They make handmade jewellery — memorial pieces, keepsakes, milk jewellery — and the brand has real emotional weight to it. It deserved a home that matched.\n\nSo I built them one.\n\n---\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/gems-juniper/home.png\" alt=\"Gems & Juniper — home page editorial layout\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">The home page — editorial layout, typography system, navigation structure</p>\n</div>\n\nIt's a private, password-protected brand hub. You get in with a password, and then everything is there. The brand story. The tone guidelines broken down by product context — because how you talk about a cremation piece is very different from how you talk about an everyday ring. The logo suite with actual download buttons. The colour palette with one-click copy for HEX, RGB, and CMYK values. The typography system. All of it in one place, navigable, designed to actually feel like the brand it's describing.\n\nThe auth isn't just a gate for the sake of it. Brand assets are proprietary. The tone guidelines are proprietary. This is a real internal tool and it needed to be treated like one — hashed passwords, rate-limited APIs, upload validation, contact honeypot. The security side was as considered as the design side.\n\nThe palette alone took longer than expected to get right. Opal. Gold. Lilac. Amethyst. Charcoal Agate. Every colour needed to work editorially and functionally — as a swatch, as a copied value, as a background. Getting that to feel cohesive across a full site rather than just a colour page was the interesting problem.\n\n---\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/gems-juniper/colours.png\" alt=\"Gems & Juniper — colour palette page with copy buttons\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Colours — swatches, gem-inspired palette, one-click HEX · RGB · CMYK copy</p>\n</div>\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/gems-juniper/logos.png\" alt=\"Gems & Juniper — logo suite with download buttons\" style=\"width:100%;display:block;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n  <p style=\"font-size:11px;color:#888;margin:4px 0 0 0;font-family:Geneva,system-ui;text-align:center;\">Logos — full suite layout with direct download buttons built into the hub</p>\n</div>\n\nI can't link you to the live site. It's password protected and it's a client's internal tool. But that's kind of the point — it exists for them, not for a portfolio. What I can show you is how it's built and what it feels like to use.\n\n---\n\n## The tech\n\n<div style=\"background:#f0f0f0;border:1px solid #ccc;box-shadow:2px 2px 0 #999;padding:12px 16px;margin:12px 0;\">\n  <div style=\"display:flex;justify-content:space-between;align-items:flex-start;\">\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/nextdotjs/333333\" alt=\"Next.js\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Next.js</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/react/333333\" alt=\"React\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">React</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/typescript/333333\" alt=\"TypeScript\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">TypeScript</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/tailwindcss/333333\" alt=\"Tailwind CSS\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Tailwind</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/framer/333333\" alt=\"Framer Motion\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Framer</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <div style=\"width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:14px;line-height:1;\">🔐</div>\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">bcryptjs</span>\n    </div>\n    <div style=\"display:flex;flex-direction:column;align-items:center;gap:5px;\">\n      <img src=\"https://cdn.simpleicons.org/vercel/333333\" alt=\"Vercel\" style=\"width:22px;height:22px;display:block;\" />\n      <span style=\"font-size:9px;font-family:Geneva,system-ui;color:#333;text-align:center;line-height:1.2;\">Vercel</span>\n    </div>\n  </div>\n</div>"
    },
    {
      "path": "/Desktop/CampusKey/AsanaTools.md",
      "name": "AsanaTools.md",
      "isDirectory": false,
      "type": "markdown",
      "icon": "/icons/default/file-text.png",
      "status": "active",
      "displayType": "Automation",
      "subtitle": "Asana task routing & approval nudger",
      "content": "# ASANA AUTOMATION TOOLS\n\n<p style=\"margin:-8px 0 16px;font-size:13px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;opacity:0.45;\">Task Sorter · Approval Nudger</p>\n\n\n**Two browser tools. Two pinned tabs. Thirty minutes of Monday morning admin — gone.**\n\n---\n\n<div style=\"margin:0 0 28px 0;\">\n  <img src=\"/projects/campuskey/asana-tools-hero.png\" alt=\"Task Sorter and Approval Nudger side by side\" style=\"width:100%;display:block;max-height:480px;object-fit:cover;object-position:top;border:1px solid #ccc;box-shadow:2px 2px 0 #999;\" />\n</div>\n\nEverything at CampusKey runs through Asana. Every brief, every request, every approval, every follow-up. Six campuses, one designer, one inbox. On a good day that's manageable. On a real day it's a firehose.\n\nTwo things were grinding time down every single week. Tasks landing in an unsorted inbox that all needed to be manually dragged into the right campus section. And design approvals sitting unreviewed for days with no escalation mechanism, no visibility, and no way to chase someone without it feeling awkward.\n\nI built two tools to fix both of those things. For myself. Nobody else needed to know they existed.\n\n---\n\n### Task Sorter\n\nTask Sorter connects to the Asana API, reads every unsorted task, figures out where it belongs based on who sent it and who it's assigned to, and moves it there automatically — every hour, in the background, while I'm doing something else. I built it entirely for myself. Tasks just end up in the right place and I never have to think about it.\n\nFirst time setup runs a discovery pass through the last year of activity, builds a list of everyone who's ever sent a task, and lets you set a routing rule for each person in about two minutes. Save it once, never think about it again.\n\n---\n\n### Approval Nudger\n\nApproval Nudger surfaces every pending approval I've submitted, shows overdue status, tracks nudge history, and posts a follow-up @mention comment directly to the approver in one click — with a 48-hour cooldown so it doesn't become harassment. Before this I was manually hunting for unreviewed tasks, remembering who I'd already chased, writing a comment, copying the @mention handle, hoping it would be seen. That whole loop is now one tab and one click.\n\n---\n\nBoth tools are single HTML files. No build step, no npm, no dependencies. Open in Chrome, pin the tab, done. The kind of solution that only makes sense when the goal is removing friction, not adding process."
    },
    {
      "path": "/Games/Pokémon Pack Opener.html",
      "name": "Pokémon Pack Opener",
      "isDirectory": false,
      "type": "application",
      "icon": "/icons/default/pokemon-pack.png",
      "status": "active",
      "appId": "pokemon-pack"
    }
  ],
  "version": 1
}