feat: T5DE's website: landing page, download, changelog and FAQ
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
8401f30f78
35 files changed
+2216
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import type { Metadata } from "next";
|
||||
import Markdown from "react-markdown";
|
||||
import { allReleases, formatDate, releasesPage } from "@/lib/releases";
|
||||
|
||||
export const metadata: Metadata = { title: "Changelog" };
|
||||
|
||||
// Release notes are Markdown, written by the client's CI: what's in each release.
|
||||
const markdown = {
|
||||
p: (props: React.ComponentProps<"p">) => <p className="text-muted-foreground" {...props} />,
|
||||
ul: (props: React.ComponentProps<"ul">) => <ul className="list-disc space-y-1 pl-5 text-muted-foreground" {...props} />,
|
||||
ol: (props: React.ComponentProps<"ol">) => <ol className="list-decimal space-y-1 pl-5 text-muted-foreground" {...props} />,
|
||||
code: (props: React.ComponentProps<"code">) => <code className="rounded bg-muted px-1 py-0.5 font-mono text-xs break-all" {...props} />,
|
||||
a: (props: React.ComponentProps<"a">) => <a className="text-foreground underline underline-offset-4" {...props} />,
|
||||
strong: (props: React.ComponentProps<"strong">) => <strong className="font-medium text-foreground" {...props} />,
|
||||
h1: (props: React.ComponentProps<"h3">) => <h3 className="font-medium" {...props} />,
|
||||
h2: (props: React.ComponentProps<"h3">) => <h3 className="font-medium" {...props} />,
|
||||
h3: (props: React.ComponentProps<"h3">) => <h3 className="font-medium" {...props} />,
|
||||
};
|
||||
|
||||
export default async function ChangelogPage() {
|
||||
const releases = await allReleases();
|
||||
return (
|
||||
<div className="max-w-2xl space-y-10">
|
||||
<h1 className="text-3xl font-semibold tracking-tight">Changelog</h1>
|
||||
{releases === null ? (
|
||||
<p className="text-muted-foreground">
|
||||
Couldn't load the releases. They're on{" "}
|
||||
<a href={releasesPage} className="text-foreground underline underline-offset-4">
|
||||
git.t5de.dev
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
) : (
|
||||
releases.map((r) => (
|
||||
<article key={r.tag} id={r.tag} className="space-y-4 border-t pt-8 first:border-t-0 first:pt-0">
|
||||
<header className="flex flex-wrap items-baseline gap-x-3">
|
||||
<h2 className="text-xl font-semibold">
|
||||
<a href={`#${r.tag}`}>{r.version}</a>
|
||||
</h2>
|
||||
<time dateTime={r.date} className="text-sm text-muted-foreground">
|
||||
{formatDate(r.date)}
|
||||
</time>
|
||||
</header>
|
||||
<div className="space-y-3 text-sm">
|
||||
<Markdown components={markdown}>{r.body}</Markdown>
|
||||
</div>
|
||||
</article>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { Checksum, Download } from "@/components/download";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { formatDate, latestRelease, nightlyRelease, releasesPage } from "@/lib/releases";
|
||||
|
||||
export const metadata: Metadata = { title: "Download" };
|
||||
|
||||
export default async function DownloadPage() {
|
||||
const [release, nightly] = await Promise.all([latestRelease(), nightlyRelease()]);
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
<section className="max-w-2xl space-y-5">
|
||||
<h1 className="text-3xl font-semibold tracking-tight">Download T5DE</h1>
|
||||
<p className="text-muted-foreground">
|
||||
For Windows. Install IMVU Classic{release?.imvu && <> {release.imvu}</>} from imvu.com first, then run
|
||||
T5DE-Setup.exe.
|
||||
</p>
|
||||
<Download release={release} />
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Windows Protected Your PC?</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
T5DE's setup isn't code-signed yet, so Windows may warn about it. Click{" "}
|
||||
<strong className="text-foreground">More info</strong>, then{" "}
|
||||
<strong className="text-foreground">Run anyway</strong>.
|
||||
</p>
|
||||
<p>To check the file first, compare its checksum with the one above in PowerShell:</p>
|
||||
<code className="block rounded bg-muted px-2 py-1.5 font-mono text-xs text-foreground">
|
||||
Get-FileHash .\T5DE-Setup.exe
|
||||
</code>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>For Testers</CardDescription>
|
||||
<CardTitle>Nightly Build</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm text-muted-foreground">
|
||||
<p>
|
||||
The latest changes, built every time they land. It may break, and it uses the staging hub
|
||||
(hub.staging.t5de.dev) unless you choose another in Settings.
|
||||
</p>
|
||||
{nightly?.setup ? (
|
||||
<>
|
||||
<p>
|
||||
<a href={nightly.setup.url} className="text-foreground underline underline-offset-4">
|
||||
Download the nightly
|
||||
</a>{" "}
|
||||
· {nightly.version} · {formatDate(nightly.date)}
|
||||
</p>
|
||||
{nightly.sha256 && <Checksum sha256={nightly.sha256} />}
|
||||
</>
|
||||
) : (
|
||||
<a href={releasesPage} className="text-foreground underline underline-offset-4">
|
||||
Releases on git.t5de.dev
|
||||
</a>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Older versions are on{" "}
|
||||
<a href={releasesPage} className="text-foreground underline underline-offset-4">
|
||||
git.t5de.dev
|
||||
</a>
|
||||
. What changed:{" "}
|
||||
<Link href="/changelog" className="text-foreground underline underline-offset-4">
|
||||
changelog
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { hubURL } from "@/lib/links";
|
||||
|
||||
export const metadata: Metadata = { title: "FAQ" };
|
||||
|
||||
const issues = "https://git.t5de.dev/t5de/t5de/issues";
|
||||
|
||||
function Path({ children }: { children: React.ReactNode }) {
|
||||
return <code className="rounded bg-muted px-1 py-0.5 font-mono text-xs break-all text-foreground">{children}</code>;
|
||||
}
|
||||
|
||||
const questions: { q: string; a: React.ReactNode }[] = [
|
||||
{
|
||||
q: "What is T5DE?",
|
||||
a: (
|
||||
<p>
|
||||
An unofficial patch for the IMVU Classic client for Windows. It adds a modern renderer, community products and
|
||||
rooms hosted by T5DE's hub, removes ads and tracking, and fixes small things. It isn't made by or
|
||||
affiliated with IMVU. Use it at your own risk.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "What do I need?",
|
||||
a: (
|
||||
<p>
|
||||
Windows, and IMVU Classic installed from imvu.com. T5DE's setup patches a copy of it, so your IMVU
|
||||
Classic stays as it is and both can be installed side by side.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "Windows says it protected my PC. Is the setup safe?",
|
||||
a: (
|
||||
<p>
|
||||
The setup isn't code-signed yet, so Windows warns about it. Click More info, then Run anyway. You can
|
||||
compare the file's checksum with the one on the{" "}
|
||||
<Link href="/download" className="text-foreground underline underline-offset-4">
|
||||
download page
|
||||
</Link>{" "}
|
||||
first. T5DE's source is public on git.t5de.dev.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "Does T5DE see my IMVU password?",
|
||||
a: (
|
||||
<p>
|
||||
No. You sign in to IMVU as usual, and your IMVU sign-in never leaves your computer. The{" "}
|
||||
<a href={hubURL} className="text-foreground underline underline-offset-4">
|
||||
hub
|
||||
</a>{" "}
|
||||
has its own account; you link avatars to it by putting a code in their tagline.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "How do I update?",
|
||||
a: (
|
||||
<p>
|
||||
T5DE checks for updates when it starts and offers them in the client, keeping your patches and settings. A
|
||||
newer setup run by hand offers to update too.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "How do I change patches, repair or uninstall?",
|
||||
a: (
|
||||
<p>
|
||||
Run the setup again, or open T5DE in Windows' Apps & features (Installed apps), to change patches,
|
||||
repair or uninstall.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "What does T5DE send, and to whom?",
|
||||
a: (
|
||||
<p>
|
||||
Nothing goes to IMVU's analytics or crash reporting. With your consent, T5DE sends anonymous usage
|
||||
statistics and crash reports to T5DE; you can turn both off in the client's Settings, under T5DE.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "What are nightlies and the staging hub?",
|
||||
a: (
|
||||
<p>
|
||||
Nightly builds have the latest changes for testers and use the staging hub (hub.staging.t5de.dev), with its
|
||||
own accounts. Releases use hub.t5de.dev. Either can switch in Settings, under T5DE, T5DE Hub, Server.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
q: "Something went wrong. Where are the logs?",
|
||||
a: (
|
||||
<p>
|
||||
The client's log is <Path>%APPDATA%\T5DE\IMVULog.log</Path>, and T5DE's proxy's is{" "}
|
||||
<Path>%TEMP%\t5de-proxy.log</Path>. Please report bugs on{" "}
|
||||
<a href={issues} className="text-foreground underline underline-offset-4">
|
||||
git.t5de.dev
|
||||
</a>
|
||||
, with what happened and the logs if you can (they may contain your avatar name).
|
||||
</p>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export default function FAQPage() {
|
||||
return (
|
||||
<div className="max-w-2xl space-y-8">
|
||||
<h1 className="text-3xl font-semibold tracking-tight">FAQ</h1>
|
||||
<div className="divide-y border-y">
|
||||
{questions.map(({ q, a }) => (
|
||||
<details key={q} className="group py-4">
|
||||
<summary className="cursor-pointer list-none font-medium marker:hidden">
|
||||
<span className="flex items-center justify-between gap-4">
|
||||
{q}
|
||||
<span className="text-muted-foreground transition-transform group-open:rotate-45">+</span>
|
||||
</span>
|
||||
</summary>
|
||||
<div className="pt-3 text-sm text-muted-foreground">{a}</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
|
||||
@custom-variant dark (@media (prefers-color-scheme: dark));
|
||||
|
||||
@theme inline {
|
||||
--font-heading: var(--font-sans);
|
||||
--font-sans: var(--font-sans);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-background: var(--background);
|
||||
--radius-sm: calc(var(--radius) * 0.6);
|
||||
--radius-md: calc(var(--radius) * 0.8);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) * 1.4);
|
||||
--radius-2xl: calc(var(--radius) * 1.8);
|
||||
--radius-3xl: calc(var(--radius) * 2.2);
|
||||
--radius-4xl: calc(var(--radius) * 2.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
}
|
||||
}
|
||||
/* Product descriptions: creators' HTML (cleaned by the API), laid out plainly. */
|
||||
@layer components {
|
||||
.product-description {
|
||||
@apply space-y-3 text-sm leading-relaxed break-words;
|
||||
}
|
||||
.product-description :where(h1, h2, h3, h4, h5, h6) {
|
||||
@apply font-semibold;
|
||||
}
|
||||
.product-description :where(h1) {
|
||||
@apply text-xl;
|
||||
}
|
||||
.product-description :where(h2) {
|
||||
@apply text-lg;
|
||||
}
|
||||
.product-description :where(a) {
|
||||
@apply underline underline-offset-4;
|
||||
}
|
||||
.product-description :where(ul) {
|
||||
@apply list-disc pl-5;
|
||||
}
|
||||
.product-description :where(ol) {
|
||||
@apply list-decimal pl-5;
|
||||
}
|
||||
.product-description :where(img) {
|
||||
@apply inline-block h-auto max-w-full;
|
||||
}
|
||||
.product-description :where(td, th) {
|
||||
@apply border px-2 py-1;
|
||||
}
|
||||
.product-description :where(hr) {
|
||||
@apply my-4;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,54 @@
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { NavLink } from "@/components/nav-link";
|
||||
import { hubURL } from "@/lib/links";
|
||||
import { cn } from "@/lib/utils";
|
||||
import logo from "@/components/t5de.png";
|
||||
import "./globals.css";
|
||||
|
||||
const sans = Geist({ subsets: ["latin"], variable: "--font-sans" });
|
||||
const mono = Geist_Mono({ subsets: ["latin"], variable: "--font-mono" });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://t5de.dev"),
|
||||
title: { template: "%s · T5DE", default: "T5DE" },
|
||||
description: "An unofficial patch for the IMVU Classic client: a modern renderer, community products, and no tracking.",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={cn("font-sans antialiased", sans.variable, mono.variable)}>
|
||||
<body className="flex min-h-dvh flex-col">
|
||||
<header className="border-b">
|
||||
<div className="mx-auto flex max-w-5xl flex-wrap items-center gap-x-6 gap-y-2 px-4 py-3 sm:px-6">
|
||||
<Link href="/" aria-label="T5DE home" className="shrink-0">
|
||||
<Image src={logo} alt="T5DE" priority className="size-9" />
|
||||
</Link>
|
||||
<nav className="flex flex-wrap items-center gap-1 text-sm">
|
||||
<NavLink href="/download">Download</NavLink>
|
||||
<NavLink href="/changelog">Changelog</NavLink>
|
||||
<NavLink href="/faq">FAQ</NavLink>
|
||||
</nav>
|
||||
<a
|
||||
href={hubURL}
|
||||
className="ml-auto rounded-md px-2.5 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
Hub
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<main className="mx-auto w-full max-w-5xl flex-1 px-4 py-10 sm:px-6 sm:py-14">{children}</main>
|
||||
<footer className="border-t">
|
||||
<div className="mx-auto flex max-w-5xl flex-wrap gap-x-6 gap-y-2 px-4 py-6 text-sm text-muted-foreground sm:px-6">
|
||||
<span>T5DE is unofficial: not made by or affiliated with IMVU.</span>
|
||||
<a href="https://git.t5de.dev/t5de" className="hover:text-foreground sm:ml-auto">
|
||||
Source
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import Link from "next/link";
|
||||
import { BoxIcon, DoorOpenIcon, GaugeIcon, ShieldIcon, SparklesIcon, UsersIcon } from "lucide-react";
|
||||
import { Download } from "@/components/download";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { hubURL } from "@/lib/links";
|
||||
import { latestRelease } from "@/lib/releases";
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: GaugeIcon,
|
||||
title: "A Modern Renderer",
|
||||
text: "Smooth frame rates up to 360 FPS or uncapped, with animations and the camera drawn between frames.",
|
||||
},
|
||||
{
|
||||
icon: BoxIcon,
|
||||
title: "Community Products",
|
||||
text: "Clothing and furniture made by the community, from catalogs on the hub, in the shop and your inventory.",
|
||||
},
|
||||
{
|
||||
icon: DoorOpenIcon,
|
||||
title: "T5DE Rooms",
|
||||
text: "Rooms hosted by T5DE's hub: public or private, with moderators, invites, favorites and ratings.",
|
||||
},
|
||||
{
|
||||
icon: UsersIcon,
|
||||
title: "One Account",
|
||||
text: "Link all your avatars to one hub account, see who's online, and show the T5 badge if you like.",
|
||||
},
|
||||
{
|
||||
icon: ShieldIcon,
|
||||
title: "No Tracking",
|
||||
text: "IMVU's analytics, crash reports, log uploads and device fingerprinting are blocked.",
|
||||
},
|
||||
{
|
||||
icon: SparklesIcon,
|
||||
title: "Small Fixes",
|
||||
text: "No ads, Creator Mode, more room filters, product IDs in the shop, and faster downloads.",
|
||||
},
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{ title: "Install IMVU Classic", text: "From imvu.com, as usual. T5DE leaves it untouched." },
|
||||
{ title: "Run T5DE's Setup", text: "It finds IMVU Classic and installs a patched copy. Pick the patches you want." },
|
||||
{ title: "Sign In", text: "With your IMVU account. T5DE keeps itself up to date." },
|
||||
];
|
||||
|
||||
export default async function Home() {
|
||||
const release = await latestRelease();
|
||||
return (
|
||||
<div className="space-y-20">
|
||||
<section className="max-w-2xl space-y-5 pt-4">
|
||||
<p className="text-sm font-medium text-muted-foreground">T5DE</p>
|
||||
<h1 className="text-4xl font-semibold tracking-tight text-balance sm:text-5xl">
|
||||
IMVU Classic, kept alive and made better.
|
||||
</h1>
|
||||
<p className="text-lg text-pretty text-muted-foreground">
|
||||
An unofficial patch for the IMVU Classic client: a modern renderer, community products, hosted rooms, and no
|
||||
tracking.
|
||||
</p>
|
||||
<div className="pt-2">
|
||||
<Download release={release}>
|
||||
<a href={hubURL} className={buttonVariants({ size: "lg", variant: "outline", className: "px-4" })}>
|
||||
Open the Hub
|
||||
</a>
|
||||
</Download>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map(({ icon: Icon, title, text }) => (
|
||||
<Card key={title}>
|
||||
<CardHeader>
|
||||
<Icon className="size-5 text-muted-foreground" />
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground">{text}</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="space-y-6">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Getting Started</h2>
|
||||
<ol className="grid gap-6 sm:grid-cols-3">
|
||||
{steps.map((step, i) => (
|
||||
<li key={step.title} className="space-y-1.5">
|
||||
<span className="flex size-7 items-center justify-center rounded-full bg-muted text-sm font-medium tabular-nums">
|
||||
{i + 1}
|
||||
</span>
|
||||
<p className="font-medium">{step.title}</p>
|
||||
<p className="text-sm text-muted-foreground">{step.text}</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Windows only.{" "}
|
||||
<Link href="/faq" className="text-foreground underline underline-offset-4">
|
||||
Questions?
|
||||
</Link>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user