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
@@ -6,12 +6,9 @@
import OrgLandingPageFooter from './landing-page-footer.svelte';
import ClassicNav from './classic/nav.svelte';
import ClassicHero from './classic/hero.svelte';
import ClassicCourseCard from './classic/course-card.svelte';
import { Button } from '../../base/button';
import * as Item from '../../base/item';
import { BlurFade } from '../animation/blurfade';
import { CourseCard } from '../course-card';
import { calcCourseDiscount, formatExerciseCountLabel, getCourseTypeLandingMeta } from './landing-page-utils';
import { cn } from '../../tools';
import { themeStyle } from './theme-style';
let {
orgName,
@@ -25,11 +22,15 @@
embed,
callout,
links,
footer
footer,
labels
}: OrgLandingPageProps = $props();
</script>
<div class="ui:min-h-screen ui:bg-muted/10 ui:text-foreground ui:font-sans">
<div
class="ui:min-h-screen ui:bg-[var(--landing-bg-section)] ui:text-[var(--landing-fg)] ui:font-sans"
style={themeStyle('classic')}
>
<ClassicNav {orgName} {logoUrl} {navItems} {authAction} />
<main>
@@ -37,93 +38,15 @@
<section class="ui:py-10 ui:px-6 ui:lg:px-8 ui:max-w-7xl ui:mx-auto">
<div class="ui:text-center ui:mb-8">
<BlurFade delay={0} once={true}>
<h2 class="ui:text-3xl ui:font-bold ui:text-foreground">Featured Programs</h2>
</BlurFade>
<BlurFade delay={0.15} once={true}>
<div class="ui:mt-6 ui:h-px ui:w-24 ui:bg-border ui:mx-auto"></div>
</BlurFade>
<h2 class="ui:text-3xl ui:font-bold ui:text-[var(--landing-fg)]">
{labels?.catalogHeading ?? 'Featured Programs'}
</h2>
<div class="ui:mt-6 ui:h-px ui:w-24 ui:bg-[var(--landing-border)] ui:mx-auto"></div>
</div>
<div class="ui:grid ui:grid-cols-1 ui:sm:grid-cols-2 ui:lg:grid-cols-3 ui:gap-8 ui:justify-items-center">
{#each courses as course, index (course.id)}
{@const courseTypeMeta = getCourseTypeLandingMeta(course)}
{@const pricingData = {
cost: course.cost ?? 0,
discount: course.metadata?.discount ?? 0,
showDiscount: !!course.metadata?.showDiscount
}}
{@const displayCost = calcCourseDiscount(pricingData.discount, pricingData.cost, pricingData.showDiscount)}
{@const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: course.currency ?? 'USD'
})}
<BlurFade delay={0.1 * index} once={true} class="ui:w-full ui:flex ui:justify-center">
<CourseCard
href={disableCourseLinks
? undefined
: course.link || (course.slug ? `/course/${course.slug}` : undefined)}
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: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-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-primary/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">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>
</BlurFade>
<ClassicCourseCard {course} {disableCourseLinks} {labels} />
{/each}
</div>
@@ -136,18 +59,18 @@
class="ui:px-8"
disabled={disableCourseLinks}
>
View more courses
{labels?.browseCoursesLabel ?? 'View more courses'}
</Button>
</div>
{/if}
</section>
</main>
<OrgLandingPageLinks {links} variant="classic" />
<OrgLandingPageLinks {links} {labels} variant="classic" />
<OrgLandingPageEmbed {embed} variant="classic" />
<OrgLandingPageEmbed {embed} {labels} variant="classic" />
<OrgLandingPageCallout {callout} variant="classic" />
<OrgLandingPageCallout {callout} {labels} variant="classic" />
<OrgLandingPageFooter {orgName} {logoUrl} {footer} variant="classic" />
</div>