ShootCircle is a creative talent platform that connects models, photographers, makeup artists, stylists, and producers in one place. Users can showcase their portfolios, find new projects, collaborate with other talents, and grow their creative careers.
Mission: Drive connections between creative talents and real opportunities, building a global community of visual artists.
- Framework: SvelteKit (latest stable version)
- Language: TypeScript
- Styling: Tailwind CSS
- UI Language: Spanish
- Code Language: English
- Authentication: Firebase Auth
- Database: Firestore
- Storage: Firebase Storage
- Real-time: Firestore real-time listeners
- Hosting: Vercel (recommended) or Firebase Hosting
- Package Manager: pnpm (or npm)
- Code Editor: Cursor
- Version Control: Git
| Component | Technology | URL | Purpose |
|---|---|---|---|
| Marketing Site | WordPress | https://shootcircle.com |
Landing, blog, SEO |
| Web App | SvelteKit + Firebase | https://app.shootcircle.com |
Main platform |
shootcircle-app/
├── src/
│ ├── routes/
│ │ ├── +layout.svelte
│ │ ├── +layout.ts
│ │ ├── +page.svelte # Landing/Redirect
│ │ │
│ │ ├── auth/
│ │ │ ├── login/
│ │ │ │ └── +page.svelte # Login page
│ │ │ ├── signup/
│ │ │ │ └── +page.svelte # Registration
│ │ │ ├── verify-email/
│ │ │ │ └── +page.svelte # Email verification
│ │ │ └── reset-password/
│ │ │ └── +page.svelte # Password reset
│ │ │
│ │ ├── dashboard/
│ │ │ └── +page.svelte # User dashboard
│ │ │
│ │ ├── explore/
│ │ │ └── +page.svelte # Browse talents
│ │ │
│ │ ├── u/
│ │ │ └── [username]/
│ │ │ └── +page.svelte # Public profile
│ │ │
│ │ ├── settings/
│ │ │ ├── profile/
│ │ │ │ └── +page.svelte # Edit profile
│ │ │ ├── account/
│ │ │ │ └── +page.svelte # Account settings
│ │ │ └── privacy/
│ │ │ └── +page.svelte # Privacy settings
│ │ │
│ │ ├── projects/
│ │ │ ├── +page.svelte # Projects list
│ │ │ ├── new/
│ │ │ │ └── +page.svelte # Create project
│ │ │ └── [id]/
│ │ │ └── +page.svelte # Project details
│ │ │
│ │ ├── messages/
│ │ │ ├── +page.svelte # Messages inbox
│ │ │ └── [chatId]/
│ │ │ └── +page.svelte # Chat conversation
│ │ │
│ │ ├── terms/
│ │ │ └── +page.svelte # Terms of service
│ │ ├── privacy/
│ │ │ └── +page.svelte # Privacy policy
│ │ └── +error.svelte # Error page
│ │
│ ├── lib/
│ │ ├── components/
│ │ │ ├── ui/
│ │ │ │ ├── Button.svelte
│ │ │ │ ├── Input.svelte
│ │ │ │ ├── Card.svelte
│ │ │ │ ├── Avatar.svelte
│ │ │ │ ├── Modal.svelte
│ │ │ │ └── Dropdown.svelte
│ │ │ ├── layout/
│ │ │ │ ├── Navbar.svelte
│ │ │ │ ├── Sidebar.svelte
│ │ │ │ └── Footer.svelte
│ │ │ ├── profile/
│ │ │ │ ├── ProfileCard.svelte
│ │ │ │ ├── PortfolioGallery.svelte
│ │ │ │ └── ProfileStats.svelte
│ │ │ ├── projects/
│ │ │ │ ├── ProjectCard.svelte
│ │ │ │ └── ProjectForm.svelte
│ │ │ └── messages/
│ │ │ ├── ChatList.svelte
│ │ │ ├── ChatMessage.svelte
│ │ │ └── MessageInput.svelte
│ │ │
│ │ ├── stores/
│ │ │ ├── auth.ts # Auth state
│ │ │ ├── user.ts # User data
│ │ │ └── notifications.ts # Notifications
│ │ │
│ │ ├── firebase/
│ │ │ ├── config.ts # Firebase configuration
│ │ │ ├── auth.ts # Auth helpers
│ │ │ ├── firestore.ts # Firestore helpers
│ │ │ └── storage.ts # Storage helpers
│ │ │
│ │ ├── utils/
│ │ │ ├── validators.ts # Form validation
│ │ │ ├── formatters.ts # Date, text formatters
│ │ │ └── constants.ts # App constants
│ │ │
│ │ └── types/
│ │ ├── user.ts # User types
│ │ ├── project.ts # Project types
│ │ └── message.ts # Message types
│ │
│ ├── app.html
│ └── app.css
│
├── static/
│ ├── images/
│ ├── icons/
│ └── fonts/
│
├── .env.example
├── .gitignore
├── package.json
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts
{
uid: string;
username: string;
email: string;
displayName: string;
photoURL: string;
bio: string;
role: 'model' | 'photographer' | 'makeup_artist' | 'stylist' | 'producer';
location: {
city: string;
country: string;
};
socialLinks: {
instagram?: string;
website?: string;
behance?: string;
};
portfolio: string[]; // Array of image URLs
followers: number;
following: number;
isVerified: boolean;
createdAt: Timestamp;
updatedAt: Timestamp;
}{
id: string;
title: string;
description: string;
creatorId: string;
creatorName: string;
type: 'photoshoot' | 'video' | 'campaign' | 'editorial';
status: 'open' | 'in_progress' | 'completed' | 'cancelled';
lookingFor: string[]; // ['model', 'photographer', etc.]
location: string;
date: Timestamp;
budget?: string;
participants: {
userId: string;
role: string;
}[];
images: string[];
createdAt: Timestamp;
updatedAt: Timestamp;
}{
id: string;
participants: string[]; // Array of user IDs
lastMessage: string;
lastMessageTime: Timestamp;
unreadCount: {
[userId: string]: number;
};
createdAt: Timestamp;
}{
id: string;
chatId: string;
senderId: string;
senderName: string;
senderPhoto: string;
content: string;
type: 'text' | 'image' | 'file';
fileURL?: string;
isRead: boolean;
createdAt: Timestamp;
}users/
{userId}/
profile-photo.jpg
portfolio/
image-1.jpg
image-2.jpg
...
projects/
{projectId}/
cover.jpg
gallery/
image-1.jpg
...
messages/
{chatId}/
{messageId}/
attachment.jpg
- Hero section with value proposition
- Features overview
- Sample profiles showcase
- CTA buttons: "Registrarse" / "Explorar talentos"
- Grid of user profiles
- Filters: role, location, availability
- Search by name or username
- No login required (basic view)
- Login: Email/password + Google OAuth
- Signup: Multi-step form
- Step 1: Email, password, name
- Step 2: Choose role (model, photographer, etc.)
- Step 3: Basic profile info
- Email verification
- Password reset
- Activity feed
- Recent messages preview
- Active projects
- Profile stats (views, followers)
- Quick actions
- Public profile view
- Portfolio gallery (masonry/grid layout)
- Bio and social links
- Action buttons:
- "Seguir" (Follow)
- "Enviar mensaje" (Send message)
- "Colaborar" (Collaborate)
- Stats: followers, following, projects
- Profile: Edit name, bio, photo, portfolio
- Account: Change email, password
- Privacy: Profile visibility, blocked users
- List: Browse all projects (open, in progress, completed)
- Create: Form to create new project
- Details: View project info, participants, gallery
- Apply: Button to join project
- Inbox: List of conversations
- Chat: Real-time messaging
- Attachments: Send images/files
- Online status indicators
// lib/firebase/auth.ts
export async function signUp(email: string, password: string, displayName: string) {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
await updateProfile(userCredential.user, { displayName });
await sendEmailVerification(userCredential.user);
return userCredential.user;
}
export async function signIn(email: string, password: string) {
return await signInWithEmailAndPassword(auth, email, password);
}
export async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
return await signInWithPopup(auth, provider);
}
export async function signOut() {
return await firebaseSignOut(auth);
}// lib/firebase/firestore.ts
export function subscribeToMessages(chatId: string, callback: (messages: Message[]) => void) {
const messagesRef = collection(db, 'messages');
const q = query(messagesRef, where('chatId', '==', chatId), orderBy('createdAt', 'asc'));
return onSnapshot(q, (snapshot) => {
const messages = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
})) as Message[];
callback(messages);
});
}// lib/firebase/storage.ts
export async function uploadProfilePhoto(userId: string, file: File): Promise<string> {
const storageRef = ref(storage, `users/${userId}/profile-photo.jpg`);
await uploadBytes(storageRef, file);
return await getDownloadURL(storageRef);
}
export async function uploadPortfolioImage(userId: string, file: File): Promise<string> {
const timestamp = Date.now();
const storageRef = ref(storage, `users/${userId}/portfolio/${timestamp}-${file.name}`);
await uploadBytes(storageRef, file);
return await getDownloadURL(storageRef);
}// lib/firebase/firestore.ts
export async function searchUsers(filters: {
role?: string;
location?: string;
searchTerm?: string;
}) {
let q = query(collection(db, 'users'));
if (filters.role) {
q = query(q, where('role', '==', filters.role));
}
if (filters.location) {
q = query(q, where('location.city', '==', filters.location));
}
const snapshot = await getDocs(q);
return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
}All user-facing text must be in Spanish:
- Buttons: "Registrarse", "Iniciar sesión", "Enviar", "Guardar"
- Labels: "Nombre", "Correo electrónico", "Contraseña"
- Navigation: "Inicio", "Explorar", "Proyectos", "Mensajes", "Perfil"
- Messages: "Bienvenido a ShootCircle", "Perfil actualizado correctamente"
All code, variables, functions, and comments must be in English:
// ✅ Correct
const userName = user.displayName;
async function updateUserProfile() {}
// ❌ Incorrect
const nombreUsuario = usuario.nombreCompleto;
async function actualizarPerfilUsuario() {}- Primary Color: #FF6B35 (creative orange)
- Secondary Color: #004E89 (professional blue)
- Accent: #F7B801 (energy yellow)
- Neutral: #F5F5F5, #E0E0E0, #333333
- Success: #4CAF50
- Error: #F44336
- Headings: Inter or Poppins (700)
- Body: Inter (400, 500)
- UI: System font stack
- Use TypeScript for all logic
- Component names in PascalCase
- File names in kebab-case
- Use Svelte stores for global state
- Implement proper error handling
- Add loading states for async operations
- Use Tailwind utility classes (avoid custom CSS when possible)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if true;
allow write: if request.auth != null && request.auth.uid == userId;
}
match /projects/{projectId} {
allow read: if true;
allow create: if request.auth != null;
allow update, delete: if request.auth != null &&
request.auth.uid == resource.data.creatorId;
}
match /messages/{messageId} {
allow read, write: if request.auth != null &&
request.auth.uid in get(/databases/$(database)/documents/chats/$(resource.data.chatId)).data.participants;
}
}
}# .env.example
PUBLIC_FIREBASE_API_KEY=
PUBLIC_FIREBASE_AUTH_DOMAIN=
PUBLIC_FIREBASE_PROJECT_ID=
PUBLIC_FIREBASE_STORAGE_BUCKET=
PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
PUBLIC_FIREBASE_APP_ID=# Install dependencies
pnpm install
# Development server
pnpm dev
# Build for production
pnpm build
# Preview production build
pnpm preview- Connect GitHub repository
- Set environment variables
- Build command:
pnpm build - Output directory:
.svelte-kit - Deploy to
app.shootcircle.com
- Authentication (email + Google)
- User profile creation
- Portfolio upload
- Basic profile view
- Explore page with filters
- Search functionality
- Follow system
- Profile stats
- Projects creation
- Projects listing
- Apply to projects
- Real-time chat
- Notifications
- Email notifications
- Advanced search
- Analytics dashboard
# Generate authentication pages
"Create auth pages with login, signup, and password reset following the Firebase auth pattern"
# Create profile component
"Build a ProfileCard component that displays user info, portfolio, and action buttons in Spanish"
# Implement chat
"Create a real-time chat system using Firestore with message list and input components"
# Build explore page
"Design an explore page with user grid, filters by role and location, and search bar"
- Auth flow works (signup, login, logout)
- Profile CRUD operations
- Image upload to Storage
- Real-time chat updates
- Search and filters work
- Responsive design (mobile, tablet, desktop)
- Spanish text throughout UI
- Error handling and loading states
Ready to build! 🚀
This spec should give you everything needed to start development in Cursor. Focus on Phase 1 first, then iterate.