pipey 0.2.1

A lightweight HTTP-to-WebSocket event delivery service.
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Pipey{% endblock %}</title>
    <meta name="description" content="Pipey delivers events published over HTTP to connected WebSocket clients in real time.">

    <!-- Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">

    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">

    <style>
        :root{
            /* --- Pipey design tokens: dispatch console theme --- */
            --bg:        #0b0c0e;
            --surface:   #15171b;
            --surface-2: #1c1e23;
            --border:    #262931;
            --border-2:  #34373f;

            --ink:       #f1efe6;
            --muted:     #9c9a90;
            --muted-2:   #6b6a62;

            --accent:      #ffab2e;   /* primary: amber signal */
            --accent-dark: #e6911a;
            --accent-soft: #ffab2e22;
            --accent-ink:  #1a1206;

            --live:      #57e6a3;   /* connected / delivered */
            --live-soft: #57e6a31c;
            --warn:      #ff8a4c;
            --warn-soft: #ff8a4c1c;
            --error:     #ff5f5f;
            --error-soft:#ff5f5f1c;

            --font-display: "Space Grotesk", system-ui, sans-serif;
            --font-body: "Inter", system-ui, -apple-system, sans-serif;
            --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Consolas, monospace;

            --radius: 14px;
            --radius-sm: 9px;

            --shadow-sm: 0 1px 2px rgba(0,0,0,.3), 0 1px 1px rgba(0,0,0,.2);
            --shadow-md: 0 4px 10px rgba(0,0,0,.35), 0 12px 28px -10px rgba(0,0,0,.5);
            --shadow-lg: 0 6px 16px rgba(0,0,0,.4), 0 24px 48px -16px rgba(0,0,0,.6);
        }

        *{ margin:0; padding:0; box-sizing:border-box; }
        html{ scroll-behavior:smooth; }

        body{
            font-family:var(--font-body);
            background:
                radial-gradient(680px 320px at 15% -8%, var(--accent-soft), transparent 65%),
                var(--bg);
            color:var(--ink);
            line-height:1.65;
            min-height:100vh;
        }

        h1,h2,h3{ font-family:var(--font-display); letter-spacing:-0.015em; }

        a{ color:var(--accent); text-decoration:none; }
        a:hover{ color:var(--accent-dark); }

        code, pre{ font-family:var(--font-mono); }

        img{ max-width:100%; display:block; }

        ::selection{ background:var(--accent-soft); color:var(--accent-dark); }

        a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible{
            outline:2px solid var(--accent);
            outline-offset:2px;
        }

        /* ---------- Sponsor banner ---------- */
        .sponsor-banner{
            position:relative;
            display:flex; align-items:center; justify-content:center;
            flex-wrap:wrap;
            gap:10px 12px;
            padding:9px 44px 9px 16px;
            background:var(--surface-2);
            border-bottom:1px solid var(--border);
            color:var(--ink);
            font-size:13px;
            text-align:center;
        }
        .sponsor-banner .banner-white-logo{
            height:20px;
            width:auto;
            padding-right: .9em;
            flex-shrink:0;
            opacity:.9;
        }
        .sponsor-banner .txt{ color:var(--muted); }
        .sponsor-banner .txt strong{ color:var(--ink); }
        .sponsor-banner a.sponsor-cta{
            display:inline-flex; align-items:center; gap:6px;
            background:var(--accent);
            color:var(--accent-ink);
            font-weight:700;
            font-size:12.5px;
            padding:5px 13px;
            border-radius:7px;
            white-space:nowrap;
            flex-shrink:0;
            transition:background .15s ease, transform .15s ease;
        }
        .sponsor-banner a.sponsor-cta:hover{ background:var(--accent-dark); color:var(--accent-ink); transform:translateY(-1px); }
        .sponsor-banner .close-banner{
            position:absolute; right:12px; top:50%; transform:translateY(-50%);
            width:26px; height:26px;
            display:flex; align-items:center; justify-content:center;
            background:transparent;
            border:none;
            color:var(--muted-2);
            cursor:pointer;
            border-radius:50%;
            font-size:13px;
            transition:background .15s ease, color .15s ease;
        }
        .sponsor-banner .close-banner:hover{ background:var(--border); color:var(--ink); }
        .sponsor-banner[hidden]{ display:none; }

        @media (max-width:640px){
            .sponsor-banner{ font-size:12px; padding:9px 40px 9px 12px; }
            .sponsor-banner .banner-white-logo{ display:none; }
        }

        /* ---------- Nav ---------- */
        nav{
            position:sticky; top:0; z-index:50;
            height:64px;
            display:flex;
            align-items:center;
            justify-content:space-between;
            gap:12px;
            padding:0 28px;
            background:rgba(11,12,14,0.85);
            backdrop-filter:blur(8px);
            border-bottom:1px solid var(--border);
        }

        .logo{
            display:flex;
            align-items:center;
            gap:9px;
            flex-shrink:0;
            transition:opacity .15s ease;
        }
        .logo:hover{ opacity:.8; }

        .logo .banner-black-logo{
            height:28px;
            width:auto;
        }

        .links{ display:flex; align-items:center; gap:4px; min-width:0; }

        .links a{
            display:flex; align-items:center; gap:7px;
            color:var(--muted);
            font-size:14px;
            font-weight:500;
            padding:8px 13px;
            border-radius:8px;
            white-space:nowrap;
            transition:background .15s ease, color .15s ease;
        }
        .links a:hover{ color:var(--ink); background:var(--surface-2); }
        .links a.active{ color:var(--accent); background:var(--accent-soft); }
        .links i{ width:14px; text-align:center; font-size:12.5px; }

        #github-link{ display:flex; align-items:center; gap:8px; }

        #stars{
            display:inline-flex; align-items:center; gap:5px;
            padding:2px 9px;
            background:var(--surface-2);
            border:1px solid var(--border);
            border-radius:999px;
            color:var(--ink);
            font-family:var(--font-mono);
            font-size:11.5px;
            font-weight:600;
        }
        #stars i{ color:var(--accent); font-size:10.5px; width:auto; }

        /* ---------- Layout ---------- */
        .container{
            max-width:920px;
            margin:0 auto;
            padding:48px 24px 24px;
        }

        .card{
            background:var(--surface);
            border:1px solid var(--border);
            border-radius:var(--radius);
            box-shadow:var(--shadow-sm);
            padding:28px;
            margin-bottom:18px;
            transition:box-shadow .2s ease, transform .2s ease, border-color .2s ease;
        }

        .eyebrow{
            display:inline-flex; align-items:center; gap:7px;
            font-family:var(--font-mono);
            font-size:12px;
            font-weight:500;
            color:var(--accent);
            letter-spacing:0.02em;
            margin-bottom:14px;
        }
        .eyebrow::before{
            content:"";
            width:6px; height:6px;
            border-radius:50%;
            background:var(--live);
            box-shadow:0 0 0 3px var(--live-soft);
            animation:pulse 2.4s ease-in-out infinite;
        }

        @keyframes pulse{
            0%, 100%{ box-shadow:0 0 0 3px var(--live-soft); }
            50%{ box-shadow:0 0 0 6px var(--live-soft); }
        }

        pre{
            background:#08090a;
            border:1px solid var(--border);
            border-radius:var(--radius-sm);
            padding:16px 18px;
            overflow-x:auto;
            font-size:13px;
            color:#e9e9f0;
            line-height:1.7;
        }
        pre code{ background:none; padding:0; color:inherit; }
        p code, li code{
            font-family:var(--font-mono);
            font-size:0.88em;
            background:var(--surface-2);
            border:1px solid var(--border);
            padding:1px 6px;
            border-radius:5px;
            color:var(--accent-dark);
        }

        footer{
            margin-top:32px;
            padding:30px 24px;
            text-align:center;
            color:var(--muted-2);
            font-size:13px;
            border-top:1px solid var(--border);
        }

        @media (prefers-reduced-motion: reduce){
            html{ scroll-behavior:auto; }
            .eyebrow::before{ animation:none; }
            *{ transition-duration:0.01ms !important; animation-duration:0.01ms !important; }
        }

        @media (max-width:640px){
            nav{ padding:0 16px; gap:6px; }
            .logo .banner-black-logo{ height:19px; }
            .links{ gap:2px; }
            .links a{ padding:8px 10px; }
            .links span#stars{ display:none; }
            .links a span.link-text{ display:none; }
            .container{ padding:32px 16px 16px; }
            .card{ padding:20px; border-radius:14px; }
        }

        @media (max-width:400px){
            #github-link .link-text{ display:none; }
        }

        {% block styles %}{% endblock %}
    </style>
</head>

<body>

<div id="sponsor-banner" class="sponsor-banner" hidden>
    <img src="https://img.ge/i/N1uqc72.png" class="banner-white-logo" alt="Pipey">
    <span class="txt">💛 Find Pipey useful? <strong>Sponsor</strong> to help cover hosting and keep it maintained.</span>
    <a href="/#sponsor" class="sponsor-cta"><i class="fa-solid fa-heart"></i> Sponsor</a>
    <button class="close-banner" id="close-banner" aria-label="Dismiss">
        <i class="fa-solid fa-xmark"></i>
    </button>
</div>

<nav>
    <a href="/" class="logo">
        <img src="https://img.ge/i/N1uqc72.png" class="banner-black-logo" alt="Pipey">
    </a>

    <div class="links">
        <a href="/"><i class="fa-solid fa-house"></i><span class="link-text">Home</span></a>
        <a href="/client"><i class="fa-solid fa-satellite-dish"></i><span class="link-text">Client</span></a>
        <a href="/sdks"><i class="fa-solid fa-code"></i><span class="link-text">SDKs</span></a>
        <a href="/docs"><i class="fa-solid fa-book"></i><span class="link-text">Docs</span></a>
        <a href="/#sponsor"><i class="fa-solid fa-heart"></i><span class="link-text">Sponsor</span></a>
        <a id="github-link" href="https://github.com/anomalous254/pipey" target="_blank" rel="noopener noreferrer">
            <i class="fa-brands fa-github"></i>
            <span class="link-text">GitHub</span>
            <span id="stars">
                <i class="fa-solid fa-star"></i>
                <span id="star-count">1</span>
            </span>
        </a>
    </div>
</nav>

<div class="container">
    {% block content %}{% endblock %}
</div>

<footer>
    Pipey. Lightweight HTTP to WebSocket event delivery.
</footer>

{% block scripts %}{% endblock %}

<script>
(async () => {
    try {
        // Public repo, so the unauthenticated GitHub API is enough (60 req/hr per IP).
        // Never call an authenticated endpoint with a token from browser JS.
        // Anyone can read it from view-source or devtools.
        const response = await fetch("https://api.github.com/repos/anomalous254/pipey");
        if (!response.ok) return;
        const repo = await response.json();
        document.getElementById("star-count").textContent = repo.stargazers_count;
    } catch (err) {
        console.error(err);
    }
})();

(() => {
    const banner = document.getElementById("sponsor-banner");
    const closeBtn = document.getElementById("close-banner");

    try {
        if (!localStorage.getItem("pipey_banner_dismissed")) {
            banner.hidden = false;
        }
    } catch (err) {
        banner.hidden = false;
    }

    closeBtn.addEventListener("click", () => {
        banner.hidden = true;
        try { localStorage.setItem("pipey_banner_dismissed", "1"); } catch (err) {}
    });
})();

(() => {
    // Highlight the current nav link
    const path = window.location.pathname.replace(/\/$/, "") || "/";
    document.querySelectorAll(".links a").forEach((a) => {
        const href = a.getAttribute("href");
        if (!href || href.startsWith("http") || href.includes("#")) return;
        const normalized = href.replace(/\/$/, "") || "/";
        if (normalized === path) a.classList.add("active");
    });
})();
</script>

</body>
</html>