feat: JTR branding + Pink Pulse community partner integration
This commit is contained in:
29
deploy/check-deploy-fail.py
Normal file
29
deploy/check-deploy-fail.py
Normal 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()
|
||||||
23
deploy/check-deploy-fail2.py
Normal file
23
deploy/check-deploy-fail2.py
Normal 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
41
deploy/redeploy-v2.py
Normal 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()
|
||||||
11
public/partners/jtr-wordmark.svg
Normal file
11
public/partners/jtr-wordmark.svg
Normal 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 |
56
public/partners/pinkpulse-icon.svg
Normal file
56
public/partners/pinkpulse-icon.svg
Normal 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 |
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
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 { 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
|
||||||
|
|||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
src/lib/partners.ts
Normal file
47
src/lib/partners.ts
Normal 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;
|
||||||
Reference in New Issue
Block a user