feat: JTR branding + Pink Pulse community partner integration

This commit is contained in:
Ryan Salazar
2026-06-26 21:12:17 -04:00
parent efbd54443d
commit 448035ad60
15 changed files with 488 additions and 10 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -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"

View 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>
);
}

View File

@@ -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

View 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>
);
}