PARTYF
All games

Storage and Cookie Disclosure

The consent-exemption reasoning in §2 follows standard, well-established EDPB/CNIL guidance for functional storage.

Last updated: 2026-10-24 Applies to: PartyF frontend (partyf-frontend)


Executive Summary

PartyF stores these kinds of data in the browser: a language preference, session-restore data (bearer token, room code, player ID), a remembered nickname, best-score records for 2-player games (which include the two players' display names), a set of remembered interface preferences (avatar, recent reactions, and which panels and controls you last collapsed or expanded), a temporary offline retry queue, your own draft decks (up to 10, device only), a first-party consent choice (accepted/declined), and, for the current tab only, a one-time reload marker used after an app update and the "Your night" summary of the room you are in (§1.11) and whether you already changed your look in that room's lobby (§1.12), which Draw a Phone screens you have already seen and your unsent Draw a Phone award picks (§1.10, §1.13). Only after you accept analytics on a deployment that uses PostHog, PostHog also keeps a per-tab copy of its session data in sessionStorage (§1.4). Only if you choose to sign in to an optional account, it also stores the sign-in token and a cached profile (§1.6a); and on builds that offer accounts, a signed-out device keeps its own count of your finished games and the date you dismissed the account offer (§1.6b). Deployments with PostHog analytics enabled may additionally contain PostHog's own client-side identifiers (see §1). The optional email list (privacy.md §13) adds no browser storage: the address you type stays in the form until you submit it, and the token in an emailed confirm/unsubscribe link is held in memory only, from the moment the page opens until you press the confirm or unsubscribe button (or close it), and is removed from the address bar immediately. PartyF does not use HTTP cookies (§3). @sentry/vue (error tracking, plus an in-app "Feedback" form), posthog-js (product analytics) and Monetag (advertising) are all wired into the frontend, each activating only when a given deployment supplies its vendor key/DSN/zone id at build time, and Monetag/PostHog additionally only after the visitor accepts the consent banner — see privacy.md §6 for the data-collection disclosure and §4 below for the tracking-specific detail.


1. Storage Used

1.1 localStorage — Language Preference

Storage key: partyf.locale
Storage duration: Indefinite (persists across sessions)
Data stored: A single locale code (en, es, fr, it, ca, or eu)
Set by: Frontend JavaScript in src/i18n/index.ts

Purpose: To remember your language preference so you do not have to re-select it every time you visit PartyF.

It is removed if you choose "Also forget me on this phone" when signing out, or when a different account signs in on this device (§1.6a).

1.2 localStorage — Session Restore

Storage keys: roomCode, token, youId, sessionRestoredAt
Storage duration: Until the app explicitly clears it (clearSavedSession() — e.g. on leaving/ending a session) or the user clears their browser storage. This is a materially different retention profile than the backend session itself: the backend enforces a hard 6-hour TTL and a 30-minute idle reap (docs/architecture.md), but a stale copy of the token can persist in the browser longer than the session it refers to remains valid server-side. A leftover token becomes inert once the backend session expires or is deleted (the backend rejects it), but it is not proactively wiped from the browser at that point — only on explicit leave/end or a manual browser clear.
Data stored: The session's bearer token (a credential — see partyf-backend/CLAUDE.md's "Tokens are credentials" section), the room code, the player's own ID, and a restoration timestamp.
Set by: src/services/sessionStorage.ts (saveSession/markSessionRestored), called from the session join/restore flow in src/store.ts.

Purpose: So a player who refreshes the page or briefly loses connectivity can rejoin their in-progress session without re-entering a room code, rather than being dropped from the game.

1.3 localStorage — Remembered Nickname

Storage key: partyf.lastNickname Storage duration: Indefinite (persists across sessions — unlike §1.2, clearSavedSession() does not touch this key). Removed only when you clear browser storage, choose "Also forget me on this phone" when signing out, or a different account signs in on this device (§1.6a) Data stored: The free-text display name you last typed when creating or joining a room, or, while you are signed in, your account's display name (unless it is still the default "Player-NNNN"). Per privacy.md §1, this is personal data if it is your real name. Set by: src/services/sessionStorage.ts (saveLastNickname), called from src/store.ts on game creation and join, and src/services/account.ts, which keeps it equal to your account's name while you are signed in; read by LandingScreen.vue to pre-fill the name field on your next visit. It is not copied to the account's synced preferences.

