Skip to content

Commit

Permalink
Landing page redesign (#363)
Browse files Browse the repository at this point in the history
* New landing page and reorganized login signup flow

* Abstract into component and style updates

* remove old images and tidy up
  • Loading branch information
Winston-Hsiao authored Sep 7, 2024
1 parent 8c19cb8 commit c7f3e35
Show file tree
Hide file tree
Showing 20 changed files with 486 additions and 2,403 deletions.
8 changes: 6 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import Navbar from "components/nav/Navbar";
import APIKeys from "components/pages/APIKeys";
import About from "components/pages/About";
import Browse from "components/pages/Browse";
import BuyStompy from "components/pages/BuyStompy";
import Create from "components/pages/Create";
import EmailSignup from "components/pages/EmailSignup";
import FileBrowser from "components/pages/FileBrowser";
import Home from "components/pages/Home";
import ListingDetails from "components/pages/ListingDetails";
Expand All @@ -30,18 +32,20 @@ const App = () => {
<AuthenticationProvider>
<AlertQueueProvider>
<AlertQueue>
<div className="dark:bg-gray-900 dark:text-white min-h-screen flex flex-col">
<div className="dark:bg-black dark:text-white min-h-screen flex flex-col">
<Navbar />
<div className="flex-grow">
<Container>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/buy-stompy" element={<BuyStompy />} />
<Route path="/keys" element={<APIKeys />} />
<Route path="/profile/:id?" element={<Profile />} />
<Route path="/login" element={<Login />} />
<Route path="/logout" element={<Logout />} />
<Route path="/signup/:id" element={<Signup />} />
<Route path="/signup/" element={<Signup />} />
<Route path="/signup/:id" element={<EmailSignup />} />
<Route path="/create" element={<Create />} />
<Route path="/browse/:page?" element={<Browse />} />
<Route path="/item/:id" element={<ListingDetails />} />
Expand Down
39 changes: 21 additions & 18 deletions frontend/src/components/auth/AuthBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,30 @@ import { Card, CardContent, CardFooter, CardHeader } from "components/ui/Card";
import Header from "components/ui/Header";
import Spinner from "components/ui/Spinner";

export const AuthBlockInner = () => {
interface AuthBlockProps {
title?: string;
onClosed?: () => void;
signup?: boolean;
}

const AuthBlock: React.FC<AuthBlockProps> = ({ title, onClosed, signup }) => {
return (
<Card className="w-[400px] shadow-md bg-white dark:bg-gray-800 text-black dark:text-white rounded-lg">
<CardHeader>
<Header title={title} onClosed={onClosed} />
</CardHeader>
<AuthBlockInner initialSignup={signup} />
</Card>
);
};

export const AuthBlockInner: React.FC<{ initialSignup?: boolean }> = ({
initialSignup,
}) => {
const auth = useAuthentication();
const { addErrorAlert } = useAlertQueue();

const [isSignup, setIsSignup] = useState(false);
const [isSignup, setIsSignup] = useState(initialSignup ?? false);
const [useSpinner, setUseSpinner] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -72,20 +91,4 @@ export const AuthBlockInner = () => {
);
};

interface AuthBlockProps {
title?: string;
onClosed?: () => void;
}

const AuthBlock: React.FC<AuthBlockProps> = ({ title, onClosed }) => {
return (
<Card className="w-[400px] shadow-md bg-white dark:bg-gray-800 text-black dark:text-white rounded-lg">
<CardHeader>
<Header title={title} onClosed={onClosed} />
</CardHeader>
<AuthBlockInner />
</Card>
);
};

export default AuthBlock;
7 changes: 4 additions & 3 deletions frontend/src/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ const Footer = () => {
pathname?.startsWith("/browse") === false &&
pathname?.startsWith("/login") === false &&
pathname?.startsWith("/item") === false &&
pathname?.startsWith("/create") === false &&
pathname?.startsWith("/signup") === false;

if (!showFooter) {
return null;
}

return (
<footer className="bg-gray-50 dark:bg-gray-800 text-sm py-20">
<footer className="bg-gray-50 dark:bg-gray-900 text-sm py-20">
<div className="flex flex-col gap-4 mx-12 sm:mx-36">
{/* Logo and Social Links */}
<div className="flex flex-row justify-between items-center mb-8">
Expand All @@ -42,7 +43,7 @@ const Footer = () => {
className="h-8 dark:invert"
/>
<span className="ml-2 text-xl font-bold text-gray-800 dark:text-gray-200">
store
K-Scale Labs
</span>
</a>
<div className="flex flex-row gap-4 rounded-full">
Expand Down Expand Up @@ -74,7 +75,7 @@ const Footer = () => {
</div>

{/* Footer Links */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-12 sm:gap-4">
<div className="flex flex-col items-start gap-2 sm:gap-3">
<h2 className="text-base sm:text-lg font-bold mb-1">Company</h2>
<a
Expand Down
159 changes: 159 additions & 0 deletions frontend/src/components/landing/HeroASCIIArt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { useEffect, useRef, useState } from "react";

import { useWindowSize } from "hooks/useWindowSize";

import { Meteors } from "components/ui/Meteors";

const HeroASCIIArt = () => {
const asciiRef = useRef<HTMLDivElement>(null);
const [startTime, setStartTime] = useState(0);
const [animationProgress, setAnimationProgress] = useState(0);
const { width: windowWidth, height: windowHeight } = useWindowSize();

useEffect(() => {
if (!asciiRef.current) return;

// Adjust these values to fine-tune the sizing
const charWidth = 7; // Approximate width of a character in pixels
const charHeight = 14; // Approximate height of a character in pixels
const initialPadding = -14; // Start with negative padding to make it larger
const finalPadding = 7; // End with the original padding

const getSize = (progress: number) => {
const currentPadding =
initialPadding + (finalPadding - initialPadding) * progress;
return {
width: Math.floor((windowWidth - currentPadding) / charWidth),
height: Math.floor((windowHeight - currentPadding) / charHeight),
};
};

const textStrings = [
"Meet Stompy Meet Stompy Meet Stompy",
"Program robots easily with K-Lang Program robots easily with K-Lang Program robots easily with K-Lang",
"Affordable and functional humanoid robots Affordable and functional humanoid robots Affordable and functional humanoid robots",
"Meet Stompy Mini Meet Stompy Mini Meet Stompy Mini",
"How to write a walking policy How to write a walking policy",
"How to build a robot How to build a robot",
"Run robot simulations in your browser Run robot simulations in your browser",
"K-Scale has the best robot developer ecosystem K-Scale has the best robot developer ecosystem",
"Download kernel images, robot models, and more Download kernel images, robot models, and more",
"Download URDFs Download URDFs Download URDFs",
"Download Mujoco models Download Mujoco models Download Mujoco models",
"View OnShape models View OnShape models View OnShape models",
"AI-POWERED ROBOTS AI-POWERED ROBOTS AI-POWERED ROBOTS",
"Future of Automation Future of Automation",
"Developing with K-Lang is the easiest way to program robots",
"K-Scale is the best place to learn about robots",
"K-Scale's robot development platform is the best in the world",
"How to train your own robot models How to train your own robot models",
"How to train your own robot policies How to train your own robot policies",
"How to build your own robot How to build your own robot",
"Buy a fully functional humanoid robot Buy a fully functional humanoid robot",
"Stompy can walk and talk Stompy can walk and talk",
"You can control and train Stompy with K-Lang",
"Stompy is the first functional and affordable humanoid robot availble to the public",
];

const kScaleLabsLogo = [
" ",
" ██╗ ██╗ ███████╗ ██████╗ █████╗ ██╗ ███████╗ ██╗ █████╗ ██████╗ ███████╗ ",
" ██║ ██╔╝ ██╔════╝██╔════╝██╔══██╗██║ ██╔════╝ ██║ ██╔══██╗██╔══██╗██╔════╝ ",
" █████╔╝ █████╗███████╗██║ ███████║██║ █████╗ ██║ ███████║██████╔╝███████╗ ",
" ██╔═██╗ ╚════╝╚════██║██║ ██╔══██║██║ ██╔══╝ ██║ ██╔══██║██╔══██╗╚════██║ ",
" ██║ ██╗ ███████║╚██████╗██║ ██║███████╗███████╗ ███████╗██║ ██║██████╔╝███████║ ",
" ╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝ ",
" ",
];

const { width, height } = getSize(1);
const logoHeight = kScaleLabsLogo.length;
const logoWidth = kScaleLabsLogo[0].length;
const logoY = Math.floor((height - logoHeight) / 2);
const logoX = Math.floor((width - logoWidth) / 2) - 5;

const animate = (timestamp: number) => {
if (!startTime) setStartTime(timestamp);
const elapsed = timestamp - startTime;
const progress = Math.min(1, elapsed / 1500); // 1.5-second animation
setAnimationProgress(progress);

const { width, height } = getSize(progress);

const a = 0.0005 * elapsed * progress;
const logoStabilizationTime = 5000;

let result = "";
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (
y >= logoY &&
y < logoY + logoHeight &&
x >= logoX &&
x < logoX + logoWidth
) {
const logoChar = kScaleLabsLogo[y - logoY][x - logoX];
if (elapsed < logoStabilizationTime) {
const t = Math.min(1, elapsed / logoStabilizationTime);
const randomChar = String.fromCharCode(
33 + Math.floor(Math.random() * 94),
);
result += Math.random() < t ? logoChar : randomChar;
} else {
result += logoChar;
}
} else {
const s = 1 - (2 * y) / height;
const o = (2 * x) / width - 1;
const d = Math.sqrt(o * o + s * s);
const l = (0.15 * a) / Math.max(0.1, d);
const f = Math.sin(l);
const b = Math.cos(l);
const u = o * f - s * b;
const m = Math.round(((o * b + s * f + 1) / 2) * width);
const h =
Math.round(((u + 1) / 2) * textStrings.length) %
textStrings.length;
const char =
m < 0 || m >= width || h < 0 || h >= textStrings.length
? " "
: textStrings[h][m] || " ";
result += char;
}
}
result += "\n";
}

// Update the result with responsive sizing
if (asciiRef.current) {
const fontSize = Math.max(
8,
Math.min(12, Math.floor(windowWidth / 100)),
);
asciiRef.current.style.fontSize = `${fontSize}px`;
asciiRef.current.style.lineHeight = "1";
asciiRef.current.textContent = result;
}

requestAnimationFrame(animate);
};

requestAnimationFrame(animate);
}, [windowWidth, windowHeight]);

return (
<div className="relative rounded-lg w-full overflow-hidden">
<div
ref={asciiRef}
className="font-mono text-xs whitespace-pre overflow-hidden mx-2 sm:mx-8 max-w-full max-h-[80vh] rounded-3xl"
style={{
transform: `scale(${1 + 0.05 * (1 - animationProgress)})`, // Start larger, end at normal size
transition: "transform 0.3s ease-out",
}}
/>
<Meteors />
</div>
);
};

export default HeroASCIIArt;
44 changes: 44 additions & 0 deletions frontend/src/components/landing/LandingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import { FaArrowRight } from "react-icons/fa";

interface LandingCardProps {
imageSrc: string;
title: string;
description: string;
onClick: () => void;
icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
}

const LandingCard: React.FC<LandingCardProps> = ({
imageSrc,
title,
description,
onClick,
icon: Icon = FaArrowRight,
}) => {
return (
<div
className="relative bg-white dark:bg-gray-900 rounded-lg shadow-md overflow-hidden cursor-pointer transition-transform hover:scale-[1.01] dark:shadow-white/10 hover:shadow-lg"
onClick={onClick}
>
<img
src={imageSrc}
alt={title}
className="w-full h-64 object-cover rounded-t-lg"
/>
<div className="p-4">
<div className="flex justify-between items-center mb-2">
<h3 className="text-base sm:text-lg font-semibold text-gray-800 dark:text-white">
{title}
</h3>
<Icon className="text-gray-600 dark:text-gray-300" />
</div>
<p className="text-xs sm:text-sm text-gray-600 dark:text-gray-300">
{description}
</p>
</div>
</div>
);
};

export default LandingCard;
Loading

0 comments on commit c7f3e35

Please sign in to comment.