From af3a287915f65e61af60a1e7d34b4f2facfc104e Mon Sep 17 00:00:00 2001 From: Tinna23 Date: Tue, 24 Mar 2026 21:20:27 +0100 Subject: [PATCH] =?UTF-8?q?feat(ui):=20landing=20content=20sections,=20foo?= =?UTF-8?q?ter,=20use=20cases,=20and=20loading=20states=20[UI=20#13?= =?UTF-8?q?=E2=80=9316]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/src/app/account/[address]/page.tsx | 120 ++-- packages/ui/src/app/page.tsx | 6 + packages/ui/src/app/tx/[hash]/page.tsx | 98 ++-- packages/ui/src/components/landing/Footer.tsx | 52 ++ .../components/landing/HowItWorksSection.tsx | 48 ++ .../components/landing/OpenSourceSection.tsx | 69 +++ .../components/landing/UseCasesSection.tsx | 524 ++++++++++++++++++ .../landing/WhatWeDecodeSection.tsx | 38 ++ packages/ui/src/lib/landing-data.tsx | 80 +++ 9 files changed, 924 insertions(+), 111 deletions(-) create mode 100644 packages/ui/src/components/landing/Footer.tsx create mode 100644 packages/ui/src/components/landing/HowItWorksSection.tsx create mode 100644 packages/ui/src/components/landing/OpenSourceSection.tsx create mode 100644 packages/ui/src/components/landing/UseCasesSection.tsx create mode 100644 packages/ui/src/components/landing/WhatWeDecodeSection.tsx create mode 100644 packages/ui/src/lib/landing-data.tsx diff --git a/packages/ui/src/app/account/[address]/page.tsx b/packages/ui/src/app/account/[address]/page.tsx index 1f1a8c2..1806aaf 100644 --- a/packages/ui/src/app/account/[address]/page.tsx +++ b/packages/ui/src/app/account/[address]/page.tsx @@ -85,10 +85,8 @@ function AccountPageInner() { Back to search - {/* {loading && } */} - {loading && ( -

Loading...

- )} + {loading && } + {error && !loading && (
@@ -124,60 +122,60 @@ export default function AccountPage() { // ── Skeleton ─────────────────────────────────────────────────────────────── -// function AccountSkeleton() { -// return ( -//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-// ); -// } +function AccountSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/packages/ui/src/app/page.tsx b/packages/ui/src/app/page.tsx index cd7bcef..c5f82f0 100644 --- a/packages/ui/src/app/page.tsx +++ b/packages/ui/src/app/page.tsx @@ -1,6 +1,9 @@ 'use client";'; import HeroSection from '@/components/landing/HeroSection'; +import HowItWorksSection from '@/components/landing/HowItWorksSection'; import Navbar from '@/components/landing/Navbar'; +import UseCasesSection from '@/components/landing/UseCasesSection'; +import WhatWeDecodeSection from '@/components/landing/WhatWeDecodeSection'; export default function LandingPage() { return ( @@ -42,6 +45,9 @@ export default function LandingPage() { + + +
); } diff --git a/packages/ui/src/app/tx/[hash]/page.tsx b/packages/ui/src/app/tx/[hash]/page.tsx index 9c69e23..7f85a21 100644 --- a/packages/ui/src/app/tx/[hash]/page.tsx +++ b/packages/ui/src/app/tx/[hash]/page.tsx @@ -84,10 +84,8 @@ function TxPageInner() { Back to search - {/* {loading && } */} - {loading && ( -

Loading...

- )} + {loading && } + {error && !loading && (
@@ -112,49 +110,49 @@ export default function TxPage() { // ── Skeleton ─────────────────────────────────────────────────────────────── -// function TransactionSkeleton() { -// return ( -//
-//
-//
-//
-//
-//
-//
-//
-//
-// ); -// } +function TransactionSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/packages/ui/src/components/landing/Footer.tsx b/packages/ui/src/components/landing/Footer.tsx new file mode 100644 index 0000000..e5a07de --- /dev/null +++ b/packages/ui/src/components/landing/Footer.tsx @@ -0,0 +1,52 @@ +import Link from "next/link"; +import React from "react"; + +export default function Footer() { + return ( + + ); +} diff --git a/packages/ui/src/components/landing/HowItWorksSection.tsx b/packages/ui/src/components/landing/HowItWorksSection.tsx new file mode 100644 index 0000000..99ab72f --- /dev/null +++ b/packages/ui/src/components/landing/HowItWorksSection.tsx @@ -0,0 +1,48 @@ +import { STEPS } from '@/lib/landing-data'; +import React from 'react'; + +function HowItWorksSection() { + return ( +
+
+

+ How it works +

+

+ Three steps to clarity +

+
+ +
+ {STEPS.map((step) => ( +
+ {/* step number watermark */} + + {step.n} + + +
+ {step.icon} +
+ +

+ {step.title} +

+

{step.body}

+
+ ))} +
+
+ ); +} + +export default HowItWorksSection; diff --git a/packages/ui/src/components/landing/OpenSourceSection.tsx b/packages/ui/src/components/landing/OpenSourceSection.tsx new file mode 100644 index 0000000..93d198e --- /dev/null +++ b/packages/ui/src/components/landing/OpenSourceSection.tsx @@ -0,0 +1,69 @@ +import React from "react"; + +export default function OpenSourceSection() { + return ( +
+
+ {/* inner glow */} +
+ +
+
+ + + + Open Source +
+ +

+ Built in the open. +
+ Come build with us. +

+ +

+ Stellar Explain is fully open source. Whether you want to add a new + operation explainer, improve the UI, or fix a bug — every + contribution makes Web3 more human readable. +

+ + +
+
+
+ ); +} diff --git a/packages/ui/src/components/landing/UseCasesSection.tsx b/packages/ui/src/components/landing/UseCasesSection.tsx new file mode 100644 index 0000000..0ce2a9d --- /dev/null +++ b/packages/ui/src/components/landing/UseCasesSection.tsx @@ -0,0 +1,524 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +const NODES = [ + { + id: 'developers', + label: 'Developers', + sub: 'debug with clarity', + icon: '', + x: 185, + y: 190, + floatDur: 2.8, + floatAmp: 9, + floatDelay: 0, + }, + { + id: 'analysts', + label: 'Analysts', + sub: 'audit made readable', + icon: '~~~', + x: 830, + y: 172, + floatDur: 3.4, + floatAmp: 11, + floatDelay: 0.4, + }, + { + id: 'curious', + label: 'Curious Users', + sub: 'your wallet, explained', + icon: '◉', + x: 158, + y: 440, + floatDur: 3.1, + floatAmp: 8, + floatDelay: 0.8, + }, + { + id: 'businesses', + label: 'Businesses', + sub: 'compliance-ready context', + icon: '⬡', + x: 840, + y: 438, + floatDur: 2.6, + floatAmp: 10, + floatDelay: 0.2, + }, + { + id: 'auditors', + label: 'Auditors', + sub: 'trace every operation', + icon: '◈', + x: 920, + y: 310, + floatDur: 3.7, + floatAmp: 7, + floatDelay: 1.0, + }, +]; + +const CENTER = { x: 530, y: 315 }; + +const CURVES: Record = { + developers: `M${CENTER.x - 26},${CENTER.y - 26} C420,265 320,230 248,200`, + analysts: `M${CENTER.x + 24},${CENTER.y - 28} C630,248 720,205 768,182`, + curious: `M${CENTER.x - 28},${CENTER.y + 24} C420,378 310,408 222,440`, + businesses: `M${CENTER.x + 26},${CENTER.y + 24} C635,378 728,412 778,438`, + auditors: `M${CENTER.x + 30},${CENTER.y} C668,308 768,310 858,312`, +}; + +const SEQUENCE_DELAYS: Record = { + developers: { line: 1100, node: 1500 }, + analysts: { line: 1750, node: 2100 }, + curious: { line: 2300, node: 2650 }, + businesses: { line: 2900, node: 3250 }, + auditors: { line: 3500, node: 3850 }, +}; + +export default function UseCasesSection() { + const sectionRef = useRef(null); + const [visible, setVisible] = useState(false); + const [centerVisible, setCenterVisible] = useState(false); + const [linesVisible, setLinesVisible] = useState>({}); + const [nodesVisible, setNodesVisible] = useState>({}); + const [started, setStarted] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && !started) { + setVisible(true); + setStarted(true); + } + }, + { threshold: 0.25 }, + ); + if (sectionRef.current) observer.observe(sectionRef.current); + return () => observer.disconnect(); + }, [started]); + + useEffect(() => { + if (!visible) return; + + const t0 = setTimeout(() => setCenterVisible(true), 600); + + const lineTimers: ReturnType[] = []; + const nodeTimers: ReturnType[] = []; + + NODES.forEach((node) => { + const d = SEQUENCE_DELAYS[node.id]; + lineTimers.push( + setTimeout(() => { + setLinesVisible((prev) => ({ ...prev, [node.id]: true })); + }, d.line), + ); + nodeTimers.push( + setTimeout(() => { + setNodesVisible((prev) => ({ ...prev, [node.id]: true })); + }, d.node), + ); + }); + + return () => { + clearTimeout(t0); + [...lineTimers, ...nodeTimers].forEach(clearTimeout); + }; + }, [visible]); + + return ( +
+ {/* Section heading */} +
+

+ who is this for?? +

+

+ Everyone navigating Stellar. +

+
+ + {/* SVG node graph */} +
+ + + {/* Line pulse filter */} + + + + + + + + + {/* CSS animations injected inline */} + + + + {/* Ambient dots */} + {[ + [360, 140], + [680, 560], + [90, 320], + [980, 200], + [460, 560], + [740, 118], + [55, 210], + [1010, 500], + [200, 560], + [900, 80], + ].map(([cx, cy], i) => ( + + ))} + + {/* Connector lines */} + {NODES.map((node) => ( + + ))} + + {/* CENTER NODE */} + + + {/* Star icon */} + + + + + + + + + + + + + {/* + Stellar Explain + */} + + plain-english blockchain + + + + {/* AUDIENCE NODES */} + {NODES.map((node, i) => { + const w = node.id === 'businesses' ? 196 : node.id === 'curious' ? 192 : 180; + const h = 84; + const nx = node.x - w / 2; + const ny = node.y - h / 2; + + return ( + + + {/* Icon box */} + + + {node.icon} + + {/* Label */} + + {node.label} + + + {node.sub} + + {/* Decorative corner dot */} + + + ); + })} + +
+
+ ); +} diff --git a/packages/ui/src/components/landing/WhatWeDecodeSection.tsx b/packages/ui/src/components/landing/WhatWeDecodeSection.tsx new file mode 100644 index 0000000..53bddaf --- /dev/null +++ b/packages/ui/src/components/landing/WhatWeDecodeSection.tsx @@ -0,0 +1,38 @@ +import { FEATURES } from '@/lib/landing-data'; +import React from 'react'; + +function WhatWeDecodeSection() { + return ( +
+
+

Coverage

+

+ What we decode +

+

+ Every major Stellar operation type, explained in context. +

+
+ +
+ {FEATURES.map((f) => ( +
+
+
+

{f.label}

+

{f.desc}

+
+
+ ))} +
+
+ ); +} + +export default WhatWeDecodeSection; diff --git a/packages/ui/src/lib/landing-data.tsx b/packages/ui/src/lib/landing-data.tsx new file mode 100644 index 0000000..1000a47 --- /dev/null +++ b/packages/ui/src/lib/landing-data.tsx @@ -0,0 +1,80 @@ +export const FEATURES = [ + { + label: "Payments", + desc: "XLM and custom asset transfers between accounts", + }, + { + label: "Accounts", + desc: "Balances, trust lines, signers, flags, and home domain", + }, + { + label: "Set Options", + desc: "Account configuration changes decoded line by line", + }, + { + label: "Create Account", + desc: "New account funding and activation events", + }, + { label: "Change Trust", desc: "Asset trust line additions and removals" }, + { label: "Clawback", desc: "Regulated asset recovery operations explained" }, +]; + +export const STEPS = [ + { + n: "01", + title: "Paste a hash or address", + body: "Copy any transaction hash or account address from your wallet, exchange, or block explorer.", + icon: ( + + + + + ), + }, + { + n: "02", + title: "Hit Explain", + body: "Stellar Explain fetches the data from the Stellar network and processes each operation in real time.", + icon: ( + + + + + ), + }, + { + n: "03", + title: "Read plain English", + body: "Every operation is translated into a clear, human-readable explanation — no blockchain knowledge needed.", + icon: ( + + + + + + + + ), + }, +]; \ No newline at end of file