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

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
import paramiko
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("10.10.0.10", username="localadministrator", password="Bbt9115xty9176!", timeout=20, allow_agent=False, look_for_keys=False)
def run(cmd, t=120):
print("$", cmd[:200])
_, o, e = c.exec_command(cmd, timeout=t)
code = o.channel.recv_exit_status()
out = (o.read() + e.read()).decode(errors="replace")
print(out[-12000:])
print("exit", code, "\n")
return out
run("cd /tmp/exposedgays && git fetch origin && git reset --hard origin/main && git log -1 --oneline")
run(
"docker exec coolify-db psql -U coolify -d coolify -t -c "
"\"SELECT id,status FROM application_deployment_queues ORDER BY id DESC LIMIT 5;\""
)
run(
"docker exec coolify-db psql -U coolify -d coolify -t -c "
"\"SELECT deployment_logs FROM application_deployment_queues ORDER BY id DESC LIMIT 1;\" | tail -c 8000"
)
run("ls -la /data/coolify/applications/ira4bw4nhbvm87o7s3fgqu6v/logs/ 2>/dev/null | tail -5")
run("tail -80 /data/coolify/applications/ira4bw4nhbvm87o7s3fgqu6v/logs/*.log 2>/dev/null | tail -80")
c.close()

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3
import paramiko
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("10.10.0.10", username="localadministrator", password="Bbt9115xty9176!", timeout=20, allow_agent=False, look_for_keys=False)
def run(cmd, t=300):
print("$", cmd[:220])
_, o, e = c.exec_command(cmd, timeout=t)
code = o.channel.recv_exit_status()
out = (o.read() + e.read()).decode(errors="replace")
print(out[-15000:])
print("exit", code, "\n")
return out
run("docker exec coolify-db psql -U coolify -d coolify -c \"\\d application_deployment_queues\"")
run("docker exec coolify-db psql -U coolify -d coolify -t -c \"SELECT logs FROM application_deployment_queues WHERE id=1116;\" | tail -c 12000")
run("find /data/coolify -name '*l9hjct9e8yxydqjl8okovpr5*' 2>/dev/null | head -5")
run("find /data/coolify -path '*ira4bw4nhbvm87o7s3fgqu6v*' -name '*.log' 2>/dev/null | head -10")
run("cd /data/coolify/applications/ira4bw4nhbvm87o7s3fgqu6v && docker compose build 2>&1 | tail -60")
c.close()

41
deploy/redeploy-v2.py Normal file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env python3
import paramiko
HOST = "10.10.0.10"
USER = "localadministrator"
PASS = "Bbt9115xty9176!"
APP = "ira4bw4nhbvm87o7s3fgqu6v"
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username=USER, password=PASS, timeout=20, allow_agent=False, look_for_keys=False)
def run(cmd, t=300):
print("$", cmd[:180])
_, o, e = c.exec_command(cmd, timeout=t)
code = o.channel.recv_exit_status()
out = (o.read() + e.read()).decode(errors="replace")
print(out[-6000:])
print("exit", code, "\n")
return out
run("cd /tmp/exposedgays && git fetch origin && git reset --hard origin/main && git log -1 --oneline")
run(
"TOKEN=$(cat /home/localadministrator/.coolify-token | tail -1); "
f"curl -sS -X POST 'http://127.0.0.1:8000/api/v1/deploy?uuid={APP}&force_rebuild=true' "
'-H "Authorization: Bearer $TOKEN"'
)
for i in range(150):
_, out = run("docker exec coolify php artisan queue:work --queue=high,default --once --timeout=900 2>&1", t=920)
if "ApplicationDeploymentJob" in out:
if "DONE" in out or "finished" in out.lower():
print("=== DEPLOY SUCCESS ===")
break
if "FAIL" in out:
print("=== DEPLOY FAILED ===")
break
run("docker exec coolify-db psql -U coolify -d coolify -t -c \"SELECT id,status FROM application_deployment_queues ORDER BY id DESC LIMIT 1;\"")
run("curl -sS https://exposedgays.com/ 2>&1 | head -c 500")
c.close()

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Just Two Roommates — Wordmark Only -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1999 220" role="img" aria-label="Just Two Roommates">
<title>Just Two Roommates — Wordmark</title>
<g font-family="Montserrat, 'Helvetica Neue', Arial, sans-serif" font-weight="900" font-size="148">
<text x="30" y="160" fill="#FAFAFA">JUST</text>
<text x="458" y="160" fill="#DD3EDD">TWO</text>
<text x="886" y="160" fill="#9A7BC0">ROOMMATES</text>
</g>
<text x="1949" y="50" font-family="Montserrat, Arial, sans-serif" font-weight="700" font-size="24" fill="#9A7BC0"></text>
</svg>

After

