Align ExposedGays theme with Pink Pulse colors, typography, and pill buttons

This commit is contained in:
Ryan Salazar
2026-06-26 21:58:29 -04:00
parent 76834b3d21
commit 9604a18f7c
20 changed files with 236 additions and 119 deletions

View File

@@ -4,8 +4,8 @@
"description": "Free gay cruising map, chat & shop", "description": "Free gay cruising map, chat & shop",
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "standalone",
"background_color": "#0d0a0f", "background_color": "#08001C",
"theme_color": "#ff2d6b", "theme_color": "#F92B9B",
"orientation": "any", "orientation": "any",
"icons": [ "icons": [
{ {

View File

@@ -4,25 +4,25 @@
@layer base { @layer base {
:root { :root {
--background: 280 30% 6%; --background: 265 100% 5%;
--foreground: 0 0% 98%; --foreground: 228 100% 97%;
--card: 280 25% 10%; --card: 268 100% 11%;
--card-foreground: 0 0% 98%; --card-foreground: 228 100% 97%;
--popover: 280 25% 10%; --popover: 268 100% 11%;
--popover-foreground: 0 0% 98%; --popover-foreground: 228 100% 97%;
--primary: 340 100% 59%; --primary: 330 94% 57%;
--primary-foreground: 0 0% 100%; --primary-foreground: 0 0% 100%;
--secondary: 280 40% 18%; --secondary: 268 55% 16%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 228 100% 97%;
--muted: 280 20% 16%; --muted: 268 35% 14%;
--muted-foreground: 280 10% 65%; --muted-foreground: 228 12% 60%;
--accent: 280 60% 45%; --accent: 280 72% 47%;
--accent-foreground: 0 0% 100%; --accent-foreground: 0 0% 100%;
--destructive: 0 84% 60%; --destructive: 0 84% 60%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 280 20% 20%; --border: 220 38% 20%;
--input: 280 20% 20%; --input: 220 38% 20%;
--ring: 340 100% 59%; --ring: 330 94% 57%;
--radius: 0.75rem; --radius: 0.75rem;
} }
} }
@@ -37,17 +37,59 @@
} }
} }
/* Leaflet dark theme overrides */ @layer components {
.gradient-text {
@apply bg-pulse-gradient bg-clip-text text-transparent;
}
.glass-card {
@apply rounded-xl border border-white/[0.06] bg-pulse-midnight/70 backdrop-blur-md;
transition: background 0.2s, border-color 0.2s;
}
.glass-card:hover {
@apply border-primary/25 bg-pulse-glow/40;
}
.pulse-pill {
@apply inline-flex items-center justify-center rounded-full px-4 py-2 text-sm font-bold transition-all touch-manipulation;
}
.pulse-pill-active {
@apply text-white shadow-pulse-glow;
background: linear-gradient(135deg, #ff2d9b, #9b30ff);
}
.pulse-pill-inactive {
color: #8b92a8;
background: rgba(30, 45, 74, 0.4);
border: 1px solid rgba(155, 48, 255, 0.3);
}
.pulse-pill-inactive:hover {
color: #ff6ec7;
border-color: rgba(255, 45, 155, 0.35);
background: rgba(255, 45, 155, 0.08);
}
.pulse-pill-tag {
@apply text-xs font-bold px-3 py-1 rounded-full;
color: #ff6ec7;
background: rgba(255, 45, 155, 0.15);
border: 1px solid rgba(255, 45, 155, 0.3);
}
}
/* Leaflet — Pink Pulse dark palette */
.leaflet-container { .leaflet-container {
background: #0a1628 !important; background: #08001c !important;
font-family: inherit; font-family: inherit;
} }
.sniffies-map .leaflet-tile-pane { .sniffies-map .leaflet-tile-pane {
filter: saturate(0.85) brightness(0.95); filter: saturate(0.9) brightness(0.92) hue-rotate(-8deg);
} }
/* Sniffies-style photo markers */
.sniffies-marker-wrap, .sniffies-marker-wrap,
.sniffies-you-wrap, .sniffies-you-wrap,
.sniffies-spot-wrap { .sniffies-spot-wrap {
@@ -68,15 +110,15 @@
width: 44px; width: 44px;
height: 44px; height: 44px;
border-radius: 50%; border-radius: 50%;
border: 2px solid #3b82f6; border: 2px solid #f92b9b;
padding: 2px; padding: 2px;
background: #0a1628; background: #08001c;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
} }
.sniffies-avatar-ring.sniffies-online { .sniffies-avatar-ring.sniffies-online {
border-color: #3b82f6; border-color: #f92b9b;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.35); box-shadow: 0 0 0 2px rgba(249, 43, 155, 0.35);
} }
.sniffies-avatar { .sniffies-avatar {
@@ -93,7 +135,7 @@
height: 16px; height: 16px;
padding: 0 4px; padding: 0 4px;
border-radius: 8px; border-radius: 8px;
background: #f97316; background: linear-gradient(135deg, #ff2d9b, #c084fc);
color: white; color: white;
font-size: 10px; font-size: 10px;
font-weight: 700; font-weight: 700;
@@ -113,9 +155,9 @@
width: 40px; width: 40px;
height: 40px; height: 40px;
border-radius: 50%; border-radius: 50%;
background: #ef4444; background: #f92b9b;
border: 3px solid white; border: 3px solid white;
box-shadow: 0 2px 12px rgba(239, 68, 68, 0.6); box-shadow: 0 2px 12px rgba(249, 43, 155, 0.6);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -133,7 +175,7 @@
.sniffies-you-label { .sniffies-you-label {
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
color: #3b82f6; color: #ff6ec7;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
} }
@@ -142,8 +184,8 @@
width: 36px; width: 36px;
height: 36px; height: 36px;
border-radius: 50%; border-radius: 50%;
background: rgba(10, 22, 40, 0.9); background: rgba(26, 0, 58, 0.95);
border: 2px solid #3b82f6; border: 2px solid #8b22cc;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -158,7 +200,7 @@
min-width: 16px; min-width: 16px;
height: 16px; height: 16px;
border-radius: 8px; border-radius: 8px;
background: #3b82f6; background: linear-gradient(135deg, #ff2d9b, #8b22cc);
color: white; color: white;
font-size: 9px; font-size: 9px;
font-weight: 700; font-weight: 700;
@@ -166,91 +208,114 @@
text-align: center; text-align: center;
padding: 0 3px; padding: 0 3px;
} }
.leaflet-popup-content-wrapper { .leaflet-popup-content-wrapper {
background: hsl(280 25% 10%); background: hsl(268 100% 11%);
color: hsl(0 0% 98%); color: hsl(228 100% 97%);
border-radius: 0.75rem; border-radius: 0.75rem;
border: 1px solid hsl(280 20% 20%); border: 1px solid hsl(220 38% 20%);
} }
.leaflet-popup-tip { .leaflet-popup-tip {
background: hsl(280 25% 10%); background: hsl(268 100% 11%);
} }
.marker-cluster-small, .marker-cluster-small,
.marker-cluster-medium, .marker-cluster-medium,
.marker-cluster-large { .marker-cluster-large {
background-color: rgba(255, 45, 107, 0.3) !important; background-color: rgba(249, 43, 155, 0.3) !important;
} }
.marker-cluster-small div, .marker-cluster-small div,
.marker-cluster-medium div, .marker-cluster-medium div,
.marker-cluster-large div { .marker-cluster-large div {
background-color: rgba(255, 45, 107, 0.8) !important; background-color: rgba(249, 43, 155, 0.85) !important;
color: white !important; color: white !important;
font-weight: 700; font-weight: 700;
} }
/* Vanilla mode blur */
.vanilla-blur img, .vanilla-blur img,
.vanilla-blur video, .vanilla-blur video,
.vanilla-blur .explicit-content { .vanilla-blur .explicit-content {
filter: blur(24px); filter: blur(24px);
transition: filter 0.3s ease; transition: filter 0.3s ease;
} }
.vanilla-blur:hover img, .vanilla-blur:hover img,
.vanilla-blur:hover video, .vanilla-blur:hover video,
.vanilla-blur:hover .explicit-content { .vanilla-blur:hover .explicit-content {
filter: blur(8px); filter: blur(8px);
} }
/* Scrollbar */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
height: 6px; height: 6px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: hsl(280 30% 6%); background: hsl(265 100% 5%);
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: hsl(340 100% 59% / 0.5); background: rgba(249, 43, 155, 0.45);
border-radius: 3px; border-radius: 3px;
} }
/* PWA safe areas */ ::-webkit-scrollbar-thumb:hover {
background: rgba(249, 43, 155, 0.65);
}
.scrollbar-none {
scrollbar-width: none;
-ms-overflow-style: none;
}
.scrollbar-none::-webkit-scrollbar {
display: none;
}
.safe-bottom { .safe-bottom {
padding-bottom: max(env(safe-area-inset-bottom, 0px), 4px); padding-bottom: max(env(safe-area-inset-bottom, 0px), 4px);
} }
.safe-top { .safe-top {
padding-top: env(safe-area-inset-top, 0); padding-top: env(safe-area-inset-top, 0);
} }
/* ID verification blur — nude photos in restricted states */
.id-blur img, .id-blur img,
.id-blur .explicit-content { .id-blur .explicit-content {
filter: blur(28px) !important; filter: blur(28px) !important;
} }
.id-blur:hover img, .id-blur:hover img,
.id-blur:hover .explicit-content { .id-blur:hover .explicit-content {
filter: blur(20px) !important; filter: blur(20px) !important;
} }
/* Mobile touch targets & scroll */
.touch-manipulation { .touch-manipulation {
touch-action: manipulation; touch-action: manipulation;
} }
@supports (height: 100dvh) { @supports (height: 100dvh) {
.min-h-screen { .min-h-screen {
min-height: 100dvh; min-height: 100dvh;
} }
} }
/* Prevent iOS zoom on input focus */
@media (max-width: 767px) { @media (max-width: 767px) {
input, select, textarea { input,
select,
textarea {
font-size: 16px !important; font-size: 16px !important;
} }
} }
/* Overscroll containment for chat */
.overscroll-contain { .overscroll-contain {
overscroll-behavior: contain; overscroll-behavior: contain;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
:focus-visible {
outline: 2px solid rgba(249, 43, 155, 0.6);
outline-offset: 2px;
}

View File

@@ -1,9 +1,12 @@
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google"; import { Manrope } from "next/font/google";
import { Providers } from "@/components/Providers"; import { Providers } from "@/components/Providers";
import "./globals.css"; import "./globals.css";
const inter = Inter({ subsets: ["latin"] }); const manrope = Manrope({
subsets: ["latin"],
variable: "--font-manrope",
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "ExposedGays — Free Gay Cruising & Shop", title: "ExposedGays — Free Gay Cruising & Shop",
@@ -22,7 +25,7 @@ export const metadata: Metadata = {
}; };
export const viewport: Viewport = { export const viewport: Viewport = {
themeColor: "#ff2d6b", themeColor: "#F92B9B",
width: "device-width", width: "device-width",
initialScale: 1, initialScale: 1,
viewportFit: "cover", viewportFit: "cover",
@@ -35,7 +38,7 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="en" className="dark"> <html lang="en" className="dark">
<body className={inter.className}> <body className={`${manrope.variable} font-sans`}>
<Providers>{children}</Providers> <Providers>{children}</Providers>
</body> </body>
</html> </html>

View File

@@ -73,7 +73,7 @@ export function AgeGate({ onVerified }: AgeGateProps) {
<div className="mx-auto mb-4 flex justify-center"> <div className="mx-auto mb-4 flex justify-center">
<Logo size="lg" showText={false} /> <Logo size="lg" showText={false} />
</div> </div>
<h1 className="text-2xl font-bold bg-horny-gradient bg-clip-text text-transparent"> <h1 className="text-2xl font-bold gradient-text">
ExposedGays ExposedGays
</h1> </h1>
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
@@ -149,9 +149,9 @@ export function AgeGate({ onVerified }: AgeGateProps) {
)} )}
</div> </div>
<div className="rounded-lg border border-blue-500/25 bg-blue-500/10 p-3"> <div className="rounded-xl border border-primary/25 bg-primary/10 p-3">
<p className="text-xs text-blue-100/90 leading-relaxed"> <p className="text-xs text-foreground/90 leading-relaxed">
<strong className="text-blue-200">No account needed.</strong> Verify your age <strong className="text-pulse-pink-light">No account needed.</strong> Verify your age
with date of birth only browse the map, spots, and profiles free. Explicit with date of birth only browse the map, spots, and profiles free. Explicit
photos and chat require a free signup. Government ID is only needed for nude photos and chat require a free signup. Government ID is only needed for nude
content in certain states. content in certain states.

View File

@@ -97,7 +97,7 @@ export function CannedMessagesPanel({ compact, onSelect }: CannedMessagesPanelPr
key={msg.id} key={msg.id}
type="button" type="button"
onClick={() => onSelect?.(msg.content)} onClick={() => onSelect?.(msg.content)}
className="text-xs px-2.5 py-1.5 rounded-full border border-border hover:border-horny-pink/50 hover:bg-horny-pink/10 transition-colors truncate max-w-[140px]" className="text-xs px-2.5 py-1.5 rounded-full pulse-pill-inactive truncate max-w-[140px]"
title={msg.content} title={msg.content}
> >
{msg.title} {msg.title}

View File

@@ -111,14 +111,14 @@ export function CruiserProfileView({
return ( return (
<div className="fixed inset-0 z-[2000] bg-black flex flex-col md:items-center md:justify-center md:p-4"> <div className="fixed inset-0 z-[2000] bg-black flex flex-col md:items-center md:justify-center md:p-4">
<div className="w-full h-full md:max-w-lg md:max-h-[92vh] md:rounded-2xl md:overflow-hidden flex flex-col bg-[#0a1628]"> <div className="w-full h-full md:max-w-lg md:max-h-[92vh] md:rounded-2xl md:overflow-hidden flex flex-col bg-pulse-deep-space">
{/* Header — Sniffies style */} {/* Header — Sniffies style */}
<div className="shrink-0 bg-black px-3 py-2 safe-top"> <div className="shrink-0 bg-black px-3 py-2 safe-top">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
type="button" type="button"
onClick={onClose} onClick={onClose}
className="p-2 text-blue-400 touch-manipulation" className="p-2 text-pulse-pink-light touch-manipulation"
aria-label="Back" aria-label="Back"
> >
<ArrowLeft className="h-5 w-5" /> <ArrowLeft className="h-5 w-5" />
@@ -126,7 +126,7 @@ export function CruiserProfileView({
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
{profile.is_verified && ( {profile.is_verified && (
<ShieldCheck className="h-4 w-4 text-blue-400 shrink-0" /> <ShieldCheck className="h-4 w-4 text-pulse-pink-light shrink-0" />
)} )}
<span className="font-bold text-white truncate">{name}</span> <span className="font-bold text-white truncate">{name}</span>
</div> </div>
@@ -188,7 +188,7 @@ export function CruiserProfileView({
type="button" type="button"
onClick={() => setActivePhoto(i + 1)} onClick={() => setActivePhoto(i + 1)}
className={cn( className={cn(
"relative w-14 h-14 rounded-full overflow-hidden border-2 border-blue-400", "relative w-14 h-14 rounded-full overflow-hidden border-2 border-primary",
shouldBlurPhoto(ph.is_explicit) && "id-blur" shouldBlurPhoto(ph.is_explicit) && "id-blur"
)} )}
> >
@@ -200,7 +200,7 @@ export function CruiserProfileView({
</div> </div>
{/* Stats + headline */} {/* Stats + headline */}
<div className="px-4 py-3 space-y-2 bg-[#0a1628]"> <div className="px-4 py-3 space-y-2 bg-pulse-deep-space">
{stats && ( {stats && (
<p className="flex items-center gap-2 text-sm text-white/90"> <p className="flex items-center gap-2 text-sm text-white/90">
<Ruler className="h-4 w-4 text-white/50" /> <Ruler className="h-4 w-4 text-white/50" />
@@ -233,7 +233,7 @@ export function CruiserProfileView({
onClick={() => setTab(t)} onClick={() => setTab(t)}
className={cn( className={cn(
"flex-1 py-2.5 text-xs font-semibold uppercase tracking-wide touch-manipulation", "flex-1 py-2.5 text-xs font-semibold uppercase tracking-wide touch-manipulation",
tab === t ? "text-blue-400 border-b-2 border-blue-400" : "text-white/50" tab === t ? "text-pulse-pink-light border-b-2 border-primary" : "text-white/50"
)} )}
> >
{t} {t}
@@ -391,7 +391,7 @@ export function CruiserProfileView({
<div className="flex items-center gap-3 text-xs text-white/70"> <div className="flex items-center gap-3 text-xs text-white/70">
<span>{timeAgo(profile.last_active)}</span> <span>{timeAgo(profile.last_active)}</span>
{profile.distance_miles != null && ( {profile.distance_miles != null && (
<span className="flex items-center gap-1 text-blue-400"> <span className="flex items-center gap-1 text-pulse-pink-light">
<MapPin className="h-3 w-3" /> <MapPin className="h-3 w-3" />
{formatDistance(profile.distance_miles)} {formatDistance(profile.distance_miles)}
</span> </span>

View File

@@ -56,7 +56,7 @@ export function GuestUpgradeBanner({
return ( return (
<> <>
<div className="rounded-xl border border-amber-500/25 bg-[#0a1628]/90 p-4 relative"> <div className="rounded-xl border border-primary/25 bg-pulse-midnight/90 p-4 relative">
{onDismiss && ( {onDismiss && (
<button <button
type="button" type="button"
@@ -68,11 +68,11 @@ export function GuestUpgradeBanner({
</button> </button>
)} )}
<div className="flex gap-3"> <div className="flex gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-blue-500/20"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/20">
<ShieldCheck className="h-5 w-5 text-blue-400" /> <ShieldCheck className="h-5 w-5 text-pulse-pink-light" />
</div> </div>
<div className="flex-1 min-w-0 pr-6"> <div className="flex-1 min-w-0 pr-6">
<p className="text-sm font-semibold text-blue-100">{msg.title}</p> <p className="text-sm font-semibold text-foreground">{msg.title}</p>
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">{msg.body}</p> <p className="text-xs text-muted-foreground mt-1 leading-relaxed">{msg.body}</p>
<Button <Button
variant="horny" variant="horny"

View File

@@ -115,7 +115,7 @@ export function LocalGuidePanel({
> >
<div <div
className={cn( className={cn(
"pointer-events-auto border border-white/10 bg-[#0a1628]/96 backdrop-blur-md shadow-2xl overflow-hidden", "pointer-events-auto border border-primary/15 bg-pulse-midnight/96 backdrop-blur-md shadow-pulse-card overflow-hidden",
isDock ? "rounded-t-2xl md:rounded-2xl" : "rounded-xl" isDock ? "rounded-t-2xl md:rounded-2xl" : "rounded-xl"
)} )}
> >
@@ -126,7 +126,7 @@ export function LocalGuidePanel({
className="w-full flex items-center gap-2 px-3 py-2.5 sm:py-3 touch-manipulation active:bg-white/5 transition-colors" className="w-full flex items-center gap-2 px-3 py-2.5 sm:py-3 touch-manipulation active:bg-white/5 transition-colors"
aria-expanded={expanded} aria-expanded={expanded}
> >
<span className="shrink-0 text-[10px] font-semibold uppercase tracking-wider text-blue-300/90"> <span className="shrink-0 pulse-pill-tag text-[10px] py-0.5 px-2">
Local Local
</span> </span>
<div className="flex-1 min-w-0 overflow-x-auto scrollbar-none"> <div className="flex-1 min-w-0 overflow-x-auto scrollbar-none">
@@ -139,7 +139,7 @@ export function LocalGuidePanel({
e.stopPropagation(); e.stopPropagation();
openSection(section.key); openSection(section.key);
}} }}
className="shrink-0 text-[11px] sm:text-xs px-2.5 py-1 rounded-full bg-white/5 border border-white/10 text-foreground/90 hover:bg-white/10 hover:border-blue-400/30 transition-colors" className="shrink-0 text-[11px] sm:text-xs px-2.5 py-1 rounded-full pulse-pill-inactive"
> >
{section.localLabel.replace("Local ", "")} {section.localLabel.replace("Local ", "")}
</span> </span>
@@ -169,7 +169,7 @@ export function LocalGuidePanel({
onClick={() => onClick={() =>
handleOutboundClick("banner", partnerHomeHref(placement)) handleOutboundClick("banner", partnerHomeHref(placement))
} }
className="block relative w-full overflow-hidden rounded-lg border border-white/10 bg-[#1a003a]/30" className="block relative w-full overflow-hidden rounded-xl border border-primary/20 bg-pulse-midnight/50"
> >
<Image <Image
src={PULSE_ATTRIBUTION.bannerRect} src={PULSE_ATTRIBUTION.bannerRect}
@@ -200,12 +200,12 @@ export function LocalGuidePanel({
className={cn( className={cn(
"rounded-lg border px-2.5 py-2.5 transition-colors group touch-manipulation min-h-[52px]", "rounded-lg border px-2.5 py-2.5 transition-colors group touch-manipulation min-h-[52px]",
active active
? "border-[#f92b9b]/40 bg-[#f92b9b]/10" ? "border-primary/40 bg-primary/10 shadow-pulse-glow"
: "border-white/10 hover:border-[#f92b9b]/25 hover:bg-[#f92b9b]/5" : "border-pulse-border/50 hover:border-primary/30 hover:bg-primary/5"
)} )}
> >
<div className="flex items-center gap-1.5 mb-0.5"> <div className="flex items-center gap-1.5 mb-0.5">
<Icon className="h-3 w-3 text-blue-300/80 group-hover:text-[#f92b9b]" /> <Icon className="h-3 w-3 text-pulse-lavender/80 group-hover:text-primary" />
<p className="text-xs font-medium leading-tight"> <p className="text-xs font-medium leading-tight">
{section.localLabel} {section.localLabel}
</p> </p>
@@ -238,7 +238,7 @@ export function LocalGuidePanel({
onClick={() => onClick={() =>
handleOutboundClick("visit_tpp", partnerHomeHref(placement)) handleOutboundClick("visit_tpp", partnerHomeHref(placement))
} }
className="shrink-0 inline-flex items-center gap-1 text-[11px] font-medium text-[#f92b9b] hover:underline touch-manipulation py-1" className="shrink-0 inline-flex items-center gap-1 text-[11px] font-medium text-primary hover:underline touch-manipulation py-1"
> >
thepinkpulse.com thepinkpulse.com
<ExternalLink className="h-3 w-3 opacity-70" /> <ExternalLink className="h-3 w-3 opacity-70" />

View File

@@ -25,7 +25,7 @@ export function Logo({ size = "md", showText = true, className }: LogoProps) {
{showText && ( {showText && (
<span <span
className={cn( className={cn(
"font-bold bg-horny-gradient bg-clip-text text-transparent", "font-bold gradient-text",
size === "sm" && "text-base", size === "sm" && "text-base",
size === "md" && "text-xl", size === "md" && "text-xl",
size === "lg" && "text-2xl" size === "lg" && "text-2xl"

View File

@@ -175,7 +175,7 @@ export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) {
}; };
return ( return (
<div className="relative h-full w-full bg-[#0a1628]"> <div className="relative h-full w-full bg-pulse-deep-space">
{/* Minimal top controls — Sniffies style */} {/* Minimal top controls — Sniffies style */}
<div className="absolute top-2 left-2 right-2 z-[1000] flex gap-2 items-start pointer-events-none"> <div className="absolute top-2 left-2 right-2 z-[1000] flex gap-2 items-start pointer-events-none">
<div className="pointer-events-auto flex gap-2 flex-wrap"> <div className="pointer-events-auto flex gap-2 flex-wrap">
@@ -183,13 +183,13 @@ export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) {
variant="secondary" variant="secondary"
size="sm" size="sm"
onClick={() => setShowFilters(!showFilters)} onClick={() => setShowFilters(!showFilters)}
className="shadow-lg bg-[#0a1628]/90 border-white/10 text-white h-9" className="shadow-lg rounded-full bg-pulse-midnight/90 border-primary/20 text-white h-9"
> >
<Filter className="h-4 w-4" /> <Filter className="h-4 w-4" />
</Button> </Button>
<Badge <Badge
variant="online" variant="online"
className="shadow-lg self-center bg-blue-500/20 text-blue-300 border-blue-500/30" className="shadow-lg self-center bg-primary/20 text-pulse-pink-light border-primary/30"
> >
{filteredProfiles.filter((p) => p.status !== "offline").length} cruising {filteredProfiles.filter((p) => p.status !== "offline").length} cruising
</Badge> </Badge>
@@ -202,7 +202,7 @@ export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) {
</div> </div>
{showFilters && ( {showFilters && (
<div className="absolute top-12 left-2 z-[1000] w-72 rounded-xl border border-white/10 bg-[#0a1628]/98 backdrop-blur-md p-4 shadow-2xl space-y-4 max-h-[70vh] overflow-y-auto"> <div className="absolute top-12 left-2 z-[1000] w-72 rounded-xl border border-primary/15 bg-pulse-midnight/98 backdrop-blur-md p-4 shadow-pulse-card space-y-4 max-h-[70vh] overflow-y-auto">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="font-semibold text-sm text-white">Filters</span> <span className="font-semibold text-sm text-white">Filters</span>
<Button variant="ghost" size="icon" onClick={() => setShowFilters(false)}> <Button variant="ghost" size="icon" onClick={() => setShowFilters(false)}>
@@ -331,7 +331,7 @@ export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) {
{/* Recenter — above local guide dock on mobile */} {/* Recenter — above local guide dock on mobile */}
<button <button
type="button" type="button"
className="absolute bottom-32 right-3 z-[1000] p-3 rounded-full bg-[#0a1628]/90 border border-white/15 text-blue-400 shadow-lg touch-manipulation md:bottom-6" className="absolute bottom-32 right-3 z-[1000] p-3 rounded-full bg-pulse-midnight/90 border border-primary/25 text-pulse-pink-light shadow-pulse-glow touch-manipulation md:bottom-6"
aria-label="Recenter map" aria-label="Recenter map"
> >
<Locate className="h-5 w-5" /> <Locate className="h-5 w-5" />

View File

@@ -23,7 +23,7 @@ export function MobileHeader({
<header className="md:hidden fixed top-0 left-0 right-0 z-50 flex h-12 items-center justify-between border-b border-border bg-horny-dark/95 backdrop-blur-md px-3 safe-top"> <header className="md:hidden fixed top-0 left-0 right-0 z-50 flex h-12 items-center justify-between border-b border-border bg-horny-dark/95 backdrop-blur-md px-3 safe-top">
<Link href="/" className="flex items-center gap-1.5 min-w-0"> <Link href="/" className="flex items-center gap-1.5 min-w-0">
<Logo size="sm" showText={false} /> <Logo size="sm" showText={false} />
<span className="text-sm font-bold bg-horny-gradient bg-clip-text text-transparent truncate"> <span className="text-sm font-bold gradient-text truncate">
ExposedGays ExposedGays
</span> </span>
{activeCount > 0 && ( {activeCount > 0 && (
@@ -44,7 +44,7 @@ export function MobileHeader({
{vanillaMode ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} {vanillaMode ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button> </Button>
{isGuest ? ( {isGuest ? (
<Badge variant="outline" className="text-[8px] px-1.5 text-blue-400 border-blue-400/30"> <Badge variant="pill" className="text-[8px] px-1.5">
Guest Guest
</Badge> </Badge>
) : ( ) : (

View File

@@ -99,7 +99,7 @@ export function Nav({
Vanilla Vanilla
</Button> </Button>
{isGuest && ( {isGuest && (
<Badge variant="outline" className="text-[10px] text-blue-400 border-blue-400/30"> <Badge variant="pill" className="text-[10px]">
Guest Guest
</Badge> </Badge>
)} )}

View File

@@ -48,7 +48,7 @@ export function Shop() {
return ( return (
<div className="min-h-screen bg-horny-dark"> <div className="min-h-screen bg-horny-dark">
{/* Hero */} {/* Hero */}
<div className="bg-horny-gradient px-4 py-8 md:py-12"> <div className="bg-pulse-gradient-bold px-4 py-8 md:py-12">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<h1 className="text-3xl md:text-4xl font-bold text-white"> <h1 className="text-3xl md:text-4xl font-bold text-white">
ExposedGays Shop ExposedGays Shop

View File

@@ -3,19 +3,25 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-bold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {
default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", default:
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", "border-transparent bg-pulse-gradient-bold text-white shadow-sm",
destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", secondary:
outline: "text-foreground", "border-transparent bg-secondary text-secondary-foreground",
top: "border-transparent bg-blue-500/20 text-blue-400", destructive:
bottom: "border-transparent bg-orange-500/20 text-orange-400", "border-transparent bg-destructive text-destructive-foreground shadow",
vers: "border-transparent bg-purple-500/20 text-purple-400", outline: "border-pulse-border/60 text-foreground bg-pulse-midnight/30",
side: "border-transparent bg-green-500/20 text-green-400", pill:
online: "border-transparent bg-green-500/20 text-green-400 animate-pulse", "border-primary/30 bg-primary/15 text-pulse-pink-light",
top: "border-transparent bg-primary/20 text-pulse-pink-light",
bottom: "border-transparent bg-orange-500/20 text-orange-300",
vers: "border-transparent bg-pulse-violet/25 text-pulse-lavender",
side: "border-transparent bg-pulse-teal/20 text-pulse-teal",
online:
"border-transparent bg-emerald-500/20 text-emerald-300 animate-pulse",
}, },
}, },
defaultVariants: { variant: "default" }, defaultVariants: { variant: "default" },

View File

@@ -4,23 +4,33 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-semibold transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 touch-manipulation active:scale-[0.98]",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", default:
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", "rounded-full bg-pulse-gradient-bold text-white shadow-pulse-glow hover:opacity-90 hover:-translate-y-px",
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", destructive:
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", "rounded-full bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
ghost: "hover:bg-accent hover:text-accent-foreground", outline:
link: "text-primary underline-offset-4 hover:underline", "rounded-full border border-pulse-border/80 bg-pulse-midnight/40 text-foreground hover:border-primary/40 hover:bg-primary/10",
horny: "bg-horny-gradient text-white shadow-lg hover:opacity-90 font-semibold", secondary:
"rounded-full bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"rounded-full hover:bg-primary/10 hover:text-primary",
link: "text-primary underline-offset-4 hover:underline rounded-none",
horny:
"rounded-full bg-pulse-gradient-bold text-white shadow-pulse-glow hover:opacity-90 hover:-translate-y-px font-bold",
pill:
"rounded-full pulse-pill-inactive font-bold hover:border-primary/35",
"pill-active":
"rounded-full pulse-pill-active font-bold",
}, },
size: { size: {
default: "h-10 px-4 py-2", default: "h-10 px-5 py-2",
sm: "h-8 rounded-md px-3 text-xs", sm: "h-8 px-4 text-xs",
lg: "h-12 rounded-md px-8 text-base", lg: "h-12 px-8 text-base",
icon: "h-10 w-10", icon: "h-10 w-10 rounded-full",
}, },
}, },
defaultVariants: { variant: "default", size: "default" }, defaultVariants: { variant: "default", size: "default" },

View File

@@ -3,7 +3,14 @@ import { cn } from "@/lib/utils";
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-xl border bg-card text-card-foreground shadow", className)} {...props} /> <div
ref={ref}
className={cn(
"rounded-xl border border-white/[0.06] bg-card/90 text-card-foreground shadow-pulse-card backdrop-blur-sm",
className
)}
{...props}
/>
) )
); );
Card.displayName = "Card"; Card.displayName = "Card";

View File

@@ -6,7 +6,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
<input <input
type={type} type={type}
className={cn( className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "flex h-11 w-full rounded-xl border border-input bg-pulse-midnight/50 px-3 py-2 text-base text-foreground ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60 focus-visible:border-primary/40 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className className
)} )}
ref={ref} ref={ref}

View File

@@ -13,7 +13,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List <TabsPrimitive.List
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground", "inline-flex h-auto min-h-10 items-center gap-1 rounded-full bg-pulse-midnight/60 p-1 text-muted-foreground border border-pulse-border/40",
className className
)} )}
{...props} {...props}
@@ -28,7 +28,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm", "inline-flex items-center justify-center whitespace-nowrap rounded-full px-4 py-2 text-sm font-bold ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-pulse-gradient-bold data-[state=active]:text-white data-[state=active]:shadow-pulse-glow data-[state=inactive]:text-pulse-muted data-[state=inactive]:hover:text-pulse-pink-light touch-manipulation",
className className
)} )}
{...props} {...props}
@@ -42,7 +42,10 @@ const TabsContent = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<TabsPrimitive.Content <TabsPrimitive.Content
ref={ref} ref={ref}
className={cn("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className)} className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props} {...props}
/> />
)); ));

View File

@@ -171,7 +171,7 @@ export const KINK_OPTIONS = [
] as const; ] as const;
export const POSITION_COLORS: Record<Position, string> = { export const POSITION_COLORS: Record<Position, string> = {
top: "#3b82f6", top: "#F92B9B",
bottom: "#f97316", bottom: "#f97316",
vers: "#a855f7", vers: "#a855f7",
side: "#22c55e", side: "#22c55e",

View File

@@ -43,20 +43,43 @@ const config: Config = {
DEFAULT: "hsl(var(--card))", DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))", foreground: "hsl(var(--card-foreground))",
}, },
pulse: {
pink: "#F92B9B",
"pink-light": "#FF6EC7",
"pink-dark": "#CC1A7A",
violet: "#8B22CC",
lavender: "#C084FC",
teal: "#03A4A8",
"deep-space": "#08001C",
midnight: "#1A003A",
glow: "#470070",
border: "#1E2D4A",
muted: "#8B92A8",
},
horny: { horny: {
pink: "#ff2d6b", pink: "#F92B9B",
purple: "#8b2fc9", purple: "#8B22CC",
dark: "#0d0a0f", dark: "#08001C",
surface: "#1a1220", surface: "#1A003A",
}, },
}, },
borderRadius: { borderRadius: {
lg: "var(--radius)", lg: "var(--radius)",
md: "calc(var(--radius) - 2px)", md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)", sm: "calc(var(--radius) - 4px)",
pill: "9999px",
}, },
backgroundImage: { backgroundImage: {
"horny-gradient": "linear-gradient(135deg, #ff2d6b 0%, #8b2fc9 50%, #ff2d6b 100%)", "pulse-gradient": "linear-gradient(135deg, #FF2D9B 0%, #C084FC 50%, #FF2D9B 100%)",
"pulse-gradient-bold": "linear-gradient(135deg, #FF2D9B 0%, #9B30FF 100%)",
"horny-gradient": "linear-gradient(135deg, #FF2D9B 0%, #C084FC 50%, #FF2D9B 100%)",
},
boxShadow: {
"pulse-glow": "0 0 16px rgba(249, 43, 155, 0.4), 0 0 32px rgba(249, 43, 155, 0.15)",
"pulse-card": "0 12px 40px rgba(249, 43, 155, 0.12)",
},
fontFamily: {
sans: ["var(--font-manrope)", "system-ui", "sans-serif"],
}, },
}, },
}, },