Skip to content

Kaelith69/blindly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

19 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

blindly hero banner

No photos. No filters. Just words.

Features β€’ Installation β€’ Usage β€’ Architecture β€’ Roadmap β€’ License


Dating apps broke the deal when they made appearance the first filter. Blindly flips that β€” your profile is text only, your match is singular, and the conversation is the whole point. Nothing to scroll past.

Blindly is a serverless, text-only blind dating progressive web app built on React 19, Firebase, and Framer Motion. There are no profile photos, no photo uploads, and no image fields anywhere in the data model. Users create a handle, a tagline, pick interest tags from a curated list, and write answers to personal prompts. Discovery is a physics-driven swipe deck. A mutual right-swipe triggers an atomic Firestore transaction that locks both users into a single shared chat. That's the entire product.


version react firebase vite framer platform license Live Demo


πŸ—‚ System Overview

Blindly is a single-page React application with no custom backend. Firebase handles every server-side concern: authentication (Email/Password and Phone OTP), real-time data via Firestore onSnapshot, and authorization via server-enforced security rules. The entire app state flows through two React contexts β€” AuthContext (identity + live userDoc sync) and ThemeContext (AMOLED dark / light toggle). The match engine runs client-side inside a Firestore transaction.

blindly/
β”œβ”€β”€ public/                     # Static assets (SVGs)
β”œβ”€β”€ assets/                     # README diagram assets
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ onboarding/         # 6 discrete step components
β”‚   β”‚   β”œβ”€β”€ SwipeDeck.jsx       # Physics swipe + match transaction
β”‚   β”‚   β”œβ”€β”€ ProfileCard.jsx     # Text-only profile display
β”‚   β”‚   β”œβ”€β”€ ChatView.jsx        # Real-time message stream
β”‚   β”‚   β”œβ”€β”€ ProfileView.jsx     # Full profile modal
β”‚   β”‚   β”œβ”€β”€ BottomNav.jsx       # Tab navigation
β”‚   β”‚   β”œβ”€β”€ AuthForm.jsx        # Login / sign-up form
β”‚   β”‚   β”œβ”€β”€ OnboardingGate.jsx  # Redirect if profile incomplete
β”‚   β”‚   └── ProtectedRoute.jsx  # Redirect if unauthenticated
β”‚   β”œβ”€β”€ context/
β”‚   β”‚   β”œβ”€β”€ AuthContext.jsx     # onAuthStateChanged + onSnapshot
β”‚   β”‚   └── ThemeContext.jsx    # Dark / light mode
β”‚   β”œβ”€β”€ layouts/
β”‚   β”‚   └── AppLayout.jsx       # Shared chrome for app routes
β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”œβ”€β”€ Home.jsx            # Landing / marketing
β”‚   β”‚   β”œβ”€β”€ Auth.jsx            # Auth page wrapper
β”‚   β”‚   β”œβ”€β”€ Onboarding.jsx      # 6-step wizard coordinator
β”‚   β”‚   └── Dashboard.jsx       # Main app: swipe deck + chat
β”‚   β”œβ”€β”€ App.jsx                 # Router definition + guards
β”‚   β”œβ”€β”€ main.jsx                # React root mount
β”‚   β”œβ”€β”€ constants.js            # AVAILABLE_TAGS, AVAILABLE_PROMPTS
β”‚   β”œβ”€β”€ firebase.js             # SDK init + db/auth exports
β”‚   └── index.css               # Design system, CSS custom properties
β”œβ”€β”€ scripts/
β”‚   └── seed-profiles.mjs       # Dev utility: populate test profiles
β”œβ”€β”€ firestore.rules             # Server-side authorization
β”œβ”€β”€ firebase.json               # Firebase project config
β”œβ”€β”€ vite.config.js
└── package.json

See the architecture diagram for the full system view.


✨ Features

