[

one goal

]

creators, developers & designers working as one.

A connected network of teams collaborating across every layer of our projects & platforms.

A connected network of teams collaborating across every layer of our projects & platforms.

0M+

[

lines shipped

]

refusing to overlook any detail

0M+

[

lines shipped

]

refusing to overlook any detail

0k+

[

assets

]

Designed across brands, platforms, and systems.

0k+

[

assets

]

Designed across brands, platforms, and systems.

0

[

countries

]

Creating experiences around the world.

0

[

countries

]

Creating experiences around the world.

[

1

]

SICKK_SOFTWARE

SICKK SOFTWARE

Our dev team designs, builds, and deploys systems from the ground up. Every interface, every interaction, every layer — engineered for precision, performance, and scale.

POS, enterprise, UI/UX overhauls, brand rebuilds and more. Whatever the challenge, we can handle it.

database

cloud

server

API

auth

CLIENTS

analytics

Architecture built to move fast, scale cleanly, and stay reliable under real-world pressure.

From connected APIs and cloud infrastructure to live data systems and operational tooling, every component is engineered to work together seamlessly — visually, technically, and at scale.

SasS

dashboards

tournament software

asset libraries

POS

management platforms

marketplaces

[

2

]

Sickk GAMING

We always create high-performance gaming experiences — from competitive platforms and player systems, to branding, community, and digital infrastructure engineered to perform under pressure.

Built by players, for players, and the future of gaming.

Contact Image
Contact Image

ARENA coming 2027

#

#

score

score

level

level

player_name

player_name

1

1

381

381

INSANE

INSANE

anonymous

anonymous

2

2

277

277

INSANE

INSANE

anonymous

anonymous

3

3

254

254

HARD

HARD

anonymous

anonymous

< FUN GAME CODE - WHY NOT />

<div id="jump-game" style="background-color: black;">
    <div class="hud-bar">
        <div class="hud-left">
            <button type="button" class="play-pause-btn" id="jump-pause" aria-pressed="false" aria-label="Pause game">
                <span class="btn-icon icon-pause" aria-hidden="true">
                    <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
                        <rect x="2" y="2" width="3.5" height="10" rx="0.5" />
                        <rect x="8.5" y="2" width="3.5" height="10" rx="0.5" />
                    </svg>
                </span>
                <span class="btn-icon icon-play" aria-hidden="true">
                    <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
                        <path d="M3 2 L12 7 L3 12 Z" />
                    </svg>
                </span>
            </button>
            <button type="button" class="play-pause-btn trophy-btn" id="jump-trophy" aria-pressed="true"
                aria-label="Leaderboard prompts on">
                <span class="btn-icon icon-trophy-on" aria-hidden="true">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"
                        stroke-linecap="round" stroke-linejoin="round">
                        <path d="M8 21h8 M9 21V11h6v10 M7 21h10" />
                        <path d="M7 11H5a2 2 0 01-2-2V9h18v6a2 2 0 01-2 2h-2" />
                        <path d="M16 11V9a4 4 0 10-8 0v2" />
                        <path d="M9 6V5a3 3 0 016 0v1" />
                    </svg>
                </span>
                <span class="btn-icon icon-trophy-off" aria-hidden="true">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"
                        stroke-linecap="round" stroke-linejoin="round">
                        <path d="M8 21h8 M9 21V11h6v10 M7 21h10" />
                        <path d="M7 11H5a2 2 0 01-2-2V9h18v6a2 2 0 01-2 2h-2" />
                        <path d="M16 11V9a4 4 0 10-8 0v2" />
                        <path d="M9 6V5a3 3 0 016 0v1" />
                        <path d="M4 20 L20 4" stroke-width="2.1" stroke-linecap="square" />
                    </svg>
                </span>
            </button>
        </div>
        <span class="score-label" id="jump-score" aria-live="polite">0</span>
    </div>
    <div class="leaderboard-overlay" id="jump-leaderboard-overlay" aria-hidden="true">
        <div class="leaderboard-overlay-inner">
            <p class="leaderboard-title">New high score</p>
            <p class="leaderboard-score" id="jump-leaderboard-score">0</p>
            <button type="button" class="leaderboard-submit-btn" id="jump-leaderboard-submit">
                Submit to leaderboard
            </button>
        </div>
    </div>
    <div class="speed-controls" role="group" aria-label="Game speed">
        <button type="button" class="speed-btn active" data-speed="2.8">Easy</button>
        <button type="button" class="speed-btn" data-speed="3.8">Hard</button>
        <button type="button" class="speed-btn" data-speed="5">Insane</button>
    </div>
    <div class="scene-dim" aria-hidden="true"></div>
    <div class="ground"></div>
    <div class="player"></div>
</div>

