feat: JTR branding + Pink Pulse community partner integration
This commit is contained in:
@@ -8,6 +8,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { Send, Image as ImageIcon, Users, Lock, Zap, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { timeAgo } from "@/lib/utils";
|
||||
import { CannedMessagesPanel } from "@/components/CannedMessagesPanel";
|
||||
import { PinkPulsePartner } from "@/components/PinkPulsePartner";
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
@@ -148,9 +149,10 @@ export function ChatPanel({ dmUserId }: ChatPanelProps) {
|
||||
<GroupRoom name="Leather Night FTL" members={8} />
|
||||
<GroupRoom name="Beach Cruisers" members={15} />
|
||||
<GroupRoom name="Gym Sauna Squad" members={4} />
|
||||
<p className="text-xs text-muted-foreground text-center pt-4">
|
||||
<p className="text-xs text-muted-foreground text-center pt-2">
|
||||
Create a group room — free, no limits.
|
||||
</p>
|
||||
<PinkPulsePartner variant="card" className="mt-4" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import type { Profile, CruisingSpot, MapFilters, Position } from "@/types";
|
||||
import { KINK_OPTIONS, POSITION_COLORS } from "@/types";
|
||||
import { MOCK_PROFILES, MOCK_SPOTS } from "@/lib/mock-data";
|
||||
import { useOpenIdVerification } from "@/components/Providers";
|
||||
import { PinkPulsePartner } from "@/components/PinkPulsePartner";
|
||||
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import "leaflet.markercluster/dist/MarkerCluster.css";
|
||||
@@ -279,9 +280,15 @@ export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) {
|
||||
vanillaMode={vanillaMode}
|
||||
/>
|
||||
))}
|
||||
<PinkPulsePartner variant="card" className="mt-2" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile — community partner strip above bottom nav */}
|
||||
<div className="md:hidden absolute bottom-0 left-0 right-0 z-[999] pb-16">
|
||||
<PinkPulsePartner variant="strip" />
|
||||
</div>
|
||||
|
||||
{/* Map */}
|
||||
{leafletReady && iconFactory && (
|
||||
<MapContainer
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Map, MessageCircle, MapPin, ShoppingBag, User, Eye, EyeOff, LogIn, LogOut, ShieldCheck } from "lucide-react";
|
||||
import { Map, MessageCircle, MapPin, ShoppingBag, User, Eye, EyeOff, LogIn, LogOut, ShieldCheck, Sparkles } from "lucide-react";
|
||||
import { PINK_PULSE } from "@/lib/partners";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Logo } from "@/components/Logo";
|
||||
@@ -76,6 +77,16 @@ export function Nav({
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden lg:flex items-center gap-1.5 text-xs text-muted-foreground hover:text-[#f92b9b] transition-colors px-2 py-1 rounded-md border border-transparent hover:border-[#f92b9b]/20"
|
||||
title={`Community partner: ${PINK_PULSE.name}`}
|
||||
>
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
<span className="font-medium">{PINK_PULSE.shortName}</span>
|
||||
</Link>
|
||||
{idBlur && onIdVerifyClick && (
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
145
src/components/PinkPulsePartner.tsx
Normal file
145
src/components/PinkPulsePartner.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ExternalLink, Sparkles } from "lucide-react";
|
||||
import { PINK_PULSE } from "@/lib/partners";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface PinkPulsePartnerProps {
|
||||
variant?: "card" | "strip" | "inline";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function PinkPulsePartner({ variant = "card", className }: PinkPulsePartnerProps) {
|
||||
if (variant === "strip") {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-3 overflow-x-auto px-3 py-2 border-t border-border/60 bg-horny-surface/90 backdrop-blur-sm",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 shrink-0 pr-2 border-r border-border/50"
|
||||
>
|
||||
<Image
|
||||
src={PINK_PULSE.logo}
|
||||
alt={PINK_PULSE.name}
|
||||
width={28}
|
||||
height={28}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
<div className="leading-tight">
|
||||
<p className="text-[10px] text-muted-foreground uppercase tracking-wide">
|
||||
Community partner
|
||||
</p>
|
||||
<p className="text-xs font-semibold text-[#f92b9b]">{PINK_PULSE.shortName}</p>
|
||||
</div>
|
||||
</Link>
|
||||
{PINK_PULSE.links.slice(0, 5).map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="shrink-0 text-xs px-3 py-1.5 rounded-full border border-[#f92b9b]/25 text-foreground/90 hover:bg-[#f92b9b]/10 hover:border-[#f92b9b]/40 transition-colors touch-manipulation"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "inline") {
|
||||
return (
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 text-xs text-muted-foreground hover:text-[#f92b9b] transition-colors",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Image src={PINK_PULSE.logo} alt="" width={18} height={18} className="rounded" />
|
||||
<span>
|
||||
Community partner: <span className="font-medium text-[#f92b9b]">{PINK_PULSE.name}</span>
|
||||
</span>
|
||||
<ExternalLink className="h-3 w-3 opacity-60" />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-xl border border-[#f92b9b]/20 bg-gradient-to-br from-[#1a003a]/40 to-horny-surface p-3 space-y-3",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="shrink-0"
|
||||
>
|
||||
<Image
|
||||
src={PINK_PULSE.logo}
|
||||
alt={PINK_PULSE.name}
|
||||
width={40}
|
||||
height={40}
|
||||
className="rounded-xl"
|
||||
/>
|
||||
</Link>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Sparkles className="h-3 w-3 text-[#f92b9b]" />
|
||||
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||
Community partner
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm font-semibold text-[#f92b9b] hover:underline"
|
||||
>
|
||||
{PINK_PULSE.name}
|
||||
</Link>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">{PINK_PULSE.tagline}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
{PINK_PULSE.links.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-lg border border-border/60 px-2.5 py-2 hover:border-[#f92b9b]/30 hover:bg-[#f92b9b]/5 transition-colors group"
|
||||
>
|
||||
<p className="text-xs font-medium group-hover:text-[#f92b9b]">{link.label}</p>
|
||||
<p className="text-[10px] text-muted-foreground truncate">{link.description}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-1.5 text-xs text-[#f92b9b] font-medium hover:underline"
|
||||
>
|
||||
Visit thepinkpulse.com
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { AgeGate } from "@/components/AgeGate";
|
||||
import { Nav } from "@/components/Nav";
|
||||
import { MobileHeader } from "@/components/MobileHeader";
|
||||
import { IdVerificationModal } from "@/components/IdVerificationModal";
|
||||
import { SiteFooter } from "@/components/SiteFooter";
|
||||
import { AuthProvider, useAuth } from "@/lib/auth/context";
|
||||
import {
|
||||
mustBlurExplicit,
|
||||
@@ -90,9 +91,10 @@ function InnerProviders({ children }: ProvidersProps) {
|
||||
onVanillaToggle={handleVanillaToggle}
|
||||
activeCount={activeCount}
|
||||
/>
|
||||
<main className="md:pt-14 pt-12 pb-16 md:pb-0 min-h-screen min-h-[100dvh]">
|
||||
<main className="md:pt-14 pt-12 pb-16 md:pb-0 min-h-screen min-h-[100dvh] flex flex-col">
|
||||
<ActiveCountContext.Provider value={setActiveCount}>
|
||||
{children}
|
||||
<div className="flex-1 flex flex-col">{children}</div>
|
||||
<SiteFooter />
|
||||
</ActiveCountContext.Provider>
|
||||
</main>
|
||||
<IdVerificationModal
|
||||
|
||||
96
src/components/SiteFooter.tsx
Normal file
96
src/components/SiteFooter.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { JTR, PINK_PULSE } from "@/lib/partners";
|
||||
import { PinkPulsePartner } from "@/components/PinkPulsePartner";
|
||||
|
||||
export function SiteFooter() {
|
||||
const pathname = usePathname();
|
||||
const isMapHome = pathname === "/";
|
||||
|
||||
if (isMapHome) return null;
|
||||
|
||||
return (
|
||||
<footer className="border-t border-border bg-horny-dark/80 mt-auto">
|
||||
<div className="mx-auto max-w-5xl px-4 py-6 space-y-5">
|
||||
<div className="hidden sm:block max-w-sm">
|
||||
<PinkPulsePartner variant="card" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4">
|
||||
<div className="space-y-2">
|
||||
<p className="text-[10px] uppercase tracking-widest text-muted-foreground">
|
||||
Designed & operated by
|
||||
</p>
|
||||
<Link
|
||||
href={JTR.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block opacity-90 hover:opacity-100 transition-opacity"
|
||||
aria-label={JTR.name}
|
||||
>
|
||||
<Image
|
||||
src={JTR.logo}
|
||||
alt={JTR.name}
|
||||
width={220}
|
||||
height={28}
|
||||
className="h-6 w-auto max-w-[200px] sm:max-w-[240px]"
|
||||
/>
|
||||
</Link>
|
||||
<p className="text-xs text-muted-foreground max-w-sm">
|
||||
ExposedGays is a free adult platform from the Just Two Roommates media
|
||||
family — alongside{" "}
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[#f92b9b] hover:underline"
|
||||
>
|
||||
{PINK_PULSE.name}
|
||||
</Link>
|
||||
, your queer city guide.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
|
||||
<Link href="/terms" className="hover:text-foreground">
|
||||
Terms
|
||||
</Link>
|
||||
<Link href="/privacy" className="hover:text-foreground">
|
||||
Privacy
|
||||
</Link>
|
||||
<Link
|
||||
href={PINK_PULSE.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-[#f92b9b] inline-flex items-center gap-1"
|
||||
>
|
||||
<Image
|
||||
src={PINK_PULSE.logo}
|
||||
alt=""
|
||||
width={14}
|
||||
height={14}
|
||||
className="rounded-sm"
|
||||
/>
|
||||
{PINK_PULSE.shortName}
|
||||
</Link>
|
||||
<Link
|
||||
href={JTR.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-foreground"
|
||||
>
|
||||
{JTR.name}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[10px] text-muted-foreground/70 text-center sm:text-left">
|
||||
© {new Date().getFullYear()} Just Two Roommates LLC · ExposedGays.com · Free forever
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user