feat: preview templates, thumbnail features, 2 new org templates

This commit is contained in:
Rotimi Best
2026-05-23 12:24:57 +01:00
parent 4fdd4115b9
commit 78ab13f54b
106 changed files with 8320 additions and 2040 deletions
@@ -0,0 +1,97 @@
<script lang="ts">
import type { CourseItem, OrgLandingPageLabels } from '../types';
import * as Item from '../../../base/item';
import { CourseCard } from '../../course-card';
import { calcCourseDiscount, formatExerciseCountLabel, getCourseTypeLandingMeta } from '../landing-page-utils';
import { cn } from '../../../tools';
interface Props {
course: CourseItem;
disableCourseLinks?: boolean;
labels?: OrgLandingPageLabels;
}
let { course, disableCourseLinks = false, labels }: Props = $props();
const courseTypeMeta = $derived(getCourseTypeLandingMeta(course));
const pricingData = $derived({
cost: course.cost ?? 0,
discount: course.metadata?.discount ?? 0,
showDiscount: !!course.metadata?.showDiscount
});
const displayCost = $derived(calcCourseDiscount(pricingData.discount, pricingData.cost, pricingData.showDiscount));
const currencyFormatter = $derived(
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: course.currency ?? 'USD'
})
);
const href = $derived.by(() => {
if (disableCourseLinks) return undefined;
return course.link || (course.slug ? `/course/${course.slug}` : undefined);
});
</script>
<CourseCard
{href}
bannerImage={course.logo || ''}
bannerAlt={course.title}
title={course.title}
description={course.description}
typeBadge={courseTypeMeta
? {
label: courseTypeMeta.label,
icon: courseTypeMeta.icon,
iconClass: courseTypeMeta.iconClass
}
: undefined}
class={cn('ui:group ui:relative', !disableCourseLinks && 'ui:cursor-pointer ui:hover:shadow-md ui:transition-shadow')}
>
{#snippet tags()}
{#if course.tags && course.tags.length > 0}
<div class="ui:flex ui:flex-wrap ui:gap-1 ui:min-w-0">
{#each course.tags as tag (tag.id)}
<Item.SubDescription
class="ui:border-[var(--landing-border)] ui:inline-flex ui:shrink-0 ui:items-center ui:gap-1.5 ui:rounded-full ui:border ui:px-2"
>
<span
class="ui:bg-[var(--landing-accent)]/60 ui:inline-block ui:h-1.5 ui:w-1.5 ui:shrink-0 ui:rounded-full"
style={tag.color ? `background-color: ${tag.color}` : undefined}
aria-hidden="true"
></span>
<span>{tag.name}</span>
</Item.SubDescription>
{/each}
</div>
{/if}
{/snippet}
{#snippet footer()}
<div class="ui:flex ui:justify-between ui:w-full">
<div class="ui:w-[80%]">
<p class="ui:text-xs ui:pl-2 ui:dark:text-white">
<span>{course.lessonCount || 0} lessons</span>
&
<span>{formatExerciseCountLabel(course.exerciseCount ?? 0)}</span>
</p>
<div class="ui:py-2 ui:text-xs">
{#if course.price != null && String(course.price).length > 0}
<span class="ui:px-2">{course.price}</span>
{:else if !displayCost}
<span class="ui:px-2">{labels?.freeLabel ?? 'Free'}</span>
{:else if pricingData.showDiscount}
<span class="ui:px-2">
{currencyFormatter.format(displayCost)}
<span class="line-through">
{currencyFormatter.format(pricingData.cost)}
</span>
</span>
{:else}
<span class="ui:px-2">{currencyFormatter.format(displayCost)}</span>
{/if}
</div>
</div>
</div>
{/snippet}
</CourseCard>
@@ -2,7 +2,6 @@
import type { OrgLandingPageProps } from '../types';
import SecondaryActionButton from '../secondary-action-button.svelte';
import { Button } from '../../../base/button';
import { BlurFade } from '../../animation/blurfade';
import { BlurIn } from '../../animation/blurin';
import { DotPattern } from '../../animation/dot-pattern';
import EditableLandingSection from '../editable-section.svelte';
@@ -16,7 +15,7 @@
<EditableLandingSection sectionKey="hero">
<section
class="ui:relative ui:bg-foreground ui:text-background ui:overflow-hidden ui:py-24 ui:sm:py-32 ui:px-6 ui:lg:px-8"
class="ui:relative ui:bg-[var(--landing-fg)] ui:text-[var(--landing-bg)] ui:overflow-hidden ui:py-24 ui:sm:py-32 ui:px-6 ui:lg:px-8"
>
{#if hero.image}
<div class="ui:absolute ui:inset-0 ui:opacity-10">
@@ -30,28 +29,24 @@
>
{hero.heading}
</BlurIn>
<BlurFade delay={0.15} once={true}>
<p class="ui:text-lg ui:sm:text-xl ui:text-background/80 ui:mb-10">{hero.subheading}</p>
</BlurFade>
<BlurFade delay={0.3} once={true}>
<div class="ui:flex ui:flex-col ui:sm:flex-row ui:justify-center ui:gap-4">
<Button
href={hero.primaryAction.href}
disabled={hero.primaryAction.disabled ?? false}
size="lg"
class="ui:px-8 ui:font-semibold"
>
{hero.primaryAction.label}
</Button>
{#if hero.secondaryAction}
<SecondaryActionButton
href={hero.secondaryAction.href}
label={hero.secondaryAction.label}
variant="classic"
/>
{/if}
</div>
</BlurFade>
<p class="ui:text-lg ui:sm:text-xl ui:text-[var(--landing-bg)]/80 ui:mb-10">{hero.subheading}</p>
<div class="ui:flex ui:flex-col ui:sm:flex-row ui:justify-center ui:gap-4">
<Button
href={hero.primaryAction.href}
disabled={hero.primaryAction.disabled ?? false}
size="lg"
class="ui:px-8 ui:font-semibold"
>
{hero.primaryAction.label}
</Button>
{#if hero.secondaryAction}
<SecondaryActionButton
href={hero.secondaryAction.href}
label={hero.secondaryAction.label}
variant="classic"
/>
{/if}
</div>
</div>
</section>
</EditableLandingSection>
@@ -17,9 +17,12 @@
</script>
<EditableLandingSection sectionKey="navigation">
<header class="ui:bg-foreground ui:text-background">
<header class="ui:bg-[var(--landing-fg)] ui:text-[var(--landing-bg)]">
<div class="ui:flex ui:items-center ui:justify-between ui:px-6 ui:lg:px-8 ui:h-20 ui:max-w-7xl ui:mx-auto">
<a href="/" class="ui:flex ui:items-center ui:gap-3 ui:text-xl ui:font-bold ui:tracking-wide ui:no-underline">
<a
href="/"
class="ui:flex ui:items-center ui:gap-3 ui:text-xl ui:font-bold ui:tracking-wide ui:no-underline ui:cursor-pointer"
>
{#if logoUrl}
<img src={logoUrl} alt={orgName} class="ui:h-8 ui:w-auto" />
{/if}
@@ -30,7 +33,7 @@
{#each navItems as item}
<a
href={item.href}
class="ui:text-sm ui:font-medium ui:text-background/70 ui:hover:text-background ui:transition-colors"
class="ui:text-sm ui:font-medium ui:text-[var(--landing-bg)]/70 ui:hover:text-[var(--landing-bg)] ui:transition-colors ui:cursor-pointer"
>{item.label}</a
>
{/each}
@@ -39,7 +42,7 @@
<Button
href={authAction.href}
variant="outline"
class="ui:bg-transparent ui:text-background ui:border-background/40 ui:hover:bg-background/10 ui:hover:text-background ui:hover:border-background/60"
class="ui:bg-transparent ui:text-[var(--landing-bg)] ui:border-[var(--landing-bg)]/40 ui:hover:bg-[var(--landing-bg)]/10 ui:hover:text-[var(--landing-bg)] ui:hover:border-[var(--landing-bg)]/60"
>
{authAction.label}
</Button>