<style>
    @import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;600&display=swap");

    html,
    body {
        margin: 0;
        height: 100%;
        overflow: hidden;
        overscroll-behavior: none;
        touch-action: none;
    }

    #jump-game {
        position: relative;
        width: 100%;
        height: 140px;
        overflow: hidden;
        background: transparent;
        user-select: none;
        touch-action: none;
    }

    #jump-game .hud-bar {
        position: absolute;
        top: 8px;
        left: 10px;
        right: 10px;
        z-index: 3;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }

    #jump-game .hud-left {
        display: flex;
        align-items: center;
        gap: 8px;
    }

    #jump-game .scene-dim {
        position: absolute;
        inset: 0;
        z-index: 2;
        background: rgba(0, 0, 0, 0.52);
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.22s ease;
    }

    #jump-game .leaderboard-overlay {
        position: absolute;
        inset: 0;
        z-index: 6;
        display: none;
        align-items: center;
        justify-content: center;
        background: rgba(0, 0, 0, 0.62);
        pointer-events: auto;
        padding: 12px;
    }

    #jump-game .leaderboard-overlay.is-visible {
        display: flex;
    }

    #jump-game .leaderboard-overlay-inner {
        width: min(340px, 100%);
        height: fit-content;
        text-align: center;
        padding: 20px;
        border: 1px solid rgba(255, 255, 255, 0.38);
        border-radius: 8px;
        background: rgba(20, 20, 22, 0.92);
    }

    #jump-game .leaderboard-title {
        margin: 0 0 6px;
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 12px;
        font-weight: 600;
        letter-spacing: 0.12em;
        text-transform: uppercase;
        color: rgba(255, 255, 255, 0.92);
    }

    #jump-game .leaderboard-score {
        margin: 0 0 5px;
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 28px;
        font-weight: 600;
        color: #f5d785;
        text-shadow: 0 0 16px rgba(245, 215, 133, 0.45);
    }

    #jump-game .leaderboard-submit-btn {
        box-sizing: border-box;
        width: 100%;
        margin: 0;
        padding: 6px 10px;
        border: 1px solid rgba(255, 255, 255, 0.4);
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.1);
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 12px;
        font-weight: 600;
        letter-spacing: 0.06em;
        color: rgba(255, 255, 255, 0.95);
        cursor: pointer;
        touch-action: manipulation;
    }

    #jump-game .leaderboard-submit-btn:hover {
        background: rgba(255, 255, 255, 0.16);
        border-color: rgba(255, 255, 255, 0.55);
    }

    #jump-game .leaderboard-submit-btn:focus-visible {
        outline: 2px solid rgba(255, 255, 255, 0.55);
        outline-offset: 3px;
    }

    #jump-game .speed-controls {
        position: absolute;
        top: 8px;
        left: 50%;
        transform: translateX(-50%);
        z-index: 3;
        display: inline-flex;
        align-items: center;
        gap: 8px;
    }

    #jump-game .speed-btn {
        box-sizing: border-box;
        margin: 0;
        padding: 0 10px;
        height: 32px;
        min-width: 62px;
        border: 1px solid rgba(255, 255, 255, 0.35);
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.08);
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 11px;
        font-weight: 600;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: rgba(255, 255, 255, 0.92);
        cursor: pointer;
        touch-action: manipulation;
    }

    #jump-game .speed-btn:hover {
        background: rgba(255, 255, 255, 0.14);
        border-color: rgba(255, 255, 255, 0.5);
    }

    #jump-game .speed-btn.active {
        background: rgba(255, 255, 255, 0.22);
        border-color: rgba(255, 255, 255, 0.72);
    }

    #jump-game .speed-btn:focus-visible {
        outline: 2px solid rgba(255, 255, 255, 0.55);
        outline-offset: 2px;
    }

    #jump-game.is-paused .scene-dim {
        opacity: 1;
    }

    #jump-game .score-label {
        display: inline-block;
        transform-origin: center center;
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 13px;
        font-weight: 600;
        letter-spacing: 0.04em;
        color: rgba(255, 255, 255, 0.92);
        text-shadow: 0 0 14px rgba(255, 255, 255, 0.25);
        pointer-events: none;
    }

    #jump-game .score-label.score-gold {
        color: #f5d785;
        text-shadow:
            0 0 10px rgba(245, 215, 133, 0.55),
            0 0 22px rgba(212, 168, 75, 0.35);
    }

    #jump-game .score-label.score-gold-win {
        animation: scoreGoldSpring 0.5s cubic-bezier(0.34, 1.4, 0.64, 1) both;
    }

    @keyframes scoreGoldSpring {
        0% {
            transform: scale(1);
        }

        30% {
            transform: scale(1.42);
        }

        58% {
            transform: scale(0.93);
        }

        82% {
            transform: scale(1.08);
        }

        100% {
            transform: scale(1);
        }
    }

    #jump-game .play-pause-btn {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        width: 32px;
        height: 32px;
        flex-shrink: 0;
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        border: 1px solid rgba(255, 255, 255, 0.35);
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.08);
        color: rgba(255, 255, 255, 0.92);
        cursor: pointer;
        touch-action: manipulation;
    }

    #jump-game .play-pause-btn .btn-icon {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        pointer-events: none;
    }

    #jump-game #jump-pause .icon-play {
        display: none;
    }

    #jump-game.is-paused #jump-pause .icon-pause {
        display: none;
    }

    #jump-game.is-paused #jump-pause .icon-play {
        display: flex;
    }

    #jump-game .trophy-btn .icon-trophy-off {
        display: none;
    }

    #jump-game.leaderboard-prompt-off .trophy-btn .icon-trophy-on {
        display: none;
    }

    #jump-game.leaderboard-prompt-off .trophy-btn .icon-trophy-off {
        display: flex;
    }

    #jump-game .play-pause-btn:hover {
        background: rgba(255, 255, 255, 0.14);
        border-color: rgba(255, 255, 255, 0.5);
    }

    #jump-game .play-pause-btn:focus-visible {
        outline: 2px solid rgba(255, 255, 255, 0.55);
        outline-offset: 2px;
    }

    #jump-game .ground {
        position: absolute;
        z-index: 1;
        left: 0;
        right: 0;
        bottom: 0;
        height: 2px;

        background: linear-gradient(90deg,
                rgba(255, 255, 255, 0.05),
                rgba(255, 255, 255, 0.55),
                rgba(255, 255, 255, 0.05));

        box-shadow:
            0 0 12px rgba(255, 255, 255, 0.18),
            0 0 30px rgba(255, 255, 255, 0.08);
    }

    #jump-game .player {
        position: absolute;
        z-index: 1;
        left: 70px;
        bottom: 2px;
        width: 22px;
        height: 22px;
        background: white;
        /* box-shadow: 0 0 18px rgba(255, 255, 255, 0.45); */
        transform: translateY(0);
    }

    #jump-game .player.invincible {
        animation: blinkLife 0.18s linear 8;
    }

    @keyframes blinkLife {

        0%,
        100% {
            opacity: 1;
        }

        50% {
            opacity: 0.2;
        }
    }

    #jump-game .triangle {
        position: absolute;
        z-index: 1;
        bottom: 2px;
        width: 0;
        height: 0;
        border-left: 11px solid transparent;
        border-right: 11px solid transparent;
        border-bottom: 22px solid #ff3faf;
        /* filter: drop-shadow(0 0 10px rgba(255, 63, 175, 0.6)); */
    }

    #jump-game .piece {
        position: absolute;
        z-index: 1;
        width: 9px;
        height: 9px;
        background: white;
        border-radius: 2px;
        pointer-events: none;
    }

    #jump-game .bg-shape {
        position: absolute;
        z-index: 0;
        pointer-events: none;
        opacity: 0.25;
        color: #fff;
        filter: blur(0.2px);
    }

    #jump-game .bg-tree {
        width: 0;
        height: 0;
        border-left: 10px solid transparent;
        border-right: 10px solid transparent;
        border-bottom: 24px solid rgba(255, 255, 255, 1);
    }

    #jump-game .bg-bush {
        width: 30px;
        height: 15px;
        border-bottom-left-radius: 0px;
        border-bottom-right-radius: 0px;
        border-top-left-radius: 15px;
        border-top-right-radius: 15px;
        background: rgba(255, 255, 255, 1);
    }
</style>

<script>
    (() => {
        const game = document.getElementById("jump-game")

        let player = game.querySelector(".player")
        let y = 0
        let velocity = 0
        let jumping = false
        let obstacles = []
        let backgroundShapes = []
        let pieces = []
        let alive = true
        let reloading = false
        let spawning = true
        let score = 0
        let paused = false
        let jumpHeld = false
        let jumpHoldTime = 0
        let burstRemaining = 0
        let postBurstRecovery = false
        let nextSpawnGap = 130

        const leaderboardUiEnabled = false
        let leaderboardPromptsEnabled = leaderboardUiEnabled
        let goldBounceUsedThisRun = false
        let persistedBest = null

        const scoreLabel = document.getElementById("jump-score")
        const pauseBtn = document.getElementById("jump-pause")
        const trophyBtn = document.getElementById("jump-trophy")
        const leaderboardOverlay = document.getElementById("jump-leaderboard-overlay")
        const leaderboardScoreEl = document.getElementById("jump-leaderboard-score")
        const leaderboardSubmitBtn = document.getElementById("jump-leaderboard-submit")
        const speedButtons = Array.from(game.querySelectorAll(".speed-btn"))

        const highScoreStorageKey = "fun-scroller-high-score"

        const gravity = 0.42
        const jumpForce = -6.7
        const maxJumpHeight = -70
        let speed = 2.8
        const maxJumpHoldTime = 1.8
        const jumpHoldLift = 0.36
        const clusterGapMin = 24
        const clusterGapMax = 34
        const normalGapMin = 110
        const normalGapMax = 170
        const minLandingGap = 155
        const frameSeconds = 1 / 60

        function loadPersistedBest() {
            const raw = localStorage.getItem(highScoreStorageKey)
            if (raw === null) {
                persistedBest = null
                return
            }
            const parsed = Number.parseInt(raw, 10)
            persistedBest = Number.isFinite(parsed) ? parsed : null
        }

        function savePersistedBest(value) {
            persistedBest = value
            localStorage.setItem(highScoreStorageKey, String(value))
        }

        function leaderboardOpen() {
            if (!leaderboardUiEnabled) return false
            return !!(leaderboardOverlay && leaderboardOverlay.classList.contains("is-visible"))
        }

        function hideLeaderboardOverlay() {
            if (!leaderboardUiEnabled) return
            if (!leaderboardOverlay) return
            leaderboardOverlay.classList.remove("is-visible")
            leaderboardOverlay.setAttribute("aria-hidden", "true")
        }

        function showLeaderboardOverlay(finalScore) {
            if (!leaderboardUiEnabled) return
            if (leaderboardScoreEl) leaderboardScoreEl.textContent = String(finalScore)
            if (!leaderboardOverlay) return
            leaderboardOverlay.classList.add("is-visible")
            leaderboardOverlay.setAttribute("aria-hidden", "false")
        }

        function updateScoreLabel() {
            if (!scoreLabel) return
            scoreLabel.textContent = String(score)

            const hasStored = persistedBest !== null

            if (!hasStored) {
                scoreLabel.classList.add("score-gold")
                return
            }

            if (score > persistedBest) {
                scoreLabel.classList.add("score-gold")
                if (!goldBounceUsedThisRun) {
                    goldBounceUsedThisRun = true
                    scoreLabel.classList.remove("score-gold-win")
                    void scoreLabel.offsetWidth
                    scoreLabel.classList.add("score-gold-win")
                    scoreLabel.addEventListener(
                        "animationend",
                        () => scoreLabel.classList.remove("score-gold-win"),
                        { once: true }
                    )
                }
            } else {
                scoreLabel.classList.remove("score-gold")
                scoreLabel.classList.remove("score-gold-win")
                goldBounceUsedThisRun = false
            }
        }

        function toggleLeaderboardPromptPreference() {
            if (!leaderboardUiEnabled) return
            leaderboardPromptsEnabled = !leaderboardPromptsEnabled
            game.classList.toggle("leaderboard-prompt-off", !leaderboardPromptsEnabled)
            if (trophyBtn) {
                trophyBtn.setAttribute("aria-pressed", String(leaderboardPromptsEnabled))
                trophyBtn.setAttribute(
                    "aria-label",
                    leaderboardPromptsEnabled
                        ? "Leaderboard prompts on"
                        : "Leaderboard prompts off"
                )
            }
        }

        function spawnBackgroundShape(force = false) {
            if (!force) {
                const last = backgroundShapes[backgroundShapes.length - 1]
                const lastX = last ? parseFloat(last.style.left) : 0
                if (last && lastX > game.offsetWidth - 100) return
            }

            const shape = document.createElement("div")
            const isTree = Math.random() < 0.55
            shape.className = `bg-shape ${isTree ? "bg-tree" : "bg-bush"}`
            shape.style.left = game.offsetWidth + Math.random() * 120 + "px"
            shape.style.bottom = (isTree ? 6 : 4) + Math.random() * 10 + "px"
            shape.style.opacity = (0.25 + Math.random() * 0.4).toFixed(2)

            game.appendChild(shape)
            backgroundShapes.push(shape)
        }

        function seedInitialBackgroundShapes() {
            const count = 6
            const minSpacing = 30
            let cursorX = -16

            for (let i = 0; i < count; i++) {
                const shape = document.createElement("div")
                const isTree = Math.random() < 0.55
                shape.className = `bg-shape ${isTree ? "bg-tree" : "bg-bush"}`

                const shapeWidth = isTree ? 20 : 22
                const randomGap = minSpacing + Math.random() * 38
                cursorX += randomGap

                shape.style.left = cursorX + "px"
                shape.style.bottom = (isTree ? 6 : 4) + Math.random() * 10 + "px"
                shape.style.opacity = (0.25 + Math.random() * 0.4).toFixed(2)

                game.appendChild(shape)
                backgroundShapes.push(shape)

                cursorX += shapeWidth
            }
        }


        function jump() {
            if (paused || leaderboardOpen()) return

            if (!alive || reloading) return

            if (!jumping) {
                jumping = true
                velocity = jumpForce
                jumpHoldTime = 0
            }
            jumpHeld = true
        }

        function releaseJump() {
            jumpHeld = false
        }

        function spawnTriangle() {
            if (!spawning || !alive) return

            const last = obstacles[obstacles.length - 1]
            const lastX = last ? parseFloat(last.style.left) : 999

            if (last && lastX > game.offsetWidth - nextSpawnGap) return

            const triangle = document.createElement("div")
            triangle.className = "triangle"
            triangle.style.left = game.offsetWidth + Math.random() * 120 + "px"

            game.appendChild(triangle)
            obstacles.push(triangle)

            if (burstRemaining > 0) {
                burstRemaining--
                if (burstRemaining > 0) {
                    nextSpawnGap =
                        clusterGapMin +
                        Math.random() * (clusterGapMax - clusterGapMin)
                } else {
                    postBurstRecovery = true
                    nextSpawnGap = minLandingGap + Math.random() * 35
                }
                return
            }

            if (postBurstRecovery) {
                postBurstRecovery = false
                nextSpawnGap =
                    normalGapMin + Math.random() * (normalGapMax - normalGapMin)
                return
            }

            const burstRoll = Math.random()
            if (burstRoll < 0.16) {
                // Triple stack (3 total): this + 2 rapid followers.
                burstRemaining = 2
                nextSpawnGap =
                    clusterGapMin +
                    Math.random() * (clusterGapMax - clusterGapMin)
            } else if (burstRoll < 0.42) {
                // Double stack (2 total): this + 1 rapid follower.
                burstRemaining = 1
                nextSpawnGap =
                    clusterGapMin +
                    Math.random() * (clusterGapMax - clusterGapMin)
            } else {
                nextSpawnGap =
                    normalGapMin + Math.random() * (normalGapMax - normalGapMin)
            }
        }

        function rectsHit(a, b) {
            return (
                a.left < b.right &&
                a.right > b.left &&
                a.top < b.bottom &&
                a.bottom > b.top
            )
        }

        /** True if p is inside or on the edge of triangle ABC (CCW or CW). */
        function pointInTriangle(px, py, ax, ay, bx, by, cx, cy) {
            const eps = 1e-6
            const c1 = (bx - ax) * (py - ay) - (by - ay) * (px - ax)
            const c2 = (cx - bx) * (py - by) - (cy - by) * (px - bx)
            const c3 = (ax - cx) * (py - cy) - (ay - cy) * (px - cx)
            const hasNeg = c1 < -eps || c2 < -eps || c3 < -eps
            const hasPos = c1 > eps || c2 > eps || c3 > eps
            return !(hasNeg && hasPos)
        }

        function pointInRect(px, py, left, top, right, bottom, pad) {
            return (
                px >= left - pad &&
                px <= right + pad &&
                py >= top - pad &&
                py <= bottom + pad
            )
        }

        function segmentIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
            const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
            if (Math.abs(denom) < 1e-12) return false
            const t =
                ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
            const u =
                -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
            const eps = 1e-9
            return t >= -eps && t <= 1 + eps && u >= -eps && u <= 1 + eps
        }

        /**
         * Player was only tested at its center, so corners could graze the slope with no hit.
         * Full AABB vs triangle: corners in tri, verts in rect, or any edge crossing.
         */
        function rectHitsTriangle(left, top, right, bottom, ax, ay, bx, by, cx, cy) {
            const pad = 2
            const l = left - pad
            const t = top - pad
            const r = right + pad
            const b = bottom + pad

            const rc = [
                [l, t],
                [r, t],
                [r, b],
                [l, b],
            ]
            for (let i = 0; i < 4; i++) {
                const [x, y] = rc[i]
                if (pointInTriangle(x, y, ax, ay, bx, by, cx, cy)) return true
            }

            if (pointInRect(ax, ay, l, t, r, b, 0)) return true
            if (pointInRect(bx, by, l, t, r, b, 0)) return true
            if (pointInRect(cx, cy, l, t, r, b, 0)) return true

            const triE = [
                [ax, ay, bx, by],
                [bx, by, cx, cy],
                [cx, cy, ax, ay],
            ]
            const rectE = [
                [l, t, r, t],
                [r, t, r, b],
                [r, b, l, b],
                [l, b, l, t],
            ]
            for (let i = 0; i < 4; i++) {
                const [x1, y1, x2, y2] = rectE[i]
                for (let j = 0; j < 3; j++) {
                    const [x3, y3, x4, y4] = triE[j]
                    if (segmentIntersect(x1, y1, x2, y2, x3, y3, x4, y4))
                        return true
                }
            }
            return false
        }

        function checkCollisions() {
            if (!alive || reloading) return

            const pr = player.getBoundingClientRect()

            obstacles.forEach(triangle => {
                const triRect = triangle.getBoundingClientRect()

                const ax = triRect.left + triRect.width / 2
                const ay = triRect.top
                const bx = triRect.left
                const by = triRect.bottom
                const cx = triRect.right
                const cy = triRect.bottom

                if (
                    rectHitsTriangle(
                        pr.left,
                        pr.top,
                        pr.right,
                        pr.bottom,
                        ax,
                        ay,
                        bx,
                        by,
                        cx,
                        cy
                    )
                ) {
                    breakPlayer()
                }
            })
        }

        function breakPlayer() {
            const finalScore = score

            alive = false
            spawning = false
            jumping = false
            jumpHeld = false
            jumpHoldTime = 0

            const beatsRecord =
                finalScore > 0 &&
                (persistedBest === null || finalScore > persistedBest)

            if (beatsRecord) {
                savePersistedBest(finalScore)
            }

            score = 0
            updateScoreLabel()

            if (leaderboardPromptsEnabled && beatsRecord) {
                showLeaderboardOverlay(finalScore)
            }

            const playerRect = player.getBoundingClientRect()
            const gameRect = game.getBoundingClientRect()

            const baseX = playerRect.left - gameRect.left
            const baseY = playerRect.top - gameRect.top

            player.remove()

            for (let i = 0; i < 6; i++) {
                const piece = document.createElement("div")
                piece.className = "piece"

                piece.style.left = baseX + 6 + "px"
                piece.style.top = baseY + 6 + "px"

                piece.vx = (Math.random() - 0.5) * 7
                piece.vy = -Math.random() * 6 - 2
                piece.life = 45

                game.appendChild(piece)
                pieces.push(piece)
            }

            setTimeout(resetGame, 900)
        }

        function resetGame() {
            reloading = true
            goldBounceUsedThisRun = false

            obstacles.forEach(triangle => triangle.remove())
            obstacles = []

            pieces.forEach(piece => piece.remove())
            pieces = []

            y = 0
            velocity = 0
            jumping = false
            jumpHeld = false
            jumpHoldTime = 0
            burstRemaining = 0
            postBurstRecovery = false
            nextSpawnGap =
                normalGapMin + Math.random() * (normalGapMax - normalGapMin)

            player = document.createElement("div")
            player.className = "player invincible"
            game.appendChild(player)

            setTimeout(() => {
                player.classList.remove("invincible")
                alive = true
                reloading = false
                spawning = true
                updateScoreLabel()
            }, 1500)
        }

        function updatePlayer() {
            if (!alive || reloading) return

            if (jumping) {
                if (jumpHeld && jumpHoldTime < maxJumpHoldTime) {
                    velocity -= jumpHoldLift
                    jumpHoldTime += frameSeconds
                }

                y += velocity
                velocity += gravity

                if (y < maxJumpHeight) {
                    y = maxJumpHeight
                    velocity *= -0.08
                }

                if (y >= 0) {
                    y = 0
                    velocity = 0
                    jumping = false
                    jumpHoldTime = 0
                }

                player.style.transform = `translateY(${y}px)`
            }
        }

        function updatePieces() {
            pieces.forEach((piece, index) => {
                piece.vy += 0.35

                const x = parseFloat(piece.style.left) + piece.vx
                const y = parseFloat(piece.style.top) + piece.vy

                piece.style.left = x + "px"
                piece.style.top = y + "px"
                piece.style.opacity = piece.life / 45

                piece.life--

                if (piece.life <= 0) {
                    piece.remove()
                    pieces.splice(index, 1)
                }
            })
        }

        function updateObstacles() {
            obstacles.forEach((triangle, index) => {
                let x = parseFloat(triangle.style.left)

                if (alive && !reloading) {
                    x -= speed
                    triangle.style.left = x + "px"
                }

                if (x < -40) {
                    if (alive && !reloading) {
                        score += 1
                        updateScoreLabel()
                    }
                    triangle.remove()
                    obstacles.splice(index, 1)
                }
            })

            if (Math.random() < 0.012) {
                spawnTriangle()
            }
        }

        function updateBackgroundShapes() {
            const backgroundSpeed = speed * 0.22

            backgroundShapes.forEach((shape, index) => {
                let x = parseFloat(shape.style.left)
                if (alive && !reloading) {
                    x -= backgroundSpeed
                    shape.style.left = x + "px"
                }

                if (x < -40) {
                    shape.remove()
                    backgroundShapes.splice(index, 1)
                }
            })

            if (alive && !reloading && Math.random() < 0.02) {
                spawnBackgroundShape()
            }
        }

        function loop() {
            if (!paused) {
                updatePlayer()
                updateBackgroundShapes()
                updateObstacles()
                updatePieces()
                checkCollisions()
            }

            requestAnimationFrame(loop)
        }

        function togglePause() {
            paused = !paused
            game.classList.toggle("is-paused", paused)
            if (pauseBtn) {
                pauseBtn.setAttribute("aria-pressed", String(paused))
                pauseBtn.setAttribute(
                    "aria-label",
                    paused ? "Resume game" : "Pause game"
                )
            }
        }

        function setGameSpeed(nextSpeed, sourceButton) {
            speed = nextSpeed
            speedButtons.forEach(btn => {
                const active = btn === sourceButton
                btn.classList.toggle("active", active)
                btn.setAttribute("aria-pressed", String(active))
            })
        }

        const scrollKeys = new Set([
            "Space",
            "ArrowUp",
            "ArrowDown",
            "PageUp",
            "PageDown",
            "Home",
            "End",
        ])

        window.addEventListener(
            "keydown",
            e => {
                if (e.code === "Space") {
                    e.preventDefault()
                    if (!e.repeat) jump()
                } else if (scrollKeys.has(e.code)) {
                    e.preventDefault()
                }
            },
            { passive: false }
        )

        window.addEventListener(
            "keyup",
            e => {
                if (e.code === "Space") {
                    e.preventDefault()
                    releaseJump()
                } else if (scrollKeys.has(e.code)) {
                    e.preventDefault()
                }
            },
            { passive: false }
        )

        const blockScroll = e => e.preventDefault()
        window.addEventListener("wheel", blockScroll, { passive: false })
        window.addEventListener("touchmove", blockScroll, { passive: false })

        game.addEventListener("mousedown", jump)
        game.addEventListener("touchstart", jump, { passive: true })
        window.addEventListener("mouseup", releaseJump)
        window.addEventListener("touchend", releaseJump, { passive: true })
        window.addEventListener("touchcancel", releaseJump, { passive: true })

        if (pauseBtn) {
            pauseBtn.addEventListener("click", e => {
                e.stopPropagation()
                togglePause()
            })
            pauseBtn.addEventListener("mousedown", e => e.stopPropagation())
            pauseBtn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        }

        if (trophyBtn && leaderboardUiEnabled) {
            trophyBtn.addEventListener("click", e => {
                e.stopPropagation()
                toggleLeaderboardPromptPreference()
            })
            trophyBtn.addEventListener("mousedown", e => e.stopPropagation())
            trophyBtn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        }

        if (leaderboardSubmitBtn && leaderboardUiEnabled) {
            leaderboardSubmitBtn.addEventListener("click", e => {
                e.stopPropagation()
                hideLeaderboardOverlay()
            })
            leaderboardSubmitBtn.addEventListener("mousedown", e => e.stopPropagation())
            leaderboardSubmitBtn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        }

        speedButtons.forEach(btn => {
            btn.addEventListener("click", e => {
                e.stopPropagation()
                const nextSpeed = Number(btn.dataset.speed)
                if (!Number.isNaN(nextSpeed)) setGameSpeed(nextSpeed, btn)
            })
            btn.addEventListener("mousedown", e => e.stopPropagation())
            btn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        })

        loadPersistedBest()
        updateScoreLabel()

        if (!leaderboardUiEnabled) {
            leaderboardPromptsEnabled = false
            game.classList.add("leaderboard-prompt-off")
            if (trophyBtn) trophyBtn.style.display = "none"
            if (leaderboardOverlay) leaderboardOverlay.style.display = "none"
        }

        seedInitialBackgroundShapes()
        setTimeout(spawnTriangle, 600)
        loop()
    })()
</script>
<div id="jump-game" style="background-color: black;">
    <div class="hud-bar">
        <div class="hud-left">
            <button type="button" class="play-pause-btn" id="jump-pause" aria-pressed="false" aria-label="Pause game">
                <span class="btn-icon icon-pause" aria-hidden="true">
                    <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
                        <rect x="2" y="2" width="3.5" height="10" rx="0.5" />
                        <rect x="8.5" y="2" width="3.5" height="10" rx="0.5" />
                    </svg>
                </span>
                <span class="btn-icon icon-play" aria-hidden="true">
                    <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
                        <path d="M3 2 L12 7 L3 12 Z" />
                    </svg>
                </span>
            </button>
            <button type="button" class="play-pause-btn trophy-btn" id="jump-trophy" aria-pressed="true"
                aria-label="Leaderboard prompts on">
                <span class="btn-icon icon-trophy-on" aria-hidden="true">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"
                        stroke-linecap="round" stroke-linejoin="round">
                        <path d="M8 21h8 M9 21V11h6v10 M7 21h10" />
                        <path d="M7 11H5a2 2 0 01-2-2V9h18v6a2 2 0 01-2 2h-2" />
                        <path d="M16 11V9a4 4 0 10-8 0v2" />
                        <path d="M9 6V5a3 3 0 016 0v1" />
                    </svg>
                </span>
                <span class="btn-icon icon-trophy-off" aria-hidden="true">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"
                        stroke-linecap="round" stroke-linejoin="round">
                        <path d="M8 21h8 M9 21V11h6v10 M7 21h10" />
                        <path d="M7 11H5a2 2 0 01-2-2V9h18v6a2 2 0 01-2 2h-2" />
                        <path d="M16 11V9a4 4 0 10-8 0v2" />
                        <path d="M9 6V5a3 3 0 016 0v1" />
                        <path d="M4 20 L20 4" stroke-width="2.1" stroke-linecap="square" />
                    </svg>
                </span>
            </button>
        </div>
        <span class="score-label" id="jump-score" aria-live="polite">0</span>
    </div>
    <div class="leaderboard-overlay" id="jump-leaderboard-overlay" aria-hidden="true">
        <div class="leaderboard-overlay-inner">
            <p class="leaderboard-title">New high score</p>
            <p class="leaderboard-score" id="jump-leaderboard-score">0</p>
            <button type="button" class="leaderboard-submit-btn" id="jump-leaderboard-submit">
                Submit to leaderboard
            </button>
        </div>
    </div>
    <div class="speed-controls" role="group" aria-label="Game speed">
        <button type="button" class="speed-btn active" data-speed="2.8">Easy</button>
        <button type="button" class="speed-btn" data-speed="3.8">Hard</button>
        <button type="button" class="speed-btn" data-speed="5">Insane</button>
    </div>
    <div class="scene-dim" aria-hidden="true"></div>
    <div class="ground"></div>
    <div class="player"></div>
