import React from 'react'
function formatDate(value) {
if (!value) return null
try {
return new Intl.DateTimeFormat('en', {
month: 'short',
day: 'numeric',
year: 'numeric',
}).format(new Date(value))
} catch {
return null
}
}
function formatYear(value) {
if (!value) return null
try {
return new Intl.DateTimeFormat('en', { year: 'numeric' }).format(new Date(value))
} catch {
return null
}
}
function iconForType(type) {
switch (type) {
case 'first_upload': return 'fa-solid fa-seedling'
case 'first_featured_artwork': return 'fa-solid fa-star'
case 'first_group_release': return 'fa-solid fa-people-group'
case 'biggest_download_spike': return 'fa-solid fa-bolt'
case 'best_performing_work': return 'fa-solid fa-trophy'
case 'most_productive_year': return 'fa-solid fa-calendar-check'
case 'yearly_recap': return 'fa-solid fa-chart-column'
case 'comeback_minor': return 'fa-solid fa-rotate-right'
case 'comeback_major': return 'fa-solid fa-person-walking-arrow-right'
case 'comeback_legendary': return 'fa-solid fa-fire-flame-curved'
case 'upload_streak_3':
case 'upload_streak_6':
case 'upload_streak_12': return 'fa-solid fa-fire'
case 'active_year_streak_3':
case 'active_year_streak_5': return 'fa-solid fa-calendar-days'
case 'before_now': return 'fa-solid fa-arrows-rotate'
case 'era_started': return 'fa-solid fa-flag'
default: return 'fa-solid fa-sparkles'
}
}
function colorForType(type) {
switch (type) {
case 'first_featured_artwork': return { icon: 'text-amber-200', bg: 'bg-amber-400/12', border: 'border-amber-300/20', accent: 'from-amber-400/60' }
case 'best_performing_work': return { icon: 'text-amber-200', bg: 'bg-amber-400/12', border: 'border-amber-300/20', accent: 'from-amber-400/60' }
case 'biggest_download_spike': return { icon: 'text-sky-200', bg: 'bg-sky-400/12', border: 'border-sky-300/20', accent: 'from-sky-400/60' }
case 'first_upload': return { icon: 'text-emerald-200', bg: 'bg-emerald-400/12', border: 'border-emerald-300/20', accent: 'from-emerald-400/60' }
case 'first_group_release': return { icon: 'text-violet-200', bg: 'bg-violet-400/12', border: 'border-violet-300/20', accent: 'from-violet-400/60' }
case 'comeback_minor':
case 'comeback_major':
case 'comeback_legendary': return { icon: 'text-orange-200', bg: 'bg-orange-400/12', border: 'border-orange-300/20', accent: 'from-orange-400/60' }
case 'most_productive_year': return { icon: 'text-teal-200', bg: 'bg-teal-400/12', border: 'border-teal-300/20', accent: 'from-teal-400/60' }
default: return { icon: 'text-sky-200', bg: 'bg-sky-400/12', border: 'border-sky-300/20', accent: 'from-sky-400/60' }
}
}
function milestoneHref(item) {
return item?.artwork?.url || item?.release?.url || null
}
function StatPill({ label, value }) {
if (value === null || value === undefined || value === '') return null
return (
)
}
function EmptyJourneyState({ username, memberSinceYear, yearsOnSkinbase }) {
return (
Creator Journey is just getting started
Public milestones will appear here as @{username} builds more history on Skinbase.
)
}
// ── v2: Era strip ─────────────────────────────────────────────────────────────
const ERA_COLORS = {
early_years: { bg: 'bg-slate-800/60', border: 'border-slate-600/40', text: 'text-slate-300', icon: 'fa-solid fa-seedling', dot: 'bg-slate-400' },
breakthrough: { bg: 'bg-amber-900/30', border: 'border-amber-600/30', text: 'text-amber-200', icon: 'fa-solid fa-star', dot: 'bg-amber-400' },
experimental: { bg: 'bg-violet-900/30', border: 'border-violet-600/30', text: 'text-violet-200', icon: 'fa-solid fa-flask', dot: 'bg-violet-400' },
comeback: { bg: 'bg-emerald-900/30', border: 'border-emerald-600/30', text: 'text-emerald-200', icon: 'fa-solid fa-rotate-right', dot: 'bg-emerald-400' },
current: { bg: 'bg-sky-900/30', border: 'border-sky-600/30', text: 'text-sky-200', icon: 'fa-solid fa-bolt', dot: 'bg-sky-400' },
}
function EraStrip({ eras }) {
if (!eras?.length) return null
return (
Creator Eras
{eras.map((era, i) => {
const style = ERA_COLORS[era.type] ?? ERA_COLORS.current
return (
{era.title}
{era.is_current && (
Now
)}
{formatYear(era.starts_at)}
{era.ends_at ? ` – ${formatYear(era.ends_at)}` : era.is_current ? ' – present' : ''}
{era.description && (
{era.description}
)}
{(era.stats?.uploads_count ?? 0) > 0 && (
{era.stats.uploads_count} upload{era.stats.uploads_count !== 1 ? 's' : ''}
)}
)
})}
)
}
// ── v2: Streaks ──────────────────────────────────────────────────────────────
function StreakBadge({ label, value, active = false }) {
if (!value) return null
return (
)
}
function StreaksSection({ streaks }) {
if (!streaks) return null
const { current_monthly_upload_streak, best_monthly_upload_streak, current_active_year_streak, best_active_year_streak } = streaks
const hasAny = current_monthly_upload_streak > 0 || best_monthly_upload_streak > 0 || best_active_year_streak > 0
if (!hasAny) return null
return (
Creative Streaks
{current_monthly_upload_streak > 0 && (
)}
{best_monthly_upload_streak > 0 && (
)}
{current_active_year_streak > 0 && (
= 3} />
)}
{best_active_year_streak > 0 && (
)}
)
}
// ── Yearly Productivity Chart ────────────────────────────────────────────────
const STATUS_BAR_COLOR = {
breakout: { bar: 'bg-emerald-400', label: 'bg-emerald-400/12 text-emerald-200 border-emerald-400/20' },
steady: { bar: 'bg-sky-400', label: 'bg-sky-400/12 text-sky-200 border-sky-400/20' },
experimental: { bar: 'bg-violet-400', label: 'bg-violet-400/12 text-violet-200 border-violet-400/20' },
comeback: { bar: 'bg-amber-400', label: 'bg-amber-400/12 text-amber-200 border-amber-400/20' },
quiet: { bar: 'bg-slate-500', label: 'bg-slate-700/60 text-slate-400 border-slate-600/30' },
}
function YearlyProductivityChart({ recaps }) {
if (!recaps?.length) return null
// Sort oldest → newest for the chart
const sorted = [...recaps]
.filter((r) => r.metrics?.year && r.metrics?.uploads_count != null)
.sort((a, b) => (a.metrics.year ?? 0) - (b.metrics.year ?? 0))
if (!sorted.length) return null
const maxUploads = Math.max(...sorted.map((r) => r.metrics.uploads_count), 1)
return (
Productivity
Year-by-year upload activity
{Object.entries(STATUS_BAR_COLOR)
.filter(([key]) => sorted.some((r) => (r.metrics?.year_status ?? 'steady') === key))
.map(([key, val]) => (
{key}
))}
{sorted.map((item) => {
const uploads = item.metrics.uploads_count
const pct = Math.max(uploads / maxUploads, uploads > 0 ? 0.018 : 0)
const status = item.metrics?.year_status ?? 'steady'
const colors = STATUS_BAR_COLOR[status] ?? STATUS_BAR_COLOR.steady
const isBest = uploads === maxUploads
return (
{/* Year label */}
{item.metrics.year}
{/* Bar */}
{/* Tooltip on hover */}
{uploads} upload{uploads !== 1 ? 's' : ''}
{(item.metrics?.views ?? 0) > 0 ? ` · ${Number(item.metrics.views).toLocaleString()} views` : ''}
{(item.metrics?.downloads ?? 0) > 0 ? ` · ${Number(item.metrics.downloads).toLocaleString()} dl` : ''}
{/* Upload count + badge */}
{uploads}
{isBest && best}
{(item.metrics?.featured_count ?? 0) > 0 && !isBest && (
{item.metrics.featured_count}
)}
)
})}
{/* Summary footer */}
{sorted.length} active years
{sorted.reduce((s, r) => s + r.metrics.uploads_count, 0).toLocaleString()} total uploads
{Number(sorted.reduce((s, r) => s + (r.metrics.views ?? 0), 0)).toLocaleString()} total views
)
}
// ── v2: Growth & Evolution ───────────────────────────────────────────────────
const RELATION_LABELS = {
remake_of: 'Remake',
remaster_of: 'Remaster',
revision_of: 'Revision',
inspired_by: 'Inspired by own work',
variation_of: 'Variation',
}
function EvolutionSection({ evolution }) {
if (!evolution?.length) return null
return (
Growth & Evolution
Then & Now
{evolution.map((item) => (
))}
)
}
export default function CreatorJourneySection({ journey, username }) {
const summary = journey?.summary ?? {}
const highlights = Array.isArray(journey?.highlights) ? journey.highlights : []
const timeline = Array.isArray(journey?.timeline) ? journey.timeline.slice(0, 6) : []
const recaps = Array.isArray(journey?.yearly_recaps) ? journey.yearly_recaps.slice(0, 3) : []
const allRecaps = Array.isArray(journey?.yearly_recaps) ? journey.yearly_recaps : []
const eras = Array.isArray(journey?.eras) ? journey.eras : []
const evolution = Array.isArray(journey?.evolution) ? journey.evolution : []
const streaks = journey?.streaks ?? null
const latestMilestone = summary.latest_milestone ?? null
const available = !!summary.available
if (!available) {
return (
Creator Journey
A profile built as a story, not only a feed
)
}
return (
Creator Journey
A profile shaped by milestones, turning points, and yearly chapters.
{latestMilestone && (
Latest moment: {latestMilestone.title}
{latestMilestone.headline ? ` - ${latestMilestone.headline}` : ''}
)}
{/* ── v2: Era strip ── */}
{highlights.length > 0 && (
{highlights.map((item) => {
const href = milestoneHref(item)
return (
{(() => { const c = colorForType(item.type); return })()
}
{item.title}
{item.headline || item.value}
{item.value && (
{item.value}
)}
{item.summary && (
{item.summary}
)}
{href && (
Open source moment
)}
)
})}
)}
Timeline
Important creator milestones
{timeline.map((item, index) => {
const href = milestoneHref(item)
return (
{index < timeline.length - 1 &&
}
{item.title}
{formatDate(item.occurred_at) && (
{formatDate(item.occurred_at)}
)}
{item.headline &&
{item.headline}
}
{item.summary &&
{item.summary}
}
{href && (
View linked work
)}
)
})}
Yearly recap
Recent chapters
{recaps.map((item) => {
const status = item.metrics?.year_status
const statusColors = {
breakout: 'bg-emerald-400/12 text-emerald-200',
steady: 'bg-sky-400/12 text-sky-200',
experimental: 'bg-violet-400/12 text-violet-200',
comeback: 'bg-amber-400/12 text-amber-200',
quiet: 'bg-slate-700 text-slate-400',
}
return (
{item.value}
{status && (
{status}
)}
{item.headline}
{item.summary}
{(item.metrics?.featured_count ?? 0) > 0 && (
)}
{item.metrics?.top_category && (
)}
)
})}
{/* ── v2: Streaks ── */}
{/* ── Yearly productivity chart ── */}
{/* ── v2: Growth & Evolution ── */}
)
}