Skip to content
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ node_modules
.DS_Store
.claude/
out/
docs/
docs/
tsconfig.tsbuildinfo
148 changes: 148 additions & 0 deletions app/diag/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"use client"

import { useEffect, useState } from "react"

// Self-contained diagnostics page. Uses ONLY inline styles so it renders even
// if the app's Tailwind CSS or animations are the problem. Share the URL
// (e.g. www.lockittrade.com/diag) with anyone seeing the blank-page issue and
// have them screenshot it or tap "Copy report".
export default function DiagPage() {
const [report, setReport] = useState<Record<string, string>>({})
const [animOpacity, setAnimOpacity] = useState("measuring…")
const [cssOk, setCssOk] = useState("measuring…")
const [copied, setCopied] = useState(false)

useEffect(() => {
const nav = navigator as any
const conn = nav.connection || nav.mozConnection || nav.webkitConnection
const mm = (q: string) => (window.matchMedia ? window.matchMedia(q).matches : "n/a")

// CSS-loaded test: create a div with a Tailwind class and read computed style.
const probe = document.createElement("div")
probe.className = "bg-black text-white"
probe.style.position = "absolute"
probe.style.left = "-9999px"
document.body.appendChild(probe)
const bg = getComputedStyle(probe).backgroundColor
setCssOk(bg === "rgb(0, 0, 0)" ? `YES (bg=${bg})` : `NO — Tailwind not applied (bg=${bg})`)
document.body.removeChild(probe)

// Animation test: an element using the site's fade class — after the
// animation window, opacity must be 1. If it stays 0, animations are the bug.
const a = document.createElement("div")
a.className = "animate-fade-in-up"
a.style.position = "absolute"
a.style.left = "-9999px"
document.body.appendChild(a)
window.setTimeout(() => {
const op = getComputedStyle(a).opacity
setAnimOpacity(op === "1" ? "OK (opacity=1)" : `STUCK at opacity=${op} ← would be blank`)
document.body.removeChild(a)
}, 1600)

// Failed / slow resources
let failed = "none"
let slow = "none"
try {
const res = performance.getEntriesByType("resource") as PerformanceResourceTiming[]
const bad = res.filter((r) => r.transferSize === 0 && r.duration === 0).map((r) => r.name)
const slowOnes = res
.filter((r) => r.duration > 3000)
.map((r) => `${r.name.split("/").pop()} (${Math.round(r.duration)}ms)`)
if (bad.length) failed = bad.slice(0, 8).join(", ")
if (slowOnes.length) slow = slowOnes.slice(0, 8).join(", ")
} catch {}

let sheets = "n/a"
try {
sheets = String(document.styleSheets.length)
} catch {}

const navTime = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming | undefined

setReport({
"Build time (this deploy)": process.env.NEXT_PUBLIC_BUILD_TIME || "unknown",
"Page loaded at": new Date().toISOString(),
"URL": window.location.href,
"User agent": navigator.userAgent,
"Platform": nav.platform || "n/a",
"Viewport": `${window.innerWidth} x ${window.innerHeight} @ DPR ${window.devicePixelRatio}`,
"Screen": `${screen.width} x ${screen.height}`,
"prefers-reduced-motion: reduce": String(mm("(prefers-reduced-motion: reduce)")),
"prefers-color-scheme dark": String(mm("(prefers-color-scheme: dark)")),
"Low Data / saveData": conn ? String(!!conn.saveData) : "n/a",
"Network type": conn ? `${conn.effectiveType || "?"} (${conn.downlink || "?"}Mbps)` : "n/a",
"Cookies enabled": String(navigator.cookieEnabled),
"localStorage": (() => { try { localStorage.setItem("_t", "1"); localStorage.removeItem("_t"); return "available" } catch { return "BLOCKED" } })(),
"Stylesheets loaded": sheets,
"Failed resources": failed,
"Slow resources (>3s)": slow,
"DOM interactive": navTime ? `${Math.round(navTime.domInteractive)}ms` : "n/a",
"Load complete": navTime ? `${Math.round(navTime.loadEventEnd)}ms` : "n/a",
})
}, [])

const full = [
"=== LockItTrade Diagnostics ===",
`Tailwind CSS applied: ${cssOk}`,
`Fade animation: ${animOpacity}`,
...Object.entries(report).map(([k, v]) => `${k}: ${v}`),
].join("\n")

const copy = () => {
try {
navigator.clipboard.writeText(full)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch {}
}

return (
<div style={{ minHeight: "100vh", background: "#0a0a0a", color: "#fff", fontFamily: "system-ui, sans-serif", padding: "20px", lineHeight: 1.5 }}>
<div style={{ maxWidth: 720, margin: "0 auto" }}>
<div style={{ background: "#00A9E0", color: "#000", padding: "16px", borderRadius: 12, fontWeight: 800, fontSize: 18, textAlign: "center" }}>
✅ DIAGNOSTICS LOADED — JS &amp; this page rendered
</div>

<h2 style={{ marginTop: 24, fontSize: 16 }}>Key checks</h2>
<div style={{ background: "#161616", border: "1px solid #2a2a2a", borderRadius: 10, padding: 14 }}>
<Row k="Tailwind CSS applied" v={cssOk} />
<Row k="Fade-in animation" v={animOpacity} />
</div>

<h2 style={{ marginTop: 24, fontSize: 16 }}>Device & build</h2>
<div style={{ background: "#161616", border: "1px solid #2a2a2a", borderRadius: 10, padding: 14 }}>
{Object.entries(report).map(([k, v]) => (
<Row key={k} k={k} v={v} />
))}
</div>

<button
onClick={copy}
style={{ marginTop: 20, width: "100%", padding: "14px", borderRadius: 10, border: "none", background: "#00A9E0", color: "#000", fontWeight: 700, fontSize: 16 }}
>
{copied ? "Copied ✓" : "Copy report"}
</button>

<textarea
readOnly
value={full}
style={{ marginTop: 16, width: "100%", height: 220, background: "#000", color: "#0f0", border: "1px solid #2a2a2a", borderRadius: 10, padding: 12, fontFamily: "monospace", fontSize: 12 }}
/>
<p style={{ color: "#888", fontSize: 12, marginTop: 12 }}>
Screenshot this page or tap “Copy report” and paste it back. The “Build
time” line tells us if you’re on the latest deploy or a cached old one.
</p>
</div>
</div>
)
}

function Row({ k, v }: { k: string; v: string }) {
return (
<div style={{ display: "flex", justifyContent: "space-between", gap: 12, padding: "6px 0", borderBottom: "1px solid #222", fontSize: 13 }}>
<span style={{ color: "#9aa", flexShrink: 0 }}>{k}</span>
<span style={{ textAlign: "right", wordBreak: "break-word" }}>{v}</span>
</div>
)
}
62 changes: 52 additions & 10 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -180,27 +180,69 @@ html {
}
}