Feature What it actually does
πŸ™ˆ Blind Profiles No photoURL field exists at schema level. Firestore rules reject image writes. You see handle, tagline, tags, and prompt answers β€” nothing else.
πŸ’œ One Match Rule currentMatchId on both user documents is set atomically in a single Firestore runTransaction. You cannot swipe while matched.
πŸƒ Physics Swipe Deck useMotionValue drives card rotation and LIKE/NOPE overlay opacity in real time. AnimatePresence handles the spring-exit on swipe completion.
πŸ’¬ Real-time Chat onSnapshot on matches/{matchId}/messages streams messages live. Auto-scroll via useRef. 500-character hard limit enforced on input.
πŸ” Dual Auth Firebase Authentication β€” Email/Password for standard sign-up, Phone OTP for passwordless. Short-lived JWTs refreshed automatically.
πŸŒ™ AMOLED Theme Pure #000000 dark mode via CSS custom properties. ThemeContext toggles the data-theme attribute on the root element.
🧭 6-Step Onboarding Discrete components for Handle β†’ BasicInfo β†’ Tagline β†’ Tags β†’ Prompts β†’ Review. State collected in Onboarding.jsx, written to Firestore in a single setDoc on the final step.
πŸ”’ Firestore Rules Profile writes isolated per UID. Swipe subcollection inaccessible to other users. Auth token required for all reads/writes.
β™Ώ Accessibility All interactive elements carry aria-label / title. Swipe actions have keyboard-accessible Pass/Like buttons. WCAG AA contrast in both themes.

πŸŽ› Capability Visualization

blindly capabilities


πŸ—οΈ Architecture

blindly architecture

Blindly has three logical layers. The browser layer is a React 19 SPA bundled by Vite 6, using React Router DOM 7 for client-side routing. Two route guards (ProtectedRoute, OnboardingGate) sit at the router level β€” an unauthenticated user never reaches the app shell, and a user with an incomplete profile can't skip onboarding by typing a URL. The Firebase SDK bridge is the only communication channel between the browser and the backend; there is no Express server, no REST API, no GraphQL layer.

The Firebase platform layer provides three services. Firebase Auth handles identity: it issues short-lived JWTs that the Firestore SDK attaches to every request. Cloud Firestore handles data: three collections (users, matches, and the messages sub-collection) store all app state. Security Rules act as the authorization layer β€” the client application never runs privileged code; everything it can and can't do is enforced server-side by Firestore before any data is touched. This means the frontend code is inherently limited: it can't escalate privileges by modifying the SDK.

The match engine runs inside SwipeDeck.jsx. When User A swipes right, it writes a swipe document and then reads whether User B already swiped right on A. If yes, it calls runTransaction β€” which atomically creates the match document and updates currentMatchId on both user documents. Both users' onSnapshot listeners fire within milliseconds, and AuthContext distributes the new state throughout the component tree without any manual refetch.


πŸ”„ Data Flow

blindly data flow

The primary match-engine path:

User A swipes right on User B
  β”‚
  β”œβ”€ setDoc(users/A/swipes/B, { direction: "right" })
  β”‚
  β”œβ”€ getDoc(users/B/swipes/A) ──── direction != "right" ──▢  next card
  β”‚                                        β”‚
  β”‚                               direction == "right"
  β”‚                                        β”‚
  └─ runTransaction() β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”œβ”€ create  matches/{A_B}  { users:[A,B], status:"active" }
       β”œβ”€ update  users/A        { currentMatchId: "A_B" }
       └─ update  users/B        { currentMatchId: "A_B" }
             β”‚
             └─ onSnapshot fires in AuthContext (both users)
                   β”‚
                   └─ Dashboard renders match overlay
                         β”‚
                         └─ ChatView streams matches/{A_B}/messages

πŸš€ Installation

Prerequisites

Requirement Version Why
Node.js β‰₯ 18 ESM support required by Vite 6
npm β‰₯ 9 Workspace-aware lockfile format
Firebase project any Auth + Firestore must be enabled
Firebase CLI β‰₯ 13 For deploying Firestore security rules

You need a Firebase project with Authentication (Email/Password and Phone providers enabled) and Cloud Firestore (native mode). Storage does not need to be enabled β€” Blindly doesn't use it.

Steps

  1. Clone the repository

    git clone https://github.com/Kaelith69/blindly.git
    cd blindly
  2. Install dependencies β€” Vite, React, Firebase, Framer Motion, React Router, Lucide icons:

    npm install
  3. Configure Firebase β€” Open src/firebase.js and replace the firebaseConfig object with your project's credentials from the Firebase Console β†’ Project Settings β†’ Your Apps:

    const firebaseConfig = {
      apiKey: "YOUR_API_KEY",
      authDomain: "YOUR_PROJECT.firebaseapp.com",
      projectId: "YOUR_PROJECT_ID",
      storageBucket: "YOUR_PROJECT.firebasestorage.app",
      messagingSenderId: "YOUR_SENDER_ID",
      appId: "YOUR_APP_ID"
    }
  4. Deploy Firestore security rules β€” The rules in firestore.rules enforce write isolation per user. Deploy them before testing so your dev environment matches production:

    firebase deploy --only firestore:rules
  5. Start the development server:

    npm run dev

    Vite starts at http://localhost:5173 with HMR.

Seed Test Profiles (Optional)

The swipe deck needs other user profiles to show you. A seed script is included:

node scripts/seed-profiles.mjs

This writes synthetic profiles to your Firestore users collection. Don't run it against production.


πŸ–₯ Usage

  1. Sign up at /auth β€” choose Email/Password or Phone OTP.
  2. Complete onboarding β€” you'll be redirected to /onboarding. Work through all 6 steps. Nothing is written to Firestore until you hit submit on the final Review step.
  3. Discover β€” swipe right (or tap Like) to express interest. Swipe left (or tap Pass) to skip. Candidates who've already been swiped on don't reappear.
  4. Match β€” if the other person has already swiped right on you, a match overlay appears immediately. Both users are now locked into this conversation.
  5. Chat β€” messages appear in real time on both ends. 500-character limit per message.
  6. Unmatch β€” tap the unmatch button in the chat header to clear the match and return to discovery.

Pro tip: If the swipe deck shows "No more profiles," it means either everyone in your Firestore has been swiped or there are no other onboardingCompleted: true users. Run the seed script (node scripts/seed-profiles.mjs) against your dev project to repopulate.


πŸ“ Project Structure

blindly/
β”œβ”€β”€ 🌐 public/
β”‚   β”œβ”€β”€ hero-banner.svg          # SVG displayed in browser tab / OG
β”‚   └── sparkle-icon.svg         # Favicon
β”‚
β”œβ”€β”€ πŸ–Ό assets/                   # README diagram SVGs (not served)
β”‚   β”œβ”€β”€ hero-banner.svg
β”‚   β”œβ”€β”€ architecture.svg
β”‚   β”œβ”€β”€ data-flow.svg
β”‚   β”œβ”€β”€ capabilities.svg
β”‚   └── stats.svg
β”‚
β”œβ”€β”€ πŸ“¦ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ onboarding/
β”‚   β”‚   β”‚   β”œβ”€β”€ HandleStep.jsx       # Step 1: pick a unique handle
β”‚   β”‚   β”‚   β”œβ”€β”€ BasicInfoStep.jsx    # Step 2: birth year, gender, city
β”‚   β”‚   β”‚   β”œβ”€β”€ TaglineStep.jsx      # Step 3: one-line profile tagline
β”‚   β”‚   β”‚   β”œβ”€β”€ TagsStep.jsx         # Step 4: pick up to N interest tags
β”‚   β”‚   β”‚   β”œβ”€β”€ PromptsStep.jsx      # Step 5: answer personal prompts
β”‚   β”‚   β”‚   └── ReviewStep.jsx       # Step 6: review + single Firestore write
β”‚   β”‚   β”œβ”€β”€ SwipeDeck.jsx            # Framer Motion swipe + match transaction
β”‚   β”‚   β”œβ”€β”€ ProfileCard.jsx          # Text-only profile card component
β”‚   β”‚   β”œβ”€β”€ ChatView.jsx             # Real-time message stream + input
β”‚   β”‚   β”œβ”€β”€ ProfileView.jsx          # Full profile modal overlay
β”‚   β”‚   β”œβ”€β”€ BottomNav.jsx            # Bottom tab navigation bar
β”‚   β”‚   β”œβ”€β”€ AuthForm.jsx             # Combined login / sign-up form
β”‚   β”‚   β”œβ”€β”€ OnboardingGate.jsx       # Redirect guard: onboardingCompleted?
β”‚   β”‚   └── ProtectedRoute.jsx       # Redirect guard: authenticated?
β”‚   β”‚
β”‚   β”œβ”€β”€ context/
β”‚   β”‚   β”œβ”€β”€ AuthContext.jsx          # onAuthStateChanged + userDoc onSnapshot
β”‚   β”‚   └── ThemeContext.jsx         # data-theme attribute toggle
β”‚   β”‚
β”‚   β”œβ”€β”€ layouts/
β”‚   β”‚   └── AppLayout.jsx            # Shared header + Outlet for /app/*
β”‚   β”‚
β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”œβ”€β”€ Home.jsx                 # Public landing page
β”‚   β”‚   β”œβ”€β”€ Auth.jsx                 # Login / sign-up page
β”‚   β”‚   β”œβ”€β”€ Onboarding.jsx           # 6-step onboarding coordinator
β”‚   β”‚   └── Dashboard.jsx            # Main app: deck, chat, match overlay
β”‚   β”‚
β”‚   β”œβ”€β”€ App.jsx                      # BrowserRouter + Routes definition
β”‚   β”œβ”€β”€ main.jsx                     # ReactDOM.createRoot entry point
β”‚   β”œβ”€β”€ constants.js                 # AVAILABLE_TAGS, AVAILABLE_PROMPTS arrays
β”‚   β”œβ”€β”€ firebase.js                  # SDK init, exports db and auth
β”‚   └── index.css                    # All styles via CSS custom properties
β”‚
β”œβ”€β”€ βš™οΈ  scripts/
β”‚   └── seed-profiles.mjs            # Dev-only: write test user documents
β”‚
β”œβ”€β”€ firestore.rules                  # Server-side security rules
β”œβ”€β”€ firebase.json                    # Firebase CLI project config
β”œβ”€β”€ vite.config.js                   # Vite + React plugin config
└── package.json

