This commit is contained in:
parent
96278333b7
commit
2cbb64e930
9 changed files with 205 additions and 182 deletions
10
frontend/src/components/media-kit/About.svelte
Normal file
10
frontend/src/components/media-kit/About.svelte
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<script lang="ts">
|
||||
let { about = "" }: { about?: string } = $props();
|
||||
</script>
|
||||
|
||||
<div class="mt-5">
|
||||
<div class="mb-2 text-3xl font-bold text-white sm:text-4xl">About Me</div>
|
||||
<p class="wrap-break-word">
|
||||
{about}
|
||||
</p>
|
||||
</div>
|
||||
23
frontend/src/components/media-kit/Intro.svelte
Normal file
23
frontend/src/components/media-kit/Intro.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
let {
|
||||
name = "RythonDev",
|
||||
tagline = "",
|
||||
}: {
|
||||
name?: string;
|
||||
tagline?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="w-full translate-y-2 rounded-lg border-2 border-cyan-300 sm:w-fit">
|
||||
<div
|
||||
class="-translate-y-2 rounded-xl bg-cyan-300 px-5 py-5 text-3xl text-black sm:-translate-x-8 sm:text-4xl lg:-translate-x-20"
|
||||
>
|
||||
Hi there, I'm <br />
|
||||
<b>{name}</b>
|
||||
</div>
|
||||
{#if tagline}
|
||||
<p class="px-5 pb-2 text-xl text-cyan-300">
|
||||
{tagline}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
46
frontend/src/components/media-kit/PlatformCard.svelte
Normal file
46
frontend/src/components/media-kit/PlatformCard.svelte
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts">
|
||||
import Card from "../Card.svelte";
|
||||
import { formatCount } from "../../lib/format";
|
||||
import { metricLabel, platformImageMap } from "../../lib/platforms";
|
||||
|
||||
let {
|
||||
name,
|
||||
handle,
|
||||
href,
|
||||
followers,
|
||||
}: {
|
||||
name: string;
|
||||
handle: string;
|
||||
href: string;
|
||||
followers?: number | null;
|
||||
} = $props();
|
||||
|
||||
let deco = $derived(platformImageMap[name.toLowerCase()]);
|
||||
</script>
|
||||
|
||||
{#if deco}
|
||||
<Card
|
||||
className="flex flex-col gap-1 rounded-2xl border p-5 transition-colors border-zinc-800 bg-neutral-900"
|
||||
{href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
color={deco.color}
|
||||
>
|
||||
<div class="flex flex-row items-start justify-start gap-3">
|
||||
<img src={deco.image} alt={name} width="40" />
|
||||
<div>
|
||||
<span
|
||||
class="text-sm font-semibold tracking-[0.08em] text-white uppercase"
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-base">{handle}</span>
|
||||
<div class="mt-2 text-2xl font-semibold tracking-tight">
|
||||
{formatCount(followers)}
|
||||
<span class="text-sm">{metricLabel(name)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
29
frontend/src/components/media-kit/Platforms.svelte
Normal file
29
frontend/src/components/media-kit/Platforms.svelte
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import type { Platform, TwitchData } from "../../lib/types";
|
||||
import PlatformCard from "./PlatformCard.svelte";
|
||||
|
||||
let {
|
||||
platforms = [],
|
||||
twitch = null,
|
||||
}: {
|
||||
platforms?: Platform[];
|
||||
twitch?: TwitchData | null;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<PlatformCard
|
||||
name="Twitch"
|
||||
handle={twitch?.display_name ?? ""}
|
||||
href={`https://twitch.tv/${twitch?.login ?? ""}`}
|
||||
followers={twitch?.followers}
|
||||
/>
|
||||
{#each platforms as platform (platform.name)}
|
||||
<PlatformCard
|
||||
name={platform.name}
|
||||
handle={platform.handle}
|
||||
href={platform.url}
|
||||
followers={platform.followers}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
18
frontend/src/components/media-kit/ProfileImage.svelte
Normal file
18
frontend/src/components/media-kit/ProfileImage.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
let {
|
||||
src,
|
||||
alt,
|
||||
class: className = "",
|
||||
}: {
|
||||
src: string;
|
||||
alt: string;
|
||||
class?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<img
|
||||
class="h-auto w-full max-w-75 rounded-xl object-cover shadow-lg shadow-cyan-300 {className}"
|
||||
{src}
|
||||
{alt}
|
||||
width="300"
|
||||
/>
|
||||
29
frontend/src/components/media-kit/Services.svelte
Normal file
29
frontend/src/components/media-kit/Services.svelte
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
let { services = "" }: { services?: string } = $props();
|
||||
|
||||
let items = $derived(
|
||||
services
|
||||
.split("\n")
|
||||
.map((service) => service.trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 6),
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if items.length}
|
||||
<section class="w-full">
|
||||
<div class="mb-3 text-3xl font-bold text-white sm:text-4xl">Services</div>
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 sm:gap-x-8">
|
||||
<ul class="list-disc space-y-2 pl-5 marker:text-cyan-300">
|
||||
{#each items.slice(0, 3) as service}
|
||||
<li>{service}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<ul class="list-disc space-y-2 pl-5 marker:text-cyan-300">
|
||||
{#each items.slice(3, 6) as service}
|
||||
<li>{service}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
12
frontend/src/lib/format.ts
Normal file
12
frontend/src/lib/format.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export function formatCount(value: number | undefined | null): string {
|
||||
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`;
|
||||
}
|
||||
if (value >= 1_000) {
|
||||
const thousands = value / 1_000;
|
||||
return `${thousands >= 100 ? Math.round(thousands) : thousands.toFixed(1).replace(/\.0$/, "")}K`;
|
||||
}
|
||||
return value.toLocaleString();
|
||||
}
|
||||
27
frontend/src/lib/platforms.ts
Normal file
27
frontend/src/lib/platforms.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export type PlatformDeco = {
|
||||
image: string;
|
||||
color?: string | null;
|
||||
};
|
||||
|
||||
export const platformImageMap: Record<string, PlatformDeco> = {
|
||||
twitch: {
|
||||
image: "/images/logos/twitch.svg",
|
||||
color: "#8E60F5",
|
||||
},
|
||||
youtube: {
|
||||
image: "/images/logos/youtube.svg",
|
||||
color: "#FF0000",
|
||||
},
|
||||
instagram: {
|
||||
image: "/images/logos/instagram.svg",
|
||||
color: "#E1306C",
|
||||
},
|
||||
tiktok: {
|
||||
image: "/images/logos/tiktok.svg",
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
};
|
||||
|
||||
export function metricLabel(name: string): string {
|
||||
return name.toLowerCase() === "youtube" ? "subscribers" : "followers";
|
||||
}
|
||||
|
|
@ -2,8 +2,12 @@
|
|||
import { onMount } from "svelte";
|
||||
import { fetchMediaKit } from "../lib/api";
|
||||
import type { MediaKit } from "../lib/types";
|
||||
import Card from "../components/Card.svelte";
|
||||
import Collabs from "../components/Collabs.svelte";
|
||||
import About from "../components/media-kit/About.svelte";
|
||||
import Intro from "../components/media-kit/Intro.svelte";
|
||||
import Platforms from "../components/media-kit/Platforms.svelte";
|
||||
import ProfileImage from "../components/media-kit/ProfileImage.svelte";
|
||||
import Services from "../components/media-kit/Services.svelte";
|
||||
|
||||
let data = $state<MediaKit | null>(null);
|
||||
let error = $state("");
|
||||
|
|
@ -19,51 +23,7 @@
|
|||
let content = $derived(data?.content ?? {});
|
||||
let platforms = $derived(data?.platforms ?? []);
|
||||
let collabs = $derived(data?.collabs ?? []);
|
||||
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 ?? "");
|
||||
|
||||
type platformDecoType = {
|
||||
image: string;
|
||||
color?: string | null;
|
||||
};
|
||||
|
||||
let platformImageMap: Record<string, platformDecoType> = {
|
||||
twitch: {
|
||||
image: "/images/logos/twitch.svg",
|
||||
color: "#8E60F5",
|
||||
},
|
||||
youtube: {
|
||||
image: "/images/logos/youtube.svg",
|
||||
color: "#FF0000",
|
||||
},
|
||||
instagram: {
|
||||
image: "/images/logos/instagram.svg",
|
||||
color: "#E1306C",
|
||||
},
|
||||
tiktok: {
|
||||
image: "/images/logos/tiktok.svg",
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
};
|
||||
function formatCount(value: number | undefined | null): string {
|
||||
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`;
|
||||
}
|
||||
if (value >= 1_000) {
|
||||
const thousands = value / 1_000;
|
||||
return `${thousands >= 100 ? Math.round(thousands) : thousands.toFixed(1).replace(/\.0$/, "")}K`;
|
||||
}
|
||||
return value.toLocaleString();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
|
|
@ -84,11 +44,9 @@
|
|||
>
|
||||
{#if content.profile_image_url}
|
||||
<div class="flex justify-center lg:hidden">
|
||||
<img
|
||||
class="h-auto w-full max-w-75 rounded-xl object-cover shadow-lg shadow-cyan-300"
|
||||
<ProfileImage
|
||||
src={content.profile_image_url}
|
||||
alt={content.brand_name ?? "Profile image"}
|
||||
width="300"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -96,122 +54,20 @@
|
|||
<div class="lg:px-5">
|
||||
<div class="hidden items-start justify-end lg:flex">
|
||||
{#if content.profile_image_url}
|
||||
<img
|
||||
class="h-auto w-full max-w-75 shrink-0 rounded-xl object-cover shadow-lg shadow-cyan-300"
|
||||
<ProfileImage
|
||||
src={content.profile_image_url}
|
||||
alt={content.brand_name ?? "Profile image"}
|
||||
width="300"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<!-- Twitch -->
|
||||
<Card
|
||||
className="flex flex-col gap-1 rounded-2xl border p-5 transition-colors border-zinc-800 bg-neutral-900"
|
||||
href={`https://twitch.tv/${twitch?.login}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
color={platformImageMap["twitch"].color}
|
||||
>
|
||||
<div
|
||||
class="flex flex-row justify-start items-start gap-3"
|
||||
>
|
||||
<img
|
||||
src={platformImageMap["twitch"].image}
|
||||
alt="Twitch"
|
||||
width="40"
|
||||
/>
|
||||
<div>
|
||||
<span
|
||||
class="text-sm font-semibold tracking-[0.08em] uppercase text-white"
|
||||
>
|
||||
Twitch
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-base"
|
||||
>{twitch?.display_name}</span
|
||||
>
|
||||
<div
|
||||
class="mt-2 text-2xl font-semibold tracking-tight"
|
||||
>
|
||||
{formatCount(twitch?.followers)}
|
||||
<span class="text-sm">followers</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{#each platforms as platform (platform.name)}
|
||||
<Card
|
||||
className="flex flex-col gap-1 rounded-2xl border p-5 transition-colors border-zinc-800 bg-neutral-900"
|
||||
href={platform.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
color={platformImageMap[platform.name]?.color}
|
||||
>
|
||||
<div
|
||||
class="flex flex-row justify-start items-start gap-3"
|
||||
>
|
||||
<img
|
||||
src={platformImageMap[platform.name].image}
|
||||
alt="Twitch"
|
||||
width="48"
|
||||
/>
|
||||
<div>
|
||||
<span
|
||||
class="text-sm font-semibold tracking-[0.08em] uppercase text-white"
|
||||
>
|
||||
{platform.name}
|
||||
</span>
|
||||
<br />
|
||||
<span class="text-base"
|
||||
>{platform.handle}</span
|
||||
>
|
||||
<div
|
||||
class="mt-2 text-2xl font-semibold tracking-tight"
|
||||
>
|
||||
{formatCount(platform.followers)}
|
||||
<span class="text-sm">
|
||||
{#if platform.name != "youtube"}
|
||||
followers
|
||||
{:else}
|
||||
subscribers
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{/each}
|
||||
</div>
|
||||
<Platforms {platforms} {twitch} />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2nd column -->
|
||||
<div
|
||||
class="order-1 flex min-w-0 flex-col items-start justify-start gap-5 lg:order-2"
|
||||
>
|
||||
<div
|
||||
class="w-full translate-y-2 rounded-lg border-2 border-cyan-300 sm:w-fit"
|
||||
>
|
||||
<div
|
||||
class="-translate-y-2 rounded-xl bg-cyan-300 px-5 py-5 text-3xl text-black sm:-translate-x-8 sm:text-4xl lg:-translate-x-20"
|
||||
>
|
||||
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="mb-2 text-3xl font-bold text-white sm:text-4xl">
|
||||
About Me
|
||||
</div>
|
||||
<p class="wrap-break-word">
|
||||
{content.about ?? ""}
|
||||
</p>
|
||||
</div>
|
||||
<Intro name="RythonDev" tagline={content.tagline ?? ""} />
|
||||
<About about={content.about ?? ""} />
|
||||
{#if collabs.length}
|
||||
<div>
|
||||
<div class="mb-2 text-3xl font-bold text-white sm:text-4xl">
|
||||
|
|
@ -220,34 +76,7 @@
|
|||
<Collabs items={collabs} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if services.length}
|
||||
<section class="w-full">
|
||||
<div class="mb-3 text-3xl font-bold text-white sm:text-4xl">
|
||||
Services
|
||||
</div>
|
||||
<div
|
||||
class="grid grid-cols-1 gap-2 sm:grid-cols-2 sm: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}
|
||||
<Services services={content.services ?? ""} />
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue