From d1a2cb2090f09b86f0e0719b7c1513256270adb0 Mon Sep 17 00:00:00 2001 From: Ben Bolte Date: Thu, 17 Oct 2024 13:13:10 -0700 Subject: [PATCH] Ben neurotic changes (#487) * better graphic * better * more ui improvements * cleanup * format --- frontend/src/App.tsx | 7 +- frontend/src/components/home/BuySection.tsx | 129 ---------- .../src/components/home/CommunitySection.tsx | 57 ----- frontend/src/components/home/HeroSection.tsx | 48 ---- frontend/src/components/home/KLangDemo.tsx | 111 --------- frontend/src/components/home/NavSection.tsx | 89 ------- frontend/src/components/nav/Navbar.tsx | 196 +++++++++------- frontend/src/components/nav/Sidebar.tsx | 8 +- frontend/src/components/pages/About.tsx | 87 +++---- frontend/src/components/pages/BuyPage.tsx | 189 --------------- frontend/src/components/pages/Home.tsx | 197 ++++++++++++++-- frontend/src/components/pages/KLangPage.tsx | 5 - frontend/src/components/ui/PageHeader.tsx | 221 +++++++++++++----- 13 files changed, 504 insertions(+), 840 deletions(-) delete mode 100644 frontend/src/components/home/BuySection.tsx delete mode 100644 frontend/src/components/home/CommunitySection.tsx delete mode 100644 frontend/src/components/home/HeroSection.tsx delete mode 100644 frontend/src/components/home/KLangDemo.tsx delete mode 100644 frontend/src/components/home/NavSection.tsx delete mode 100644 frontend/src/components/pages/BuyPage.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d5847132..e2b5799c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -13,10 +13,10 @@ import APIKeys from "@/components/pages/APIKeys"; import About from "@/components/pages/About"; import Account from "@/components/pages/Account"; import Browse from "@/components/pages/Browse"; -import BuyPage from "@/components/pages/BuyPage"; 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 Home from "@/components/pages/Home"; import KLangPage from "@/components/pages/KLangPage"; import ListingDetails from "@/components/pages/ListingDetails"; @@ -55,14 +55,14 @@ const App = () => {
- {/* } /> */} + } /> } /> } /> } /> } /> - } /> + } /> } /> { } /> } /> - } /> } /> } /> } /> diff --git a/frontend/src/components/home/BuySection.tsx b/frontend/src/components/home/BuySection.tsx deleted file mode 100644 index 302028eb..00000000 --- a/frontend/src/components/home/BuySection.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { useInView } from "react-intersection-observer"; -import { useNavigate } from "react-router-dom"; - -import { Image } from "@/components/Image"; -import { Button } from "@/components/ui/button"; -import KScale_Garage from "@/images/KScale_Garage.jpeg"; -import { motion } from "framer-motion"; - -export default function BuySection() { - const navigate = useNavigate(); - const [ref, inView] = useInView({ - triggerOnce: true, - threshold: 0.1, - }); - - const containerVariants = { - hidden: { opacity: 0, y: 50 }, - visible: { - opacity: 1, - y: 0, - transition: { duration: 0.5, staggerChildren: 0.2 }, - }, - }; - - const itemVariants = { - hidden: { opacity: 0, y: 20 }, - visible: { opacity: 1, y: 0 }, - }; - - return ( - -
- -
-
- New Release -
-

- Stompy Pro -

-

- While K-Lang is compatible with other humanoid robot platforms, - Stompy Pro comes with our K-Scale OS and other software that makes - it the fastest learning robot on the market. -

-
-
-
- - Robot - - -
    -
  • -
    -

    - Program Your Robot to Do Anything -

    -

    - Powered by K-Lang, our neural network integrated programming - language. You can tell your robot to do anything and it will - learn and improve over time based on positive and negative - reinforcement feedback loops. -

    -
    -
  • -
  • -
    -

    Build Quality

    -

    - Built with an aircraft grade aluminum frame, and the best - motors, actuators, and sensors available. -

    -
    -
  • -
  • -
    -

    Swappable Batteries

    -

    - Stompy Pro comes with a removable battery pack that can be - swapped out for a charged one when needed. -

    -
    -
  • -
-
- - -
-
-
-
-
- ); -} diff --git a/frontend/src/components/home/CommunitySection.tsx b/frontend/src/components/home/CommunitySection.tsx deleted file mode 100644 index 50283d8f..00000000 --- a/frontend/src/components/home/CommunitySection.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { FaDiscord, FaGithub } from "react-icons/fa"; - -import { Button } from "@/components/ui/button"; -import { ChevronRightIcon } from "@radix-ui/react-icons"; - -export default function CommunitySection() { - return ( -
-
-
-
- -

- Join the conversation -

-

- Connect with fellow robot enthusiasts, industry experts, and - researchers. Share projects and ideas, and stay updated on the - latest K-Scale developments -

- -
-
- -

- Developer Resources -

-

- Explore or contribute to dozens of open-source repositories from - robot builds to edge vision-language-action models, kernel images, - simulations, and more. -

- -
-
-
-
- ); -} diff --git a/frontend/src/components/home/HeroSection.tsx b/frontend/src/components/home/HeroSection.tsx deleted file mode 100644 index c3102359..00000000 --- a/frontend/src/components/home/HeroSection.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; -import { FaCirclePlay } from "react-icons/fa6"; - -import KScaleASCII from "@/images/KScaleASCII.png"; -import KScaleASCIIMobile from "@/images/KScaleASCIIMobile.png"; - -import Meteors from "../ui/Meteors"; -import { Button } from "../ui/button"; - -const HeroSection: React.FC = () => { - return ( -
-
- K Scale Labs Logo -
-
- K Scale Labs Logo Mobile -
-

- Program robots with K-Lang, our language purpose-built for humanoid - robots. -

- - - -
- ); -}; - -export default HeroSection; diff --git a/frontend/src/components/home/KLangDemo.tsx b/frontend/src/components/home/KLangDemo.tsx deleted file mode 100644 index e00114b2..00000000 --- a/frontend/src/components/home/KLangDemo.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; - -import { VideoDemo } from "@/components/VideoDemo"; -import TextRevealByWord from "@/components/ui/TextReveal"; -import { Button } from "@/components/ui/button"; - -export default function KLangDemo() { - const navigate = useNavigate(); - const [activeAction, setActiveAction] = - useState("manipulate"); - - const codeSnippets = { - manipulate: `function manipulateObject() { - console.log("Manipulating object"); - // Add manipulation logic here -} - -manipulateObject();`, - turn: `function turnRobot(degrees) { - console.log(\`Turning robot \${degrees} degrees\`); - // Add turning logic here -} - -turnRobot(90);`, - talk: `function speakPhrase(phrase) { - console.log(\`Robot says: \${phrase}\`); - // Add text-to-speech logic here -} - -speakPhrase("Hello, I am a robot.");`, - }; - - return ( -
-
-
-
- -
-
-
-
-
-

- Watch K-Lang In - Action -

-
-
-
- - - -
-
-
-              {codeSnippets[activeAction]}
-            
-
-
-
- - - Programming robots used to be difficult, but with K-Lang anyone can - program robots to do complex tasks. And your robot will continue to - learn and improve over time. - -
- -
-
-
-
- ); -} diff --git a/frontend/src/components/home/NavSection.tsx b/frontend/src/components/home/NavSection.tsx deleted file mode 100644 index 5062e387..00000000 --- a/frontend/src/components/home/NavSection.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useNavigate } from "react-router-dom"; - -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/Card"; -import { - ChevronRightIcon, - CodeIcon, - DownloadIcon, - LayersIcon, - MagnifyingGlassIcon, -} from "@radix-ui/react-icons"; - -export default function NavSection() { - const navigate = useNavigate(); - - return ( -
-
-

