AI LLM with Gemini, MiniMax, Replicate, OpenRouter. Vision, search, code review. USDC on Base.
A premium portfolio website for Kyle Touchet (@Sandemon) - developer, creative technologist, and AI enthusiast.
| Category | Technology |
|---|---|
| Frontend | React 19 + Vite 7 |
| Styling | Tailwind CSS 3.4 |
| Animation | Framer Motion 12 |
| Routing | React Router DOM 7 |
| Backend | Firebase (Firestore, Auth, Storage, Functions) |
| Web3 | OnchainKit, Wagmi, viem |
| Payments | Stripe |
/) - Landing page with Kyle's bio and title/gallery) - 8-category portfolio showcase/about) - About page/contact) - Contact information/pricing) - Credit purchase (accessible via user dropdown)/profile) - User profile and credits balance/admin) - Site management (owner only)/admin/media) - Media upload and category management (owner only)# Clone the repository
git clone https://github.com/TheSandemon/sand-gallery.git
cd sand-gallery
# Install dependencies
npm install
# Create .env file with your Firebase config
# See .env.example or Firebase consoleCreate a .env file with:
VITE_FIREBASE_API_KEY=your_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
VITE_FIREBASE_APP_ID=your_app_id
VITE_FIREBASE_VAPID_KEY=your_vapid_key
VITE_STRIPE_PUBLIC_KEY=your_stripe_key
VITE_WALLET_CONNECT_PROJECT_ID=your_wallet_connect_id# Start development server
npm run dev
# Build for production
npm run buildsand-gallery/
├── src/
│ ├── components/
│ │ ├── admin/ # MediaUploader, CategoryManager
│ │ ├── cms/ # DynamicRenderer, PricingGrid, GalleryGrid
│ │ ├── gallery/ # MasonryGrid, MediaCard, GalleryExplorer
│ │ ├── tools/ # AudioGenerator
│ │ ├── Navbar.jsx # Navigation (hardcoded whitelist)
│ │ ├── Footer.jsx # Dynamic versioning
│ │ ├── UserButton.jsx # Avatar + dropdown
│ │ └── ...
│ ├── pages/
│ │ ├── Home.jsx # Landing page
│ │ ├── Gallery.jsx # 8-category gallery
│ │ ├── AdminMedia.jsx # Media management
│ │ └── ...
│ ├── hooks/
│ │ ├── useSiteSettings.js
│ │ └── ...
│ ├── context/
│ │ ├── AuthContext.jsx
│ │ └── Web3Provider.jsx
│ └── ...
├── .env # Environment variables
├── firebase.json # Firebase config
├── firestore.rules # Database rules
└── package.jsonThe navigation bar uses a hardcoded whitelist in Navbar.jsx to prevent flickering issues that occurred when loading from Firestore. The current navigation is:
/ (landing page)/gallery/about/contact/admin (owner only)PRICING is NOT in the main nav - it can only be accessed via the credits button in the user dropdown menu.
The footer displays a dynamic unique version timestamp (vYYYYMMDD-HHMMSS) that changes on every page load. This ensures complete uniqueness for debugging purposes.
Gallery categories are hardcoded (8 categories), but the items within each category are fetched from Firestore at runtime. Use the Admin Media page to upload and assign content to categories.
| Collection | Purpose |
|---|---|
users/{uid} | User profiles, credits, roles |
gallery_categories/{id} | Category items (games, apps, etc.) |
media/{id} | Uploaded media files |
config/site_settings | Site configuration |
Firestore Nav Flicker: Historically, nav links loaded from Firestore caused visual flicker. This was resolved by hardcoding the navigation in Navbar.jsx.
Legacy Features: The /studio and /crm routes exist but are legacy AI generation features not currently in active use.
Admin Access: Only users with role === 'owner' can access admin features. Role is set in Firestore under users/{uid}/role.
# Deploy to production
firebase deploy
# Deploy to preview channel
firebase hosting:channel:deployThe repository includes GitHub Actions workflows that:
Private - All rights reserved.
Kyle Touchet (@Sandemon)
See also: