@medialane/ui 0.82.1 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/components/animated-token-media.cjs +100 -0
  2. package/dist/components/animated-token-media.cjs.map +1 -0
  3. package/dist/components/animated-token-media.d.cts +28 -0
  4. package/dist/components/animated-token-media.d.ts +28 -0
  5. package/dist/components/animated-token-media.js +66 -0
  6. package/dist/components/animated-token-media.js.map +1 -0
  7. package/dist/components/asset-card.cjs +14 -13
  8. package/dist/components/asset-card.cjs.map +1 -1
  9. package/dist/components/asset-card.d.cts +5 -1
  10. package/dist/components/asset-card.d.ts +5 -1
  11. package/dist/components/asset-card.js +14 -13
  12. package/dist/components/asset-card.js.map +1 -1
  13. package/dist/components/asset-top-sections.cjs +58 -27
  14. package/dist/components/asset-top-sections.cjs.map +1 -1
  15. package/dist/components/asset-top-sections.d.cts +19 -2
  16. package/dist/components/asset-top-sections.d.ts +19 -2
  17. package/dist/components/asset-top-sections.js +58 -27
  18. package/dist/components/asset-top-sections.js.map +1 -1
  19. package/dist/components/nav-shell.cjs +2 -2
  20. package/dist/components/nav-shell.cjs.map +1 -1
  21. package/dist/components/nav-shell.d.cts +9 -3
  22. package/dist/components/nav-shell.d.ts +9 -3
  23. package/dist/components/nav-shell.js +2 -2
  24. package/dist/components/nav-shell.js.map +1 -1
  25. package/dist/components/token-card.cjs +15 -13
  26. package/dist/components/token-card.cjs.map +1 -1
  27. package/dist/components/token-card.js +15 -13
  28. package/dist/components/token-card.js.map +1 -1
  29. package/dist/data/living-render-collections.cjs +43 -0
  30. package/dist/data/living-render-collections.cjs.map +1 -0
  31. package/dist/data/living-render-collections.d.cts +16 -0
  32. package/dist/data/living-render-collections.d.ts +16 -0
  33. package/dist/data/living-render-collections.js +18 -0
  34. package/dist/data/living-render-collections.js.map +1 -0
  35. package/dist/index.cjs +11 -0
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +4 -1
  38. package/dist/index.d.ts +4 -1
  39. package/dist/index.js +7 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/utils/use-intersection-active.cjs +49 -0
  42. package/dist/utils/use-intersection-active.cjs.map +1 -0
  43. package/dist/utils/use-intersection-active.d.cts +12 -0
  44. package/dist/utils/use-intersection-active.d.ts +12 -0
  45. package/dist/utils/use-intersection-active.js +25 -0
  46. package/dist/utils/use-intersection-active.js.map +1 -0
  47. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n stats,\n}: AssetMediaColumnProps) {\n return (\n <motion.div\n initial={shouldReduce ? false : { scale: 1.0, opacity: 0 }}\n animate={{ scale: 1.02, opacity: 1 }}\n transition={{ duration: 0.6, ease: \"easeOut\" }}\n className=\"overflow-hidden rounded-xl lg:sticky lg:top-16\"\n >\n <div className=\"rounded-2xl overflow-hidden border border-border bg-muted\">\n {image && !imgError ? (\n <Image\n src={image}\n alt={imageAlt}\n width={0}\n height={0}\n sizes=\"(max-width: 1024px) 100vw, 66vw\"\n className=\"w-full h-auto\"\n onError={onImageError}\n crossOrigin=\"anonymous\"\n priority\n />\n ) : (\n fallback\n )}\n </div>\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </motion.div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div>\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-[11px] font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-3xl lg:text-5xl font-bold\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\n/** Single-owner identity (ERC-721) — its own sibling row, not nested inside\n * the collection bar (that reads as if ownership were a collection\n * attribute, 2026-07-05 feedback). ERC-1155 editions use `AssetOwnersPanel`\n * instead (multiple owners). */\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0CU;AAxCV,mBAAkB;AAClB,kBAAiB;AACjB,2BAAuB;AACvB,6BAA+B;AAC/B,uCAAwC;AACxC,2BAA4B;AAC5B,0BAA8B;AAgBvB,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0B;AACxB,SACE;AAAA,IAAC,4BAAO;AAAA,IAAP;AAAA,MACC,SAAS,eAAe,QAAQ,EAAE,OAAO,GAAK,SAAS,EAAE;AAAA,MACzD,SAAS,EAAE,OAAO,MAAM,SAAS,EAAE;AAAA,MACnC,YAAY,EAAE,UAAU,KAAK,MAAM,UAAU;AAAA,MAC7C,WAAU;AAAA,MAEV;AAAA,oDAAC,SAAI,WAAU,6DACZ,mBAAS,CAAC,WACT;AAAA,UAAC,aAAAA;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAK;AAAA,YACL,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,OAAM;AAAA,YACN,WAAU;AAAA,YACV,SAAS;AAAA,YACT,aAAY;AAAA,YACZ,UAAQ;AAAA;AAAA,QACV,IAEA,UAEJ;AAAA,QAEC,SAAS,MAAM,SAAS,IACvB,4CAAC,SAAI,WAAW,mBAAmB,MAAM,WAAW,IAAI,gBAAgB,aAAa,IAClF,gBAAM,IAAI,CAAC,SACV,6CAAC,SAAqB,WAAU,+DAC9B;AAAA,sDAAC,OAAE,WAAU,uBAAuB,eAAK,OAAM;AAAA,UAC/C,6CAAC,SAAI,WAAU,6EACZ;AAAA,iBAAK;AAAA,YACL,KAAK;AAAA,aACR;AAAA,aALQ,KAAK,KAMf,CACD,GACH,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;AAWO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA,wBAAwB;AAAA,EACxB;AAAA,EACA;AACF,GAA0B;AACxB,SACE,6CAAC,SACE;AAAA,cAAU,wBACT,6CAAC,SAAI,WAAU,0CACZ;AAAA,eAAS,4CAAC,oCAAY,QAAgB,MAAK,MAAK,IAAK;AAAA,MACrD,wBACC,6CAAC,UAAK,WAAU,kJACd;AAAA,oDAAC,8BAAO,WAAU,WAAU;AAAA,QAAE;AAAA,SAEhC,IACE;AAAA,OACN,IACE;AAAA,IACH,kBAAkB,gBACjB,4CAAC,SAAI,WAAU,QACb;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,YAAY,UAAU,aAAa;AAAA;AAAA,IACrC,GACF,IACE;AAAA,IACJ,4CAAC,QAAG,WAAU,kCAAkC,gBAAK;AAAA,IACpD,cACC,4CAAC,OAAE,WAAU,sDAAsD,uBAAY,IAC7E;AAAA,KACN;AAEJ;AAWO,SAAS,cAAc,EAAE,cAAc,UAAU,GAAuB;AAC7E,SACE,6CAAC,SAAI,WAAU,2DACb;AAAA,gDAAC,UAAK,mBAAK;AAAA,IACX,4CAAC,YAAAC,SAAA,EAAK,MAAM,WAAW,WAAU,oDAC/B,sDAAC,yCAAe,SAAS,cAAc,GACzC;AAAA,KACF;AAEJ;AAEO,SAAS,kBAAkB,eAAuB,cAAsB;AAC7E,SAAO;AAAA,IACL;AAAA,MACE,OAAO,cAAc,eAAe;AAAA,MACpC,OAAO;AAAA,MACP,MAAM,4CAAC,8BAAO,WAAU,WAAU;AAAA,IACpC;AAAA,IACA;AAAA,MACE,OAAO,aAAa,eAAe;AAAA,MACnC,OAAO;AAAA,MACP,MAAM,4CAAC,6BAAM,WAAU,WAAU;AAAA,IACnC;AAAA,EACF;AACF;","names":["Image","Link"]}
1
+ {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { useIntersectionActive } from \"../utils/use-intersection-active.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string | null;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n /** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */\n onZoom?: () => void;\n /** Resolved animation_url — the live on-chain renderer, if any. */\n animationUrl?: string | null;\n /** Caller-computed eligibility for the living-render treatment. */\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\n/**\n * The platform's one asset media column — borderless, respects the work's\n * real aspect ratio (never forced 1:1), capped to the viewport so it always\n * fits on screen, optional click-to-zoom (foundations §III: image leads, no\n * border clutter).\n *\n * `live`-eligible tokens (a small partner allowlist, see\n * living-render-collections) swap the static image for a sandboxed iframe of\n * the token's own on-chain animation_url once the media scrolls into view —\n * see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.\n */\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n onZoom,\n animationUrl,\n live = false,\n stats,\n}: AssetMediaColumnProps) {\n const [ref, isVisible] = useIntersectionActive<HTMLDivElement>();\n const showLive = live && !!animationUrl && isVisible;\n\n return (\n <div ref={ref} className=\"w-full lg:sticky lg:top-16\">\n {showLive ? (\n <div className=\"w-full overflow-hidden rounded-3xl aspect-square\">\n <iframe\n src={animationUrl!}\n title={imageAlt}\n sandbox=\"allow-scripts\"\n loading=\"lazy\"\n className=\"w-full h-full border-0\"\n />\n </div>\n ) : !image || imgError ? (\n <div className=\"w-full overflow-hidden rounded-3xl\">{fallback}</div>\n ) : onZoom ? (\n <motion.button\n type=\"button\"\n onClick={onZoom}\n aria-label=\"View full image\"\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"group block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none\"\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\n transition duration-300 group-hover:opacity-95 group-active:scale-[0.99]\"\n />\n </motion.button>\n ) : (\n <motion.div\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"block w-full overflow-hidden rounded-3xl\"\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\"\n />\n </motion.div>\n )}\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div>\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-[11px] font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-3xl lg:text-5xl font-bold\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\n/** Single-owner identity (ERC-721) — its own sibling row, not nested inside\n * the collection bar (that reads as if ownership were a collection\n * attribute, 2026-07-05 feedback). ERC-1155 editions use `AssetOwnersPanel`\n * instead (multiple owners). */\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":"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","names":["Link"]}
@@ -2,18 +2,35 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
3
  interface AssetMediaColumnProps {
4
4
  shouldReduce: boolean;
5
- image: string;
5
+ image: string | null;
6
6
  imageAlt: string;
7
7
  imgError: boolean;
8
8
  onImageError: () => void;
9
9
  fallback: React.ReactNode;
10
+ /** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */
11
+ onZoom?: () => void;
12
+ /** Resolved animation_url — the live on-chain renderer, if any. */
13
+ animationUrl?: string | null;
14
+ /** Caller-computed eligibility for the living-render treatment. */
15
+ live?: boolean;
10
16
  stats?: Array<{
11
17
  value: string;
12
18
  label: string;
13
19
  icon: React.ReactNode;
14
20
  }>;
15
21
  }
16
- declare function AssetMediaColumn({ shouldReduce, image, imageAlt, imgError, onImageError, fallback, stats, }: AssetMediaColumnProps): react_jsx_runtime.JSX.Element;
22
+ /**
23
+ * The platform's one asset media column — borderless, respects the work's
24
+ * real aspect ratio (never forced 1:1), capped to the viewport so it always
25
+ * fits on screen, optional click-to-zoom (foundations §III: image leads, no
26
+ * border clutter).
27
+ *
28
+ * `live`-eligible tokens (a small partner allowlist, see
29
+ * living-render-collections) swap the static image for a sandboxed iframe of
30
+ * the token's own on-chain animation_url once the media scrolls into view —
31
+ * see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.
32
+ */
33
+ declare function AssetMediaColumn({ shouldReduce, image, imageAlt, imgError, onImageError, fallback, onZoom, animationUrl, live, stats, }: AssetMediaColumnProps): react_jsx_runtime.JSX.Element;
17
34
  interface AssetHeaderBlockProps {
18
35
  name: string;
19
36
  description?: string | null;
@@ -2,18 +2,35 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
3
  interface AssetMediaColumnProps {
4
4
  shouldReduce: boolean;
5
- image: string;
5
+ image: string | null;
6
6
  imageAlt: string;
7
7
  imgError: boolean;
8
8
  onImageError: () => void;
9
9
  fallback: React.ReactNode;
10
+ /** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */
11
+ onZoom?: () => void;
12
+ /** Resolved animation_url — the live on-chain renderer, if any. */
13
+ animationUrl?: string | null;
14
+ /** Caller-computed eligibility for the living-render treatment. */
15
+ live?: boolean;
10
16
  stats?: Array<{
11
17
  value: string;
12
18
  label: string;
13
19
  icon: React.ReactNode;
14
20
  }>;
15
21
  }
16
- declare function AssetMediaColumn({ shouldReduce, image, imageAlt, imgError, onImageError, fallback, stats, }: AssetMediaColumnProps): react_jsx_runtime.JSX.Element;
22
+ /**
23
+ * The platform's one asset media column — borderless, respects the work's
24
+ * real aspect ratio (never forced 1:1), capped to the viewport so it always
25
+ * fits on screen, optional click-to-zoom (foundations §III: image leads, no
26
+ * border clutter).
27
+ *
28
+ * `live`-eligible tokens (a small partner allowlist, see
29
+ * living-render-collections) swap the static image for a sandboxed iframe of
30
+ * the token's own on-chain animation_url once the media scrolls into view —
31
+ * see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.
32
+ */
33
+ declare function AssetMediaColumn({ shouldReduce, image, imageAlt, imgError, onImageError, fallback, onZoom, animationUrl, live, stats, }: AssetMediaColumnProps): react_jsx_runtime.JSX.Element;
17
34
  interface AssetHeaderBlockProps {
18
35
  name: string;
19
36
  description?: string | null;
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import Image from "next/image";
4
3
  import Link from "next/link";
5
4
  import { motion } from "framer-motion";
5
+ import { useIntersectionActive } from "../utils/use-intersection-active.js";
6
6
  import { AddressDisplay } from "./address-display.js";
7
7
  import { ParentAttributionBanner } from "./parent-attribution-banner.js";
8
8
  import { IpTypeBadge } from "./ip-type-badge.js";
@@ -14,40 +14,71 @@ function AssetMediaColumn({
14
14
  imgError,
15
15
  onImageError,
16
16
  fallback,
17
+ onZoom,
18
+ animationUrl,
19
+ live = false,
17
20
  stats
18
21
  }) {
19
- return /* @__PURE__ */ jsxs(
20
- motion.div,
21
- {
22
- initial: shouldReduce ? false : { scale: 1, opacity: 0 },
23
- animate: { scale: 1.02, opacity: 1 },
24
- transition: { duration: 0.6, ease: "easeOut" },
25
- className: "overflow-hidden rounded-xl lg:sticky lg:top-16",
26
- children: [
27
- /* @__PURE__ */ jsx("div", { className: "rounded-2xl overflow-hidden border border-border bg-muted", children: image && !imgError ? /* @__PURE__ */ jsx(
28
- Image,
22
+ const [ref, isVisible] = useIntersectionActive();
23
+ const showLive = live && !!animationUrl && isVisible;
24
+ return /* @__PURE__ */ jsxs("div", { ref, className: "w-full lg:sticky lg:top-16", children: [
25
+ showLive ? /* @__PURE__ */ jsx("div", { className: "w-full overflow-hidden rounded-3xl aspect-square", children: /* @__PURE__ */ jsx(
26
+ "iframe",
27
+ {
28
+ src: animationUrl,
29
+ title: imageAlt,
30
+ sandbox: "allow-scripts",
31
+ loading: "lazy",
32
+ className: "w-full h-full border-0"
33
+ }
34
+ ) }) : !image || imgError ? /* @__PURE__ */ jsx("div", { className: "w-full overflow-hidden rounded-3xl", children: fallback }) : onZoom ? /* @__PURE__ */ jsx(
35
+ motion.button,
36
+ {
37
+ type: "button",
38
+ onClick: onZoom,
39
+ "aria-label": "View full image",
40
+ initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
41
+ animate: { opacity: 1, scale: 1 },
42
+ transition: { duration: 0.5 },
43
+ className: "group block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none",
44
+ children: /* @__PURE__ */ jsx(
45
+ "img",
29
46
  {
30
47
  src: image,
31
48
  alt: imageAlt,
32
- width: 0,
33
- height: 0,
34
- sizes: "(max-width: 1024px) 100vw, 66vw",
35
- className: "w-full h-auto",
49
+ crossOrigin: "anonymous",
36
50
  onError: onImageError,
51
+ className: "w-full h-auto max-h-[80vh] object-contain\n transition duration-300 group-hover:opacity-95 group-active:scale-[0.99]"
52
+ }
53
+ )
54
+ }
55
+ ) : /* @__PURE__ */ jsx(
56
+ motion.div,
57
+ {
58
+ initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
59
+ animate: { opacity: 1, scale: 1 },
60
+ transition: { duration: 0.5 },
61
+ className: "block w-full overflow-hidden rounded-3xl",
62
+ children: /* @__PURE__ */ jsx(
63
+ "img",
64
+ {
65
+ src: image,
66
+ alt: imageAlt,
37
67
  crossOrigin: "anonymous",
38
- priority: true
68
+ onError: onImageError,
69
+ className: "w-full h-auto max-h-[80vh] object-contain"
39
70
  }
40
- ) : fallback }),
41
- stats && stats.length > 0 ? /* @__PURE__ */ jsx("div", { className: `grid gap-3 mt-4 ${stats.length === 2 ? "grid-cols-2" : "grid-cols-1"}`, children: stats.map((stat) => /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border bg-muted/20 p-4 text-center", children: [
42
- /* @__PURE__ */ jsx("p", { className: "text-2xl font-black", children: stat.value }),
43
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1", children: [
44
- stat.icon,
45
- stat.label
46
- ] })
47
- ] }, stat.label)) }) : null
48
- ]
49
- }
50
- );
71
+ )
72
+ }
73
+ ),
74
+ stats && stats.length > 0 ? /* @__PURE__ */ jsx("div", { className: `grid gap-3 mt-4 ${stats.length === 2 ? "grid-cols-2" : "grid-cols-1"}`, children: stats.map((stat) => /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border bg-muted/20 p-4 text-center", children: [
75
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-black", children: stat.value }),
76
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1", children: [
77
+ stat.icon,
78
+ stat.label
79
+ ] })
80
+ ] }, stat.label)) }) : null
81
+ ] });
51
82
  }
