mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
The kit's Hero drew a centred column of its own at 768px. It hands its crumb, badge, title, line and actions to the shared fold now, so it keeps the bar's measure, and fills the window with a preview beside it where a page gives one: every course walks its syllabus week by week, and the University index its courses. A course's trail starts at University rather than Models.
461 lines
19 KiB
TypeScript
461 lines
19 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import Link from 'next/link'
|
|
import { Hero } from '@/components/models/kit'
|
|
import { Steps } from '@/components/preview/steps'
|
|
import { Band, Card, Head } from '@/components/marketing/band'
|
|
import { Box, Text, XStack, YStack } from '@/components/ui'
|
|
import { Action, Title, Lede, Eyebrow, Chip } from '@hanzo/ui/marketing'
|
|
import { Grid } from '@hanzo/ui/grid'
|
|
import {
|
|
CheckCircle2,
|
|
GraduationCap,
|
|
Coins,
|
|
BookOpen,
|
|
Check,
|
|
Code2,
|
|
} from 'lucide-react'
|
|
import { UNIVERSITY_COURSES, type UniversityCourse } from '../courses-data'
|
|
import { checkoutUrl } from '@hanzo/app/lib/pay'
|
|
import { phrase } from '@hanzo/i18n'
|
|
import { useI18n } from '@hanzo/i18n/react'
|
|
|
|
export function CourseView({ course }: { course: UniversityCourse }) {
|
|
const { t } = useI18n()
|
|
const defaultEnrollUrl = checkoutUrl(course.planId, `https://hanzo.ai/university/${course.slug}`)
|
|
|
|
return (
|
|
<Box minH="100vh" bg="$background" $platform-web={{ color: 'var(--foreground)' }}>
|
|
{/* ── Breadcrumb & Hero ── */}
|
|
<Hero
|
|
crumb={`${course.code}: ${t(course.title)}`}
|
|
trail={[{ label: phrase('University'), href: '/university' }, { label: course.code }]}
|
|
preview={<Steps title={course.code} icon={<BookOpen size={12} color="currentColor" />} steps={course.syllabus.map((w) => ({ title: w.title, body: w.summary }))} />}
|
|
badge={t('Course {code} · Units: {units}.0 · Credential: {credential}', { code: course.code, units: course.units, credential: course.credential })}
|
|
title={t(course.title)}
|
|
lede={<>{t(course.summary)}</>}
|
|
>
|
|
<Action href={defaultEnrollUrl} fill>
|
|
{t('Enroll in {code} — ${price} USD (+${credit} Credit Rebate)', { code: course.code, price: course.price, credit: course.rebateCredits })}
|
|
</Action>
|
|
<Action href="/university/portal">
|
|
{t('Preview Enrolled Student Portal →')}
|
|
</Action>
|
|
</Hero>
|
|
|
|
{/* ── Course Specification Matrix & Tuition Card ── */}
|
|
<Band pad="$10" measure={1152}>
|
|
<Grid columns={{ min: 340, max: 2 }} gap={24}>
|
|
{/* Academic Specifications Card */}
|
|
<Card p="$6" display="flex" flexDirection="column" justify="space-between" borderWidth={1} borderColor="var(--border)">
|
|
<YStack gap="$5">
|
|
<XStack items="center" justify="space-between">
|
|
<Text fontSize="$1" fontFamily="$mono" color="var(--white-60)" letterSpacing={1}>
|
|
{t('Academic specifications')}
|
|
</Text>
|
|
<Chip px={10} py={2} fontSize="$1" fontFamily="$mono">
|
|
{t('{code} · {units}.0 UNITS', { code: course.code, units: course.units })}
|
|
</Chip>
|
|
</XStack>
|
|
|
|
<Grid columns={{ min: 140, max: 2 }} gap={16}>
|
|
<YStack
|
|
gap="$1"
|
|
p="$3"
|
|
rounded="var(--radius-lg)"
|
|
bg="var(--pure-black)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
>
|
|
<Text fontFamily="$mono" fontSize="$1" color="var(--muted-foreground)">
|
|
{t('Duration')}
|
|
</Text>
|
|
<Text fontSize="$2" fontWeight="700" color="var(--white)">
|
|
{t(course.duration)}
|
|
</Text>
|
|
</YStack>
|
|
|
|
<YStack
|
|
gap="$1"
|
|
p="$3"
|
|
rounded="var(--radius-lg)"
|
|
bg="var(--pure-black)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
>
|
|
<Text fontFamily="$mono" fontSize="$1" color="var(--muted-foreground)">
|
|
{t('LEVEL')}
|
|
</Text>
|
|
<Text fontSize="$2" fontWeight="700" color="var(--white)">
|
|
{t(course.level)}
|
|
</Text>
|
|
</YStack>
|
|
|
|
<YStack
|
|
gap="$1"
|
|
p="$3"
|
|
rounded="var(--radius-lg)"
|
|
bg="var(--pure-black)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
>
|
|
<Text fontFamily="$mono" fontSize="$1" color="var(--muted-foreground)">
|
|
{t('Credential')}
|
|
</Text>
|
|
<Text fontSize="$2" fontWeight="700" color="var(--white)">
|
|
{course.credential}
|
|
</Text>
|
|
</YStack>
|
|
|
|
<YStack
|
|
gap="$1"
|
|
p="$3"
|
|
rounded="var(--radius-lg)"
|
|
bg="var(--pure-black)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
>
|
|
<Text fontFamily="$mono" fontSize="$1" color="var(--muted-foreground)">
|
|
{t('Credential format')}
|
|
</Text>
|
|
<Text fontSize="$2" fontWeight="700" color="var(--white)">
|
|
{t('W3C Verifiable Credential')}
|
|
</Text>
|
|
</YStack>
|
|
</Grid>
|
|
|
|
<YStack gap="$2" pt="$2">
|
|
<Text fontSize="$1" fontWeight="700" color="var(--white)">
|
|
{t('Lead Faculty & Academic Direction')}
|
|
</Text>
|
|
<XStack items="center" gap="$3">
|
|
<Box
|
|
width={36}
|
|
height={36}
|
|
rounded={9999}
|
|
bg="var(--neutral-800)"
|
|
items="center"
|
|
justify="center"
|
|
>
|
|
<GraduationCap size={18} color="var(--white)" />
|
|
</Box>
|
|
<YStack>
|
|
<Text fontSize="$1" fontWeight="600" color="var(--white)">
|
|
{course.instructor.name}
|
|
</Text>
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
{t(course.instructor.role)}
|
|
</Text>
|
|
</YStack>
|
|
</XStack>
|
|
</YStack>
|
|
|
|
<YStack gap="$2">
|
|
<Text fontSize="$1" fontWeight="700" color="var(--white)">
|
|
{t('Prerequisites')}
|
|
</Text>
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
{t(course.prerequisites)}
|
|
</Text>
|
|
</YStack>
|
|
</YStack>
|
|
</Card>
|
|
|
|
{/* Tuition & Enrollment Card */}
|
|
<Card p="$6" display="flex" flexDirection="column" justify="space-between" borderWidth={1} borderColor="var(--border)">
|
|
<YStack gap="$4">
|
|
<XStack items="center" justify="space-between">
|
|
<Text fontSize="$1" fontFamily="$mono" color="var(--white-60)" letterSpacing={1}>
|
|
{t('Tuition & compute rebate')}
|
|
</Text>
|
|
<Chip px={8} py={2} fontSize="$1" fontFamily="$mono" color="var(--state-success)">
|
|
{t('Open enrollment')}
|
|
</Chip>
|
|
</XStack>
|
|
|
|
{/* Tuition Price and Rebate Breakdown */}
|
|
<YStack
|
|
gap="$3"
|
|
p="$5"
|
|
bg="var(--pure-black)"
|
|
rounded="var(--radius-lg)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
>
|
|
<XStack items="baseline" justify="space-between">
|
|
<YStack>
|
|
<Text fontSize="$1" fontFamily="$mono" color="var(--muted-foreground)">
|
|
{t('Total tuition')}
|
|
</Text>
|
|
<XStack items="baseline" gap="$1">
|
|
<Text fontSize="$6" fontWeight="700" color="var(--white)">
|
|
${course.price}
|
|
</Text>
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
USD
|
|
</Text>
|
|
</XStack>
|
|
</YStack>
|
|
<Chip px={10} py={4} fontSize="$1" fontFamily="$mono" color="var(--state-success)">
|
|
{t('+${credit} CREDITS (25% REBATE)', { credit: course.rebateCredits })}
|
|
</Chip>
|
|
</XStack>
|
|
<Text fontSize="$1" color="var(--muted-foreground)" lineHeight="$1">
|
|
{t('Guaranteed compute allocation deposited immediately on enrollment. Subsidizes Zen 6 inference and Hanzo Visor sandbox runtimes.')}
|
|
</Text>
|
|
</YStack>
|
|
|
|
{/* Primary Enrollment CTA Button */}
|
|
<YStack gap="$2">
|
|
<Action
|
|
href={defaultEnrollUrl}
|
|
fill
|
|
fontSize="$3"
|
|
$platform-web={{
|
|
textAlign: 'center',
|
|
padding: '16px 24px',
|
|
fontWeight: 700,
|
|
}}
|
|
>
|
|
{t('Enroll in {code} — ${price} USD →', { code: course.code, price: course.price })}
|
|
</Action>
|
|
<Text
|
|
fontSize="$1"
|
|
color="var(--muted-foreground)"
|
|
$platform-web={{ textAlign: 'center' }}
|
|
>
|
|
{t('Have a coupon code or fellowship grant? Enter it during checkout.')}
|
|
</Text>
|
|
</YStack>
|
|
|
|
{/* Rebate Explanation Card */}
|
|
<Card p="$4" bg="$panel" borderWidth={1} borderColor="var(--border)">
|
|
<XStack items="center" gap="$2" mb="$1">
|
|
<Coins size={16} color="var(--state-success)" />
|
|
<Text fontSize="$1" fontWeight="700" color="var(--white)">
|
|
{t('Automatic 25% Compute Credit Rebate')}
|
|
</Text>
|
|
</XStack>
|
|
<Text fontSize="$1" color="var(--muted-foreground)" lineHeight="$2">
|
|
{t('25% of tuition (rounded up) is immediately deposited into your Hanzo Cloud compute balance on day one. Fully subsidizes your Hanzo Visor sandbox container leases and model inference throughout the course.')}
|
|
</Text>
|
|
</Card>
|
|
|
|
{/* Direct Portal Preview Link */}
|
|
<XStack items="center" justify="center" gap="$2" pt="$1">
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
{t('Already purchased?')}
|
|
</Text>
|
|
<Text
|
|
render="a"
|
|
{...{ href: '/university/portal' }}
|
|
color="var(--white)"
|
|
fontSize="$1"
|
|
fontWeight="600"
|
|
textDecorationLine="underline"
|
|
>
|
|
{t('Enter Enrolled Student Portal →')}
|
|
</Text>
|
|
</XStack>
|
|
</YStack>
|
|
</Card>
|
|
</Grid>
|
|
</Band>
|
|
|
|
{/* ── Core Competencies Band ── */}
|
|
<Band pad="$10" measure={1152} ground="var(--pure-black)">
|
|
<Head
|
|
eyebrow={phrase('Competency Framework')}
|
|
title={t('What you master in {code}', { code: course.code })}
|
|
lede={t('Engineered for software practitioners building resilient autonomous production systems.')}
|
|
/>
|
|
|
|
<Grid columns={{ min: 260, max: 4 }} gap={16}>
|
|
{course.competencies.map((comp, idx) => (
|
|
<Card key={idx} p="$5" borderWidth={1} borderColor="var(--border)">
|
|
<XStack items="flex-start" gap="$3">
|
|
<Box
|
|
p="$2"
|
|
rounded="var(--radius-md)"
|
|
bg="$panel"
|
|
$platform-web={{ backgroundColor: 'color-mix(in srgb, var(--state-success) 15%, transparent)' }}
|
|
>
|
|
<CheckCircle2 size={16} color="var(--state-success)" />
|
|
</Box>
|
|
<Text fontSize="$2" fontWeight="600" color="var(--white)" lineHeight="$2">
|
|
{t(comp)}
|
|
</Text>
|
|
</XStack>
|
|
</Card>
|
|
))}
|
|
</Grid>
|
|
</Band>
|
|
|
|
{/* ── Complete Week-by-Week Syllabus ── */}
|
|
<Band pad="$12" measure={1152}>
|
|
<Head
|
|
eyebrow={phrase('Comprehensive Syllabus')}
|
|
title={t('Curriculum & Laboratory Breakdown')}
|
|
lede={t('Every module combines systems architecture lectures with hands-on containerized lab defense.')}
|
|
/>
|
|
|
|
<YStack gap="$6">
|
|
{course.syllabus.map((week) => (
|
|
<Card key={week.week} p="$6" borderWidth={1} borderColor="var(--border)">
|
|
<YStack gap="$4">
|
|
<XStack items="center" justify="space-between" flexWrap="wrap" gap="$2">
|
|
<XStack items="center" flexWrap="wrap" gap="$3" shrink={1} minW={0}>
|
|
<Chip px={10} py={3} fontSize="$1" fontFamily="$mono">
|
|
{week.code}
|
|
</Chip>
|
|
<Text fontSize="$4" fontWeight="700" color="var(--white)">
|
|
{t(week.title)}
|
|
</Text>
|
|
</XStack>
|
|
<Text fontSize="$1" fontFamily="$mono" color="var(--muted-foreground)">
|
|
{t(week.week)}
|
|
</Text>
|
|
</XStack>
|
|
|
|
<Text fontSize="$2" color="var(--white-80)" lineHeight="$2">
|
|
{t(week.summary)}
|
|
</Text>
|
|
|
|
<Grid columns={{ min: 320, max: 2 }} gap={16} pt="$2">
|
|
<YStack gap="$2" p="$4" rounded="var(--radius-md)" bg="$panel" borderWidth={1} borderColor="var(--border)">
|
|
<Text fontSize="$1" fontWeight="700" color="var(--white)">
|
|
{t('Key Topics & Lectures:')}
|
|
</Text>
|
|
{week.lectures.map((lec, lIdx) => (
|
|
<XStack key={lIdx} items="flex-start" gap="$2">
|
|
<Box mt={4}>
|
|
<Check size={12} color="var(--state-success)" />
|
|
</Box>
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
{t(lec)}
|
|
</Text>
|
|
</XStack>
|
|
))}
|
|
</YStack>
|
|
|
|
<YStack gap="$2" p="$4" rounded="var(--radius-md)" bg="$panel" borderWidth={1} borderColor="var(--border)">
|
|
<Text fontSize="$1" fontWeight="700" color="var(--white)">
|
|
{t('Assigned Readings:')}
|
|
</Text>
|
|
{week.readings.map((reading, rIdx) => (
|
|
<XStack key={rIdx} items="flex-start" gap="$2">
|
|
<Box mt={4}>
|
|
<BookOpen size={12} color="var(--white-60)" />
|
|
</Box>
|
|
<Text fontSize="$1" color="var(--muted-foreground)">
|
|
{reading}
|
|
</Text>
|
|
</XStack>
|
|
))}
|
|
</YStack>
|
|
</Grid>
|
|
|
|
<Box
|
|
p="$3"
|
|
px="$4"
|
|
rounded="var(--radius-md)"
|
|
bg="var(--pure-black)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
>
|
|
<XStack items="center" gap="$2">
|
|
<Code2 size={16} color="var(--state-success)" />
|
|
<Text fontSize="$1" fontWeight="600" color="var(--white)">
|
|
{t(week.lab)}
|
|
</Text>
|
|
</XStack>
|
|
</Box>
|
|
</YStack>
|
|
</Card>
|
|
))}
|
|
</YStack>
|
|
</Band>
|
|
|
|
{/* ── Capstone Project Defense Card ── */}
|
|
<Band pad="$10" measure={1080} ground="var(--pure-black)">
|
|
<Card p="$8" bg="$panel" borderColor="var(--border)" borderWidth={1}>
|
|
<XStack items="center" justify="space-between" flexWrap="wrap" gap="$4">
|
|
<YStack gap="$2" flex={1} minW={280}>
|
|
<Eyebrow>{t('Final Examination')}</Eyebrow>
|
|
<Title quiet={false}>{t('Capstone Defense & Credential Issuance')}</Title>
|
|
<Lede maxW={640}>
|
|
{t(course.capstone)} {t('Upon automated grading verification, your {credential} W3C Verifiable Credential is cryptographically signed and issued to your Hanzo DID.', { credential: course.credential })}
|
|
</Lede>
|
|
</YStack>
|
|
|
|
<YStack gap="$2">
|
|
<Action href={defaultEnrollUrl} fill>
|
|
{t('Enroll in {code} — ${price}', { code: course.code, price: course.price })}
|
|
</Action>
|
|
<Action href="/university/portal">
|
|
{t('Preview Student Portal')}
|
|
</Action>
|
|
</YStack>
|
|
</XStack>
|
|
</Card>
|
|
</Band>
|
|
|
|
{/* ── Switcher to All 6 Courses ── */}
|
|
<Band pad="$10" measure={1152}>
|
|
<Head
|
|
eyebrow={phrase('Hanzo University Tracks')}
|
|
title={t('Explore the Complete Curriculum')}
|
|
lede={t('Six specialized engineering credentials designed for the frontier of autonomous intelligence.')}
|
|
/>
|
|
|
|
<Grid columns={{ min: 320, max: 3 }} gap={16}>
|
|
{UNIVERSITY_COURSES.map((c) => (
|
|
<Card
|
|
key={c.slug}
|
|
p="$5"
|
|
bg={c.slug === course.slug ? 'var(--pure-black)' : '$panel'}
|
|
borderWidth={1}
|
|
borderColor={c.slug === course.slug ? 'var(--white)' : 'var(--border)'}
|
|
>
|
|
<YStack gap="$3" justify="space-between" height="100%">
|
|
<YStack gap="$2">
|
|
<XStack items="center" justify="space-between">
|
|
<Text fontSize="$1" fontFamily="$mono" color="var(--muted-foreground)">
|
|
{c.code}
|
|
</Text>
|
|
<Chip px={8} py={2} fontSize="$1" fontFamily="$mono">
|
|
{c.credential}
|
|
</Chip>
|
|
</XStack>
|
|
<Text fontSize="$3" fontWeight="700" color="var(--white)">
|
|
{t(c.title)}
|
|
</Text>
|
|
<Text fontSize="$1" color="var(--muted-foreground)" numberOfLines={2}>
|
|
{t(c.summary)}
|
|
</Text>
|
|
</YStack>
|
|
|
|
<XStack items="center" justify="space-between" pt="$3" borderTopWidth={1} borderColor="var(--border)">
|
|
<Text fontSize="$2" fontWeight="700" color="var(--white)">
|
|
${c.price} USD
|
|
</Text>
|
|
<Text
|
|
render="a"
|
|
{...{ href: `/university/${c.slug}` }}
|
|
color="var(--white)"
|
|
fontSize="$1"
|
|
fontWeight="600"
|
|
>
|
|
{t('View Course →')}
|
|
</Text>
|
|
</XStack>
|
|
</YStack>
|
|
</Card>
|
|
))}
|
|
</Grid>
|
|
</Band>
|
|
</Box>
|
|
)
|
|
}
|