feat(cli): add fanout push strategy and improve website responsiveness

- Add --fanout flag to push command for server-to-server deployment
- Implement agent forwarding for efficient multi-node distribution
- Update landing page scene heights and section padding for mobile devices
This commit is contained in:
anonpenguin23 2026-03-28 15:27:54 +02:00
parent b9b6e19bb8
commit 7284cb4578
16 changed files with 167 additions and 83 deletions

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"github.com/DeBrosOfficial/network/pkg/cli" "github.com/DeBrosOfficial/network/pkg/cli"
"github.com/DeBrosOfficial/network/pkg/cli/noderesolver" "github.com/DeBrosOfficial/network/pkg/cli/noderesolver"
@ -14,9 +15,10 @@ import (
) )
var ( var (
envFlag string envFlag string
ipFlag string ipFlag string
userFlag string userFlag string
fanoutFlag bool
) )
// Cmd is the top-level "push" command — upload binary archive to nodes. // Cmd is the top-level "push" command — upload binary archive to nodes.
@ -25,14 +27,13 @@ var Cmd = &cobra.Command{
Short: "Push binary archive to your nodes", Short: "Push binary archive to your nodes",
Long: `Upload the pre-built binary archive to nodes and extract it. Long: `Upload the pre-built binary archive to nodes and extract it.
Use --ip to push to a single node, or omit it to push to all nodes By default, uploads from your machine to each node sequentially.
in the active environment. Use --fanout to upload to one node, then fan out server-to-server (faster).
Examples: Examples:
orama push --ip 1.2.3.4 # Push to one node orama push --ip 1.2.3.4 # Push to one node
orama push --ip 1.2.3.4 --user ubuntu # Push with specific SSH user orama push --env devnet # Sequential push to all devnet nodes
orama push --env devnet # Push to all devnet nodes orama push --env devnet --fanout # Fan out server-to-server (faster)`,
orama push --env devnet --ip 1.2.3.4 # Push to one devnet node`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
archivePath := findNewestArchive() archivePath := findNewestArchive()
if archivePath == "" { if archivePath == "" {
@ -47,7 +48,6 @@ Examples:
var nodes []inspector.Node var nodes []inspector.Node
if ipFlag != "" { if ipFlag != "" {
// Single node push
user := userFlag user := userFlag
if user == "" { if user == "" {
user = "root" user = "root"
@ -55,8 +55,8 @@ Examples:
vaultTarget := fmt.Sprintf("%s/%s", ipFlag, user) vaultTarget := fmt.Sprintf("%s/%s", ipFlag, user)
env := envFlag env := envFlag
if env == "" { if env == "" {
active, err := cli.GetActiveEnvironment() active, _ := cli.GetActiveEnvironment()
if err == nil { if active != nil {
env = active.Name env = active.Name
} }
} }
@ -64,13 +64,9 @@ Examples:
vaultTarget = "sandbox/root" vaultTarget = "sandbox/root"
} }
nodes = []inspector.Node{{ nodes = []inspector.Node{{
Host: ipFlag, Host: ipFlag, User: user, VaultTarget: vaultTarget, Environment: env,
User: user,
VaultTarget: vaultTarget,
Environment: env,
}} }}
} else { } else {
// All nodes in environment
env := envFlag env := envFlag
if env == "" { if env == "" {
active, err := cli.GetActiveEnvironment() active, err := cli.GetActiveEnvironment()
@ -79,7 +75,6 @@ Examples:
} }
env = active.Name env = active.Name
} }
resolved, err := noderesolver.ResolveNodes(env) resolved, err := noderesolver.ResolveNodes(env)
if err != nil { if err != nil {
return fmt.Errorf("failed to resolve nodes: %w", err) return fmt.Errorf("failed to resolve nodes: %w", err)
@ -97,27 +92,13 @@ Examples:
} }
defer cleanup() defer cleanup()
fmt.Printf("Pushing to %d node(s)...\n\n", len(nodes)) // Single node or default: upload sequentially
if len(nodes) == 1 || !fanoutFlag {
remotePath := "/tmp/" + filepath.Base(archivePath) return pushDirect(nodes, archivePath)
extractCmd := fmt.Sprintf("sudo bash -c 'mkdir -p /opt/orama && tar xzf %s -C /opt/orama && rm -f %s && /opt/orama/bin/orama version'", remotePath, remotePath)
for _, n := range nodes {
fmt.Printf(" %s: uploading...", n.Host)
if err := remotessh.UploadFile(n, archivePath, remotePath); err != nil {
fmt.Printf(" FAILED (%v)\n", err)
continue
}
fmt.Printf(" extracting...")
if err := remotessh.RunSSHStreaming(n, extractCmd); err != nil {
fmt.Printf(" FAILED (%v)\n", err)
continue
}
fmt.Println(" OK")
} }
fmt.Println("\nPush complete") // Multi-node with --fanout: use agent forwarding
return nil return pushFanout(nodes, archivePath)
}, },
} }
@ -125,6 +106,88 @@ func init() {
Cmd.Flags().StringVar(&envFlag, "env", "", "Target environment (default: active)") Cmd.Flags().StringVar(&envFlag, "env", "", "Target environment (default: active)")
Cmd.Flags().StringVar(&ipFlag, "ip", "", "Push to a single node by IP") Cmd.Flags().StringVar(&ipFlag, "ip", "", "Push to a single node by IP")
Cmd.Flags().StringVar(&userFlag, "user", "", "SSH user (default: root)") Cmd.Flags().StringVar(&userFlag, "user", "", "SSH user (default: root)")
Cmd.Flags().BoolVar(&fanoutFlag, "fanout", false, "Upload to first node, then fan out server-to-server (faster)")
}
// pushDirect uploads the archive from local machine to each node sequentially.
func pushDirect(nodes []inspector.Node, archivePath string) error {
fmt.Printf("Pushing to %d node(s) (direct)...\n\n", len(nodes))
remotePath := "/tmp/" + filepath.Base(archivePath)
extractCmd := fmt.Sprintf("sudo bash -c 'mkdir -p /opt/orama && tar xzf %s -C /opt/orama && rm -f %s && /opt/orama/bin/orama version'", remotePath, remotePath)
for _, n := range nodes {
fmt.Printf(" %s: uploading...", n.Host)
if err := remotessh.UploadFile(n, archivePath, remotePath); err != nil {
fmt.Printf(" FAILED (%v)\n", err)
continue
}
fmt.Printf(" extracting...")
if err := remotessh.RunSSHStreaming(n, extractCmd); err != nil {
fmt.Printf(" FAILED (%v)\n", err)
continue
}
fmt.Println(" OK")
}
fmt.Println("\nPush complete")
return nil
}
// pushFanout uploads the archive to the first node, then fans out server-to-server
// using SSH agent forwarding.
func pushFanout(nodes []inspector.Node, archivePath string) error {
fmt.Printf("Pushing to %d node(s) (fanout)...\n\n", len(nodes))
hub := nodes[0]
targets := nodes[1:]
remotePath := "/tmp/" + filepath.Base(archivePath)
extractCmd := fmt.Sprintf("mkdir -p /opt/orama && tar xzf %s -C /opt/orama && rm -f %s", remotePath, remotePath)
// Load SSH keys into the system ssh-agent for agent forwarding
fmt.Println(" Loading SSH keys into agent...")
if err := remotessh.LoadAgentKeys(nodes); err != nil {
fmt.Printf(" Warning: failed to load agent keys: %v\n", err)
fmt.Println(" Falling back to direct push...")
return pushDirect(nodes, archivePath)
}
// Upload archive to hub
fmt.Printf(" %s (hub): uploading...", hub.Host)
if err := remotessh.UploadFile(hub, archivePath, remotePath); err != nil {
return fmt.Errorf("failed to upload to hub %s: %w", hub.Host, err)
}
fmt.Printf(" extracting...")
if err := remotessh.RunSSHStreaming(hub, "sudo bash -c '"+extractCmd+"'"); err != nil {
return fmt.Errorf("failed to extract on hub %s: %w", hub.Host, err)
}
fmt.Println(" OK")
// Build the fanout command — hub SCPs to all targets in parallel
var fanoutParts []string
for _, t := range targets {
scpCmd := fmt.Sprintf(
"scp -o StrictHostKeyChecking=accept-new -o IdentitiesOnly=no %s %s@%s:%s && ssh -o StrictHostKeyChecking=accept-new %s@%s 'sudo bash -c \"%s\"' && echo '%s: done'",
remotePath, t.User, t.Host, remotePath,
t.User, t.Host, extractCmd,
t.Host,
)
fanoutParts = append(fanoutParts, "("+scpCmd+") &")
}
fanoutParts = append(fanoutParts, "wait", "echo 'Fanout complete'")
fanoutScript := strings.Join(fanoutParts, "\n")
fmt.Printf(" Fanning out to %d nodes from %s...\n", len(targets), hub.Host)
if err := remotessh.RunSSHStreaming(hub, "bash -c '"+fanoutScript+"'", remotessh.WithAgentForward()); err != nil {
fmt.Printf(" Fanout failed: %v\n", err)
fmt.Println(" Some nodes may not have been updated")
}
// Clean up archive on hub
remotessh.RunSSHStreaming(hub, "rm -f "+remotePath)
fmt.Println("\nPush complete")
return nil
} }
func findNewestArchive() string { func findNewestArchive() string {

View File

@ -254,7 +254,7 @@ function AboutNetwork() {
export function AboutHeroScene() { export function AboutHeroScene() {
return ( return (
<div className="w-full h-[550px] -mt-[400px]"> <div className="w-full h-[350px] md:h-[550px] -mt-[250px] md:-mt-[400px]">
<Canvas <Canvas
camera={{ position: [0, 2, 2.5], fov: 42 }} camera={{ position: [0, 2, 2.5], fov: 42 }}
dpr={[1, 2]} dpr={[1, 2]}

View File

@ -341,7 +341,7 @@ function ComputeMesh() {
export function ComputeMeshScene() { export function ComputeMeshScene() {
return ( return (
<div className="w-full h-[550px] -mt-[400px]"> <div className="w-full h-[350px] md:h-[550px] -mt-[250px] md:-mt-[400px]">
<Canvas <Canvas
camera={{ position: [0, 3, 3.5], fov: 38 }} camera={{ position: [0, 3, 3.5], fov: 38 }}
dpr={[1, 2]} dpr={[1, 2]}

View File

@ -230,7 +230,7 @@ function ConsensusNetwork() {
export function ConsensusScene() { export function ConsensusScene() {
return ( return (
<div className="w-full h-[550px] -mt-[400px]"> <div className="w-full h-[350px] md:h-[550px] -mt-[250px] md:-mt-[400px]">
<Canvas <Canvas
camera={{ position: [0, 2, 2], fov: 45 }} camera={{ position: [0, 2, 2], fov: 45 }}
dpr={[1, 2]} dpr={[1, 2]}

View File

@ -334,7 +334,7 @@ function GrowthVaultNetwork() {
export function GrowthVaultScene() { export function GrowthVaultScene() {
return ( return (
<div className="w-full h-[550px] -mt-[400px]"> <div className="w-full h-[350px] md:h-[550px] -mt-[250px] md:-mt-[400px]">
<Canvas <Canvas
camera={{ position: [0, 2, 3], fov: 40 }} camera={{ position: [0, 2, 3], fov: 40 }}
dpr={[1, 2]} dpr={[1, 2]}

View File

@ -180,8 +180,8 @@ function OramaOneNode() {
export function OramaOneScene() { export function OramaOneScene() {
return ( return (
<div <div
className="absolute left-0 right-0 bottom-0 pointer-events-none" className="absolute left-0 right-0 bottom-0 pointer-events-none h-[50%] md:h-[70%]"
style={{ height: "70%", opacity: 0.75 }} style={{ opacity: 0.75 }}
> >
<Canvas <Canvas
camera={{ position: [2.2, 2.2, 2.2], fov: 28 }} camera={{ position: [2.2, 2.2, 2.2], fov: 28 }}

View File

@ -4,7 +4,7 @@ import { cn } from "../../lib/utils";
const paddingVariants = { const paddingVariants = {
default: "py-16 sm:py-24", default: "py-16 sm:py-24",
narrow: "py-8 sm:py-12", narrow: "py-8 sm:py-12",
wide: "py-24 sm:py-32", wide: "py-12 sm:py-24 lg:py-32",
none: "py-0", none: "py-0",
} as const; } as const;

View File

@ -1,5 +1,5 @@
import { Outlet } from "react-router"; import { Outlet } from "react-router";
import { Suspense } from "react"; import { Suspense, useState } from "react";
import { LoadingSpinner } from "../ui/loading-spinner"; import { LoadingSpinner } from "../ui/loading-spinner";
import { WhitelistBanner } from "../navigation/whitelist-banner"; import { WhitelistBanner } from "../navigation/whitelist-banner";
import { Navbar } from "../navigation/navbar"; import { Navbar } from "../navigation/navbar";
@ -8,6 +8,8 @@ import { ScrollToTop } from "../ui/scroll-to-top";
import { FloatingCTA } from "../navigation/floating-cta"; import { FloatingCTA } from "../navigation/floating-cta";
export function Shell() { export function Shell() {
const [bannerVisible, setBannerVisible] = useState(true);
return ( return (
<div <div
className="min-h-screen bg-surface text-fg" className="min-h-screen bg-surface text-fg"
@ -17,8 +19,8 @@ export function Shell() {
backgroundSize: "24px 24px", backgroundSize: "24px 24px",
}} }}
> >
<WhitelistBanner /> <WhitelistBanner onDismiss={() => setBannerVisible(false)} />
<Navbar /> <Navbar bannerVisible={bannerVisible} />
<Suspense <Suspense
fallback={ fallback={
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
@ -26,7 +28,7 @@ export function Shell() {
</div> </div>
} }
> >
<main className="pt-32"> <main className="pt-16 md:pt-32">
<Outlet /> <Outlet />
</main> </main>
</Suspense> </Suspense>

View File

@ -1,6 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Link, useLocation } from "react-router"; import { Link, useLocation } from "react-router";
import { ExternalLink } from "lucide-react"; import { ExternalLink, X } from "lucide-react";
import { NAV_LINKS, MORE_LINKS } from "../../data/navigation"; import { NAV_LINKS, MORE_LINKS } from "../../data/navigation";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { cn } from "../../lib/utils"; import { cn } from "../../lib/utils";
@ -33,13 +33,22 @@ export function MobileMenu({ open, onClose }: MobileMenuProps) {
return ( return (
<div <div
className={cn( className={cn(
"fixed inset-0 z-40 bg-bg/95 backdrop-blur-md md:hidden flex flex-col transition-all duration-300", "fixed inset-0 z-[60] bg-bg/95 backdrop-blur-md md:hidden flex flex-col transition-all duration-300",
open open
? "opacity-100 pointer-events-auto" ? "opacity-100 pointer-events-auto"
: "opacity-0 pointer-events-none", : "opacity-0 pointer-events-none",
)} )}
> >
<div className="h-20 shrink-0" /> <div className="flex items-center justify-end px-6 pt-5 pb-2">
<button
type="button"
onClick={onClose}
className="flex items-center justify-center w-10 h-10 text-muted hover:text-fg transition-colors"
aria-label="Close menu"
>
<X size={24} />
</button>
</div>
<nav className="flex flex-col px-6 gap-1"> <nav className="flex flex-col px-6 gap-1">
{NAV_LINKS.map((link) => { {NAV_LINKS.map((link) => {
@ -69,20 +78,18 @@ export function MobileMenu({ open, onClose }: MobileMenuProps) {
); );
})} })}
<div className="border-t border-border/30 mt-3 pt-3"> {MORE_LINKS.map((link) => {
{MORE_LINKS.map((link) => { const isActive = location.pathname === link.href;
const isActive = location.pathname === link.href; return (
return ( <Link
<Link key={link.href}
key={link.href} to={link.href}
to={link.href} className={cn(menuLinkClass, isActive ? "text-fg" : "text-muted hover:text-fg")}
className={cn(menuLinkClass, "text-lg", isActive ? "text-fg" : "text-muted hover:text-fg")} >
> {link.label}
{link.label} </Link>
</Link> );
); })}
})}
</div>
</nav> </nav>
<div className="mt-auto px-6 pb-8"> <div className="mt-auto px-6 pb-8">

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useCallback } from "react";
import { Link, useLocation } from "react-router"; import { Link, useLocation } from "react-router";
import { Menu, X, ExternalLink, ChevronDown } from "lucide-react"; import { Menu, X, ExternalLink, ChevronDown } from "lucide-react";
import { cn } from "../../lib/utils"; import { cn } from "../../lib/utils";
@ -11,10 +11,11 @@ const linkClass = "px-3 py-1.5 text-xs tracking-wider uppercase font-mono rounde
const activeClass = "text-fg bg-white/[0.06]"; const activeClass = "text-fg bg-white/[0.06]";
const inactiveClass = "text-muted hover:text-fg hover:bg-white/[0.04]"; const inactiveClass = "text-muted hover:text-fg hover:bg-white/[0.04]";
export function Navbar() { export function Navbar({ bannerVisible = true }: { bannerVisible?: boolean }) {
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const [moreOpen, setMoreOpen] = useState(false); const [moreOpen, setMoreOpen] = useState(false);
const { pathname } = useLocation(); const { pathname } = useLocation();
const handleMobileClose = useCallback(() => setMobileOpen(false), []);
useEffect(() => { useEffect(() => {
setMoreOpen(false); setMoreOpen(false);
@ -22,7 +23,7 @@ export function Navbar() {
return ( return (
<> <>
<header className="fixed top-16 left-0 right-0 z-50 flex justify-center px-4 pt-2"> <header className={cn("fixed left-0 right-0 z-50 flex justify-center px-4 pt-2 transition-[top] duration-300", bannerVisible ? "top-16" : "top-4")}>
<nav className="flex items-center justify-between w-full max-w-5xl h-12 px-5 bg-surface-2/80 backdrop-blur-xl border border-border/60 rounded-full shadow-[0_4px_24px_rgba(0,0,0,0.4)]"> <nav className="flex items-center justify-between w-full max-w-5xl h-12 px-5 bg-surface-2/80 backdrop-blur-xl border border-border/60 rounded-full shadow-[0_4px_24px_rgba(0,0,0,0.4)]">
<Link to="/" className="flex items-center gap-2 group"> <Link to="/" className="flex items-center gap-2 group">
<img src={oramaIcon} alt="Orama" className="h-8 w-8 shrink-0 transition-transform duration-700 ease-in-out group-hover:rotate-[360deg]" /> <img src={oramaIcon} alt="Orama" className="h-8 w-8 shrink-0 transition-transform duration-700 ease-in-out group-hover:rotate-[360deg]" />
@ -125,7 +126,7 @@ export function Navbar() {
<MobileMenu <MobileMenu
open={mobileOpen} open={mobileOpen}
onClose={() => setMobileOpen(false)} onClose={handleMobileClose}
/> />
</> </>
); );

View File

@ -2,7 +2,7 @@ import { useState } from "react";
import { X, ArrowRight } from "lucide-react"; import { X, ArrowRight } from "lucide-react";
import { cn } from "../../lib/utils"; import { cn } from "../../lib/utils";
export function WhitelistBanner() { export function WhitelistBanner({ onDismiss }: { onDismiss?: () => void }) {
const [dismissed, setDismissed] = useState(false); const [dismissed, setDismissed] = useState(false);
if (dismissed) return null; if (dismissed) return null;
@ -33,7 +33,7 @@ export function WhitelistBanner() {
</a> </a>
<button <button
type="button" type="button"
onClick={() => setDismissed(true)} onClick={() => { setDismissed(true); onDismiss?.(); }}
className="flex items-center justify-center w-6 h-6 text-black/40 hover:text-black transition-colors cursor-pointer ml-2" className="flex items-center justify-center w-6 h-6 text-black/40 hover:text-black transition-colors cursor-pointer ml-2"
aria-label="Dismiss banner" aria-label="Dismiss banner"
> >

View File

@ -111,8 +111,13 @@ orama push --ip 1.2.3.4
# Push to all nodes in an environment # Push to all nodes in an environment
orama push --env devnet orama push --env devnet
# Fan out server-to-server (faster for many nodes)
orama push --env devnet --fanout
``` ```
Use `--fanout` to upload to the first node, then fan out server-to-server via SSH agent forwarding. Much faster when you have many nodes.
## Adding New Nodes ## Adding New Nodes
Add a new VPS to your cluster with one command: Add a new VPS to your cluster with one command:

View File

@ -158,15 +158,21 @@ orama push --ip 1.2.3.4
# Push to all nodes in an environment # Push to all nodes in an environment
orama push --env devnet orama push --env devnet
# Fan out server-to-server (faster for many nodes)
orama push --env devnet --fanout
# Push with a specific SSH user # Push with a specific SSH user
orama push --ip 1.2.3.4 --user ubuntu orama push --ip 1.2.3.4 --user ubuntu
``` ```
Use `--fanout` to upload to the first node, then distribute server-to-server via SSH agent forwarding. Much faster when pushing to many nodes.
| Flag | Default | Description | | Flag | Default | Description |
|------|---------|-------------| |------|---------|-------------|
| `--ip` | | Push to a single node by IP | | `--ip` | | Push to a single node by IP |
| `--env` | active env | Push to all nodes in environment | | `--env` | active env | Push to all nodes in environment |
| `--user` | `root` | SSH user | | `--user` | `root` | SSH user |
| `--fanout` | `false` | Fan out from first node server-to-server (faster) |
### Legacy Push ### Legacy Push

View File

@ -97,7 +97,7 @@ function BlockchainHero() {
</a> </a>
</Button> </Button>
<Button asChild variant="ghost" size="lg"> <Button asChild variant="ghost" size="lg">
<Link to="/node"> <Link to="/investors#participate">
Run a Node Run a Node
<ArrowRight className="w-3.5 h-3.5 ml-2" /> <ArrowRight className="w-3.5 h-3.5 ml-2" />
</Link> </Link>

View File

@ -92,9 +92,9 @@ function ComputeHero() {
</p> </p>
<div className="flex flex-wrap items-center justify-center gap-3"> <div className="flex flex-wrap items-center justify-center gap-3">
<Link to="/investors"> <Link to="/dashboard">
<Button size="lg" className="silver-button text-black font-mono font-semibold tracking-wider uppercase px-8 py-3 text-sm rounded-sm cursor-pointer opacity-50 pointer-events-none"> <Button size="lg" className="silver-button text-black font-mono font-semibold tracking-wider uppercase px-8 py-3 text-sm rounded-sm cursor-pointer">
COMING SOON <ArrowRight className="w-4 h-4 ml-2" /> Start Deploying <ArrowRight className="w-4 h-4 ml-2" />
</Button> </Button>
</Link> </Link>
<a href="https://t.me/debrosportal" target="_blank" rel="noopener noreferrer"> <a href="https://t.me/debrosportal" target="_blank" rel="noopener noreferrer">
@ -106,7 +106,7 @@ function ComputeHero() {
<div className="flex items-center gap-2 text-xs font-mono text-muted"> <div className="flex items-center gap-2 text-xs font-mono text-muted">
<StatusDot status="active" /> <StatusDot status="active" />
<span>TESTNET LIVE 300 NODES REQUIRED FOR GENESIS</span> <span>TESTNET LIVE WITH 50+ NODES</span>
</div> </div>
</div> </div>
</Section> </Section>
@ -187,7 +187,7 @@ function OramaOneSection() {
{ label: "Consensus", value: "Hybrid PoS+PoC+PoI" }, { label: "Consensus", value: "Hybrid PoS+PoC+PoI" },
{ label: "Block Time", value: "6 seconds" }, { label: "Block Time", value: "6 seconds" },
{ label: "OramaOS", value: "1.5x Multiplier" }, { label: "OramaOS", value: "1.5x Multiplier" },
{ label: "Status", value: "Coming Soon" }, { label: "Status", value: "Live" },
].map((stat) => ( ].map((stat) => (
<div key={stat.label} className="flex flex-col gap-1 text-center"> <div key={stat.label} className="flex flex-col gap-1 text-center">
<span className="text-xs font-mono text-zinc-500 uppercase">{stat.label}</span> <span className="text-xs font-mono text-zinc-500 uppercase">{stat.label}</span>
@ -196,8 +196,10 @@ function OramaOneSection() {
))} ))}
</div> </div>
<Button className="silver-button text-black font-mono font-semibold tracking-wider uppercase px-8 py-3 text-sm rounded-sm opacity-50 pointer-events-none"> <Button asChild className="silver-button text-black font-mono font-semibold tracking-wider uppercase px-8 py-3 text-sm rounded-sm cursor-pointer">
Coming Soon <ArrowRight className="w-4 h-4 ml-2" /> <Link to="/dashboard">
Start Deploying <ArrowRight className="w-4 h-4 ml-2" />
</Link>
</Button> </Button>
{/* 3D Node */} {/* 3D Node */}

View File

@ -75,7 +75,7 @@ function HomeHero() {
Decentralized Cloud + L1 Blockchain Decentralized Cloud + L1 Blockchain
</span> </span>
<h1 className="relative z-10 font-display font-bold text-4xl lg:text-6xl leading-tight"> <h1 className="relative z-10 font-display font-bold text-3xl md:text-4xl lg:text-6xl leading-tight">
<SplitText <SplitText
text="Blockchain was step one." text="Blockchain was step one."
className="text-fg" className="text-fg"
@ -107,9 +107,7 @@ function HomeHero() {
`}</style> `}</style>
<p className="relative z-10 text-muted text-sm leading-relaxed max-w-lg"> <p className="relative z-10 text-muted text-sm leading-relaxed max-w-lg">
Bitcoin gave us decentralized money. Ethereum gave us decentralized An L1 blockchain fused with a full cloud platform decentralized everything.
contracts. Orama gives us decentralized everything
an L1 blockchain fused with a full cloud platform.
</p> </p>
<div className="relative z-10 flex flex-wrap gap-2 justify-center"> <div className="relative z-10 flex flex-wrap gap-2 justify-center">
@ -149,7 +147,7 @@ function HomeHero() {
<div className="relative z-10 flex items-center gap-2 mt-2"> <div className="relative z-10 flex items-center gap-2 mt-2">
<span className="w-2.5 h-2.5 rounded-full bg-emerald-400 animate-pulse-dot" /> <span className="w-2.5 h-2.5 rounded-full bg-emerald-400 animate-pulse-dot" />
<span className="text-xs font-mono text-muted tracking-wider uppercase"> <span className="text-xs font-mono text-muted tracking-wider uppercase">
Testnet Live 300 Nodes Required for Genesis Testnet Live WITH 50+ NODES
</span> </span>
</div> </div>