/* IMPORTANT: classes do NOT set a base opacity:0. `both` fill-mode applies the
keyframe's `from` (opacity:0) only while the animation is queued/running, so
if the animation never runs (iOS Reduce Motion, throttling, JS-less) the
element falls back to its natural VISIBLE state instead of being stranded
invisible. This is what keeps the page from rendering blank. */
.animate-fade-in-up {
opacity: 0;
animation: fadeInUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation: fadeInUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.animate-fade-in-down {
opacity: 0;
animation: fadeInDown 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation: fadeInDown 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.animate-fade-in {
opacity: 0;
animation: fadeIn 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation: fadeIn 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.animate-fade-in-left {
opacity: 0;
animation: fadeInLeft 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation: fadeInLeft 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.animate-fill-circle {
stroke-dashoffset: 283;
animation: fillCircle 1.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
animation: fillCircle 1.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Reduce Motion (common on iOS): skip the entrance animations entirely and
show everything immediately so content is never hidden behind motion. */
@media (prefers-reduced-motion: reduce) {
.animate-fade-in-up,
.animate-fade-in-down,
.animate-fade-in,
.animate-fade-in-left {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
.animate-fill-circle {
animation: none !important;
stroke-dashoffset: 36.79 !important;
}
}

/* ── Mobile GPU-cost reduction ──────────────────────────────────────────────
Real iOS Safari (unlike virtualized BrowserStack devices) has hard GPU
memory/compositing limits. Many backdrop-filters + huge gaussian blurs make
every paint extremely expensive, so the page trickles in very slowly. On
small screens we drop backdrop-filter entirely (cards keep a solid-ish bg so
they stay legible) and cap the giant decorative blur orbs. Desktop is
untouched. */
@media (max-width: 767px) {
.glass-panel,
[class*="backdrop-blur"] {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
.glass-panel {
background: rgba(18, 18, 20, 0.94);
}
/* Cap oversized glow blurs (blur-[120px]..[150px]) to something cheap. */
.blur-\[120px\],
.blur-\[130px\],
.blur-\[140px\],
.blur-\[150px\] {
filter: blur(45px) !important;
}
}
8 changes: 0 additions & 8 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,6 @@ export default function RootLayout({
return (
<html lang="en" className="dark" suppressHydrationWarning>
<head>
{/* Enhanced Structured Data with Organization Schema */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData),
}}
/>

{/* Additional favicon links for better browser support */}
<link rel="icon" type="image/png" sizes="32x32" href={getAssetPath("/favicon.png")} />
<link rel="icon" type="image/png" sizes="16x16" href={getAssetPath("/favicon.png")} />
Expand Down
36 changes: 11 additions & 25 deletions components/affiliate-section.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use client"

import { useRef } from "react"
import { motion, useInView } from "framer-motion"
import { Button } from "@/components/ui/button"
import { Handshake, DollarSign, Users, MousePointerClick, ArrowRight, Link2 } from "lucide-react"

Expand All @@ -13,21 +11,16 @@ const stats = [
]

export function AffiliateSection() {
const ref = useRef<HTMLDivElement>(null)
const inView = useInView(ref, { once: true, amount: 0.15 })

return (
<section className="relative bg-gradient-to-b from-black to-zinc-900 py-16 md:py-24 overflow-hidden" ref={ref}>
<section className="relative bg-gradient-to-b from-black to-zinc-900 py-16 md:py-24 overflow-hidden">
<div className="absolute bottom-0 left-1/4 w-[500px] h-[500px] bg-emerald-500/5 blur-[130px] rounded-full pointer-events-none" />

<div className="container max-w-screen-xl mx-auto px-4 relative z-10">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
{/* Portal preview card */}
<motion.div
initial={{ y: 30 }}
animate={inView ? { y: 0 } : { y: 30 }}
transition={{ duration: 0.7, delay: 0.1 }}
className="order-2 lg:order-1 glass-panel rounded-[2rem] p-6 sm:p-8 relative overflow-hidden"
<div
className="order-2 lg:order-1 glass-panel rounded-[2rem] p-6 sm:p-8 relative overflow-hidden animate-fade-in-up"
style={{ animationDelay: "100ms" }}
>
<div className="absolute inset-0 bg-gradient-to-br from-[#00A9E0]/5 to-transparent pointer-events-none" />
<div className="relative z-10">
Expand All @@ -43,19 +36,17 @@ export function AffiliateSection() {

<div className="grid grid-cols-2 gap-3 mb-5">
{stats.map((stat, i) => (
<motion.div
<div
key={i}
initial={{ scale: 0.9 }}
animate={inView ? { scale: 1 } : { scale: 0.9 }}
transition={{ duration: 0.4, delay: 0.3 + i * 0.1 }}
className="p-4 rounded-xl bg-white/[0.03] border border-white/5"
className="p-4 rounded-xl bg-white/[0.03] border border-white/5 animate-fade-in"
style={{ animationDelay: `${300 + i * 100}ms` }}
>
<div className={`w-9 h-9 rounded-lg ${stat.bg} flex items-center justify-center mb-3`}>
<stat.icon className={`w-4 h-4 ${stat.accent}`} />
</div>
<p className="text-2xl font-bold text-white tabular-nums">{stat.value}</p>
<p className="text-xs text-zinc-500">{stat.label}</p>
</motion.div>
</div>
))}
</div>

Expand All @@ -66,15 +57,10 @@ export function AffiliateSection() {
<button className="text-[11px] font-semibold text-[#00A9E0] hover:text-white transition-colors shrink-0">Copy</button>
</div>
</div>
</motion.div>
</div>

{/* Copy */}
<motion.div
initial={{ y: 20 }}
animate={inView ? { y: 0 } : { y: 20 }}
transition={{ duration: 0.6 }}
className="order-1 lg:order-2"
>
<div className="order-1 lg:order-2 animate-fade-in-up">
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-sm font-medium mb-6">
<Handshake size={16} />
Affiliate Program
Expand Down Expand Up @@ -108,7 +94,7 @@ export function AffiliateSection() {
<ArrowRight className="w-5 h-5 ml-2 transition-transform group-hover:translate-x-1" />
</a>
</Button>
</motion.div>
</div>
</div>
</div>
</section>
Expand Down
2 changes: 1 addition & 1 deletion components/ai-grid-background.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,6 @@ export function AIGridBackground() {
}, [])

return (
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full pointer-events-none z-0" style={{ opacity: 1 }} />
<canvas ref={canvasRef} className="hidden md:block absolute inset-0 w-full h-full pointer-events-none z-0" style={{ opacity: 1 }} />
)
}
Loading
Loading