52
83
  function AssetHeaderBlock({
53
84
  name,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n stats,\n}: AssetMediaColumnProps) {\n return (\n <motion.div\n initial={shouldReduce ? false : { scale: 1.0, opacity: 0 }}\n animate={{ scale: 1.02, opacity: 1 }}\n transition={{ duration: 0.6, ease: \"easeOut\" }}\n className=\"overflow-hidden rounded-xl lg:sticky lg:top-16\"\n >\n <div className=\"rounded-2xl overflow-hidden border border-border bg-muted\">\n {image && !imgError ? (\n <Image\n src={image}\n alt={imageAlt}\n width={0}\n height={0}\n sizes=\"(max-width: 1024px) 100vw, 66vw\"\n className=\"w-full h-auto\"\n onError={onImageError}\n crossOrigin=\"anonymous\"\n priority\n />\n ) : (\n fallback\n )}\n </div>\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </motion.div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div>\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-[11px] font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-3xl lg:text-5xl font-bold\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\n/** Single-owner identity (ERC-721) — its own sibling row, not nested inside\n * the collection bar (that reads as if ownership were a collection\n * attribute, 2026-07-05 feedback). ERC-1155 editions use `AssetOwnersPanel`\n * instead (multiple owners). */\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":";AA0CU,cAqBI,YArBJ;AAxCV,OAAO,WAAW;AAClB,OAAO,UAAU;AACjB,SAAS,cAAc;AACvB,SAAS,sBAAsB;AAC/B,SAAS,+BAA+B;AACxC,SAAS,mBAAmB;AAC5B,SAAS,QAAQ,aAAa;AAgBvB,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0B;AACxB,SACE;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,eAAe,QAAQ,EAAE,OAAO,GAAK,SAAS,EAAE;AAAA,MACzD,SAAS,EAAE,OAAO,MAAM,SAAS,EAAE;AAAA,MACnC,YAAY,EAAE,UAAU,KAAK,MAAM,UAAU;AAAA,MAC7C,WAAU;AAAA,MAEV;AAAA,4BAAC,SAAI,WAAU,6DACZ,mBAAS,CAAC,WACT;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAK;AAAA,YACL,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,OAAM;AAAA,YACN,WAAU;AAAA,YACV,SAAS;AAAA,YACT,aAAY;AAAA,YACZ,UAAQ;AAAA;AAAA,QACV,IAEA,UAEJ;AAAA,QAEC,SAAS,MAAM,SAAS,IACvB,oBAAC,SAAI,WAAW,mBAAmB,MAAM,WAAW,IAAI,gBAAgB,aAAa,IAClF,gBAAM,IAAI,CAAC,SACV,qBAAC,SAAqB,WAAU,+DAC9B;AAAA,8BAAC,OAAE,WAAU,uBAAuB,eAAK,OAAM;AAAA,UAC/C,qBAAC,SAAI,WAAU,6EACZ;AAAA,iBAAK;AAAA,YACL,KAAK;AAAA,aACR;AAAA,aALQ,KAAK,KAMf,CACD,GACH,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;AAWO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA,wBAAwB;AAAA,EACxB;AAAA,EACA;AACF,GAA0B;AACxB,SACE,qBAAC,SACE;AAAA,cAAU,wBACT,qBAAC,SAAI,WAAU,0CACZ;AAAA,eAAS,oBAAC,eAAY,QAAgB,MAAK,MAAK,IAAK;AAAA,MACrD,wBACC,qBAAC,UAAK,WAAU,kJACd;AAAA,4BAAC,UAAO,WAAU,WAAU;AAAA,QAAE;AAAA,SAEhC,IACE;AAAA,OACN,IACE;AAAA,IACH,kBAAkB,gBACjB,oBAAC,SAAI,WAAU,QACb;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,YAAY,UAAU,aAAa;AAAA;AAAA,IACrC,GACF,IACE;AAAA,IACJ,oBAAC,QAAG,WAAU,kCAAkC,gBAAK;AAAA,IACpD,cACC,oBAAC,OAAE,WAAU,sDAAsD,uBAAY,IAC7E;AAAA,KACN;AAEJ;AAWO,SAAS,cAAc,EAAE,cAAc,UAAU,GAAuB;AAC7E,SACE,qBAAC,SAAI,WAAU,2DACb;AAAA,wBAAC,UAAK,mBAAK;AAAA,IACX,oBAAC,QAAK,MAAM,WAAW,WAAU,oDAC/B,8BAAC,kBAAe,SAAS,cAAc,GACzC;AAAA,KACF;AAEJ;AAEO,SAAS,kBAAkB,eAAuB,cAAsB;AAC7E,SAAO;AAAA,IACL;AAAA,MACE,OAAO,cAAc,eAAe;AAAA,MACpC,OAAO;AAAA,MACP,MAAM,oBAAC,UAAO,WAAU,WAAU;AAAA,IACpC;AAAA,IACA;AAAA,MACE,OAAO,aAAa,eAAe;AAAA,MACnC,OAAO;AAAA,MACP,MAAM,oBAAC,SAAM,WAAU,WAAU;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { useIntersectionActive } from \"../utils/use-intersection-active.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string | null;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n /** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */\n onZoom?: () => void;\n /** Resolved animation_url — the live on-chain renderer, if any. */\n animationUrl?: string | null;\n /** Caller-computed eligibility for the living-render treatment. */\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\n/**\n * The platform's one asset media column — borderless, respects the work's\n * real aspect ratio (never forced 1:1), capped to the viewport so it always\n * fits on screen, optional click-to-zoom (foundations §III: image leads, no\n * border clutter).\n *\n * `live`-eligible tokens (a small partner allowlist, see\n * living-render-collections) swap the static image for a sandboxed iframe of\n * the token's own on-chain animation_url once the media scrolls into view —\n * see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.\n */\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n onZoom,\n animationUrl,\n live = false,\n stats,\n}: AssetMediaColumnProps) {\n const [ref, isVisible] = useIntersectionActive<HTMLDivElement>();\n const showLive = live && !!animationUrl && isVisible;\n\n return (\n <div ref={ref} className=\"w-full lg:sticky lg:top-16\">\n {showLive ? (\n <div className=\"w-full overflow-hidden rounded-3xl aspect-square\">\n <iframe\n src={animationUrl!}\n title={imageAlt}\n sandbox=\"allow-scripts\"\n loading=\"lazy\"\n className=\"w-full h-full border-0\"\n />\n </div>\n ) : !image || imgError ? (\n <div className=\"w-full overflow-hidden rounded-3xl\">{fallback}</div>\n ) : onZoom ? (\n <motion.button\n type=\"button\"\n onClick={onZoom}\n aria-label=\"View full image\"\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"group block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none\"\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\n transition duration-300 group-hover:opacity-95 group-active:scale-[0.99]\"\n />\n </motion.button>\n ) : (\n <motion.div\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"block w-full overflow-hidden rounded-3xl\"\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\"\n />\n </motion.div>\n )}\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div>\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-[11px] font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-3xl lg:text-5xl font-bold\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\n/** Single-owner identity (ERC-721) — its own sibling row, not nested inside\n * the collection bar (that reads as if ownership were a collection\n * attribute, 2026-07-05 feedback). ERC-1155 editions use `AssetOwnersPanel`\n * instead (multiple owners). */\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":"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","names":[]}
@@ -87,7 +87,7 @@ function NavIconButton({ onClick, className, indicator, children, ...rest }) {
87
87
  );
88
88
  }
89
89
  const NavWalletTrigger = React.forwardRef(
90
- function NavWalletTrigger2({ onClick, className, connected = false, ...rest }, ref) {
90
+ function NavWalletTrigger2({ onClick, className, connected = false, iconSrc, ...rest }, ref) {
91
91
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
92
92
  "button",
93
93
  {
@@ -105,7 +105,7 @@ const NavWalletTrigger = React.forwardRef(
105
105
  ),
106
106
  children: [
107
107
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-nav-wallet-ring", "aria-hidden": "true" }),
108
- connected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.User, { className: "h-3.5 w-3.5" })
108
+ connected && (iconSrc ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: iconSrc, alt: "", width: 16, height: 16, className: "h-4 w-4 shrink-0 rounded-full" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.User, { className: "h-3.5 w-3.5" }))
109
109
  ]
110
110
  }
111
111
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/nav-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { User } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Header buttons ────────────────────────────────────────────────────────────\n\nexport interface NavBrandButtonProps {\n /** Defaults to opening the nav command menu (`ml:nav-open`). */\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Path to the brand icon image (the app's real icon asset). */\n iconSrc?: string;\n}\n\n/**\n * The main header trigger: brand mark + menu glyph in one quiet glass pill.\n * Mobile-first — a single ≥44px tap target instead of two small adjacent icons.\n * With no `onClick`, it opens the NavCommandMenu mounted elsewhere in the app.\n */\nexport function NavBrandButton({ onClick, className, iconSrc = \"/icon.png\", ...rest }: NavBrandButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick ?? (() => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)))}\n aria-label={rest[\"aria-label\"] ?? \"Open navigation\"}\n className={cn(\n \"group flex h-11 items-center gap-2 rounded-full\",\n \"bg-background/10 pl-2.5 pr-3 backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={iconSrc} alt=\"Medialane\" width={30} height={30} className=\"h-[30px] w-[30px] shrink-0\" />\n <span className=\"flex flex-col gap-[5px]\" aria-hidden=\"true\">\n <span className=\"h-[1.5px] w-4 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n <span className=\"h-[1.5px] w-2.5 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n </span>\n </button>\n );\n}\n\nexport interface NavIconButtonProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\": string;\n /** Show a small brand-colored status dot (e.g. wallet connected). */\n indicator?: boolean;\n children: React.ReactNode;\n}\n\n/**\n * Circular glass header button — the right-side counterpart to NavBrandButton\n * (wallet / account trigger).\n */\nexport function NavIconButton({ onClick, className, indicator, children, ...rest }: NavIconButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"]}\n className={cn(\n \"relative flex h-11 w-11 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {children}\n {indicator && (\n <span className=\"absolute right-[3px] top-[3px] h-2.5 w-2.5 rounded-full border-2 border-background bg-brand-blue\" />\n )}\n </button>\n );\n}\n\nexport interface NavWalletTriggerProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Whether a wallet is currently connected. */\n connected?: boolean;\n}\n\n/**\n * The right-side counterpart to `NavBrandButton` for a wallet/account entry\n * point: the same glass pill material, with a thin brand-gradient ring\n * traced around the rim instead of a static icon. Not connected — the ring\n * rotates slowly (an ambient \"something opens here\" cue) around an empty\n * center, no glyph, no placeholder avatar. Connected — the ring settles to a\n * static accent and a plain `User` glyph fills the center (never a\n * gradient-filled avatar — this button doesn't know the wallet's real\n * identity, only whether one is attached).\n */\nexport const NavWalletTrigger = React.forwardRef<HTMLButtonElement, NavWalletTriggerProps>(\n function NavWalletTrigger({ onClick, className, connected = false, ...rest }, ref) {\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"] ?? (connected ? \"Account\" : \"Connect wallet\")}\n className={cn(\n \"ml-nav-wallet-trigger relative flex h-8 w-8 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n connected && \"ml-nav-wallet-trigger--connected\",\n className\n )}\n >\n <span className=\"ml-nav-wallet-ring\" aria-hidden=\"true\" />\n {connected && <User className=\"h-3.5 w-3.5\" />}\n </button>\n );\n }\n);\n\n// ── Account sheet ─────────────────────────────────────────────────────────────\n\nconst ML_ACCOUNT_OPEN = \"ml:account-open\";\nconst ML_ACCOUNT_CLOSE = \"ml:account-close\";\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavAccountSheet() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_CLOSE)),\n };\n}\n\nexport interface NavAccountSheetProps {\n /** The app's account/wallet panel content — identity, network, disconnect, etc. */\n children: React.ReactNode;\n}\n\n/**\n * The wallet-side surface: a centered, backdrop-blurred panel — the exact\n * same overlay + card treatment as `NavCommandMenu` (`.nav-canvas-overlay`,\n * `bg-background/90 backdrop-blur-2xl backdrop-saturate-150`), so every\n * global panel in the header reads as one system. No built-in title — this\n * is an account/wallet card, not a menu; content is entirely app-owned.\n *\n * Opens via `useNavAccountSheet().open()`. Mutually exclusive with the\n * command menu: opening one closes the other, and the shared `ml:nav-close`\n * event (fired by `useNavCommandMenu().close()`) closes this too.\n */\nexport function NavAccountSheet({ children }: NavAccountSheetProps) {\n const [open, setOpen] = React.useState(false);\n\n React.useEffect(() => {\n const onOpen = () => {\n document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE));\n setOpen(true);\n };\n const onClose = () => setOpen(false);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.addEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.addEventListener(ML_NAV_OPEN, onClose);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.removeEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.removeEventListener(ML_NAV_OPEN, onClose);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, []);\n\n return (\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur — identical to NavCommandMenu's */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Panel — centered on desktop, bottom sheet on mobile, same as NavCommandMenu */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className=\"relative w-full max-w-[380px] overflow-hidden rounded-[20px] border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n <button\n onClick={() => setOpen(false)}\n className=\"absolute right-3 top-3 rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-muted/50 hover:text-foreground\"\n aria-label=\"Close\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n <div className=\"px-4 pb-4 pt-5 sm:pt-6\">{children}</div>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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","names":["NavWalletTrigger"]}
1
+ {"version":3,"sources":["../../src/components/nav-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { User } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Header buttons ────────────────────────────────────────────────────────────\n\nexport interface NavBrandButtonProps {\n /** Defaults to opening the nav command menu (`ml:nav-open`). */\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Path to the brand icon image (the app's real icon asset). */\n iconSrc?: string;\n}\n\n/**\n * The main header trigger: brand mark + menu glyph in one quiet glass pill.\n * Mobile-first — a single ≥44px tap target instead of two small adjacent icons.\n * With no `onClick`, it opens the NavCommandMenu mounted elsewhere in the app.\n */\nexport function NavBrandButton({ onClick, className, iconSrc = \"/icon.png\", ...rest }: NavBrandButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick ?? (() => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)))}\n aria-label={rest[\"aria-label\"] ?? \"Open navigation\"}\n className={cn(\n \"group flex h-11 items-center gap-2 rounded-full\",\n \"bg-background/10 pl-2.5 pr-3 backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={iconSrc} alt=\"Medialane\" width={30} height={30} className=\"h-[30px] w-[30px] shrink-0\" />\n <span className=\"flex flex-col gap-[5px]\" aria-hidden=\"true\">\n <span className=\"h-[1.5px] w-4 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n <span className=\"h-[1.5px] w-2.5 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n </span>\n </button>\n );\n}\n\nexport interface NavIconButtonProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\": string;\n /** Show a small brand-colored status dot (e.g. wallet connected). */\n indicator?: boolean;\n children: React.ReactNode;\n}\n\n/**\n * Circular glass header button — the right-side counterpart to NavBrandButton\n * (wallet / account trigger).\n */\nexport function NavIconButton({ onClick, className, indicator, children, ...rest }: NavIconButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"]}\n className={cn(\n \"relative flex h-11 w-11 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {children}\n {indicator && (\n <span className=\"absolute right-[3px] top-[3px] h-2.5 w-2.5 rounded-full border-2 border-background bg-brand-blue\" />\n )}\n </button>\n );\n}\n\nexport interface NavWalletTriggerProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Whether a wallet is currently connected. */\n connected?: boolean;\n /**\n * The connected wallet's own icon (e.g. Argent/Braavos/Cartridge). Optional —\n * apps with no single \"connected wallet\" identity to show (e.g. an\n * email/social-login app) simply omit it and get the plain glyph below.\n */\n iconSrc?: string;\n}\n\n/**\n * The right-side counterpart to `NavBrandButton` for a wallet/account entry\n * point: the same glass pill material, with a thin brand-gradient ring\n * traced around the rim instead of a static icon. Not connected — the ring\n * rotates slowly (an ambient \"something opens here\" cue) around an empty\n * center, no glyph, no placeholder avatar. Connected — the ring settles to a\n * static accent and shows the connected wallet's own icon via `iconSrc` when\n * the caller has one; otherwise a plain `User` glyph (never a fabricated\n * generic avatar).\n */\nexport const NavWalletTrigger = React.forwardRef<HTMLButtonElement, NavWalletTriggerProps>(\n function NavWalletTrigger({ onClick, className, connected = false, iconSrc, ...rest }, ref) {\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"] ?? (connected ? \"Account\" : \"Connect wallet\")}\n className={cn(\n \"ml-nav-wallet-trigger relative flex h-8 w-8 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n connected && \"ml-nav-wallet-trigger--connected\",\n className\n )}\n >\n <span className=\"ml-nav-wallet-ring\" aria-hidden=\"true\" />\n {connected && (iconSrc\n ? <img src={iconSrc} alt=\"\" width={16} height={16} className=\"h-4 w-4 shrink-0 rounded-full\" />\n : <User className=\"h-3.5 w-3.5\" />)}\n </button>\n );\n }\n);\n\n// ── Account sheet ─────────────────────────────────────────────────────────────\n\nconst ML_ACCOUNT_OPEN = \"ml:account-open\";\nconst ML_ACCOUNT_CLOSE = \"ml:account-close\";\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavAccountSheet() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_CLOSE)),\n };\n}\n\nexport interface NavAccountSheetProps {\n /** The app's account/wallet panel content — identity, network, disconnect, etc. */\n children: React.ReactNode;\n}\n\n/**\n * The wallet-side surface: a centered, backdrop-blurred panel — the exact\n * same overlay + card treatment as `NavCommandMenu` (`.nav-canvas-overlay`,\n * `bg-background/90 backdrop-blur-2xl backdrop-saturate-150`), so every\n * global panel in the header reads as one system. No built-in title — this\n * is an account/wallet card, not a menu; content is entirely app-owned.\n *\n * Opens via `useNavAccountSheet().open()`. Mutually exclusive with the\n * command menu: opening one closes the other, and the shared `ml:nav-close`\n * event (fired by `useNavCommandMenu().close()`) closes this too.\n */\nexport function NavAccountSheet({ children }: NavAccountSheetProps) {\n const [open, setOpen] = React.useState(false);\n\n React.useEffect(() => {\n const onOpen = () => {\n document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE));\n setOpen(true);\n };\n const onClose = () => setOpen(false);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.addEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.addEventListener(ML_NAV_OPEN, onClose);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.removeEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.removeEventListener(ML_NAV_OPEN, onClose);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, []);\n\n return (\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur — identical to NavCommandMenu's */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Panel — centered on desktop, bottom sheet on mobile, same as NavCommandMenu */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className=\"relative w-full max-w-[380px] overflow-hidden rounded-[20px] border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n <button\n onClick={() => setOpen(false)}\n className=\"absolute right-3 top-3 rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-muted/50 hover:text-foreground\"\n aria-label=\"Close\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n <div className=\"px-4 pb-4 pt-5 sm:pt-6\">{children}</div>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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","names":["NavWalletTrigger"]}
@@ -34,6 +34,12 @@ interface NavWalletTriggerProps {
34
34
  "aria-label"?: string;
35
35
  /** Whether a wallet is currently connected. */
36
36
  connected?: boolean;
37
+ /**
38
+ * The connected wallet's own icon (e.g. Argent/Braavos/Cartridge). Optional —
39
+ * apps with no single "connected wallet" identity to show (e.g. an
40
+ * email/social-login app) simply omit it and get the plain glyph below.
41
+ */
42
+ iconSrc?: string;
37
43
  }
38
44
  /**
39
45
  * The right-side counterpart to `NavBrandButton` for a wallet/account entry
@@ -41,9 +47,9 @@ interface NavWalletTriggerProps {
41
47
  * traced around the rim instead of a static icon. Not connected — the ring
42
48
  * rotates slowly (an ambient "something opens here" cue) around an empty
43
49
  * center, no glyph, no placeholder avatar. Connected — the ring settles to a
44
- * static accent and a plain `User` glyph fills the center (never a
45
- * gradient-filled avatar this button doesn't know the wallet's real
46
- * identity, only whether one is attached).
50
+ * static accent and shows the connected wallet's own icon via `iconSrc` when
51
+ * the caller has one; otherwise a plain `User` glyph (never a fabricated
52
+ * generic avatar).
47
53
  */
48
54
  declare const NavWalletTrigger: React.ForwardRefExoticComponent<NavWalletTriggerProps & React.RefAttributes<HTMLButtonElement>>;
49
55
  declare function useNavAccountSheet(): {
@@ -34,6 +34,12 @@ interface NavWalletTriggerProps {
34
34
  "aria-label"?: string;
35
35
  /** Whether a wallet is currently connected. */
36
36
  connected?: boolean;
37
+ /**
38
+ * The connected wallet's own icon (e.g. Argent/Braavos/Cartridge). Optional —
39
+ * apps with no single "connected wallet" identity to show (e.g. an
40
+ * email/social-login app) simply omit it and get the plain glyph below.
41
+ */
42
+ iconSrc?: string;
37
43
  }
38
44
  /**
39
45
  * The right-side counterpart to `NavBrandButton` for a wallet/account entry
@@ -41,9 +47,9 @@ interface NavWalletTriggerProps {
41
47
  * traced around the rim instead of a static icon. Not connected — the ring
42
48
  * rotates slowly (an ambient "something opens here" cue) around an empty
43
49
  * center, no glyph, no placeholder avatar. Connected — the ring settles to a
44
- * static accent and a plain `User` glyph fills the center (never a
45
- * gradient-filled avatar this button doesn't know the wallet's real
46
- * identity, only whether one is attached).
50
+ * static accent and shows the connected wallet's own icon via `iconSrc` when
51
+ * the caller has one; otherwise a plain `User` glyph (never a fabricated
52
+ * generic avatar).
47
53
  */
48
54
  declare const NavWalletTrigger: React.ForwardRefExoticComponent<NavWalletTriggerProps & React.RefAttributes<HTMLButtonElement>>;
49
55
  declare function useNavAccountSheet(): {
@@ -50,7 +50,7 @@ function NavIconButton({ onClick, className, indicator, children, ...rest }) {
50
50
  );
51
51
  }
52
52
  const NavWalletTrigger = React.forwardRef(
53
- function NavWalletTrigger2({ onClick, className, connected = false, ...rest }, ref) {
53
+ function NavWalletTrigger2({ onClick, className, connected = false, iconSrc, ...rest }, ref) {
54
54
  return /* @__PURE__ */ jsxs(
55
55
  "button",
56
56
  {
@@ -68,7 +68,7 @@ const NavWalletTrigger = React.forwardRef(
68
68
  ),
69
69
  children: [
70
70
  /* @__PURE__ */ jsx("span", { className: "ml-nav-wallet-ring", "aria-hidden": "true" }),
71
- connected && /* @__PURE__ */ jsx(User, { className: "h-3.5 w-3.5" })
71
+ connected && (iconSrc ? /* @__PURE__ */ jsx("img", { src: iconSrc, alt: "", width: 16, height: 16, className: "h-4 w-4 shrink-0 rounded-full" }) : /* @__PURE__ */ jsx(User, { className: "h-3.5 w-3.5" }))
72
72
  ]
73
73
  }
74
74
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/nav-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { User } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Header buttons ────────────────────────────────────────────────────────────\n\nexport interface NavBrandButtonProps {\n /** Defaults to opening the nav command menu (`ml:nav-open`). */\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Path to the brand icon image (the app's real icon asset). */\n iconSrc?: string;\n}\n\n/**\n * The main header trigger: brand mark + menu glyph in one quiet glass pill.\n * Mobile-first — a single ≥44px tap target instead of two small adjacent icons.\n * With no `onClick`, it opens the NavCommandMenu mounted elsewhere in the app.\n */\nexport function NavBrandButton({ onClick, className, iconSrc = \"/icon.png\", ...rest }: NavBrandButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick ?? (() => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)))}\n aria-label={rest[\"aria-label\"] ?? \"Open navigation\"}\n className={cn(\n \"group flex h-11 items-center gap-2 rounded-full\",\n \"bg-background/10 pl-2.5 pr-3 backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={iconSrc} alt=\"Medialane\" width={30} height={30} className=\"h-[30px] w-[30px] shrink-0\" />\n <span className=\"flex flex-col gap-[5px]\" aria-hidden=\"true\">\n <span className=\"h-[1.5px] w-4 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n <span className=\"h-[1.5px] w-2.5 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n </span>\n </button>\n );\n}\n\nexport interface NavIconButtonProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\": string;\n /** Show a small brand-colored status dot (e.g. wallet connected). */\n indicator?: boolean;\n children: React.ReactNode;\n}\n\n/**\n * Circular glass header button — the right-side counterpart to NavBrandButton\n * (wallet / account trigger).\n */\nexport function NavIconButton({ onClick, className, indicator, children, ...rest }: NavIconButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"]}\n className={cn(\n \"relative flex h-11 w-11 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {children}\n {indicator && (\n <span className=\"absolute right-[3px] top-[3px] h-2.5 w-2.5 rounded-full border-2 border-background bg-brand-blue\" />\n )}\n </button>\n );\n}\n\nexport interface NavWalletTriggerProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Whether a wallet is currently connected. */\n connected?: boolean;\n}\n\n/**\n * The right-side counterpart to `NavBrandButton` for a wallet/account entry\n * point: the same glass pill material, with a thin brand-gradient ring\n * traced around the rim instead of a static icon. Not connected — the ring\n * rotates slowly (an ambient \"something opens here\" cue) around an empty\n * center, no glyph, no placeholder avatar. Connected — the ring settles to a\n * static accent and a plain `User` glyph fills the center (never a\n * gradient-filled avatar — this button doesn't know the wallet's real\n * identity, only whether one is attached).\n */\nexport const NavWalletTrigger = React.forwardRef<HTMLButtonElement, NavWalletTriggerProps>(\n function NavWalletTrigger({ onClick, className, connected = false, ...rest }, ref) {\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"] ?? (connected ? \"Account\" : \"Connect wallet\")}\n className={cn(\n \"ml-nav-wallet-trigger relative flex h-8 w-8 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n connected && \"ml-nav-wallet-trigger--connected\",\n className\n )}\n >\n <span className=\"ml-nav-wallet-ring\" aria-hidden=\"true\" />\n {connected && <User className=\"h-3.5 w-3.5\" />}\n </button>\n );\n }\n);\n\n// ── Account sheet ─────────────────────────────────────────────────────────────\n\nconst ML_ACCOUNT_OPEN = \"ml:account-open\";\nconst ML_ACCOUNT_CLOSE = \"ml:account-close\";\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavAccountSheet() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_CLOSE)),\n };\n}\n\nexport interface NavAccountSheetProps {\n /** The app's account/wallet panel content — identity, network, disconnect, etc. */\n children: React.ReactNode;\n}\n\n/**\n * The wallet-side surface: a centered, backdrop-blurred panel — the exact\n * same overlay + card treatment as `NavCommandMenu` (`.nav-canvas-overlay`,\n * `bg-background/90 backdrop-blur-2xl backdrop-saturate-150`), so every\n * global panel in the header reads as one system. No built-in title — this\n * is an account/wallet card, not a menu; content is entirely app-owned.\n *\n * Opens via `useNavAccountSheet().open()`. Mutually exclusive with the\n * command menu: opening one closes the other, and the shared `ml:nav-close`\n * event (fired by `useNavCommandMenu().close()`) closes this too.\n */\nexport function NavAccountSheet({ children }: NavAccountSheetProps) {\n const [open, setOpen] = React.useState(false);\n\n React.useEffect(() => {\n const onOpen = () => {\n document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE));\n setOpen(true);\n };\n const onClose = () => setOpen(false);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.addEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.addEventListener(ML_NAV_OPEN, onClose);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.removeEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.removeEventListener(ML_NAV_OPEN, onClose);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, []);\n\n return (\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur — identical to NavCommandMenu's */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Panel — centered on desktop, bottom sheet on mobile, same as NavCommandMenu */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className=\"relative w-full max-w-[380px] overflow-hidden rounded-[20px] border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n <button\n onClick={() => setOpen(false)}\n className=\"absolute right-3 top-3 rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-muted/50 hover:text-foreground\"\n aria-label=\"Close\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n <div className=\"px-4 pb-4 pt-5 sm:pt-6\">{children}</div>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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","names":["NavWalletTrigger"]}
1
+ {"version":3,"sources":["../../src/components/nav-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { User } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Header buttons ────────────────────────────────────────────────────────────\n\nexport interface NavBrandButtonProps {\n /** Defaults to opening the nav command menu (`ml:nav-open`). */\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Path to the brand icon image (the app's real icon asset). */\n iconSrc?: string;\n}\n\n/**\n * The main header trigger: brand mark + menu glyph in one quiet glass pill.\n * Mobile-first — a single ≥44px tap target instead of two small adjacent icons.\n * With no `onClick`, it opens the NavCommandMenu mounted elsewhere in the app.\n */\nexport function NavBrandButton({ onClick, className, iconSrc = \"/icon.png\", ...rest }: NavBrandButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick ?? (() => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)))}\n aria-label={rest[\"aria-label\"] ?? \"Open navigation\"}\n className={cn(\n \"group flex h-11 items-center gap-2 rounded-full\",\n \"bg-background/10 pl-2.5 pr-3 backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={iconSrc} alt=\"Medialane\" width={30} height={30} className=\"h-[30px] w-[30px] shrink-0\" />\n <span className=\"flex flex-col gap-[5px]\" aria-hidden=\"true\">\n <span className=\"h-[1.5px] w-4 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n <span className=\"h-[1.5px] w-2.5 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n </span>\n </button>\n );\n}\n\nexport interface NavIconButtonProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\": string;\n /** Show a small brand-colored status dot (e.g. wallet connected). */\n indicator?: boolean;\n children: React.ReactNode;\n}\n\n/**\n * Circular glass header button — the right-side counterpart to NavBrandButton\n * (wallet / account trigger).\n */\nexport function NavIconButton({ onClick, className, indicator, children, ...rest }: NavIconButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"]}\n className={cn(\n \"relative flex h-11 w-11 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {children}\n {indicator && (\n <span className=\"absolute right-[3px] top-[3px] h-2.5 w-2.5 rounded-full border-2 border-background bg-brand-blue\" />\n )}\n </button>\n );\n}\n\nexport interface NavWalletTriggerProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n /** Whether a wallet is currently connected. */\n connected?: boolean;\n /**\n * The connected wallet's own icon (e.g. Argent/Braavos/Cartridge). Optional —\n * apps with no single \"connected wallet\" identity to show (e.g. an\n * email/social-login app) simply omit it and get the plain glyph below.\n */\n iconSrc?: string;\n}\n\n/**\n * The right-side counterpart to `NavBrandButton` for a wallet/account entry\n * point: the same glass pill material, with a thin brand-gradient ring\n * traced around the rim instead of a static icon. Not connected — the ring\n * rotates slowly (an ambient \"something opens here\" cue) around an empty\n * center, no glyph, no placeholder avatar. Connected — the ring settles to a\n * static accent and shows the connected wallet's own icon via `iconSrc` when\n * the caller has one; otherwise a plain `User` glyph (never a fabricated\n * generic avatar).\n */\nexport const NavWalletTrigger = React.forwardRef<HTMLButtonElement, NavWalletTriggerProps>(\n function NavWalletTrigger({ onClick, className, connected = false, iconSrc, ...rest }, ref) {\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"] ?? (connected ? \"Account\" : \"Connect wallet\")}\n className={cn(\n \"ml-nav-wallet-trigger relative flex h-8 w-8 items-center justify-center rounded-full\",\n \"bg-background/10 text-muted-foreground backdrop-blur-xl backdrop-saturate-150\",\n \"transition-colors hover:bg-background/20 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n connected && \"ml-nav-wallet-trigger--connected\",\n className\n )}\n >\n <span className=\"ml-nav-wallet-ring\" aria-hidden=\"true\" />\n {connected && (iconSrc\n ? <img src={iconSrc} alt=\"\" width={16} height={16} className=\"h-4 w-4 shrink-0 rounded-full\" />\n : <User className=\"h-3.5 w-3.5\" />)}\n </button>\n );\n }\n);\n\n// ── Account sheet ─────────────────────────────────────────────────────────────\n\nconst ML_ACCOUNT_OPEN = \"ml:account-open\";\nconst ML_ACCOUNT_CLOSE = \"ml:account-close\";\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavAccountSheet() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_CLOSE)),\n };\n}\n\nexport interface NavAccountSheetProps {\n /** The app's account/wallet panel content — identity, network, disconnect, etc. */\n children: React.ReactNode;\n}\n\n/**\n * The wallet-side surface: a centered, backdrop-blurred panel — the exact\n * same overlay + card treatment as `NavCommandMenu` (`.nav-canvas-overlay`,\n * `bg-background/90 backdrop-blur-2xl backdrop-saturate-150`), so every\n * global panel in the header reads as one system. No built-in title — this\n * is an account/wallet card, not a menu; content is entirely app-owned.\n *\n * Opens via `useNavAccountSheet().open()`. Mutually exclusive with the\n * command menu: opening one closes the other, and the shared `ml:nav-close`\n * event (fired by `useNavCommandMenu().close()`) closes this too.\n */\nexport function NavAccountSheet({ children }: NavAccountSheetProps) {\n const [open, setOpen] = React.useState(false);\n\n React.useEffect(() => {\n const onOpen = () => {\n document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE));\n setOpen(true);\n };\n const onClose = () => setOpen(false);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.addEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.addEventListener(ML_NAV_OPEN, onClose);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.removeEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.removeEventListener(ML_NAV_OPEN, onClose);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, []);\n\n return (\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur — identical to NavCommandMenu's */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Panel — centered on desktop, bottom sheet on mobile, same as NavCommandMenu */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className=\"relative w-full max-w-[380px] overflow-hidden rounded-[20px] border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n <button\n onClick={() => setOpen(false)}\n className=\"absolute right-3 top-3 rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-muted/50 hover:text-foreground\"\n aria-label=\"Close\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n <div className=\"px-4 pb-4 pt-5 sm:pt-6\">{children}</div>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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","names":["NavWalletTrigger"]}
@@ -34,9 +34,7 @@ __export(token_card_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(token_card_exports);
36
36
  var import_jsx_runtime = require("react/jsx-runtime");
37
- var import_react = require("react");
38
37
  var import_link = __toESM(require("next/link"), 1);
39
- var import_image = __toESM(require("next/image"), 1);
40
38
  var import_lucide_react = require("lucide-react");
41
39
  var import_dropdown_menu = require("./dropdown-menu.js");
42
40
  var import_cn = require("../utils/cn.js");
@@ -44,6 +42,8 @@ var import_ipfs = require("../utils/ipfs.js");
44
42
  var import_format = require("../utils/format.js");
45
43
  var import_currency_icon = require("./currency-icon.js");
46
44
  var import_ip_type_badge = require("./ip-type-badge.js");
45
+ var import_animated_token_media = require("./animated-token-media.js");
46
+ var import_living_render_collections = require("../data/living-render-collections.js");
47
47
  var import_sdk = require("@medialane/sdk");
48
48
  const BTN_BASE = "h-8 rounded-[9px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0";
49
49
  const BTN_OUTLINE = (0, import_cn.cn)(BTN_BASE, "border border-border/60 text-foreground hover:bg-muted/60");
@@ -57,9 +57,9 @@ function TokenCard({
57
57
  onOffer,
58
58
  onReport
59
59
  }) {
60
- const [imgError, setImgError] = (0, import_react.useState)(false);
61
60
  const name = token.metadata?.name || `Token #${token.tokenId}`;
62
61
  const image = (0, import_ipfs.ipfsToHttp)(token.metadata?.image);
62
+ const animationUrl = token.metadata?.animationUrl ?? null;
63
63
  const ipType = token.metadata?.ipType;
64
64
  const listingOrder = token.activeOrders?.find(
65
65
  (o) => o.offer.itemType === "ERC721" || o.offer.itemType === "ERC1155"
@@ -68,6 +68,7 @@ function TokenCard({
68
68
  const creator = typeof creatorAttr?.value === "string" ? creatorAttr.value : null;
69
69
  const owner = token.balances?.[0]?.owner ?? token.owner ?? null;
70
70
  const chain = token.chain.toUpperCase();
71
+ const live = (0, import_living_render_collections.isLivingRenderCollection)(chain, token.contractAddress);
71
72
  const assetHref = (0, import_sdk.assetHref)(chain, token.contractAddress, token.tokenId);
72
73
  const collectionHref = (0, import_sdk.collectionHref)(chain, token.contractAddress);
73
74
  const remixHref = `/create/remix/${token.contractAddress}/${token.tokenId}`;
@@ -84,21 +85,22 @@ function TokenCard({
84
85
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "card-base relative overflow-hidden flex flex-col w-full", children: [
85
86
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: assetHref, className: "flex flex-col flex-1", children: [
86
87
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative aspect-square bg-muted overflow-hidden shrink-0", children: [
87
- !imgError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
88
- import_image.default,
88
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
89
+ import_animated_token_media.AnimatedTokenMedia,
89
90
  {
90
- src: image,
91
+ image,
92
+ animationUrl,
93
+ live,
91
94
  alt: name,
92
- fill: true,
93
- unoptimized: true,
95
+ mode: "fill",
94
96
  sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw",
95
97
  className: "object-cover",
96
- onError: () => setImgError(true)
98
+ fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-2xl tabular-nums text-muted-foreground", children: [
99
+ "#",
100
+ token.tokenId
101
+ ] }) })
97
102
  }
98
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-2xl tabular-nums text-muted-foreground", children: [
99
- "#",
100
- token.tokenId
101
- ] }) }),
103
+ ),
102
104
  listingOrder && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute bottom-2 right-2 z-10", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-center gap-1 px-2 py-1 rounded-full text-[11px] font-bold bg-background/90 backdrop-blur-sm border border-border/40 shadow-sm", children: [
103
105
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: listingOrder.price.currency ?? "", size: 10 }),
104
106
  (0, import_format.formatDisplayPrice)(listingOrder.price.formatted),