Purpose: So a returning player doesn't have to retype their name every visit.

Unlike §1.1/§1.2, this key, together with the pair records in §1.5, is client-side storage that (a) can hold personal data and (b) is never cleared on a timer. It is still "strictly necessary"-exempt under the same functional-convenience reasoning as §1.1 (see §2).

1.4 localStorage — Product Analytics (PostHog, deployment-dependent)

Storage keys: Set and managed internally by the posthog-js client library (its own distinctid, session, and locally-queued-event keys) — not keys PartyF's own code defines. posthog-js also keeps one small consent flag in localStorage, named phoptinout followed by PartyF's public PostHog project key: it holds 1 once you accept and 0 if you later decline (see §1.8), so the choice is remembered; it contains nothing about you. It also keeps a per-tab copy of its session data in sessionStorage, discarded when the tab closes.
Storage duration: Per posthog-js's own default browser-storage behavior.
Data stored: An anonymous distinct ID and locally-queued analytics events, written by PostHog's client library, not by PartyF's application code.
Set by: posthog-js, initialized in src/telemetry/postHogAnalytics.ts — only on deployments built with a VITE
POSTHOG_KEY; a deployment without one never loads this library and writes nothing.

Purpose: Lets PostHog attribute anonymous product-analytics events to the same browser across a visit/session. Unlike §1.1–1.2, this is third-party (vendor) storage serving an analytics purpose, not PartyF's own functional storage — see the note at the end of §2 for why this changes the consent analysis.

Autocapture is disabled (autocapture: false, set at posthog.init() in postHogAnalytics.ts). Without this, PostHog's default autocapture would report DOM element text ($eltext) on clicks and inputs across the app — PartyF's screens are built around typed guesses, custom category names, and drawing prompts, so leaving autocapture on would have sent player-submitted content to PostHog beyond the named custom events this disclosure describes (screenviewed and PostHog's own $pageview for the same screen views, gamestarted, gamestartcancelled, gameended, localhubopened, localgameopened — screen views, which multiplayer games are started, cancelled before starting or ended, and when the single-phone games section and its games are opened; keepsakesaved, when a Draw a Phone image is saved, carrying only its format and whether it was shared or downloaded, never the image or its content; game properties carry only the game id, player/round counts, how long the game ran in seconds, and the instant-vs-gate path, never names, guesses, or prompts; plus connection-quality events wsstuck, wsgaveup, wsrecovered, wsatcapacity, resyncrequested, resyncapplied, resynctimeout, whose properties carry only the screen name, durations in milliseconds (msSinceLastFrame while the connection was silent, outageMs for a disconnection), the number of consecutive connection attempts that never opened (failures, only on wsatcapacity), the browser's network class (effectiveType/saveData, when available), and a state-revision number (revision, only on resync_applied) — never room codes, tokens, nicknames, or message content).

1.5 localStorage — Pair Records (2-player games)

Storage keys: partyf.duoRecord.<game>.<nameA>|<nameB>, one per game and pair of players Storage duration: Indefinite (persists across sessions, never cleared automatically) Data stored: The best combined score this pair has reached in that game on this device, a single number. The key itself contains the game's id and the two players' display names as they typed them, sorted alphabetically. Per privacy.md §1, those names are personal data if either player typed a real name. Set by: src/components/PairScoreCard.vue, on the results and scoreboard screens of 2-player (duo) games; read by the same component to show the pair's record and a "new record" stamp.

Purpose: So a pair who plays again on the same device can see whether they beat their best score. The record never leaves the device and is not sent to the backend.

1.6 localStorage — Remembered Interface Preferences

