Privacy

    KuberNap is cookieless, collects no personally identifiable information, runs on EU-hosted PostHog, and respects your opt-out preferences immediately. Below is an exhaustive list of every event we capture, generated from the same source of truth our code uses at runtime - if it isn't on this page, we don't send it.

    What we collect

    44 custom events. The PostHog $pageview event is also captured for traffic counts (path + referrer only - no IP, no fingerprint).

    EventPropertiesDescription
    calculator_provider_changedproviderFires when the user picks one of the four cloud-provider pills in the savings calculator.
    calculator_slider_changedslider_name, valueFires after the user finishes dragging a calculator slider (debounced 500ms so we don't spam mid-drag).
    calculator_result_viewedsavings_amount, provider, commitment_pctFires (throttled 1s) when the calculator recomputes and shows a savings figure.
    paste_scan_command_copied-Fires when the user copies the kubectl command snippet from the Paste & Scan section.
    paste_scan_submittedinput_length, detected_provider, namespace_count, total_savingsFires when the user submits pasted kubectl JSON for analysis. Only derived metrics - never the raw JSON content.
    paste_scan_table_sortedsort_directionFires when the user toggles the sort direction on the Paste & Scan results table.
    paste_scan_result_viewednamespace_count, total_savingsFires once after a successful Paste & Scan parse, when the result table is shown to the user.
    install_command_copiedcommand, locationFires when the user copies the helm install command snippet from any TerminalBlock surface (hero, how-it-works, final CTA, or terminal landing page). This is the primary install-funnel conversion event.
    internal_link_clickedfrom_section, to_sectionFires when the user clicks an in-page anchor link that scrolls between sections of the landing page.
    external_link_clickeddestination, link_locationFires when the user clicks a link that leaves kubernap.dev (GitHub, docs, social).
    footer_link_clickedlinkFires when the user clicks any link inside the site footer.
    footer_contact_clickedchannelFires when a user clicks a contact pill in the footer (Email Milan or LinkedIn).
    cta_clickedcta_label, section, page_variantFires when the user clicks a primary call-to-action button anywhere on the site.
    pricing_tier_viewedtier, surfaceFires once per session per pricing tier the first time the corresponding card scrolls into view. Used together with cta_clicked + fake_door_click to compute view->click conversion per tier.
    theme_togglednew_themeFires when the user switches the site between light and dark themes.
    sticky_header_revealedscroll_depthFires once per session the first time the sticky header appears as the user scrolls down.
    faq_expandedquestion_id, indexFires when the user expands an FAQ accordion item.
    easter_egg_triggeredegg_typeFires when the user discovers an easter egg (Konami code, console message interaction, etc.). Once per session per egg.
    flappy_kubby_started-Fires when the user starts a Flappy Kubby game session (overlay or /dream route).
    flappy_kubby_game_overscore, crash_namespaceFires when a Flappy Kubby session ends, recording the final score and which namespace caused the crash.
    flappy_kubby_sharedscoreFires when the user clicks the share-on-Twitter button on the Flappy Kubby game-over screen.
    not_found_wake_clickedclick_countFires every time the user clicks the wake-Kubby button on the 404 page (the count grows with each click).
    not_found_home_clicked-Fires when the user clicks the return-home link on the 404 page.
    privacy_opt_out_changednew_stateFires when the user toggles their analytics opt-out state from the /privacy page. Recorded BEFORE the gate flips so the opt-out itself is captured.
    fake_door_viewdoor_id, surfaceFires when a fake-door surface (dialog, gate, teaser) becomes visible to the user. One per open.
    fake_door_clickdoor_id, surfaceFires when the user clicks the CTA that opens a fake-door surface (e.g. a pricing plan button).
    fake_door_submitdoor_id, surface, has_noteFires when the user submits the waitlist form inside a fake-door dialog. The form's email is sent to the form provider for follow-up; analytics receives only door_id, surface, and a boolean indicating whether the optional note field was filled - never the email itself or any hash of it.
    fake_door_dismissdoor_id, surfaceFires when the user closes a fake-door dialog without submitting (X button, Escape, or outside click).
    quiz_started-Fires once when the /quiz route mounts and the visitor enters the discovery quiz funnel. No payload beyond the standard page_variant super-property.
    quiz_question_answeredquestion_id, answer_kind, value_countFires when the user advances past a quiz question with at least one answer entered. Per D-24, NO open-text content and NO email reach this event - only derived metadata (question id, answer kind, and the count of multi-select picks).
    quiz_question_skippedquestion_id, answer_kind, value_countFires when the user clicks 'Skip this question'. Same shape as quiz_question_answered with value_count=0.
    quiz_branchedbranchFires after Q5 advances, recording which Q6 branch the user enters. branch='skipped_q5_nothing_yet' is emitted when Q5 was 'nothing yet' and Q6 is auto-skipped per D-09.
    quiz_completedoutcome, has_quote_consentFires once per session when the user finishes the email-opt-in step in EITHER outcome - opted_in (clicked 'Request access' and the POST returned 200) OR declined (clicked 'No thanks', then dismissed the empathetic re-ask). Carries outcome ('opted_in' | 'declined') and has_quote_consent (boolean - true only when the user opted in AND checked the consent box; always false on the declined path because the consent UI was never shown). Per D-13 + CONTEXT.md revision 1: both paths are 'done', and the discriminator lets analytics tell them apart.
    quiz_email_optin_shown-Fires when the email-opt-in screen mounts.
    quiz_email_submittedhas_quote_consentFires after the JSONL POST returns 200. Carries has_quote_consent only - never the email itself or any hash of it.
    quiz_email_declined-Fires when the user clicks 'No thanks' on the email-opt-in screen for the first time. Triggers the one-shot empathetic re-ask.
    quiz_decline_reack_shown-Fires when the decline re-ask screen mounts (after the first 'No thanks' click).
    quiz_decline_reack_accepted-Fires when the user clicks 'Yes, keep me on the list' on the re-ask, returning them to the email field.
    quiz_decline_reack_dismissed-Fires when the user clicks 'No, I'm sure' on the re-ask. The user advances to the thank-you screen. Note: the dismiss handler ALSO fires quiz_completed with outcome='declined' immediately before this event so the funnel registers a clean 'done' signal - see the quiz_completed entry above.
    quiz_resumed-Fires when the user clicks 'Pick up where I left off' on the resume prompt.
    quiz_restarted-Fires when the user clicks 'Start over' on the resume prompt. The previous answers are overwritten in localStorage.
    quiz_question_revisitedquestion_id, from_question_idFires when the user clicks a previously visited progress dot to jump back to an earlier question. Carries only screen identifiers (question_id = target screen, from_question_id = current screen) - no answer content, no email value, no PII.
    fake_door_submit_faileddoor_id, surface, kindFires when the intake form POST fails on a server-side path (rate-limited, network/5xx, or 4xx validation). Client-side validation does NOT fire this event (D-25). Carries door_id + surface + kind discriminator - NEVER email, role-other, or pain-other content.
    quiz_email_submit_failedkindPhase 29 backport (Phase 30 D-27): fires when the quiz email-opt-in POST fails on a server-side path (rate-limited, network/5xx, or 4xx validation). Client-side validation does NOT fire this event (D-25). Carries kind only - the quiz has a single submit surface so no door_id or surface is needed. NEVER carries email or open-text quiz answers (D-26 closed-key invariant).

    Local-only browser storage

    The Discovery Quiz at /quiz writes a key called kubernap_quiz_state_v1 to your browser's localStorage so you can leave the quiz and pick up where you left off. The key contains your in-progress answers - never your email. It is cleared automatically when you complete the quiz. You can clear it any time by clicking "Clear my saved quiz" on the resume prompt or by clearing site data in your browser settings. The three analytics opt-outs below (DNT, the toggle, ?notrack=1) do not block this autosave because it is local-only and never leaves your device.

    Validation submissions storage

    Validation submissions (quiz answers, intake form fields, and your email when you provide one) are stored in Cloudflare D1 with primary copy in Western Europe. Each submission is retained for 90 days and then automatically deleted. We never store your raw IP address or User-Agent - those are salted SHA-256 hashes used only for per-IP rate limiting.

    Right to erasure: email us at [email protected] and we delete your row by email match. The 90-day retention bounds the window even if you don't ask.

    Anti-abuse: we use Cloudflare Turnstile to filter automated form submissions. Turnstile is privacy-preserving by design - no third-party tracking cookies, no behavioral profile, no Google reCAPTCHA. The Turnstile token issued to your browser is used once to verify a single submission and is never written to PostHog or any analytics surface.

    What we do NOT collect

    • Personally identifiable information (no names, no emails)
    • IP addresses (PostHog is configured with ip: false and ip_anonymize)
    • Session recordings or replays
    • Autocapture or heatmaps (every event is explicit)
    • Cluster data from the Paste & Scan section
    • Raw JSON content you paste - only derived numeric metrics (length, namespace count, total savings)

    Opt out

    Three independent ways - any one is enough, all are honored immediately on every event:

    1. Enable Do Not Track in your browser settings
    2. Set localStorage.kubernap_optout = "true" in DevTools
    3. Append ?notrack=1 to any KuberNap URL

    Current status: Opted in - events sent.

    Click below to opt out of all analytics for this browser.

    Questions?

    File an issue at github.com/milando12/KuberNap or reach out on the public channels listed in our README.