Allow heading tags (h1-h6) in ContentSanitizer so news editor headings render

This commit is contained in:
2026-06-04 07:52:57 +02:00
parent 0b33a1b074
commit 15870ddb1f
191 changed files with 15453 additions and 1786 deletions

View File

@@ -7,22 +7,110 @@ function academyHref(section, slug) {
return `/academy/${section}/${encodeURIComponent(slug)}`
}
function FeatureCard({ title, description, href, cta }) {
function formatStatValue(value, singular, plural = `${singular}s`) {
const numericValue = Number(value || 0)
return `${numericValue.toLocaleString()} ${numericValue === 1 ? singular : plural}`
}
function FeatureCard({ title, description, href, cta, icon, eyebrow, highlights = [], tags = [], meta, theme }) {
return (
<Link href={href} className="rounded-[28px] border border-white/10 bg-white/[0.04] p-6 transition hover:border-white/20 hover:bg-white/[0.06]">
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-amber-200/80">Academy</p>
<h2 className="mt-3 text-2xl font-semibold tracking-[-0.04em] text-white">{title}</h2>
<p className="mt-3 text-sm leading-7 text-slate-300">{description}</p>
<span className="mt-5 inline-flex rounded-full border border-sky-300/25 bg-sky-300/12 px-4 py-2 text-sm font-semibold text-sky-100">{cta}</span>
<Link href={href} className={`group relative overflow-hidden rounded-[32px] border p-6 shadow-[0_24px_80px_rgba(2,6,23,0.22)] transition hover:-translate-y-1 hover:shadow-[0_30px_95px_rgba(2,6,23,0.32)] ${theme.shell}`}>
<div className={`absolute inset-0 ${theme.backdrop}`} />
<div className={`absolute inset-0 opacity-60 ${theme.pattern}`} />
<div className={`absolute -right-14 top-6 h-32 w-32 rounded-full blur-3xl ${theme.glow}`} />
<div className="relative flex min-h-[290px] flex-col">
<div className="flex items-start justify-between gap-4">
<div>
<p className={`text-[11px] font-semibold uppercase tracking-[0.24em] ${theme.eyebrow}`}>{eyebrow}</p>
<h2 className="mt-4 text-[2rem] font-semibold tracking-[-0.05em] text-white">{title}</h2>
</div>
<span className={`flex h-14 w-14 shrink-0 items-center justify-center rounded-[18px] border text-lg shadow-[0_14px_34px_rgba(2,6,23,0.28)] transition group-hover:scale-105 ${theme.iconWrap}`}>
<i className={icon} />
</span>
</div>
<p className="mt-5 max-w-[34ch] text-sm leading-7 text-slate-200/95">{description}</p>
<div className="mt-6 grid gap-3 sm:grid-cols-2">
{highlights.map((item) => (
<div key={`${title}-${item.label}`} className={`rounded-[22px] border px-4 py-3 backdrop-blur-sm ${theme.highlightCard}`}>
<p className={`text-[10px] font-semibold uppercase tracking-[0.18em] ${theme.highlightLabel}`}>{item.label}</p>
<p className="mt-1 text-sm font-semibold text-white">{item.value}</p>
</div>
))}
</div>
<div className="mt-5 flex flex-wrap gap-2">
{tags.map((tag) => (
<span key={`${title}-${tag}`} className={`rounded-full border px-3 py-1 text-[11px] font-semibold tracking-[0.12em] ${theme.tag}`}>
{tag}
</span>
))}
</div>
<div className="mt-auto flex items-center justify-between gap-4 pt-6">
<span className={`inline-flex rounded-full border px-4 py-2 text-sm font-semibold transition group-hover:translate-x-1 ${theme.cta}`}>{cta}</span>
<span className={`text-right text-[11px] font-semibold uppercase tracking-[0.2em] ${theme.meta}`}>{meta}</span>
</div>
</div>
</Link>
)
}
function FeatureRailCard({ eyebrow, title, description, icon, items = [], emptyText, actionHref = null, actionLabel = null, theme, renderItem }) {
return (
<section className={`relative overflow-hidden rounded-[30px] border p-6 shadow-[0_22px_70px_rgba(2,6,23,0.26)] ${theme.shell}`}>
<div className={`absolute inset-0 ${theme.backdrop}`} />
<div className={`absolute inset-0 opacity-60 ${theme.pattern}`} />
<div className={`absolute right-0 top-0 h-28 w-28 translate-x-8 -translate-y-6 rounded-full blur-3xl ${theme.glow}`} />
<div className="relative">
<div className="flex items-start justify-between gap-4">
<div className="max-w-[30ch]">
<p className={`text-[11px] font-semibold uppercase tracking-[0.24em] ${theme.eyebrow}`}>{eyebrow}</p>
<div className="mt-3 flex items-center gap-3">
<span className={`flex h-11 w-11 items-center justify-center rounded-[16px] border text-sm ${theme.iconWrap}`}>
<i className={icon} />
</span>
<h3 className="text-2xl font-semibold tracking-[-0.04em] text-white">{title}</h3>
</div>
<p className="mt-4 text-sm leading-7 text-slate-200/92">{description}</p>
</div>
{actionHref && actionLabel ? (
<Link href={actionHref} className={`inline-flex shrink-0 rounded-full border px-4 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] transition ${theme.action}`}>
{actionLabel}
</Link>
) : null}
</div>
<div className="mt-6 space-y-3">
{items.length > 0 ? items.map((item, index) => renderItem(item, index)) : (
<div className={`rounded-[22px] border px-4 py-4 text-sm ${theme.empty}`}>
{emptyText}
</div>
)}
</div>
</div>
</section>
)
}
function MetricCard({ label, value, accent }) {
return (
<div className={`rounded-[24px] border px-5 py-5 backdrop-blur-sm ${accent.shell}`}>
<p className={`text-[10px] font-semibold uppercase tracking-[0.2em] ${accent.label}`}>{label}</p>
<p className="mt-3 text-3xl font-semibold tracking-[-0.05em] text-white md:text-[2.4rem]">{value}</p>
</div>
)
}
function FeaturedCourseCard({ course }) {
const cover = course?.cover_image_url || course?.teaser_image_url || course?.cover_image || course?.teaser_image || ''
return (
<Link href={course.public_url} className="group overflow-hidden rounded-[28px] border border-white/10 bg-white/[0.04] transition hover:border-sky-300/25 hover:bg-white/[0.06]">
<Link href={course.public_url} className="group relative overflow-hidden rounded-[28px] border border-sky-200/12 bg-[linear-gradient(180deg,rgba(15,23,42,0.9),rgba(15,23,42,0.72))] transition hover:-translate-y-1 hover:border-sky-300/24 hover:shadow-[0_24px_72px_rgba(2,6,23,0.3)]">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(125,211,252,0.14),transparent_24%),linear-gradient(135deg,transparent_0%,transparent_48%,rgba(125,211,252,0.05)_48%,rgba(125,211,252,0.05)_52%,transparent_52%,transparent_100%)] opacity-80" />
<div className="relative h-44 overflow-hidden bg-[linear-gradient(135deg,rgba(14,165,233,0.24),rgba(15,23,42,0.92))]">
{cover ? <img src={cover} alt="" aria-hidden="true" className="h-full w-full object-cover" /> : null}
<div className="absolute inset-0 bg-[linear-gradient(180deg,transparent,rgba(2,6,23,0.82))]" />
@@ -31,17 +119,316 @@ function FeaturedCourseCard({ course }) {
<span className="rounded-full border border-white/10 bg-black/30 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-slate-200">{course.access_level}</span>
</div>
</div>
<div className="p-5">
<div className="relative p-5">
<p className="text-[10px] font-semibold uppercase tracking-[0.2em] text-sky-100/75">Guided course</p>
<h3 className="text-2xl font-semibold tracking-[-0.04em] text-white transition group-hover:text-sky-100">{course.title}</h3>
<p className="mt-3 text-sm leading-7 text-slate-300">{course.excerpt || course.description || 'Guided Academy course.'}</p>
<p className="mt-4 text-xs uppercase tracking-[0.18em] text-slate-500">{course.lessons_count || 0} lessons · {course.estimated_minutes ? `${course.estimated_minutes} min` : 'Flexible duration'}</p>
<div className="mt-4 flex items-center justify-between gap-3">
<p className="text-xs uppercase tracking-[0.18em] text-slate-500">{course.lessons_count || 0} lessons · {course.estimated_minutes ? `${course.estimated_minutes} min` : 'Flexible duration'}</p>
<span className="inline-flex rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-[0.18em] text-sky-100 transition group-hover:translate-x-1">Open path</span>
</div>
</div>
</Link>
)
}
export default function AcademyIndex({ seo, pricingUrl, links, featureFlags, stats, featuredCourses, featuredLessons, featuredPrompts, featuredChallenges, analytics }) {
function formatAccessDate(value) {
if (!value) {
return null
}
const parsed = new Date(value)
if (Number.isNaN(parsed.getTime())) {
return null
}
return new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
}).format(parsed)
}
function academyAccessHeading(access) {
switch (access?.status) {
case 'staff_access':
return 'You currently have full staff access to the Academy.'
case 'grace_period':
return `${access.tierLabel} access is still active.`
case 'trialing':
return `${access.tierLabel} trial is active right now.`
case 'active':
return access?.hasPaidAccess ? `${access.tierLabel} access is active.` : 'Your Academy access is active.'
case 'free':
return 'You currently have Free access to the Academy.'
default:
return 'Preview the Academy before you upgrade.'
}
}
function academyAccessMeta(access) {
const items = [
{ label: 'Current tier', value: access?.tierLabel || 'Guest' },
{ label: 'Status', value: access?.statusLabel || 'Preview access only' },
]
const formattedDate = formatAccessDate(access?.expiresAt)
if (formattedDate && access?.dateLabel) {
items.push({ label: access.dateLabel, value: formattedDate })
} else if (access?.renewsAutomatically) {
items.push({ label: 'Billing', value: 'Renews automatically' })
} else if (access?.signedIn && !access?.hasPaidAccess) {
items.push({ label: 'Upgrade', value: 'Creator and Pro unlock premium workflows' })
} else if (!access?.signedIn) {
items.push({ label: 'Upgrade', value: 'Sign in to track access and unlock premium content' })
}
return items
}
export default function AcademyIndex({ seo, pricingUrl, academyAccess = null, links, featureFlags, stats, featuredCourses, featuredLessons, featuredPrompts, featuredChallenges, analytics }) {
useAcademyPageAnalytics(analytics)
const accessHeading = academyAccessHeading(academyAccess)
const accessMeta = academyAccessMeta(academyAccess)
const useBillingAction = academyAccess?.signedIn && academyAccess?.hasPaidAccess && academyAccess?.billingUrl
const accessActionLabel = useBillingAction ? (academyAccess?.status === 'grace_period' ? 'Renew now' : 'Manage billing') : 'See plans'
const accessActionHref = useBillingAction ? academyAccess.billingUrl : pricingUrl
const academySections = [
{
title: 'Courses',
description: 'Follow guided learning paths that stitch reusable Academy lessons into a clean progression with completion tracking.',
href: links.courses,
cta: 'Browse courses',
icon: 'fa-solid fa-route',
eyebrow: 'Academy paths',
highlights: [
{ label: 'Library', value: formatStatValue(stats?.courseCount, 'course') },
{ label: 'Includes', value: formatStatValue(stats?.lessonCount, 'lesson') },
],
tags: ['Progress tracked', 'Learning paths', 'Skill ladders'],
meta: 'Structured progression',
theme: {
shell: 'border-sky-300/18 bg-slate-950/40 hover:border-sky-300/30',
backdrop: 'bg-[linear-gradient(145deg,rgba(14,165,233,0.16),rgba(15,23,42,0.95)_42%,rgba(16,185,129,0.18))]',
pattern: 'bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.16),transparent_30%),linear-gradient(125deg,transparent_0%,transparent_45%,rgba(125,211,252,0.08)_45%,rgba(125,211,252,0.08)_52%,transparent_52%,transparent_100%)]',
glow: 'bg-sky-300/25',
eyebrow: 'text-sky-100/80',
iconWrap: 'border-sky-200/20 bg-sky-300/12 text-sky-100',
highlightCard: 'border-sky-200/12 bg-slate-950/40',
highlightLabel: 'text-sky-100/75',
tag: 'border-sky-200/12 bg-sky-300/10 text-sky-100',
cta: 'border-sky-300/25 bg-sky-300/12 text-sky-100',
meta: 'text-sky-100/75',
},
},
{
title: 'Lessons',
description: 'Structured tutorials for prompt writing, cleanup workflows, AI ethics, and Skinbase-native publishing habits.',
href: links.lessons,
cta: 'Open lessons',
icon: 'fa-solid fa-book-open-reader',
eyebrow: 'Focused tutorials',
highlights: [
{ label: 'Depth', value: formatStatValue(stats?.lessonCount, 'lesson') },
{ label: 'Coverage', value: 'Prompt craft + workflow cleanup' },
],
tags: ['Short wins', 'Creative habits', 'Practical steps'],
meta: 'Skill-by-skill learning',
theme: {
shell: 'border-amber-300/18 bg-slate-950/40 hover:border-amber-300/30',
backdrop: 'bg-[linear-gradient(160deg,rgba(251,191,36,0.18),rgba(15,23,42,0.95)_40%,rgba(249,115,22,0.14))]',
pattern: 'bg-[radial-gradient(circle_at_top_right,rgba(253,230,138,0.14),transparent_28%),linear-gradient(180deg,transparent_0%,transparent_54%,rgba(251,191,36,0.08)_54%,rgba(251,191,36,0.08)_58%,transparent_58%,transparent_100%)]',
glow: 'bg-amber-300/20',
eyebrow: 'text-amber-100/85',
iconWrap: 'border-amber-200/20 bg-amber-300/12 text-amber-100',
highlightCard: 'border-amber-200/12 bg-slate-950/42',
highlightLabel: 'text-amber-100/75',
tag: 'border-amber-200/12 bg-amber-300/10 text-amber-100',
cta: 'border-amber-300/25 bg-amber-300/12 text-amber-100',
meta: 'text-amber-100/75',
},
},
{
title: 'Prompt Library',
description: 'Discover reusable prompt templates, locked premium previews, and creator-focused visual workflows.',
href: links.prompts,
cta: 'Explore prompts',
icon: 'fa-solid fa-wand-magic-sparkles',
eyebrow: 'Reusable prompt kits',
highlights: [
{ label: 'Templates', value: formatStatValue(stats?.promptCount, 'prompt') },
{ label: 'Use case', value: 'Reusable systems + premium previews' },
],
tags: ['Fast starts', 'Visual workflows', 'Copy + adapt'],
meta: 'High-speed ideation',
theme: {
shell: 'border-rose-300/18 bg-slate-950/40 hover:border-rose-300/30',
backdrop: 'bg-[linear-gradient(150deg,rgba(244,63,94,0.16),rgba(15,23,42,0.95)_38%,rgba(45,212,191,0.16))]',
pattern: 'bg-[radial-gradient(circle_at_20%_15%,rgba(251,113,133,0.16),transparent_24%),linear-gradient(90deg,rgba(255,255,255,0.04)_1px,transparent_1px),linear-gradient(180deg,rgba(255,255,255,0.04)_1px,transparent_1px)] bg-[length:auto,22px_22px,22px_22px]',
glow: 'bg-rose-300/20',
eyebrow: 'text-rose-100/85',
iconWrap: 'border-rose-200/20 bg-rose-300/12 text-rose-100',
highlightCard: 'border-rose-200/12 bg-slate-950/42',
highlightLabel: 'text-rose-100/75',
tag: 'border-rose-200/12 bg-rose-300/10 text-rose-100',
cta: 'border-rose-300/25 bg-rose-300/12 text-rose-100',
meta: 'text-rose-100/75',
},
},
]
const academyFeatureRails = [
{
key: 'lessons',
eyebrow: 'Featured lessons',
title: 'Jump-in tutorials',
description: 'Shorter Academy pieces for specific prompt problems, cleanup workflows, and publishing habits.',
icon: 'fa-solid fa-book-open-reader',
actionHref: links.lessons,
actionLabel: 'All lessons',
items: (featuredLessons || []).slice(0, 3),
emptyText: 'Featured lessons will appear here when the Academy team highlights a new tutorial.',
theme: {
shell: 'border-amber-300/16 bg-slate-950/45',
backdrop: 'bg-[linear-gradient(160deg,rgba(251,191,36,0.15),rgba(15,23,42,0.96)_42%,rgba(249,115,22,0.14))]',
pattern: 'bg-[radial-gradient(circle_at_top_right,rgba(253,230,138,0.12),transparent_24%),linear-gradient(180deg,transparent_0%,transparent_52%,rgba(251,191,36,0.08)_52%,rgba(251,191,36,0.08)_56%,transparent_56%,transparent_100%)]',
glow: 'bg-amber-300/18',
eyebrow: 'text-amber-100/82',
iconWrap: 'border-amber-200/20 bg-amber-300/12 text-amber-100',
action: 'border-amber-300/22 bg-amber-300/10 text-amber-100 hover:border-amber-300/34 hover:bg-amber-300/16',
item: 'border-amber-200/10 bg-slate-950/38 hover:border-amber-200/18 hover:bg-slate-950/52',
itemEyebrow: 'text-amber-100/75',
itemMeta: 'text-amber-100/70',
empty: 'border-amber-200/10 bg-slate-950/30 text-amber-50/80',
},
renderItem: (item, index, theme) => (
<Link key={item.id} href={academyHref('lessons', item.slug)} className={`group block rounded-[22px] border px-4 py-4 transition ${theme.item}`}>
<div className="flex items-start gap-3">
<span className={`mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-[10px] font-semibold ${theme.iconWrap}`}>{index + 1}</span>
<div className="min-w-0">
<span className={`block text-[10px] font-semibold uppercase tracking-[0.18em] ${theme.itemEyebrow}`}>{item.lesson_label || 'Featured lesson'}</span>
<span className="mt-2 block text-sm font-semibold text-white transition group-hover:text-amber-50">{item.title}</span>
<span className={`mt-2 block text-[11px] font-semibold uppercase tracking-[0.16em] ${theme.itemMeta}`}>Practical tutorial</span>
</div>
</div>
</Link>
),
},
{
key: 'prompts',
eyebrow: 'Featured prompts',
title: 'Reusable prompt packs',
description: 'Template-driven prompt entries designed for fast reuse, remixing, and premium workflow previews.',
icon: 'fa-solid fa-wand-magic-sparkles',
actionHref: links.promptPopular,
actionLabel: 'Top prompts',
items: (featuredPrompts || []).slice(0, 3),
emptyText: 'Featured prompts will appear here when reusable prompt templates are promoted on the homepage.',
theme: {
shell: 'border-rose-300/16 bg-slate-950/45',
backdrop: 'bg-[linear-gradient(155deg,rgba(244,63,94,0.14),rgba(15,23,42,0.96)_40%,rgba(45,212,191,0.14))]',
pattern: 'bg-[radial-gradient(circle_at_20%_18%,rgba(251,113,133,0.14),transparent_24%),linear-gradient(90deg,rgba(255,255,255,0.04)_1px,transparent_1px),linear-gradient(180deg,rgba(255,255,255,0.04)_1px,transparent_1px)] bg-[length:auto,22px_22px,22px_22px]',
glow: 'bg-rose-300/18',
eyebrow: 'text-rose-100/82',
iconWrap: 'border-rose-200/20 bg-rose-300/12 text-rose-100',
action: 'border-rose-300/22 bg-rose-300/10 text-rose-100 hover:border-rose-300/34 hover:bg-rose-300/16',
item: 'border-rose-200/10 bg-slate-950/38 hover:border-rose-200/18 hover:bg-slate-950/52',
itemEyebrow: 'text-rose-100/75',
itemMeta: 'text-rose-100/70',
empty: 'border-rose-200/10 bg-slate-950/30 text-rose-50/80',
},
renderItem: (item, index, theme) => (
<Link key={item.id} href={academyHref('prompts', item.slug)} className={`group block rounded-[22px] border px-4 py-4 transition ${theme.item}`}>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<span className={`block text-[10px] font-semibold uppercase tracking-[0.18em] ${theme.itemEyebrow}`}>Prompt template #{index + 1}</span>
<span className="mt-2 block text-sm font-semibold text-white transition group-hover:text-rose-50">{item.title}</span>
</div>
<span className={`rounded-full border px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] ${theme.iconWrap}`}>Template</span>
</div>
<span className={`mt-3 block text-[11px] font-semibold uppercase tracking-[0.16em] ${theme.itemMeta}`}>Reusable workflow</span>
</Link>
),
},
{
key: 'challenges',
eyebrow: 'Current challenges',
title: 'Build around a brief',
description: 'Academy challenges turn lessons and prompt systems into practical output with a clear creative objective.',
icon: 'fa-solid fa-trophy',
items: (featuredChallenges || []).slice(0, 3),
emptyText: 'Current challenges will appear here when the Academy team launches a new guided brief.',
theme: {
shell: 'border-emerald-300/16 bg-slate-950/45',
backdrop: 'bg-[linear-gradient(155deg,rgba(16,185,129,0.14),rgba(15,23,42,0.96)_42%,rgba(56,189,248,0.12))]',
pattern: 'bg-[radial-gradient(circle_at_top_left,rgba(110,231,183,0.14),transparent_24%),linear-gradient(135deg,transparent_0%,transparent_48%,rgba(16,185,129,0.08)_48%,rgba(16,185,129,0.08)_56%,transparent_56%,transparent_100%)]',
glow: 'bg-emerald-300/18',
eyebrow: 'text-emerald-100/82',
iconWrap: 'border-emerald-200/20 bg-emerald-300/12 text-emerald-100',
action: 'border-emerald-300/22 bg-emerald-300/10 text-emerald-100 hover:border-emerald-300/34 hover:bg-emerald-300/16',
item: 'border-emerald-200/10 bg-slate-950/38 hover:border-emerald-200/18 hover:bg-slate-950/52',
itemEyebrow: 'text-emerald-100/75',
itemMeta: 'text-emerald-100/70',
empty: 'border-emerald-200/10 bg-slate-950/30 text-emerald-50/80',
},
renderItem: (item, index, theme) => (
<Link key={item.id} href={academyHref('challenges', item.slug)} className={`group block rounded-[22px] border px-4 py-4 transition ${theme.item}`}>
<div className="flex items-start gap-3">
<span className={`mt-0.5 flex h-8 min-w-8 items-center justify-center rounded-full border px-2 text-[10px] font-semibold ${theme.iconWrap}`}>#{index + 1}</span>
<div className="min-w-0">
<span className={`block text-[10px] font-semibold uppercase tracking-[0.18em] ${theme.itemEyebrow}`}>Active brief</span>
<span className="mt-2 block text-sm font-semibold text-white transition group-hover:text-emerald-50">{item.title}</span>
<span className={`mt-2 block text-[11px] font-semibold uppercase tracking-[0.16em] ${theme.itemMeta}`}>Apply what you learned</span>
</div>
</div>
</Link>
),
},
]
const handleAccessAction = () => {
if (!useBillingAction) {
trackUpgradeClick(analytics, { source: 'academy_home_hero' })
}
}
const academyMetrics = [
{
key: 'courses',
label: 'Courses',
value: stats?.courseCount || 0,
accent: {
shell: 'border-sky-300/14 bg-sky-300/[0.08]',
label: 'text-sky-100/78',
},
},
{
key: 'lessons',
label: 'Lessons',
value: stats?.lessonCount || 0,
accent: {
shell: 'border-amber-300/14 bg-amber-300/[0.08]',
label: 'text-amber-100/78',
},
},
{
key: 'prompts',
label: 'Prompts',
value: stats?.promptCount || 0,
accent: {
shell: 'border-rose-300/14 bg-rose-300/[0.08]',
label: 'text-rose-100/78',
},
},
{
key: 'challenges',
label: 'Challenges',
value: stats?.challengeCount || 0,
accent: {
shell: 'border-emerald-300/14 bg-emerald-300/[0.08]',
label: 'text-emerald-100/78',
},
},
]
const jsonLd = [{
'@context': 'https://schema.org',
@@ -52,68 +439,115 @@ export default function AcademyIndex({ seo, pricingUrl, links, featureFlags, sta
}]
return (
<main className="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(251,191,36,0.18),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(56,189,248,0.18),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-8 sm:px-6 lg:px-8">
<main className="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(251,191,36,0.18),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(56,189,248,0.18),_transparent_24%),linear-gradient(180deg,_#0f172a_0%,_#111827_100%)] px-4 py-6 sm:px-6 sm:py-8 lg:px-8 lg:py-10">
<SeoHead seo={seo || {}} title="Skinbase AI Academy" description={seo?.description} jsonLd={jsonLd} />
<div className="mx-auto max-w-[1440px] space-y-8">
<section className="overflow-hidden rounded-[40px] border border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.94),rgba(28,25,23,0.88)),radial-gradient(circle_at_top_right,rgba(251,191,36,0.2),transparent_26%)] p-8 shadow-[0_32px_100px_rgba(2,6,23,0.38)] md:p-10 lg:p-12">
<div className="grid gap-8 xl:grid-cols-[minmax(0,1fr)_360px] xl:items-end">
<div className="mx-auto max-w-[1440px] space-y-6 md:space-y-8 xl:space-y-10">
<section className="overflow-hidden rounded-[40px] border border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.94),rgba(28,25,23,0.88)),radial-gradient(circle_at_top_right,rgba(251,191,36,0.2),transparent_26%)] p-6 shadow-[0_32px_100px_rgba(2,6,23,0.38)] md:p-7 lg:p-8">
<div className="grid gap-5 xl:grid-cols-[minmax(0,1fr)_300px] xl:items-center">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-amber-200/80">Skinbase AI Academy</p>
<h1 className="mt-4 max-w-4xl text-4xl font-semibold tracking-[-0.05em] text-white md:text-5xl xl:text-6xl">Learn how to turn prompts into wallpapers, digital art, skins, covers, and visual worlds.</h1>
<p className="mt-5 max-w-3xl text-base leading-8 text-slate-300 md:text-lg">Skinbase AI Academy is the creative learning hub for AI-assisted art on Skinbase. Start with free lessons, explore prompt templates, and unlock premium workflows later.</p>
<h1 className="mt-3 max-w-[15ch] text-4xl font-semibold tracking-[-0.05em] text-white md:max-w-[16ch] md:text-5xl xl:max-w-[19ch] xl:text-[3.2rem]">Learn how to turn prompts into wallpapers, digital art, skins, covers, and visual worlds.</h1>
<p className="mt-4 max-w-3xl text-base leading-7 text-slate-300 md:text-lg md:leading-8">Skinbase AI Academy is the creative learning hub for AI-assisted art on Skinbase. Start with free lessons, explore prompt templates, and unlock premium workflows later.</p>
<div className="mt-7 flex flex-wrap gap-3">
<div className="mt-5 flex flex-wrap gap-2.5">
<Link href={links.courses} className="rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-300/18">Browse courses</Link>
<Link href={links.lessons} className="rounded-full border border-amber-300/25 bg-amber-300/12 px-5 py-3 text-sm font-semibold text-amber-100 transition hover:border-amber-300/40 hover:bg-amber-300/18">Browse lessons</Link>
<Link href={links.prompts} className="rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.07]">Open prompt library</Link>
<Link href={links.promptPopular} className="rounded-full border border-rose-300/25 bg-rose-300/12 px-5 py-3 text-sm font-semibold text-rose-100 transition hover:border-rose-300/40 hover:bg-rose-300/18">Top prompts</Link>
<Link href={pricingUrl} onClick={() => trackUpgradeClick(analytics, { source: 'academy_home_hero' })} className="rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-300/18">See plans</Link>
</div>
</div>
<div className="rounded-[30px] border border-white/10 bg-black/20 p-6">
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-300">Launch status</p>
<div className="mt-4 space-y-3 text-sm text-slate-300">
<div className="flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3"><span>Challenges</span><span>{featureFlags?.challengesEnabled ? 'Enabled' : 'Disabled'}</span></div>
<div className="flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3"><span>Badges</span><span>{featureFlags?.badgesEnabled ? 'Enabled' : 'Disabled'}</span></div>
<div className="flex items-center justify-between rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3"><span>Payments</span><span>{featureFlags?.paymentsEnabled ? 'Preview only' : 'Disabled'}</span></div>
<div className="rounded-[30px] border border-sky-300/20 bg-[linear-gradient(135deg,rgba(56,189,248,0.16),rgba(15,23,42,0.82))] p-4 md:p-5">
<div className="flex items-start gap-3">
<span className="flex h-11 w-11 items-center justify-center rounded-2xl border border-sky-300/25 bg-sky-300/12 text-sky-100"><i className="fa-solid fa-crown text-sm" /></span>
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-100/80">Your Academy access</p>
<p className="mt-1 text-lg font-semibold text-white">{accessHeading}</p>
</div>
</div>
<div className="mt-4 grid gap-3">
{accessMeta.map((item) => (
<div key={item.label} className="flex items-center justify-between rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-slate-200">
<span className="text-slate-300">{item.label}</span>
<span className="font-semibold text-white">{item.value}</span>
</div>
))}
</div>
<Link href={accessActionHref} onClick={handleAccessAction} className="mt-4 inline-flex rounded-full border border-sky-300/25 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-300/18">{accessActionLabel}</Link>
{academyAccess?.status === 'grace_period' ? <p className="mt-2 text-xs text-sky-100/75">Opens billing account to restore renewal before access ends.</p> : null}
</div>
</div>
</section>
<section className="grid gap-5 lg:grid-cols-3">
<FeatureCard title="Courses" description="Follow guided learning paths that stitch reusable Academy lessons into a clean progression with completion tracking." href={links.courses} cta="Browse courses" />
<FeatureCard title="Lessons" description="Structured tutorials for prompt writing, cleanup workflows, AI ethics, and Skinbase-native publishing habits." href={links.lessons} cta="Open lessons" />
<FeatureCard title="Prompt Library" description="Discover reusable prompt templates, locked premium previews, and creator-focused visual workflows." href={links.prompts} cta="Explore prompts" />
<section className="space-y-4 md:space-y-5">
<div className="flex flex-col gap-3 rounded-[30px] border border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.6),rgba(15,23,42,0.22))] px-5 py-4 shadow-[0_16px_48px_rgba(2,6,23,0.18)] md:flex-row md:items-end md:justify-between md:px-6">
<div className="max-w-2xl">
<p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-100/75">Choose your Academy lane</p>
<h2 className="mt-2 text-2xl font-semibold tracking-[-0.045em] text-white md:text-[2rem]">Start with the format that matches how you learn.</h2>
</div>
<div className="flex items-center gap-3 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400">
<span className="h-px flex-1 bg-gradient-to-r from-transparent via-sky-300/35 to-transparent md:min-w-24" />
<span>Courses, lessons, prompts</span>
</div>
</div>
<div className="grid gap-5 lg:grid-cols-3">
{academySections.map((section) => (
<FeatureCard key={section.title} {...section} />
))}
</div>
</section>
<section className="grid gap-5 lg:grid-cols-4">
<div className="rounded-[28px] border border-white/10 bg-white/[0.04] p-6"><p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Courses</p><p className="mt-3 text-4xl font-semibold tracking-[-0.05em] text-white">{stats?.courseCount || 0}</p></div>
<div className="rounded-[28px] border border-white/10 bg-white/[0.04] p-6"><p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Lessons</p><p className="mt-3 text-4xl font-semibold tracking-[-0.05em] text-white">{stats?.lessonCount || 0}</p></div>
<div className="rounded-[28px] border border-white/10 bg-white/[0.04] p-6"><p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Prompts</p><p className="mt-3 text-4xl font-semibold tracking-[-0.05em] text-white">{stats?.promptCount || 0}</p></div>
<div className="rounded-[28px] border border-white/10 bg-white/[0.04] p-6"><p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Challenges</p><p className="mt-3 text-4xl font-semibold tracking-[-0.05em] text-white">{stats?.challengeCount || 0}</p></div>
<section className="overflow-hidden rounded-[34px] border border-white/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.82),rgba(15,23,42,0.58)),radial-gradient(circle_at_top_left,rgba(56,189,248,0.12),transparent_30%),radial-gradient(circle_at_bottom_right,rgba(251,191,36,0.12),transparent_28%)] p-3 shadow-[0_18px_56px_rgba(2,6,23,0.24)] sm:p-4">
<div className="grid gap-3 lg:grid-cols-4">
{academyMetrics.map((metric) => (
<MetricCard key={metric.key} label={metric.label} value={metric.value} accent={metric.accent} />
))}
</div>
</section>
{featuredCourses?.length ? (
<section className="space-y-5">
<div className="flex flex-wrap items-end justify-between gap-4">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Featured courses</p>
<h2 className="mt-2 text-3xl font-semibold tracking-[-0.045em] text-white">Guided Academy paths</h2>
<section className="relative overflow-hidden rounded-[36px] border border-sky-200/10 bg-[linear-gradient(135deg,rgba(15,23,42,0.82),rgba(3,7,18,0.94)),radial-gradient(circle_at_top_left,rgba(14,165,233,0.14),transparent_28%),radial-gradient(circle_at_bottom_right,rgba(16,185,129,0.1),transparent_30%)] p-5 shadow-[0_24px_70px_rgba(2,6,23,0.26)] md:p-6 lg:p-7">
<div className="absolute inset-0 bg-[linear-gradient(90deg,rgba(255,255,255,0.03)_1px,transparent_1px),linear-gradient(180deg,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-[length:28px_28px] opacity-25" />
<div className="relative space-y-4 md:space-y-5">
<div className="flex flex-wrap items-end justify-between gap-4 rounded-[28px] border border-white/10 bg-black/15 px-5 py-4 backdrop-blur-sm">
<div className="max-w-2xl">
<p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-100/78">Featured courses</p>
<h2 className="mt-2 text-3xl font-semibold tracking-[-0.045em] text-white">Guided Academy paths</h2>
<p className="mt-3 max-w-[54ch] text-sm leading-7 text-slate-300">Longer learning paths for people who want a clearer start-to-finish route instead of individual tutorials or standalone prompt templates.</p>
</div>
<div className="flex items-center gap-3">
<div className="rounded-full border border-sky-300/18 bg-sky-300/10 px-4 py-2 text-[11px] font-semibold uppercase tracking-[0.2em] text-sky-100/85">
{formatStatValue(featuredCourses.length, 'featured path')}
</div>
<Link href={links.courses} className="rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.08]">All courses</Link>
</div>
</div>
<div className="grid gap-5 xl:grid-cols-3">
{featuredCourses.slice(0, 3).map((course) => <FeaturedCourseCard key={course.id} course={course} />)}
</div>
<Link href={links.courses} className="rounded-full border border-white/10 bg-white/[0.04] px-5 py-3 text-sm font-semibold text-white">All courses</Link>
</div>
<div className="grid gap-5 xl:grid-cols-3">
{featuredCourses.slice(0, 3).map((course) => <FeaturedCourseCard key={course.id} course={course} />)}
</div>
</section>
) : null}
<section className="grid gap-5 xl:grid-cols-3">
<div className="rounded-[28px] border border-white/10 bg-black/20 p-6"><p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Featured lessons</p><div className="mt-4 space-y-3">{(featuredLessons || []).slice(0, 3).map((item) => <Link key={item.id} href={academyHref('lessons', item.slug)} className="block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white"><span className="block text-[10px] font-semibold uppercase tracking-[0.18em] text-amber-100">{item.lesson_label || 'Featured lesson'}</span><span className="mt-1 block">{item.title}</span></Link>)}</div></div>
<div className="rounded-[28px] border border-white/10 bg-black/20 p-6"><p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Featured prompts</p><div className="mt-4 space-y-3">{(featuredPrompts || []).slice(0, 3).map((item) => <Link key={item.id} href={academyHref('prompts', item.slug)} className="block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">{item.title}</Link>)}</div></div>
<div className="rounded-[28px] border border-white/10 bg-black/20 p-6"><p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">Current challenges</p><div className="mt-4 space-y-3">{(featuredChallenges || []).slice(0, 3).map((item) => <Link key={item.id} href={academyHref('challenges', item.slug)} className="block rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white">{item.title}</Link>)}</div></div>
<section className="grid gap-4 xl:grid-cols-3 xl:gap-5">
{academyFeatureRails.map((rail) => (
<FeatureRailCard
key={rail.key}
eyebrow={rail.eyebrow}
title={rail.title}
description={rail.description}
icon={rail.icon}
items={rail.items}
emptyText={rail.emptyText}
actionHref={rail.actionHref}
actionLabel={rail.actionLabel}
theme={rail.theme}
renderItem={(item, index) => rail.renderItem(item, index, rail.theme)}
/>
))}
</section>
</div>
</main>