Storage keys: partyf.lastAvatar, partyf.recentReactions, partyf.headerCollapsed, partyf.reactionFabCollapsed, partyf.preRoundGateMode, partyf.startCountdown, partyf.house, partyf.pack.<game> (one per game with themed packs), partyf.mode.<game>, partyf.setup.<game>, partyf.tip.bet, partyf.boardroomBluff.coachHidden, partyf.boardroomBluff.groupsHidden, partyf.keepsake.hideNames, partyf.sound, partyf.haptics, partyf.motion, partyf.dataSaver, partyf.hostedBefore, partyf.finderPrefill, partyf.hostTips, partyf.seenTopics Storage duration: Indefinite; each value is retained until changed or browser storage is cleared Data stored: Only values from PartyF's own fixed lists:

  • one avatar emoji from PartyF's avatar list (partyf.lastAvatar); while you are signed in, it is kept equal to your account's emoji;
  • up to three reaction emoji from PartyF's reaction list, the last three you sent, so the reaction button can offer them first (partyf.recentReactions). An older key, partyf.lastReaction, is read once to carry its value over and is then removed;
  • a "0"/"1" value recording whether you last collapsed the in-game header (partyf.headerCollapsed), tucked the reaction button away to the screen edge (partyf.reactionFabCollapsed), or switched off Boardroom Bluff's coach hint (partyf.boardroomBluff.coachHidden);
  • whether a host prefers new rounds to start straight away or pause on the rules screen first, as instant or explain (partyf.preRoundGateMode);
  • whether a host wants everyone to see a short 3-2-1 countdown before a game starts, as on or off (partyf.startCountdown);
  • the host's saved "My house rules", one object holding only PartyF's fixed room settings (pace, rules gate, countdown, speed bonus, host watches, prompt language and an optional second language, whether the room is Adults only (18+), players picking the topic, topic visibility) that new rooms you host start with (partyf.house). It is synced to your account while you are signed in (§1.6a);
  • which themed pack a host last chose for a game, as one of PartyF's fixed pack ids (couples, work-night, nhie) or empty for "Mixed" (partyf.pack.<game>, e.g. partyf.pack.who-is-the);
  • which play mode a host last chose for a game that offers one (for example Who-Is-The's "Bet on it"), as classic or bet, one per game (partyf.mode.<game>);
  • a host's last rule and timing choices for a game (for example the round timer, a speed-bonus switch or a difficulty level), as a short list of setting names and values from PartyF's own fixed options, one per game (partyf.setup.<game>). Prompts, words, topics and anything else that would give a round away are never stored here;
  • a "1" value recording that you have dismissed, or already acted on, the one-time tip above Who-Is-The's bet row (partyf.tip.bet);
  • which groups of Boardroom Bluff's action panel you last collapsed, as a list of the fixed ids safe and claim (partyf.boardroomBluff.groupsHidden);
  • whether the "Hide names" switch in the Draw a Phone results album was on the last time you used it, as "true" or "false", so saved keepsake images show avatar emoji instead of nicknames by default (partyf.keepsake.hideNames). It is written only when you flip that switch, and it is removed when you clear your browser's storage for PartyF; it holds no name, drawing or game content;
  • which of the host's one-time tips in the game's bottom bar you have already seen, as a list of PartyF's fixed tip ids tools and ticket (partyf.hostTips). It is written the first time a tip is shown and holds no name or game content;
  • whether sound effects are on, as on or off (partyf.sound). Without a stored choice, sound is off on touch devices and when the device asks for reduced motion, and on otherwise. This one stays on the device and is not copied to an account (§1.6a).
  • the "This phone" settings in your profile (or, without accounts, in the lobby's "Your look" sheet), each written only when you change it: whether vibration is on, as on or off (partyf.haptics; without a stored choice it is on unless the device asks for reduced motion); and whether PartyF should reduce motion and save data, each as auto, on or off (partyf.motion, partyf.dataSaver; auto follows your device's own motion setting and, for data saver, the connection your browser reports). They stay on the device and are not copied to an account (§1.6a).
  • a "1" value recording that this browser has hosted a game before, so the lobby shows a returning host the short "Find our game" bar instead of the larger first-time card (partyf.hostedBefore). It is written the first time you start a game as host and holds no name or game content;
  • short, opaque fingerprints of the built-in topics this device was recently shown, so the suggestions you get are not always the same (partyf.seenTopics). For each game it keeps the last 300 fingerprints, each an 8-character code (for example 3fa9c01b) that the server derives from a topic; no topic text, room code, name or other player data is stored. It stays on this device, is never copied to an account, and the only place it is sent is to PartyF's own server, as the "avoid" list of the request that draws a new topic, so that the draw can prefer something you have not seen. Clearing your browser's storage resets it;
  • the kind of fun you picked on the home page before creating a room (up to two of PartyF's fixed ids laugh, bluff, think, draw, optionally a "just the two of us" flag, and the time it was saved), so the lobby's game finder can pre-select it (partyf.finderPrefill). It is read once and removed as soon as the host's lobby opens for the room you create (whether or not the finder is opened; it is kept in memory for that room only), and ignored after six hours.

These local preference values are not retained by the backend as profile/account attributes and are not used for tracking. The selected avatar itself is also sent in the normal gameplay player update where all room players can see it during the session.

Choosing "Also forget me on this phone" when signing out removes partyf.lastAvatar and the keys in this section that are synced to an account (listed in §1.6a); a different account signing in on this device removes the same keys.

Set by: src/services/avatarPreference.ts (and src/services/account.ts for the signed-in copy of your emoji), src/services/reactionPreference.ts, src/services/headerPreference.ts, src/services/preRoundGatePreference.ts, src/services/startCountdownPreference.ts, src/services/soundPreference.ts, src/services/hapticsPreference.ts, src/services/motionPreference.ts, src/services/dataSaverPreference.ts, src/services/playedTonight.ts (the hosted-before flag), src/services/finderPrefill.ts (the home page's kind-of-fun pick) and src/services/packPreference.ts and src/services/gameModePreference.ts and src/services/setupParamsPreference.ts and src/services/betTipPreference.ts and src/services/hostTipsPreference.ts, and the Draw a Phone results album's "Hide names" switch (src/utils/keepsakeSave.ts); the avatar is read by the create/join identity screens, the reaction preferences by ReactionFab.vue, the header preference by GameHeader.vue, the pre-round and countdown preferences by GameSetupModal.vue and when a host starts a game, the pack preference by GameSetupModal.vue, the lobby and the host tools' "Change pack", the play mode and the last rule and timing choices by GameSetupModal.vue, and the bet tip by WhoIsScreen.vue. The two Boardroom Bluff preferences are read and written by src/components/boardroom/CoachStrip.vue and src/components/boardroom/ActionPicker.vue. Every one of these reads and writes inside a try/catch, so a browser that blocks storage (private windows, blocked third-party storage) simply falls back to the default every time rather than failing.

Purpose: To avoid making a returning device choose the same avatar again, lose its recent reactions, or redo a layout or host choice it already made (a compact header, a stowed reaction button, this phone's sound, vibration, motion and data-saver settings, the pre-round rules choice, a game's themed pack, your house rules, a game's play mode and rule/timing choices, a dismissed hint or tip, a collapsed panel, or the keepsake "Hide names" choice) every time. The saved values are restricted to PartyF's finite built-in emoji, on/off and option values and are used only for this functional convenience; they are not identifiers, profile attributes, or analytics.

1.6a localStorage — Optional Account Session

Storage key: partyf.account Storage duration: Until you sign out, delete your account, or clear browser storage Data stored: Only if you choose to sign in with an optional PartyF account: the account session token (a credential) and a cached copy of your account profile (display name, avatar emoji, synced preferences, chosen badge, whether you show your profile in rooms, creation date). Nothing is stored here if you never sign in; the key is not created and no Google script is loaded. Set by: src/services/account.ts, only after you explicitly start sign-in. While signed in, these remembered-preference values are also copied to your account on PartyF's backend (the synced preferences): partyf.locale (§1.1), and from §1.6 partyf.recentReactions, partyf.headerCollapsed, partyf.preRoundGateMode, partyf.startCountdown, partyf.house, partyf.pack.<game>, partyf.mode.<game> and partyf.setup.<game>. The other §1.6 keys stay on this device only; partyf.lastNickname (§1.3) and partyf.lastAvatar are not synced either, but mirror your account's name and emoji. The token is sent with room create/join requests so the backend can link your seat to your account, and, if you sign in, sign out or delete your account while in a room, with a request that links or unlinks your seat there (privacy.md §11).

Purpose: To keep you signed in on this device and sync your preferences across devices. It is functional storage you requested by signing in, and is removed when you sign out. Signing out offers "Also forget me on this phone", which additionally removes partyf.lastNickname, partyf.lastAvatar, the synced preferences listed above, partyf.guestStats (§1.6b) and partyf.account.lastId; it never removes the session-restore keys (§1.2), so you stay in a room you are in. Signing in loads Google's sign-in script (accounts.google.com), which Google may set its own storage for; that is outside PartyF's control.

Storage key: partyf.account.lastId Storage duration: Kept after you sign out, until you delete your account, choose "Also forget me on this phone" when signing out, or clear browser storage Data stored: The opaque identifier of the last account that signed in on this device (no name or email). It is used only so that remembered preferences from one account are not copied into a different account that signs in later on the same device; when the account differs, the synced preferences and the remembered name and avatar stored on this device are cleared instead.

1.6b localStorage — Guest Game Stats and the Account Offer

Storage key: partyf.guestStats Storage duration: Until you sign in and add them to an account or choose "Start fresh", choose "Also forget me on this phone" when signing out, or clear browser storage Data stored: Only on builds that offer optional accounts, and only while you are not signed in: per game type, how many games you finished, won, finished in the top three and hosted, the points you earned in them, and when you last played one. A game counts only if at least 2 players took part and at least 2 rounds were played; a game you host while sitting out as a viewer counts only as hosted. No names, answers, drawings or room codes are stored, and nothing identifies you. Set by: src/services/guestStats.ts, when a game ends on the results screen.

Purpose: To show what an account would keep (the "Save tonight's stats" card) and, right after you sign in, to offer adding these counts to your account. They never leave this device unless you choose "Add to my account" after signing in; either answer removes them from the device.

Storage key: partyf.accountNudge Storage duration: Until you clear browser storage Data stored: When you tapped "Not now" on the "Save tonight's stats" card (a date), or a marker that you have signed in on this device. Set by: src/services/accountNudge.ts

Purpose: To hide that card for 30 days after "Not now", and for good once you have signed in, so the offer is not repeated after every game. Functional storage for a feature you can ignore; see §2.1a.

1.6c localStorage — Draft Decks

Storage key: partyf.decks Storage duration: Until you delete the draft or clear browser storage. Never cleared automatically, not even when you sign out or delete your account. Data stored: The custom decks you write on this device, up to 10 drafts. Each draft holds its name, language, kind (lines or words), the card texts you entered, and when it was created and last edited. If you save a draft to your account, the draft also records the id of the saved deck and the id of your account (opaque identifiers, no name or email), so it is not offered for import again; that link is ignored if a different account signs in on this device. Set by: src/domain/decks.ts, as you write in the deck editor and when you save a copy of a shared deck while signed out (src/screens/DeckPreviewScreen.vue); the account link by src/composables/useSavedDecks.ts.

Purpose: So a deck you are writing is still there when you come back on this device. Drafts are never sent to the backend while they sit on the device. A draft leaves the device in one of three cases, and only then:

  • You attach it to a room you host. Its cards are sent to the game server and kept with that room's session until the room ends, the same as the room's other data.
  • You save it to your account. If you are signed in, the draft is sent to your account, where it is kept until you delete it (privacy.md §12). The device copy is kept after saving until you delete it.
  • You import drafts into your account on sign-in. Offered once after you sign in, and only if you choose "Add to my account". The device copies are likewise kept.

To clear drafts, delete each deck under "My decks", or clear this site's browser storage (which removes every key in this section). Opening a shared deck's /d/<code> link stores nothing beyond this key, and only if you save a copy.

1.7 localStorage — Temporary Offline Action Queue

Storage key: partyfpendingactions Storage duration: Normally until queued actions are sent successfully or discarded during a retry pass. Entries older than 10 minutes are never retried and are dropped at the next queue-processing pass. A stale copy can remain in browser storage longer than 10 minutes if the browser never gets another retry pass, until browser storage is cleared; it is not proactively deleted merely because it becomes stale. Data stored: If a gameplay REST request fails because the device is offline, the queue can contain the request URL/method, timestamp and retry metadata; request headers, including the session bearer token; and the request body, which can include game answers or other submitted content. Session/player identifiers can also appear in the request URL. Set by: src/utils/api.ts (enqueuePendingAction/savePendingActions), and consumed by its online/retry queue processor.

Purpose: To let a transient network failure retry a requested game action without making the player submit it again. Because this queue can contain credentials and user-entered game content, it is kept only for short-lived operational recovery and is never used for analytics.

1.8 localStorage — Consent Choice

Storage key: partyf.consent
Storage duration: Indefinite (persists across sessions, until you clear browser storage)
Data stored: A single value, accepted or declined — nothing else.
Set by: src/utils/consent.ts (setConsentChoice), written when you click Accept/Decline on ConsentBanner.vue; read by main.ts (to gate PostHog) and by index.html's Monetag loader / scripts/pageChrome.mjs's native-banner script (to gate the ad scripts).

Purpose: Remembers your advertising/analytics consent choice so you are not asked again every visit, and so PartyF's own code — not a vendor-provided consent tool — can gate Monetag and PostHog on it. This key itself holds no tracking data; declining writes declined, and from then on neither script is loaded (see below for what happens if you decline after accepting).

Changing or withdrawing your choice: The "Privacy choices" link in the footer of the home page, and the "Privacy choices" row in the "This phone" settings (tap your own name in a game's lobby, or open your profile from the home screen when accounts are available), show the banner again with your current choice; picking Accept or Decline overwrites this key. Declining after accepting stops PostHog from collecting from then on: PartyF resets PostHog, which clears its identifier and session data from your browser, and then opts out (the consent flag in §1.4). If Monetag's ad script had already loaded, the page reloads once so it is no longer running: right away if you are on the home screen, otherwise (in a game room including its lobby, a single-phone game, or anywhere else) when you next return to the home screen. Declining stops new data collection; it does not delete cookies Monetag has already placed or data already sent to PostHog or Monetag. You can delete cookies in your browser settings, and you can ask us to delete analytics data (see §7). Accepting again lets PostHog resume. Clearing this site's local storage also still works.

1.9 sessionStorage — Update Reload Marker

(The sections from §1.9 to §1.13 are PartyF's own per-tab keys. The one other per-tab copy, PostHog's session data, exists only after you accept analytics and is described in §1.4.)

Storage key: partyf.chunkReloadAttempted Storage duration: The current browser tab only; sessionStorage is discarded when the tab closes, and PartyF clears the key itself as soon as the app has loaded successfully Data stored: The single value "1", nothing else. Set by: src/utils/chunkLoadError.ts, when part of the app fails to load (typically right after a new version was deployed while the page was open); cleared by src/main.ts once the app has mounted.

Purpose: Lets PartyF reload the page once to pick up the new version, while preventing an endless reload loop if loading keeps failing. It identifies no one and is never sent anywhere.

1.10 sessionStorage — Draw a Phone Screens Already Shown

Storage keys: partyf.dap.intermission.<game> and partyf.dap.drumroll.<game>, where <game> is the room code plus a random id the server gives each Draw a Phone game (or, from an older server that sends none, a short hash of that game's seating and opening sentences) Storage duration: The current browser tab only; sessionStorage is discarded when the tab closes Data stored: The single value "1", nothing else. Set by: src/domain/drawAPhoneReveal.ts, when Draw a Phone shows its awards explainer before voting or its "And the winners are…" drumroll before the results.

Purpose: Shows each of those two screens once per game, so reloading the page does not replay them. It identifies no one and is never sent anywhere.

1.11 sessionStorage — "Your night" Summary

Storage key: partyf.night Storage duration: The current browser tab only; sessionStorage is discarded when the tab closes. It holds one room at a time and starts again when you play in a different room Data stored: The room code; for each game you finished in that room (same 2-player, 2-round rule as §1.6b), its game type and whether you played it (a host sitting out as a viewer did not), won it or finished in the top three; and, if you are signed in, the badges you earned while in that room. No names, answers, drawings or points. Set by: src/services/roomNight.ts, when a game ends and when a badge is announced; read by the "Your night" card on the results screen. It is written for every player, with or without an account, and also on builds without accounts.

Purpose: Lets the results screen sum up your night in that room, and keeps that summary if the page reloads or the phone discards the tab mid-party. It is never sent anywhere.

1.12 sessionStorage — "Make it yours" Card Folded

Storage key: partyf.lookDone.<room code> Storage duration: The current browser tab only; sessionStorage is discarded when the tab closes Data stored: The single value "1", nothing else. Set by: src/composables/useLookFold.ts, when you change your name or animal in a room's lobby.

Purpose: Folds the lobby's "Make it yours" card to one line once you have changed your look in that room, so it stays folded if the page reloads. It identifies no one and is never sent anywhere.

1.13 sessionStorage — Draw a Phone Award Picks Not Yet Sent

Storage key: partyf.dap.ballot.<game>, where <game> is the same per-game value as in §1.10 Storage duration: The current browser tab only; sessionStorage is discarded when the tab closes Data stored: Your award picks while you are voting in Draw a Phone (which drawing, line and story you chose, as the game's internal entry ids and a story number; no names and no drawing or text content) and whether you have tapped Lock in. Set by: src/domain/drawAPhoneBallot.ts, while you vote.

Purpose: Keeps your picks if the page reloads in the middle of voting. They leave the device only as your vote, when you tap Lock in.


2. Consent Analysis

2.1 Why a Consent Banner Is NOT Required

Under the ePrivacy Directive (2002/58/EC, as amended) and GDPR, consent is required for storage on a user's device unless the storage is strictly necessary for the operation of the requested service.

The first-party functional uses in §1.1–§1.3, §1.5, §1.6, §1.6a, §1.6b, §1.6c and §1.9–§1.12 qualify as strictly necessary under the same exemption: they restore a language, active session, nickname, pair record, avatar, recent reactions, or layout and host choice that the player explicitly made for this device, keep a signed-in account signed in on the device where the player asked for that (§1.6a), remember the player's own on-device game counts and a dismissed account offer (§1.6b), keep the draft decks the player is writing on this device (§1.6c), keep an app update from reloading in a loop (§1.9), avoid replaying a screen already shown (§1.10), or keep the results screen's summary of the player's own games in the current room for the current tab (§1.11), or keep the lobby's "Make it yours" card folded once the player has changed their look (§1.12). None is used for tracking, profiling, or advertising. The temporary queue in §1.7 is necessary to deliver a requested action during a transient network failure and is kept only for short-lived recovery. The finite interface values are not identifiers or profile attributes.

1. It is functionally essential to the service: The user explicitly selects a language. Without localStorage, the language preference cannot be saved. The user would have to re-select their language every single visit—a poor user experience and arguably a failure of the service.

2. It contains no tracking, profiling, or analytics: The locale code is not used to track user behavior, build profiles, or serve targeted content. It is read locally in the browser and used only to display content in the selected language.

3. It is exempt under GDPR Recital 32: Storage necessary to provide a service explicitly requested by a user does not require consent. A user's language preference is a request-specific setting, not a persistent profile or tracking mechanism.

4. EU guidance supports this: The European Data Protection Board (EDPB) and Member State guidance (e.g., CNIL in France) explicitly exempt language preferences and similar UX settings from consent requirements, provided they are not linked to analytics or profiling.

Conclusion: A consent banner for language preference storage is unnecessary and would degrade UX without providing meaningful privacy protection.

This §2 analysis covers §1.1–§1.3, §1.5–§1.7 and §1.9–§1.12 (and §1.6a–§1.6b, see §2.1a), plus the consent choice itself (§1.8) — recording whether you accepted or declined is exempt under the same reasoning EU guidance applies to consent-management cookies generally: a mechanism can't ask for consent to remember that you already answered. PostHog's storage (§1.4) serves an analytics purpose rather than delivering the specific service the user requested, so the "strictly necessary" exemption does not extend to it — its consent basis is handled separately, below. Monetag's ad storage is likewise non-essential and out of scope for this exemption; see §4.

src/main.ts gates analytics.init() behind PartyF's own first-party consent banner (src/utils/consent.ts, rendered by ConsentBanner.vue), only starting PostHog once the visitor has clicked "Accept". It fails closed: no decision yet, or a decline, and PostHog stays off rather than assuming consent; a later decline stops it again (§1.8). This replaced Google's Funding Choices CMP (window.__tcfapi), which required a live AdSense account to publish its consent config against — AdSense was removed after repeated Google review rejection (see CLAUDE.md), leaving Funding Choices with nothing to attach to.

2.1a Optional-account storage (§1.6a and §1.6b)

These keys are written only because of something the visitor did, and none is used for tracking, profiling or advertising:

  • partyf.account and partyf.account.lastId exist only after the visitor explicitly signs in; the token is the credential that keeps that requested session alive, and lastId stops one account's synced preferences leaking into another account on a shared device. They are removed on sign-out/deletion (partyf.account) or kept only as an opaque id (lastId, also removed if the visitor chooses "Also forget me on this phone").
  • partyf.guestStats and partyf.accountNudge are functional storage that remembers the visitor's own on-device game counts and the date they dismissed the account offer, so the offer can show what an account would keep and is not repeated after every game. The counts never leave the device unless the visitor chooses "Add to my account" after signing in. They hold no names, answers or room codes and are not identifiers.
  • While signed in, partyf.lastNickname and partyf.lastAvatar (§1.3, §1.6) hold a copy of the account's own name and emoji, so the identity the visitor chose is pre-filled on this device; they are cleared with "Also forget me on this phone".

Because the feature is optional and the stored values are the visitor's own, PartyF treats these keys as functional storage for a feature the user requested and does not gate them behind the consent banner. Google's sign-in script loaded when the visitor opens sign-in is third-party and out of scope for this exemption; it is requested only on that explicit action (see privacy.md §11).


2.2 Recommended Disclosure Instead

Rather than a consent banner, PartyF should include a clear, plain-language disclosure of localStorage use in:

1. The Privacy Notice (see privacy.md) — include a section on client-side storage. 2. A footer link or in-app help text — a brief line: "We save your language preference locally on your device so you don't have to re-select it."

This satisfies the transparency requirement of GDPR Art. 13–14 without the friction of a consent banner.


3. No Cookies

PartyF does not use HTTP cookies. Session tokens are:

  • Sent as HTTP Authorization: Bearer <token> headers in REST requests, and over WebSocket as a Sec-WebSocket-Protocol subprotocol value rather than in the connection URL's query string (src/services/wsClient.ts, ws/handler.go's extractToken). Keeping the token out of the URL means it isn't captured in platform-level request logs (e.g. Cloud Run's).
  • Persisted to localStorage, not memory-only (see §1.2) for session-restore purposes.
  • Never written to cookies.

Tokens are credentials and are treated as such in the backend; see privacy.md and partyf-backend/CLAUDE.md for details. The Privacy Notice's data-retention table should be read alongside §1.2 above — the backend's TTL bounds how long a token remains valid, not how long a copy of it can sit in a user's browser storage.


4. Third-Party Tracking

  • PostHog (posthog-js) is integrated for anonymous product analytics. It only runs on deployments built with VITEPOSTHOGKEY; see §1.4 for the storage it writes and §2.1 for its consent gate.
  • Sentry (@sentry/vue) is integrated for error tracking and an in-app feedback form. It only runs on deployments built with VITESENTRYDSN.
  • Monetag is integrated for advertising, in two formats: an In-Page Push unit (index.html, sitewide) and a Vignette/Native banner (static /games/<id>/ pages only). Each only runs on deployments with its own zone id configured (VITEMONETAGIPPZONEID / VITEMONETAGNATIVEZONEID), and — on top of that — only after the visitor accepts the consent banner (§1.8). Monetag's script sets its own cookies/device identifiers on the visitor's device for ad delivery and measurement; this storage is not first-party localStorage under PartyF's control and is out of scope for the "strictly necessary" analysis in §2. See privacy.md §6 for the disclosure.

PartyF does not use Google Analytics, Facebook Pixel, Hotjar, Drift, Intercom, Google AdSense, or session-replay tooling (no Sentry.replayIntegration() or equivalent is configured). AdSense was removed in 2026-09 after repeated Google review rejection; see CLAUDE.md.

Fonts remain self-hosted (3 woff2 font files bundled with the app); confirmed no fonts.googleapis.com/fonts.gstatic.com references remain in the codebase or built image.


5. Implementation Guidance

5.1 What NOT to Do

  • ❌ Do not add a cookie consent banner for localStorage.
  • ❌ Do not gate language preference selection behind a consent flow.
  • ❌ Do not copy a generic "cookie notice" from a template.

5.2 What TO Do

  • ✅ Add a clear sentence to the Privacy Notice disclosing localStorage use and its purpose.
  • ✅ Optionally, add an in-app tooltip or help text explaining language preference storage.

6. Assumptions and Caveats

This analysis assumes:

1. Frontend error handlers (app.config.errorHandler, window.onunhandledrejection) always log to console.error first; @sentry/vue and posthog-js (see §1.4 and §4) additionally send data off the browser when a deployment supplies the relevant vendor key.

2. Fonts are self-hosted — confirmed, see §4.

3. No further undisclosed data flows beyond §1.1–§1.12. If the frontend begins storing anything else in localStorage, this document needs updating (see CLAUDE.md's "Legal docs" section for the maintenance checklist).

4. Jurisdiction is EU/EEA. This analysis applies to visitors in the EU and EEA. If you serve significant traffic from other jurisdictions (e.g., UK, Switzerland, Canada, California), their respective data protection laws may impose additional requirements.


Status: The §1.3 nickname key is disclosed in privacy.md's client-side storage bullet, and the first-party consent banner described in §1.8/§2.1 is live in production, gating both PostHog and Monetag. No dashboard publication step is needed for this consent mechanism — unlike the removed Funding Choices/AdSense flow, it's PartyF's own code and storage end to end.

All games Guides About Contact Privacy Terms Storage Licenses

© 2026 PartyF. Free browser-based party games.