Width:  |  Height:  |  Size: 646 B

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The Pink Pulse — Icon Mark (Pulse Wave) -->
<!-- Use for: favicons, app icons, social avatars, small-space applications -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="The Pink Pulse icon">
<title>The Pink Pulse — Icon</title>
<defs>
<filter id="ic-glow" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="b1"/>
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="b2"/>
<feMerge>
<feMergeNode in="b1"/>
<feMergeNode in="b2"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="ic-bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#1A003A"/>
<stop offset="55%" stop-color="#470070"/>
<stop offset="100%" stop-color="#08001C"/>
</linearGradient>
<linearGradient id="ic-stroke" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#F92B9B"/>
<stop offset="100%" stop-color="#8B22CC"/>
</linearGradient>
<radialGradient id="ic-bloom" cx="50%" cy="55%" r="50%">
<stop offset="0%" stop-color="#F92B9B" stop-opacity="0.55"/>
<stop offset="70%" stop-color="#8B22CC" stop-opacity="0.15"/>
<stop offset="100%" stop-color="#08001C" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- Rounded square container -->
<rect x="16" y="16" width="480" height="480" rx="108" ry="108" fill="url(#ic-bg)"/>
<rect x="16" y="16" width="480" height="480" rx="108" ry="108" fill="url(#ic-bloom)"/>
<rect x="22" y="22" width="468" height="468" rx="102" ry="102" fill="none" stroke="url(#ic-stroke)" stroke-width="4" opacity="0.9"/>
<!-- Pulse / heartbeat line -->
<g filter="url(#ic-glow)">
<path d="M64 256 L152 256 L184 180 L232 336 L280 140 L328 320 L360 256 L448 256"
fill="none"
stroke="#F92B9B"
stroke-width="16"
stroke-linecap="round"
stroke-linejoin="round"/>
</g>
<path d="M64 256 L152 256 L184 180 L232 336 L280 140 L328 320 L360 256 L448 256"
fill="none"
stroke="#FAFAFA"
stroke-width="8"
stroke-linecap="round"
stroke-linejoin="round"
opacity="0.95"/>
<!-- Anchor dot on right -->
<circle cx="448" cy="256" r="10" fill="#8B22CC"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -3,9 +3,11 @@ export default function PrivacyPage() {
<div className="mx-auto max-w-2xl px-4 py-8 prose prose-invert"> <div className="mx-auto max-w-2xl px-4 py-8 prose prose-invert">
<h1>Privacy Policy</h1> <h1>Privacy Policy</h1>
<p className="text-muted-foreground text-sm"> <p className="text-muted-foreground text-sm">
We collect minimal data. Location is used for map features and city chat auto-join. ExposedGays is operated by Just Two Roommates LLC. We collect minimal data.
Anonymous browsing is supported. Cart data stays in your browser (localStorage). Location is used for map features and city chat auto-join. Anonymous browsing
Placeholder policy consult legal counsel before launch. is supported. Cart data stays in your browser (localStorage). When you follow
links to our community partner The Pink Pulse, their privacy policy applies on
their site. Placeholder policy consult legal counsel before launch.
</p> </p>
</div> </div>
); );

View File