</div>

<style>
    @import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;600&display=swap");

    html,
    body {
        margin: 0;
        height: 100%;
        overflow: hidden;
        overscroll-behavior: none;
        touch-action: none;
    }

    #jump-game {
        position: relative;
        width: 100%;
        height: 140px;
        overflow: hidden;
        background: transparent;
        user-select: none;
        touch-action: none;
    }

    #jump-game .hud-bar {
        position: absolute;
        top: 8px;
        left: 10px;
        right: 10px;
        z-index: 3;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }

    #jump-game .hud-left {
        display: flex;
        align-items: center;
        gap: 8px;
    }

    #jump-game .scene-dim {
        position: absolute;
        inset: 0;
        z-index: 2;
        background: rgba(0, 0, 0, 0.52);
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.22s ease;
    }

    #jump-game .leaderboard-overlay {
        position: absolute;
        inset: 0;
        z-index: 6;
        display: none;
        align-items: center;
        justify-content: center;
        background: rgba(0, 0, 0, 0.62);
        pointer-events: auto;
        padding: 12px;
    }

    #jump-game .leaderboard-overlay.is-visible {
        display: flex;
    }

    #jump-game .leaderboard-overlay-inner {
        width: min(340px, 100%);
        height: fit-content;
        text-align: center;
        padding: 20px;
        border: 1px solid rgba(255, 255, 255, 0.38);
        border-radius: 8px;
        background: rgba(20, 20, 22, 0.92);
    }

    #jump-game .leaderboard-title {
        margin: 0 0 6px;
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 12px;
        font-weight: 600;
        letter-spacing: 0.12em;
        text-transform: uppercase;
        color: rgba(255, 255, 255, 0.92);
    }

    #jump-game .leaderboard-score {
        margin: 0 0 5px;
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 28px;
        font-weight: 600;
        color: #f5d785;
        text-shadow: 0 0 16px rgba(245, 215, 133, 0.45);
    }

    #jump-game .leaderboard-submit-btn {
        box-sizing: border-box;
        width: 100%;
        margin: 0;
        padding: 6px 10px;
        border: 1px solid rgba(255, 255, 255, 0.4);
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.1);
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 12px;
        font-weight: 600;
        letter-spacing: 0.06em;
        color: rgba(255, 255, 255, 0.95);
        cursor: pointer;
        touch-action: manipulation;
    }

    #jump-game .leaderboard-submit-btn:hover {
        background: rgba(255, 255, 255, 0.16);
        border-color: rgba(255, 255, 255, 0.55);
    }

    #jump-game .leaderboard-submit-btn:focus-visible {
        outline: 2px solid rgba(255, 255, 255, 0.55);
        outline-offset: 3px;
    }

    #jump-game .speed-controls {
        position: absolute;
        top: 8px;
        left: 50%;
        transform: translateX(-50%);
        z-index: 3;
        display: inline-flex;
        align-items: center;
        gap: 8px;
    }

    #jump-game .speed-btn {
        box-sizing: border-box;
        margin: 0;
        padding: 0 10px;
        height: 32px;
        min-width: 62px;
        border: 1px solid rgba(255, 255, 255, 0.35);
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.08);
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 11px;
        font-weight: 600;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: rgba(255, 255, 255, 0.92);
        cursor: pointer;
        touch-action: manipulation;
    }

    #jump-game .speed-btn:hover {
        background: rgba(255, 255, 255, 0.14);
        border-color: rgba(255, 255, 255, 0.5);
    }

    #jump-game .speed-btn.active {
        background: rgba(255, 255, 255, 0.22);
        border-color: rgba(255, 255, 255, 0.72);
    }

    #jump-game .speed-btn:focus-visible {
        outline: 2px solid rgba(255, 255, 255, 0.55);
        outline-offset: 2px;
    }

    #jump-game.is-paused .scene-dim {
        opacity: 1;
    }

    #jump-game .score-label {
        display: inline-block;
        transform-origin: center center;
        font-family: "Geist Mono", ui-monospace, monospace;
        font-size: 13px;
        font-weight: 600;
        letter-spacing: 0.04em;
        color: rgba(255, 255, 255, 0.92);
        text-shadow: 0 0 14px rgba(255, 255, 255, 0.25);
        pointer-events: none;
    }

    #jump-game .score-label.score-gold {
        color: #f5d785;
        text-shadow:
            0 0 10px rgba(245, 215, 133, 0.55),
            0 0 22px rgba(212, 168, 75, 0.35);
    }

    #jump-game .score-label.score-gold-win {
        animation: scoreGoldSpring 0.5s cubic-bezier(0.34, 1.4, 0.64, 1) both;
    }

    @keyframes scoreGoldSpring {
        0% {
            transform: scale(1);
        }

        30% {
            transform: scale(1.42);
        }

        58% {
            transform: scale(0.93);
        }

        82% {
            transform: scale(1.08);
        }

        100% {
            transform: scale(1);
        }
    }

    #jump-game .play-pause-btn {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        width: 32px;
        height: 32px;
        flex-shrink: 0;
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        border: 1px solid rgba(255, 255, 255, 0.35);
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.08);
        color: rgba(255, 255, 255, 0.92);
        cursor: pointer;
        touch-action: manipulation;
    }

    #jump-game .play-pause-btn .btn-icon {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        pointer-events: none;
    }

    #jump-game #jump-pause .icon-play {
        display: none;
    }

    #jump-game.is-paused #jump-pause .icon-pause {
        display: none;
    }

    #jump-game.is-paused #jump-pause .icon-play {
        display: flex;
    }

    #jump-game .trophy-btn .icon-trophy-off {
        display: none;
    }

    #jump-game.leaderboard-prompt-off .trophy-btn .icon-trophy-on {
        display: none;
    }

    #jump-game.leaderboard-prompt-off .trophy-btn .icon-trophy-off {
        display: flex;
    }

    #jump-game .play-pause-btn:hover {
        background: rgba(255, 255, 255, 0.14);
        border-color: rgba(255, 255, 255, 0.5);
    }

    #jump-game .play-pause-btn:focus-visible {
        outline: 2px solid rgba(255, 255, 255, 0.55);
        outline-offset: 2px;
    }

    #jump-game .ground {
        position: absolute;
        z-index: 1;
        left: 0;
        right: 0;
        bottom: 0;
        height: 2px;

        background: linear-gradient(90deg,
                rgba(255, 255, 255, 0.05),
                rgba(255, 255, 255, 0.55),
                rgba(255, 255, 255, 0.05));

        box-shadow:
            0 0 12px rgba(255, 255, 255, 0.18),
            0 0 30px rgba(255, 255, 255, 0.08);
    }

    #jump-game .player {
        position: absolute;
        z-index: 1;
        left: 70px;
        bottom: 2px;
        width: 22px;
        height: 22px;
        background: white;
        /* box-shadow: 0 0 18px rgba(255, 255, 255, 0.45); */
        transform: translateY(0);
    }

    #jump-game .player.invincible {
        animation: blinkLife 0.18s linear 8;
    }

    @keyframes blinkLife {

        0%,
        100% {
            opacity: 1;
        }

        50% {
            opacity: 0.2;
        }
    }

    #jump-game .triangle {
        position: absolute;
        z-index: 1;
        bottom: 2px;
        width: 0;
        height: 0;
        border-left: 11px solid transparent;
        border-right: 11px solid transparent;
        border-bottom: 22px solid #ff3faf;
        /* filter: drop-shadow(0 0 10px rgba(255, 63, 175, 0.6)); */
    }

    #jump-game .piece {
        position: absolute;
        z-index: 1;
        width: 9px;
        height: 9px;
        background: white;
        border-radius: 2px;
        pointer-events: none;
    }

    #jump-game .bg-shape {
        position: absolute;
        z-index: 0;
        pointer-events: none;
        opacity: 0.25;
        color: #fff;
        filter: blur(0.2px);
    }

    #jump-game .bg-tree {
        width: 0;
        height: 0;
        border-left: 10px solid transparent;
        border-right: 10px solid transparent;
        border-bottom: 24px solid rgba(255, 255, 255, 1);
    }

    #jump-game .bg-bush {
        width: 30px;
        height: 15px;
        border-bottom-left-radius: 0px;
        border-bottom-right-radius: 0px;
        border-top-left-radius: 15px;
        border-top-right-radius: 15px;
        background: rgba(255, 255, 255, 1);
    }
