()
| 43 | ); |
| 44 | |
| 45 | export function Timeline() { |
| 46 | const t = useTranslations("timeline"); |
| 47 | const tv = useTranslations("version"); |
| 48 | const locale = useLocale(); |
| 49 | |
| 50 | return ( |
| 51 | <div className="flex flex-col gap-12"> |
| 52 | {/* Layer Legend */} |
| 53 | <div> |
| 54 | <h3 className="mb-3 text-sm font-medium text-[var(--color-text-secondary)]"> |
| 55 | {t("layer_legend")} |
| 56 | </h3> |
| 57 | <div className="flex flex-wrap gap-2"> |
| 58 | {LAYERS.map((layer) => ( |
| 59 | <div key={layer.id} className="flex items-center gap-1.5"> |
| 60 | <span |
| 61 | className={cn("h-3 w-3 rounded-full", LAYER_DOT_BG[layer.id])} |
| 62 | /> |
| 63 | <span className="text-xs font-medium">{layer.label}</span> |
| 64 | </div> |
| 65 | ))} |
| 66 | </div> |
| 67 | </div> |
| 68 | |
| 69 | {/* Vertical Timeline */} |
| 70 | <div className="relative"> |
| 71 | {LEARNING_PATH.map((versionId, index) => { |
| 72 | const meta = VERSION_META[versionId]; |
| 73 | const data = getVersionData(versionId); |
| 74 | if (!meta || !data) return null; |
| 75 | |
| 76 | const isLast = index === LEARNING_PATH.length - 1; |
| 77 | const locPercent = Math.round((data.loc / MAX_LOC) * 100); |
| 78 | |
| 79 | return ( |
| 80 | <div key={versionId} className="relative flex gap-4 pb-8 sm:gap-6"> |
| 81 | {/* Timeline line + dot */} |
| 82 | <div className="flex flex-col items-center"> |
| 83 | <div |
| 84 | className={cn( |
| 85 | "z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full ring-4 ring-[var(--color-bg)] sm:h-10 sm:w-10", |
| 86 | LAYER_DOT_BG[meta.layer] |
| 87 | )} |
| 88 | > |
| 89 | <span className="text-[10px] font-bold text-white sm:text-xs"> |
| 90 | {versionId.replace("s", "").replace("_mini", "m")} |
| 91 | </span> |
| 92 | </div> |
| 93 | {!isLast && ( |
| 94 | <div |
| 95 | className={cn( |
| 96 | "w-0.5 flex-1", |
| 97 | LAYER_LINE_BG[ |
| 98 | VERSION_META[LEARNING_PATH[index + 1]]?.layer || meta.layer |
| 99 | ] |
| 100 | )} |
| 101 | /> |
| 102 | )} |
nothing calls this directly
no test coverage detected