@@ -15,6 +15,7 @@ import { AuthModal } from "@/components/AuthModal";
import { PhotoGalleryManager } from "@/components/PhotoGalleryManager"; import { PhotoGalleryManager } from "@/components/PhotoGalleryManager";
import { CannedMessagesPanel } from "@/components/CannedMessagesPanel"; import { CannedMessagesPanel } from "@/components/CannedMessagesPanel";
import { IdVerificationModal } from "@/components/IdVerificationModal"; import { IdVerificationModal } from "@/components/IdVerificationModal";
import { PinkPulsePartner } from "@/components/PinkPulsePartner";
import { import {
US_STATES, US_STATES,
getUserState, getUserState,
@@ -307,6 +308,8 @@ export default function ProfilePage() {
<CannedMessagesPanel /> <CannedMessagesPanel />
<PinkPulsePartner variant="card" />
{message && ( {message && (
<p className={`text-sm text-center ${message.startsWith("Error") ? "text-destructive" : "text-green-400"}`}> <p className={`text-sm text-center ${message.startsWith("Error") ? "text-destructive" : "text-green-400"}`}>
{message} {message}

View File

@@ -3,9 +3,12 @@ export default function TermsPage() {
<div className="mx-auto max-w-2xl px-4 py-8 prose prose-invert"> <div className="mx-auto max-w-2xl px-4 py-8 prose prose-invert">
<h1>Terms of Service</h1> <h1>Terms of Service</h1>
<p className="text-muted-foreground text-sm"> <p className="text-muted-foreground text-sm">
ExposedGays is a free adult platform. You must be 18+ to use this site. ExposedGays is a free adult platform operated by Just Two Roommates LLC. You must
All content is user-generated. We reserve the right to remove illegal content. be 18+ to use this site. All content is user-generated. We reserve the right to
No paid subscriptions ever. Placeholder terms consult legal counsel before launch. remove illegal content. No paid subscriptions ever. Links to our community
partner The Pink Pulse (thepinkpulse.com) are provided for LGBTQ+ news, events,
and local resources separate editorial property, same parent company.
Placeholder terms consult legal counsel before launch.
</p> </p>
</div> </div>
); );

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 { Send, Image as ImageIcon, Users, Lock, Zap, ChevronDown, ChevronUp } from "lucide-react";
import { timeAgo } from "@/lib/utils"; import { timeAgo } from "@/lib/utils";
import { CannedMessagesPanel } from "@/components/CannedMessagesPanel"; import { CannedMessagesPanel } from "@/components/CannedMessagesPanel";
import { PinkPulsePartner } from "@/components/PinkPulsePartner";
interface Message { interface Message {
id: string; id: string;
@@ -148,9 +149,10 @@ export function ChatPanel({ dmUserId }: ChatPanelProps) {
<GroupRoom name="Leather Night FTL" members={8} /> <GroupRoom name="Leather Night FTL" members={8} />
<GroupRoom name="Beach Cruisers" members={15} /> <GroupRoom name="Beach Cruisers" members={15} />
<GroupRoom name="Gym Sauna Squad" members={4} /> <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. Create a group room free, no limits.
</p> </p>
<PinkPulsePartner variant="card" className="mt-4" />
</div> </div>
</TabsContent> </TabsContent>

View File

@@ -14,6 +14,7 @@ import type { Profile, CruisingSpot, MapFilters, Position } from "@/types";
import { KINK_OPTIONS, POSITION_COLORS } from "@/types"; import { KINK_OPTIONS, POSITION_COLORS } from "@/types";
import { MOCK_PROFILES, MOCK_SPOTS } from "@/lib/mock-data"; import { MOCK_PROFILES, MOCK_SPOTS } from "@/lib/mock-data";
import { useOpenIdVerification } from "@/components/Providers"; import { useOpenIdVerification } from "@/components/Providers";
import { PinkPulsePartner } from "@/components/PinkPulsePartner";
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import "leaflet.markercluster/dist/MarkerCluster.css"; import "leaflet.markercluster/dist/MarkerCluster.css";
@@ -279,9 +280,15 @@ export function CruisingMap({ vanillaMode, onActiveCountChange }: MapProps) {
vanillaMode={vanillaMode} vanillaMode={vanillaMode}
/> />
))} ))}
<PinkPulsePartner variant="card" className="mt-2" />
</div> </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 */} {/* Map */}
{leafletReady && iconFactory && ( {leafletReady && iconFactory && (
<MapContainer <MapContainer

View File

@@ -3,7 +3,8 @@
import { useState } from "react"; import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; 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 { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Logo } from "@/components/Logo"; import { Logo } from "@/components/Logo";
@@ -76,6 +77,16 @@ export function Nav({
</nav> </nav>
<div className="flex items-center gap-2"> <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 && ( {idBlur && onIdVerifyClick && (
<Button <Button
variant="outline" 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 { Nav } from "@/components/Nav";
import { MobileHeader } from "@/components/MobileHeader"; import { MobileHeader } from "@/components/MobileHeader";
import { IdVerificationModal } from "@/components/IdVerificationModal"; import { IdVerificationModal } from "@/components/IdVerificationModal";
import { SiteFooter } from "@/components/SiteFooter";
import { AuthProvider, useAuth } from "@/lib/auth/context"; import { AuthProvider, useAuth } from "@/lib/auth/context";
import { import {
mustBlurExplicit, mustBlurExplicit,
@@ -90,9 +91,10 @@ function InnerProviders({ children }: ProvidersProps) {
onVanillaToggle={handleVanillaToggle} onVanillaToggle={handleVanillaToggle}
activeCount={activeCount} 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}> <ActiveCountContext.Provider value={setActiveCount}>
{children} <div className="flex-1 flex flex-col">{children}</div>
<SiteFooter />
</ActiveCountContext.Provider> </ActiveCountContext.Provider>
</main> </main>
<IdVerificationModal <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>
);
}

47
src/lib/partners.ts Normal file
View File

@@ -0,0 +1,47 @@
/** Just Two Roommates network — partner properties (JTR only, not RMP). */
export const JTR = {
name: "Just Two Roommates",
url: "https://justtworoommates.com",
logo: "/partners/jtr-wordmark.svg",
} as const;
export const PINK_PULSE = {
name: "The Pink Pulse",
shortName: "Pink Pulse",
tagline: "LGBTQ+ news, events & 165+ cities",
url: "https://thepinkpulse.com",
logo: "/partners/pinkpulse-icon.svg",
links: [
{
label: "The Latest",
href: "https://thepinkpulse.com/the-latest",
description: "Queer news & stories",
},
{
label: "Events",
href: "https://thepinkpulse.com/events-calendar",
description: "This weekend near you",
},
{
label: "Directory",
href: "https://thepinkpulse.com/directory",
description: "Local LGBTQ+ businesses",
},
{
label: "Health",
href: "https://thepinkpulse.com/health-wellness",
description: "Testing, PrEP & wellness",
},
{
label: "Nightlife",
href: "https://thepinkpulse.com/nightlife",
description: "Bars, drag & going out",
},
{
label: "Groups",
href: "https://thepinkpulse.com/groups",
description: "Community meetups",
},
],
} as const;