- The K-Scale Ecosystem -

-

- We're open source and always iterating. Join us in building the - future of robotics. -

-
-
- {[ - { - title: "K-Lang", - description: "Write, run, find, and upload Klang programs", - icon: CodeIcon, - path: "/k-lang", - buttonText: "Explore K-Lang", - }, - { - title: "Downloads", - description: "Kernel images, URDFs, ML models, and more", - icon: DownloadIcon, - path: "/downloads", - buttonText: "Browse Downloads", - }, - { - title: "Browse Builds", - description: - "Browse robot builds with linked CAD files, part lists, and various related downloads", - icon: MagnifyingGlassIcon, - path: "/browse", - buttonText: "View Builds", - }, - { - title: "K-Sim", - description: "Run simulations", - icon: LayersIcon, - path: "/k-sim", - buttonText: "Start Simulation", - }, - ].map((item, index) => ( - navigate(item.path)} - > - - - {item.title} - - - {item.description} - - - - -
- {item.buttonText} - -
-
-
- ))} -
-
- ); -} diff --git a/frontend/src/components/nav/Navbar.tsx b/frontend/src/components/nav/Navbar.tsx index 9e1d6d3a..ff881932 100644 --- a/frontend/src/components/nav/Navbar.tsx +++ b/frontend/src/components/nav/Navbar.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { FaBars, FaDiscord, @@ -12,14 +12,6 @@ import { Link, useLocation } from "react-router-dom"; import Logo from "@/components/Logo"; import Sidebar from "@/components/nav/Sidebar"; -import { - NavigationMenu, - NavigationMenuContent, - NavigationMenuItem, - NavigationMenuLink, - NavigationMenuList, - NavigationMenuTrigger, -} from "@/components/ui/navigation-menu"; import { useAuthentication } from "@/hooks/useAuth"; import { DownloadIcon, @@ -31,7 +23,9 @@ const Navbar = () => { const { isAuthenticated } = useAuthentication(); const [showSidebar, setShowSidebar] = useState(false); const location = useLocation(); - const [isHoveringDropdown, setIsHoveringDropdown] = useState(false); + const [showDevelopersDropdown, setShowDevelopersDropdown] = useState(false); + const dropdownRef = useRef(null); + const timeoutRef = useRef(null); const navItems = [ { name: "Pro", path: "/pro", isExternal: false }, @@ -55,13 +49,7 @@ const Navbar = () => { const technicalItems = [ { - name: "Docs", - path: "https://docs.kscale.dev/", - icon: , - isExternal: true, - }, - { - name: "Browse", + name: "Builds", path: "/browse", icon: , isExternal: false, @@ -72,12 +60,6 @@ const Navbar = () => { icon: , isExternal: false, }, - { - name: "Code", - path: "https://github.com/kscalelabs", - icon: , - isExternal: true, - }, { name: "Playground", path: "/playground", @@ -90,6 +72,18 @@ const Navbar = () => { icon: , isExternal: false, }, + { + name: "Docs", + path: "https://docs.kscale.dev/", + icon: , + isExternal: true, + }, + { + name: "Code", + path: "https://github.com/kscalelabs", + icon: , + isExternal: true, + }, ]; const ListItem = ({ @@ -107,51 +101,70 @@ const Navbar = () => { }) => { return (
  • - - {isExternal ? ( - -
    - {icon} - {title} - -
    -
    - ) : ( - -
    - {icon} - {title} -
    - - )} -
    + {isExternal ? ( + +
    + {icon} + {title} + +
    +
    + ) : ( + +
    + {icon} + {title} +
    + + )}
  • ); }; + const handleMouseEnter = () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + setShowDevelopersDropdown(true); + }; + + const handleMouseLeave = () => { + timeoutRef.current = window.setTimeout(() => { + setShowDevelopersDropdown(false); + }, 500); + }; + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + return ( <>
    + ); +}; + +const HeroSection: React.FC = () => { + return ( +
    + + +
    +
    +
    + K Scale Labs Logo +
    +
    + K Scale Labs Logo Mobile +
    +
    +
    +
    + ); +}; + +const StompyProSection: React.FC = () => { + const navigate = useNavigate(); + + return ( +
    navigate("/pro")} + > +
    + Stompy Pro Background +
    + +
    +
    +
    +

    + Stompy Pro +

    +
    +
    +
    +
    + ); +}; + +const StompyMiniSection: React.FC = () => { + const navigate = useNavigate(); + + return ( +
    navigate("/mini")} + > +
    + Stompy Mini Background +
    + +
    +
    +
    +

    + Stompy Mini +

    +
    +
    +
    +
    ); }; diff --git a/frontend/src/components/pages/KLangPage.tsx b/frontend/src/components/pages/KLangPage.tsx index 8058ffb5..d5195fe1 100644 --- a/frontend/src/components/pages/KLangPage.tsx +++ b/frontend/src/components/pages/KLangPage.tsx @@ -2,15 +2,10 @@ import React from "react"; import { FiArrowUpRight } from "react-icons/fi"; import { ExampleContent, TextParallaxContent } from "@/components/TextParallax"; -import PageHeader from "@/components/ui/PageHeader"; const KLangPage: React.FC = () => { return (
    -
    = () => { +const PageHeader = () => { const canvasRef = useRef(null); const gridRef = useRef([]); const intervalRef = useRef(); - const { width: windowWidth } = useWindowSize(); const activeCellsRef = useRef>(new Set()); - const mousePosRef = useRef<{ x: number; y: number } | null>(null); + const prevMousePosRef = useRef<{ x: number; y: number } | null>(null); const [gridInitialized, setGridInitialized] = useState(false); - // Define canvas dimensions - const canvasWidth = windowWidth; - const canvasHeight = 800; - const rule = (neighbors: number, cell: boolean) => cell ? neighbors >= 1 && neighbors <= 5 : neighbors === 3; const updateGrid = useCallback((currentGrid: boolean[][]) => { + if ( + !currentGrid || + currentGrid.length === 0 || + currentGrid[0].length === 0 + ) { + return { newGrid: [], changedCells: [] }; + } + const rows = currentGrid.length; const cols = currentGrid[0].length; const newGrid = currentGrid.map((row) => [...row]); @@ -67,27 +63,16 @@ const PageHeader: React.FC = () => { checkAndUpdateCell(y, x); }); - if (mousePosRef.current) { - const { x, y } = mousePosRef.current; - const radius = 10; - for (let dy = -radius; dy <= radius; dy++) { - for (let dx = -radius; dx <= radius; dx++) { - if (dx * dx + dy * dy <= radius * radius) { - const newY = (y + dy + rows) % rows; - const newX = (x + dx + cols) % cols; - newGrid[newY][newX] = false; - newActiveCells.add(`${newY},${newX}`); - } - } - } - } - activeCellsRef.current = newActiveCells; - return newGrid; + return { newGrid, changedCells: Array.from(newActiveCells) }; }, []); const initializeGrid = useCallback( (rows: number, cols: number) => { + if (rows <= 0 || cols <= 0) { + return { newGrid: [], changedCells: [] }; + } + const grid = Array(rows) .fill(null) .map(() => Array(cols).fill(false)); @@ -137,56 +122,176 @@ const PageHeader: React.FC = () => { const ctx = canvas.getContext("2d"); if (!ctx) return; - const cols = Math.floor(canvasWidth / 5); - const rows = Math.floor(canvasHeight / 5); + // Set canvas size to match its display size + const resizeCanvas = () => { + const { width, height } = canvas.getBoundingClientRect(); + canvas.width = width; + canvas.height = height; + setGridInitialized(false); + }; - canvas.width = canvasWidth; - canvas.height = canvasHeight; + if (!gridInitialized) { + resizeCanvas(); + } + window.addEventListener("resize", resizeCanvas); + + // Adjust cell size to ensure all cells fit within the canvas + const cellSize = 5; + const cols = Math.floor(canvas.width / cellSize); + const rows = Math.floor(canvas.height / cellSize); if (!gridInitialized) { - gridRef.current = initializeGrid(rows, cols); + const { newGrid } = initializeGrid(rows, cols); + gridRef.current = newGrid; setGridInitialized(true); } - const drawGrid = (currentGrid: boolean[][]) => { + const drawCell = (x: number, y: number, isAlive: boolean) => { + if (!ctx) return; + if (isAlive) { + const centerX = cols / 2; + const centerY = rows / 2; + const distanceFromCenter = Math.sqrt( + Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2), + ); + const maxDistance = Math.sqrt( + Math.pow(cols / 2, 2) + Math.pow(rows / 2, 2), + ); + // Adjust the gradient factor to make the purple area larger + const gradientFactor = Math.max( + 0, + (distanceFromCenter - maxDistance / 4) / (maxDistance * 0.75), + ); + + // Interpolate between purple (hsl(280, 100%, 50%)) and dark blue (hsl(240, 100%, 20%)) + const hue = 280 - gradientFactor * 40; + const saturation = 100; + const lightness = 50 - gradientFactor * 30; + + ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`; + } else { + ctx.fillStyle = "#1e1f24"; + } + ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); + }; + + const drawFullGrid = (currentGrid: boolean[][]) => { + if (!ctx) return; ctx.fillStyle = "#1e1f24"; ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = "white"; for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { if (currentGrid[y][x]) { - ctx.fillRect(x * 5, y * 5, 5, 5); + drawCell(x, y, true); } } } }; + const updateChangedCells = ( + currentGrid: boolean[][], + changedCells: string[], + ) => { + changedCells.forEach((cellKey) => { + const [y, x] = cellKey.split(",").map(Number); + drawCell(x, y, currentGrid[y][x]); + }); + }; + const updateAndDraw = () => { - gridRef.current = updateGrid(gridRef.current); - drawGrid(gridRef.current); + const { newGrid, changedCells } = updateGrid(gridRef.current); + gridRef.current = newGrid; + updateChangedCells(gridRef.current, changedCells); }; - drawGrid(gridRef.current); + // Initial full draw + drawFullGrid(gridRef.current); + intervalRef.current = window.setInterval(updateAndDraw, 33); + const clearLineBetweenPoints = ( + x1: number, + y1: number, + x2: number, + y2: number, + ) => { + const radius = 20; + const clearRadius = radius + 1; + const clearedCells = new Set(); + + // Pre-calculate squared radius for faster distance checks + const radiusSquared = radius * radius; + + // Bresenham's line algorithm (optimized) + let x = x1; + let y = y1; + const dx = Math.abs(x2 - x1); + const dy = Math.abs(y2 - y1); + const sx = x1 < x2 ? 1 : -1; + const sy = y1 < y2 ? 1 : -1; + let err = dx - dy; + + while (true) { + // Clear circle at current point and mark surrounding cells + for (let dy = -clearRadius; dy <= clearRadius; dy++) { + for (let dx = -clearRadius; dx <= clearRadius; dx++) { + // Use squared distance for faster comparison + const distanceSquared = dx * dx + dy * dy; + if (distanceSquared <= radiusSquared) { + const newY = Math.max(0, Math.min(rows - 1, y + dy)); + const newX = Math.max(0, Math.min(cols - 1, x + dx)); + const cellKey = `${newY},${newX}`; + + if (!clearedCells.has(cellKey)) { + gridRef.current[newY][newX] = false; + clearedCells.add(cellKey); + activeCellsRef.current.add(cellKey); + } + } + } + } + + if (x === x2 && y === y2) break; + const e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x += sx; + } + if (e2 < dx) { + err += dx; + y += sy; + } + } + + return Array.from(clearedCells); + }; + const handleMouseMove = (event: MouseEvent) => { const rect = canvas.getBoundingClientRect(); - const x = event.clientX - rect.left; - const y = event.clientY - rect.top; - - if (x >= 0 && x < canvas.width && y >= 0 && y < canvas.height) { - mousePosRef.current = { - x: Math.floor(x / 5), - y: Math.floor(y / 5), - }; + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + const x = Math.floor(((event.clientX - rect.left) * scaleX) / cellSize); + const y = Math.floor(((event.clientY - rect.top) * scaleY) / cellSize); + + if (x >= 0 && x < cols && y >= 0 && y < rows) { + if (prevMousePosRef.current) { + const changedCells = clearLineBetweenPoints( + prevMousePosRef.current.x, + prevMousePosRef.current.y, + x, + y, + ); + updateChangedCells(gridRef.current, changedCells); + } + prevMousePosRef.current = { x, y }; } else { - mousePosRef.current = null; + prevMousePosRef.current = null; } }; const handleMouseLeave = () => { - mousePosRef.current = null; + prevMousePosRef.current = null; }; document.addEventListener("mousemove", handleMouseMove); @@ -198,17 +303,13 @@ const PageHeader: React.FC = () => { } document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseleave", handleMouseLeave); + window.removeEventListener("resize", resizeCanvas); }; - }, [canvasWidth, canvasHeight, initializeGrid, updateGrid, gridInitialized]); + }, [initializeGrid, updateGrid, gridInitialized]); return ( -
    - +
    +
    ); };