</style>

<script>
    (() => {
        const game = document.getElementById("jump-game")

        let player = game.querySelector(".player")
        let y = 0
        let velocity = 0
        let jumping = false
        let obstacles = []
        let backgroundShapes = []
        let pieces = []
        let alive = true
        let reloading = false
        let spawning = true
        let score = 0
        let paused = false
        let jumpHeld = false
        let jumpHoldTime = 0
        let burstRemaining = 0
        let postBurstRecovery = false
        let nextSpawnGap = 130

        const leaderboardUiEnabled = false
        let leaderboardPromptsEnabled = leaderboardUiEnabled
        let goldBounceUsedThisRun = false
        let persistedBest = null

        const scoreLabel = document.getElementById("jump-score")
        const pauseBtn = document.getElementById("jump-pause")
        const trophyBtn = document.getElementById("jump-trophy")
        const leaderboardOverlay = document.getElementById("jump-leaderboard-overlay")
        const leaderboardScoreEl = document.getElementById("jump-leaderboard-score")
        const leaderboardSubmitBtn = document.getElementById("jump-leaderboard-submit")
        const speedButtons = Array.from(game.querySelectorAll(".speed-btn"))

        const highScoreStorageKey = "fun-scroller-high-score"

        const gravity = 0.42
        const jumpForce = -6.7
        const maxJumpHeight = -70
        let speed = 2.8
        const maxJumpHoldTime = 1.8
        const jumpHoldLift = 0.36
        const clusterGapMin = 24
        const clusterGapMax = 34
        const normalGapMin = 110
        const normalGapMax = 170
        const minLandingGap = 155
        const frameSeconds = 1 / 60

        function loadPersistedBest() {
            const raw = localStorage.getItem(highScoreStorageKey)
            if (raw === null) {
                persistedBest = null
                return
            }
            const parsed = Number.parseInt(raw, 10)
            persistedBest = Number.isFinite(parsed) ? parsed : null
        }

        function savePersistedBest(value) {
            persistedBest = value
            localStorage.setItem(highScoreStorageKey, String(value))
        }

        function leaderboardOpen() {
            if (!leaderboardUiEnabled) return false
            return !!(leaderboardOverlay && leaderboardOverlay.classList.contains("is-visible"))
        }

        function hideLeaderboardOverlay() {
            if (!leaderboardUiEnabled) return
            if (!leaderboardOverlay) return
            leaderboardOverlay.classList.remove("is-visible")
            leaderboardOverlay.setAttribute("aria-hidden", "true")
        }

        function showLeaderboardOverlay(finalScore) {
            if (!leaderboardUiEnabled) return
            if (leaderboardScoreEl) leaderboardScoreEl.textContent = String(finalScore)
            if (!leaderboardOverlay) return
            leaderboardOverlay.classList.add("is-visible")
            leaderboardOverlay.setAttribute("aria-hidden", "false")
        }

        function updateScoreLabel() {
            if (!scoreLabel) return
            scoreLabel.textContent = String(score)

            const hasStored = persistedBest !== null

            if (!hasStored) {
                scoreLabel.classList.add("score-gold")
                return
            }

            if (score > persistedBest) {
                scoreLabel.classList.add("score-gold")
                if (!goldBounceUsedThisRun) {
                    goldBounceUsedThisRun = true
                    scoreLabel.classList.remove("score-gold-win")
                    void scoreLabel.offsetWidth
                    scoreLabel.classList.add("score-gold-win")
                    scoreLabel.addEventListener(
                        "animationend",
                        () => scoreLabel.classList.remove("score-gold-win"),
                        { once: true }
                    )
                }
            } else {
                scoreLabel.classList.remove("score-gold")
                scoreLabel.classList.remove("score-gold-win")
                goldBounceUsedThisRun = false
            }
        }

        function toggleLeaderboardPromptPreference() {
            if (!leaderboardUiEnabled) return
            leaderboardPromptsEnabled = !leaderboardPromptsEnabled
            game.classList.toggle("leaderboard-prompt-off", !leaderboardPromptsEnabled)
            if (trophyBtn) {
                trophyBtn.setAttribute("aria-pressed", String(leaderboardPromptsEnabled))
                trophyBtn.setAttribute(
                    "aria-label",
                    leaderboardPromptsEnabled
                        ? "Leaderboard prompts on"
                        : "Leaderboard prompts off"
                )
            }
        }

        function spawnBackgroundShape(force = false) {
            if (!force) {
                const last = backgroundShapes[backgroundShapes.length - 1]
                const lastX = last ? parseFloat(last.style.left) : 0
                if (last && lastX > game.offsetWidth - 100) return
            }

            const shape = document.createElement("div")
            const isTree = Math.random() < 0.55
            shape.className = `bg-shape ${isTree ? "bg-tree" : "bg-bush"}`
            shape.style.left = game.offsetWidth + Math.random() * 120 + "px"
            shape.style.bottom = (isTree ? 6 : 4) + Math.random() * 10 + "px"
            shape.style.opacity = (0.25 + Math.random() * 0.4).toFixed(2)

            game.appendChild(shape)
            backgroundShapes.push(shape)
        }

        function seedInitialBackgroundShapes() {
            const count = 6
            const minSpacing = 30
            let cursorX = -16

            for (let i = 0; i < count; i++) {
                const shape = document.createElement("div")
                const isTree = Math.random() < 0.55
                shape.className = `bg-shape ${isTree ? "bg-tree" : "bg-bush"}`

                const shapeWidth = isTree ? 20 : 22
                const randomGap = minSpacing + Math.random() * 38
                cursorX += randomGap

                shape.style.left = cursorX + "px"
                shape.style.bottom = (isTree ? 6 : 4) + Math.random() * 10 + "px"
                shape.style.opacity = (0.25 + Math.random() * 0.4).toFixed(2)

                game.appendChild(shape)
                backgroundShapes.push(shape)

                cursorX += shapeWidth
            }
        }


        function jump() {
            if (paused || leaderboardOpen()) return

            if (!alive || reloading) return

            if (!jumping) {
                jumping = true
                velocity = jumpForce
                jumpHoldTime = 0
            }
            jumpHeld = true
        }

        function releaseJump() {
            jumpHeld = false
        }

        function spawnTriangle() {
            if (!spawning || !alive) return

            const last = obstacles[obstacles.length - 1]
            const lastX = last ? parseFloat(last.style.left) : 999

            if (last && lastX > game.offsetWidth - nextSpawnGap) return

            const triangle = document.createElement("div")
            triangle.className = "triangle"
            triangle.style.left = game.offsetWidth + Math.random() * 120 + "px"

            game.appendChild(triangle)
            obstacles.push(triangle)

            if (burstRemaining > 0) {
                burstRemaining--
                if (burstRemaining > 0) {
                    nextSpawnGap =
                        clusterGapMin +
                        Math.random() * (clusterGapMax - clusterGapMin)
                } else {
                    postBurstRecovery = true
                    nextSpawnGap = minLandingGap + Math.random() * 35
                }
                return
            }

            if (postBurstRecovery) {
                postBurstRecovery = false
                nextSpawnGap =
                    normalGapMin + Math.random() * (normalGapMax - normalGapMin)
                return
            }

            const burstRoll = Math.random()
            if (burstRoll < 0.16) {
                // Triple stack (3 total): this + 2 rapid followers.
                burstRemaining = 2
                nextSpawnGap =
                    clusterGapMin +
                    Math.random() * (clusterGapMax - clusterGapMin)
            } else if (burstRoll < 0.42) {
                // Double stack (2 total): this + 1 rapid follower.
                burstRemaining = 1
                nextSpawnGap =
                    clusterGapMin +
                    Math.random() * (clusterGapMax - clusterGapMin)
            } else {
                nextSpawnGap =
                    normalGapMin + Math.random() * (normalGapMax - normalGapMin)
            }
        }

        function rectsHit(a, b) {
            return (
                a.left < b.right &&
                a.right > b.left &&
                a.top < b.bottom &&
                a.bottom > b.top
            )
        }

        /** True if p is inside or on the edge of triangle ABC (CCW or CW). */
        function pointInTriangle(px, py, ax, ay, bx, by, cx, cy) {
            const eps = 1e-6
            const c1 = (bx - ax) * (py - ay) - (by - ay) * (px - ax)
            const c2 = (cx - bx) * (py - by) - (cy - by) * (px - bx)
            const c3 = (ax - cx) * (py - cy) - (ay - cy) * (px - cx)
            const hasNeg = c1 < -eps || c2 < -eps || c3 < -eps
            const hasPos = c1 > eps || c2 > eps || c3 > eps
            return !(hasNeg && hasPos)
        }

        function pointInRect(px, py, left, top, right, bottom, pad) {
            return (
                px >= left - pad &&
                px <= right + pad &&
                py >= top - pad &&
                py <= bottom + pad
            )
        }

        function segmentIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
            const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
            if (Math.abs(denom) < 1e-12) return false
            const t =
                ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
            const u =
                -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
            const eps = 1e-9
            return t >= -eps && t <= 1 + eps && u >= -eps && u <= 1 + eps
        }

        /**
         * Player was only tested at its center, so corners could graze the slope with no hit.
         * Full AABB vs triangle: corners in tri, verts in rect, or any edge crossing.
         */
        function rectHitsTriangle(left, top, right, bottom, ax, ay, bx, by, cx, cy) {
            const pad = 2
            const l = left - pad
            const t = top - pad
            const r = right + pad
            const b = bottom + pad

            const rc = [
                [l, t],
                [r, t],
                [r, b],
                [l, b],
            ]
            for (let i = 0; i < 4; i++) {
                const [x, y] = rc[i]
                if (pointInTriangle(x, y, ax, ay, bx, by, cx, cy)) return true
            }

            if (pointInRect(ax, ay, l, t, r, b, 0)) return true
            if (pointInRect(bx, by, l, t, r, b, 0)) return true
            if (pointInRect(cx, cy, l, t, r, b, 0)) return true

            const triE = [
                [ax, ay, bx, by],
                [bx, by, cx, cy],
                [cx, cy, ax, ay],
            ]
            const rectE = [
                [l, t, r, t],
                [r, t, r, b],
                [r, b, l, b],
                [l, b, l, t],
            ]
            for (let i = 0; i < 4; i++) {
                const [x1, y1, x2, y2] = rectE[i]
                for (let j = 0; j < 3; j++) {
                    const [x3, y3, x4, y4] = triE[j]
                    if (segmentIntersect(x1, y1, x2, y2, x3, y3, x4, y4))
                        return true
                }
            }
            return false
        }

        function checkCollisions() {
            if (!alive || reloading) return

            const pr = player.getBoundingClientRect()

            obstacles.forEach(triangle => {
                const triRect = triangle.getBoundingClientRect()

                const ax = triRect.left + triRect.width / 2
                const ay = triRect.top
                const bx = triRect.left
                const by = triRect.bottom
                const cx = triRect.right
                const cy = triRect.bottom

                if (
                    rectHitsTriangle(
                        pr.left,
                        pr.top,
                        pr.right,
                        pr.bottom,
                        ax,
                        ay,
                        bx,
                        by,
                        cx,
                        cy
                    )
                ) {
                    breakPlayer()
                }
            })
        }

        function breakPlayer() {
            const finalScore = score

            alive = false
            spawning = false
            jumping = false
            jumpHeld = false
            jumpHoldTime = 0

            const beatsRecord =
                finalScore > 0 &&
                (persistedBest === null || finalScore > persistedBest)

            if (beatsRecord) {
                savePersistedBest(finalScore)
            }

            score = 0
            updateScoreLabel()

            if (leaderboardPromptsEnabled && beatsRecord) {
                showLeaderboardOverlay(finalScore)
            }

            const playerRect = player.getBoundingClientRect()
            const gameRect = game.getBoundingClientRect()

            const baseX = playerRect.left - gameRect.left
            const baseY = playerRect.top - gameRect.top

            player.remove()

            for (let i = 0; i < 6; i++) {
                const piece = document.createElement("div")
                piece.className = "piece"

                piece.style.left = baseX + 6 + "px"
                piece.style.top = baseY + 6 + "px"

                piece.vx = (Math.random() - 0.5) * 7
                piece.vy = -Math.random() * 6 - 2
                piece.life = 45

                game.appendChild(piece)
                pieces.push(piece)
            }

            setTimeout(resetGame, 900)
        }

        function resetGame() {
            reloading = true
            goldBounceUsedThisRun = false

            obstacles.forEach(triangle => triangle.remove())
            obstacles = []

            pieces.forEach(piece => piece.remove())
            pieces = []

            y = 0
            velocity = 0
            jumping = false
            jumpHeld = false
            jumpHoldTime = 0
            burstRemaining = 0
            postBurstRecovery = false
            nextSpawnGap =
                normalGapMin + Math.random() * (normalGapMax - normalGapMin)

            player = document.createElement("div")
            player.className = "player invincible"
            game.appendChild(player)

            setTimeout(() => {
                player.classList.remove("invincible")
                alive = true
                reloading = false
                spawning = true
                updateScoreLabel()
            }, 1500)
        }

        function updatePlayer() {
            if (!alive || reloading) return

            if (jumping) {
                if (jumpHeld && jumpHoldTime < maxJumpHoldTime) {
                    velocity -= jumpHoldLift
                    jumpHoldTime += frameSeconds
                }

                y += velocity
                velocity += gravity

                if (y < maxJumpHeight) {
                    y = maxJumpHeight
                    velocity *= -0.08
                }

                if (y >= 0) {
                    y = 0
                    velocity = 0
                    jumping = false
                    jumpHoldTime = 0
                }

                player.style.transform = `translateY(${y}px)`
            }
        }

        function updatePieces() {
            pieces.forEach((piece, index) => {
                piece.vy += 0.35

                const x = parseFloat(piece.style.left) + piece.vx
                const y = parseFloat(piece.style.top) + piece.vy

                piece.style.left = x + "px"
                piece.style.top = y + "px"
                piece.style.opacity = piece.life / 45

                piece.life--

                if (piece.life <= 0) {
                    piece.remove()
                    pieces.splice(index, 1)
                }
            })
        }

        function updateObstacles() {
            obstacles.forEach((triangle, index) => {
                let x = parseFloat(triangle.style.left)

                if (alive && !reloading) {
                    x -= speed
                    triangle.style.left = x + "px"
                }

                if (x < -40) {
                    if (alive && !reloading) {
                        score += 1
                        updateScoreLabel()
                    }
                    triangle.remove()
                    obstacles.splice(index, 1)
                }
            })

            if (Math.random() < 0.012) {
                spawnTriangle()
            }
        }

        function updateBackgroundShapes() {
            const backgroundSpeed = speed * 0.22

            backgroundShapes.forEach((shape, index) => {
                let x = parseFloat(shape.style.left)
                if (alive && !reloading) {
                    x -= backgroundSpeed
                    shape.style.left = x + "px"
                }

                if (x < -40) {
                    shape.remove()
                    backgroundShapes.splice(index, 1)
                }
            })

            if (alive && !reloading && Math.random() < 0.02) {
                spawnBackgroundShape()
            }
        }

        function loop() {
            if (!paused) {
                updatePlayer()
                updateBackgroundShapes()
                updateObstacles()
                updatePieces()
                checkCollisions()
            }

            requestAnimationFrame(loop)
        }

        function togglePause() {
            paused = !paused
            game.classList.toggle("is-paused", paused)
            if (pauseBtn) {
                pauseBtn.setAttribute("aria-pressed", String(paused))
                pauseBtn.setAttribute(
                    "aria-label",
                    paused ? "Resume game" : "Pause game"
                )
            }
        }

        function setGameSpeed(nextSpeed, sourceButton) {
            speed = nextSpeed
            speedButtons.forEach(btn => {
                const active = btn === sourceButton
                btn.classList.toggle("active", active)
                btn.setAttribute("aria-pressed", String(active))
            })
        }

        const scrollKeys = new Set([
            "Space",
            "ArrowUp",
            "ArrowDown",
            "PageUp",
            "PageDown",
            "Home",
            "End",
        ])

        window.addEventListener(
            "keydown",
            e => {
                if (e.code === "Space") {
                    e.preventDefault()
                    if (!e.repeat) jump()
                } else if (scrollKeys.has(e.code)) {
                    e.preventDefault()
                }
            },
            { passive: false }
        )

        window.addEventListener(
            "keyup",
            e => {
                if (e.code === "Space") {
                    e.preventDefault()
                    releaseJump()
                } else if (scrollKeys.has(e.code)) {
                    e.preventDefault()
                }
            },
            { passive: false }
        )

        const blockScroll = e => e.preventDefault()
        window.addEventListener("wheel", blockScroll, { passive: false })
        window.addEventListener("touchmove", blockScroll, { passive: false })

        game.addEventListener("mousedown", jump)
        game.addEventListener("touchstart", jump, { passive: true })
        window.addEventListener("mouseup", releaseJump)
        window.addEventListener("touchend", releaseJump, { passive: true })
        window.addEventListener("touchcancel", releaseJump, { passive: true })

        if (pauseBtn) {
            pauseBtn.addEventListener("click", e => {
                e.stopPropagation()
                togglePause()
            })
            pauseBtn.addEventListener("mousedown", e => e.stopPropagation())
            pauseBtn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        }

        if (trophyBtn && leaderboardUiEnabled) {
            trophyBtn.addEventListener("click", e => {
                e.stopPropagation()
                toggleLeaderboardPromptPreference()
            })
            trophyBtn.addEventListener("mousedown", e => e.stopPropagation())
            trophyBtn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        }

        if (leaderboardSubmitBtn && leaderboardUiEnabled) {
            leaderboardSubmitBtn.addEventListener("click", e => {
                e.stopPropagation()
                hideLeaderboardOverlay()
            })
            leaderboardSubmitBtn.addEventListener("mousedown", e => e.stopPropagation())
            leaderboardSubmitBtn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        }

        speedButtons.forEach(btn => {
            btn.addEventListener("click", e => {
                e.stopPropagation()
                const nextSpeed = Number(btn.dataset.speed)
                if (!Number.isNaN(nextSpeed)) setGameSpeed(nextSpeed, btn)
            })
            btn.addEventListener("mousedown", e => e.stopPropagation())
            btn.addEventListener(
                "touchstart",
                e => e.stopPropagation(),
                { passive: true }
            )
        })

        loadPersistedBest()
        updateScoreLabel()

        if (!leaderboardUiEnabled) {
            leaderboardPromptsEnabled = false
            game.classList.add("leaderboard-prompt-off")
            if (trophyBtn) trophyBtn.style.display = "none"
            if (leaderboardOverlay) leaderboardOverlay.style.display = "none"
        }

        seedInitialBackgroundShapes()
        setTimeout(spawnTriangle, 600)
        loop()
    })()
</script>

Our gaming team develops competitive multiplayer experiences, immersive worlds, and modern gameplay systems built to AAA-inspired standards.

With experience across tournament platforms, open-world systems, side scrollers, and online multiplayer infrastructure, we focus on polished design, responsive gameplay, and high-quality player experiences.

scrollers

open world

multiplayer networks

AI behavior

character models

custom HUDs

physics engines

tournament infrastructure

physics engines

[

3

]

UI / UX

UI / UX

Our UI / UX team designs refined digital experiences built around interaction, identity, and visual precision.

Through modern interface systems, immersive design languages, motion, branding, and scalable component ecosystems, we create premium experiences engineered to feel intuitive & memorable.

Contact Image
Contact Image

Every detail is intentional — from typography, spacing, and animation behavior to full design systems built for consistency across products, platforms, and brands. The result is a cohesive experience that not only looks premium, but performs like it.

interface systems

UX architecture

branding

design languages

motion

prototypes

custom interactions

theme mapping

[

engineered with

]

All product names, logos, and brands are property of their respective owners.

[

engineered with

]

All product names, logos, and brands are property of their respective owners.

[

engineered with

]

All product names, logos, and brands are property of their respective owners.