import Image from 'next/image' import type { ReactNode } from 'react' import { LogosMark } from '@acid-info/logos-ui' import type { TechStackOverviewSection } from '@repo/content/schemas' import { ButtonArrowIcon } from '@/components/ui' import { Link } from '@/i18n/navigation' import { DownloadIcon } from './builder-cta-card' const STACK_THUMBNAILS: Record = { basecamp: '/images/technology-stack/thumb-networking.webp', storage: '/images/technology-stack/thumb-storage.webp', messaging: '/images/technology-stack/thumb-messaging.webp', blockchain: '/images/technology-stack/thumb-blockchain.webp', userModules: '/images/technology-stack/thumb-user-modules.webp', networking: '/images/technology-stack/thumb-networking.webp', foundation: '/images/technology-stack/thumb-foundation.webp', } function formatNetworkingTitle(title: string) { return title.replace(': ', ':\n') } function StackItemCta({ children, icon, className, }: { children: string icon?: ReactNode className: string }) { return ( ) } function HoverStackItem({ title, description, href, target, rel, ctaLabel = 'Learn More', className, labelClassName, ctaIcon, ctaVisibleByDefault = false, details, mobileFeatured = false, desktopAt1367 = false, comingSoon = false, hideCta = false, thumbnailSrc, }: { title: string description?: string href: string target?: '_blank' rel?: 'noopener noreferrer' ctaLabel?: string className: string labelClassName?: string ctaIcon?: ReactNode ctaVisibleByDefault?: boolean details?: ReadonlyArray<{ title: string body: string }> mobileFeatured?: boolean desktopAt1367?: boolean /** * When true the item is rendered as a non-interactive card (no link) and its * CTA shows "Soon" instead of "Learn More". Used for pillars whose * destination is not yet available. */ comingSoon?: boolean /** * When true the item is rendered as a non-interactive card (no link) with no * CTA button at all. Used for pillars that have no destination to link to. */ hideCta?: boolean thumbnailSrc: string }) { const hasDetails = details !== undefined && details.length > 0 const hasMultipleDetails = details !== undefined && details.length > 1 const desktopHoverLarge = desktopAt1367 ? 'min-[1367px]:group-hover/stack-item:-translate-y-24' : 'md:group-hover/stack-item:-translate-y-24' const desktopHoverSmall = desktopAt1367 ? 'min-[1367px]:group-hover/stack-item:-translate-y-8' : 'md:group-hover/stack-item:-translate-y-8' const desktopBlock = desktopAt1367 ? 'min-[1367px]:block' : 'md:block' const desktopHidden = desktopAt1367 ? 'min-[1367px]:hidden' : 'md:hidden' const desktopFlex = desktopAt1367 ? 'min-[1367px]:flex' : 'md:flex' const desktopGrid = desktopAt1367 ? 'min-[1367px]:grid' : 'md:grid' // Hover reveals are desktop-only: in the mobile/tablet layout the details // are already rendered inline, so an unscoped group-hover would paint the // desktop overlay on top of them when a desktop user resizes and hovers. const desktopHoverReveal = desktopAt1367 ? 'min-[1367px]:group-hover/stack-item:opacity-100' : 'md:group-hover/stack-item:opacity-100' const desktopCtaPosition = desktopAt1367 ? 'min-[1367px]:top-3 min-[1367px]:right-3' : 'md:top-3 md:right-3' const desktopContentWidth = hasMultipleDetails ? desktopAt1367 ? 'min-[1367px]:max-w-[290px]' : 'md:max-w-[290px]' : '' const contentHoverOffset = hasMultipleDetails ? desktopHoverLarge : hasDetails ? desktopHoverSmall : '' const stackItemLayoutClass = mobileFeatured ? desktopAt1367 ? 'items-start justify-start p-3 min-[1367px]:items-center min-[1367px]:justify-center min-[1367px]:px-6 min-[1367px]:py-0' : 'items-start justify-start p-3 md:items-center md:justify-center md:px-6 md:py-0' : hasDetails ? desktopAt1367 ? 'items-center justify-between p-1.5 min-[1367px]:justify-center min-[1367px]:px-6 min-[1367px]:py-0' : 'items-center justify-between p-1.5 md:justify-center md:px-6 md:py-0' : desktopAt1367 ? 'items-center justify-center px-3 min-[1367px]:px-6' : 'items-center justify-center px-3 md:px-6' const contentLayoutClass = mobileFeatured ? desktopAt1367 ? 'items-start text-left min-[1367px]:items-center min-[1367px]:text-center' : 'items-start text-left md:items-center md:text-center' : hasDetails ? desktopAt1367 ? 'h-[134px] w-full items-center justify-center px-3 py-8.5 text-center min-[1367px]:h-auto min-[1367px]:w-auto min-[1367px]:px-0 min-[1367px]:py-0' : 'h-[134px] w-full items-center justify-center px-3 py-8.5 text-center md:h-auto md:w-auto md:px-0 md:py-0' : 'items-center' const mobileDescriptionClass = mobileFeatured ? 'text-left' : hasDetails ? 'w-full text-center' : 'text-center' const containerClassName = `group/stack-item relative flex flex-col ${ comingSoon || hideCta ? '' : 'cursor-pointer ' }overflow-hidden rounded-3xl border border-brand-dark-green text-center text-brand-dark-green transition-[border-color] duration-200 ease-out hover:border-brand-dark-green/30 focus-visible:border-brand-dark-green/30 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-dark-green ${stackItemLayoutClass} ${className}` const content = ( <> {hideCta ? null : ( )} className={`absolute top-2.5 right-2.5 z-10 transition-opacity duration-200 ease-out ${ comingSoon ? '' : 'cursor-pointer ' }${desktopCtaPosition} ${ ctaVisibleByDefault ? '' : `pointer-events-none opacity-0 ${desktopHoverReveal} group-focus-visible/stack-item:opacity-100` }`} > {comingSoon ? 'Soon' : ctaLabel} )}
{/* h-[1.15em] = one line of text-subhead-sans, so the mark centers on the FIRST line (looks centered for single-line titles, and top-aligned when the title wraps to multiple lines). */} {/* -translate-y-px seats the mark on the text baseline; centering in the line box alone leaves it dipping slightly below. */} {title} {description ? ( <>

{description}

{/* Collapse the hover description to 0 height until hover so the title sits dead-center in the card (identically across every pillar, incl. the details card). On hover the grid row expands (0fr -> 1fr); because the content block is centered, growing it pushes the title upward and reveals the copy below. The -mt-3 cancels the parent gap-3 while collapsed so the title is truly centered (gap restored on hover via mt-0). */} ) : null}
{hasDetails ? ( <>
{details.map((detail) => (

{detail.title}

))}
) : null} ) if (comingSoon || hideCta) { return
{content}
} return ( {content} ) } export function TechStackDiagram({ data, networkingHref, foundationHref, className, desktopAt1367 = false, }: { data: TechStackOverviewSection networkingHref: string foundationHref: string className?: string desktopAt1367?: boolean }) { const basecampClass = desktopAt1367 ? 'h-[111px] w-full border-brand-dark-green min-[1367px]:h-[196px]' : 'h-[111px] w-full border-brand-dark-green md:h-[196px]' const pillarsGridClass = desktopAt1367 ? 'mt-3 grid grid-cols-2 gap-3 min-[1367px]:grid-cols-4' : 'mt-3 grid grid-cols-2 gap-3 md:grid-cols-4' const pillarClass = desktopAt1367 ? 'h-[258px] w-full min-[1367px]:h-[366px]' : 'h-[258px] w-full md:h-[366px]' const rowClass = desktopAt1367 ? 'h-[196px] w-full min-[1367px]:h-[196px]' : 'h-[196px] w-full md:h-[196px]' return (
{data.basecamp ? ( } ctaVisibleByDefault mobileFeatured desktopAt1367={desktopAt1367} className={basecampClass} thumbnailSrc={STACK_THUMBNAILS.basecamp} /> ) : null}
{data.pillars.map((pillar) => ( ))}
) }