updated services
This commit is contained in:
parent
e51f83aa8c
commit
0f5155811d
5 changed files with 743 additions and 636 deletions
|
|
@ -83,3 +83,4 @@ export async function updatePlatforms(
|
|||
const data = await res.json();
|
||||
return data.platforms;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import { onMount } from "svelte";
|
||||
import {
|
||||
clearStoredToken,
|
||||
fetchAdminState,
|
||||
|
|
@ -7,109 +7,123 @@
|
|||
updateContent,
|
||||
updatePlatforms,
|
||||
verifyAdmin,
|
||||
} from '../lib/api'
|
||||
import type { Platform } from '../lib/types'
|
||||
} from "../lib/api";
|
||||
import type { Platform } from "../lib/types";
|
||||
|
||||
let authed = $state(false)
|
||||
let loading = $state(true)
|
||||
let message = $state('')
|
||||
let error = $state('')
|
||||
let username = $state('')
|
||||
let password = $state('')
|
||||
let authed = $state(false);
|
||||
let loading = $state(true);
|
||||
let message = $state("");
|
||||
let error = $state("");
|
||||
let username = $state("");
|
||||
let password = $state("");
|
||||
|
||||
let content = $state<Record<string, string>>({})
|
||||
let platforms = $state<Platform[]>([])
|
||||
let content = $state<Record<string, string>>({});
|
||||
let platforms = $state<Platform[]>([]);
|
||||
|
||||
onMount(async () => {
|
||||
if (getStoredToken()) {
|
||||
await loadState()
|
||||
await loadState();
|
||||
} else {
|
||||
loading = false
|
||||
loading = false;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function titleCase(key: string): string {
|
||||
return key
|
||||
.split('_')
|
||||
.split("_")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
async function loadState() {
|
||||
loading = true
|
||||
error = ''
|
||||
loading = true;
|
||||
error = "";
|
||||
try {
|
||||
const state = await fetchAdminState()
|
||||
content = state.content
|
||||
platforms = state.platforms
|
||||
authed = true
|
||||
const state = await fetchAdminState();
|
||||
content = state.content;
|
||||
platforms = state.platforms;
|
||||
authed = true;
|
||||
} catch (err) {
|
||||
authed = false
|
||||
error = err instanceof Error ? err.message : 'Unable to load admin state'
|
||||
authed = false;
|
||||
error =
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "Unable to load admin state";
|
||||
} finally {
|
||||
loading = false
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogin(event: SubmitEvent) {
|
||||
event.preventDefault()
|
||||
error = ''
|
||||
message = ''
|
||||
event.preventDefault();
|
||||
error = "";
|
||||
message = "";
|
||||
|
||||
if (await verifyAdmin(username, password)) {
|
||||
password = ''
|
||||
await loadState()
|
||||
password = "";
|
||||
await loadState();
|
||||
} else {
|
||||
error = 'Invalid credentials'
|
||||
error = "Invalid credentials";
|
||||
}
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
clearStoredToken()
|
||||
authed = false
|
||||
content = {}
|
||||
platforms = []
|
||||
message = ''
|
||||
error = ''
|
||||
clearStoredToken();
|
||||
authed = false;
|
||||
content = {};
|
||||
platforms = [];
|
||||
message = "";
|
||||
error = "";
|
||||
}
|
||||
|
||||
async function handleSaveContent() {
|
||||
error = ''
|
||||
message = ''
|
||||
error = "";
|
||||
message = "";
|
||||
try {
|
||||
content = await updateContent(content)
|
||||
message = 'Content saved'
|
||||
content = await updateContent(content);
|
||||
message = "Content saved";
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Unable to save content'
|
||||
error =
|
||||
err instanceof Error ? err.message : "Unable to save content";
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSavePlatforms() {
|
||||
error = ''
|
||||
message = ''
|
||||
error = "";
|
||||
message = "";
|
||||
try {
|
||||
const payload = platforms.map((platform) => ({
|
||||
...platform,
|
||||
followers: Number(platform.followers) || 0,
|
||||
}))
|
||||
platforms = await updatePlatforms(payload)
|
||||
message = 'Platforms saved'
|
||||
}));
|
||||
platforms = await updatePlatforms(payload);
|
||||
message = "Platforms saved";
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Unable to save platforms'
|
||||
error =
|
||||
err instanceof Error ? err.message : "Unable to save platforms";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="mx-auto w-full max-w-[920px] px-6 pt-12 pb-[72px]">
|
||||
<main class="mx-auto w-full max-w-230 px-6 pt-12 pb-18">
|
||||
<header class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<p class="mb-3 text-xs font-semibold tracking-[0.14em] uppercase text-gray-500 dark:text-gray-400">CMS</p>
|
||||
<h1 class="text-[clamp(36px,8vw,52px)] leading-[1.05] font-semibold tracking-tight">
|
||||
<p
|
||||
class="mb-3 text-xs font-semibold tracking-[0.14em] uppercase text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
CMS
|
||||
</p>
|
||||
<h1
|
||||
class="text-[clamp(36px,8vw,52px)] leading-[1.05] font-semibold tracking-tight"
|
||||
>
|
||||
RythonDev media kit
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<a class="text-sm text-gray-500 hover:text-neutral-900 dark:text-gray-400 dark:hover:text-neutral-50" href="#/">
|
||||
<a
|
||||
class="text-sm text-gray-500 hover:text-neutral-900 dark:text-gray-400 dark:hover:text-neutral-50"
|
||||
href="#/"
|
||||
>
|
||||
View media kit
|
||||
</a>
|
||||
{#if authed}
|
||||
|
|
@ -126,11 +140,15 @@
|
|||
{#if loading}
|
||||
<div class="py-8 text-gray-500 dark:text-gray-400">Loading…</div>
|
||||
{:else if !authed}
|
||||
<form class="mt-10 max-w-[400px]" onsubmit={handleLogin}>
|
||||
<form class="mt-10 max-w-100" onsubmit={handleLogin}>
|
||||
<h2 class="mb-2 text-xl font-semibold tracking-tight">Sign in</h2>
|
||||
<p class="text-gray-500 dark:text-gray-400">Enter the admin credentials configured for this instance.</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
Enter the admin credentials configured for this instance.
|
||||
</p>
|
||||
|
||||
<label class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<label
|
||||
class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>Username</span>
|
||||
<input
|
||||
class="w-full rounded-[10px] border border-gray-200 bg-white px-3 py-2.5 text-neutral-900 outline-none focus:border-violet-600 dark:border-zinc-800 dark:bg-neutral-950 dark:text-neutral-50 dark:focus:border-violet-400"
|
||||
|
|
@ -140,7 +158,9 @@
|
|||
/>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<label
|
||||
class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>Password</span>
|
||||
<input
|
||||
class="w-full rounded-[10px] border border-gray-200 bg-white px-3 py-2.5 text-neutral-900 outline-none focus:border-violet-600 dark:border-zinc-800 dark:bg-neutral-950 dark:text-neutral-50 dark:focus:border-violet-400"
|
||||
|
|
@ -152,7 +172,9 @@
|
|||
</label>
|
||||
|
||||
{#if error}
|
||||
<div class="mt-6 rounded-[10px] bg-red-600/10 px-4 py-3 text-sm text-red-600 dark:text-red-400">
|
||||
<div
|
||||
class="mt-6 rounded-[10px] bg-red-600/10 px-4 py-3 text-sm text-red-600 dark:text-red-400"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -168,7 +190,9 @@
|
|||
<div class="mt-10 grid grid-cols-1 gap-8 md:grid-cols-2 md:items-start">
|
||||
<section class="border-t border-gray-200 pt-8 dark:border-zinc-800">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<h2 class="text-xl font-semibold tracking-tight">Text content</h2>
|
||||
<h2 class="text-xl font-semibold tracking-tight">
|
||||
Text content
|
||||
</h2>
|
||||
<button
|
||||
class="cursor-pointer rounded-[10px] border border-transparent bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white transition-opacity hover:opacity-85 dark:bg-neutral-50 dark:text-neutral-950"
|
||||
onclick={handleSaveContent}
|
||||
|
|
@ -179,15 +203,16 @@
|
|||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{#each Object.keys(content) as key}
|
||||
<label class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<label
|
||||
class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>{titleCase(key)}</span>
|
||||
{#if key === 'about'}
|
||||
{#if key === "about" || key === "services"}
|
||||
<textarea
|
||||
class="w-full resize-y rounded-[10px] border border-gray-200 bg-white px-3 py-2.5 text-neutral-900 outline-none focus:border-violet-600 dark:border-zinc-800 dark:bg-neutral-950 dark:text-neutral-50 dark:focus:border-violet-400"
|
||||
rows="4"
|
||||
bind:value={content[key]}
|
||||
></textarea>
|
||||
{:else if key === 'profile_image_url'}
|
||||
rows={key === "services" ? 6 : 4}
|
||||
bind:value={content[key]}></textarea>
|
||||
{:else if key === "profile_image_url"}
|
||||
<div class="flex items-start gap-4">
|
||||
<input
|
||||
class="w-full rounded-[10px] border border-gray-200 bg-white px-3 py-2.5 text-neutral-900 outline-none focus:border-violet-600 dark:border-zinc-800 dark:bg-neutral-950 dark:text-neutral-50 dark:focus:border-violet-400"
|
||||
|
|
@ -214,7 +239,9 @@
|
|||
|
||||
<section class="border-t border-gray-200 pt-8 dark:border-zinc-800">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<h2 class="text-xl font-semibold tracking-tight">Follower counts</h2>
|
||||
<h2 class="text-xl font-semibold tracking-tight">
|
||||
Follower counts
|
||||
</h2>
|
||||
<button
|
||||
class="cursor-pointer rounded-[10px] border border-transparent bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white transition-opacity hover:opacity-85 dark:bg-neutral-50 dark:text-neutral-950"
|
||||
onclick={handleSavePlatforms}
|
||||
|
|
@ -223,28 +250,38 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(260px,1fr))] gap-5">
|
||||
<div
|
||||
class="grid grid-cols-[repeat(auto-fit,minmax(260px,1fr))] gap-5"
|
||||
>
|
||||
{#each platforms as platform, index (platform.name)}
|
||||
<div
|
||||
class="rounded-2xl border border-gray-200 bg-neutral-50 p-5 dark:border-zinc-800 dark:bg-neutral-900"
|
||||
>
|
||||
<div class="mb-4 text-sm font-semibold">{titleCase(platform.name)}</div>
|
||||
<div class="mb-4 text-sm font-semibold">
|
||||
{titleCase(platform.name)}
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<label class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<label
|
||||
class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>Handle</span>
|
||||
<input
|
||||
class="w-full rounded-[10px] border border-gray-200 bg-white px-3 py-2.5 text-neutral-900 outline-none focus:border-violet-600 dark:border-zinc-800 dark:bg-neutral-950 dark:text-neutral-50 dark:focus:border-violet-400"
|
||||
bind:value={platform.handle}
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<label
|
||||
class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>URL</span>
|
||||
<input
|
||||
class="w-full rounded-[10px] border border-gray-200 bg-white px-3 py-2.5 text-neutral-900 outline-none focus:border-violet-600 dark:border-zinc-800 dark:bg-neutral-950 dark:text-neutral-50 dark:focus:border-violet-400"
|
||||
bind:value={platform.url}
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<label
|
||||
class="flex flex-col gap-2 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span>Followers</span>
|
||||
<input
|
||||
class="w-full rounded-[10px] border border-gray-200 bg-white px-3 py-2.5 text-neutral-900 outline-none focus:border-violet-600 dark:border-zinc-800 dark:bg-neutral-950 dark:text-neutral-50 dark:focus:border-violet-400"
|
||||
|
|
@ -262,12 +299,16 @@
|
|||
</div>
|
||||
|
||||
{#if message}
|
||||
<div class="mt-6 rounded-[10px] bg-green-600/10 px-4 py-3 text-sm text-green-600 dark:text-green-400">
|
||||
<div
|
||||
class="mt-6 rounded-[10px] bg-green-600/10 px-4 py-3 text-sm text-green-600 dark:text-green-400"
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
{/if}
|
||||
{#if error}
|
||||
<div class="mt-6 rounded-[10px] bg-red-600/10 px-4 py-3 text-sm text-red-600 dark:text-red-400">
|
||||
<div
|
||||
class="mt-6 rounded-[10px] bg-red-600/10 px-4 py-3 text-sm text-red-600 dark:text-red-400"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@
|
|||
|
||||
let content = $derived(data?.content ?? {});
|
||||
let platforms = $derived(data?.platforms ?? []);
|
||||
let services = $derived(
|
||||
(content.services ?? "")
|
||||
.split("\n")
|
||||
.map((service) => service.trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 6),
|
||||
);
|
||||
let twitch = $derived(data?.twitch);
|
||||
// let twitchLogin = $derived(twitch?.login ?? "");
|
||||
|
||||
|
|
@ -176,6 +183,11 @@
|
|||
Hi there, I'm <br />
|
||||
<b>RythonDev</b>
|
||||
</div>
|
||||
{#if content.tagline}
|
||||
<p class="text-xl px-5 text-cyan-300 pb-2">
|
||||
{content.tagline}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<div class="text-white font-bold text-4xl mb-2">About Me</div>
|
||||
|
|
@ -189,6 +201,29 @@
|
|||
</div>
|
||||
<Collabs />
|
||||
</div>
|
||||
{#if services.length}
|
||||
<section class="w-full">
|
||||
<div class="mb-3 text-4xl font-bold text-white">
|
||||
Services
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-x-8">
|
||||
<ul
|
||||
class="list-disc space-y-2 pl-5 marker:text-cyan-300"
|
||||
>
|
||||
{#each services.slice(0, 3) as service}
|
||||
<li>{service}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<ul
|
||||
class="list-disc space-y-2 pl-5 marker:text-cyan-300"
|
||||
>
|
||||
{#each services.slice(3, 6) as service}
|
||||
<li>{service}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,43 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import { fetchMediaKit } from '../lib/api'
|
||||
import type { MediaKit } from '../lib/types'
|
||||
import { onMount } from "svelte";
|
||||
import { fetchMediaKit } from "../lib/api";
|
||||
import type { MediaKit } from "../lib/types";
|
||||
|
||||
let data = $state<MediaKit | null>(null)
|
||||
let error = $state('')
|
||||
let data = $state<MediaKit | null>(null);
|
||||
let error = $state("");
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
data = await fetchMediaKit()
|
||||
data = await fetchMediaKit();
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Something went wrong'
|
||||
error = err instanceof Error ? err.message : "Something went wrong";
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const content = $derived(data?.content ?? {})
|
||||
const platforms = $derived(data?.platforms ?? [])
|
||||
const twitch = $derived(data?.twitch ?? null)
|
||||
const twitchLogin = $derived(twitch?.login ?? '')
|
||||
const content = $derived(data?.content ?? {});
|
||||
const platforms = $derived(data?.platforms ?? []);
|
||||
const twitch = $derived(data?.twitch ?? null);
|
||||
const twitchLogin = $derived(twitch?.login ?? "");
|
||||
|
||||
function formatCount(value: number | undefined | null): string {
|
||||
if (value == null) return '—'
|
||||
if (value == null) return "—";
|
||||
if (value >= 1_000_000) {
|
||||
const millions = value / 1_000_000
|
||||
return `${millions >= 100 ? Math.round(millions) : millions.toFixed(1).replace(/\.0$/, '')}M`
|
||||
const millions = value / 1_000_000;
|
||||
return `${millions >= 100 ? Math.round(millions) : millions.toFixed(1).replace(/\.0$/, "")}M`;
|
||||
}
|
||||
if (value >= 1_000) {
|
||||
const thousands = value / 1_000
|
||||
return `${thousands >= 100 ? Math.round(thousands) : thousands.toFixed(1).replace(/\.0$/, '')}K`
|
||||
const thousands = value / 1_000;
|
||||
return `${thousands >= 100 ? Math.round(thousands) : thousands.toFixed(1).replace(/\.0$/, "")}K`;
|
||||
}
|
||||
return value.toLocaleString()
|
||||
return value.toLocaleString();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<main class="mx-auto w-full max-w-230 px-6 pt-12 pb-18">
|
||||
<div class="mt-6 rounded-[10px] bg-red-600/10 px-4 py-3 text-sm text-red-600 dark:text-red-400">
|
||||
<div
|
||||
class="mt-6 rounded-[10px] bg-red-600/10 px-4 py-3 text-sm text-red-600 dark:text-red-400"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
</main>
|
||||
|
|
@ -45,28 +47,38 @@
|
|||
</main>
|
||||
{:else}
|
||||
<main class="grid grid-cols-2">
|
||||
<div class="flex flex-col items-start gap-6 pt-6 pb-2 sm:flex-row sm:items-center">
|
||||
<div
|
||||
class="flex flex-col items-start gap-6 pt-6 pb-2 sm:flex-row sm:items-center"
|
||||
>
|
||||
{#if content.profile_image_url}
|
||||
<img
|
||||
class="h-24 w-24 shrink-0 rounded-md object-cover"
|
||||
src={content.profile_image_url}
|
||||
alt={content.brand_name ?? 'Profile image'}
|
||||
alt={content.brand_name ?? "Profile image"}
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<h1 class="text-[clamp(36px,8vw,52px)] leading-[1.05] font-semibold tracking-tight">
|
||||
{content.brand_name ?? 'RythonDev'}
|
||||
<h1
|
||||
class="text-[clamp(36px,8vw,52px)] leading-[1.05] font-semibold tracking-tight"
|
||||
>
|
||||
{content.brand_name ?? "RythonDev"}
|
||||
</h1>
|
||||
{#if content.tagline}
|
||||
<p class="mt-4 text-xl text-gray-500 dark:text-gray-400">{content.tagline}</p>
|
||||
<p class="mt-4 text-xl text-gray-500 dark:text-gray-400">
|
||||
{content.tagline}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="mt-14 border-t border-gray-200 pt-8 dark:border-zinc-800">
|
||||
<section
|
||||
class="mt-14 border-t border-gray-200 pt-8 dark:border-zinc-800"
|
||||
>
|
||||
<h2 class="text-xl font-semibold tracking-tight">About</h2>
|
||||
<p class="mt-4 max-w-160 text-lg leading-relaxed text-gray-500 dark:text-gray-400">
|
||||
{content.about ?? ''}
|
||||
<p
|
||||
class="mt-4 max-w-160 text-lg leading-relaxed text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{content.about ?? ""}
|
||||
</p>
|
||||
{#if content.email}
|
||||
<a
|
||||
|
|
@ -76,25 +88,39 @@
|
|||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="mt-14 border-t border-gray-200 pt-8 dark:border-zinc-800">
|
||||
<section
|
||||
class="mt-14 border-t border-gray-200 pt-8 dark:border-zinc-800"
|
||||
>
|
||||
<h2 class="text-xl font-semibold tracking-tight">Twitch</h2>
|
||||
{#if twitch?.configured && twitch?.found}
|
||||
<div class="mt-6 grid grid-cols-[repeat(auto-fit,minmax(160px,1fr))] gap-4">
|
||||
<div
|
||||
class="mt-6 grid grid-cols-[repeat(auto-fit,minmax(160px,1fr))] gap-4"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col gap-2 rounded-2xl border border-gray-200 bg-neutral-50 p-5 dark:border-zinc-800 dark:bg-neutral-900"
|
||||
>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Followers</span>
|
||||
<span class="text-3xl font-semibold tracking-tight">{formatCount(twitch?.followers)}</span>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>Followers</span
|
||||
>
|
||||
<span class="text-3xl font-semibold tracking-tight"
|
||||
>{formatCount(twitch?.followers)}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-gray-500 dark:text-gray-400">{twitch?.message ?? 'Twitch stats are unavailable.'}</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
{twitch?.message ?? "Twitch stats are unavailable."}
|
||||
</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="mt-14 border-t border-gray-200 pt-8 dark:border-zinc-800">
|
||||
<section
|
||||
class="mt-14 border-t border-gray-200 pt-8 dark:border-zinc-800"
|
||||
>
|
||||
<h2 class="text-xl font-semibold tracking-tight">Platforms</h2>
|
||||
<div class="mt-6 grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-4">
|
||||
<div
|
||||
class="mt-6 grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-4"
|
||||
>
|
||||
{#each platforms as platform (platform.name)}
|
||||
<a
|
||||
class="flex flex-col gap-1 rounded-2xl border border-gray-200 bg-neutral-50 p-5 transition-colors hover:border-violet-600 dark:border-zinc-800 dark:bg-neutral-900 dark:hover:border-violet-400"
|
||||
|
|
@ -102,11 +128,15 @@
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span class="text-sm font-semibold tracking-[0.08em] uppercase text-gray-500 dark:text-gray-400">
|
||||
<span
|
||||
class="text-sm font-semibold tracking-[0.08em] uppercase text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{platform.name}
|
||||
</span>
|
||||
<span class="text-base">{platform.handle}</span>
|
||||
<span class="mt-2 text-2xl font-semibold tracking-tight">
|
||||
<span
|
||||
class="mt-2 text-2xl font-semibold tracking-tight"
|
||||
>
|
||||
{formatCount(platform.followers)} followers
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
|||
Loading…
Reference in a new issue