Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.3.0",
"autoprefixer": "^10.5.0",
"clsx": "^2.1.1",
"lucide-svelte": "^1.0.1",
"postcss": "^8.5.14",
"svelte": "^5.51.0",
"svelte-check": "^4.4.2",
"tailwind-merge": "^3.6.0",
"tailwindcss": "^4.3.0",
"typescript": "^5.9.3",
"vite": "^7.3.1"
}
Expand Down
271 changes: 188 additions & 83 deletions apps/web/src/app.css
Original file line number Diff line number Diff line change
@@ -1,106 +1,211 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@500;600;700;800;900&display=swap');
@import "tailwindcss";

:root {
/* Primary Palette */
--primary: #6366f1;
--primary-glow: rgba(99, 102, 241, 0.5);
--accent: #a855f7;
--accent-glow: rgba(168, 85, 247, 0.4);
@theme {
--color-primary: #7C3AED;
--color-secondary: #06B6D4;
--color-accent-pink: #EC4899;
--color-accent-blue: #3B82F6;
--color-dark-950: #030712;
--color-dark-900: #0B1020;

/* Backgrounds */
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-glass: rgba(255, 255, 255, 0.7);
--bg-card: #ffffff;
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--font-display: "Space Grotesk", sans-serif;

/* Text */
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;

/* Effects */
--border: rgba(226, 232, 240, 0.8);
--border-glass: rgba(255, 255, 255, 0.3);
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);

--radius: 12px;
--radius-lg: 20px;
--radius-xl: 32px;

--theme-transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
--radius-xl: 1.5rem;
--radius-2xl: 2rem;
--radius-3xl: 3rem;
}

html.dark {
--bg-primary: #020617;
--bg-secondary: #0f172a;
--bg-glass: rgba(15, 23, 42, 0.6);
--bg-card: #0f172a;

--text-primary: #f8fafc;
--text-secondary: #cbd5e1;
--text-muted: #64748b;

--border: rgba(30, 41, 59, 0.8);
--border-glass: rgba(255, 255, 255, 0.1);

@layer base {
:root {
--bg-main: #f8fafc;
--bg-secondary: #ffffff;
--text-main: #0f172a;
--text-muted: #64748b;
--border-main: #e2e8f0;
--glass-bg: rgba(255, 255, 255, 0.75);
--glass-border: rgba(15, 23, 42, 0.08);
--primary-glow: rgba(124, 58, 237, 0.2);
--btn-secondary-bg: rgba(124, 58, 237, 0.05);

/* Legacy/Upstream compatibility variables */
--primary: #7C3AED; /* Premium theme color */
--accent: #EC4899; /* Premium accent color */
--primary-glow: rgba(124, 58, 237, 0.2);
--accent-glow: rgba(236, 72, 153, 0.2);
--bg-primary: var(--bg-main);
--bg-glass: var(--glass-bg);
--bg-card: var(--bg-secondary);
--text-primary: var(--text-main);
--text-secondary: var(--text-muted);
--border: var(--border-main);
--border-glass: var(--glass-border);
--theme-transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}

.dark {
--bg-main: #030712;
--bg-secondary: #0B1020;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--border-main: #1f2937;
--glass-bg: rgba(3, 7, 18, 0.6);
--glass-border: rgba(255, 255, 255, 0.08);
--primary-glow: rgba(124, 58, 237, 0.3);
--btn-secondary-bg: rgba(255, 255, 255, 0.05);

/* Legacy/Upstream compatibility variables */
--primary: #7C3AED;
--accent: #EC4899;
--primary-glow: rgba(124, 58, 237, 0.3);
--accent-glow: rgba(236, 72, 153, 0.3);
--bg-primary: var(--bg-main);
--bg-glass: var(--glass-bg);
--bg-card: var(--bg-secondary);
--text-primary: var(--text-main);
--text-secondary: var(--text-muted);
--border: var(--border-main);
--border-glass: var(--glass-border);
}

body {
@apply bg-(--bg-main) text-(--text-main) transition-colors duration-500 antialiased font-sans overflow-x-hidden;
font-family: 'Inter', sans-serif;
}

h1, h2, h3, h4, h5, h6 {
font-family: 'Outfit', sans-serif;
font-weight: 700;
line-height: 1.1;
}
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
@utility glass {
backdrop-filter: blur(20px) saturate(180%);
background-color: var(--glass-bg);
border: 1px solid var(--glass-border);
}

body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
transition: var(--theme-transition);
-webkit-font-smoothing: antialiased;
min-height: 100vh;
overflow-x: hidden;
@utility glass-morphic {
@apply glass shadow-[0_8px_32px_0_rgba(31,38,135,0.07)];
}

h1, h2, h3, h4, h5, h6 {
font-family: 'Outfit', sans-serif;
font-weight: 700;
line-height: 1.1;
@utility text-gradient {
@apply bg-clip-text text-transparent bg-linear-to-r from-primary via-secondary to-accent-pink;
}

a {
color: inherit;
text-decoration: none;
transition: var(--theme-transition);
@layer components {
.mesh-bg {
@apply absolute inset-0 -z-20 opacity-40;
background-image:
radial-gradient(at 0% 0%, rgba(124, 58, 237, 0.15) 0px, transparent 50%),
radial-gradient(at 100% 0%, rgba(6, 182, 212, 0.15) 0px, transparent 50%),
radial-gradient(at 100% 100%, rgba(236, 72, 153, 0.15) 0px, transparent 50%),
radial-gradient(at 0% 100%, rgba(59, 130, 246, 0.15) 0px, transparent 50%);
filter: blur(100px);
animation: mesh 20s ease infinite;
}

.card-cinematic {
@apply glass-morphic rounded-3xl p-8 transition-all duration-700 hover:-translate-y-2 hover:shadow-2xl hover:shadow-primary/20 relative overflow-hidden;
}

.card-premium {
@apply glass-morphic rounded-3xl transition-all duration-700 relative overflow-hidden;
}

.btn-premium-primary {
@apply relative inline-flex items-center justify-center rounded-2xl font-black uppercase tracking-widest text-white transition-all duration-500 overflow-hidden;
background: linear-gradient(135deg, var(--color-primary), var(--color-accent-pink));
box-shadow: 0 4px 15px rgba(124, 58, 237, 0.4);
}
.btn-premium-primary:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 8px 25px rgba(124, 58, 237, 0.6);
}

.btn-premium-secondary {
@apply relative inline-flex items-center justify-center rounded-2xl font-black uppercase tracking-widest transition-all duration-500 overflow-hidden;
background: var(--btn-secondary-bg);
border: 1px solid rgba(124, 58, 237, 0.3);
color: var(--color-primary);
backdrop-filter: blur(10px);
}
.btn-premium-secondary:hover {
transform: translateY(-2px) scale(1.02);
background: rgba(124, 58, 237, 0.1);
border-color: rgba(124, 58, 237, 0.6);
}

/* Upstream compatibility */
.gradient-text {
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}

.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--accent));
color: white;
padding: 0.8rem 1.6rem;
border-radius: var(--radius, 12px);
font-weight: 600;
box-shadow: 0 4px 15px var(--primary-glow);
border: none;
cursor: pointer;
transition: var(--theme-transition);
}

.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px var(--primary-glow);
}
}