πŸ“Š Performance Stats

blindly stats


πŸ”’ Privacy

Blindly collects the minimum data required to function:

  • No photo storage β€” The Firestore schema has no image fields. Firebase Storage is not enabled. Appearance is structurally excluded from the system.
  • No real name β€” The profile has a handle, not a legal name.
  • No precise location β€” Approximate city is an optional text field. GPS is never requested.
  • Swipe history stays in your own users/{uid}/swipes/ subcollection. Other users can't read it.
  • Chat messages are plain text only. No files, images, or link previews.
  • Auth tokens are short-lived JWTs managed by the Firebase SDK. Passwords are hashed by Firebase (bcrypt). Blindly never touches your raw password.
  • No analytics, no ads, no third-party tracking scripts.

See wiki/Privacy.md for the full privacy model and known limitations.


πŸ—Ί Roadmap

Core

  • Text-only profile creation (6-step onboarding)
  • Physics swipe deck (Framer Motion)
  • Atomic match detection via Firestore transaction
  • Real-time chat (onSnapshot)
  • Unmatch flow
  • Dark / light theme (AMOLED)

Trust & Safety

  • Cloud Function for symmetric unmatch cleanup (delete match doc + both currentMatchIds)
  • Report / block users (write-only reports collection)
  • Field-level Firestore rules (lock trustLevel, onboardingCompleted)
  • Restrict match/message reads to participants only

Product

  • Profile editing after onboarding
  • Push notifications on match and new message (Firebase Cloud Messaging)
  • Conversation time-boxing (24-hour chat windows)
  • Candidate filtering (age range, proximity)
  • Account deletion self-service UI

πŸ“¦ Packaging

# Production build (output to dist/)
npm run build

# Preview the production build locally
npm run preview

# Deploy to GitHub Pages
npm run deploy

npm run deploy runs vite build first (via predeploy), then uses gh-pages to push dist/ to the gh-pages branch of the repository.


🀝 Contributing

Fork the repo, create a feat/* or fix/* branch, and open a PR against main. There's no automated test suite β€” use the manual testing checklist in wiki/Contributing.md.


πŸ›‘ Security

Found a vulnerability? Open a GitHub Issue with [Security] in the title, or contact the maintainer directly. Don't post exploits in public. See wiki/Privacy.md for known limitations in the current security posture.


πŸ“„ License

MIT β€” see LICENSE.

Built with πŸ’œ by Kaelith69

About

Text-only dating. No pics. No performative peacocking. One active match at a time to keep you honest.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors