mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-05-04 10:33:47 +01:00
feat(website): home page
This commit is contained in:
parent
ac210ef24e
commit
a2b6296f2e
15 changed files with 302 additions and 43 deletions
Binary file not shown.
|
@ -1,33 +1,33 @@
|
||||||
import js from '@eslint/js';
|
import js from "@eslint/js"
|
||||||
import ts from 'typescript-eslint';
|
import ts from "typescript-eslint"
|
||||||
import svelte from 'eslint-plugin-svelte';
|
import svelte from "eslint-plugin-svelte"
|
||||||
import prettier from 'eslint-config-prettier';
|
import prettier from "eslint-config-prettier"
|
||||||
import globals from 'globals';
|
import globals from "globals"
|
||||||
|
|
||||||
/** @type {import('eslint').Linter.Config[]} */
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
export default [
|
export default [
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
...ts.configs.recommended,
|
...ts.configs.recommended,
|
||||||
...svelte.configs['flat/recommended'],
|
...svelte.configs["flat/recommended"],
|
||||||
prettier,
|
prettier,
|
||||||
...svelte.configs['flat/prettier'],
|
...svelte.configs["flat/prettier"],
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.browser,
|
...globals.browser,
|
||||||
...globals.node
|
...globals.node,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.svelte'],
|
files: ["**/*.svelte"],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: ts.parser
|
parser: ts.parser,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ignores: ['build/', '.svelte-kit/', 'dist/']
|
ignores: ["build/", ".svelte-kit/", "dist/"],
|
||||||
}
|
},
|
||||||
];
|
]
|
||||||
|
|
|
@ -33,5 +33,9 @@
|
||||||
"typescript-eslint": "^8.0.0",
|
"typescript-eslint": "^8.0.0",
|
||||||
"vite": "^5.0.3"
|
"vite": "^5.0.3"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource-variable/nunito-sans": "^5.0.14",
|
||||||
|
"date-fns": "^3.6.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {}
|
autoprefixer: {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
|
@ -1,3 +1,45 @@
|
||||||
@import 'tailwindcss/base';
|
@import "tailwindcss/base";
|
||||||
@import 'tailwindcss/components';
|
@import "tailwindcss/components";
|
||||||
@import 'tailwindcss/utilities';
|
@import "tailwindcss/utilities";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: 10 7 4;
|
||||||
|
--color-card: 28 22 17;
|
||||||
|
--color-card-hover: 40 32 25;
|
||||||
|
|
||||||
|
--color-body: 198 167 140;
|
||||||
|
--color-heading: 227 213 200;
|
||||||
|
--color-light: 255 255 255;
|
||||||
|
|
||||||
|
--color-input-bg: 22 15 8;
|
||||||
|
--color-input-border: 82 60 41;
|
||||||
|
--color-placeholder: 169 147 128;
|
||||||
|
|
||||||
|
--color-primary: 241 157 30;
|
||||||
|
--color-primary-hover: 255 172 42;
|
||||||
|
--color-primary-fg: var(--color-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-padding-top: theme(spacing.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: theme(colors.background);
|
||||||
|
color: theme(colors.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cursor-blink {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
website/src/app.d.ts
vendored
2
website/src/app.d.ts
vendored
|
@ -10,4 +10,4 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {}
|
||||||
|
|
31
website/src/lib/registry-api.ts
Normal file
31
website/src/lib/registry-api.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { PUBLIC_REGISTRY_URL } from "$env/static/public"
|
||||||
|
|
||||||
|
export type PackageResponse = {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
target: TargetInfo
|
||||||
|
description: string
|
||||||
|
published_at: string
|
||||||
|
license: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TargetInfo = {
|
||||||
|
kind: TargetKind
|
||||||
|
lib: boolean
|
||||||
|
bin: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TargetKind = "roblox" | "lune" | "luau"
|
||||||
|
|
||||||
|
export async function fetchRegistry<T>(
|
||||||
|
path: string,
|
||||||
|
fetcher: typeof fetch,
|
||||||
|
options?: RequestInit,
|
||||||
|
): Promise<T> {
|
||||||
|
const response = await fetcher(new URL(path, PUBLIC_REGISTRY_URL), options)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch from registry: ${response.status} ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
}
|
9
website/src/routes/+layout.server.ts
Normal file
9
website/src/routes/+layout.server.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { ISR_BYPASS_TOKEN } from "$env/static/private"
|
||||||
|
import type { Config } from "@sveltejs/adapter-vercel"
|
||||||
|
|
||||||
|
export const config: Config = {
|
||||||
|
isr: {
|
||||||
|
expiration: 30 * 60,
|
||||||
|
bypassToken: ISR_BYPASS_TOKEN,
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import '@fontsource-variable/nunito-sans';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
8
website/src/routes/+page.server.ts
Normal file
8
website/src/routes/+page.server.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { fetchRegistry, type PackageResponse } from "$lib/registry-api"
|
||||||
|
import type { PageServerLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ fetch }) => {
|
||||||
|
const packages = await fetchRegistry<PackageResponse[]>("search", fetch)
|
||||||
|
|
||||||
|
return { packages }
|
||||||
|
}
|
|
@ -1,2 +1,40 @@
|
||||||
<h1>Welcome to SvelteKit</h1>
|
<script lang="ts">
|
||||||
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
import type { PageData } from "./$types"
|
||||||
|
import { formatDistanceToNow } from "date-fns"
|
||||||
|
|
||||||
|
import Hero from "./Hero.svelte"
|
||||||
|
|
||||||
|
export let data: PageData
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hero />
|
||||||
|
|
||||||
|
<section class="mx-auto max-w-screen-xl px-4 pb-32">
|
||||||
|
<h2 class="mb-4 text-2xl font-semibold text-heading">
|
||||||
|
<a id="recently-published" href="#recently-published">Recently Published</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
|
{#each data.packages as pkg}
|
||||||
|
{@const [scope, name] = pkg.name.split("/")}
|
||||||
|
{@const targetName = pkg.target.kind[0].toUpperCase() + pkg.target.kind.slice(1)}
|
||||||
|
|
||||||
|
<article
|
||||||
|
class="hover:bg-card-hover relative overflow-hidden rounded bg-card px-5 py-4 transition"
|
||||||
|
>
|
||||||
|
<h3 class="text-xl font-semibold">
|
||||||
|
<a href={`/packages/${pkg.name}`} class="after:absolute after:inset-0 after:content-['']">
|
||||||
|
<span class="text-heading">{scope}/</span><span class="text-light">{name}</span>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<div class="mb-3 text-sm font-semibold text-primary">v{pkg.version} · {targetName}</div>
|
||||||
|
<p class="mb-3 line-clamp-2 h-[2lh] overflow-hidden text-sm">{pkg.description}</p>
|
||||||
|
<div class="text-sm font-semibold text-heading">
|
||||||
|
<time datetime={pkg.published_at}>
|
||||||
|
{formatDistanceToNow(new Date(pkg.published_at), { addSuffix: true })}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
90
website/src/routes/Hero.svelte
Normal file
90
website/src/routes/Hero.svelte
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
export const prerender = true
|
||||||
|
|
||||||
|
const tools = ["Luau", "Roblox", "Lune"]
|
||||||
|
|
||||||
|
let typewriteText = $state("Luau")
|
||||||
|
let blink = $state(true)
|
||||||
|
let cursorVisible = $state(false)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let current = 0
|
||||||
|
let timeout: number
|
||||||
|
|
||||||
|
function typewrite(text: string) {
|
||||||
|
blink = false
|
||||||
|
|
||||||
|
let progress = 0
|
||||||
|
|
||||||
|
timeout = setInterval(() => {
|
||||||
|
progress++
|
||||||
|
typewriteText = text.slice(0, progress)
|
||||||
|
|
||||||
|
if (progress >= text.length) {
|
||||||
|
blink = true
|
||||||
|
|
||||||
|
clearInterval(timeout)
|
||||||
|
timeout = setTimeout(() => clear(), 3500)
|
||||||
|
}
|
||||||
|
}, 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
blink = false
|
||||||
|
|
||||||
|
let progress = typewriteText.length
|
||||||
|
|
||||||
|
timeout = setInterval(() => {
|
||||||
|
progress--
|
||||||
|
typewriteText = typewriteText.slice(0, progress)
|
||||||
|
|
||||||
|
if (progress <= 0) {
|
||||||
|
clearInterval(timeout)
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
current++
|
||||||
|
if (current >= tools.length) current = 0
|
||||||
|
typewrite(tools[current])
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}, 80)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorVisible = true
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
clear()
|
||||||
|
}, 4500)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="mx-auto max-w-screen-xl px-4 py-32">
|
||||||
|
<h1 class="mb-6 text-5xl font-semibold text-heading">
|
||||||
|
Manage your packages<br />
|
||||||
|
<span class="sr-only"> for Luau</span>
|
||||||
|
<span class="text-primary" aria-hidden="true">
|
||||||
|
for {typewriteText}{#if cursorVisible}
|
||||||
|
<span
|
||||||
|
class="ml-1 inline-block h-9 w-0.5 bg-current duration-100"
|
||||||
|
class:animate-cursor-blink={blink}
|
||||||
|
></span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="mb-8 max-w-md text-lg">
|
||||||
|
A package manager for the Luau programming language, supporting multiple runtimes including
|
||||||
|
Roblox and Lune.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="hover:bg-primary-hover inline-flex h-11 items-center rounded bg-primary px-5 font-semibold text-primary-fg transition"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</section>
|
|
@ -1,16 +1,16 @@
|
||||||
import { mdsvex } from 'mdsvex';
|
import { mdsvex } from "mdsvex"
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
|
||||||
import adapter from '@sveltejs/adapter-vercel';
|
import adapter from "@sveltejs/adapter-vercel"
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
preprocess: [vitePreprocess(), mdsvex()],
|
preprocess: [vitePreprocess(), mdsvex()],
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter({})
|
adapter: adapter({}),
|
||||||
},
|
},
|
||||||
|
|
||||||
extensions: ['.svelte', '.svx']
|
extensions: [".svelte", ".svx"],
|
||||||
};
|
}
|
||||||
|
|
||||||
export default config;
|
export default config
|
||||||
|
|
|
@ -1,12 +1,48 @@
|
||||||
import type { Config } from 'tailwindcss';
|
import type { Config } from "tailwindcss"
|
||||||
|
import defaultTheme from "tailwindcss/defaultTheme"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||||
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {}
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Nunito Sans Variable", ...defaultTheme.fontFamily.sans],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
background: "rgb(var(--color-background) / <alpha-value>)",
|
||||||
|
card: {
|
||||||
|
DEFAULT: "rgb(var(--color-card) / <alpha-value>)",
|
||||||
|
hover: "rgb(var(--color-card-hover) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
|
||||||
|
body: "rgb(var(--color-body) / <alpha-value>)",
|
||||||
|
heading: "rgb(var(--color-heading) / <alpha-value>)",
|
||||||
|
light: "rgb(var(--color-light) / <alpha-value>)",
|
||||||
|
|
||||||
|
input: {
|
||||||
|
bg: "rgb(var(--color-input-bg) / <alpha-value>)",
|
||||||
|
border: "rgb(var(--color-input-border) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
placeholder: "rgb(var(--color-placeholder) / <alpha-value>)",
|
||||||
|
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "rgb(var(--color-primary) / <alpha-value>)",
|
||||||
|
hover: "rgb(var(--color-primary-hover) / <alpha-value>)",
|
||||||
|
fg: "rgb(var(--color-primary-fg) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"cursor-blink": "cursor-blink 1s ease-in-out 500ms infinite",
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
none: "0",
|
||||||
|
sm: `${4 / 16}rem`,
|
||||||
|
DEFAULT: `${8 / 16}rem`,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
plugins: [require('@tailwindcss/typography')]
|
plugins: [require("@tailwindcss/typography")],
|
||||||
} as Config;
|
} as Config
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from "@sveltejs/kit/vite"
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from "vite"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()]
|
plugins: [sveltekit()],
|
||||||
});
|
})
|
||||||
|
|
Loading…
Add table
Reference in a new issue