Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions soroscan-frontend/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ export default function Home() {
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<div className="text-[10px] text-terminal-gray tracking-widest mb-3">Python SDK</div>
<div className="text-[10px] text-terminal-gray-light tracking-widest mb-3 font-bold uppercase">Python SDK</div>
<CodeSnippet code={PY_EXAMPLE} language="python" filename="example.py" />
</div>
<div>
<div className="text-[10px] text-terminal-gray tracking-widest mb-3">TypeScript SDK</div>
<div className="text-[10px] text-terminal-gray-light tracking-widest mb-3 font-bold uppercase">TypeScript SDK</div>
<CodeSnippet code={TS_EXAMPLE} language="typescript" filename="example.ts" />
</div>
</div>
Expand Down
12 changes: 12 additions & 0 deletions soroscan-frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
--color-terminal-danger: #ff3366;
--color-terminal-warning: #ffaa00;
--color-terminal-gray: #64748b;
--color-terminal-gray-light: #94a3b8;

--font-terminal-mono: "JetBrains Mono", "IBM Plex Mono", monospace;
--font-terminal-sans: "Inter", system-ui, sans-serif;
Expand All @@ -73,6 +74,17 @@
}
}

@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
Expand Down
20 changes: 8 additions & 12 deletions soroscan-frontend/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,36 +93,32 @@ function LoginPageInner() {

<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div>
<label className="block text-[10px] text-terminal-green uppercase tracking-widest mb-2">
&gt; USER_EMAIL
</label>
<Input
label="USER_EMAIL"
{...register('email')}
placeholder="operator@soroscan.io"
className={errors.email ? 'border-terminal-danger text-terminal-danger' : ''}
autoComplete="email"
/>
{errors.email && (
<p className="mt-1 text-[10px] text-terminal-danger">
{String(errors.email.message)}
<p className="mt-1 text-[10px] text-terminal-danger animate-pulse" role="alert">
&gt; {String(errors.email.message)}
</p>
)}
</div>

<div>
<label className="block text-[10px] text-terminal-green uppercase tracking-widest mb-2">
&gt; ACCESS_PASSWORD
</label>
<Input
{...register('password')}
label="PASSWORD"
type="password"
placeholder="********"
{...register('password')}
placeholder="••••••••"
className={errors.password ? 'border-terminal-danger text-terminal-danger' : ''}
autoComplete="current-password"
/>
{errors.password && (
<p className="mt-1 text-[10px] text-terminal-danger">
{String(errors.password.message)}
<p className="mt-1 text-[10px] text-terminal-danger animate-pulse" role="alert">
&gt; {String(errors.password.message)}
</p>
)}
</div>
Expand Down
15 changes: 11 additions & 4 deletions soroscan-frontend/components/terminal/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,26 @@ export interface InputProps
}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, label, ...props }, ref) => {
({ className, type, label, id, ...props }, ref) => {
const generatedId = React.useId()
const inputId = id || generatedId

return (
<div className="w-full space-y-1 group">
{label && (
<label className="text-xs font-terminal-mono text-terminal-cyan uppercase tracking-wider block ml-1">
<label
htmlFor={inputId}
className="text-xs font-terminal-mono text-terminal-cyan uppercase tracking-wider block ml-1"
>
{label}
</label>
)}
<div className="relative flex items-center">
<span className="absolute left-3 text-terminal-green font-terminal-mono group-focus-within:animate-pulse">
<span className="absolute left-3 text-terminal-green font-terminal-mono group-focus-within:animate-pulse" aria-hidden="true">
&gt;
</span>
<input
id={inputId}
type={type}
className={cn(
"flex h-10 w-full bg-terminal-black border-terminal border-terminal-gray/30 px-8 py-2 text-sm font-terminal-mono text-terminal-green ring-offset-terminal-black file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-terminal-gray/50 focus-visible:outline-none focus-visible:border-terminal-green focus-visible:shadow-glow-green/20 transition-all disabled:cursor-not-allowed disabled:opacity-50",
Expand All @@ -29,7 +36,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
{...props}
/>
{/* Active status indicator */}
<div className="absolute right-3 w-2 h-2 rounded-full border border-terminal-green/30 group-focus-within:bg-terminal-green group-focus-within:shadow-glow-green transition-all" />
<div className="absolute right-3 w-2 h-2 rounded-full border border-terminal-green/30 group-focus-within:bg-terminal-green group-focus-within:shadow-glow-green transition-all" aria-hidden="true" />
</div>
</div>
)
Expand Down
52 changes: 33 additions & 19 deletions soroscan-frontend/components/terminal/LiveEventStream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,21 @@ export function LiveEventStream({ contractId }: LiveEventStreamProps) {
{/* Header / Status */}
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 p-4 border-b-terminal border-terminal-green/30 bg-terminal-green/5">
<div className="flex items-center gap-3">
<div className="text-terminal-cyan font-bold">LIVE_EVENT_STREAM</div>
<div className="h-4 w-[1px] bg-terminal-green/30" />
<div className={cn("flex items-center gap-2 text-xs", statusColor[status])}>
<span className={cn("w-2 h-2 rounded-full", status === "CONNECTED" && "animate-pulse",
status === "CONNECTED" ? "bg-terminal-green" :
status === "CONNECTING" ? "bg-terminal-warning" :
status === "ERROR" ? "bg-terminal-danger" : "bg-terminal-gray"
)} />
<h2 className="text-terminal-cyan font-bold">LIVE_EVENT_STREAM</h2>
<div className="h-4 w-[1px] bg-terminal-green/30" aria-hidden="true" />
<div
className={cn("flex items-center gap-2 text-xs", statusColor[status])}
role="status"
aria-live="polite"
>
<span
className={cn("w-2 h-2 rounded-full", status === "CONNECTED" && "animate-pulse",
status === "CONNECTED" ? "bg-terminal-green" :
status === "CONNECTING" ? "bg-terminal-warning" :
status === "ERROR" ? "bg-terminal-danger" : "bg-terminal-gray"
)}
aria-hidden="true"
/>
{status}
</div>
</div>
Expand All @@ -87,11 +94,13 @@ export function LiveEventStream({ contractId }: LiveEventStreamProps) {
size="sm"
onClick={() => setIsPaused(!isPaused)}
className="min-w-[100px]"
aria-pressed={isPaused}
>
{isPaused ? <Play className="w-3 h-3 mr-2" /> : <Pause className="w-3 h-3 mr-2" />}
{isPaused ? <Play className="w-3 h-3 mr-2" aria-hidden="true" /> : <Pause className="w-3 h-3 mr-2" aria-hidden="true" />}
{isPaused ? "RESUME" : "PAUSE"}
{newEventsCount > 0 && isPaused && (
<span className="absolute -top-1 -right-1 bg-terminal-danger text-terminal-black text-[8px] px-1 rounded-full animate-bounce font-bold">
<span className="sr-only">New events: </span>
{newEventsCount > 99 ? "99+" : newEventsCount}
</span>
)}
Expand All @@ -108,21 +117,22 @@ export function LiveEventStream({ contractId }: LiveEventStreamProps) {
<div className="px-4 py-2 flex items-center justify-between gap-4 border-b-terminal border-terminal-green/10">
<div className="flex-1 max-w-sm">
<Input
label="Filter events"
placeholder="FILTER_BY_TYPE_OR_CONTRACT..."
className="h-8 text-xs"
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
</div>
<div className="text-[10px] text-terminal-gray">
<div className="text-[10px] text-terminal-gray" aria-live="polite">
SHOWING {filteredEvents.length} OF {displayEvents.length} BUFF_EVENTS
{isPaused && <span className="ml-2 text-terminal-warning animate-pulse">[PAUSED]</span>}
{isPaused && <span className="ml-2 text-terminal-warning animate-pulse" aria-hidden="true">[PAUSED]</span>}
</div>
</div>

{/* Stream Window */}
<div className="max-h-[500px] overflow-y-auto scrollbar-terminal custom-scrollbar">
<Table className="border-none">
<Table className="border-none" aria-label="Live event stream">
<TableHeader className="sticky top-0 z-20 bg-terminal-black">
<TableRow className="bg-terminal-green/10 hover:bg-terminal-green/10">
<TableHead className="w-[100px] text-[10px]">TIME</TableHead>
Expand All @@ -142,26 +152,30 @@ export function LiveEventStream({ contractId }: LiveEventStreamProps) {
filteredEvents.map((ev, i) => (
<TableRow key={`${ev.id}-${i}`} className="group/row">
<TableCell className="text-[10px] whitespace-nowrap opacity-70">
{new Date(ev.ts).toLocaleTimeString([], { hour12: false })}
<time dateTime={ev.ts}>
{new Date(ev.ts).toLocaleTimeString([], { hour12: false })}
</time>
</TableCell>
<TableCell>
<span className="text-[10px] px-1.5 py-0.5 border border-terminal-green/30 bg-terminal-green/5">
{ev.type}
</span>
</TableCell>
<TableCell className="text-[10px] text-terminal-cyan">
{ev.contract.slice(0, 4)}...{ev.contract.slice(-4)}
<TableCell className="text-[10px] text-terminal-cyan font-mono">
<span aria-label={`Contract ID: ${ev.contract}`}>
{ev.contract.slice(0, 4)}...{ev.contract.slice(-4)}
</span>
</TableCell>
<TableCell className="flex justify-center">
<button
onClick={() => handleCopy(ev)}
className="p-1 hover:text-terminal-green transition-colors"
title="Copy Data"
className="p-1 hover:text-terminal-green transition-colors outline-none focus-visible:ring-1 focus-visible:ring-terminal-green rounded-sm"
aria-label={`Copy data for event ${ev.type}`}
>
{lastCopiedId === ev.id ? (
<CheckCircle2 className="w-3 h-3 text-terminal-green" />
<CheckCircle2 className="w-3 h-3 text-terminal-green" aria-hidden="true" />
) : (
<Copy className="w-3 h-3 opacity-30 group-hover/row:opacity-100" />
<Copy className="w-3 h-3 opacity-30 group-hover/row:opacity-100" aria-hidden="true" />
)}
</button>
</TableCell>
Expand Down
8 changes: 4 additions & 4 deletions soroscan-frontend/components/terminal/landing/EventStream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ export function EventStream() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 border border-terminal-green/20 p-6 md:p-8 relative">
<div className="scanline-overlay" />
<div>
<div className="text-[10px] text-terminal-danger tracking-widest mb-2 font-terminal-mono">[PROBLEM]</div>
<div className="text-[10px] text-terminal-danger tracking-widest mb-2 font-terminal-mono font-bold">[PROBLEM]</div>
<h3 className="text-xl font-bold text-terminal-danger mb-3 font-terminal-mono">NO_THE_GRAPH_FOR_SOROBAN</h3>
<p className="text-terminal-gray text-sm leading-relaxed">
<p className="text-terminal-gray-light text-sm leading-relaxed">
Developers building on Stellar&apos;s Soroban smart contracts have no reliable way to
index or query on-chain events. Building custom indexers means managing infrastructure,
dealing with ledger polling, and writing brittle parsers — just to answer &quot;what events did my contract emit?&quot;
</p>
</div>
<div>
<div className="text-[10px] text-terminal-green tracking-widest mb-2 font-terminal-mono">[SOLUTION]</div>
<div className="text-[10px] text-terminal-green tracking-widest mb-2 font-terminal-mono font-bold">[SOLUTION]</div>
<h3 className="text-xl font-bold text-terminal-green mb-3 font-terminal-mono">SOROSCAN_IS_THE_FIX</h3>
<p className="text-terminal-gray text-sm leading-relaxed">
<p className="text-terminal-gray-light text-sm leading-relaxed">
SoroScan provides a managed event indexing service — connect your contract, define your
event schema, and immediately query events via GraphQL or REST. Webhook subscriptions
push events to your backend in real-time. No infrastructure. No polling. No complexity.
Expand Down
6 changes: 3 additions & 3 deletions soroscan-frontend/components/terminal/landing/Features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ export function Features() {
<Card title={f.title} className="h-full hover:shadow-glow-green transition-shadow duration-300">
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
{f.icon}
<span className="text-[10px] text-terminal-gray tracking-widest">{f.title}</span>
<span aria-hidden="true">{f.icon}</span>
<span className="text-[10px] text-terminal-gray-light tracking-widest font-bold uppercase">{f.title}</span>
</div>
<p className="text-sm leading-relaxed text-terminal-gray">{f.body}</p>
<p className="text-sm leading-relaxed text-terminal-gray-light">{f.body}</p>
</div>
</Card>
</div>
Expand Down
25 changes: 14 additions & 11 deletions soroscan-frontend/components/terminal/landing/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function Footer() {
<h2 className="text-2xl md:text-3xl font-bold text-terminal-cyan tracking-tight font-terminal-mono">
READY_TO_UPLINK?
</h2>
<p className="text-terminal-gray max-w-md mx-auto text-sm leading-relaxed">
<p className="text-terminal-gray-light max-w-md mx-auto text-sm leading-relaxed font-medium">
Join the decentralised indexing network and fuel your Soroban dApps with
high-fidelity event data — free during open beta.
</p>
Expand All @@ -67,43 +67,46 @@ export function Footer() {
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 mb-10">
{/* Brand */}
<div className="col-span-2 md:col-span-1">
<Link href="/" className="text-terminal-green text-lg font-bold tracking-tighter hover:text-terminal-cyan transition-colors">
<Link href="/" className="text-terminal-green text-lg font-bold tracking-tighter hover:text-terminal-cyan transition-colors" aria-label="SoroScan Home">
[SOROSCAN]
</Link>
<p className="text-[11px] text-terminal-gray mt-2 leading-relaxed max-w-xs">
<p className="text-[11px] text-terminal-gray-light mt-2 leading-relaxed max-w-xs font-medium">
The Graph for Soroban. Real-time event indexing for the Stellar ecosystem.
</p>
<a
href="https://github.com/SoroScan/soroscan"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 mt-4 text-terminal-gray hover:text-terminal-green transition-colors text-xs"
className="inline-flex items-center gap-1.5 mt-4 text-terminal-gray-light hover:text-terminal-green transition-colors text-xs font-bold"
aria-label="SoroScan on GitHub"
>
<Github size={14} />
<Github size={14} aria-hidden="true" />
GitHub
</a>
</div>

{/* Nav columns */}
{NAV_COLS.map((col) => (
<div key={col.heading}>
<h3 className="text-[10px] text-terminal-green tracking-widest mb-3">{col.heading}</h3>
<ul className="space-y-2">
<h3 className="text-[10px] text-terminal-green tracking-widest mb-3 font-bold" id={`footer-heading-${col.heading.toLowerCase()}`}>
{col.heading}
</h3>
<ul className="space-y-2" aria-labelledby={`footer-heading-${col.heading.toLowerCase()}`}>
{col.links.map((link) => (
<li key={link.label}>
{link.external ? (
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="text-[11px] text-terminal-gray hover:text-terminal-green transition-colors"
className="text-[11px] text-terminal-gray-light hover:text-terminal-green transition-colors font-medium"
>
{link.label}
</a>
) : (
<Link
href={link.href}
className="text-[11px] text-terminal-gray hover:text-terminal-green transition-colors"
className="text-[11px] text-terminal-gray-light hover:text-terminal-green transition-colors font-medium"
>
{link.label}
</Link>
Expand All @@ -118,13 +121,13 @@ export function Footer() {
{/* Bottom bar */}
<div className="border-t border-terminal-green/10 pt-4 flex flex-col md:flex-row justify-between items-center text-[10px] text-terminal-gray gap-3">
<div className="flex flex-wrap gap-4 justify-center md:justify-start">
<span>&copy; 2026 SOROSCAN_INDEXER_SERVICES</span>
<span>&copy; {new Date().getFullYear()} SOROSCAN_INDEXER_SERVICES</span>
<a href="#" className="hover:text-terminal-green underline underline-offset-4">TERMS_OF_SERVICE</a>
<a href="#" className="hover:text-terminal-green underline underline-offset-4">PRIVACY_POLICY</a>
</div>
<div className="flex items-center gap-4">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-terminal-green animate-pulse" />
<span className="w-2 h-2 rounded-full bg-terminal-green animate-pulse" aria-hidden="true" />
STELLAR_MAINNET_UPLINK: ONLINE
</span>
<span className="border border-terminal-gray/30 px-2 py-0.5">
Expand Down
Loading
Loading