{"version":1,"id":"glass-showcase","framework":"next","kind":"block","files":[{"path":"src/blocks/gfa/glass-showcase.tsx","content":"\"use client\";\n\nimport { useState } from \"react\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n} from \"motion/react\";\nimport { EASE_OUT, SPRING_PRESS } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\nexport type GlassShowcaseItem = {\n  id: string;\n  title: string;\n  description: string;\n  imageSrc: string;\n  imageAlt?: string;\n};\n\nexport type GlassShowcaseProps = {\n  items: GlassShowcaseItem[];\n  defaultActiveId?: string;\n  className?: string;\n  /** Compact embed for gallery cards */\n  preview?: boolean;\n};\n\nconst APPLE_SANS =\n  \"font-[system-ui,-apple-system,BlinkMacSystemFont,'SF_Pro_Text','SF_Pro_Display',sans-serif]\";\n\nconst SMOOTH = {\n  duration: 0.5,\n  ease: EASE_OUT,\n} as const;\n\nexport const GLASS_SHOWCASE_DEMO_ITEMS: GlassShowcaseItem[] = [\n  {\n    id: \"northpeak\",\n    title: \"Northpeak\",\n    description:\n      \"Editorial product stories shaped around altitude, craft, and quiet outdoor rituals.\",\n    imageSrc:\n      \"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=1600&q=80\",\n    imageAlt: \"Mountain landscape\",\n  },\n  {\n    id: \"studio-k\",\n    title: \"Studio K\",\n    description:\n      \"Studio systems for brands that need precise type, calm spacing, and tactile interface detail.\",\n    imageSrc:\n      \"https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?auto=format&fit=crop&w=1600&q=80\",\n    imageAlt: \"Abstract studio forms\",\n  },\n  {\n    id: \"arcade\",\n    title: \"Arcade\",\n    description:\n      \"Playful motion language for launches — punchy color, fast loops, and memorable micro-interactions.\",\n    imageSrc:\n      \"https://images.unsplash.com/photo-1550745165-9bc0b252726f?auto=format&fit=crop&w=1600&q=80\",\n    imageAlt: \"Retro arcade lights\",\n  },\n  {\n    id: \"lumen\",\n    title: \"Lumen\",\n    description:\n      \"Atmospheric visuals and subtle motion language focused on light, depth, and immersive storytelling for modern creative projects.\",\n    imageSrc:\n      \"https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=1600&q=80\",\n    imageAlt: \"Portrait with soft light\",\n  },\n  {\n    id: \"kairo\",\n    title: \"Kairo\",\n    description:\n      \"Time-aware interfaces that shift tone through the day — soft mornings, sharp evenings, measured focus.\",\n    imageSrc:\n      \"https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1600&q=80\",\n    imageAlt: \"Night mountains\",\n  },\n];\n\nfunction padIndex(n: number, total: number) {\n  return `${String(n).padStart(2, \"0\")} / ${String(total).padStart(2, \"0\")}`;\n}\n\nexport function GlassShowcase({\n  items,\n  defaultActiveId,\n  className,\n  preview = false,\n}: GlassShowcaseProps) {\n  const reduceMotion = useReducedMotion();\n  const initialId =\n    defaultActiveId && items.some((i) => i.id === defaultActiveId)\n      ? defaultActiveId\n      : (items[3]?.id ?? items[0]?.id);\n  const [activeId, setActiveId] = useState(initialId);\n  const active = items.find((i) => i.id === activeId) ?? items[0];\n  const total = items.length;\n\n  if (!active) return null;\n\n  return (\n    <div\n      data-gfa-glass-showcase\n      className={cn(\n        APPLE_SANS,\n        \"relative isolate w-full overflow-hidden\",\n        preview\n          ? \"aspect-[16/10] min-h-0 rounded-[1.05rem]\"\n          : \"aspect-[16/10] min-h-[22rem] rounded-[1.75rem]\",\n        className,\n      )}\n    >\n      <div className=\"absolute inset-0 overflow-hidden\">\n        <AnimatePresence initial={false}>\n          <motion.div\n            key={active.id}\n            className=\"absolute inset-0\"\n            initial={reduceMotion ? false : { opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={\n              reduceMotion ? { duration: 0 } : { duration: 0.7, ease: EASE_OUT }\n            }\n          >\n            {/* eslint-disable-next-line @next/next/no-img-element */}\n            <img\n              src={active.imageSrc}\n              alt=\"\"\n              className=\"absolute inset-0 size-full object-cover\"\n              draggable={false}\n            />\n            <div className=\"absolute inset-0 bg-black/30\" />\n          </motion.div>\n        </AnimatePresence>\n      </div>\n\n      <div\n        className=\"pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,transparent_20%,rgba(0,0,0,0.35)_100%)]\"\n        aria-hidden\n      />\n\n      <div\n        className={cn(\n          \"relative z-10 flex size-full items-center justify-center\",\n          preview ? \"p-4 sm:p-5\" : \"p-5 sm:p-8\",\n        )}\n      >\n        <div\n          className={cn(\n            \"flex w-full flex-col rounded-[1.35rem] shadow-[0_24px_80px_rgba(0,0,0,0.35)]\",\n            \"bg-black/35 backdrop-blur-2xl supports-[backdrop-filter]:bg-black/28\",\n            preview\n              ? \"max-w-[18rem] gap-1.5 p-2\"\n              : \"max-w-[28rem] gap-2 p-2.5\",\n          )}\n        >\n          {items.map((item, index) => {\n            const isActive = item.id === active.id;\n            return (\n              <motion.button\n                key={item.id}\n                type=\"button\"\n                aria-pressed={isActive}\n                onMouseEnter={() => setActiveId(item.id)}\n                onFocus={() => setActiveId(item.id)}\n                onClick={() => setActiveId(item.id)}\n                whileHover={\n                  reduceMotion || isActive ? undefined : { scale: 1.02 }\n                }\n                whileTap={reduceMotion ? undefined : { scale: 0.98 }}\n                transition={SPRING_PRESS}\n                className={cn(\n                  \"flex w-full cursor-pointer text-left outline-none\",\n                  \"rounded-2xl\",\n                  \"focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:ring-offset-2 focus-visible:ring-offset-black/40\",\n                  isActive\n                    ? \"bg-white text-zinc-950 shadow-[0_10px_32px_rgba(0,0,0,0.28)]\"\n                    : \"bg-transparent text-white shadow-none\",\n                )}\n              >\n                <div\n                  className={cn(\n                    \"flex w-full gap-2.5\",\n                    isActive\n                      ? preview\n                        ? \"items-start p-2.5\"\n                        : \"items-start p-3\"\n                      : preview\n                        ? \"items-center px-2.5 py-2\"\n                        : \"items-center px-3 py-2\",\n                  )}\n                >\n                  <div\n                    className={cn(\n                      \"relative shrink-0 overflow-hidden rounded-xl bg-zinc-800\",\n                      isActive\n                        ? preview\n                          ? \"size-11\"\n                          : \"size-14\"\n                        : preview\n                          ? \"size-7\"\n                          : \"size-9\",\n                    )}\n                    style={{\n                      transition: reduceMotion\n                        ? undefined\n                        : \"width 0.5s cubic-bezier(0.16,1,0.3,1), height 0.5s cubic-bezier(0.16,1,0.3,1)\",\n                    }}\n                  >\n                    {/* eslint-disable-next-line @next/next/no-img-element */}\n                    <img\n                      src={item.imageSrc}\n                      alt={item.imageAlt ?? \"\"}\n                      className=\"size-full object-cover\"\n                      draggable={false}\n                    />\n                  </div>\n\n                  <div className=\"min-w-0 flex-1 self-center\">\n                    <div className=\"flex items-center justify-between gap-2\">\n                      <span\n                        className={cn(\n                          \"block truncate font-semibold tracking-tight\",\n                          preview ? \"text-[0.7rem]\" : \"text-[0.78rem]\",\n                        )}\n                      >\n                        {item.title}\n                      </span>\n                      <AnimatePresence initial={false}>\n                        {isActive ? (\n                          <motion.span\n                            key=\"counter\"\n                            initial={\n                              reduceMotion ? false : { opacity: 0, y: -3 }\n                            }\n                            animate={{ opacity: 1, y: 0 }}\n                            exit={{ opacity: 0, y: -3 }}\n                            transition={\n                              reduceMotion ? { duration: 0 } : SMOOTH\n                            }\n                            className={cn(\n                              \"shrink-0 tabular-nums text-zinc-400\",\n                              preview ? \"text-[0.58rem]\" : \"text-[0.65rem]\",\n                            )}\n                          >\n                            {padIndex(index + 1, total)}\n                          </motion.span>\n                        ) : null}\n                      </AnimatePresence>\n                    </div>\n\n                    <AnimatePresence initial={false}>\n                      {isActive ? (\n                        <motion.div\n                          key=\"body\"\n                          initial={\n                            reduceMotion\n                              ? false\n                              : { opacity: 0, height: 0, marginTop: 0 }\n                          }\n                          animate={{\n                            opacity: 1,\n                            height: \"auto\",\n                            marginTop: preview ? 4 : 6,\n                          }}\n                          exit={{ opacity: 0, height: 0, marginTop: 0 }}\n                          transition={reduceMotion ? { duration: 0 } : SMOOTH}\n                          className=\"overflow-hidden\"\n                        >\n                          <p\n                            className={cn(\n                              \"text-zinc-600\",\n                              preview\n                                ? \"line-clamp-2 text-[0.58rem] leading-snug\"\n                                : \"text-[0.68rem] leading-relaxed\",\n                            )}\n                          >\n                            {item.description}\n                          </p>\n                        </motion.div>\n                      ) : null}\n                    </AnimatePresence>\n                  </div>\n                </div>\n              </motion.button>\n            );\n          })}\n        </div>\n      </div>\n    </div>\n  );\n}\n"}],"meta":{"studioName":"GFA Development & Design Studio","siteUrl":"https://ui.gkhn.dev"}}