@layer utilities {
.animate-mesh {
animation: mesh 20s ease infinite;
}

.animate-float {
animation: float 6s ease-in-out infinite;
}

.animate-pulse-glow {
animation: pulse-glow 4s ease-in-out infinite;
}
}

.glass {
background: var(--bg-glass);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--border-glass);
@keyframes mesh {
0%, 100% { background-position: 0% 50%; transform: scale(1); }
50% { background-position: 100% 50%; transform: scale(1.1); }
}

.gradient-text {
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
@keyframes float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-20px) rotate(2deg); }
}

.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--accent));
color: white;
padding: 0.8rem 1.6rem;
border-radius: var(--radius);
font-weight: 600;
box-shadow: 0 4px 15px var(--primary-glow);
border: none;
cursor: pointer;
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 20px rgba(124, 58, 237, 0.2); }
50% { box-shadow: 0 0 40px rgba(124, 58, 237, 0.4); }
}

.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px var(--primary-glow);
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
@apply bg-transparent;
}
::-webkit-scrollbar-thumb {
@apply bg-primary/30 rounded-full hover:bg-primary/50 transition-colors;
}

/* Fluid Typography Helpers */
.text-fluid-h1 { font-size: clamp(2.5rem, 8vw, 6rem); }
.text-fluid-h2 { font-size: clamp(2rem, 5vw, 4rem); }
.text-fluid-p { font-size: clamp(1rem, 2vw, 1.25rem); }
7 changes: 6 additions & 1 deletion apps/web/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
declare global {
namespace App {
// interface Error {}
// interface Locals {}
interface Locals {
user?: {
id: string;
username: string;
}
}
// interface PageData {}
// interface PageState {}
// interface Platform {}
Expand Down
21 changes: 21 additions & 0 deletions apps/web/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
const token = event.cookies.get('token');

if (token) {
try {
// Call backend to verify token and get user info
const apiUrl = import.meta.env.PUBLIC_API_URL || process.env.BACKEND_URL || 'http://localhost:3000';
const res = await event.fetch(`${apiUrl}/api/profiles/me`);

if (res.ok) {
event.locals.user = await res.json();
} else {
event.cookies.delete('token', { path: '/' });
event.locals.user = undefined;
}
} catch (err) {
console.error('Auth verification failed:', err);
// Optional: fallback to mock for UI development if backend is not running
// event.locals.user = { id: 'mock-id', username: 'dev-user' };
}
}

const response = await resolve(event);

// Security Headers (Note: CSP is handled in svelte.config.js)
Expand Down
26 changes: 26 additions & 0 deletions apps/web/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const BASE_URL = import.meta.env.PUBLIC_API_URL || (typeof process !== 'undefined' && process.env?.BACKEND_URL) || 'http://localhost:3000';


export async function fetchWithAuth(path: string, options: RequestInit = {}) {
// In SvelteKit, we should handle cookies properly
// For client-side calls, the browser sends the cookie automatically
const res = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});

if (!res.ok) {
throw new Error(`API Error: ${res.statusText}`);
}

return res.json();
}

export const api = {
getMe: () => fetchWithAuth('/api/profiles/me'),
getCards: () => fetchWithAuth('/api/cards'),
getProfiles: (username: string) => fetchWithAuth(`/api/u/${username}`),
};
Loading