mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-17 09:33:38 -05:00
Create basic homepage and format docs
This commit is contained in:
26
docs/components/Card.tsx
Normal file
26
docs/components/Card.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ReactNode, createElement } from "react";
|
||||
|
||||
export function Card({
|
||||
name,
|
||||
icon,
|
||||
description,
|
||||
}: {
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
description: string;
|
||||
}) {
|
||||
const iconElement = createElement(icon, {
|
||||
className: "text-black size-7 dark:text-white inline mr-2",
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
{iconElement}
|
||||
<span className="align-middle text-2xl font-bold text-black dark:text-white">
|
||||
{name}
|
||||
</span>
|
||||
<p className="text-2xl font-semibold text-neutral-500 dark:text-neutral-500">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
docs/components/GradientText.tsx
Normal file
31
docs/components/GradientText.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export function GradientText({
|
||||
text,
|
||||
innerClassName,
|
||||
className,
|
||||
blur,
|
||||
}: {
|
||||
text: string;
|
||||
innerClassName: string;
|
||||
className?: string;
|
||||
blur: number;
|
||||
}) {
|
||||
return (
|
||||
<div className={clsx("relative", className)}>
|
||||
<p className={clsx("bg-clip-text text-transparent", innerClassName)}>
|
||||
{text}
|
||||
</p>
|
||||
<p
|
||||
className={clsx(
|
||||
"pointer-events-none absolute top-0 hidden select-none bg-clip-text text-transparent dark:block",
|
||||
innerClassName,
|
||||
)}
|
||||
style={{ filter: `blur(${blur}px)` }}
|
||||
>
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
32
docs/components/GridPattern.tsx
Normal file
32
docs/components/GridPattern.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useId } from "react";
|
||||
|
||||
const SIZE = 36;
|
||||
|
||||
export function GridPattern() {
|
||||
const id = useId();
|
||||
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0 bottom-0 left-0 right-0 top-0 -z-10 h-full w-full stroke-neutral-200 dark:stroke-neutral-600/30"
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
id={id}
|
||||
width={SIZE}
|
||||
height={SIZE}
|
||||
patternUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
>
|
||||
<path
|
||||
d={`M.5 ${SIZE}V.5H${SIZE}`}
|
||||
fill="none"
|
||||
strokeDasharray={"4 2"}
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
28
docs/components/Section.tsx
Normal file
28
docs/components/Section.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ReactNode } from "react";
|
||||
import { GradientText } from "./GradientText";
|
||||
|
||||
export function Section({
|
||||
title,
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
className: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="border-t bg-neutral-50 py-32 dark:border-t-neutral-600/30 dark:bg-neutral-950">
|
||||
<div className="mx-auto w-full max-w-screen-xl">
|
||||
<GradientText
|
||||
text={title}
|
||||
className="mx-auto mb-20 w-fit text-center text-4xl font-bold tracking-tighter"
|
||||
innerClassName={className}
|
||||
blur={12}
|
||||
/>
|
||||
<div className="m-2 grid w-full auto-cols-fr gap-20 lg:grid-cols-3">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
docs/components/Step.tsx
Normal file
23
docs/components/Step.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
export function Step({
|
||||
children,
|
||||
title,
|
||||
number,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
title: string;
|
||||
number: number;
|
||||
}) {
|
||||
return (
|
||||
<div className="mb-2 flex grow-0 items-baseline">
|
||||
<p className="m-2 flex size-10 items-center justify-center rounded-full bg-neutral-100 dark:bg-neutral-900">
|
||||
{number}
|
||||
</p>
|
||||
<div>
|
||||
<p className="text-xl font-bold">{title}</p>
|
||||
<div className="my-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
docs/components/Thing.tsx
Normal file
26
docs/components/Thing.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { ReactNode, createElement } from "react";
|
||||
|
||||
export function Thing({
|
||||
title,
|
||||
icon,
|
||||
content,
|
||||
}: {
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
content: string;
|
||||
}) {
|
||||
const iconElement = createElement(icon, {
|
||||
className: "text-black size-7 dark:text-white inline mr-2",
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
{iconElement}
|
||||
<span className="align-middle text-2xl font-bold text-black dark:text-white">
|
||||
{title}
|
||||
</span>
|
||||
<p className="text-2xl font-semibold text-neutral-500 dark:text-neutral-500">
|
||||
{content}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
176
docs/components/pages/Home.tsx
Normal file
176
docs/components/pages/Home.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
IconAdjustments,
|
||||
IconArrowRight,
|
||||
IconBolt,
|
||||
IconBraces,
|
||||
IconCheck,
|
||||
IconClipboard,
|
||||
IconDevices,
|
||||
IconFeather,
|
||||
IconLockCheck,
|
||||
} from "@tabler/icons-react";
|
||||
import { GitHubIcon } from "../../assets/GitHubIcon";
|
||||
import { GridPattern } from "../GridPattern";
|
||||
import { Section } from "../Section";
|
||||
import { GradientText } from "../GradientText";
|
||||
import Image from "next/image";
|
||||
import screenshot_light from "../../assets/screenshot_light.png";
|
||||
import screenshot_dark from "../../assets/screenshot_dark.png";
|
||||
import { Step } from "../Step";
|
||||
import { Card } from "../Card";
|
||||
|
||||
export function Home() {
|
||||
const [copySuccess, setCopySuccess] = useState(false);
|
||||
const [isBrowser, setIsBrowser] = useState(false); // To prevent hydration mismatch
|
||||
useEffect(() => setIsBrowser(true));
|
||||
const handleCopy = (text: string) => {
|
||||
return () => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
setCopySuccess(true);
|
||||
setTimeout(() => {
|
||||
setCopySuccess(false);
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
};
|
||||
return (
|
||||
<div className="home-animation" style={{ opacity: 0 }}>
|
||||
<div className="relative h-full overflow-x-hidden p-4 pt-10 lg:pt-20">
|
||||
<GridPattern />
|
||||
<div className="mx-auto h-full min-h-svh w-full max-w-screen-xl md:h-[46rem] md:min-h-0">
|
||||
<div className="grid gap-8 lg:grid-cols-2">
|
||||
<div className="flex max-w-3xl flex-col gap-8">
|
||||
<div className="text-6xl font-extrabold leading-none tracking-tighter sm:text-7xl">
|
||||
The easiest way to manage your
|
||||
<GradientText
|
||||
text="container updates."
|
||||
innerClassName="bg-gradient-to-r from-blue-500 to-green-500"
|
||||
blur={30}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Cup is a small utility with a big impact. Simplify your
|
||||
container management workflow with fast and efficient update
|
||||
checking, a full-featured CLI and web inteface, and more.
|
||||
</h3>
|
||||
<div className="*:-0 mt-auto grid w-fit grid-cols-2 gap-4 *:flex *:items-center *:gap-2 *:rounded-lg *:px-3 *:py-2">
|
||||
<a
|
||||
href="/docs"
|
||||
target="_blank"
|
||||
className="hide-focus group h-full bg-black text-white dark:bg-white dark:text-black"
|
||||
>
|
||||
Get started
|
||||
<IconArrowRight className="ml-auto mr-1 transition-transform duration-300 ease-out group-hover:translate-x-1 group-focus:translate-x-1 dark:!text-black" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/sergi0g/cup"
|
||||
target="_blank"
|
||||
className="hide-focus h-full text-nowrap border border-neutral-400 transition-colors duration-200 ease-in-out hover:border-neutral-600 focus:border-neutral-600 dark:border-neutral-600 hover:dark:border-neutral-400 hover:dark:shadow-sm hover:dark:shadow-neutral-600 focus:dark:border-neutral-400"
|
||||
>
|
||||
Star on GitHub
|
||||
<GitHubIcon className="ml-auto size-4 md:size-5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full">
|
||||
<div className="max-h-[33.75rem] max-w-full overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-800">
|
||||
<Image
|
||||
src={screenshot_light}
|
||||
alt="Screenshot of Cup's web interface"
|
||||
className="dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src={screenshot_dark}
|
||||
alt="Screenshot of Cup's web interface"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Section
|
||||
title="Powerful at its core."
|
||||
className="bg-gradient-to-r from-red-500 to-amber-500"
|
||||
>
|
||||
<Card
|
||||
name="100% Safe Code"
|
||||
icon={IconLockCheck}
|
||||
description="Built with safe Rust and Typescript to ensure security and reliability."
|
||||
/>
|
||||
<Card
|
||||
name="Lightning Fast Performance"
|
||||
icon={IconBolt}
|
||||
description="Heavily optimized to squeeze out every last drop of performance. Each release is extensively benchmarked and profiled so that you'll never have to stare at a loading spinner for long."
|
||||
/>
|
||||
<Card
|
||||
name="Lightweight"
|
||||
icon={IconFeather}
|
||||
description="No runtimes or libraries are needed. All you need is the 5.3 MB static binary that works out of the box on any system."
|
||||
/>
|
||||
</Section>
|
||||
<Section
|
||||
title="Efficient, yet flexible."
|
||||
className="bg-gradient-to-r from-blue-500 to-indigo-500"
|
||||
>
|
||||
<Card
|
||||
name="JSON output"
|
||||
description="Connect Cup to your favorite intergrations with JSON output for the CLI and an API for the server. Now go make that cool dashboard you've been dreaming of!"
|
||||
icon={IconBraces}
|
||||
/>
|
||||
<Card
|
||||
name="Both CLI and web interface"
|
||||
description="Whether you prefer the command line, or the web, Cup runs wherever you choose."
|
||||
icon={IconDevices}
|
||||
/>
|
||||
<Card
|
||||
name="Configurable"
|
||||
description="The simple configuration file provides you with all the tools you need to specify a custom Docker socket, manage registry connection options, choose a theme for the web interface and more."
|
||||
icon={IconAdjustments}
|
||||
/>
|
||||
</Section>
|
||||
<div className="relative py-24">
|
||||
<GridPattern />
|
||||
<div className="mx-auto flex w-full max-w-screen-xl flex-col items-center">
|
||||
<p className="mb-8 text-center text-3xl font-bold">
|
||||
Still not convinced? Try it out now!
|
||||
</p>
|
||||
<div>
|
||||
<Step title="Open a terminal and run" number={1}>
|
||||
<div className="group relative mx-auto flex max-w-screen-xl items-center rounded-lg bg-neutral-100 px-3 py-2 font-mono text-neutral-700 dark:bg-neutral-950 dark:text-neutral-300">
|
||||
<p className="overflow-scroll">
|
||||
docker run -v /var/run/docker.sock:/var/run/docker.sock -p
|
||||
8000:8000 -t ghcr.io/sergi0g/cup serve
|
||||
</p>
|
||||
{isBrowser &&
|
||||
navigator.clipboard &&
|
||||
(copySuccess ? (
|
||||
<IconCheck className="absolute right-3 size-7 bg-neutral-100 pl-2 dark:bg-neutral-950" />
|
||||
) : (
|
||||
<button
|
||||
className="duration-50 absolute right-3 bg-neutral-100 pl-2 opacity-0 transition-opacity group-hover:opacity-100 dark:bg-neutral-950"
|
||||
onClick={handleCopy(
|
||||
"docker run --rm -t -v /var/run/docker.sock:/var/run/docker.sock -p 8000:8000 ghcr.io/sergi0g/cup serve",
|
||||
)}
|
||||
>
|
||||
<IconClipboard className="size-5" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Step>
|
||||
<Step number={2} title="Open the dashboard in your browser">
|
||||
<p>
|
||||
Visit{" "}
|
||||
<a href="http://localhost:8000" className="underline">
|
||||
http://localhost:8000
|
||||
</a>{" "}
|
||||
in your browser to try it out!
|
||||
</p>
|
||||
</Step>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user