SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,027
Folders
377
Scanned Size
3.79 GB
PHP Files
791
Editable Text Files
12,311
File viewer
guarded to /htdocs
/gaylord/demo/index23.php
<?php declare(strict_types=1); /** * GayLord Spatial — adult-only social connection MVP * PHP 8.1+, SQLite by default, MySQL optional. * * Optional environment variables: * GAYLORD_DSN="mysql:host=localhost;dbname=gaylord;charset=utf8mb4" * GAYLORD_DB_USER="..." * GAYLORD_DB_PASS="..." */ const APP_NAME = 'GayLord'; const MAX_UPLOAD_BYTES = 150_000_000; const MIN_PASSWORD_LENGTH = 10; const LOCATION_STALE_MINUTES = 180; // Compatibility and server diagnostics. Errors are logged privately instead of producing a blank 500 page. function gl_starts_with(string $haystack, string $needle): bool { return $needle === '' || substr($haystack, 0, strlen($needle)) === $needle; } function gl_contains(string $haystack, string $needle): bool { return $needle === '' || strpos($haystack, $needle) !== false; } if (!function_exists('mb_strlen')) { function mb_strlen($value, $encoding = null) { return strlen((string)$value); } } if (!function_exists('mb_substr')) { function mb_substr($value, $start, $length = null, $encoding = null) { return $length === null ? substr((string)$value, (int)$start) : substr((string)$value, (int)$start, (int)$length); } } if (!function_exists('mb_strtoupper')) { function mb_strtoupper($value, $encoding = null) { return strtoupper((string)$value); } } $gaylordDebug = isset($_GET['debug']) && $_GET['debug'] === '1'; ini_set('display_errors', $gaylordDebug ? '1' : '0'); ini_set('log_errors', '1'); error_reporting(E_ALL); set_exception_handler(function (Throwable $error) use ($gaylordDebug): void { $directory = __DIR__ . '/_gaylord_private'; if (!is_dir($directory)) { @mkdir($directory, 0700, true); } $message = '[' . gmdate('c') . '] ' . get_class($error) . ': ' . $error->getMessage() . ' in ' . $error->getFile() . ':' . $error->getLine() . "\n" . $error->getTraceAsString() . "\n\n"; @file_put_contents($directory . '/error.log', $message, FILE_APPEND | LOCK_EX); if (!headers_sent()) { http_response_code(500); header('Content-Type: text/html; charset=utf-8'); } $detail = $gaylordDebug ? htmlspecialchars($error->getMessage(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') : 'The server could not start GayLord.'; echo '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GayLord setup error</title>' . '<style>body{margin:0;background:#07070a;color:#fff;font-family:system-ui;padding:32px}.box{max-width:760px;margin:auto;background:#15151b;border:1px solid #333;border-radius:20px;padding:28px}code{display:block;background:#050506;padding:14px;border-radius:10px;overflow:auto;color:#ffd84d}a{color:#ffd84d} /* GayLord Cosmic Spatial UI */ #cosmicNebula,#cosmicStars{position:fixed;inset:0;width:100%;height:100%;pointer-events:none} #cosmicNebula{z-index:-3;background:#020207} #cosmicStars{z-index:-2;mix-blend-mode:screen;opacity:.9} body:before{content:"";position:fixed;inset:-20%;z-index:-1;pointer-events:none;background: radial-gradient(ellipse at 18% 18%,rgba(255,0,153,.18),transparent 34%), radial-gradient(ellipse at 82% 22%,rgba(0,229,255,.16),transparent 36%), radial-gradient(ellipse at 48% 76%,rgba(112,0,255,.22),transparent 42%);filter:blur(45px);animation:cosmicDrift 18s ease-in-out infinite alternate} @keyframes cosmicDrift{0%{transform:translate3d(-2%,-1%,0) scale(1) rotate(-2deg)}100%{transform:translate3d(3%,2%,0) scale(1.08) rotate(3deg)}} .topbar{background:linear-gradient(180deg,rgba(5,5,12,.9),rgba(5,5,12,.63));box-shadow:0 16px 50px rgba(0,0,0,.28)} .brand{position:relative;text-shadow:0 0 24px rgba(255,216,77,.28)} .brand:after{content:"";position:absolute;left:0;right:0;bottom:-6px;height:2px;background:linear-gradient(90deg,#ff3d81,#ffd84d,#55d7ff,#9d76ff,#ff3d81);background-size:240% 100%;animation:rainbowFlow 5s linear infinite;border-radius:999px} @keyframes rainbowFlow{to{background-position:240% 0}} .card,.profile-card,.room-item,.conversation{backdrop-filter:blur(18px) saturate(135%)} .section-head h1{background:linear-gradient(90deg,#fff,#ffd84d 24%,#55d7ff 56%,#d596ff 78%,#fff);background-size:220% auto;-webkit-background-clip:text;background-clip:text;color:transparent;animation:rainbowFlow 8s linear infinite} .spatial-grid{position:relative;perspective:1400px} .map-card{border-color:rgba(255,255,255,.14);box-shadow:0 40px 110px rgba(0,0,0,.5),0 0 80px rgba(111,67,255,.14);transform-style:preserve-3d} .map-card:after{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(115deg,rgba(255,0,123,.08),transparent 34%,rgba(0,229,255,.09) 68%,transparent);mix-blend-mode:screen} .map{filter:saturate(1.2) contrast(1.08)} .leaflet-tile-pane{filter:hue-rotate(172deg) saturate(.72) brightness(.54) contrast(1.25)} .person-marker{width:64px!important;height:64px!important;border:3px solid rgba(255,255,255,.95);box-shadow:0 0 0 4px rgba(119,78,255,.3),0 0 28px rgba(0,229,255,.75),0 16px 38px rgba(0,0,0,.65);transform:translateZ(34px);transition:transform .22s ease,filter .22s ease;overflow:visible!important} .person-marker:before{content:"";position:absolute;inset:-10px;border-radius:50%;border:1px solid rgba(255,255,255,.45);animation:markerOrbit 2.8s linear infinite} .person-marker:after{content:"";position:absolute;inset:-5px;border-radius:50%;background:conic-gradient(from 0deg,#ff3d81,#ffd84d,#55d7ff,#9d76ff,#ff3d81);z-index:-1;filter:blur(5px);animation:spin 4s linear infinite} .person-marker:hover{transform:scale(1.16) translateY(-8px);filter:brightness(1.18)} .person-marker img,.person-marker .initial{position:relative;z-index:2;width:100%;height:100%;border-radius:50%;overflow:hidden} @keyframes spin{to{transform:rotate(360deg)}} @keyframes markerOrbit{50%{transform:scale(1.14);opacity:.25}100%{transform:rotate(360deg)}} .spatial-thumb-rail{position:absolute;z-index:650;left:18px;right:18px;bottom:18px;display:flex;gap:11px;overflow-x:auto;padding:12px;border:1px solid rgba(255,255,255,.13);border-radius:22px;background:rgba(5,5,12,.68);backdrop-filter:blur(18px) saturate(150%);scrollbar-width:none} .spatial-thumb-rail::-webkit-scrollbar{display:none} .spatial-thumb{position:relative;flex:0 0 auto;width:70px;height:78px;border-radius:20px;border:1px solid rgba(255,255,255,.18);background:#111;overflow:hidden;transform:translateZ(0);box-shadow:0 12px 28px rgba(0,0,0,.35);transition:.25s ease} .spatial-thumb:hover{transform:translateY(-7px) scale(1.07);border-color:#fff} .spatial-thumb img,.spatial-thumb .initial{width:100%;height:100%;object-fit:cover;display:grid;place-items:center;font-weight:900;font-size:24px} .spatial-thumb .thumb-distance{position:absolute;left:5px;right:5px;bottom:5px;padding:3px 5px;border-radius:8px;background:rgba(0,0,0,.7);font-size:10px;font-weight:800;text-align:center} .profile-grid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));grid-auto-rows:18px;align-items:start} .profile-card{grid-row:span 23;transform-style:preserve-3d;transition:transform .32s cubic-bezier(.2,.8,.2,1),box-shadow .32s ease;border-color:rgba(255,255,255,.13);box-shadow:0 24px 60px rgba(0,0,0,.36)} .profile-card:nth-of-type(3n+2){grid-row:span 28}.profile-card:nth-of-type(4n){grid-row:span 25} .profile-card:hover{transform:translateY(-10px) rotateX(2deg) rotateY(-2deg) scale(1.015);box-shadow:0 38px 90px rgba(0,0,0,.52),0 0 50px rgba(99,81,255,.16)} .profile-photo{transition:transform .55s ease,filter .55s ease}.profile-card:hover .profile-photo{transform:scale(1.08);filter:saturate(1.18) contrast(1.06)} .profile-card:before{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;background:linear-gradient(120deg,rgba(255,255,255,.12),transparent 24%,transparent 72%,rgba(91,216,255,.1));mix-blend-mode:screen} .leaflet-popup-content-wrapper,.leaflet-popup-tip{background:rgba(10,10,18,.94);color:#fff;border:1px solid rgba(255,255,255,.15);box-shadow:0 20px 55px rgba(0,0,0,.5)} .map-profile-popup{display:grid;grid-template-columns:64px 1fr;gap:11px;align-items:center;min-width:205px}.map-profile-popup img,.map-profile-popup .popup-initial{width:64px;height:64px;border-radius:17px;object-fit:cover;display:grid;place-items:center;background:linear-gradient(135deg,#ff3d81,#6640ff);font-size:24px;font-weight:900}.map-profile-popup strong{font-size:15px}.map-profile-popup a{display:inline-block;margin-top:6px;color:#ffd84d;font-weight:800} .cosmic-toggle{position:fixed;right:18px;bottom:calc(96px + var(--safe));z-index:900;width:44px;height:44px;border-radius:50%;border:1px solid rgba(255,255,255,.18);background:rgba(8,8,16,.72);color:#fff;backdrop-filter:blur(12px);cursor:pointer;box-shadow:0 0 25px rgba(120,78,255,.28)} body.cosmic-paused #cosmicNebula,body.cosmic-paused #cosmicStars,body.cosmic-paused:before{animation-play-state:paused;opacity:.24} @media(max-width:760px){.spatial-thumb-rail{left:10px;right:10px;bottom:10px}.map{height:68vh;min-height:520px}.map-card{min-height:520px}.profile-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.profile-card,.profile-card:nth-of-type(3n+2),.profile-card:nth-of-type(4n){grid-row:span 18;min-height:290px}.person-marker{width:56px!important;height:56px!important}.cosmic-toggle{right:12px}} .rainbow-scrollbar{position:fixed;top:0;right:0;z-index:1200;width:5px;height:0;background:linear-gradient(180deg,#ff165d,#ff9f1c,#ffe66d,#2ec4b6,#00bbf9,#9b5de5,#f15bb5);box-shadow:0 0 20px rgba(255,255,255,.75),0 0 44px rgba(95,225,255,.55);border-radius:0 0 0 12px;pointer-events:none} .cosmic-layer{position:fixed;inset:0;z-index:-4;pointer-events:none;overflow:hidden;background:#020207} .cosmic-layer:before,.cosmic-layer:after{content:"";position:absolute;inset:-22%;background:conic-gradient(from 0deg at 50% 50%,rgba(255,0,98,.28),rgba(255,156,0,.16),rgba(18,255,190,.2),rgba(0,156,255,.24),rgba(126,48,255,.28),rgba(255,0,98,.28));filter:blur(70px) saturate(150%);animation:stormSpin 28s linear infinite;transform:translateZ(-180px) scale(1.22)} .cosmic-layer:after{inset:-35%;opacity:.72;animation-direction:reverse;animation-duration:41s;mix-blend-mode:screen} @keyframes stormSpin{to{transform:translateZ(-180px) scale(1.22) rotate(360deg)}} .depth-grid{position:fixed;inset:-18vh -15vw;z-index:-1;pointer-events:none;background-image:linear-gradient(rgba(255,255,255,.035) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.035) 1px,transparent 1px);background-size:72px 72px;transform:perspective(900px) rotateX(72deg) translateY(45vh) scale(1.6);transform-origin:center bottom;mask-image:linear-gradient(to top,black,transparent 70%);animation:gridDrift 14s linear infinite} @keyframes gridDrift{to{background-position:0 72px,72px 0}} .profile-card,.map-card,.person-row,.room-item,.conversation,.porn-card{will-change:transform;transform-style:preserve-3d} .profile-card{box-shadow:0 26px 70px rgba(0,0,0,.46),0 0 0 1px rgba(255,255,255,.03) inset} .profile-card::after{content:"";position:absolute;inset:-45%;background:conic-gradient(from var(--card-angle,0deg),transparent 0 18%,rgba(255,0,119,.26),rgba(0,229,255,.24),transparent 56%);mix-blend-mode:screen;animation:cardHalo 7s linear infinite;pointer-events:none} @keyframes cardHalo{to{transform:rotate(360deg)}} .profile-info{z-index:4}.profile-shade{z-index:2}.profile-photo{z-index:1}.profile-distance{position:absolute;z-index:5;top:12px;right:12px;padding:7px 10px;border-radius:999px;background:rgba(0,0,0,.72);border:1px solid rgba(255,255,255,.25);font-size:11px;font-weight:900;backdrop-filter:blur(12px);box-shadow:0 0 24px rgba(0,229,255,.22)} .spatial-thumb::before{content:"";position:absolute;inset:-2px;border-radius:22px;padding:2px;background:conic-gradient(from 0deg,#ff165d,#ff9f1c,#ffe66d,#2ec4b6,#00bbf9,#9b5de5,#f15bb5,#ff165d);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;animation:thumbRainbow 3.4s linear infinite;pointer-events:none} @keyframes thumbRainbow{to{transform:rotate(360deg)}} .spatial-thumb .thumb-distance{z-index:4;border:1px solid rgba(255,255,255,.2);box-shadow:0 0 18px rgba(255,0,140,.28)} .leaflet-tile-pane{filter:saturate(1.32) contrast(1.08) hue-rotate(8deg)} .person-marker{position:relative;overflow:visible!important;transform:translateZ(40px)} .person-marker:after{content:"";position:absolute;inset:-8px;border-radius:50%;background:conic-gradient(#ff165d,#ffe66d,#2ec4b6,#00bbf9,#9b5de5,#ff165d);z-index:-1;filter:blur(1px);animation:thumbRainbow 4s linear infinite} .porn-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:18px}.porn-card{position:relative;overflow:hidden;min-height:310px;padding:0;border-radius:26px;background:linear-gradient(145deg,rgba(55,8,29,.72),rgba(8,8,18,.68));border:1px solid rgba(255,255,255,.15);box-shadow:0 34px 95px rgba(0,0,0,.52),0 0 55px rgba(255,0,102,.18)}.porn-card:before{content:"18+";position:absolute;z-index:3;top:15px;left:15px;padding:7px 10px;border-radius:999px;background:#ff165d;color:#fff;font-weight:900;box-shadow:0 0 30px rgba(255,22,93,.45)}.porn-card .porn-visual{height:190px;display:grid;place-items:center;background:radial-gradient(circle at 32% 35%,rgba(255,255,255,.19),transparent 16%),conic-gradient(from 180deg,#ff165d,#9b5de5,#00bbf9,#2ec4b6,#ffe66d,#ff165d);font-size:74px;transform:translateZ(40px)}.porn-card .porn-copy{padding:20px;position:relative;z-index:2}.porn-card:hover{transform:translateY(-10px) rotateX(3deg) rotateY(-3deg) scale(1.015)} .scroll-rainbow{position:fixed;inset:0;z-index:-2;pointer-events:none;background:linear-gradient(115deg,transparent 0 42%,rgba(255,0,119,.08) 49%,rgba(0,229,255,.08) 52%,transparent 59%);background-size:220% 220%;animation:scrollPrism 10s linear infinite;mix-blend-mode:screen}.scroll-rainbow.active{filter:hue-rotate(var(--scroll-hue,0deg)) saturate(1.5)} @keyframes scrollPrism{to{background-position:220% 220%}} @media(prefers-reduced-motion:reduce){#cosmicNebula,#cosmicStars,body:before,.brand:after,.section-head h1,.person-marker:before,.person-marker:after{animation:none!important}.profile-card,.spatial-thumb{transition:none}} /* Full-screen spatial shell: the page stays fixed; each view scrolls inside its own scene. */ html,body{height:100%;overflow:hidden}body{padding-bottom:0}.shell{height:calc(100dvh - 68px);max-width:1500px;overflow:auto;padding:18px 18px 104px;scrollbar-width:thin;scrollbar-color:#ff44bb transparent}.shell::-webkit-scrollbar{width:8px}.shell::-webkit-scrollbar-thumb{border-radius:20px;background:linear-gradient(#ff2f88,#8c5cff,#21d4ff,#ffd84d)}.topbar{position:relative;height:68px}.bottom-nav{grid-template-columns:repeat(8,1fr);width:min(940px,calc(100% - 20px))}.card,.market-card,.auth-console{backdrop-filter:blur(22px) saturate(1.35)} .auth-stage{height:calc(100dvh - 104px);min-height:560px;display:grid;grid-template-columns:minmax(0,1.15fr) minmax(380px,.85fr);gap:28px;align-items:center;overflow:hidden}.auth-visual{padding:clamp(12px,4vw,54px);position:relative;z-index:2}.auth-console{max-height:calc(100dvh - 130px);overflow:auto;transform:perspective(1200px) rotateY(-4deg) translateZ(20px);box-shadow:0 35px 100px #000,0 0 70px rgba(149,75,255,.28)}.auth-tabs{display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:6px;border-radius:16px;background:#090910;margin-bottom:16px}.auth-tabs button{border:0;background:transparent;color:#aaa;padding:11px;border-radius:12px;font-weight:850;cursor:pointer}.auth-tabs button.active{color:#101014;background:linear-gradient(90deg,#ffd84d,#ff5dbb,#68e7ff)}.auth-two{display:grid;grid-template-columns:1fr 1fr;gap:10px}.auth-submit{width:100%;margin-top:14px}.brand-planet{width:min(330px,42vh);aspect-ratio:1;border-radius:50%;position:relative;margin:0 auto 22px;display:grid;place-items:center;transform-style:preserve-3d;animation:planetFloat 5s ease-in-out infinite;background:radial-gradient(circle at 38% 32%,#fff 0 2%,#ff56c8 8%,#6339ff 34%,#09091a 68%);box-shadow:inset -35px -25px 70px #000,0 0 55px #ff37b7,0 0 110px #5f45ff}.brand-planet span{font-size:clamp(90px,15vw,180px);font-weight:1000;background:linear-gradient(140deg,#fff,#ffe666,#ff4cb7,#68e7ff);-webkit-background-clip:text;color:transparent;filter:drop-shadow(0 12px 20px #000);transform:translateZ(70px)}.brand-planet i,.brand-planet b{position:absolute;inset:-9%;border:4px solid rgba(255,255,255,.6);border-radius:50%;transform:rotateX(68deg) rotateZ(12deg);box-shadow:0 0 24px #4deaff,0 0 50px #ff45bd}.brand-planet b{inset:-22%;border-width:2px;transform:rotateX(72deg) rotateZ(-25deg);animation:ringSpin 8s linear infinite}@keyframes planetFloat{50%{transform:translateY(-13px) rotateY(8deg)}}@keyframes ringSpin{to{transform:rotateX(72deg) rotateZ(335deg)}} .market-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:18px}.market-card{overflow:hidden;border:1px solid rgba(255,255,255,.15);border-radius:25px;background:linear-gradient(155deg,rgba(27,20,46,.88),rgba(7,9,18,.78));box-shadow:0 25px 70px rgba(0,0,0,.5);transform-style:preserve-3d}.market-card>img,.market-placeholder{width:100%;height:230px;object-fit:cover}.market-placeholder{display:grid;place-items:center;font-size:76px;background:radial-gradient(circle at 30% 20%,#f64db7,#5d3af2 45%,#061323);color:#fff}.market-body{padding:18px}.market-kicker{display:flex;justify-content:space-between;gap:8px;color:#bdb5d8;font-size:11px;text-transform:uppercase;letter-spacing:.08em}.market-price{font-size:28px;font-weight:950;color:#ffd84d;margin:8px 0}.market-body p{color:#b9b8c8;min-height:42px}.market-seller{font-size:13px;color:#aaa}.market-actions{display:flex;gap:8px;align-items:center;margin-top:14px;flex-wrap:wrap}.market-actions form{margin:0} @media(max-width:900px){.auth-stage{grid-template-columns:1fr;height:calc(100dvh - 88px);min-height:0}.auth-visual{display:none}.auth-console{max-height:100%;transform:none}.bottom-nav{overflow-x:auto;display:flex;justify-content:flex-start}.bottom-nav a{min-width:76px}.shell{padding:12px 12px 96px}.auth-two{grid-template-columns:1fr}.market-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:560px){.market-grid{grid-template-columns:1fr}.auth-console{padding:16px}.topbar{height:60px}.shell{height:calc(100dvh - 60px)}} /* 2026-06-26 definitive geometry + illuminated navigation patch */ :root{ --nav-w:238px; --top-h:72px; --nav-light-x:50%; --nav-light-y:18%; } @media (min-width:901px){ html,body{width:100%;height:100%;overflow:hidden!important} body.is-authenticated{ display:grid!important; grid-template-columns:var(--nav-w) minmax(0,1fr)!important; grid-template-rows:var(--top-h) minmax(0,1fr)!important; padding:0!important; } body.is-authenticated>.topbar{ grid-column:2!important;grid-row:1!important; position:relative!important;inset:auto!important;left:auto!important;right:auto!important;top:auto!important; width:100%!important;height:var(--top-h)!important;min-width:0!important;margin:0!important; } body.is-authenticated>.shell{ grid-column:2!important;grid-row:2!important; position:relative!important;inset:auto!important;left:auto!important;right:auto!important;top:auto!important;bottom:auto!important; width:100%!important;height:100%!important;min-width:0!important;min-height:0!important; max-width:none!important;margin:0!important;padding:clamp(16px,1.6vw,28px)!important; overflow:auto!important;transform:none!important; } body.is-authenticated>.bottom-nav, body.is-authenticated main>.bottom-nav{ grid-column:1!important;grid-row:1 / 3!important; position:relative!important;inset:auto!important;left:auto!important;right:auto!important;top:auto!important;bottom:auto!important; width:var(--nav-w)!important;height:100%!important;min-width:0!important;margin:0!important;transform:none!important; } body.is-authenticated>.shell>.section-head, body.is-authenticated>.shell>.card, body.is-authenticated>.shell>.spatial-grid, body.is-authenticated>.shell>.profile-grid, body.is-authenticated>.shell>.market-grid, body.is-authenticated>.shell>.album-grid, body.is-authenticated>.shell>.groups-layout, body.is-authenticated>.shell>.inbox-layout, body.is-authenticated>.shell>.profile-form, body.is-authenticated>.shell>.porn-directory{width:100%!important;max-width:100%!important;min-width:0!important;margin-left:0!important;transform:none!important} body.is-authenticated.view-spatial>.shell{overflow:hidden!important} } .bottom-nav{ isolation:isolate; background: radial-gradient(320px 240px at var(--nav-light-x) var(--nav-light-y),rgba(72,190,255,.16),transparent 60%), linear-gradient(180deg,rgba(5,10,17,.985),rgba(3,7,12,.985))!important; box-shadow:18px 0 70px rgba(0,0,0,.48),inset -1px 0 rgba(255,255,255,.04)!important; } .bottom-nav:before{ content:"";position:absolute;inset:0;z-index:-1;pointer-events:none; background:linear-gradient(180deg,transparent,rgba(244,201,93,.035),transparent 72%); animation:navAmbient 8s ease-in-out infinite alternate; } .bottom-nav:after{ content:"";position:absolute;left:100%;top:0;width:1px;height:100%;pointer-events:none; background:linear-gradient(180deg,transparent,var(--gold),var(--blue),transparent); box-shadow:0 0 18px rgba(73,182,255,.55),0 0 34px rgba(244,201,93,.22); } @keyframes navAmbient{to{filter:brightness(1.25);transform:translateY(10px)}} .side-brand{position:relative;overflow:hidden;border-radius:18px;padding:12px 10px 20px!important;background:linear-gradient(145deg,rgba(255,255,255,.035),rgba(255,255,255,.008));box-shadow:inset 0 0 0 1px rgba(255,255,255,.04)} .side-brand:after{content:"";position:absolute;inset:-60% -30%;background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.24) 50%,transparent 60%);transform:translateX(-70%) rotate(8deg);animation:brandSweep 4.8s ease-in-out infinite} .side-mark{position:relative;z-index:1;background:linear-gradient(145deg,#ffe27a,#e8ad31)!important;box-shadow:0 0 0 1px rgba(255,255,255,.35),0 0 24px rgba(244,201,93,.45),0 14px 34px rgba(0,0,0,.42)!important;animation:markPulse 3s ease-in-out infinite} .side-brand strong,.side-brand small{position:relative;z-index:1} .side-brand strong{letter-spacing:-.04em;text-shadow:0 0 20px rgba(255,255,255,.18)} @keyframes brandSweep{0%,42%{transform:translateX(-75%) rotate(8deg)}72%,100%{transform:translateX(75%) rotate(8deg)}} @keyframes markPulse{50%{transform:translateY(-2px) scale(1.035);box-shadow:0 0 0 1px rgba(255,255,255,.48),0 0 36px rgba(244,201,93,.62),0 18px 38px rgba(0,0,0,.46)}} .bottom-nav a{position:relative;overflow:hidden;isolation:isolate;border:1px solid transparent!important;transition:transform .22s ease,border-color .22s ease,background .22s ease,color .22s ease,box-shadow .22s ease!important} .bottom-nav a:before{content:"";position:absolute;inset:0;z-index:-1;opacity:0;background:radial-gradient(circle at 18% 50%,rgba(244,201,93,.28),transparent 45%),linear-gradient(90deg,rgba(244,201,93,.10),rgba(73,182,255,.07));transition:opacity .22s ease} .bottom-nav a:after{content:"";position:absolute;left:-40%;top:-130%;width:36%;height:360%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.25),transparent);transform:rotate(20deg);transition:left .55s ease} .bottom-nav a:hover{transform:translateX(7px) translateZ(10px)!important;border-color:rgba(244,201,93,.28)!important;color:#fff!important;box-shadow:0 10px 30px rgba(0,0,0,.28),0 0 24px rgba(73,182,255,.08)!important} .bottom-nav a:hover:before,.bottom-nav a.active:before{opacity:1} .bottom-nav a:hover:after{left:125%} .bottom-nav a.active{background:linear-gradient(90deg,rgba(244,201,93,.18),rgba(73,182,255,.10))!important;border-color:rgba(244,201,93,.34)!important;box-shadow:inset 3px 0 0 var(--gold),0 0 28px rgba(244,201,93,.09)!important} .bottom-nav a.active .nav-icon{background:linear-gradient(145deg,#302613,#102536)!important;box-shadow:0 0 0 1px rgba(244,201,93,.28),0 0 20px rgba(244,201,93,.16)!important} .nav-icon{transition:transform .25s ease,box-shadow .25s ease,background .25s ease!important} .bottom-nav a:hover .nav-icon{transform:scale(1.12) rotate(-4deg);box-shadow:0 0 22px rgba(73,182,255,.22)} .brand.rainbow-brand{position:relative;filter:drop-shadow(0 10px 24px rgba(0,0,0,.35))} .brand.rainbow-brand span{animation:brandLetter 3.4s ease-in-out infinite,brandGlow 4.6s ease-in-out infinite!important} .brand.rainbow-brand span:nth-child(4){color:var(--gold)!important;text-shadow:0 0 18px rgba(244,201,93,.55)!important} @keyframes brandGlow{0%,100%{text-shadow:0 0 0 rgba(73,182,255,0)}50%{text-shadow:0 0 20px rgba(73,182,255,.28)}} #gaylordBrandLaunch{position:fixed;inset:0;z-index:5000;display:grid;place-items:center;background:radial-gradient(circle at 50% 45%,#10273a 0,#07111b 38%,#020508 75%);transition:opacity .65s ease,visibility .65s ease} #gaylordBrandLaunch.hide{opacity:0;visibility:hidden} .brand-launch-core{position:relative;display:grid;place-items:center;perspective:900px} .brand-launch-mark{width:130px;height:130px;border-radius:34px;display:grid;place-items:center;font-size:42px;font-weight:1000;color:#111;background:linear-gradient(145deg,#fff0a5,#f2bd3c);box-shadow:0 0 0 1px rgba(255,255,255,.55),0 0 60px rgba(244,201,93,.38),0 40px 90px rgba(0,0,0,.52);animation:launchMark 1.65s cubic-bezier(.2,.8,.2,1) both} .brand-launch-name{margin-top:24px;font-size:clamp(34px,5vw,72px);font-weight:1000;letter-spacing:-.065em;opacity:0;transform:translateY(20px);animation:launchName .8s .45s ease forwards} .brand-launch-name b{color:var(--gold)} .brand-launch-line{width:min(420px,72vw);height:2px;margin-top:15px;background:linear-gradient(90deg,transparent,var(--gold),var(--blue),transparent);transform:scaleX(0);animation:launchLine .9s .72s ease forwards;box-shadow:0 0 18px rgba(73,182,255,.45)} @keyframes launchMark{0%{opacity:0;transform:translateZ(-260px) rotateY(-55deg) scale(.35)}65%{opacity:1;transform:translateZ(28px) rotateY(8deg) scale(1.05)}100%{transform:none}} @keyframes launchName{to{opacity:1;transform:none}} @keyframes launchLine{to{transform:scaleX(1)}} @media(max-width:900px){ :root{--top-h:58px} body.is-authenticated{display:grid!important;grid-template-columns:minmax(0,1fr)!important;grid-template-rows:var(--top-h) minmax(0,1fr) 74px!important} body.is-authenticated>.topbar{grid-column:1!important;grid-row:1!important;position:relative!important;inset:auto!important;width:100%!important} body.is-authenticated>.shell{grid-column:1!important;grid-row:2!important;position:relative!important;inset:auto!important;width:100%!important;height:100%!important;padding:10px!important;overflow:auto!important} body.is-authenticated>.bottom-nav,body.is-authenticated main>.bottom-nav{grid-column:1!important;grid-row:3!important;position:relative!important;inset:auto!important;width:100%!important;height:74px!important} .bottom-nav:after{left:0;top:0;width:100%;height:1px;background:linear-gradient(90deg,transparent,var(--gold),var(--blue),transparent)} .bottom-nav a:hover{transform:none!important} } /* FINAL AUTO-HIDE FULL-VIEW PATCH — menu retracts completely to an icon rail, expands wide enough for every label, and the active panel follows it. */ :root{ --nav-collapsed:72px; --nav-expanded:320px; --nav-current:var(--nav-collapsed); } @media (min-width:901px){ body.is-authenticated{--nav-current:var(--nav-collapsed)!important} body.is-authenticated.nav-open{--nav-current:var(--nav-expanded)!important} body.is-authenticated>.topbar, body.is-authenticated>.shell{ left:var(--nav-current)!important; right:0!important; width:auto!important; max-width:none!important; transition:left .32s cubic-bezier(.22,.8,.2,1)!important; } body.is-authenticated>.shell{ padding:clamp(18px,1.55vw,30px)!important; overflow-x:hidden!important; } body.is-authenticated .bottom-nav{ width:var(--nav-current)!important; min-width:var(--nav-current)!important; max-width:var(--nav-current)!important; overflow-x:hidden!important; overflow-y:auto!important; z-index:1200!important; transition:width .32s cubic-bezier(.22,.8,.2,1), min-width .32s cubic-bezier(.22,.8,.2,1), max-width .32s cubic-bezier(.22,.8,.2,1), box-shadow .32s ease!important; } body.is-authenticated .bottom-nav a{ width:100%!important; min-width:0!important; box-sizing:border-box!important; padding:9px 14px!important; justify-content:flex-start!important; font-size:15px!important; line-height:1.2!important; letter-spacing:.01em!important; overflow:hidden!important; text-overflow:ellipsis!important; } body.is-authenticated.nav-open .bottom-nav a{ font-size:15px!important; overflow:visible!important; text-overflow:clip!important; } body.is-authenticated:not(.nav-open) .bottom-nav a{ width:52px!important; margin-inline:auto!important; padding:7px!important; justify-content:center!important; font-size:0!important; overflow:hidden!important; } body.is-authenticated .side-brand{ width:100%!important; box-sizing:border-box!important; grid-template-columns:48px minmax(0,1fr)!important; } body.is-authenticated.nav-open .side-brand strong, body.is-authenticated.nav-open .side-brand small{ display:block!important; width:auto!important; max-width:none!important; opacity:1!important; overflow:visible!important; white-space:nowrap!important; } body.is-authenticated:not(.nav-open) .side-brand{ width:52px!important; margin-inline:auto!important; grid-template-columns:48px!important; } /* Keep every page title and panel fully inside the moving content viewport. */ body.is-authenticated>.shell>.section-head, body.is-authenticated>.shell>.spatial-grid, body.is-authenticated>.shell>.profile-grid, body.is-authenticated>.shell>.market-grid, body.is-authenticated>.shell>.album-grid, body.is-authenticated>.shell>.groups-layout, body.is-authenticated>.shell>.inbox-layout, body.is-authenticated>.shell>.profile-form, body.is-authenticated>.shell>.porn-hub-layout, body.is-authenticated>.shell>.porn-directory{ position:relative; left:auto!important; right:auto!important; width:100%!important; max-width:100%!important; min-width:0!important; box-sizing:border-box!important; } body.is-authenticated.view-spatial>.shell{position:fixed!important} body.is-authenticated.view-spatial .section-head{ position:absolute!important; left:clamp(18px,1.55vw,30px)!important; right:clamp(18px,1.55vw,30px)!important; width:auto!important; } body.is-authenticated.view-spatial .spatial-grid{ position:relative!important; height:100%!important; } } @media (min-width:901px) and (max-width:1180px){ :root{--nav-expanded:286px} } </style></head><body class="view-<?=e($view)?>"><div class="box">' . '<h1>GayLord could not start</h1><p>' . $detail . '</p><p>Open <a href="server_check.php">server_check.php</a> to see the exact PHP, database-driver, and folder-permission status.</p>' . ($gaylordDebug ? '<p><strong>Debug is enabled.</strong> Remove <code>?debug=1</code> after repair.</p>' : '') . '</div></body></html>'; }); if (PHP_VERSION_ID < 70400) { throw new RuntimeException('PHP 7.4 or newer is required. Current version: ' . PHP_VERSION); } if (!extension_loaded('pdo')) { throw new RuntimeException('The PHP PDO extension is not enabled.'); } $privateDir = __DIR__ . '/_gaylord_private'; $uploadDir = $privateDir . '/uploads'; foreach ([$privateDir, $uploadDir] as $dir) { if (!is_dir($dir) && !mkdir($dir, 0700, true) && !is_dir($dir)) { http_response_code(500); exit('Unable to initialize private storage.'); } } ini_set('session.use_strict_mode', '1'); ini_set('session.cookie_httponly', '1'); ini_set('session.cookie_samesite', 'Lax'); if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (int)($_SERVER['SERVER_PORT'] ?? 0) === 443) { ini_set('session.cookie_secure', '1'); } session_name('GAYLORDSESSID'); session_start(); header('X-Frame-Options: DENY'); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: strict-origin-when-cross-origin'); header('Permissions-Policy: camera=(self), microphone=(), geolocation=(self)'); header("Content-Security-Policy: default-src 'self'; img-src 'self' data: blob: https://*.tile.openstreetmap.org; media-src 'self' blob:; style-src 'self' 'unsafe-inline' https://unpkg.com; script-src 'self' 'unsafe-inline' https://unpkg.com; connect-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'self'"); function db(): PDO { static $pdo; if ($pdo instanceof PDO) return $pdo; $dsn = getenv('GAYLORD_DSN') ?: 'sqlite:' . __DIR__ . '/_gaylord_private/gaylord.sqlite'; $pdo = new PDO($dsn, getenv('GAYLORD_DB_USER') ?: null, getenv('GAYLORD_DB_PASS') ?: null, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); if (gl_starts_with($dsn, 'sqlite:')) { $pdo->exec('PRAGMA foreign_keys = ON'); $pdo->exec('PRAGMA journal_mode = WAL'); } migrate($pdo); return $pdo; } function columnExists(PDO $pdo, string $table, string $column): bool { if ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql') { $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?'); $stmt->execute([$table, $column]); return (int)$stmt->fetchColumn() > 0; } $rows = $pdo->query('PRAGMA table_info(' . $table . ')')->fetchAll(); foreach ($rows as $row) if (($row['name'] ?? '') === $column) return true; return false; } function migrate(PDO $pdo): void { $mysql = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql'; $id = $mysql ? 'BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY' : 'INTEGER PRIMARY KEY AUTOINCREMENT'; $bool = $mysql ? 'TINYINT(1)' : 'INTEGER'; $dtDefault = $mysql ? 'CURRENT_TIMESTAMP' : "(datetime('now'))"; $pdo->exec("CREATE TABLE IF NOT EXISTS users ( id {$id}, username VARCHAR(32) NOT NULL UNIQUE, email VARCHAR(190) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, display_name VARCHAR(80) NOT NULL, birthdate VARCHAR(10) NOT NULL, bio TEXT NOT NULL DEFAULT '', city VARCHAR(100) NOT NULL DEFAULT '', pronouns VARCHAR(50) NOT NULL DEFAULT '', looking_for VARCHAR(120) NOT NULL DEFAULT '', is_online {$bool} NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT {$dtDefault}, last_seen DATETIME NULL )"); $userAdds = [ 'latitude' => 'DECIMAL(10,7) NULL', 'longitude' => 'DECIMAL(10,7) NULL', 'location_accuracy' => 'DECIMAL(10,2) NULL', 'location_updated_at' => 'DATETIME NULL', 'share_location' => "{$bool} NOT NULL DEFAULT 0", 'selfie_verified' => "{$bool} NOT NULL DEFAULT 0", 'vibe' => "VARCHAR(80) NOT NULL DEFAULT ''", ]; foreach ($userAdds as $name => $sql) if (!columnExists($pdo, 'users', $name)) $pdo->exec("ALTER TABLE users ADD COLUMN {$name} {$sql}"); $pdo->exec("CREATE TABLE IF NOT EXISTS media ( id {$id}, user_id BIGINT NOT NULL, storage_name VARCHAR(255) NOT NULL, original_name VARCHAR(255) NOT NULL, mime VARCHAR(80) NOT NULL, size_bytes BIGINT NOT NULL, is_primary {$bool} NOT NULL DEFAULT 0, is_selfie {$bool} NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT {$dtDefault} )"); if (!columnExists($pdo, 'media', 'is_selfie')) $pdo->exec("ALTER TABLE media ADD COLUMN is_selfie {$bool} NOT NULL DEFAULT 0"); $pdo->exec("CREATE TABLE IF NOT EXISTS messages ( id {$id}, sender_id BIGINT NOT NULL, recipient_id BIGINT NOT NULL, body TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT {$dtDefault}, read_at DATETIME NULL )"); $pdo->exec("CREATE TABLE IF NOT EXISTS favorites (user_id BIGINT NOT NULL, target_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT {$dtDefault}, PRIMARY KEY (user_id, target_id))"); $pdo->exec("CREATE TABLE IF NOT EXISTS blocks (blocker_id BIGINT NOT NULL, blocked_id BIGINT NOT NULL, created_at DATETIME NOT NULL DEFAULT {$dtDefault}, PRIMARY KEY (blocker_id, blocked_id))"); $pdo->exec("CREATE TABLE IF NOT EXISTS reports (id {$id}, reporter_id BIGINT NOT NULL, reported_id BIGINT NOT NULL, reason VARCHAR(80) NOT NULL, details TEXT NOT NULL DEFAULT '', status VARCHAR(30) NOT NULL DEFAULT 'open', created_at DATETIME NOT NULL DEFAULT {$dtDefault})"); $pdo->exec("CREATE TABLE IF NOT EXISTS group_rooms ( id {$id}, owner_id BIGINT NOT NULL, name VARCHAR(80) NOT NULL, description VARCHAR(240) NOT NULL DEFAULT '', visibility VARCHAR(20) NOT NULL DEFAULT 'public', radius_km DECIMAL(8,2) NOT NULL DEFAULT 50, created_at DATETIME NOT NULL DEFAULT {$dtDefault} )"); $pdo->exec("CREATE TABLE IF NOT EXISTS group_members ( room_id BIGINT NOT NULL, user_id BIGINT NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'member', joined_at DATETIME NOT NULL DEFAULT {$dtDefault}, PRIMARY KEY (room_id, user_id) )"); $pdo->exec("CREATE TABLE IF NOT EXISTS group_messages ( id {$id}, room_id BIGINT NOT NULL, sender_id BIGINT NOT NULL, body TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT {$dtDefault} )"); $pdo->exec("CREATE TABLE IF NOT EXISTS adult_videos ( id {$id}, user_id BIGINT NOT NULL, storage_name VARCHAR(255) NOT NULL, original_name VARCHAR(255) NOT NULL, mime VARCHAR(80) NOT NULL, size_bytes BIGINT NOT NULL, title VARCHAR(140) NOT NULL, description TEXT NOT NULL DEFAULT '', status VARCHAR(24) NOT NULL DEFAULT 'published', created_at DATETIME NOT NULL DEFAULT {$dtDefault} )"); $pdo->exec("CREATE TABLE IF NOT EXISTS adult_video_votes ( video_id BIGINT NOT NULL, user_id BIGINT NOT NULL, vote SMALLINT NOT NULL DEFAULT 1, rating SMALLINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT {$dtDefault}, PRIMARY KEY (video_id, user_id) )"); $pdo->exec("CREATE TABLE IF NOT EXISTS market_items ( id {$id}, seller_id BIGINT NOT NULL, title VARCHAR(120) NOT NULL, description TEXT NOT NULL DEFAULT '', price_cents BIGINT NOT NULL DEFAULT 0, category VARCHAR(60) NOT NULL DEFAULT 'Other', condition_name VARCHAR(40) NOT NULL DEFAULT 'Used', storage_name VARCHAR(255) NULL, mime VARCHAR(80) NULL, status VARCHAR(24) NOT NULL DEFAULT 'active', created_at DATETIME NOT NULL DEFAULT {$dtDefault} )"); if ($mysql) { $indexes = [ 'idx_messages_pair' => 'CREATE INDEX idx_messages_pair ON messages(sender_id, recipient_id, created_at)', 'idx_media_user' => 'CREATE INDEX idx_media_user ON media(user_id, created_at)', 'idx_group_messages_room' => 'CREATE INDEX idx_group_messages_room ON group_messages(room_id, created_at)', 'idx_users_location' => 'CREATE INDEX idx_users_location ON users(latitude, longitude)', ]; foreach ($indexes as $name => $sql) { $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND index_name = ?'); $stmt->execute([$name]); if ((int)$stmt->fetchColumn() === 0) $pdo->exec($sql); } } else { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_messages_pair ON messages(sender_id, recipient_id, created_at)'); $pdo->exec('CREATE INDEX IF NOT EXISTS idx_media_user ON media(user_id, created_at)'); $pdo->exec('CREATE INDEX IF NOT EXISTS idx_group_messages_room ON group_messages(room_id, created_at)'); $pdo->exec('CREATE INDEX IF NOT EXISTS idx_users_location ON users(latitude, longitude)'); } } function e(?string $v): string { return htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function csrf(): string { if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(24)); return $_SESSION['csrf']; } function verifyCsrf(): void { if (!hash_equals((string)($_SESSION['csrf'] ?? ''), (string)($_POST['csrf'] ?? ''))) { http_response_code(419); exit('Session expired. Reload and try again.'); } } function flash(string $type, string $message): void { $_SESSION['flash'][] = compact('type', 'message'); } function pullFlashes(): array { $f = $_SESSION['flash'] ?? []; unset($_SESSION['flash']); return is_array($f) ? $f : []; } function redirect(string $url = './') { header('Location: ' . $url, true, 303); exit; } function jsonResponse(array $data, int $status = 200) { http_response_code($status); header('Content-Type: application/json; charset=utf-8'); echo json_encode($data, JSON_UNESCAPED_SLASHES); exit; } function currentUser(): ?array { if (empty($_SESSION['uid'])) return null; $stmt = db()->prepare('SELECT * FROM users WHERE id = ?'); $stmt->execute([(int)$_SESSION['uid']]); return $stmt->fetch() ?: null; } function requireUser(): array { $u = currentUser(); if (!$u) { flash('error', 'Please sign in first.'); redirect('./'); } return $u; } function requireApiUser(): array { $u = currentUser(); if (!$u) jsonResponse(['ok'=>false,'error'=>'Authentication required'], 401); return $u; } function ageFromBirthdate(string $d): int { try { return (new DateTimeImmutable($d))->diff(new DateTimeImmutable('today'))->y; } catch (Throwable) { return 0; } } function validUsername(string $u): bool { return (bool)preg_match('/^[a-zA-Z0-9_]{3,32}$/', $u); } function findUserByUsername(string $u): ?array { $s=db()->prepare('SELECT * FROM users WHERE lower(username)=lower(?)'); $s->execute([$u]); return $s->fetch() ?: null; } function isBlockedEitherWay(int $a, int $b): bool { $s=db()->prepare('SELECT 1 FROM blocks WHERE (blocker_id=? AND blocked_id=?) OR (blocker_id=? AND blocked_id=?) LIMIT 1'); $s->execute([$a,$b,$b,$a]); return (bool)$s->fetchColumn(); } function mediaUrl(int $id): string { return './?action=media&id=' . $id; } function adultVideoUrl(int $id): string { return './?action=adult_video&id=' . $id; } function marketImageUrl(int $id): string { return './?action=market_image&id=' . $id; } function primaryMediaForUsers(array $ids): array { if (!$ids) return []; $ids = array_values(array_unique(array_map('intval', $ids))); $ph = implode(',', array_fill(0, count($ids), '?')); $s = db()->prepare("SELECT m.* FROM media m INNER JOIN (SELECT user_id, MAX(is_primary * 1000000000 + id) score FROM media WHERE user_id IN ($ph) GROUP BY user_id) p ON p.user_id=m.user_id AND (m.is_primary * 1000000000 + m.id)=p.score"); $s->execute($ids); $out=[]; foreach($s->fetchAll() as $r)$out[(int)$r['user_id']]=$r; return $out; } function avatarMarkup(array $u, ?array $m, string $class='avatar'): string { $online=(int)$u['is_online']===1?'<span class="online-dot"></span>':''; if($m && gl_starts_with((string)$m['mime'],'image/')) return '<span class="avatar-wrap"><img class="'.e($class).'" src="'.e(mediaUrl((int)$m['id'])).'" alt="">'.$online.'</span>'; return '<span class="avatar-wrap"><span class="'.e($class).' avatar-fallback">'.e(mb_strtoupper(mb_substr((string)$u['display_name'],0,1))).'</span>'.$online.'</span>'; } function haversineKm(float $aLat,float $aLng,float $bLat,float $bLng): float { $r=6371.0; $dLat=deg2rad($bLat-$aLat); $dLng=deg2rad($bLng-$aLng); $x=sin($dLat/2)**2+cos(deg2rad($aLat))*cos(deg2rad($bLat))*sin($dLng/2)**2; return $r*2*atan2(sqrt($x),sqrt(1-$x)); } function privacyOffset(int $userId, float $lat, float $lng): array { $seed = hexdec(substr(hash('sha256', date('Y-m-d') . '|' . $userId), 0, 8)); $angle = ($seed % 360) * M_PI / 180; $km = 0.8 + (($seed >> 8) % 220) / 100; $lat += ($km / 111.0) * cos($angle); $lng += ($km / max(20.0, 111.0 * cos(deg2rad($lat)))) * sin($angle); return [$lat,$lng]; } function streamPrivateMedia(int $id) { $me=requireUser(); $s=db()->prepare('SELECT * FROM media WHERE id=?'); $s->execute([$id]); $m=$s->fetch(); if(!$m || isBlockedEitherWay((int)$me['id'],(int)$m['user_id'])){http_response_code(404);exit('Not found');} $path=__DIR__.'/_gaylord_private/uploads/'.basename((string)$m['storage_name']); if(!is_file($path)){http_response_code(404);exit('Not found');} header('Content-Type: '.$m['mime']); header('Content-Length: '.filesize($path)); header('Cache-Control: private, max-age=900'); readfile($path); exit; } function streamAdultVideo(int $id) { requireUser(); $s=db()->prepare("SELECT * FROM adult_videos WHERE id=? AND status='published'"); $s->execute([$id]); $m=$s->fetch(); if(!$m){http_response_code(404);exit('Not found');} $path=__DIR__.'/_gaylord_private/uploads/'.basename((string)$m['storage_name']); if(!is_file($path)){http_response_code(404);exit('Not found');} $size=filesize($path); $start=0; $end=$size-1; header('Content-Type: '.$m['mime']); header('Accept-Ranges: bytes'); header('Cache-Control: private, max-age=300'); if(isset($_SERVER['HTTP_RANGE']) && preg_match('/bytes=(\d*)-(\d*)/', (string)$_SERVER['HTTP_RANGE'], $r)){ if($r[1]!=='')$start=(int)$r[1]; if($r[2]!=='')$end=min($end,(int)$r[2]); if($start>$end||$start>=$size){http_response_code(416);header('Content-Range: bytes */'.$size);exit;} http_response_code(206);header('Content-Range: bytes '.$start.'-'.$end.'/'.$size); } $length=$end-$start+1; header('Content-Length: '.$length); $fh=fopen($path,'rb'); fseek($fh,$start); $remaining=$length; while(!feof($fh)&&$remaining>0){$chunk=fread($fh,min(8192,$remaining));echo $chunk;$remaining-=strlen($chunk);} fclose($fh); exit; } $action=(string)($_GET['action']??''); if($action==='media') streamPrivateMedia((int)($_GET['id']??0)); if($action==='adult_video') streamAdultVideo((int)($_GET['id']??0)); if($action==='market_image'){ requireUser();$s=db()->prepare("SELECT storage_name,mime FROM market_items WHERE id=? AND status='active'");$s->execute([(int)($_GET['id']??0)]);$m=$s->fetch(); if(!$m||empty($m['storage_name'])){http_response_code(404);exit('Not found');} $path=__DIR__.'/_gaylord_private/uploads/'.basename((string)$m['storage_name']);if(!is_file($path)){http_response_code(404);exit('Not found');} header('Content-Type: '.($m['mime']?:'application/octet-stream'));header('Content-Length: '.filesize($path));header('Cache-Control: private, max-age=900');readfile($path);exit; } $api=(string)($_GET['api']??''); if($api!==''){ $me=requireApiUser(); $myId=(int)$me['id']; if($api==='nearby'){ if(!(int)$me['share_location'] || $me['latitude']===null || $me['longitude']===null) jsonResponse(['ok'=>true,'enabled'=>false,'people'=>[]]); $s=db()->prepare("SELECT id,username,display_name,birthdate,city,vibe,is_online,latitude,longitude,location_updated_at FROM users WHERE id<>? AND share_location=1 AND latitude IS NOT NULL AND longitude IS NOT NULL AND id NOT IN (SELECT blocked_id FROM blocks WHERE blocker_id=?) AND id NOT IN (SELECT blocker_id FROM blocks WHERE blocked_id=?) LIMIT 100"); $s->execute([$myId,$myId,$myId]); $rows=$s->fetchAll(); $media=primaryMediaForUsers(array_column($rows,'id')); $people=[]; foreach($rows as $u){ $d=haversineKm((float)$me['latitude'],(float)$me['longitude'],(float)$u['latitude'],(float)$u['longitude']); if($d>160)continue; [$plat,$plng]=privacyOffset((int)$u['id'],(float)$u['latitude'],(float)$u['longitude']); $people[]=['id'=>(int)$u['id'],'username'=>$u['username'],'name'=>$u['display_name'],'age'=>ageFromBirthdate((string)$u['birthdate']),'city'=>$u['city'],'vibe'=>$u['vibe'],'online'=>(bool)$u['is_online'],'distance_km'=>round($d,1),'distance_label'=>$d<1?'< 1 km':($d<10?round($d,1).' km':round($d).' km'),'lat'=>$plat,'lng'=>$plng,'avatar'=>isset($media[(int)$u['id']])?mediaUrl((int)$media[(int)$u['id']]['id']):null]; } usort($people,fn($a,$b)=>$a['distance_km']<=>$b['distance_km']); [$myLat,$myLng]=privacyOffset($myId,(float)$me['latitude'],(float)$me['longitude']); jsonResponse(['ok'=>true,'enabled'=>true,'center'=>['lat'=>$myLat,'lng'=>$myLng],'people'=>array_slice($people,0,60)]); } if($api==='group_messages'){ $roomId=(int)($_GET['room']??0); $s=db()->prepare('SELECT 1 FROM group_members WHERE room_id=? AND user_id=?');$s->execute([$roomId,$myId]);if(!$s->fetchColumn())jsonResponse(['ok'=>false,'error'=>'Join the room first'],403); $s=db()->prepare('SELECT gm.id,gm.body,gm.created_at,u.id user_id,u.username,u.display_name FROM group_messages gm JOIN users u ON u.id=gm.sender_id WHERE gm.room_id=? ORDER BY gm.id DESC LIMIT 100');$s->execute([$roomId]);$rows=array_reverse($s->fetchAll());$media=primaryMediaForUsers(array_column($rows,'user_id')); foreach($rows as &$r){$r['mine']=(int)$r['user_id']===$myId;$r['avatar']=isset($media[(int)$r['user_id']])?mediaUrl((int)$media[(int)$r['user_id']]['id']):null;}unset($r); jsonResponse(['ok'=>true,'messages'=>$rows]); } jsonResponse(['ok'=>false,'error'=>'Unknown endpoint'],404); } if($_SERVER['REQUEST_METHOD']==='POST'){ verifyCsrf(); $pa=(string)($_POST['action']??''); try{ if($pa==='register'){ $username=trim((string)($_POST['username']??''));$email=strtolower(trim((string)($_POST['email']??'')));$name=trim((string)($_POST['display_name']??''));$birth=trim((string)($_POST['birthdate']??''));$pass=(string)($_POST['password']??''); if(!validUsername($username))throw new RuntimeException('Username must be 3–32 letters, numbers, or underscores.'); if(!filter_var($email,FILTER_VALIDATE_EMAIL))throw new RuntimeException('Enter a valid email address.'); if($name===''||mb_strlen($name)>80)throw new RuntimeException('Enter a display name up to 80 characters.'); if(ageFromBirthdate($birth)<18)throw new RuntimeException('GayLord is strictly for adults age 18 or older.'); if(strlen($pass)<MIN_PASSWORD_LENGTH)throw new RuntimeException('Password must be at least '.MIN_PASSWORD_LENGTH.' characters.'); if(empty($_POST['agree']))throw new RuntimeException('Accept the adult-use, consent, and conduct rules.'); $s=db()->prepare('INSERT INTO users(username,email,password_hash,display_name,birthdate,is_online,last_seen) VALUES(?,?,?,?,?,1,CURRENT_TIMESTAMP)');$s->execute([$username,$email,password_hash($pass,PASSWORD_DEFAULT),$name,$birth]);$_SESSION['uid']=(int)db()->lastInsertId();session_regenerate_id(true);redirect('./?view=profile'); } if($pa==='login'){ $id=strtolower(trim((string)($_POST['identity']??'')));$pass=(string)($_POST['password']??'');$s=db()->prepare('SELECT * FROM users WHERE lower(email)=? OR lower(username)=? LIMIT 1');$s->execute([$id,$id]);$u=$s->fetch();if(!$u||!password_verify($pass,(string)$u['password_hash']))throw new RuntimeException('The sign-in information did not match.'); $_SESSION['uid']=(int)$u['id'];session_regenerate_id(true);db()->prepare('UPDATE users SET is_online=1,last_seen=CURRENT_TIMESTAMP WHERE id=?')->execute([(int)$u['id']]);redirect('./?view=spatial'); } if($pa==='logout'){ if(!empty($_SESSION['uid']))db()->prepare('UPDATE users SET is_online=0,last_seen=CURRENT_TIMESTAMP WHERE id=?')->execute([(int)$_SESSION['uid']]); $_SESSION=[];session_regenerate_id(true);redirect('./'); } $me=requireUser();$myId=(int)$me['id']; if($pa==='update_profile'){ $name=trim((string)($_POST['display_name']??''));$bio=trim((string)($_POST['bio']??''));$city=trim((string)($_POST['city']??''));$pronouns=trim((string)($_POST['pronouns']??''));$looking=trim((string)($_POST['looking_for']??''));$vibe=trim((string)($_POST['vibe']??'')); if($name===''||mb_strlen($name)>80||mb_strlen($bio)>700||mb_strlen($city)>100||mb_strlen($pronouns)>50||mb_strlen($looking)>120||mb_strlen($vibe)>80)throw new RuntimeException('One or more profile fields is too long.'); db()->prepare('UPDATE users SET display_name=?,bio=?,city=?,pronouns=?,looking_for=?,vibe=?,last_seen=CURRENT_TIMESTAMP WHERE id=?')->execute([$name,$bio,$city,$pronouns,$looking,$vibe,$myId]);flash('success','Profile updated.');redirect('./?view=profile'); } if($pa==='update_location'){ $lat=filter_var($_POST['latitude']??null,FILTER_VALIDATE_FLOAT);$lng=filter_var($_POST['longitude']??null,FILTER_VALIDATE_FLOAT);$acc=filter_var($_POST['accuracy']??null,FILTER_VALIDATE_FLOAT); if($lat===false||$lng===false||$lat<-90||$lat>90||$lng<-180||$lng>180)throw new RuntimeException('Location could not be validated.'); db()->prepare('UPDATE users SET latitude=?,longitude=?,location_accuracy=?,location_updated_at=CURRENT_TIMESTAMP,share_location=1 WHERE id=?')->execute([$lat,$lng,$acc===false?null:$acc,$myId]); jsonResponse(['ok'=>true]); } if($pa==='disable_location'){ db()->prepare('UPDATE users SET share_location=0 WHERE id=?')->execute([$myId]);flash('success','Location sharing is off.');redirect('./?view=spatial'); } if($pa==='upload_media' || $pa==='upload_selfie'){ $field=$pa==='upload_selfie'?'selfie':'media';if(empty($_FILES[$field])||!is_array($_FILES[$field]))throw new RuntimeException('Choose media to upload.');$f=$_FILES[$field]; if((int)$f['error']!==UPLOAD_ERR_OK)throw new RuntimeException('The upload failed.');if((int)$f['size']<=0||(int)$f['size']>MAX_UPLOAD_BYTES)throw new RuntimeException('Media must be 25 MB or smaller.'); $mime=(string)(new finfo(FILEINFO_MIME_TYPE))->file((string)$f['tmp_name']);$allowed=['image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp','video/mp4'=>'mp4','video/webm'=>'webm'];if(!isset($allowed[$mime]))throw new RuntimeException('Allowed: JPEG, PNG, WebP, MP4, or WebM.'); if($pa==='upload_selfie'&&!gl_starts_with($mime,'image/'))throw new RuntimeException('Selfie must be an image.'); $storage=bin2hex(random_bytes(24)).'.'.$allowed[$mime];$target=$GLOBALS['uploadDir'].'/'.$storage;if(!move_uploaded_file((string)$f['tmp_name'],$target))throw new RuntimeException('Unable to store the upload.');chmod($target,0600); $count=(int)db()->query('SELECT COUNT(*) FROM media WHERE user_id='.$myId)->fetchColumn();$isSelfie=$pa==='upload_selfie'?1:0; db()->prepare('INSERT INTO media(user_id,storage_name,original_name,mime,size_bytes,is_primary,is_selfie) VALUES(?,?,?,?,?,?,?)')->execute([$myId,$storage,basename((string)$f['name']),$mime,(int)$f['size'],$count===0?1:0,$isSelfie]); if($isSelfie)db()->prepare('UPDATE users SET selfie_verified=1 WHERE id=?')->execute([$myId]);flash('success',$isSelfie?'Selfie added.':'Media added.');redirect('./?view=albums'); } if($pa==='create_market_item'){ $title=trim((string)($_POST['title']??''));$description=trim((string)($_POST['description']??''));$category=trim((string)($_POST['category']??'Other'));$condition=trim((string)($_POST['condition_name']??'Used')); $price=(float)($_POST['price']??0);if($title===''||mb_strlen($title)>120||mb_strlen($description)>1200||$price<0||$price>1000000)throw new RuntimeException('Enter a valid item title, description, and price.'); $storage=null;$mime=null;if(!empty($_FILES['item_image'])&&is_array($_FILES['item_image'])&&(int)$_FILES['item_image']['error']!==UPLOAD_ERR_NO_FILE){$f=$_FILES['item_image'];if((int)$f['error']!==UPLOAD_ERR_OK||$f['size']>MAX_UPLOAD_BYTES)throw new RuntimeException('Item image upload failed or is too large.');$mime=(string)(new finfo(FILEINFO_MIME_TYPE))->file((string)$f['tmp_name']);$allowed=['image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp'];if(!isset($allowed[$mime]))throw new RuntimeException('Item image must be JPEG, PNG, or WebP.');$storage='market_'.bin2hex(random_bytes(24)).'.'.$allowed[$mime];$target=$GLOBALS['uploadDir'].'/'.$storage;if(!move_uploaded_file((string)$f['tmp_name'],$target))throw new RuntimeException('Unable to store item image.');chmod($target,0600);} db()->prepare('INSERT INTO market_items(seller_id,title,description,price_cents,category,condition_name,storage_name,mime) VALUES(?,?,?,?,?,?,?,?)')->execute([$myId,$title,$description,(int)round($price*100),mb_substr($category,0,60),mb_substr($condition,0,40),$storage,$mime]);flash('success','Market item listed.');redirect('./?view=market'); } if($pa==='delete_market_item'){ $itemId=(int)($_POST['item_id']??0);$q=db()->prepare('SELECT storage_name FROM market_items WHERE id=? AND seller_id=?');$q->execute([$itemId,$myId]);$item=$q->fetch();if(!$item)throw new RuntimeException('Item not found.');db()->prepare("UPDATE market_items SET status='removed' WHERE id=? AND seller_id=?")->execute([$itemId,$myId]);if(!empty($item['storage_name']))@unlink($GLOBALS['uploadDir'].'/'.basename((string)$item['storage_name']));flash('success','Market item removed.');redirect('./?view=market'); } if($pa==='upload_adult_video'){ if(empty($_FILES['adult_video'])||!is_array($_FILES['adult_video']))throw new RuntimeException('Choose a video to upload.'); $f=$_FILES['adult_video']; if((int)$f['error']!==UPLOAD_ERR_OK)throw new RuntimeException('The video upload failed.'); if((int)$f['size']<=0||(int)$f['size']>MAX_UPLOAD_BYTES)throw new RuntimeException('Video must be 150 MB or smaller.'); $mime=(string)(new finfo(FILEINFO_MIME_TYPE))->file((string)$f['tmp_name']); $allowed=['video/mp4'=>'mp4','video/webm'=>'webm']; if(!isset($allowed[$mime]))throw new RuntimeException('Adult reels must be MP4 or WebM.'); $title=trim((string)($_POST['title']??''));$description=trim((string)($_POST['description']??'')); if($title===''||mb_strlen($title)>140||mb_strlen($description)>1000)throw new RuntimeException('Add a title up to 140 characters and description up to 1000 characters.'); if(empty($_POST['rights_confirm'])||empty($_POST['adult_confirm']))throw new RuntimeException('Confirm that every person is 18+, consenting, and that you have upload rights.'); $storage='adult_'.bin2hex(random_bytes(24)).'.'.$allowed[$mime];$target=$GLOBALS['uploadDir'].'/'.$storage; if(!move_uploaded_file((string)$f['tmp_name'],$target))throw new RuntimeException('Unable to store the video.');chmod($target,0600); db()->prepare('INSERT INTO adult_videos(user_id,storage_name,original_name,mime,size_bytes,title,description,status) VALUES(?,?,?,?,?,?,?,?)')->execute([$myId,$storage,basename((string)$f['name']),$mime,(int)$f['size'],$title,$description,'published']); flash('success','Adult reel uploaded.');redirect('./?view=porn'); } if($pa==='delete_adult_video'){ $videoId=(int)($_POST['video_id']??0);$s=db()->prepare('SELECT storage_name FROM adult_videos WHERE id=? AND user_id=?');$s->execute([$videoId,$myId]);$video=$s->fetch();if(!$video)throw new RuntimeException('Reel not found or not owned by you.');db()->prepare("UPDATE adult_videos SET status='removed' WHERE id=? AND user_id=?")->execute([$videoId,$myId]);if(!empty($video['storage_name']))@unlink($GLOBALS['uploadDir'].'/'.basename((string)$video['storage_name']));flash('success','Reel deleted.');redirect('./?view=porn'); } if($pa==='vote_adult_video'){ $videoId=(int)($_POST['video_id']??0);$vote=(int)($_POST['vote']??1);$rating=(int)($_POST['rating']??0); $vote=$vote<0?-1:1;$rating=max(0,min(5,$rating));$s=db()->prepare("SELECT id FROM adult_videos WHERE id=? AND status='published'");$s->execute([$videoId]);if(!$s->fetch())throw new RuntimeException('Video not found.'); if(db()->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql')db()->prepare('INSERT INTO adult_video_votes(video_id,user_id,vote,rating) VALUES(?,?,?,?) ON DUPLICATE KEY UPDATE vote=VALUES(vote),rating=VALUES(rating),created_at=CURRENT_TIMESTAMP')->execute([$videoId,$myId,$vote,$rating]); else db()->prepare('INSERT INTO adult_video_votes(video_id,user_id,vote,rating) VALUES(?,?,?,?) ON CONFLICT(video_id,user_id) DO UPDATE SET vote=excluded.vote,rating=excluded.rating,created_at=CURRENT_TIMESTAMP')->execute([$videoId,$myId,$vote,$rating]); redirect('./?view=porn#reel-'.$videoId); } if($pa==='set_primary'){ $mid=(int)($_POST['media_id']??0);$s=db()->prepare('SELECT id FROM media WHERE id=? AND user_id=?');$s->execute([$mid,$myId]);if(!$s->fetch())throw new RuntimeException('Media not found.');db()->beginTransaction();db()->prepare('UPDATE media SET is_primary=0 WHERE user_id=?')->execute([$myId]);db()->prepare('UPDATE media SET is_primary=1 WHERE id=? AND user_id=?')->execute([$mid,$myId]);db()->commit();redirect('./?view=albums'); } if($pa==='delete_media'){ $mid=(int)($_POST['media_id']??0);$s=db()->prepare('SELECT * FROM media WHERE id=? AND user_id=?');$s->execute([$mid,$myId]);$m=$s->fetch();if(!$m)throw new RuntimeException('Media not found.');db()->prepare('DELETE FROM media WHERE id=?')->execute([$mid]);@unlink($GLOBALS['uploadDir'].'/'.basename((string)$m['storage_name']));redirect('./?view=albums'); } if($pa==='favorite'){ $u=findUserByUsername(trim((string)($_POST['username']??'')));if(!$u||(int)$u['id']===$myId)throw new RuntimeException('Profile not found.'); if(db()->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql')db()->prepare('INSERT IGNORE INTO favorites(user_id,target_id) VALUES(?,?)')->execute([$myId,(int)$u['id']]);else db()->prepare('INSERT OR IGNORE INTO favorites(user_id,target_id) VALUES(?,?)')->execute([$myId,(int)$u['id']]); redirect((string)($_POST['return_to']??'./?view=discover')); } if($pa==='send_message'){ $u=findUserByUsername(trim((string)($_POST['to']??'')));$body=trim((string)($_POST['body']??''));if(!$u||(int)$u['id']===$myId||isBlockedEitherWay($myId,(int)$u['id']))throw new RuntimeException('Recipient is unavailable.');if($body===''||mb_strlen($body)>2000)throw new RuntimeException('Message must be 1–2000 characters.');db()->prepare('INSERT INTO messages(sender_id,recipient_id,body) VALUES(?,?,?)')->execute([$myId,(int)$u['id'],$body]);redirect('./?view=inbox&chat='.rawurlencode((string)$u['username'])); } if($pa==='delete_message'){ $messageId=(int)($_POST['message_id']??0);$s=db()->prepare('SELECT id,sender_id,recipient_id FROM messages WHERE id=? AND (sender_id=? OR recipient_id=?)');$s->execute([$messageId,$myId,$myId]);$msg=$s->fetch();if(!$msg)throw new RuntimeException('Message not found.');db()->prepare('DELETE FROM messages WHERE id=?')->execute([$messageId]);flash('success','Message deleted.');redirect((string)($_POST['return_to']??'./?view=inbox')); } if($pa==='delete_conversation'){ $u=findUserByUsername(trim((string)($_POST['username']??'')));if(!$u||(int)$u['id']===$myId)throw new RuntimeException('Conversation not found.');$other=(int)$u['id'];db()->prepare('DELETE FROM messages WHERE (sender_id=? AND recipient_id=?) OR (sender_id=? AND recipient_id=?)')->execute([$myId,$other,$other,$myId]);flash('success','Conversation cleared.');redirect('./?view=inbox'); } if($pa==='create_room'){ $name=trim((string)($_POST['name']??''));$desc=trim((string)($_POST['description']??''));if($name===''||mb_strlen($name)>80||mb_strlen($desc)>240)throw new RuntimeException('Room name or description is invalid.');$s=db()->prepare('INSERT INTO group_rooms(owner_id,name,description,visibility,radius_km) VALUES(?,?,?,\'public\',50)');$s->execute([$myId,$name,$desc]);$rid=(int)db()->lastInsertId();db()->prepare('INSERT INTO group_members(room_id,user_id,role) VALUES(?,?,\'owner\')')->execute([$rid,$myId]);redirect('./?view=groups&room='.$rid); } if($pa==='join_room'){ $rid=(int)($_POST['room_id']??0);$s=db()->prepare('SELECT id FROM group_rooms WHERE id=?');$s->execute([$rid]);if(!$s->fetch())throw new RuntimeException('Room not found.');if(db()->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql')db()->prepare('INSERT IGNORE INTO group_members(room_id,user_id) VALUES(?,?)')->execute([$rid,$myId]);else db()->prepare('INSERT OR IGNORE INTO group_members(room_id,user_id) VALUES(?,?)')->execute([$rid,$myId]);redirect('./?view=groups&room='.$rid); } if($pa==='send_group_message'){ $rid=(int)($_POST['room_id']??0);$body=trim((string)($_POST['body']??''));$s=db()->prepare('SELECT 1 FROM group_members WHERE room_id=? AND user_id=?');$s->execute([$rid,$myId]);if(!$s->fetchColumn())throw new RuntimeException('Join the room before posting.');if($body===''||mb_strlen($body)>2000)throw new RuntimeException('Message must be 1–2000 characters.');db()->prepare('INSERT INTO group_messages(room_id,sender_id,body) VALUES(?,?,?)')->execute([$rid,$myId,$body]);jsonResponse(['ok'=>true]); } if($pa==='delete_group_message'){ $messageId=(int)($_POST['message_id']??0);$s=db()->prepare('SELECT id FROM group_messages WHERE id=? AND sender_id=?');$s->execute([$messageId,$myId]);if(!$s->fetch())throw new RuntimeException('Group message not found or not owned by you.');db()->prepare('DELETE FROM group_messages WHERE id=? AND sender_id=?')->execute([$messageId,$myId]);jsonResponse(['ok'=>true]); } if($pa==='block_user'){ $u=findUserByUsername(trim((string)($_POST['username']??'')));if(!$u||(int)$u['id']===$myId)throw new RuntimeException('Profile not found.');if(db()->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql')db()->prepare('INSERT IGNORE INTO blocks(blocker_id,blocked_id) VALUES(?,?)')->execute([$myId,(int)$u['id']]);else db()->prepare('INSERT OR IGNORE INTO blocks(blocker_id,blocked_id) VALUES(?,?)')->execute([$myId,(int)$u['id']]);redirect('./?view=discover'); } if($pa==='report_user'){ $u=findUserByUsername(trim((string)($_POST['username']??'')));if(!$u)throw new RuntimeException('Profile not found.');db()->prepare('INSERT INTO reports(reporter_id,reported_id,reason,details) VALUES(?,?,?,?)')->execute([$myId,(int)$u['id'],trim((string)($_POST['reason']??'Other')),mb_substr(trim((string)($_POST['details']??'')),0,1500)]);flash('success','Report submitted.');redirect('./?view=discover'); } throw new RuntimeException('Unknown action.'); }catch(PDOException $ex){ $msg=gl_contains(strtolower($ex->getMessage()),'unique')?'That username or email is already in use.':'Database action failed.'; if($api!==''||in_array($pa,['update_location','send_group_message'],true))jsonResponse(['ok'=>false,'error'=>$msg],400);flash('error',$msg);redirect($_SERVER['HTTP_REFERER']??'./'); }catch(Throwable $ex){ if($api!==''||in_array($pa,['update_location','send_group_message'],true))jsonResponse(['ok'=>false,'error'=>$ex->getMessage()],400);flash('error',$ex->getMessage());redirect($_SERVER['HTTP_REFERER']??'./'); } } $me=currentUser();$flashes=pullFlashes();$view=(string)($_GET['view']??($me?'spatial':'welcome')); $allowedViews=['welcome','spatial','discover','groups','inbox','porn','market','albums','profile','network','fuckclub','safety'];if(!in_array($view,$allowedViews,true))$view=$me?'spatial':'welcome'; $myId=$me?(int)$me['id']:0;$myMedia=[];$myPrimary=null;$users=[];$userMedia=[];$rooms=[];$selectedRoom=null;$inboxRows=[];$chatUser=null;$chatMessages=[];$unread=0;$adultVideos=[];$marketItems=[]; if($me){ db()->prepare('UPDATE users SET is_online=1,last_seen=CURRENT_TIMESTAMP WHERE id=?')->execute([$myId]); $s=db()->prepare('SELECT * FROM media WHERE user_id=? ORDER BY is_primary DESC,id DESC');$s->execute([$myId]);$myMedia=$s->fetchAll();$myPrimary=$myMedia[0]??null; $s=db()->prepare("SELECT u.*,CASE WHEN f.target_id IS NULL THEN 0 ELSE 1 END favorited FROM users u LEFT JOIN favorites f ON f.target_id=u.id AND f.user_id=? WHERE u.id<>? AND u.id NOT IN(SELECT blocked_id FROM blocks WHERE blocker_id=?) AND u.id NOT IN(SELECT blocker_id FROM blocks WHERE blocked_id=?) ORDER BY u.is_online DESC,u.last_seen DESC LIMIT 100");$s->execute([$myId,$myId,$myId,$myId]);$users=$s->fetchAll();$userMedia=primaryMediaForUsers(array_column($users,'id')); $rooms=db()->query("SELECT r.*,u.display_name owner_name,(SELECT COUNT(*) FROM group_members gm WHERE gm.room_id=r.id) member_count,(SELECT COUNT(*) FROM group_members gm WHERE gm.room_id=r.id AND gm.user_id={$myId}) joined FROM group_rooms r JOIN users u ON u.id=r.owner_id ORDER BY r.created_at DESC")->fetchAll(); $roomId=(int)($_GET['room']??0);if($roomId){foreach($rooms as $r)if((int)$r['id']===$roomId)$selectedRoom=$r;} $s=db()->prepare("SELECT u.*,MAX(m.created_at) last_message,MAX(m.body) last_body,SUM(CASE WHEN m.recipient_id=? AND m.read_at IS NULL THEN 1 ELSE 0 END) unread FROM users u JOIN messages m ON (m.sender_id=u.id AND m.recipient_id=?) OR (m.recipient_id=u.id AND m.sender_id=?) WHERE u.id NOT IN(SELECT blocked_id FROM blocks WHERE blocker_id=?) AND u.id NOT IN(SELECT blocker_id FROM blocks WHERE blocked_id=?) GROUP BY u.id ORDER BY last_message DESC");$s->execute([$myId,$myId,$myId,$myId,$myId]);$inboxRows=$s->fetchAll(); $unread=(int)db()->query("SELECT COUNT(*) FROM messages WHERE recipient_id={$myId} AND read_at IS NULL")->fetchColumn(); $chatName=trim((string)($_GET['chat']??''));if($chatName!==''){$chatUser=findUserByUsername($chatName);if($chatUser&&!isBlockedEitherWay($myId,(int)$chatUser['id'])){$cid=(int)$chatUser['id'];$s=db()->prepare('SELECT * FROM messages WHERE (sender_id=? AND recipient_id=?) OR (sender_id=? AND recipient_id=?) ORDER BY id ASC LIMIT 300');$s->execute([$myId,$cid,$cid,$myId]);$chatMessages=$s->fetchAll();db()->prepare('UPDATE messages SET read_at=CURRENT_TIMESTAMP WHERE sender_id=? AND recipient_id=? AND read_at IS NULL')->execute([$cid,$myId]);}else$chatUser=null;} $adultVideos=db()->query("SELECT av.*,u.username,u.display_name,COALESCE(SUM(v.vote),0) score,COALESCE(AVG(NULLIF(v.rating,0)),0) avg_rating,COUNT(CASE WHEN v.rating>0 THEN 1 END) rating_count FROM adult_videos av JOIN users u ON u.id=av.user_id LEFT JOIN adult_video_votes v ON v.video_id=av.id WHERE av.status='published' GROUP BY av.id ORDER BY av.created_at DESC LIMIT 60")->fetchAll(); $marketItems=db()->query("SELECT mi.*,u.username,u.display_name FROM market_items mi JOIN users u ON u.id=mi.seller_id WHERE mi.status='active' ORDER BY mi.created_at DESC LIMIT 120")->fetchAll(); } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><meta name="theme-color" content="#07070a"> <title><?=e(APP_NAME)?> Spatial</title> <link rel="preconnect" href="https://unpkg.com"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"> <style> :root{--bg:#050507;--panel:#111116;--panel2:#18181f;--line:#2a2a33;--text:#f7f7fa;--muted:#a2a2ad;--yellow:#ffd84d;--red:#ff4d66;--green:#54e27a;--cyan:#55d7ff;--purple:#9d76ff;--safe:env(safe-area-inset-bottom)}*{box-sizing:border-box}html,body{margin:0;background:transparent;color:var(--text);font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;min-height:100%}body{padding-bottom:calc(96px + var(--safe));overflow-x:hidden;perspective:1400px}a{color:inherit;text-decoration:none}button,input,textarea,select{font:inherit}.shell{position:relative;z-index:5;max-width:1280px;margin:auto;padding:18px;transform-style:preserve-3d}.topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;justify-content:space-between;padding:12px 18px;background:rgba(5,5,7,.82);backdrop-filter:blur(18px);border-bottom:1px solid rgba(255,255,255,.06)}.brand{font-weight:900;letter-spacing:-1.2px;font-size:25px}.brand b{color:var(--yellow)}.top-actions{display:flex;gap:8px;align-items:center}.btn{border:1px solid var(--line);background:#1b1b22;color:#fff;border-radius:14px;padding:11px 15px;font-weight:750;cursor:pointer}.btn:hover{border-color:#4b4b58}.btn-primary{background:var(--yellow);color:#111;border-color:transparent}.btn-danger{background:#3b151d;color:#ff9cac;border-color:#5d222d}.icon-btn{width:43px;height:43px;padding:0;border-radius:50%;display:grid;place-items:center}.card{background:linear-gradient(180deg,rgba(25,25,32,.72),rgba(8,8,16,.58));border:1px solid var(--line);border-radius:22px;padding:20px;box-shadow:0 24px 70px rgba(0,0,0,.22)}h1,h2,h3,p{margin-top:0}h1{font-size:clamp(29px,5vw,52px);letter-spacing:-2px;margin-bottom:8px}h2{letter-spacing:-.6px}.muted,.small{color:var(--muted)}.small{font-size:12px;letter-spacing:.09em;text-transform:uppercase;font-weight:800}.field{display:grid;gap:7px;margin:14px 0}.field label{font-weight:750}.field input,.field textarea,.field select{width:100%;border:1px solid var(--line);background:#0b0b0f;color:#fff;border-radius:14px;padding:13px;outline:none}.field input:focus,.field textarea:focus{border-color:var(--yellow);box-shadow:0 0 0 3px rgba(255,216,77,.12)}.flash{max-width:900px;margin:12px auto;padding:13px 16px;border-radius:14px;background:#17341f;border:1px solid #275d37}.flash.error{background:#37171f;border-color:#652733}.welcome{min-height:82vh;display:grid;grid-template-columns:1.2fr .8fr;gap:24px;align-items:center}.hero-copy{padding:30px}.hero-orbit{position:relative;width:min(520px,88vw);aspect-ratio:1;margin:auto;border-radius:50%;background:radial-gradient(circle,#23232d 0 3%,transparent 4%),radial-gradient(circle at center,transparent 0 35%,rgba(85,215,255,.12) 36% 37%,transparent 38% 55%,rgba(157,118,255,.12) 56% 57%,transparent 58%);animation:float 6s ease-in-out infinite}.hero-orbit:before,.hero-orbit:after{content:"";position:absolute;border-radius:50%;background:linear-gradient(135deg,var(--yellow),var(--red));box-shadow:0 0 50px rgba(255,216,77,.3)}.hero-orbit:before{width:110px;height:110px;left:12%;top:20%}.hero-orbit:after{width:150px;height:150px;right:8%;bottom:18%;background:linear-gradient(135deg,var(--cyan),var(--purple))}@keyframes float{50%{transform:translateY(-10px) rotate(2deg)}}.auth-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.section-head{display:flex;align-items:end;justify-content:space-between;gap:16px;margin:22px 0}.spatial-grid{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(300px,.7fr);gap:18px}.map-card{padding:0;overflow:hidden;min-height:610px;position:relative}.map-toolbar{position:absolute;z-index:500;left:14px;right:14px;top:14px;display:flex;gap:8px;justify-content:space-between;pointer-events:none}.map-toolbar>*{pointer-events:auto}.map-status{background:rgba(8,8,12,.85);backdrop-filter:blur(10px);border:1px solid var(--line);padding:10px 13px;border-radius:14px}.map{height:610px;background:radial-gradient(circle at center,#13202b,#08080c 60%)}.leaflet-container{background:#08080c}.person-marker{border:3px solid #fff;border-radius:50%;overflow:hidden;background:#222;width:56px!important;height:56px!important;box-shadow:0 10px 35px rgba(0,0,0,.45)}.person-marker img{width:100%;height:100%;object-fit:cover}.person-marker .initial{display:grid;place-items:center;width:100%;height:100%;font-weight:900;font-size:20px}.me-pulse{width:24px;height:24px;border-radius:50%;background:var(--yellow);box-shadow:0 0 0 0 rgba(255,216,77,.6);animation:pulse 1.8s infinite}@keyframes pulse{70%{box-shadow:0 0 0 22px rgba(255,216,77,0)}100%{box-shadow:0 0 0 0 rgba(255,216,77,0)}}.nearby-list{display:grid;gap:10px;max-height:610px;overflow:auto}.person-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:12px;padding:12px;border-radius:17px;background:#121218;border:1px solid var(--line)}.avatar,.avatar-sm{width:58px;height:58px;border-radius:18px;object-fit:cover;display:grid;place-items:center}.avatar-sm{width:46px;height:46px;border-radius:14px}.avatar-fallback{background:linear-gradient(135deg,#32264b,#1a3d4f);font-weight:900}.avatar-wrap{position:relative;display:inline-flex}.online-dot{position:absolute;right:-2px;bottom:-2px;width:15px;height:15px;border-radius:50%;background:var(--green);border:3px solid var(--panel)}.distance{font-size:12px;padding:6px 9px;border-radius:999px;background:#202028;color:#d7d7df}.profile-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px}.profile-card{position:relative;overflow:hidden;min-height:330px;border-radius:24px;border:1px solid var(--line);background:#111}.profile-photo{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.profile-shade{position:absolute;inset:0;background:linear-gradient(180deg,transparent 25%,rgba(0,0,0,.92) 88%)}.profile-info{position:absolute;left:16px;right:16px;bottom:15px}.profile-actions{display:flex;gap:8px;margin-top:10px}.profile-actions .btn{padding:9px 11px}.badge{display:inline-flex;align-items:center;gap:5px;padding:6px 9px;border-radius:999px;background:rgba(0,0,0,.66);font-size:12px}.groups-layout,.inbox-layout{display:grid;grid-template-columns:320px minmax(0,1fr);gap:18px}.room-list,.conversation-list{display:grid;gap:9px;align-content:start}.room-item,.conversation{padding:14px;border-radius:17px;border:1px solid var(--line);background:#121218;display:grid;grid-template-columns:auto 1fr auto;gap:11px;align-items:center}.room-item.active,.conversation.active{border-color:var(--yellow);background:#201e14}.room-icon{width:45px;height:45px;border-radius:15px;display:grid;place-items:center;background:linear-gradient(135deg,var(--purple),var(--cyan));font-weight:900}.chat-panel{min-height:610px;display:flex;flex-direction:column;padding:0;overflow:hidden}.chat-head{padding:15px 18px;border-bottom:1px solid var(--line);display:flex;gap:11px;align-items:center}.messages{flex:1;overflow:auto;padding:20px;display:flex;flex-direction:column;gap:10px;min-height:420px}.bubble{max-width:min(76%,650px);padding:12px 14px;border-radius:17px;background:#22222a;align-self:flex-start}.bubble.mine{background:var(--yellow);color:#151515;align-self:flex-end}.bubble time{display:block;font-size:10px;opacity:.65;margin-top:5px}.message-box{display:flex;gap:10px;padding:14px;border-top:1px solid var(--line)}.message-box textarea{flex:1;min-height:50px;max-height:120px;resize:vertical;background:#0b0b0f;color:#fff;border:1px solid var(--line);border-radius:14px;padding:12px}.album-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px}.media-card{background:#111;border:1px solid var(--line);border-radius:19px;overflow:hidden}.media-card img,.media-card video{width:100%;height:250px;object-fit:cover;background:#000}.media-actions{display:flex;gap:8px;padding:11px;flex-wrap:wrap}.selfie-stage{position:fixed;inset:0;background:rgba(0,0,0,.9);z-index:1000;display:none;place-items:center;padding:20px}.selfie-stage.open{display:grid}.selfie-card{width:min(560px,100%);background:#111;border:1px solid var(--line);border-radius:24px;padding:18px}.selfie-view{width:100%;aspect-ratio:3/4;object-fit:cover;border-radius:18px;background:#000;transform:scaleX(-1)}.profile-form{display:grid;grid-template-columns:minmax(0,1fr) 330px;gap:18px}.bottom-nav{position:fixed;z-index:800;left:50%;bottom:12px;transform:translateX(-50%);width:min(760px,calc(100% - 20px));display:grid;grid-template-columns:repeat(7,1fr);padding:8px;background:rgba(17,17,22,.9);backdrop-filter:blur(18px);border:1px solid var(--line);border-radius:22px;box-shadow:0 18px 50px #000}.bottom-nav a{display:grid;place-items:center;gap:3px;padding:9px 4px;border-radius:15px;font-size:11px;color:#aaa}.bottom-nav a.active{background:#2a2717;color:var(--yellow)}.nav-icon{font-size:19px}.empty{padding:35px;text-align:center;color:var(--muted)}.pill-row{display:flex;gap:8px;flex-wrap:wrap}.pill{padding:8px 11px;border:1px solid var(--line);border-radius:999px;background:#15151b}.safety-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:15px}.modal{display:none;position:fixed;inset:0;z-index:1000;background:rgba(0,0,0,.78);place-items:center;padding:20px}.modal:target{display:grid}.modal-card{width:min(520px,100%);background:#15151b;border:1px solid var(--line);border-radius:23px;padding:22px}.close{float:right;color:var(--muted)} @media(max-width:900px){.welcome,.spatial-grid,.groups-layout,.inbox-layout,.profile-form{grid-template-columns:1fr}.auth-grid{grid-template-columns:1fr}.nearby-list{max-height:none}.map-card,.map{min-height:480px;height:480px}.groups-layout .room-list,.inbox-layout .conversation-list{max-height:260px;overflow:auto}.chat-panel{min-height:520px}.bottom-nav{grid-template-columns:repeat(7,1fr)}}@media(max-width:600px){.shell{padding:11px}.topbar{padding:10px 12px}.brand{font-size:22px}.btn{padding:10px 12px}.bottom-nav{bottom:7px}.bottom-nav a{font-size:9px}.profile-grid{grid-template-columns:1fr 1fr}.profile-card{min-height:275px}.profile-info h3{font-size:16px}.profile-actions{display:none}.map-toolbar{flex-direction:column;align-items:flex-start}.map-toolbar .btn{font-size:12px}.section-head{align-items:flex-start;flex-direction:column}} html{scroll-behavior:smooth;overflow-y:auto!important}body{overflow-y:auto!important;min-height:100vh;background:#050509!important}.shell{min-height:100vh}.rainbow-brand{display:inline-flex;gap:0;font-size:28px;font-weight:1000;letter-spacing:-1.5px;filter:drop-shadow(0 0 18px rgba(255,255,255,.18))}.rainbow-brand span{display:inline-block;background:linear-gradient(180deg,#fff 0 12%,var(--rc) 26% 78%,#fff 100%);-webkit-background-clip:text;background-clip:text;color:transparent;text-shadow:0 0 20px color-mix(in srgb,var(--rc) 70%,transparent);animation:brandWave 2.6s ease-in-out infinite}.rainbow-brand span:nth-child(1){--rc:#ff2d78}.rainbow-brand span:nth-child(2){--rc:#ff8a2a;animation-delay:.08s}.rainbow-brand span:nth-child(3){--rc:#ffe15a;animation-delay:.16s}.rainbow-brand span:nth-child(4){--rc:#4ff0a3;animation-delay:.24s}.rainbow-brand span:nth-child(5){--rc:#42c8ff;animation-delay:.32s}.rainbow-brand span:nth-child(6){--rc:#8d6cff;animation-delay:.4s}.rainbow-brand span:nth-child(7){--rc:#ff61df;animation-delay:.48s}@keyframes brandWave{50%{transform:translateY(-3px) rotateX(12deg);filter:brightness(1.3)}} body:after{content:"";position:fixed;inset:0;z-index:-5;pointer-events:none;background:radial-gradient(circle at 15% 10%,rgba(255,208,73,.12),transparent 28%),radial-gradient(circle at 83% 18%,rgba(167,84,255,.19),transparent 34%),radial-gradient(circle at 50% 88%,rgba(255,31,122,.13),transparent 40%),linear-gradient(150deg,#020204,#080511 46%,#030307);} .rainbow-title{background:linear-gradient(90deg,#ff2d78,#ff9b2f,#ffe15a,#4ff0a3,#42c8ff,#8d6cff,#ff61df);background-size:220% auto;-webkit-background-clip:text;background-clip:text;color:transparent;animation:rainbowFlow 7s linear infinite}.porn-hub-layout{display:grid;grid-template-columns:minmax(0,1fr) 310px;gap:20px;align-items:start}.reel-stage{height:calc(100vh - 165px);min-height:620px;overflow-y:auto;scroll-snap-type:y mandatory;overscroll-behavior:contain;border-radius:30px;scrollbar-width:none}.reel-stage::-webkit-scrollbar{display:none}.reel-card{position:relative;height:calc(100vh - 165px);min-height:620px;scroll-snap-align:start;overflow:hidden;border-radius:30px;border:1px solid rgba(255,255,255,.15);background:#000;box-shadow:0 35px 110px rgba(0,0,0,.6),0 0 70px rgba(128,58,255,.15)}.reel-video{width:100%;height:100%;object-fit:contain;background:#000}.reel-shade{position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,transparent 45%,rgba(0,0,0,.88) 100%)}.reel-meta{position:absolute;left:20px;right:18px;bottom:20px;z-index:3;display:flex;align-items:end;justify-content:space-between;gap:20px}.reel-title{max-width:min(680px,75%)}.reel-title h2{margin:9px 0 6px;font-size:clamp(23px,4vw,38px)}.reel-title p{margin:0;color:#ddd}.creator-chip{display:inline-block;padding:7px 10px;border-radius:999px;background:rgba(0,0,0,.58);border:1px solid rgba(255,255,255,.2);backdrop-filter:blur(10px);font-weight:850}.reel-actions{display:grid;gap:10px}.reel-action,.star-form select{min-width:54px;height:50px;border-radius:17px;border:1px solid rgba(255,255,255,.2);background:rgba(7,7,12,.72);color:#fff;backdrop-filter:blur(14px);display:grid;place-items:center;font-weight:900;cursor:pointer;padding:0 12px}.star-form select{width:92px}.porn-side{display:grid;gap:14px;position:sticky;top:84px}.ad-slot{padding:22px;border-radius:24px;background:linear-gradient(145deg,rgba(255,45,120,.18),rgba(90,64,255,.18));border:1px solid rgba(255,255,255,.17);box-shadow:0 28px 70px rgba(0,0,0,.4)}.ad-slot>span,.ad-popup>span{font-size:10px;letter-spacing:.16em;color:#ffd66b}.modal.open{display:grid}.upload-modal{max-height:90vh;overflow:auto}.check{display:flex;gap:10px;align-items:flex-start;margin:14px 0;color:#ddd}.ad-popup{position:fixed;right:18px;bottom:110px;z-index:1400;width:min(330px,calc(100vw - 36px));padding:20px;border-radius:22px;background:linear-gradient(145deg,rgba(13,10,25,.96),rgba(44,10,42,.96));border:1px solid rgba(255,255,255,.18);box-shadow:0 25px 90px rgba(0,0,0,.65),0 0 50px rgba(255,40,150,.18);transform:translateY(140%);opacity:0;transition:.45s ease}.ad-popup.show{transform:translateY(0);opacity:1}.ad-popup button{position:absolute;right:9px;top:7px;border:0;background:transparent;color:#fff;font-size:25px;cursor:pointer}.ad-popup strong{display:block;font-size:19px;margin:5px 0}.ad-popup p{margin:0 0 12px;color:#bbb}.ad-popup a{color:#ffd66b;font-weight:900}.distance-pin{position:absolute;left:50%;top:100%;transform:translate(-50%,4px);white-space:nowrap;padding:3px 7px;border-radius:999px;background:rgba(4,4,8,.88);border:1px solid rgba(255,255,255,.2);font-size:10px;font-weight:900;color:#fff;box-shadow:0 0 16px rgba(66,200,255,.35)} .porn-directory{margin-top:20px}.porn-directory-head{display:flex;align-items:end;justify-content:space-between;gap:16px;margin-bottom:14px}.porn-directory-head h2{margin:4px 0}.site-search{width:min(360px,100%);height:46px;border:1px solid rgba(255,255,255,.14);border-radius:15px;background:rgba(7,10,18,.78);color:#fff;padding:0 14px}.site-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}.site-card{position:relative;min-height:168px;padding:18px;border-radius:22px;overflow:hidden;background:linear-gradient(145deg,rgba(16,21,35,.96),rgba(7,9,16,.98));border:1px solid rgba(255,255,255,.12);box-shadow:0 22px 58px rgba(0,0,0,.38);transition:transform .22s ease,border-color .22s ease,box-shadow .22s ease}.site-card:hover{transform:translateY(-5px) rotateX(2deg);border-color:rgba(245,194,77,.5);box-shadow:0 28px 72px rgba(0,0,0,.52),0 0 26px rgba(245,194,77,.12)}.site-card:before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 85% 5%,rgba(74,144,255,.18),transparent 36%);pointer-events:none}.site-icon{position:relative;width:50px;height:50px;display:grid;place-items:center;border-radius:16px;background:linear-gradient(145deg,#f2c35d,#8d6119);color:#080a0f;font-weight:950;letter-spacing:-.04em;box-shadow:0 10px 28px rgba(242,195,93,.2)}.site-card h3{position:relative;margin:14px 0 5px}.site-card p{position:relative;margin:0 0 15px;color:#aeb5c5;font-size:13px}.site-card .btn{position:relative}.site-domain{position:absolute;top:18px;right:18px;color:#737b8d;font-size:10px;letter-spacing:.08em;text-transform:uppercase}.directory-note{margin-top:12px;color:#8e96a8;font-size:12px} @media(max-width:620px){.porn-directory-head{align-items:stretch;flex-direction:column}.site-search{width:100%}.site-grid{grid-template-columns:1fr 1fr}.site-card{min-height:154px;padding:15px}.site-domain{display:none}} @media(max-width:420px){.site-grid{grid-template-columns:1fr}} @media(max-width:900px){.porn-hub-layout{grid-template-columns:1fr}.porn-side{position:static}.reel-stage,.reel-card{height:78vh;min-height:560px}.reel-title{max-width:72%}}@media(max-width:620px){.reel-stage,.reel-card{height:72vh;min-height:520px;border-radius:22px}.reel-meta{left:14px;right:12px;bottom:14px}.reel-title p{display:none}.reel-actions{gap:7px}.porn-head{align-items:flex-start;flex-direction:column}.rainbow-brand{font-size:24px}} /* GayLord 2026 interface — restrained branded motion, left navigation, mobile-first */ :root{--nav-w:232px;--gold:#f4c95d;--blue:#49b6ff;--ink:#05070b;--panel:#0d1219;--panel2:#121a24;--line:rgba(255,255,255,.1)} html,body{background:var(--ink)!important;overflow:hidden!important}body:before,body:after,.cosmic-layer,.depth-grid,.perspective-grid,.scroll-rainbow,.rainbow-scrollbar,#cosmicNebula,#cosmicStars,.cosmic-toggle{display:none!important} .brand-backdrop{position:fixed;inset:0;z-index:-4;overflow:hidden;background:linear-gradient(135deg,#05070b 0%,#0a1018 46%,#07121a 100%);pointer-events:none}.brand-backdrop:before{content:"";position:absolute;inset:-15%;background:radial-gradient(circle at 20% 20%,rgba(73,182,255,.13),transparent 28%),radial-gradient(circle at 82% 76%,rgba(244,201,93,.10),transparent 32%);animation:brandDrift 14s ease-in-out infinite alternate}.brand-grid{position:absolute;inset:0;background-image:linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);background-size:48px 48px;transform:perspective(700px) rotateX(64deg) scale(1.7) translateY(22%);transform-origin:center bottom;mask-image:linear-gradient(to top,#000,transparent 75%)}.brand-monogram{position:absolute;right:4vw;bottom:-6vh;font-size:min(38vw,620px);font-weight:1000;letter-spacing:-.12em;color:rgba(255,255,255,.018);line-height:.8;transform:rotate(-8deg);animation:monogramFloat 10s ease-in-out infinite alternate}.brand-beam{position:absolute;width:52vw;height:1px;background:linear-gradient(90deg,transparent,var(--blue),transparent);box-shadow:0 0 24px rgba(73,182,255,.55);animation:beamMove 8s linear infinite}.beam-a{top:24%;left:-20%;transform:rotate(-16deg)}.beam-b{top:70%;right:-22%;transform:rotate(18deg);animation-delay:-4s;background:linear-gradient(90deg,transparent,var(--gold),transparent);box-shadow:0 0 24px rgba(244,201,93,.45)}@keyframes brandDrift{to{transform:translate3d(2%,-1%,0) scale(1.05)}}@keyframes monogramFloat{to{transform:translateY(-18px) rotate(-5deg)}}@keyframes beamMove{to{translate:170vw 0}} .topbar{position:fixed!important;top:0;left:var(--nav-w);right:0;height:64px!important;padding:0 22px!important;background:rgba(5,8,12,.88)!important;border-bottom:1px solid var(--line);backdrop-filter:blur(18px);z-index:850}.brand.rainbow-brand{display:flex;gap:0;font-size:28px;letter-spacing:-1.5px;perspective:700px;text-shadow:none!important}.brand.rainbow-brand span{display:inline-block;color:#fff!important;background:none!important;animation:brandLetter 3.8s ease-in-out infinite;transform-origin:50% 80%;text-shadow:none!important}.brand.rainbow-brand span:nth-child(4){color:var(--gold)!important}.brand.rainbow-brand span:nth-child(2n){animation-delay:-.35s}.brand.rainbow-brand span:nth-child(3n){animation-delay:-.7s}.brand.rainbow-brand:after{background:linear-gradient(90deg,var(--gold),var(--blue))!important;animation:none!important}@keyframes brandLetter{0%,100%{transform:translateZ(0) rotateX(0);filter:brightness(1)}50%{transform:translateY(-2px) translateZ(22px) rotateX(10deg);filter:brightness(1.28)}} .shell{position:fixed!important;left:var(--nav-w);right:0;top:64px;bottom:0;height:auto!important;max-width:none!important;margin:0!important;padding:22px 24px 34px!important;overflow:auto!important;scrollbar-width:thin;scrollbar-color:var(--gold) transparent}.bottom-nav{position:fixed!important;left:0!important;top:0!important;bottom:0!important;transform:none!important;width:var(--nav-w)!important;height:100dvh;display:flex!important;flex-direction:column;gap:5px;padding:18px 14px!important;border-radius:0!important;border:0!important;border-right:1px solid var(--line)!important;background:rgba(6,9,13,.96)!important;box-shadow:18px 0 60px rgba(0,0,0,.35)!important;overflow-y:auto;z-index:900}.side-brand{display:grid;grid-template-columns:46px 1fr;grid-template-rows:auto auto;column-gap:10px;align-items:center;padding:4px 5px 20px;margin-bottom:10px;border-bottom:1px solid var(--line)}.side-mark{grid-row:1/3;width:46px;height:46px;border-radius:15px;display:grid;place-items:center;background:linear-gradient(145deg,var(--gold),#d89d29);color:#111;font-weight:1000;box-shadow:0 10px 30px rgba(244,201,93,.18)}.side-brand strong{font-size:18px}.side-brand small{color:#7e8b9a;font-size:11px}.bottom-nav a{display:grid!important;grid-template-columns:38px 1fr!important;place-items:center start!important;gap:10px!important;padding:11px 12px!important;border-radius:14px!important;font-size:13px!important;color:#9aa7b7!important;min-width:0!important;transition:.2s ease}.bottom-nav a:hover{background:rgba(255,255,255,.05);color:#fff!important;transform:translateX(3px)}.bottom-nav a.active{background:linear-gradient(90deg,rgba(244,201,93,.18),rgba(73,182,255,.08))!important;color:#fff!important;box-shadow:inset 3px 0 0 var(--gold)}.nav-icon{width:30px;height:30px;display:grid;place-items:center;border-radius:10px;background:#111923;font-size:15px!important;color:var(--gold)} .card,.market-card,.auth-console{background:linear-gradient(180deg,rgba(15,21,30,.92),rgba(9,13,19,.92))!important;border-color:var(--line)!important;box-shadow:0 24px 70px rgba(0,0,0,.28)!important;backdrop-filter:blur(16px)}.section-head h1,.rainbow-title{background:none!important;color:#fff!important;animation:none!important}.section-head h1:after{content:"";display:block;width:64px;height:3px;margin-top:10px;border-radius:99px;background:linear-gradient(90deg,var(--gold),var(--blue))} .map-card{border:1px solid rgba(73,182,255,.22)!important;box-shadow:0 30px 90px rgba(0,0,0,.45),0 0 0 1px rgba(255,255,255,.03)!important}.map{background:#0b141d!important}.leaflet-tile-pane{filter:saturate(.88) contrast(1.08) brightness(.82)!important}.leaflet-control-zoom a{background:#101823!important;color:#fff!important;border-color:rgba(255,255,255,.1)!important}.leaflet-control-zoom a:hover{background:#182435!important}.leaflet-control-attribution{display:none}.map-status{background:rgba(7,12,18,.9)!important;border-color:rgba(73,182,255,.25)!important;box-shadow:0 12px 32px rgba(0,0,0,.35)}.person-marker{width:66px!important;height:66px!important;border:3px solid #fff!important;box-shadow:0 0 0 5px rgba(73,182,255,.18),0 12px 30px rgba(0,0,0,.6)!important}.person-marker:before,.person-marker:after{display:none!important}.person-marker .distance-pin{position:absolute;left:50%;top:100%;transform:translate(-50%,4px);white-space:nowrap;padding:4px 7px;border-radius:999px;background:#07111a;color:#fff;border:1px solid rgba(73,182,255,.35);font-size:10px;font-weight:900;box-shadow:0 6px 16px rgba(0,0,0,.45)}.me-pulse{background:var(--gold)!important;box-shadow:0 0 0 5px rgba(244,201,93,.2),0 0 25px rgba(244,201,93,.75)!important;position:relative}.me-pulse:after{content:"YOU";position:absolute;left:50%;top:28px;transform:translateX(-50%);font-size:9px;font-weight:1000;color:#111;background:var(--gold);padding:3px 5px;border-radius:5px}.spatial-thumb-rail{background:rgba(7,12,18,.88)!important;border-color:rgba(255,255,255,.1)!important}.spatial-thumb{border-radius:16px!important;border-color:rgba(73,182,255,.25)!important}.spatial-thumb .thumb-distance{background:rgba(5,9,14,.9)!important;color:#fff}.map-profile-popup a{color:var(--gold)!important}.profile-card:hover,.market-card:hover{transform:translateY(-6px)!important;box-shadow:0 30px 80px rgba(0,0,0,.45)!important}.auth-stage{height:calc(100dvh - 110px)!important}.brand-planet{background:radial-gradient(circle at 35% 30%,#fff 0 2%,var(--gold) 8%,#1d5778 38%,#07111a 70%)!important;box-shadow:inset -35px -25px 70px #000,0 0 55px rgba(73,182,255,.35),0 0 100px rgba(244,201,93,.18)!important}.brand-planet span{background:linear-gradient(145deg,#fff,var(--gold),var(--blue))!important;-webkit-background-clip:text!important;color:transparent!important}.brand-planet i,.brand-planet b{box-shadow:0 0 24px rgba(73,182,255,.45)!important;border-color:rgba(244,201,93,.58)!important} @media(max-width:900px){:root{--nav-w:0px}.topbar{left:0!important;height:58px!important;padding:0 12px!important}.shell{left:0;top:58px;bottom:74px;padding:12px 11px 18px!important}.bottom-nav{left:0!important;right:0!important;top:auto!important;bottom:0!important;width:100%!important;height:74px;display:flex!important;flex-direction:row!important;align-items:center;gap:4px;padding:7px 7px calc(7px + env(safe-area-inset-bottom))!important;border-right:0!important;border-top:1px solid var(--line)!important;overflow-x:auto;overflow-y:hidden}.side-brand{display:none}.bottom-nav a{flex:0 0 68px;display:grid!important;grid-template-columns:1fr!important;grid-template-rows:29px auto!important;place-items:center!important;gap:2px!important;padding:5px 3px!important;font-size:9px!important}.bottom-nav a:hover{transform:none}.bottom-nav a.active{box-shadow:inset 0 3px 0 var(--gold)!important}.nav-icon{width:28px;height:28px}.brand.rainbow-brand{font-size:23px}.top-actions .small{display:none}.map-card,.map{height:56vh!important;min-height:420px!important}.spatial-grid{gap:12px}.nearby-list{max-height:none!important}.profile-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:10px!important}.profile-card{min-height:280px!important}} @media(max-width:520px){.shell{padding:10px 9px 16px!important}.card{padding:14px!important;border-radius:18px!important}.map-card{padding:0!important}.map-card,.map{height:52vh!important;min-height:380px!important}.profile-grid{grid-template-columns:1fr 1fr!important}.profile-card{min-height:240px!important}.section-head{margin:12px 0!important}.section-head h1{font-size:30px!important}.market-grid{grid-template-columns:1fr!important}.auth-console{padding:14px!important}.top-actions .btn{padding:8px 10px;font-size:12px}} @media(prefers-reduced-motion:reduce){.brand-backdrop:before,.brand-monogram,.brand-beam,.brand.rainbow-brand span{animation:none!important}} /* Layout correction: viewport-filling spatial scene with layered controls. */ :root{ --nav-w:clamp(220px,15vw,310px); --top-h:clamp(64px,5.2vw,82px); --page-gap:clamp(14px,1.4vw,26px); } .topbar{left:var(--nav-w)!important;height:var(--top-h)!important;padding-inline:clamp(16px,1.8vw,30px)!important} .shell{left:var(--nav-w)!important;top:var(--top-h)!important;padding:var(--page-gap)!important} .bottom-nav{width:var(--nav-w)!important;padding:clamp(16px,1.4vw,24px)!important} .side-brand{grid-template-columns:clamp(46px,3.4vw,62px) 1fr} .side-mark{width:clamp(46px,3.4vw,62px);height:clamp(46px,3.4vw,62px);font-size:clamp(15px,1.15vw,21px)} .side-brand strong{font-size:clamp(18px,1.25vw,24px)} .bottom-nav a{font-size:clamp(13px,.9vw,16px)!important;padding:clamp(10px,.9vw,15px) clamp(11px,1vw,16px)!important} .nav-icon{width:clamp(30px,2.2vw,42px);height:clamp(30px,2.2vw,42px);font-size:clamp(15px,1.05vw,20px)!important} .brand.rainbow-brand{font-size:clamp(26px,2vw,38px)} /* Spatial screen becomes one continuous visual surface instead of two detached boxes. */ .view-spatial .shell{overflow:hidden!important} .view-spatial .section-head{ position:absolute;z-index:620;left:calc(var(--page-gap) + 18px);right:calc(var(--page-gap) + 18px);top:calc(var(--page-gap) + 14px); margin:0!important;padding:18px 20px;align-items:center; border:1px solid rgba(255,255,255,.12);border-radius:22px; background:linear-gradient(135deg,rgba(5,10,16,.92),rgba(11,20,29,.76)); box-shadow:0 20px 70px rgba(0,0,0,.42);backdrop-filter:blur(20px) saturate(1.2) } .view-spatial .section-head h1{font-size:clamp(28px,2.6vw,48px)!important;margin-bottom:3px} .view-spatial .section-head p{margin:0;max-width:780px} .view-spatial .spatial-grid{display:block;position:relative;height:100%;min-height:0} .view-spatial .map-card{position:absolute;inset:0;min-height:0!important;height:100%!important;border-radius:28px!important;overflow:hidden} .view-spatial .map{height:100%!important;min-height:0!important} .view-spatial .map-toolbar{top:132px;left:20px;right:clamp(390px,29vw,520px)} .view-spatial .nearby-list{ position:absolute;z-index:610;top:132px;right:20px;bottom:20px;width:clamp(340px,26vw,470px); max-height:none!important;overflow:auto;padding:10px;border-radius:24px; border:1px solid rgba(255,255,255,.12); background:linear-gradient(180deg,rgba(7,12,18,.9),rgba(7,12,18,.72)); box-shadow:0 24px 80px rgba(0,0,0,.5);backdrop-filter:blur(18px) } .view-spatial .nearby-list .card{background:transparent!important;box-shadow:none!important;border:0!important} .view-spatial .person-row{background:rgba(13,22,31,.88);border-color:rgba(255,255,255,.09);box-shadow:0 10px 25px rgba(0,0,0,.18)} .view-spatial .person-row:hover{transform:translateX(-4px);border-color:rgba(244,201,93,.45);background:rgba(20,31,42,.96)} .view-spatial .spatial-thumb-rail{left:20px!important;right:clamp(390px,29vw,520px)!important;bottom:20px!important} /* Other app screens use the viewport more efficiently and avoid a large empty lower half. */ .view-discover .profile-grid,.view-market .market-grid,.view-albums .album-grid{padding-bottom:24px} .groups-layout,.inbox-layout{min-height:calc(100dvh - var(--top-h) - 130px)} .chat-panel{min-height:0;height:calc(100dvh - var(--top-h) - 130px)} @media(max-width:1100px){ :root{--nav-w:210px} .view-spatial .nearby-list{width:330px} .view-spatial .map-toolbar{right:370px} .view-spatial .spatial-thumb-rail{right:370px!important} } @media(max-width:900px){ :root{--nav-w:0px;--top-h:58px} .topbar{left:0!important;height:var(--top-h)!important} .shell{left:0!important;top:var(--top-h)!important;bottom:74px!important;padding:10px!important} .view-spatial .shell{overflow:auto!important} .view-spatial .section-head{position:relative;inset:auto;margin:0 0 10px!important;padding:14px;display:grid;gap:10px} .view-spatial .section-head .pill-row{width:100%} .view-spatial .section-head .btn{flex:1} .view-spatial .spatial-grid{display:grid;height:auto;gap:10px} .view-spatial .map-card{position:relative;inset:auto;height:58dvh!important;min-height:430px!important} .view-spatial .map{height:100%!important} .view-spatial .map-toolbar{top:12px;left:12px;right:12px} .view-spatial .nearby-list{position:relative;inset:auto;width:auto;max-height:42dvh!important;padding:8px;border-radius:20px} .view-spatial .spatial-thumb-rail{left:10px!important;right:10px!important;bottom:10px!important} .groups-layout,.inbox-layout{min-height:0} .chat-panel{height:auto;min-height:520px} } @media(max-width:560px){ .view-spatial .map-card{height:54dvh!important;min-height:390px!important} .view-spatial .section-head h1{font-size:29px!important} .view-spatial .section-head p{font-size:13px} .view-spatial .map-status{max-width:62vw;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} } /* 2026-06 overlay correction: use a real application grid so the menu never covers content. */ html,body{width:100%;height:100%;min-height:100%;overflow:hidden} body.is-authenticated{ display:grid!important; grid-template-columns:var(--nav-w) minmax(0,1fr); grid-template-rows:var(--top-h) minmax(0,1fr); } body.is-authenticated>.bottom-nav, body.is-authenticated main>.bottom-nav{ grid-column:1; grid-row:1 / 3; } body.is-authenticated>.topbar{position:relative!important;inset:auto!important;grid-column:2;grid-row:1;width:auto!important;min-width:0} body.is-authenticated>.shell{position:relative!important;inset:auto!important;grid-column:2;grid-row:2;width:auto!important;min-width:0;min-height:0;height:auto!important;overflow:auto!important} body.is-authenticated main>.bottom-nav{position:fixed!important;left:0!important;top:0!important;bottom:0!important} body.is-authenticated .section-head, body.is-authenticated .card, body.is-authenticated .profile-form, body.is-authenticated .porn-directory{min-width:0;max-width:100%} body.is-authenticated .section-head{overflow:visible} body.is-authenticated.view-profile .profile-form{grid-template-columns:minmax(0,1fr) minmax(260px,340px)} body.is-authenticated.view-porn .porn-directory{margin-top:18px} body.is-authenticated .site-grid{grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr))} .site-card{min-width:0;overflow:hidden} .site-card h3,.site-card p,.site-domain{overflow-wrap:anywhere} @media(max-width:900px){ html,body{overflow:hidden} body.is-authenticated{grid-template-columns:minmax(0,1fr);grid-template-rows:var(--top-h) minmax(0,1fr) 74px} body.is-authenticated>.topbar{grid-column:1;grid-row:1} body.is-authenticated>.shell{grid-column:1;grid-row:2;bottom:auto!important;padding-bottom:18px!important} body.is-authenticated main>.bottom-nav{grid-column:1;grid-row:3;position:fixed!important;left:0!important;right:0!important;top:auto!important;bottom:0!important} body.is-authenticated.view-profile .profile-form{grid-template-columns:1fr} } @media(max-width:650px){ body.is-authenticated .topbar{padding-inline:10px!important} body.is-authenticated .top-actions .btn{padding:8px 10px} body.is-authenticated .section-head{padding-inline:2px} body.is-authenticated .site-grid{grid-template-columns:1fr} .porn-directory-head{display:grid!important;grid-template-columns:1fr!important;gap:12px} .site-search{width:100%!important;max-width:none!important} } /* FINAL LAYOUT LOCK — fixed sidebar never overlays or clips page panels. */ @media (min-width:901px){ html,body{width:100%;height:100%;overflow:hidden!important} body.is-authenticated{display:block!important;padding:0!important} body.is-authenticated>.topbar{ position:fixed!important;top:0!important;left:var(--nav-w)!important;right:0!important; width:auto!important;height:var(--top-h)!important;margin:0!important;z-index:900!important } body.is-authenticated>.shell{ position:fixed!important;top:var(--top-h)!important;left:var(--nav-w)!important;right:0!important;bottom:0!important; width:auto!important;max-width:none!important;height:auto!important;min-width:0!important;min-height:0!important; margin:0!important;padding:var(--page-gap)!important;overflow:auto!important;transform:none!important;z-index:5!important } body.is-authenticated .bottom-nav{ position:fixed!important;top:0!important;left:0!important;right:auto!important;bottom:0!important; width:var(--nav-w)!important;height:100dvh!important;margin:0!important;transform:none!important;z-index:950!important; overflow-y:auto!important;overflow-x:hidden!important } body.is-authenticated>.shell>*{min-width:0;max-width:100%} body.is-authenticated .section-head{width:100%;margin-left:0!important;margin-right:0!important} body.is-authenticated .market-grid, body.is-authenticated .profile-grid, body.is-authenticated .album-grid, body.is-authenticated .porn-directory, body.is-authenticated .groups-layout, body.is-authenticated .inbox-layout, body.is-authenticated .profile-form{width:100%;min-width:0;max-width:100%;margin-left:0!important} body.is-authenticated.view-market .market-head{position:relative!important;inset:auto!important} } @media (max-width:900px){ body.is-authenticated{display:block!important;padding:0!important;overflow:hidden!important} body.is-authenticated>.topbar{position:fixed!important;top:0!important;left:0!important;right:0!important;width:auto!important} body.is-authenticated>.shell{ position:fixed!important;top:var(--top-h)!important;left:0!important;right:0!important;bottom:74px!important; width:auto!important;max-width:none!important;height:auto!important;margin:0!important;transform:none!important; overflow:auto!important;padding:10px!important } body.is-authenticated .bottom-nav{ position:fixed!important;left:0!important;right:0!important;top:auto!important;bottom:0!important; width:100%!important;height:74px!important;transform:none!important;margin:0!important } } /* AUTO-HIDE DESKTOP MENU — collapsed rail expands on hover, focus, or edge activation. */ :root{ --nav-collapsed:78px; --nav-expanded:clamp(220px,15vw,310px); --nav-current:var(--nav-collapsed); } @media (min-width:901px){ body.is-authenticated{--nav-current:var(--nav-collapsed)} body.is-authenticated.nav-open{--nav-current:var(--nav-expanded)} body.is-authenticated>.topbar{ left:var(--nav-current)!important; transition:left .34s cubic-bezier(.2,.75,.2,1)!important; } body.is-authenticated>.shell{ left:var(--nav-current)!important; transition:left .34s cubic-bezier(.2,.75,.2,1)!important; } body.is-authenticated .bottom-nav{ width:var(--nav-current)!important; padding:14px 10px!important; transition:width .34s cubic-bezier(.2,.75,.2,1),box-shadow .34s ease!important; box-shadow:12px 0 50px rgba(0,0,0,.26),inset -1px 0 rgba(255,255,255,.06)!important; } body.is-authenticated.nav-open .bottom-nav{ box-shadow:22px 0 75px rgba(0,0,0,.46),0 0 36px rgba(65,170,255,.08),inset -1px 0 rgba(244,201,93,.14)!important; } body.is-authenticated .bottom-nav:after{ content:"";position:absolute;right:-13px;top:50%;width:13px;height:88px;transform:translateY(-50%); border-radius:0 12px 12px 0;background:linear-gradient(180deg,rgba(244,201,93,.8),rgba(73,182,255,.62)); box-shadow:0 0 20px rgba(73,182,255,.28);opacity:.62;transition:opacity .2s ease,filter .2s ease; } body.is-authenticated.nav-open .bottom-nav:after{opacity:1;filter:brightness(1.35)} body.is-authenticated .bottom-nav a{ min-height:48px;display:flex!important;align-items:center;gap:11px;white-space:nowrap; overflow:hidden;padding:8px 12px!important; } body.is-authenticated:not(.nav-open) .bottom-nav a{ width:56px;padding:8px!important;justify-content:center;font-size:0!important; } body.is-authenticated:not(.nav-open) .bottom-nav a .nav-icon{margin:0!important;flex:0 0 auto} body.is-authenticated:not(.nav-open) .bottom-nav a:hover{transform:translateX(2px) translateZ(8px)!important} body.is-authenticated .side-brand{ display:grid!important;grid-template-columns:48px minmax(0,1fr)!important;grid-template-rows:auto auto; column-gap:10px;align-items:center;min-height:72px;transition:padding .3s ease!important; } body.is-authenticated .side-mark{grid-row:1 / 3;width:48px!important;height:48px!important;min-width:48px} body.is-authenticated:not(.nav-open) .side-brand{grid-template-columns:48px!important;padding:10px 4px 16px!important;justify-content:center} body.is-authenticated:not(.nav-open) .side-brand strong, body.is-authenticated:not(.nav-open) .side-brand small{opacity:0;width:0;overflow:hidden;pointer-events:none} body.is-authenticated .side-brand strong, body.is-authenticated .side-brand small{transition:opacity .2s ease .08s,width .25s ease} body.is-authenticated:not(.nav-open) .bottom-nav .nav-icon{ width:38px!important;height:38px!important;font-size:15px!important; } body.is-authenticated.nav-open .bottom-nav a{font-size:clamp(13px,.9vw,16px)!important} /* Preserve the spatial overlays while the rail animates. */ body.is-authenticated.view-spatial .section-head, body.is-authenticated.view-spatial .nearby-list, body.is-authenticated.view-spatial .spatial-thumb-rail{transition:left .34s ease,right .34s ease} } @media (max-width:900px){ :root{--nav-current:0px} body.is-authenticated .bottom-nav:after{display:none!important} } /* GAYLORD KOSMOS definitive non-overlap geometry and visual system */ :root{--gl-rail:82px;--gl-menu:312px;--gl-current:var(--gl-rail);--gl-top:72px} @media(min-width:901px){ body.is-authenticated{display:grid!important;grid-template-columns:var(--gl-current) minmax(0,1fr)!important;grid-template-rows:var(--gl-top) minmax(0,1fr)!important;height:100dvh!important;overflow:hidden!important;transition:grid-template-columns .34s cubic-bezier(.2,.75,.2,1)!important} body.is-authenticated.nav-open{--gl-current:var(--gl-menu)} body.is-authenticated>.topbar{grid-column:2!important;grid-row:1!important;position:relative!important;inset:auto!important;width:auto!important;height:var(--gl-top)!important;min-width:0!important;z-index:60!important} body.is-authenticated>.shell{grid-column:2!important;grid-row:2!important;position:relative!important;inset:auto!important;width:auto!important;max-width:none!important;height:auto!important;min-width:0!important;min-height:0!important;margin:0!important;padding:clamp(16px,2vw,30px)!important;overflow:auto!important;z-index:5!important} body.is-authenticated>.bottom-nav{grid-column:1!important;grid-row:1/3!important;position:relative!important;inset:auto!important;transform:none!important;width:auto!important;height:auto!important;min-width:0!important;max-width:none!important;border-radius:0!important;overflow:hidden auto!important;z-index:70!important} body.is-authenticated>.flash{position:fixed!important;top:82px!important;left:calc(var(--gl-current) + 18px)!important;right:18px!important;z-index:999!important} body.is-authenticated .bottom-nav a{width:100%!important;min-width:0!important;grid-template-columns:48px minmax(0,1fr)!important;display:grid!important;justify-items:start!important;align-items:center!important;overflow:hidden!important} body.is-authenticated:not(.nav-open) .bottom-nav a{grid-template-columns:48px 0!important;padding-inline:8px!important} body.is-authenticated:not(.nav-open) .nav-label{opacity:0;width:0;overflow:hidden} body.is-authenticated.nav-open .nav-label{opacity:1;width:auto;transition:opacity .22s ease .08s} } @media(max-width:900px){ body.is-authenticated{display:grid!important;grid-template-columns:1fr!important;grid-template-rows:60px minmax(0,1fr) 78px!important;height:100dvh!important;overflow:hidden!important} body.is-authenticated>.topbar{grid-column:1!important;grid-row:1!important;position:relative!important;inset:auto!important;height:60px!important} body.is-authenticated>.shell{grid-column:1!important;grid-row:2!important;position:relative!important;inset:auto!important;width:auto!important;height:auto!important;min-height:0!important;padding:12px 12px 24px!important;overflow:auto!important} body.is-authenticated>.bottom-nav{grid-column:1!important;grid-row:3!important;position:relative!important;inset:auto!important;transform:none!important;width:100%!important;height:78px!important;border-radius:0!important;display:flex!important;overflow-x:auto!important;overflow-y:hidden!important} body.is-authenticated .bottom-nav a{flex:0 0 76px!important;min-width:76px!important;font-size:9px!important} .network-launch{padding:9px 10px!important}.top-actions>.small{display:none!important} } .rainbow-brand{position:relative;isolation:isolate;font-size:clamp(26px,2.6vw,38px)!important;font-weight:1000!important;transform-style:preserve-3d;filter:drop-shadow(0 12px 22px rgba(0,0,0,.38))} .rainbow-brand span{background:linear-gradient(180deg,#fff 0 18%,#ffe56e 28%,#ff4fbe 48%,#8c6cff 68%,#43e8ff 86%,#fff 100%)!important;background-size:100% 240%!important;-webkit-background-clip:text!important;background-clip:text!important;color:transparent!important;text-shadow:0 2px 0 rgba(0,0,0,.65),0 0 18px rgba(255,74,190,.26)!important;animation:glLetter 2.8s ease-in-out infinite,glHue 8s linear infinite!important} .rainbow-brand span:nth-child(2n){animation-delay:-.3s!important}.rainbow-brand span:nth-child(3n){animation-delay:-.65s!important} @keyframes glLetter{0%,100%{transform:translateZ(0) rotateX(0deg)}50%{transform:translateY(-4px) translateZ(32px) rotateX(18deg)}} @keyframes glHue{to{filter:hue-rotate(360deg)}} .side-brand strong{font-size:24px!important;background:linear-gradient(90deg,#fff,#ffe65e,#ff4dbe,#7c67ff,#44eaff,#fff);background-size:260% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;animation:brandFlow 5s linear infinite;text-shadow:0 8px 24px rgba(0,0,0,.55)} @keyframes brandFlow{to{background-position:260% 0}} .side-mark{animation:sideMarkPulse 2.8s ease-in-out infinite!important;box-shadow:0 0 0 1px rgba(255,255,255,.18),0 0 26px rgba(255,74,190,.28),0 0 52px rgba(65,225,255,.14)!important} @keyframes sideMarkPulse{50%{transform:translateZ(24px) rotateY(12deg) scale(1.08);filter:hue-rotate(130deg) brightness(1.25)}} .brand-backdrop:before{content:"GAYLORD";position:fixed;left:18%;top:50%;transform:translateY(-50%) rotate(-12deg);font-size:min(18vw,280px);font-weight:1000;letter-spacing:-.08em;color:transparent;-webkit-text-stroke:1px rgba(255,255,255,.025);text-shadow:0 0 80px rgba(90,70,255,.055);pointer-events:none;animation:bgBrandDrift 18s ease-in-out infinite alternate} .brand-backdrop:after{content:"KOSMOS NETWORK";position:fixed;right:3vw;bottom:4vh;font-size:clamp(18px,3vw,50px);font-weight:1000;letter-spacing:.16em;color:rgba(255,255,255,.025);transform:rotate(-7deg);pointer-events:none} @keyframes bgBrandDrift{to{transform:translate(4vw,-46%) rotate(-8deg) scale(1.06)}} .profile-form aside .avatar,.profile-form aside .avatar-fallback{width:150px!important;height:150px!important;border-radius:36px!important;margin:8px auto 22px!important;display:grid!important;animation:profileOrbit 5s ease-in-out infinite;box-shadow:0 0 0 4px rgba(255,255,255,.08),0 0 0 8px rgba(255,75,190,.08),0 24px 65px rgba(0,0,0,.5),0 0 52px rgba(66,225,255,.18)!important} @keyframes profileOrbit{0%,100%{transform:translateY(0) rotateY(-5deg)}50%{transform:translateY(-8px) rotateY(8deg) scale(1.035);filter:saturate(1.2)}} .profile-form aside{overflow:hidden;position:relative;text-align:center}.profile-form aside:before{content:"";position:absolute;inset:-40%;background:conic-gradient(transparent,rgba(255,65,190,.15),transparent,rgba(56,228,255,.13),transparent);animation:spin 12s linear infinite;pointer-events:none}.profile-form aside>*{position:relative;z-index:1}@keyframes spin{to{transform:rotate(360deg)}} .network-launch{display:inline-flex!important;align-items:center;gap:8px!important}.network-dot{width:10px;height:10px;border-radius:50%;background:#5dff91;box-shadow:0 0 0 0 rgba(93,255,145,.55);animation:networkPulse 1.7s infinite}@keyframes networkPulse{70%{box-shadow:0 0 0 10px rgba(93,255,145,0)}} .network-hero{display:grid;grid-template-columns:190px minmax(0,1fr);align-items:center;gap:28px;margin-bottom:18px;overflow:hidden}.kosmos-orb{width:160px;aspect-ratio:1;border-radius:50%;display:grid;place-items:center;position:relative;background:radial-gradient(circle at 36% 28%,#fff,#6de9ff 9%,#5142ff 28%,#16052b 62%,#03050b 100%);box-shadow:0 0 35px rgba(85,220,255,.38),0 0 100px rgba(139,80,255,.28);font-size:64px;font-weight:1000;transform-style:preserve-3d;animation:kosmosFloat 5s ease-in-out infinite}.kosmos-orb i,.kosmos-orb b{position:absolute;inset:-18px;border:1px solid rgba(255,255,255,.28);border-radius:50%;transform:rotateX(66deg);animation:spin 7s linear infinite}.kosmos-orb b{inset:-36px;transform:rotateY(68deg);animation-duration:11s;animation-direction:reverse}@keyframes kosmosFloat{50%{transform:translateY(-9px) rotateY(18deg)}} .network-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px}.status-card{display:grid;grid-template-columns:auto 1fr;gap:13px;align-items:center;padding:18px;border:1px solid rgba(255,255,255,.1);border-radius:20px;background:linear-gradient(145deg,rgba(20,25,38,.9),rgba(6,8,15,.78));box-shadow:0 18px 45px rgba(0,0,0,.25)}.status-card small{display:block;color:var(--muted);margin-top:5px}.status-light{width:16px;height:16px;border-radius:50%;background:#778;box-shadow:0 0 18px currentColor}.status-light.online{color:#53f08b;background:currentColor}.status-light.offline{color:#ff5c77;background:currentColor}.status-light.pending{color:#ffd84d;background:currentColor;animation:blink 1s infinite alternate}@keyframes blink{to{opacity:.35}}.kosmos-promo{margin-top:18px;background:linear-gradient(145deg,rgba(57,33,104,.75),rgba(9,21,35,.84))} .club-stage{min-height:calc(100% - 20px);display:grid;grid-template-columns:minmax(300px,.8fr) minmax(0,1.2fr);align-items:center;gap:clamp(30px,6vw,100px);padding:clamp(20px,4vw,60px);border-radius:32px;overflow:hidden;position:relative;background:radial-gradient(circle at 25% 50%,rgba(255,60,180,.18),transparent 32%),radial-gradient(circle at 75% 50%,rgba(60,218,255,.14),transparent 36%),linear-gradient(145deg,rgba(11,12,25,.96),rgba(26,5,35,.88))}.club-core{width:min(36vw,420px);aspect-ratio:1;position:relative;display:grid;place-items:center;margin:auto;perspective:900px}.club-bang{width:44%;aspect-ratio:1;border-radius:50%;display:grid;place-items:center;background:radial-gradient(circle at 35% 25%,#fff,#ffe955 8%,#ff3db6 26%,#5b46ff 54%,#050611 100%);font-size:clamp(48px,7vw,100px);font-weight:1000;box-shadow:0 0 45px rgba(255,70,190,.45),0 0 140px rgba(84,80,255,.38);animation:bigBang 3s ease-in-out infinite}.club-ring{position:absolute;inset:8%;border-radius:50%;border:2px solid rgba(255,255,255,.22);animation:ringSpin 8s linear infinite}.ring-two{inset:0;transform:rotateX(67deg);animation-duration:11s}.ring-three{inset:-10%;transform:rotateY(71deg);animation-duration:15s;animation-direction:reverse}@keyframes bigBang{50%{transform:scale(1.08) translateZ(35px);filter:hue-rotate(130deg) brightness(1.25)}}@keyframes ringSpin{to{transform:rotate(360deg) rotateX(62deg)}}.club-copy h1{font-size:clamp(54px,10vw,150px)!important;margin-bottom:12px}.club-copy p{font-size:clamp(16px,1.5vw,22px);max-width:780px}.club-actions{display:flex;gap:12px;flex-wrap:wrap;margin:25px 0} .chat-clear{margin-left:auto}.bubble-meta{display:flex;gap:10px;align-items:center;justify-content:flex-end;margin-top:6px}.bubble-meta form{margin:0}.message-delete{border:0;background:transparent;color:inherit;opacity:.55;font-size:10px;cursor:pointer;padding:0}.message-delete:hover{opacity:1;text-decoration:underline}.bubble.mine .message-delete{color:#241c00}.reel-actions form{display:inline-flex;margin:0} @media(max-width:760px){.network-hero,.club-stage{grid-template-columns:1fr}.kosmos-orb{width:130px}.club-core{width:min(82vw,360px)}.chat-clear .btn{padding:8px 10px;font-size:11px}.profile-form aside .avatar,.profile-form aside .avatar-fallback{width:120px!important;height:120px!important}.rainbow-brand{font-size:25px!important}} </style> </head> <body class="view-<?=e($view)?> <?= $me ? 'is-authenticated' : 'is-guest' ?>"> <div class="brand-backdrop" aria-hidden="true"><div class="brand-grid"></div><div class="brand-beam beam-a"></div><div class="brand-beam beam-b"></div><div class="brand-monogram">GL</div></div> <header class="topbar"><a class="brand rainbow-brand" href="./" aria-label="GayLord"><span>G</span><span>a</span><span>y</span><span>L</span><span>o</span><span>r</span><span>d</span></a><div class="top-actions"><?php if($me):?><a class="btn network-launch" href="?view=network"><span class="network-dot"></span>Gay Central</a><span class="small"><?=e($me['display_name'])?></span><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="logout"><button class="btn">Sign out</button></form><?php endif;?></div></header> <?php foreach($flashes as $f):?><div class="flash <?=e($f['type'])?>"><?=e($f['message'])?></div><?php endforeach;?> <main class="shell"> <?php if(!$me):?> <section class="auth-stage"> <div class="auth-visual"> <div class="brand-planet"><span>G</span><i></i><b></b></div> <div class="small">GAYLORD SPATIAL NETWORK · 18+</div><h1 class="rainbow-title">Meet nearby. Chat live. Move through the universe.</h1><p>Private messaging, spatial discovery, group rooms, reels, and a member marketplace—inside one full-screen interface.</p> </div> <div class="auth-console card"> <div class="auth-tabs"><button type="button" class="active" data-auth-tab="login">Sign in</button><button type="button" data-auth-tab="register">Create account</button></div> <form method="post" data-auth-panel="login"><h2>Welcome back</h2><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="login"><div class="field"><label>Email or username</label><input name="identity" autocomplete="username" required autofocus></div><div class="field"><label>Password</label><input type="password" name="password" autocomplete="current-password" required></div><button class="btn btn-primary auth-submit">Enter GayLord</button></form> <form method="post" data-auth-panel="register" hidden><h2>Create your profile</h2><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="register"><div class="auth-two"><div class="field"><label>Display name</label><input name="display_name" maxlength="80" required></div><div class="field"><label>Username</label><input name="username" pattern="[A-Za-z0-9_]{3,32}" required></div></div><div class="field"><label>Email</label><input type="email" name="email" required></div><div class="auth-two"><div class="field"><label>Birthdate</label><input type="date" name="birthdate" required></div><div class="field"><label>Password</label><input type="password" name="password" minlength="10" required></div></div><label class="check"><input type="checkbox" name="agree" value="1" required> I am 18+ and accept the consent and conduct rules.</label><button class="btn btn-primary auth-submit">Create account</button></form> </div> </section> <?php else:?> <?php if($view==='spatial'):?> <section class="section-head"><div><div class="small">SPATIAL DISCOVERY</div><h1>Nearby now</h1><p class="muted">Location is optional. Other members see a privacy-shifted map position, never your exact coordinates.</p></div><div class="pill-row"><button class="btn btn-primary" id="locateBtn">Use my location</button><?php if((int)$me['share_location']===1):?><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="disable_location"><button class="btn">Turn location off</button></form><?php endif;?></div></section> <div class="spatial-grid"><section class="card map-card"><div class="map-toolbar"><div class="map-status" id="mapStatus">Loading spatial field…</div><a class="btn" href="?view=discover">Grid view</a></div><div id="map" class="map"></div><div class="spatial-thumb-rail" id="spatialThumbRail" aria-label="Nearby profile thumbnails"></div></section><aside class="nearby-list" id="nearbyList"><div class="card empty">Enable location to see nearby members.</div></aside></div> <?php elseif($view==='discover'):?> <section class="section-head"><div><div class="small">DYNAMIC DISCOVERY</div><h1>Discover</h1></div><a class="btn btn-primary" href="?view=spatial">Open spatial map</a></section><div class="profile-grid"><?php foreach($users as $u):$uid=(int)$u['id'];$m=$userMedia[$uid]??null;$discoverDistance=null;if($me&&$me['latitude']!==null&&$me['longitude']!==null&&$u['latitude']!==null&&$u['longitude']!==null){$dk=haversineKm((float)$me['latitude'],(float)$me['longitude'],(float)$u['latitude'],(float)$u['longitude']);$discoverDistance=$dk<1?'< 1 km':($dk<10?round($dk,1).' km':round($dk).' km');}?><article class="profile-card tilt-card"><?php if($discoverDistance!==null):?><span class="profile-distance"><?=e($discoverDistance)?></span><?php endif;?><?php if($m&&gl_starts_with((string)$m['mime'],'image/')):?><img class="profile-photo" src="<?=e(mediaUrl((int)$m['id']))?>" alt=""><?php endif;?><div class="profile-shade"></div><div class="profile-info"><div class="badge"><?=((int)$u['is_online']===1?'● Online':'Recently active')?><?=((int)$u['selfie_verified']===1?' • Selfie':'')?></div><h3><?=e($u['display_name'])?>, <?=ageFromBirthdate((string)$u['birthdate'])?></h3><p><?=e($u['vibe']?:($u['city']?:'Open to connection'))?></p><div class="profile-actions"><a class="btn btn-primary" href="?view=inbox&chat=<?=rawurlencode((string)$u['username'])?>">Message</a><a class="btn" href="#safety-<?=$uid?>">•••</a></div></div></article><div id="safety-<?=$uid?>" class="modal"><div class="modal-card"><a class="close" href="#">Close</a><h2><?=e($u['display_name'])?></h2><p><?=e($u['bio'])?></p><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="block_user"><input type="hidden" name="username" value="<?=e($u['username'])?>"><button class="btn btn-danger">Block profile</button></form><hr style="border-color:var(--line);margin:20px 0"><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="report_user"><input type="hidden" name="username" value="<?=e($u['username'])?>"><div class="field"><label>Report reason</label><select name="reason"><option>Spam</option><option>Harassment</option><option>Impersonation</option><option>Underage concern</option><option>Non-consensual content</option><option>Other</option></select></div><div class="field"><textarea name="details" maxlength="1500" placeholder="Details"></textarea></div><button class="btn">Submit report</button></form></div></div><?php endforeach;?></div> <?php elseif($view==='groups'):?> <section class="section-head"><div><div class="small">LIVE GROUP CHAT</div><h1>Rooms</h1></div><a class="btn btn-primary" href="#new-room">Create room</a></section><div class="groups-layout"><aside class="room-list"><?php if(!$rooms):?><div class="card empty">No rooms yet.</div><?php endif;foreach($rooms as $r):?><a class="room-item <?=($selectedRoom&&(int)$selectedRoom['id']===(int)$r['id'])?'active':''?>" href="?view=groups&room=<?=(int)$r['id']?>"><span class="room-icon">#</span><span><strong><?=e($r['name'])?></strong><small class="muted" style="display:block"><?=(int)$r['member_count']?> members</small></span><span>›</span></a><?php endforeach;?></aside><section class="card chat-panel"><?php if(!$selectedRoom):?><div class="empty" style="margin:auto">Choose a room or create one.</div><?php else:?><div class="chat-head"><span class="room-icon">#</span><div><strong><?=e($selectedRoom['name'])?></strong><div class="muted"><?=e($selectedRoom['description'])?></div></div><?php if(!(int)$selectedRoom['joined']):?><form method="post" style="margin-left:auto"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="join_room"><input type="hidden" name="room_id" value="<?=(int)$selectedRoom['id']?>"><button class="btn btn-primary">Join</button></form><?php endif;?></div><div class="messages" id="groupMessages" data-room="<?=(int)$selectedRoom['id']?>" data-joined="<?=(int)$selectedRoom['joined']?>"><div class="empty">Loading messages…</div></div><?php if((int)$selectedRoom['joined']):?><form class="message-box" id="groupForm"><textarea id="groupBody" maxlength="2000" required placeholder="Message the room…"></textarea><button class="btn btn-primary">Send</button></form><?php endif;?><?php endif;?></section></div><div id="new-room" class="modal"><form class="modal-card" method="post"><a class="close" href="#">Close</a><h2>Create group room</h2><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="create_room"><div class="field"><label>Room name</label><input name="name" maxlength="80" required placeholder="Dallas After Dark"></div><div class="field"><label>Description</label><textarea name="description" maxlength="240" placeholder="What this room is for"></textarea></div><button class="btn btn-primary">Create room</button></form></div> <?php elseif($view==='inbox'):?> <section class="section-head"><div><div class="small">PRIVATE MESSAGES</div><h1>Inbox</h1></div><a class="btn btn-primary" href="?view=discover">Find people</a></section><div class="inbox-layout"><aside class="conversation-list"><?php $im=primaryMediaForUsers(array_column($inboxRows,'id'));if(!$inboxRows):?><div class="card empty">No conversations yet.</div><?php endif;foreach($inboxRows as $r):?><a class="conversation <?=($chatUser&&(int)$chatUser['id']===(int)$r['id'])?'active':''?>" href="?view=inbox&chat=<?=rawurlencode((string)$r['username'])?>"><?=avatarMarkup($r,$im[(int)$r['id']]??null,'avatar-sm')?><span><strong><?=e($r['display_name'])?></strong><small class="muted" style="display:block"><?=e(mb_substr((string)$r['last_body'],0,45))?></small></span><?php if((int)$r['unread']):?><span class="distance"><?=(int)$r['unread']?></span><?php endif;?></a><?php endforeach;?></aside><section class="card chat-panel"><?php if(!$chatUser):?><div class="empty" style="margin:auto">Select a conversation.</div><?php else:$cm=primaryMediaForUsers([(int)$chatUser['id']]);?><div class="chat-head"><?=avatarMarkup($chatUser,$cm[(int)$chatUser['id']]??null,'avatar-sm')?><div><strong><?=e($chatUser['display_name'])?></strong><div class="muted">@<?=e($chatUser['username'])?></div></div><form method="post" class="chat-clear" onsubmit="return confirm('Delete this entire conversation?')"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="delete_conversation"><input type="hidden" name="username" value="<?=e($chatUser['username'])?>"><button class="btn btn-danger" type="submit">Clear chat</button></form></div><div class="messages" id="messages"><?php if(!$chatMessages):?><div class="empty">No messages yet.</div><?php endif;foreach($chatMessages as $msg):?><div class="bubble <?=((int)$msg['sender_id']===$myId?'mine':'')?>"><div class="bubble-copy"><?=nl2br(e($msg['body']))?></div><div class="bubble-meta"><time><?=e($msg['created_at'])?></time><form method="post" onsubmit="return confirm('Delete this message?')"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="delete_message"><input type="hidden" name="message_id" value="<?=(int)$msg['id']?>"><input type="hidden" name="return_to" value="./?view=inbox&chat=<?=rawurlencode((string)$chatUser['username'])?>"><button class="message-delete" type="submit" aria-label="Delete message">Delete</button></form></div></div><?php endforeach;?></div><form class="message-box" method="post"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="send_message"><input type="hidden" name="to" value="<?=e($chatUser['username'])?>"><textarea name="body" maxlength="2000" required placeholder="Write a respectful message…"></textarea><button class="btn btn-primary">Send</button></form><?php endif;?></section></div> <?php elseif($view==='porn'):?> <section class="section-head porn-head"><div><div class="small">GAYLORD PORN HUB · 18+</div><h1 class="rainbow-title">Reels that move with you</h1><p class="muted">Built-in playback, community votes, star ratings, and creator uploads.</p></div><button class="btn btn-primary" type="button" id="openPornUpload">Upload reel</button></section> <div class="porn-hub-layout"> <section class="reel-stage" id="reelStage" aria-label="Adult video reels"> <?php if(!$adultVideos):?><article class="reel-empty card"><h2>No community reels yet</h2><p class="muted">Upload the first MP4 or WebM reel. Public launch still requires moderation and age-assurance review.</p></article><?php endif;?> <?php foreach($adultVideos as $v):?> <article class="reel-card" id="reel-<?=(int)$v['id']?>" data-reel> <video class="reel-video" controls muted loop playsinline preload="metadata" src="<?=e(adultVideoUrl((int)$v['id']))?>"></video> <div class="reel-shade"></div> <div class="reel-meta"> <div class="reel-title"><span class="creator-chip">@<?=e($v['username'])?></span><h2><?=e($v['title'])?></h2><p><?=e($v['description'])?></p></div> <div class="reel-actions"> <form method="post"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="vote_adult_video"><input type="hidden" name="video_id" value="<?=(int)$v['id']?>"><input type="hidden" name="vote" value="1"><button class="reel-action" title="Upvote">▲ <b><?=(int)$v['score']?></b></button></form> <form method="post" class="star-form"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="vote_adult_video"><input type="hidden" name="video_id" value="<?=(int)$v['id']?>"><input type="hidden" name="vote" value="1"><select name="rating" aria-label="Rate this reel" onchange="this.form.submit()"><option value="0">★ <?=number_format((float)$v['avg_rating'],1)?></option><?php for($i=5;$i>=1;$i--):?><option value="<?=$i?>"><?=$i?> stars</option><?php endfor;?></select></form> <a class="reel-action" href="?view=inbox&chat=<?=rawurlencode((string)$v['username'])?>" title="Message creator">✉</a> <?php if((int)$v['user_id']===$myId):?><form method="post" onsubmit="return confirm('Delete this reel?')"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="delete_adult_video"><input type="hidden" name="video_id" value="<?=(int)$v['id']?>"><button class="reel-action delete-reel" type="submit" title="Delete reel">×</button></form><?php endif;?> </div> </div> </article> <?php endforeach;?> </section> <aside class="porn-side"> <article class="ad-slot tilt-card"><span>SPONSORED</span><h3>GayLord Sinclair</h3><p>Books, art, systems, and queer cosmic design.</p><a class="btn" href="https://www.gaylordsinclair.com/" target="_blank" rel="noopener">Visit site</a></article> <article class="card"><h3>External feature</h3><p class="muted">Open the supplied third-party video in a new tab.</p><a class="btn btn-primary" href="https://www.xvideos.com/video.ouutuucd5d7/maximo_garcia_and_baby_nicols_in_a_sexy_pussy_fuck_" target="_blank" rel="noopener noreferrer nofollow">Open XVideos</a></article> <article class="card"><h3>Upload rules</h3><p class="muted">Only upload lawful content featuring consenting adults age 18+. You must own the content or have explicit distribution rights.</p><a class="btn" href="?view=safety">Safety center</a></article> </aside> </div> <section class="porn-directory" aria-labelledby="siteDirectoryTitle"> <div class="porn-directory-head"> <div><div class="small">EXTERNAL ADULT SITES</div><h2 id="siteDirectoryTitle">Browse outside GayLord</h2><p class="muted">Third-party sites open in a new tab. GayLord does not host or control their content.</p></div> <input class="site-search" id="siteSearch" type="search" placeholder="Search sites" aria-label="Search external adult sites"> </div> <div class="site-grid" id="siteGrid"> <?php $externalAdultSites=[ ['Gay0Day — Dallas Steele & Sir Jet','Direct HTTPS bookmark supplied by the site owner. Opens on Gay0Day in a separate tab.','https://gay0day.com/videos/126994/dallas-steele-and-sir-jet/','G0','gay0day.com'], ['Gay0Day','Gay-focused external video directory with performer, studio, and search pages.','https://gay0day.com/','G0','gay0day.com'], ['Gay0Day — Athletic William Seed','Saved HTTPS bookmark from the supplied Safari list.','https://gay0day.com/videos/292387/athletic-william-seed-tries-bottoming-with-a-muscular-top-for-the-first-time-and-he-loves-it-men/','G0','gay0day.com'], ['Gay0Day — Rocco Search','Saved performer/search page from the supplied bookmark collection.','https://gay0day.com/search/rocco/','G0','gay0day.com'], ['Gay0Day — Max Konnor & Wess Russel','Saved HTTPS bookmark from the supplied Safari list.','https://gay0day.com/videos/85344/max-konnor-wess-russel/','G0','gay0day.com'], ['Gay0Day — CB / Jake Jaxson','Saved HTTPS bookmark from the supplied Safari list.','https://gay0day.com/videos/153900/cb-jake-jaxson-s-all-saints-part-4/','G0','gay0day.com'], ['Gay0Day — Steven Lee & Cazden Hunter','Saved HTTPS bookmark from the supplied Safari list.','https://gay0day.com/videos/128758/steven-lee-cazden-hunter/','G0','gay0day.com'], ['XVideos','Large external adult video platform with search, channel, tag, and individual video pages.','https://www.xvideos.com/','XV','xvideos.com'], ['XVideos — Gay Muscles','Saved category/search link from the supplied bookmark collection.','https://www.xvideos.com/c/Gay_muscles-129','XV','xvideos.com'], ['XVideos — Featured Maximo Garcia Link','Direct HTTPS video link previously supplied for the GayLord hub.','https://www.xvideos.com/video.ouutuucd5d7/maximo_garcia_and_baby_nicols_in_a_sexy_pussy_fuck_','XV','xvideos.com'], ['xHamster','External adult video and creator platform with categories, search, performer, and video pages.','https://xhamster.com/','XH','xhamster.com'], ['xHamster — Gay Muscular','Saved category link from the supplied Safari bookmarks.','https://xhamster.com/gay/categories/muscular','XH','xhamster.com'], ['XNXX','External adult video platform with search, category, and individual video pages.','https://www.xnxx.com/','XN','xnxx.com'], ['XNXX — Gay Search','Saved search link from the supplied bookmark collection.','https://www.xnxx.com/search/gay/noir+male+gay','XN','xnxx.com'], ['iGayTube TV','Gay-focused external video index visible in the supplied bookmarks.','https://www.igaytube.tv/','IG','igaytube.tv'], ['GayPorn.com','Gay-focused category directory visible in the supplied bookmarks.','https://gayporn.com/categories','GP','gayporn.com'], ['CA3H','External adult video site visible in the supplied bookmark collection.','https://ca3h.com/','C3','ca3h.com'], ['GayMaleTube','Gay-focused external video index.','https://www.gaymaletube.com/','GM','gaymaletube.com'], ['GayBoysTube','Gay-focused external video index.','https://www.gayboystube.com/','GB','gayboystube.com'], ['BoyFriendTV','Gay-focused external video directory.','https://www.boyfriendtv.com/','BF','boyfriendtv.com'], ['Pornhub Gay','Gay category on a third-party adult platform.','https://www.pornhub.com/gayporn','PH','pornhub.com'] ]; foreach($externalAdultSites as $site): ?> <article class="site-card" data-site-card data-search="<?=e(strtolower($site[0].' '.$site[1].' '.$site[4]))?>"> <span class="site-domain"><?=e($site[4])?></span><div class="site-icon"><?=e($site[3])?></div> <h3><?=e($site[0])?></h3><p><?=e($site[1])?></p> <a class="btn" href="<?=e($site[2])?>" target="_blank" rel="noopener noreferrer nofollow">Open site ↗</a> </article> <?php endforeach;?> </div> <p class="directory-note">18+ only. Use each provider’s safety, consent, and reporting tools. External links may change without notice.</p> </section> <div class="modal" id="pornUploadModal"><form class="modal-card upload-modal" method="post" enctype="multipart/form-data"><button class="close" type="button" id="closePornUpload">Close</button><h2>Upload adult reel</h2><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="upload_adult_video"><div class="field"><label>Video</label><input type="file" name="adult_video" accept="video/mp4,video/webm" required></div><div class="field"><label>Title</label><input name="title" maxlength="140" required></div><div class="field"><label>Description</label><textarea name="description" maxlength="1000" rows="4"></textarea></div><label class="check"><input type="checkbox" name="adult_confirm" required> Every person shown is 18+ and consented to recording and publication.</label><label class="check"><input type="checkbox" name="rights_confirm" required> I own this media or have explicit rights to distribute it.</label><button class="btn btn-primary">Upload reel</button></form></div> <div class="ad-popup" id="adPopup" aria-hidden="true"><button type="button" id="closeAdPopup" aria-label="Close advertisement">×</button><span>SPONSORED</span><strong>Discover Gaylord Sinclair</strong><p>Books, art, SIMON, and web systems.</p><a href="https://www.gaylordsinclair.com/" target="_blank" rel="noopener">Explore</a></div> <?php elseif($view==='market'):?> <section class="section-head market-head"><div><div class="small">MEMBER MARKET</div><h1 class="rainbow-title">Buy, sell, connect</h1><p class="muted">Listings are member-posted. Payments happen outside GayLord; verify items and meet safely.</p></div><a class="btn btn-primary" href="#new-market-item">Sell an item</a></section> <div class="market-grid"><?php if(!$marketItems):?><div class="card empty">No items listed yet. Be the first seller.</div><?php endif;?><?php foreach($marketItems as $item):?><article class="market-card tilt-card"><?php if(!empty($item['storage_name'])):?><img src="<?=e(marketImageUrl((int)$item['id']))?>" alt="<?=e($item['title'])?>"><?php else:?><div class="market-placeholder">◆</div><?php endif;?><div class="market-body"><div class="market-kicker"><span><?=e($item['category'])?></span><span><?=e($item['condition_name'])?></span></div><h2><?=e($item['title'])?></h2><div class="market-price">$<?=number_format(((int)$item['price_cents'])/100,2)?></div><p><?=e($item['description'])?></p><div class="market-seller">Sold by <strong><?=e($item['display_name'])?></strong></div><div class="market-actions"><a class="btn btn-primary" href="?view=inbox&chat=<?=rawurlencode((string)$item['username'])?>">Message seller</a><?php if((int)$item['seller_id']===$myId):?><form method="post" onsubmit="return confirm('Remove this listing?')"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="delete_market_item"><input type="hidden" name="item_id" value="<?=(int)$item['id']?>"><button class="btn btn-danger">Remove</button></form><?php endif;?></div></div></article><?php endforeach;?></div> <div id="new-market-item" class="modal"><form class="modal-card" method="post" enctype="multipart/form-data"><a class="close" href="#">Close</a><h2>List an item</h2><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="create_market_item"><div class="field"><label>Photo</label><input type="file" name="item_image" accept="image/jpeg,image/png,image/webp"></div><div class="field"><label>Title</label><input name="title" maxlength="120" required></div><div class="auth-two"><div class="field"><label>Price</label><input name="price" type="number" min="0" max="1000000" step="0.01" required></div><div class="field"><label>Category</label><select name="category"><option>Clothing</option><option>Collectibles</option><option>Books & Art</option><option>Electronics</option><option>Home</option><option>Tickets</option><option>Other</option></select></div></div><div class="field"><label>Condition</label><select name="condition_name"><option>New</option><option selected>Used - Good</option><option>Used - Fair</option><option>Digital</option></select></div><div class="field"><label>Description</label><textarea name="description" maxlength="1200" rows="5"></textarea></div><button class="btn btn-primary">Publish listing</button></form></div> <?php elseif($view==='albums'):?> <section class="section-head"><div><div class="small">CAMERA + MEDIA</div><h1>Your album</h1></div><button class="btn btn-primary" id="openSelfie">Take selfie</button></section><div class="card" style="margin-bottom:16px"><form method="post" enctype="multipart/form-data"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="upload_media"><div class="field"><label>Add photo or video</label><input type="file" name="media" accept="image/jpeg,image/png,image/webp,video/mp4,video/webm" required></div><button class="btn">Upload</button></form></div><div class="album-grid"><?php foreach($myMedia as $m):?><div class="media-card"><?php if(gl_starts_with((string)$m['mime'],'video/')):?><video controls preload="metadata" src="<?=e(mediaUrl((int)$m['id']))?>"></video><?php else:?><img src="<?=e(mediaUrl((int)$m['id']))?>" alt=""><?php endif;?><div class="media-actions"><?php if((int)$m['is_primary']):?><span class="pill">Primary</span><?php else:?><form method="post"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="set_primary"><input type="hidden" name="media_id" value="<?=(int)$m['id']?>"><button class="btn">Set primary</button></form><?php endif;?><form method="post" onsubmit="return confirm('Delete this media?')"><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="delete_media"><input type="hidden" name="media_id" value="<?=(int)$m['id']?>"><button class="btn btn-danger">Delete</button></form></div></div><?php endforeach;?></div><div class="selfie-stage" id="selfieStage"><div class="selfie-card"><h2>Camera selfie</h2><video class="selfie-view" id="selfieVideo" autoplay playsinline muted></video><canvas id="selfieCanvas" hidden></canvas><div class="pill-row" style="margin-top:12px"><button class="btn btn-primary" id="captureSelfie">Capture and upload</button><button class="btn" id="closeSelfie">Cancel</button></div><p class="muted" style="margin-top:12px">Your browser will ask for camera permission. The image is uploaded only after you press Capture.</p></div></div> <?php elseif($view==='profile'):?> <section class="section-head"><div><div class="small">ACCOUNT</div><h1>Your profile</h1></div></section><div class="profile-form"><form class="card" method="post"><h2>Public details</h2><input type="hidden" name="csrf" value="<?=e(csrf())?>"><input type="hidden" name="action" value="update_profile"><div class="field"><label>Display name</label><input name="display_name" maxlength="80" value="<?=e($me['display_name'])?>" required></div><div class="field"><label>City or area</label><input name="city" maxlength="100" value="<?=e($me['city'])?>"></div><div class="field"><label>Current vibe</label><input name="vibe" maxlength="80" value="<?=e($me['vibe'])?>" placeholder="Coffee, conversation, tonight…"></div><div class="field"><label>Pronouns</label><input name="pronouns" maxlength="50" value="<?=e($me['pronouns'])?>"></div><div class="field"><label>Looking for</label><input name="looking_for" maxlength="120" value="<?=e($me['looking_for'])?>"></div><div class="field"><label>About you</label><textarea name="bio" rows="7" maxlength="700"><?=e($me['bio'])?></textarea></div><button class="btn btn-primary">Save profile</button></form><aside class="card"><h2>Identity card</h2><?=avatarMarkup($me,$myPrimary)?><p><strong>@<?=e($me['username'])?></strong><br><?=ageFromBirthdate((string)$me['birthdate'])?> years old</p><div class="pill-row"><span class="pill"><?=((int)$me['selfie_verified']?'Selfie added':'No selfie yet')?></span><span class="pill"><?=((int)$me['share_location']?'Location on':'Location off')?></span></div><p class="muted" style="margin-top:16px">Exact location is never displayed to other members. Map markers are shifted daily for privacy.</p></aside></div> <?php elseif($view==='network'):?> <section class="section-head network-head"><div><div class="small">KOSMOS NETWORK CONTROL</div><h1 class="rainbow-title">GayLord Network</h1><p class="muted">Live application, database, media, map, location, and browser capability status.</p></div><button class="btn btn-primary" type="button" id="refreshNetwork">Refresh status</button></section> <section class="network-hero card"><div class="kosmos-orb"><span>K</span><i></i><b></b></div><div><div class="small">PROMOTED NETWORK</div><h2>KOSMOS connects GayLord, SIMON, spatial services, media, and community systems.</h2><p>One identity layer. One network pulse. Modular services designed to grow with the GayLord universe.</p><div class="pill-row"><span class="pill">GayLord Spatial</span><span class="pill">SIMON Intelligence</span><span class="pill">Kosmos Media</span><span class="pill">Member Market</span></div></div></section> <div class="network-grid" id="networkGrid"> <article class="status-card" data-status="server"><span class="status-light pending"></span><div><strong>GayLord Server</strong><small>Checking current application response…</small></div></article> <article class="status-card" data-status="database"><span class="status-light online"></span><div><strong>Database</strong><small>Connected for this authenticated session.</small></div></article> <article class="status-card" data-status="map"><span class="status-light pending"></span><div><strong>Spatial Map Engine</strong><small>Checking Leaflet map library.</small></div></article> <article class="status-card" data-status="location"><span class="status-light pending"></span><div><strong>Location Services</strong><small>Checking browser geolocation capability.</small></div></article> <article class="status-card" data-status="camera"><span class="status-light pending"></span><div><strong>Camera / Selfie</strong><small>Checking secure media capture support.</small></div></article> <article class="status-card" data-status="storage"><span class="status-light pending"></span><div><strong>Browser Storage</strong><small>Checking session storage availability.</small></div></article> </div> <section class="card kosmos-promo"><div class="small">KOSMOS NETWORK</div><h2>Build the connected universe.</h2><p>Promote creators, communities, commerce, and intelligent services across the GayLord and Gaylord Sinclair ecosystem.</p><a class="btn btn-primary" href="https://www.gaylordsinclair.com/" target="_blank" rel="noopener">Explore Gaylord Sinclair</a></section> <?php elseif($view==='fuckclub'):?> <section class="club-stage"> <div class="club-core"><span class="club-ring ring-one"></span><span class="club-ring ring-two"></span><span class="club-ring ring-three"></span><div class="club-bang">GL</div></div> <div class="club-copy"><div class="small">GAY CENTRAL · 18+ COMMUNITY</div><h1 class="rainbow-title">Fuck Club</h1><p>A high-energy GayLord community concept where connection, consent, conversation, and cosmic nightlife meet at the center of the network.</p><div class="club-actions"><a class="btn btn-primary" href="?view=groups">Enter group rooms</a><a class="btn" href="?view=spatial">Open Gay Central map</a></div><p class="muted">Adults only. Consent is mandatory. No content or activity involving minors is permitted.</p></div> </section> <?php elseif($view==='safety'):?> <section class="section-head"><div><div class="small">CONSENT • PRIVACY • CONTROL</div><h1>Safety center</h1></div></section><div class="safety-grid"><article class="card"><h2>Consent is continuous</h2><p>Interest can be withdrawn at any time. A message, photo, or prior meeting is never permanent consent.</p></article><article class="card"><h2>Location privacy</h2><p>Nearby discovery is opt-in. Public markers are intentionally shifted and should never be treated as an exact address.</p></article><article class="card"><h2>Meet in public first</h2><p>Tell a trusted person where you are going, keep your own transportation, and leave when anything feels wrong.</p></article><article class="card"><h2>Protect private data</h2><p>Never send money, passwords, verification codes, banking details, or government identification.</p></article><article class="card"><h2>Report abuse</h2><p>Use block and report controls for harassment, impersonation, underage concerns, or non-consensual content.</p></article><article class="card"><h2>Launch requirements</h2><p>Before public release, add trained moderation, age assurance, content review, legal policies, and jurisdiction-specific compliance.</p></article></div> <?php endif;?> <nav class="bottom-nav"><div class="side-brand"><span class="side-mark">GL</span><strong>GayLord</strong><small>KOSMOS connected</small></div><a class="<?=$view==='spatial'?'active':''?>" href="?view=spatial"><span class="nav-icon">◎</span><span class="nav-label">Spatial</span></a><a class="<?=$view==='discover'?'active':''?>" href="?view=discover"><span class="nav-icon">◉</span><span class="nav-label">Discover</span></a><a class="<?=$view==='groups'?'active':''?>" href="?view=groups"><span class="nav-icon">#</span><span class="nav-label">Groups</span></a><a class="<?=$view==='inbox'?'active':''?>" href="?view=inbox"><span class="nav-icon">▰</span><span class="nav-label">Inbox<?=$unread?' · '.$unread:''?></span></a><a class="<?=$view==='fuckclub'?'active':''?>" href="?view=fuckclub"><span class="nav-icon">✦</span><span class="nav-label">Fuck Club</span></a><a class="<?=$view==='porn'?'active':''?>" href="?view=porn"><span class="nav-icon">18+</span><span class="nav-label">18+</span></a><a class="<?=$view==='market'?'active':''?>" href="?view=market"><span class="nav-icon">◇</span><span class="nav-label">Market</span></a><a class="<?=$view==='network'?'active':''?>" href="?view=network"><span class="nav-icon">⌁</span><span class="nav-label">Gay Central</span></a><a class="<?=$view==='albums'?'active':''?>" href="?view=albums"><span class="nav-icon">▧</span><span class="nav-label">Media</span></a><a class="<?=$view==='profile'?'active':''?>" href="?view=profile"><span class="nav-icon">●</span><span class="nav-label">Profile</span></a></nav> <?php endif;?> </main> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script> for(const card of document.querySelectorAll('.tilt-card,.profile-card,.map-card')){card.addEventListener('pointermove',e=>{if(matchMedia('(pointer:coarse)').matches)return;const r=card.getBoundingClientRect(),x=(e.clientX-r.left)/r.width-.5,y=(e.clientY-r.top)/r.height-.5;card.style.transform='perspective(1000px) rotateX('+(-y*7)+'deg) rotateY('+(x*9)+'deg) translateZ(12px)';card.style.setProperty('--card-angle',((x+.5)*360)+'deg');});card.addEventListener('pointerleave',()=>card.style.transform='');} const CSRF=<?=json_encode(csrf())?>; const messageBox=document.getElementById('messages');if(messageBox)messageBox.scrollTop=messageBox.scrollHeight; setTimeout(()=>document.querySelectorAll('.flash').forEach(x=>x.remove()),5500); async function postForm(data){const body=new FormData();for(const [k,v] of Object.entries(data))body.append(k,v);const r=await fetch('./',{method:'POST',body});const j=await r.json().catch(()=>({ok:false,error:'Unexpected server response'}));if(!r.ok||!j.ok)throw new Error(j.error||'Request failed');return j;} const locateBtn=document.getElementById('locateBtn'); if(locateBtn){locateBtn.addEventListener('click',()=>{if(!navigator.geolocation){alert('Location is not supported by this browser.');return;}locateBtn.disabled=true;locateBtn.textContent='Locating…';navigator.geolocation.getCurrentPosition(async p=>{try{await postForm({csrf:CSRF,action:'update_location',latitude:p.coords.latitude,longitude:p.coords.longitude,accuracy:p.coords.accuracy});location.reload();}catch(e){alert(e.message);locateBtn.disabled=false;locateBtn.textContent='Use my location';}},e=>{alert('Location permission was not granted.');locateBtn.disabled=false;locateBtn.textContent='Use my location';},{enableHighAccuracy:true,timeout:15000,maximumAge:120000});});} if(navigator.geolocation && document.getElementById('map')){let lastLocationPush=0;navigator.geolocation.watchPosition(async p=>{const now=Date.now();if(now-lastLocationPush<60000)return;lastLocationPush=now;try{await postForm({csrf:CSRF,action:'update_location',latitude:p.coords.latitude,longitude:p.coords.longitude,accuracy:p.coords.accuracy});}catch(e){}},()=>{},{enableHighAccuracy:true,maximumAge:30000,timeout:12000});} async function loadSpatial(){ const el=document.getElementById('map');if(!el)return; const status=document.getElementById('mapStatus'); const list=document.getElementById('nearbyList'); const rail=document.getElementById('spatialThumbRail'); try{ const r=await fetch('./?api=nearby',{cache:'no-store'});const data=await r.json(); if(!data.enabled){status.textContent='Location is off';list.innerHTML='<div class="card empty">Press “Use my location” to activate private nearby discovery.</div>';if(rail)rail.innerHTML='';return;} status.textContent=data.people.length+' people in your spatial field'; if(typeof L==='undefined'){list.innerHTML='<div class="card empty">Map library did not load. Nearby list is still available.</div>';return;} const map=L.map(el,{zoomControl:true,attributionControl:false,preferCanvas:true}).setView([data.center.lat,data.center.lng],12); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19,subdomains:'abc'}).addTo(map);L.control.scale({imperial:true,metric:false,position:'bottomright'}).addTo(map); L.marker([data.center.lat,data.center.lng],{icon:L.divIcon({className:'',html:'<div class="me-pulse" title="Approximate location"></div>',iconSize:[24,24],iconAnchor:[12,12]})}).addTo(map); list.innerHTML='';if(rail)rail.innerHTML=''; for(const p of data.people){ const visual=(p.avatar?'<img src="'+p.avatar+'" alt="">':'<span class="initial">'+escapeHtml(p.name.slice(0,1).toUpperCase())+'</span>')+'<span class="distance-pin">'+escapeHtml(p.distance_label)+'</span>'; const icon=L.divIcon({className:'person-marker',html:visual,iconSize:[64,64],iconAnchor:[32,32]}); const popupVisual=p.avatar?'<img src="'+p.avatar+'" alt="">':'<span class="popup-initial">'+escapeHtml(p.name.slice(0,1).toUpperCase())+'</span>'; const popup='<div class="map-profile-popup">'+popupVisual+'<div><strong>'+escapeHtml(p.name)+', '+p.age+'</strong><br><span>'+escapeHtml(p.distance_label)+' away</span><br><small>'+escapeHtml(p.vibe||p.city||'Nearby now')+'</small><br><a href="?view=inbox&chat='+encodeURIComponent(p.username)+'">Open profile chat →</a></div></div>'; const marker=L.marker([p.lat,p.lng],{icon}).addTo(map).bindPopup(popup,{maxWidth:300}); const row=document.createElement('a');row.className='person-row';row.href='?view=inbox&chat='+encodeURIComponent(p.username); row.innerHTML=(p.avatar?'<img class="avatar-sm" src="'+p.avatar+'" alt="">':'<span class="avatar-sm avatar-fallback">'+escapeHtml(p.name.slice(0,1).toUpperCase())+'</span>')+'<span><strong>'+(p.online?'<span style="color:var(--green)">●</span> ':'')+escapeHtml(p.name)+', '+p.age+'</strong><small class="muted" style="display:block">'+escapeHtml(p.vibe||p.city||'Nearby')+'</small></span><span class="distance">'+escapeHtml(p.distance_label)+'</span>'; row.addEventListener('mouseenter',()=>marker.openPopup());list.appendChild(row); if(rail){const thumb=document.createElement('a');thumb.className='spatial-thumb';thumb.href='?view=inbox&chat='+encodeURIComponent(p.username);thumb.title=p.name+' · '+p.distance_label;thumb.innerHTML=(p.avatar?'<img src="'+p.avatar+'" alt="'+escapeHtml(p.name)+'">':'<span class="initial">'+escapeHtml(p.name.slice(0,1).toUpperCase())+'</span>')+'<span class="thumb-distance">'+escapeHtml(p.distance_label)+'</span>';thumb.addEventListener('mouseenter',()=>marker.openPopup());rail.appendChild(thumb);} } if(!data.people.length){list.innerHTML='<div class="card empty">No nearby members are sharing location yet.</div>';if(rail)rail.innerHTML='<span class="muted">No nearby profile thumbnails yet.</span>';} setTimeout(()=>map.invalidateSize(),120); }catch(e){status.textContent='Spatial field unavailable';list.innerHTML='<div class="card empty">Could not load nearby members.</div>';} } function escapeHtml(s){return String(s).replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c]));} loadSpatial(); const selfieStage=document.getElementById('selfieStage'),openSelfie=document.getElementById('openSelfie'),closeSelfie=document.getElementById('closeSelfie'),captureSelfie=document.getElementById('captureSelfie'),selfieVideo=document.getElementById('selfieVideo');let selfieStream=null; if(openSelfie){openSelfie.addEventListener('click',async()=>{try{selfieStream=await navigator.mediaDevices.getUserMedia({video:{facingMode:'user'},audio:false});selfieVideo.srcObject=selfieStream;selfieStage.classList.add('open');}catch(e){alert('Camera permission is required to take a selfie.');}});} if(closeSelfie){closeSelfie.addEventListener('click',()=>{selfieStage.classList.remove('open');if(selfieStream)selfieStream.getTracks().forEach(t=>t.stop());});} if(captureSelfie){captureSelfie.addEventListener('click',()=>{const c=document.getElementById('selfieCanvas');c.width=selfieVideo.videoWidth||720;c.height=selfieVideo.videoHeight||960;const ctx=c.getContext('2d');ctx.translate(c.width,0);ctx.scale(-1,1);ctx.drawImage(selfieVideo,0,0,c.width,c.height);c.toBlob(async blob=>{if(!blob)return;const fd=new FormData();fd.append('csrf',CSRF);fd.append('action','upload_selfie');fd.append('selfie',blob,'selfie.jpg');captureSelfie.disabled=true;captureSelfie.textContent='Uploading…';const r=await fetch('./',{method:'POST',body:fd});if(r.redirected)location.href=r.url;else location.reload();},'image/jpeg',.9);});} const groupMessages=document.getElementById('groupMessages');let groupTimer=null; async function refreshGroup(){if(!groupMessages||groupMessages.dataset.joined!=='1')return;const rid=groupMessages.dataset.room;try{const r=await fetch('./?api=group_messages&room='+encodeURIComponent(rid),{cache:'no-store'});const data=await r.json();if(!data.ok)return;groupMessages.innerHTML=data.messages.length?'':'<div class="empty">No messages yet. Start the room.</div>';for(const m of data.messages){const b=document.createElement('div');b.className='bubble'+(m.mine?' mine':'');b.innerHTML=escapeHtml(m.body).replace(/\n/g,'<br>')+'<time>'+escapeHtml(m.created_at)+'</time>';groupMessages.appendChild(b);}groupMessages.scrollTop=groupMessages.scrollHeight;}catch(e){}} if(groupMessages){refreshGroup();groupTimer=setInterval(refreshGroup,5000);} if(groupMessages){groupMessages.addEventListener('click',async e=>{const button=e.target.closest('[data-delete-group]');if(!button||!confirm('Delete this group message?'))return;try{await postForm({csrf:CSRF,action:'delete_group_message',message_id:button.dataset.deleteGroup});await refreshGroup();}catch(err){alert(err.message);}});} const groupForm=document.getElementById('groupForm');if(groupForm){groupForm.addEventListener('submit',async e=>{e.preventDefault();const body=document.getElementById('groupBody');if(!body.value.trim())return;try{await postForm({csrf:CSRF,action:'send_group_message',room_id:groupMessages.dataset.room,body:body.value});body.value='';await refreshGroup();}catch(err){alert(err.message);}});} const siteSearch=document.getElementById('siteSearch'); if(siteSearch){siteSearch.addEventListener('input',()=>{const q=siteSearch.value.trim().toLowerCase();document.querySelectorAll('[data-site-card]').forEach(card=>{card.hidden=q!==''&&!card.dataset.search.includes(q);});});} const pornUploadModal=document.getElementById('pornUploadModal'),openPornUpload=document.getElementById('openPornUpload'),closePornUpload=document.getElementById('closePornUpload'); if(openPornUpload)openPornUpload.addEventListener('click',()=>pornUploadModal.classList.add('open')); if(closePornUpload)closePornUpload.addEventListener('click',()=>pornUploadModal.classList.remove('open')); if(pornUploadModal)pornUploadModal.addEventListener('click',e=>{if(e.target===pornUploadModal)pornUploadModal.classList.remove('open')}); const reels=[...document.querySelectorAll('[data-reel] .reel-video')]; if(reels.length){const observer=new IntersectionObserver(entries=>{for(const entry of entries){const video=entry.target;if(entry.isIntersecting&&entry.intersectionRatio>.72){for(const other of reels)if(other!==video)other.pause();video.play().catch(()=>{});}else video.pause();}},{threshold:[.25,.72,.95]});reels.forEach(v=>observer.observe(v));} const adPopup=document.getElementById('adPopup'),closeAdPopup=document.getElementById('closeAdPopup'); if(adPopup&&!sessionStorage.getItem('gaylord_ad_seen'))setTimeout(()=>{adPopup.classList.add('show');adPopup.setAttribute('aria-hidden','false');sessionStorage.setItem('gaylord_ad_seen','1')},9000); if(closeAdPopup)closeAdPopup.addEventListener('click',()=>adPopup.classList.remove('show')); for(const tab of document.querySelectorAll('[data-auth-tab]'))tab.addEventListener('click',()=>{const name=tab.dataset.authTab;document.querySelectorAll('[data-auth-tab]').forEach(b=>b.classList.toggle('active',b===tab));document.querySelectorAll('[data-auth-panel]').forEach(p=>p.hidden=p.dataset.authPanel!==name);}); // GayLord automated launch branding and pointer-reactive menu lighting. (function(){ const authenticated=document.body.classList.contains('is-authenticated'); if(authenticated && !sessionStorage.getItem('gaylord_brand_launch_seen')){ const launch=document.createElement('div'); launch.id='gaylordBrandLaunch'; launch.setAttribute('aria-hidden','true'); launch.innerHTML='<div class="brand-launch-core"><div class="brand-launch-mark">GL</div><div class="brand-launch-name">Gay<b>Lord</b></div><div class="brand-launch-line"></div></div>'; document.body.appendChild(launch); sessionStorage.setItem('gaylord_brand_launch_seen','1'); setTimeout(()=>launch.classList.add('hide'),1650); setTimeout(()=>launch.remove(),2400); } const nav=document.querySelector('.bottom-nav'); if(nav){ nav.addEventListener('pointermove',event=>{ const r=nav.getBoundingClientRect(); nav.style.setProperty('--nav-light-x',((event.clientX-r.left)/r.width*100).toFixed(1)+'%'); nav.style.setProperty('--nav-light-y',((event.clientY-r.top)/r.height*100).toFixed(1)+'%'); }); nav.addEventListener('pointerleave',()=>{ nav.style.setProperty('--nav-light-x','50%'); nav.style.setProperty('--nav-light-y','18%'); }); } })(); // Auto-hide desktop navigation: reveal at the left edge, on hover, or keyboard focus. (function(){ const body=document.body; const nav=document.querySelector('.bottom-nav'); if(!nav || !body.classList.contains('is-authenticated')) return; const desktop=()=>window.matchMedia('(min-width:901px)').matches; let closeTimer=null; const openNav=()=>{ if(!desktop()) return; clearTimeout(closeTimer); body.classList.add('nav-open'); }; const scheduleClose=(delay=480)=>{ if(!desktop()) return; clearTimeout(closeTimer); closeTimer=setTimeout(()=>{ if(!nav.matches(':hover') && !nav.contains(document.activeElement)) body.classList.remove('nav-open'); },delay); }; nav.addEventListener('pointerenter',openNav); nav.addEventListener('pointerleave',()=>scheduleClose()); nav.addEventListener('focusin',openNav); nav.addEventListener('focusout',()=>scheduleClose(220)); document.addEventListener('pointermove',event=>{ if(desktop() && event.clientX<=14) openNav(); },{passive:true}); document.addEventListener('keydown',event=>{ if(event.key==='Escape') body.classList.remove('nav-open'); }); nav.querySelectorAll('a').forEach(link=>{ const label=link.textContent.trim(); if(label) link.setAttribute('aria-label',label); if(label) link.setAttribute('title',label); }); // Brief branded reveal on first load, then retract automatically. if(desktop()){ body.classList.add('nav-open'); setTimeout(()=>scheduleClose(0),1450); } window.addEventListener('resize',()=>{ if(!desktop()) body.classList.remove('nav-open'); }); })(); // GayLord KOSMOS network status console. (function(){ const grid=document.getElementById('networkGrid'); if(!grid)return; const set=(name,ok,text)=>{const card=grid.querySelector('[data-status="'+name+'"]');if(!card)return;const light=card.querySelector('.status-light');const small=card.querySelector('small');light.className='status-light '+(ok?'online':'offline');if(text)small.textContent=text;}; async function refresh(){ grid.querySelectorAll('.status-light').forEach(x=>x.className='status-light pending'); try{const r=await fetch('./?api=nearby',{cache:'no-store'});set('server',r.ok,r.ok?'Application endpoint responding.':'Application endpoint returned '+r.status);}catch(e){set('server',false,'Application endpoint unavailable.');} set('database',true,'Authenticated database session is active.'); set('map',typeof window.L!=='undefined',typeof window.L!=='undefined'?'Leaflet map engine loaded.':'Map library did not load.'); set('location','geolocation' in navigator,'geolocation' in navigator?'Browser geolocation is available.':'Geolocation is unavailable.'); set('camera',!!(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia),navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?'Secure camera capture is supported.':'Camera capture is unavailable or HTTPS is missing.'); let storage=false;try{sessionStorage.setItem('gl_status_test','1');sessionStorage.removeItem('gl_status_test');storage=true;}catch(e){}set('storage',storage,storage?'Session storage is operational.':'Browser storage is blocked.'); } document.getElementById('refreshNetwork')?.addEventListener('click',refresh);refresh(); })(); </script> </body></html>
Save file
Quick jump
open a path
Open