{"id":2,"date":"2026-04-14T11:46:32","date_gmt":"2026-04-14T11:46:32","guid":{"rendered":"https:\/\/muddev.co.za\/test-space\/?page_id=2"},"modified":"2026-04-14T12:15:52","modified_gmt":"2026-04-14T12:15:52","slug":"sample-page","status":"publish","type":"page","link":"https:\/\/muddev.co.za\/test-space\/sample-page\/","title":{"rendered":"Sample Page"},"content":{"rendered":"\n<!--\n  3D HERO EXPERIENCE \u2014 Kadence Custom HTML Block\n  Three.js + Bloom + GSAP ScrollTrigger + Mouse-Reactive 3D\n  Paste entire contents into a Gutenberg Custom HTML block\n-->\n\n<!-- Fonts -->\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&#038;family=Syne:wght@400;500;600;700;800&#038;display=swap\" rel=\"stylesheet\">\n\n<!-- GSAP + ScrollTrigger -->\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/gsap@3.12.5\/dist\/gsap.min.js\"><\/script>\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/gsap@3.12.5\/dist\/ScrollTrigger.min.js\"><\/script>\n\n<style>\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     RESET & SCOPING\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  .xp * { margin: 0; padding: 0; box-sizing: border-box; }\n  .xp { --c-bg: #050507; --c-surface: #0c0d12; --c-text: #e8e4dc; --c-muted: rgba(232,228,220,0.35); --c-accent: #d4a046; --c-accent2: #e8552e; --c-glow: rgba(212,160,70,0.4); --c-glow2: rgba(232,85,46,0.25); --c-line: rgba(232,228,220,0.06); --f-display: 'Playfair Display', serif; --f-body: 'Syne', sans-serif; --ease: cubic-bezier(0.16, 1, 0.3, 1); font-family: var(--f-body); color: var(--c-text); background: var(--c-bg); overflow-x: hidden; }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     THREE.JS CANVAS \u2014 FIXED BACKGROUND\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  .xp-canvas-wrap {\n    position: fixed;\n    inset: 0;\n    z-index: 0;\n  }\n  .xp-canvas-wrap canvas { display: block; width: 100%; height: 100%; }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     SCROLL CONTAINER\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  .xp-scroll { position: relative; z-index: 1; }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     SECTION 1 \u2014 HERO\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  .xp-hero {\n    position: relative;\n    min-height: 100vh;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n    padding: 2rem;\n  }\n\n  \/* Tag *\/\n  .xp-tag {\n    display: inline-flex;\n    align-items: center;\n    gap: 10px;\n    font-family: var(--f-body);\n    font-size: 0.7rem;\n    font-weight: 600;\n    letter-spacing: 0.25em;\n    text-transform: uppercase;\n    color: var(--c-accent);\n    margin-bottom: 2.5rem;\n    opacity: 0;\n  }\n  .xp-tag::before {\n    content: '';\n    width: 30px;\n    height: 1px;\n    background: var(--c-accent);\n  }\n  .xp-tag::after {\n    content: '';\n    width: 30px;\n    height: 1px;\n    background: var(--c-accent);\n  }\n\n  \/* Headline *\/\n  .xp-headline {\n    font-family: var(--f-display);\n    font-size: clamp(3rem, 8vw, 7rem);\n    font-weight: 400;\n    line-height: 1.0;\n    letter-spacing: -0.03em;\n    max-width: 1000px;\n    margin-bottom: 2rem;\n  }\n  .xp-headline-line {\n    display: block;\n    overflow: hidden;\n    padding-bottom: 0.05em;\n  }\n  .xp-headline-inner {\n    display: inline-block;\n    transform: translateY(120%);\n    opacity: 0;\n  }\n  .xp-headline em {\n    font-family: var(--f-display);\n    font-style: italic;\n    color: var(--c-accent);\n  }\n\n  \/* Subtext *\/\n  .xp-subtext {\n    font-family: var(--f-body);\n    font-size: clamp(0.95rem, 1.5vw, 1.15rem);\n    font-weight: 400;\n    line-height: 1.8;\n    color: var(--c-muted);\n    max-width: 520px;\n    margin-bottom: 3rem;\n    opacity: 0;\n  }\n\n  \/* CTA row *\/\n  .xp-cta-row {\n    display: flex;\n    gap: 1rem;\n    flex-wrap: wrap;\n    justify-content: center;\n    opacity: 0;\n  }\n  .xp-cta {\n    font-family: var(--f-body);\n    font-size: 0.85rem;\n    font-weight: 600;\n    letter-spacing: 0.05em;\n    text-transform: uppercase;\n    padding: 16px 36px;\n    border-radius: 4px;\n    text-decoration: none;\n    cursor: pointer;\n    transition: all 0.4s var(--ease);\n    position: relative;\n  }\n  .xp-cta--fill {\n    background: var(--c-accent);\n    color: var(--c-bg);\n    border: 1px solid var(--c-accent);\n  }\n  .xp-cta--fill:hover {\n    background: #c4912e;\n    box-shadow: 0 0 40px var(--c-glow), 0 10px 30px rgba(0,0,0,0.4);\n    transform: translateY(-3px);\n  }\n  .xp-cta--ghost {\n    background: transparent;\n    color: var(--c-text);\n    border: 1px solid var(--c-line);\n  }\n  .xp-cta--ghost:hover {\n    border-color: var(--c-accent);\n    color: var(--c-accent);\n    transform: translateY(-3px);\n  }\n\n  \/* Scroll prompt *\/\n  .xp-scroll-prompt {\n    position: absolute;\n    bottom: 2.5rem;\n    left: 50%;\n    transform: translateX(-50%);\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 8px;\n    opacity: 0;\n  }\n  .xp-scroll-prompt span {\n    font-size: 0.6rem;\n    letter-spacing: 0.3em;\n    text-transform: uppercase;\n    color: var(--c-muted);\n  }\n  .xp-scroll-prompt .xp-scroll-dot {\n    width: 1px;\n    height: 50px;\n    position: relative;\n    overflow: hidden;\n    background: var(--c-line);\n  }\n  .xp-scroll-dot::after {\n    content: '';\n    position: absolute;\n    top: -100%;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background: linear-gradient(to bottom, transparent, var(--c-accent));\n    animation: scrollDot 2s ease-in-out infinite;\n  }\n  @keyframes scrollDot {\n    0% { top: -100%; }\n    50% { top: 100%; }\n    100% { top: 100%; }\n  }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     SECTION 2 \u2014 MANIFESTO\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  .xp-manifesto {\n    min-height: 100vh;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding: 6rem 2rem;\n  }\n  .xp-manifesto-inner {\n    max-width: 800px;\n    text-align: center;\n  }\n  .xp-manifesto-text {\n    font-family: var(--f-display);\n    font-size: clamp(1.6rem, 3.5vw, 2.8rem);\n    font-weight: 400;\n    line-height: 1.5;\n    color: var(--c-text);\n  }\n  .xp-manifesto-text .xp-word {\n    display: inline-block;\n    opacity: 0.08;\n    transition: opacity 0.3s ease;\n  }\n  .xp-manifesto-text em {\n    font-style: italic;\n    color: var(--c-accent);\n  }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     SECTION 3 \u2014 CAPABILITIES\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  .xp-capabilities {\n    min-height: 100vh;\n    padding: 8rem 2rem;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n  }\n  .xp-cap-label {\n    font-family: var(--f-body);\n    font-size: 0.7rem;\n    font-weight: 600;\n    letter-spacing: 0.25em;\n    text-transform: uppercase;\n    color: var(--c-accent);\n    margin-bottom: 3rem;\n    opacity: 0;\n  }\n  .xp-cap-grid {\n    display: grid;\n    grid-template-columns: repeat(4, 1fr);\n    gap: 1px;\n    max-width: 1100px;\n    width: 100%;\n    background: var(--c-line);\n    border: 1px solid var(--c-line);\n  }\n  .xp-cap-card {\n    background: var(--c-bg);\n    padding: 2.5rem 2rem;\n    position: relative;\n    overflow: hidden;\n    opacity: 0;\n    transform: translateY(40px);\n    transition: background 0.4s ease;\n  }\n  .xp-cap-card:hover {\n    background: var(--c-surface);\n  }\n  .xp-cap-card::before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 2px;\n    background: linear-gradient(90deg, transparent, var(--c-accent), transparent);\n    transform: scaleX(0);\n    transition: transform 0.6s var(--ease);\n  }\n  .xp-cap-card:hover::before {\n    transform: scaleX(1);\n  }\n  .xp-cap-num {\n    font-family: var(--f-body);\n    font-size: 0.65rem;\n    font-weight: 600;\n    letter-spacing: 0.15em;\n    color: var(--c-accent);\n    margin-bottom: 1.5rem;\n    display: block;\n  }\n  .xp-cap-title {\n    font-family: var(--f-display);\n    font-size: clamp(1.1rem, 1.8vw, 1.35rem);\n    font-weight: 400;\n    line-height: 1.3;\n    margin-bottom: 0.75rem;\n    color: var(--c-text);\n  }\n  .xp-cap-desc {\n    font-family: var(--f-body);\n    font-size: 0.8rem;\n    font-weight: 400;\n    line-height: 1.6;\n    color: var(--c-muted);\n  }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     SECTION 4 \u2014 CLOSING CTA\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  .xp-closing {\n    min-height: 80vh;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n    padding: 6rem 2rem;\n    position: relative;\n  }\n  .xp-closing-headline {\n    font-family: var(--f-display);\n    font-size: clamp(2.5rem, 6vw, 5rem);\n    font-weight: 400;\n    line-height: 1.05;\n    margin-bottom: 2rem;\n    opacity: 0;\n  }\n  .xp-closing-headline em {\n    font-style: italic;\n    color: var(--c-accent2);\n  }\n  .xp-closing-sub {\n    font-size: 1rem;\n    color: var(--c-muted);\n    margin-bottom: 2.5rem;\n    opacity: 0;\n  }\n  .xp-closing-cta {\n    opacity: 0;\n  }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     RESPONSIVE\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  @media (max-width: 1024px) {\n    .xp-cap-grid { grid-template-columns: repeat(2, 1fr); }\n  }\n  @media (max-width: 768px) {\n    .xp-hero { min-height: 100svh; padding: 2rem 1.5rem; }\n    .xp-cap-grid { grid-template-columns: 1fr; }\n    .xp-cap-card { padding: 2rem 1.5rem; }\n    .xp-scroll-prompt { display: none; }\n    .xp-manifesto { padding: 4rem 1.5rem; }\n    .xp-capabilities { padding: 4rem 1.5rem; }\n  }\n  @media (max-width: 480px) {\n    .xp-cta-row { flex-direction: column; width: 100%; }\n    .xp-cta { width: 100%; text-align: center; }\n    .xp-headline { font-size: clamp(2.4rem, 11vw, 3.5rem); }\n  }\n\n  \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     REDUCED MOTION\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n  @media (prefers-reduced-motion: reduce) {\n    .xp *, .xp *::before, .xp *::after {\n      animation-duration: 0.01ms !important;\n      transition-duration: 0.01ms !important;\n    }\n    .xp-headline-inner, .xp-tag, .xp-subtext, .xp-cta-row,\n    .xp-scroll-prompt, .xp-cap-card, .xp-cap-label,\n    .xp-closing-headline, .xp-closing-sub, .xp-closing-cta {\n      opacity: 1 !important;\n      transform: none !important;\n    }\n    .xp-manifesto-text .xp-word { opacity: 1 !important; }\n    .xp-canvas-wrap { display: none; }\n  }\n<\/style>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     HTML STRUCTURE\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div class=\"xp\">\n\n  <!-- Three.js canvas (fixed behind everything) -->\n  <div class=\"xp-canvas-wrap\">\n    <canvas id=\"xp-three\"><\/canvas>\n  <\/div>\n\n  <!-- Scrollable content -->\n  <div class=\"xp-scroll\">\n\n    <!-- SECTION 1: HERO -->\n    <section class=\"xp-hero\">\n      <div class=\"xp-tag\">3D Web Experiences<\/div>\n\n      <h1 class=\"xp-headline\">\n        <span class=\"xp-headline-line\"><span class=\"xp-headline-inner\">We Bring the<\/span><\/span>\n        <span class=\"xp-headline-line\"><span class=\"xp-headline-inner\">Third <em>Dimension<\/em><\/span><\/span>\n        <span class=\"xp-headline-line\"><span class=\"xp-headline-inner\">to the Web<\/span><\/span>\n      <\/h1>\n\n      <p class=\"xp-subtext\">\n        Balancing visual impact with performance. Creating moments of\n        wonder without sacrificing usability. Making 3D accessible to everyone.\n      <\/p>\n\n      <div class=\"xp-cta-row\">\n        <a href=\"#work\" class=\"xp-cta xp-cta--fill\">Explore Our Work<\/a>\n        <a href=\"#contact\" class=\"xp-cta xp-cta--ghost\">Start a Project<\/a>\n      <\/div>\n\n      <div class=\"xp-scroll-prompt\">\n        <span>Discover<\/span>\n        <div class=\"xp-scroll-dot\"><\/div>\n      <\/div>\n    <\/section>\n\n    <!-- SECTION 2: MANIFESTO (word-by-word reveal) -->\n    <section class=\"xp-manifesto\">\n      <div class=\"xp-manifesto-inner\">\n        <p class=\"xp-manifesto-text\" id=\"xp-manifesto-p\">\n          We know when 3D <em>enhances<\/em> and when it&#8217;s just showing off.\n          We balance visual impact with performance. We make 3D accessible\n          to users who&#8217;ve never touched a 3D app. We create moments of\n          <em>wonder<\/em> without sacrificing usability.\n        <\/p>\n      <\/div>\n    <\/section>\n\n    <!-- SECTION 3: CAPABILITIES -->\n    <section class=\"xp-capabilities\">\n      <div class=\"xp-cap-label\">What We Do<\/div>\n      <div class=\"xp-cap-grid\">\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">01<\/span>\n          <h3 class=\"xp-cap-title\">Three.js Implementation<\/h3>\n          <p class=\"xp-cap-desc\">Custom-built 3D scenes with full creative control. Vanilla or framework-integrated, always performance-first.<\/p>\n        <\/div>\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">02<\/span>\n          <h3 class=\"xp-cap-title\">WebGL Optimisation<\/h3>\n          <p class=\"xp-cap-desc\">Squeezing every frame out of the GPU. LOD strategies, texture compression, shader optimisation, draw call reduction.<\/p>\n        <\/div>\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">03<\/span>\n          <h3 class=\"xp-cap-title\">3D Model Integration<\/h3>\n          <p class=\"xp-cap-desc\">From Blender to browser. GLTF\/GLB pipeline with Draco compression, PBR materials, and web-ready optimisation.<\/p>\n        <\/div>\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">04<\/span>\n          <h3 class=\"xp-cap-title\">Spline Workflows<\/h3>\n          <p class=\"xp-cap-desc\">Rapid 3D prototyping with designer-friendly tools. Interactive embeds that your team can update without code.<\/p>\n        <\/div>\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">05<\/span>\n          <h3 class=\"xp-cap-title\">Product Configurators<\/h3>\n          <p class=\"xp-cap-desc\">Interactive 3D product viewers with real-time material, colour, and component swapping. E-commerce ready.<\/p>\n        <\/div>\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">06<\/span>\n          <h3 class=\"xp-cap-title\">Interactive 3D Scenes<\/h3>\n          <p class=\"xp-cap-desc\">Immersive experiences that respond to scroll, mouse, touch, and device orientation. Storytelling in three dimensions.<\/p>\n        <\/div>\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">07<\/span>\n          <h3 class=\"xp-cap-title\">Scroll-Driven 3D<\/h3>\n          <p class=\"xp-cap-desc\">Camera fly-throughs, model reveals, exploded views \u2014 all wired to scroll position with GSAP ScrollTrigger.<\/p>\n        <\/div>\n        <div class=\"xp-cap-card\">\n          <span class=\"xp-cap-num\">08<\/span>\n          <h3 class=\"xp-cap-title\">Performance Engineering<\/h3>\n          <p class=\"xp-cap-desc\">Lazy loading, mobile fallbacks, memory cleanup, pixel ratio capping. 3D that doesn&#8217;t destroy your PageSpeed score.<\/p>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <!-- SECTION 4: CLOSING -->\n    <section class=\"xp-closing\">\n      <h2 class=\"xp-closing-headline\">Ready to Add<br><em>Depth?<\/em><\/h2>\n      <p class=\"xp-closing-sub\">Let&#8217;s build something people remember.<\/p>\n      <div class=\"xp-closing-cta\">\n        <a href=\"#contact\" class=\"xp-cta xp-cta--fill\">Get in Touch<\/a>\n      <\/div>\n    <\/section>\n\n  <\/div>\n<\/div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     THREE.JS \u2014 ES MODULE FROM CDN\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<script type=\"importmap\">\n{\n  \"imports\": {\n    \"three\": \"https:\/\/cdn.jsdelivr.net\/npm\/three@0.164.1\/build\/three.module.js\",\n    \"three\/addons\/\": \"https:\/\/cdn.jsdelivr.net\/npm\/three@0.164.1\/examples\/jsm\/\"\n  }\n}\n<\/script>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { EffectComposer } from 'three\/addons\/postprocessing\/EffectComposer.js';\nimport { RenderPass } from 'three\/addons\/postprocessing\/RenderPass.js';\nimport { UnrealBloomPass } from 'three\/addons\/postprocessing\/UnrealBloomPass.js';\n\n\/\/ \u2500\u2500 SETUP \u2500\u2500\nconst canvas   = document.getElementById('xp-three');\nconst renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });\nrenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\nrenderer.setSize(window.innerWidth, window.innerHeight);\nrenderer.toneMapping = THREE.ACESFilmicToneMapping;\nrenderer.toneMappingExposure = 1.2;\n\nconst scene  = new THREE.Scene();\nscene.fog    = new THREE.FogExp2(0x050507, 0.06);\n\nconst camera = new THREE.PerspectiveCamera(50, window.innerWidth \/ window.innerHeight, 0.1, 200);\ncamera.position.set(0, 0, 8);\n\n\/\/ \u2500\u2500 POST-PROCESSING (BLOOM) \u2500\u2500\nconst composer   = new EffectComposer(renderer);\nconst renderPass = new RenderPass(scene, camera);\ncomposer.addPass(renderPass);\n\nconst bloomPass = new UnrealBloomPass(\n  new THREE.Vector2(window.innerWidth, window.innerHeight),\n  1.5,   \/\/ strength\n  0.4,   \/\/ radius\n  0.85   \/\/ threshold\n);\ncomposer.addPass(bloomPass);\n\n\/\/ \u2500\u2500 LIGHTS \u2500\u2500\nconst ambientLight = new THREE.AmbientLight(0x404050, 0.4);\nscene.add(ambientLight);\n\nconst pointLight1 = new THREE.PointLight(0xd4a046, 2, 30);\npointLight1.position.set(5, 5, 5);\nscene.add(pointLight1);\n\nconst pointLight2 = new THREE.PointLight(0xe8552e, 1.5, 30);\npointLight2.position.set(-5, -3, 3);\nscene.add(pointLight2);\n\nconst pointLight3 = new THREE.PointLight(0x4466ff, 0.8, 25);\npointLight3.position.set(0, 8, -5);\nscene.add(pointLight3);\n\n\/\/ \u2500\u2500 CENTRAL ICOSAHEDRON (wireframe + vertices) \u2500\u2500\nconst icoGeo    = new THREE.IcosahedronGeometry(2.2, 1);\nconst icoMat    = new THREE.MeshBasicMaterial({\n  color: 0xd4a046,\n  wireframe: true,\n  transparent: true,\n  opacity: 0.15,\n});\nconst ico = new THREE.Mesh(icoGeo, icoMat);\nscene.add(ico);\n\n\/\/ Glowing vertices on the icosahedron\nconst vertPositions = icoGeo.attributes.position;\nconst vertGeo       = new THREE.BufferGeometry();\nconst uniqueVerts   = [];\nconst seen          = new Set();\nfor (let i = 0; i < vertPositions.count; i++) {\n  const key = [\n    vertPositions.getX(i).toFixed(3),\n    vertPositions.getY(i).toFixed(3),\n    vertPositions.getZ(i).toFixed(3),\n  ].join(',');\n  if (!seen.has(key)) {\n    seen.add(key);\n    uniqueVerts.push(vertPositions.getX(i), vertPositions.getY(i), vertPositions.getZ(i));\n  }\n}\nvertGeo.setAttribute('position', new THREE.Float32BufferAttribute(uniqueVerts, 3));\nconst vertMat = new THREE.PointsMaterial({\n  color: 0xd4a046,\n  size: 0.08,\n  transparent: true,\n  opacity: 0.9,\n  sizeAttenuation: true,\n});\nconst vertPoints = new THREE.Points(vertGeo, vertMat);\nscene.add(vertPoints);\n\n\/\/ \u2500\u2500 OUTER RING PARTICLES \u2500\u2500\nfunction createRing(count, radius, color, speed, yOffset) {\n  const positions = new Float32Array(count * 3);\n  const sizes     = new Float32Array(count);\n  for (let i = 0; i < count; i++) {\n    const angle = (i \/ count) * Math.PI * 2 + (Math.random() - 0.5) * 0.3;\n    const r     = radius + (Math.random() - 0.5) * 0.8;\n    positions[i * 3]     = Math.cos(angle) * r;\n    positions[i * 3 + 1] = yOffset + (Math.random() - 0.5) * 0.5;\n    positions[i * 3 + 2] = Math.sin(angle) * r;\n    sizes[i] = Math.random() * 0.04 + 0.02;\n  }\n  const geo = new THREE.BufferGeometry();\n  geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));\n  geo.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));\n  const mat = new THREE.PointsMaterial({\n    color,\n    size: 0.05,\n    transparent: true,\n    opacity: 0.6,\n    sizeAttenuation: true,\n  });\n  const points = new THREE.Points(geo, mat);\n  points.userData.speed = speed;\n  return points;\n}\n\nconst ring1 = createRing(200, 4.0, 0xd4a046, 0.08, 0);\nconst ring2 = createRing(150, 5.5, 0xe8552e, -0.05, 0.5);\nconst ring3 = createRing(100, 3.2, 0x4466ff, 0.12, -0.3);\nscene.add(ring1, ring2, ring3);\n\n\/\/ \u2500\u2500 FLOATING FIELD PARTICLES (distant) \u2500\u2500\nconst fieldCount = 500;\nconst fieldPositions = new Float32Array(fieldCount * 3);\nfor (let i = 0; i < fieldCount; i++) {\n  fieldPositions[i * 3]     = (Math.random() - 0.5) * 40;\n  fieldPositions[i * 3 + 1] = (Math.random() - 0.5) * 30;\n  fieldPositions[i * 3 + 2] = (Math.random() - 0.5) * 40 - 5;\n}\nconst fieldGeo = new THREE.BufferGeometry();\nfieldGeo.setAttribute('position', new THREE.Float32BufferAttribute(fieldPositions, 3));\nconst fieldMat = new THREE.PointsMaterial({\n  color: 0xe8e4dc,\n  size: 0.025,\n  transparent: true,\n  opacity: 0.3,\n  sizeAttenuation: true,\n});\nconst fieldParticles = new THREE.Points(fieldGeo, fieldMat);\nscene.add(fieldParticles);\n\n\/\/ \u2500\u2500 CONNECTING LINES (dynamic) \u2500\u2500\nconst lineGeo = new THREE.BufferGeometry();\nconst maxLines = 60;\nconst linePositions = new Float32Array(maxLines * 6);\nlineGeo.setAttribute('position', new THREE.Float32BufferAttribute(linePositions, 3));\nconst lineMat = new THREE.LineBasicMaterial({\n  color: 0xd4a046,\n  transparent: true,\n  opacity: 0.08,\n});\nconst lines = new THREE.LineSegments(lineGeo, lineMat);\nscene.add(lines);\n\n\/\/ Update connecting lines between ring1 particles\nfunction updateLines() {\n  const pos = ring1.geometry.attributes.position;\n  let idx = 0;\n  for (let i = 0; i < Math.min(pos.count, 30); i++) {\n    for (let j = i + 1; j < Math.min(pos.count, 30); j++) {\n      if (idx >= maxLines) break;\n      const dx = pos.getX(i) - pos.getX(j);\n      const dy = pos.getY(i) - pos.getY(j);\n      const dz = pos.getZ(i) - pos.getZ(j);\n      const dist = Math.sqrt(dx*dx + dy*dy + dz*dz);\n      if (dist < 2.5) {\n        linePositions[idx * 6]     = pos.getX(i);\n        linePositions[idx * 6 + 1] = pos.getY(i);\n        linePositions[idx * 6 + 2] = pos.getZ(i);\n        linePositions[idx * 6 + 3] = pos.getX(j);\n        linePositions[idx * 6 + 4] = pos.getY(j);\n        linePositions[idx * 6 + 5] = pos.getZ(j);\n        idx++;\n      }\n    }\n    if (idx >= maxLines) break;\n  }\n  \/\/ Zero out unused\n  for (let i = idx * 6; i < maxLines * 6; i++) linePositions[i] = 0;\n  lineGeo.attributes.position.needsUpdate = true;\n  lineGeo.setDrawRange(0, idx * 2);\n}\n\n\/\/ \u2500\u2500 MOUSE TRACKING \u2500\u2500\nlet mouseX = 0, mouseY = 0;\nlet targetMouseX = 0, targetMouseY = 0;\ndocument.addEventListener('mousemove', (e) => {\n  targetMouseX = (e.clientX \/ window.innerWidth - 0.5) * 2;\n  targetMouseY = (e.clientY \/ window.innerHeight - 0.5) * 2;\n});\n\n\/\/ \u2500\u2500 SCROLL PROGRESS (for 3D scene) \u2500\u2500\nlet scrollProgress = 0;\nwindow.addEventListener('scroll', () => {\n  const docH = document.documentElement.scrollHeight - window.innerHeight;\n  scrollProgress = docH > 0 ? window.scrollY \/ docH : 0;\n});\n\n\/\/ \u2500\u2500 MOBILE DETECTION \u2500\u2500\nconst isMobile = window.innerWidth < 768 || \/Mobi|Android\/i.test(navigator.userAgent);\nif (isMobile) {\n  bloomPass.strength = 0.8;\n  fieldMat.opacity = 0.15;\n}\n\n\/\/ \u2500\u2500 ANIMATION LOOP \u2500\u2500\nconst clock = new THREE.Clock();\nlet frameId;\n\nfunction animate() {\n  frameId = requestAnimationFrame(animate);\n  const t = clock.getElapsedTime();\n\n  \/\/ Smooth mouse\n  mouseX += (targetMouseX - mouseX) * 0.03;\n  mouseY += (targetMouseY - mouseY) * 0.03;\n\n  \/\/ Camera reacts to mouse + scroll\n  camera.position.x = mouseX * 1.2;\n  camera.position.y = -mouseY * 0.8 + scrollProgress * -4;\n  camera.position.z = 8 - scrollProgress * 5;\n  camera.lookAt(0, scrollProgress * -2, 0);\n\n  \/\/ Icosahedron breathes + rotates\n  const breathe = 1 + Math.sin(t * 0.8) * 0.08;\n  ico.scale.set(breathe, breathe, breathe);\n  ico.rotation.x = t * 0.1 + mouseY * 0.2;\n  ico.rotation.y = t * 0.15 + mouseX * 0.2;\n  vertPoints.scale.copy(ico.scale);\n  vertPoints.rotation.copy(ico.rotation);\n\n  \/\/ Rings orbit\n  ring1.rotation.y = t * ring1.userData.speed;\n  ring2.rotation.y = t * ring2.userData.speed;\n  ring2.rotation.x = Math.sin(t * 0.3) * 0.2;\n  ring3.rotation.y = t * ring3.userData.speed;\n  ring3.rotation.z = Math.sin(t * 0.2) * 0.15;\n\n  \/\/ Field particles drift\n  fieldParticles.rotation.y = t * 0.01;\n  fieldParticles.rotation.x = t * 0.005;\n\n  \/\/ Lights orbit slowly\n  pointLight1.position.x = Math.sin(t * 0.5) * 6;\n  pointLight1.position.z = Math.cos(t * 0.5) * 6;\n  pointLight2.position.x = Math.sin(t * 0.3 + 2) * 5;\n  pointLight2.position.z = Math.cos(t * 0.3 + 2) * 5;\n\n  \/\/ Dynamic bloom based on scroll\n  bloomPass.strength = 1.5 - scrollProgress * 0.8;\n\n  \/\/ Update connecting lines\n  updateLines();\n\n  \/\/ Render with bloom\n  composer.render();\n}\nanimate();\n\n\/\/ \u2500\u2500 RESIZE \u2500\u2500\nwindow.addEventListener('resize', () => {\n  camera.aspect = window.innerWidth \/ window.innerHeight;\n  camera.updateProjectionMatrix();\n  renderer.setSize(window.innerWidth, window.innerHeight);\n  composer.setSize(window.innerWidth, window.innerHeight);\n});\n\n\/\/ \u2500\u2500 CLEANUP WHEN OFF SCREEN \u2500\u2500\nif ('IntersectionObserver' in window) {\n  const xpRoot = document.querySelector('.xp');\n  const obs = new IntersectionObserver((entries) => {\n    if (!entries[0].isIntersecting) {\n      cancelAnimationFrame(frameId);\n    } else {\n      animate();\n    }\n  }, { threshold: 0 });\n  if (xpRoot) obs.observe(xpRoot);\n}\n<\/script>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n     GSAP SCROLL ANIMATIONS\n     \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<script>\n(function() {\n  'use strict';\n  gsap.registerPlugin(ScrollTrigger);\n\n  \/\/ \u2500\u2500 HERO ENTRANCE \u2500\u2500\n  const heroTL = gsap.timeline({ delay: 0.3 });\n\n  heroTL\n    .to('.xp-tag', {\n      opacity: 1,\n      duration: 0.8,\n      ease: 'power3.out',\n    })\n    .to('.xp-headline-inner', {\n      y: 0,\n      opacity: 1,\n      duration: 1.2,\n      ease: 'power4.out',\n      stagger: 0.12,\n    }, '-=0.4')\n    .to('.xp-subtext', {\n      opacity: 1,\n      y: 0,\n      duration: 0.8,\n      ease: 'power3.out',\n    }, '-=0.6')\n    .to('.xp-cta-row', {\n      opacity: 1,\n      y: 0,\n      duration: 0.8,\n      ease: 'power3.out',\n    }, '-=0.5')\n    .to('.xp-scroll-prompt', {\n      opacity: 1,\n      duration: 1,\n      ease: 'power2.out',\n    }, '-=0.3');\n\n  \/\/ \u2500\u2500 MANIFESTO \u2014 WORD-BY-WORD REVEAL \u2500\u2500\n  const manifestoP = document.getElementById('xp-manifesto-p');\n  if (manifestoP) {\n    \/\/ Wrap each word in a span (preserve <em> tags)\n    const html = manifestoP.innerHTML;\n    \/\/ Split text nodes into words, preserve tags\n    const wrapped = html.replace(\/(<em>|<\\\/em>)\/g, '\u2206$1\u2206').split('\u2206').map(function(part) {\n      if (part.startsWith('<')) return part;\n      return part.split(\/\\s+\/).filter(Boolean).map(function(word) {\n        return '<span class=\"xp-word\">' + word + '<\/span>';\n      }).join(' ');\n    }).join('');\n    manifestoP.innerHTML = wrapped;\n\n    const words = manifestoP.querySelectorAll('.xp-word');\n    gsap.to(words, {\n      opacity: 1,\n      stagger: 0.04,\n      ease: 'none',\n      scrollTrigger: {\n        trigger: '.xp-manifesto',\n        start: 'top 60%',\n        end: 'bottom 40%',\n        scrub: 1,\n      },\n    });\n  }\n\n  \/\/ \u2500\u2500 CAPABILITIES \u2014 STAGGERED CARD REVEAL \u2500\u2500\n  gsap.to('.xp-cap-label', {\n    opacity: 1,\n    y: 0,\n    duration: 0.6,\n    scrollTrigger: {\n      trigger: '.xp-capabilities',\n      start: 'top 70%',\n    },\n  });\n\n  gsap.to('.xp-cap-card', {\n    opacity: 1,\n    y: 0,\n    duration: 0.7,\n    ease: 'power3.out',\n    stagger: 0.08,\n    scrollTrigger: {\n      trigger: '.xp-cap-grid',\n      start: 'top 75%',\n    },\n  });\n\n  \/\/ \u2500\u2500 CLOSING SECTION \u2500\u2500\n  var closingTL = gsap.timeline({\n    scrollTrigger: {\n      trigger: '.xp-closing',\n      start: 'top 60%',\n    },\n  });\n\n  closingTL\n    .to('.xp-closing-headline', { opacity: 1, y: 0, duration: 0.8, ease: 'power3.out' })\n    .to('.xp-closing-sub', { opacity: 1, y: 0, duration: 0.6, ease: 'power3.out' }, '-=0.4')\n    .to('.xp-closing-cta', { opacity: 1, y: 0, duration: 0.6, ease: 'power3.out' }, '-=0.3');\n\n})();\n<\/script>\n\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><\/p>\n<\/blockquote>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>3D Web Experiences We Bring the Third Dimension to the Web Balancing visual impact with performance. Creating moments of wonder without sacrificing usability. Making 3D accessible to everyone. Explore Our Work Start a Project Discover We know when 3D enhances and when it&#8217;s just showing off. We balance visual impact with performance. We make 3D&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"open","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"hide","_kad_post_layout":"fullwidth","_kad_post_sidebar_id":"","_kad_post_content_style":"unboxed","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-2","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/pages\/2","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/comments?post=2"}],"version-history":[{"count":5,"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/pages\/2\/revisions"}],"predecessor-version":[{"id":12,"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/pages\/2\/revisions\/12"}],"wp:attachment":[{"href":"https:\/\/muddev.co.za\/test-space\/wp-json\/wp\/v2\/media?parent=2"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}