Skip to content
Merged
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
2 changes: 1 addition & 1 deletion landing/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function App() {
<>
<Nav repo={repo} theme={theme} onToggleTheme={toggle} />
<main>
<Hero repo={repo} />
<Hero />
<Platforms />
<Features />
<SelfHost />
Expand Down
73 changes: 34 additions & 39 deletions landing/src/components/Download.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
import { ArrowUpRight, Globe, Smartphone } from "lucide-react";
import type { ComponentType, ReactNode, SVGProps } from "react";
import type { RepoInfo } from "../lib/github";
import { spotlight } from "../lib/hooks";
import { MOBILE_README_URL, RELEASES_URL } from "../lib/site";
import { AndroidIcon, AppleIcon, LinuxIcon, WindowsIcon } from "./icons";
import { ButtonLink, SectionHeading, delay } from "./ui";
import { SectionHeading } from "./ui";

type Icon = ComponentType<SVGProps<SVGSVGElement>>;

function PlatformRow({ icon: I, name, formats, href }: { icon: Icon; name: string; formats: string; href: string }) {
const ROW = "flex items-center gap-3 rounded-lg border px-3 py-2.5";

/** A platform and what you get for it. With `href` it's a bordered link to
* the download; without, a borderless row in the same grid, so it doesn't
* read as one of the links. */
function PlatformRow({ icon: I, name, formats, href }: { icon: Icon; name: string; formats: string; href?: string }) {
const body = (
<>
<I className="size-4 text-muted-foreground" />
<span className="text-sm font-medium">{name}</span>
<span className="ml-auto font-mono text-[11px] text-muted-foreground">{formats}</span>
</>
);
if (!href) return <div className={`${ROW} border-transparent`}>{body}</div>;
return (
<a
href={href}
target="_blank"
rel="noreferrer"
className="press group flex items-center gap-3 rounded-lg border border-border bg-background/60 px-3 py-2.5 hover:border-ring/60 hover:bg-accent/50"
className={`press group ${ROW} border-border bg-background/60 hover:border-ring/60 hover:bg-accent/50`}
>
<I className="size-4 text-muted-foreground" />
<span className="text-sm font-medium">{name}</span>
<span className="ml-auto font-mono text-[11px] text-muted-foreground">{formats}</span>
{body}
<ArrowUpRight className="size-3.5 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
</a>
);
}

function Card({ title, badge, children, stagger = 0 }: { title: string; badge?: ReactNode; children: ReactNode; stagger?: number }) {
function Card({ title, meta, children, note }: { title: string; meta?: ReactNode; children: ReactNode; note: string }) {
return (
<div onPointerMove={spotlight} style={delay(stagger)} className="card-surface spotlight reveal flex flex-col rounded-2xl p-6">
<div className="card-surface flex flex-col rounded-2xl p-6">
<div className="flex items-center justify-between">
<h3 className="font-semibold tracking-tight">{title}</h3>
{badge}
<h3 className="font-medium">{title}</h3>
{meta}
</div>
<div className="mt-5 flex flex-1 flex-col gap-2">{children}</div>
<div className="mt-5 flex flex-col gap-2">{children}</div>
<p className="mt-auto pt-5 text-xs text-muted-foreground">{note}</p>
</div>
);
}
Expand All @@ -41,47 +52,31 @@ export default function Download({ repo }: { repo: RepoInfo }) {

return (
<section id="download" className="relative py-28">
<div className="mx-auto max-w-6xl px-5">
<SectionHeading eyebrow="Download">Get the apps.</SectionHeading>
<div className="reveal mx-auto max-w-6xl px-5">
<SectionHeading eyebrow="Download">Get the apps</SectionHeading>

<div className="mt-16 grid gap-4 lg:grid-cols-3">
<Card
title="Desktop"
badge={
repo.release && (
<span className="rounded-full border border-border px-2 py-0.5 font-mono text-[11px] text-muted-foreground">
{repo.release.tag}
</span>
)
}
meta={repo.release && <span className="font-mono text-xs text-muted-foreground">{repo.release.tag}</span>}
note="Unsigned builds. Discord Rich Presence and in-app updates on Windows and Linux."
>
<PlatformRow icon={WindowsIcon} name="Windows" formats="installer · portable" href={href} />
<PlatformRow icon={AppleIcon} name="macOS" formats="universal .dmg" href={href} />
<PlatformRow icon={LinuxIcon} name="Linux" formats=".AppImage · .deb" href={href} />
<p className="mt-auto pt-3 text-xs text-muted-foreground">
Unsigned builds. Discord Rich Presence and in-app updates on Windows and Linux.
</p>
</Card>

<Card title="Mobile" stagger={80}>
<Card
title="Mobile"
note="Not in the stores yet. The Expo app builds from source with your own Apple or Google account."
>
<PlatformRow icon={AppleIcon} name="iOS" formats="build with EAS" href={MOBILE_README_URL} />
<PlatformRow icon={AndroidIcon} name="Android" formats="build with EAS" href={MOBILE_README_URL} />
<p className="mt-auto pt-3 text-xs text-muted-foreground">
Not in the stores yet. The Expo app builds from source with your own Apple or Google account.
</p>
</Card>

<Card title="Web" stagger={160}>
<div className="flex flex-1 flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border py-6 text-center">
<span className="grid size-10 place-items-center rounded-full bg-brand-soft text-brand">
<Globe className="size-5" />
</span>
<span className="text-sm font-medium">Already running</span>
<span className="font-mono text-xs text-muted-foreground">ships with the server</span>
</div>
<ButtonLink href="#self-host" variant="outline" size="sm" className="mt-2">
<Smartphone /> Works in mobile browsers too
</ButtonLink>
<Card title="Web" note="Nothing to install. Open your server's address on a computer or a phone.">
<PlatformRow icon={Globe} name="Any browser" formats="ships with the server" />
<PlatformRow icon={Smartphone} name="Phone browsers" formats="same address" />
</Card>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions landing/src/components/Faq.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ export default function Faq() {
const [open, setOpen] = useState<number | null>(0);
return (
<section id="faq" className="py-28">
<div className="mx-auto grid max-w-6xl gap-12 px-5 lg:grid-cols-[1fr_1.6fr]">
<div className="reveal mx-auto grid max-w-6xl gap-12 px-5 lg:grid-cols-[1fr_1.6fr]">
<SectionHeading eyebrow="FAQ" align="left">
Questions, answered.
Before you set it up
</SectionHeading>
<div className="reveal border-t border-border">
<div className="border-t border-border">
{QUESTIONS.map((item, i) => (
<Item key={item.q} {...item} open={open === i} onToggle={() => setOpen(open === i ? null : i)} />
))}
Expand Down
31 changes: 13 additions & 18 deletions landing/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { ArrowRight, Star } from "lucide-react";
import type { RepoInfo } from "../lib/github";
import { INSTALL_STEPS, ISSUES_URL, README_URL, RELEASES_URL, REPO_URL } from "../lib/site";
import { ISSUES_URL, README_URL, RELEASES_URL, REPO_URL } from "../lib/site";
import { GitHubIcon } from "./icons";
import Logo, { LogoMark } from "./Logo";
import { ButtonLink, CommandPill } from "./ui";
import { ButtonLink } from "./ui";

export function FinalCta({ repo }: { repo: RepoInfo }) {
return (
<section className="relative overflow-hidden px-5 pb-28">
<div className="card-surface reveal relative mx-auto max-w-6xl overflow-hidden rounded-3xl px-6 py-20 text-center">
<div className="bg-grid pointer-events-none absolute inset-0" />
<div className="glow pointer-events-none absolute left-1/2 top-full h-[420px] w-[900px] -translate-x-1/2 -translate-y-1/2" />
<div className="relative flex flex-col items-center">
<LogoMark className="size-14 shadow-lg" />
<h2 className="text-gradient mt-8 max-w-2xl text-balance text-4xl font-semibold tracking-[-0.035em] sm:text-5xl">
Own your music again.
<section className="px-5 pb-28">
<div className="card-surface reveal mx-auto max-w-6xl rounded-3xl px-6 py-20 text-center">
<div className="flex flex-col items-center">
<LogoMark className="size-14" />
<h2 className="mt-8 max-w-2xl text-balance text-4xl font-medium tracking-[-0.02em] sm:text-5xl">
Own your music again
</h2>
<div className="mt-10 flex flex-col items-center gap-3 sm:flex-row">
{/* Stacked on phones at one shared width, as in the hero. */}
<div className="mt-10 flex w-full max-w-xs flex-col gap-3 sm:w-auto sm:max-w-none sm:flex-row">
<ButtonLink href="#self-host" size="lg">
Get started <ArrowRight />
</ButtonLink>
Expand All @@ -30,7 +29,6 @@ export function FinalCta({ repo }: { repo: RepoInfo }) {
)}
</ButtonLink>
</div>
<CommandPill command={INSTALL_STEPS[2].cmd} className="mt-4" />
</div>
</div>
</section>
Expand Down Expand Up @@ -68,10 +66,7 @@ export default function Footer() {
<div className="mx-auto grid max-w-6xl gap-10 px-5 py-14 sm:grid-cols-[1.5fr_repeat(3,1fr)]">
<div className="flex flex-col gap-4">
<Logo />
<span className="flex items-center gap-2 text-xs text-muted-foreground">
<span className="size-1.5 rounded-full bg-success" />
Self-hosted. No accounts with us, no ads.
</span>
<span className="text-xs text-muted-foreground">Self-hosted, no accounts with us, no ads</span>
</div>
{COLUMNS.map((c) => (
<div key={c.title} className="flex flex-col gap-3">
Expand All @@ -90,9 +85,9 @@ export default function Footer() {
))}
</div>
<div className="border-t border-border">
<div className="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-2 px-5 py-6 font-mono text-[11px] text-muted-foreground">
<div className="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-2 px-5 py-6 text-xs text-muted-foreground">
<span>© {new Date().getFullYear()} Lumen</span>
<span>Made for people who still keep their music in folders.</span>
<span>Made for people who still keep their music in folders</span>
</div>
</div>
</footer>
Expand Down
52 changes: 9 additions & 43 deletions landing/src/components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,27 @@
import { ArrowRight, Download } from "lucide-react";
import type { RepoInfo } from "../lib/github";
import { INSTALL_STEPS, RELEASES_URL } from "../lib/site";
import Showcase from "./devices/Showcase";
import { ButtonLink, CommandPill, delay } from "./ui";

export default function Hero({ repo }: { repo: RepoInfo }) {
const release = repo.release;
import { ButtonLink, delay } from "./ui";

export default function Hero() {
return (
<section id="top" className="relative overflow-hidden pb-24 pt-36 sm:pt-44">
<div className="bg-grid pointer-events-none absolute inset-0 -z-10" />
<div className="glow pointer-events-none absolute left-1/2 top-[520px] -z-10 h-[640px] w-[1100px] -translate-x-1/2" />

<section id="top" className="relative overflow-hidden pb-24 pt-28 sm:pt-40">
<div className="mx-auto flex max-w-6xl flex-col items-center px-5 text-center">
<a
href={release?.url ?? RELEASES_URL}
target="_blank"
rel="noreferrer"
className="enter press group inline-flex items-center gap-2 rounded-full border border-border bg-background/60 py-1 pl-1 pr-3 text-[13px] backdrop-blur hover:border-ring/60"
>
<span className="rounded-full bg-brand-soft px-2 py-0.5 text-xs font-medium text-brand">
New
</span>
<span className="text-muted-foreground">
{release ? (
<>
Lumen <span className="font-mono text-foreground">{release.tag.replace(/^v/, "")}</span> is out
</>
) : (
"Free, open source, self-hosted"
)}
</span>
<ArrowRight className="size-3.5 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
</a>

<h1
className="enter text-gradient mt-7 max-w-4xl text-balance text-5xl font-semibold leading-[1.02] tracking-[-0.045em] sm:text-7xl"
style={delay(80)}
>
Your music library, <span className="text-brand-gradient">on your own server.</span>
<h1 className="enter max-w-4xl text-balance text-5xl font-medium leading-[1.04] tracking-[-0.03em] sm:text-7xl">
Your music library, on your own server
</h1>

<div
className="enter mt-10 flex flex-col items-center gap-3 sm:flex-row"
style={delay(160)}
>
{/* Stacked on phones at one shared width; side by side from sm up. */}
<div className="enter mt-10 flex w-full max-w-xs flex-col gap-3 sm:w-auto sm:max-w-none sm:flex-row" style={delay(80)}>
<ButtonLink href="#self-host" size="lg">
Self-host in 5 minutes <ArrowRight />
Set up your server <ArrowRight />
</ButtonLink>
<ButtonLink href="#download" size="lg" variant="outline">
<Download /> Get the apps
</ButtonLink>
</div>
<CommandPill command={INSTALL_STEPS[2].cmd} className="enter mt-4" style={delay(220)} />
</div>

<div className="enter-plain mx-auto mt-16 max-w-6xl px-5" style={delay(320)}>
<div className="enter mx-auto mt-16 max-w-6xl px-5" style={delay(160)}>
<Showcase />
</div>
</section>
Expand Down
2 changes: 1 addition & 1 deletion landing/src/components/Logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function Logo({ className }: { className?: string }) {
return (
<span className={clsx("inline-flex items-center gap-2", className)}>
<LogoMark />
<span className="text-base font-semibold tracking-tight">Lumen</span>
<span className="text-base font-semibold">Lumen</span>
</span>
);
}
6 changes: 3 additions & 3 deletions landing/src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ export default function Nav({
href={REPO_URL}
target="_blank"
rel="noreferrer"
className="press hidden h-8 items-center gap-2 rounded-lg border border-border px-2.5 text-[13px] hover:bg-accent sm:inline-flex"
className="press hidden h-8 items-center gap-2 rounded-lg px-2.5 text-[13px] text-muted-foreground hover:bg-accent hover:text-foreground sm:inline-flex"
>
<GitHubIcon className="size-4" />
<span className="font-medium">GitHub</span>
GitHub
{repo.stars !== null && (
<span className="flex items-center gap-1 border-l border-border pl-2 font-mono text-xs text-muted-foreground">
<span className="flex items-center gap-1 font-mono text-xs">
<Star className="size-3" />
{repo.stars}
</span>
Expand Down
17 changes: 7 additions & 10 deletions landing/src/components/Platforms.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Globe } from "lucide-react";
import { AndroidIcon, AppleIcon, LinuxIcon, WindowsIcon } from "./icons";
import { delay } from "./ui";

const PLATFORMS = [
{ icon: Globe, label: "Web" },
Expand All @@ -16,9 +15,9 @@ const STACK = ["Go", "Postgres", "React", "Electron", "Expo", "Docker"];
export default function Platforms() {
return (
<section className="border-y border-border bg-sidebar/50">
<div className="mx-auto grid max-w-6xl items-center gap-8 px-5 py-10 md:grid-cols-[1fr_auto_1fr]">
<div className="reveal flex flex-col gap-3">
<span className="font-mono text-[11px] uppercase tracking-[0.14em] text-muted-foreground">One server, every screen</span>
<div className="reveal mx-auto grid max-w-6xl items-center gap-8 px-5 py-10 md:grid-cols-[1fr_auto_1fr]">
<div className="flex flex-col gap-3">
<span className="text-[13px] text-muted-foreground">One server, every screen</span>
<div className="flex flex-wrap gap-x-6 gap-y-3">
{PLATFORMS.map(({ icon: I, label }) => (
<span key={label} className="flex items-center gap-2 text-sm text-foreground/80">
Expand All @@ -29,13 +28,11 @@ export default function Platforms() {
</div>
</div>
<div className="hidden h-10 w-px bg-border md:block" />
<div className="reveal flex flex-col gap-3 md:items-end" style={delay(100)}>
<span className="font-mono text-[11px] uppercase tracking-[0.14em] text-muted-foreground">Built with</span>
<div className="flex flex-wrap gap-2">
<div className="flex flex-col gap-3 md:items-end">
<span className="text-[13px] text-muted-foreground">Built with</span>
<div className="flex flex-wrap gap-x-5 gap-y-3 text-sm text-foreground/80">
{STACK.map((s) => (
<span key={s} className="rounded-md border border-border bg-background px-2 py-1 font-mono text-xs text-muted-foreground">
{s}
</span>
<span key={s}>{s}</span>
))}
</div>
</div>
Expand Down
Loading
Loading