@medialane/ui 0.71.0 → 0.72.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.
@@ -33,8 +33,7 @@ function LaunchpadFilterBar({
33
33
  onQueryChange,
34
34
  groups,
35
35
  activeGroups,
36
- onToggleGroup,
37
- resultCount
36
+ onToggleGroup
38
37
  }) {
39
38
  const inputRef = (0, import_react.useRef)(null);
40
39
  (0, import_react.useEffect)(() => {
@@ -96,12 +95,7 @@ function LaunchpadFilterBar({
96
95
  },
97
96
  group.key
98
97
  );
99
- }) }),
100
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-xs text-muted-foreground whitespace-nowrap sm:pl-2", children: [
101
- resultCount,
102
- " ",
103
- resultCount === 1 ? "service" : "services"
104
- ] })
98
+ }) })
105
99
  ] });
106
100
  }
107
101
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/launchpad-filter-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { serviceMatchesQuery } from \"./launchpad-services.js\";\nimport {\n LAUNCHPAD_SERVICE_DEFINITIONS,\n type ServiceGroup,\n type ServiceGroupDefinition,\n} from \"../data/launchpad-services.js\";\n\nexport interface LaunchpadFilterBarProps {\n query: string;\n onQueryChange: (value: string) => void;\n groups: ServiceGroupDefinition[];\n activeGroups: Set<ServiceGroup>;\n onToggleGroup: (key: ServiceGroup) => void;\n resultCount: number;\n}\n\n/**\n * Search + group-filter bar above the launchpad grid. Fully controlled — all\n * state (query, active groups) lives in the caller so the grid below reacts\n * to the same state. Each pill shows the live count of services it matches\n * under the current search query. Pressing \"/\" anywhere on the page focuses\n * the search input.\n */\nexport function LaunchpadFilterBar({\n query,\n onQueryChange,\n groups,\n activeGroups,\n onToggleGroup,\n resultCount,\n}: LaunchpadFilterBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"/\" || e.metaKey || e.ctrlKey || e.altKey) return;\n const target = e.target as HTMLElement | null;\n if (target && (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\" || target.isContentEditable)) return;\n e.preventDefault();\n inputRef.current?.focus();\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, []);\n\n const countFor = (key: ServiceGroup) =>\n LAUNCHPAD_SERVICE_DEFINITIONS.filter(\n (d) => d.group === key && d.status === \"live\" && serviceMatchesQuery(d, query),\n ).length;\n\n return (\n <div className=\"flex flex-col sm:flex-row gap-3 sm:items-center\">\n <div className=\"relative flex-1 sm:max-w-xs\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => onQueryChange(e.target.value)}\n placeholder=\"Search services\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-9 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {query ? (\n <button\n type=\"button\"\n onClick={() => onQueryChange(\"\")}\n aria-label=\"Clear search\"\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 h-5 w-5 flex items-center justify-center rounded-full text-muted-foreground hover:text-foreground transition-colors\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n ) : (\n <kbd className=\"absolute right-3 top-1/2 -translate-y-1/2 hidden sm:flex h-5 w-5 items-center justify-center rounded border border-border bg-muted/40 text-[11px] font-medium text-muted-foreground pointer-events-none\">\n /\n </kbd>\n )}\n </div>\n\n <div className=\"flex flex-wrap gap-2 flex-1\">\n {groups.map((group) => {\n const active = activeGroups.has(group.key);\n const count = countFor(group.key);\n return (\n <button\n key={group.key}\n type=\"button\"\n onClick={() => onToggleGroup(group.key)}\n aria-pressed={active}\n className={cn(\n \"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border inline-flex items-center gap-1.5\",\n active\n ? \"bg-gradient-to-r from-brand-purple to-brand-blue text-white border-transparent shadow-sm\"\n : \"bg-card text-muted-foreground border-border active:bg-muted/60 sm:hover:bg-muted/40\",\n )}\n >\n {group.title}\n <span className={cn(\"tabular-nums\", active ? \"text-white/70\" : \"text-muted-foreground/60\")}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n\n <p className=\"text-xs text-muted-foreground whitespace-nowrap sm:pl-2\">\n {resultCount} {resultCount === 1 ? \"service\" : \"services\"}\n </p>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyDM;AAvDN,mBAAkC;AAClC,0BAA0B;AAC1B,gBAAmB;AACnB,gCAAoC;AACpC,IAAAA,6BAIO;AAkBA,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,eAAW,qBAAyB,IAAI;AAE9C,8BAAU,MAAM;AACd,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAQ;AACzD,YAAM,SAAS,EAAE;AACjB,UAAI,WAAW,OAAO,YAAY,WAAW,OAAO,YAAY,cAAc,OAAO,mBAAoB;AACzG,QAAE,eAAe;AACjB,eAAS,SAAS,MAAM;AAAA,IAC1B;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,CAAC,QAChB,yDAA8B;AAAA,IAC5B,CAAC,MAAM,EAAE,UAAU,OAAO,EAAE,WAAW,cAAU,+CAAoB,GAAG,KAAK;AAAA,EAC/E,EAAE;AAEJ,SACE,6CAAC,SAAI,WAAU,mDACb;AAAA,iDAAC,SAAI,WAAU,+BACb;AAAA,kDAAC,8BAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,UAC7C,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,MACC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,EAAE;AAAA,UAC/B,cAAW;AAAA,UACX,WAAU;AAAA,UAEV,sDAAC,yBAAE,WAAU,eAAc;AAAA;AAAA,MAC7B,IAEA,4CAAC,SAAI,WAAU,2MAA0M,eAEzN;AAAA,OAEJ;AAAA,IAEA,4CAAC,SAAI,WAAU,+BACZ,iBAAO,IAAI,CAAC,UAAU;AACrB,YAAM,SAAS,aAAa,IAAI,MAAM,GAAG;AACzC,YAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,MAAM,GAAG;AAAA,UACtC,gBAAc;AAAA,UACd,eAAW;AAAA,YACT;AAAA,YACA,SACI,6FACA;AAAA,UACN;AAAA,UAEC;AAAA,kBAAM;AAAA,YACP,4CAAC,UAAK,eAAW,cAAG,gBAAgB,SAAS,kBAAkB,0BAA0B,GACtF,iBACH;AAAA;AAAA;AAAA,QAdK,MAAM;AAAA,MAeb;AAAA,IAEJ,CAAC,GACH;AAAA,IAEA,6CAAC,OAAE,WAAU,2DACV;AAAA;AAAA,MAAY;AAAA,MAAE,gBAAgB,IAAI,YAAY;AAAA,OACjD;AAAA,KACF;AAEJ;","names":["import_launchpad_services"]}
1
+ {"version":3,"sources":["../../src/components/launchpad-filter-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { serviceMatchesQuery } from \"./launchpad-services.js\";\nimport {\n LAUNCHPAD_SERVICE_DEFINITIONS,\n type ServiceGroup,\n type ServiceGroupDefinition,\n} from \"../data/launchpad-services.js\";\n\nexport interface LaunchpadFilterBarProps {\n query: string;\n onQueryChange: (value: string) => void;\n groups: ServiceGroupDefinition[];\n activeGroups: Set<ServiceGroup>;\n onToggleGroup: (key: ServiceGroup) => void;\n /** Accepted for API stability; the bar no longer renders a total count. */\n resultCount?: number;\n}\n\n/**\n * Search + group-filter bar above the launchpad grid. Fully controlled — all\n * state (query, active groups) lives in the caller so the grid below reacts\n * to the same state. Each pill shows the live count of services it matches\n * under the current search query. Pressing \"/\" anywhere on the page focuses\n * the search input.\n */\nexport function LaunchpadFilterBar({\n query,\n onQueryChange,\n groups,\n activeGroups,\n onToggleGroup,\n}: LaunchpadFilterBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"/\" || e.metaKey || e.ctrlKey || e.altKey) return;\n const target = e.target as HTMLElement | null;\n if (target && (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\" || target.isContentEditable)) return;\n e.preventDefault();\n inputRef.current?.focus();\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, []);\n\n const countFor = (key: ServiceGroup) =>\n LAUNCHPAD_SERVICE_DEFINITIONS.filter(\n (d) => d.group === key && d.status === \"live\" && serviceMatchesQuery(d, query),\n ).length;\n\n return (\n <div className=\"flex flex-col sm:flex-row gap-3 sm:items-center\">\n <div className=\"relative flex-1 sm:max-w-xs\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => onQueryChange(e.target.value)}\n placeholder=\"Search services\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-9 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {query ? (\n <button\n type=\"button\"\n onClick={() => onQueryChange(\"\")}\n aria-label=\"Clear search\"\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 h-5 w-5 flex items-center justify-center rounded-full text-muted-foreground hover:text-foreground transition-colors\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n ) : (\n <kbd className=\"absolute right-3 top-1/2 -translate-y-1/2 hidden sm:flex h-5 w-5 items-center justify-center rounded border border-border bg-muted/40 text-[11px] font-medium text-muted-foreground pointer-events-none\">\n /\n </kbd>\n )}\n </div>\n\n <div className=\"flex flex-wrap gap-2 flex-1\">\n {groups.map((group) => {\n const active = activeGroups.has(group.key);\n const count = countFor(group.key);\n return (\n <button\n key={group.key}\n type=\"button\"\n onClick={() => onToggleGroup(group.key)}\n aria-pressed={active}\n className={cn(\n \"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border inline-flex items-center gap-1.5\",\n active\n ? \"bg-gradient-to-r from-brand-purple to-brand-blue text-white border-transparent shadow-sm\"\n : \"bg-card text-muted-foreground border-border active:bg-muted/60 sm:hover:bg-muted/40\",\n )}\n >\n {group.title}\n <span className={cn(\"tabular-nums\", active ? \"text-white/70\" : \"text-muted-foreground/60\")}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyDM;AAvDN,mBAAkC;AAClC,0BAA0B;AAC1B,gBAAmB;AACnB,gCAAoC;AACpC,IAAAA,6BAIO;AAmBA,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,eAAW,qBAAyB,IAAI;AAE9C,8BAAU,MAAM;AACd,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAQ;AACzD,YAAM,SAAS,EAAE;AACjB,UAAI,WAAW,OAAO,YAAY,WAAW,OAAO,YAAY,cAAc,OAAO,mBAAoB;AACzG,QAAE,eAAe;AACjB,eAAS,SAAS,MAAM;AAAA,IAC1B;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,CAAC,QAChB,yDAA8B;AAAA,IAC5B,CAAC,MAAM,EAAE,UAAU,OAAO,EAAE,WAAW,cAAU,+CAAoB,GAAG,KAAK;AAAA,EAC/E,EAAE;AAEJ,SACE,6CAAC,SAAI,WAAU,mDACb;AAAA,iDAAC,SAAI,WAAU,+BACb;AAAA,kDAAC,8BAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,UAC7C,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,MACC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,EAAE;AAAA,UAC/B,cAAW;AAAA,UACX,WAAU;AAAA,UAEV,sDAAC,yBAAE,WAAU,eAAc;AAAA;AAAA,MAC7B,IAEA,4CAAC,SAAI,WAAU,2MAA0M,eAEzN;AAAA,OAEJ;AAAA,IAEA,4CAAC,SAAI,WAAU,+BACZ,iBAAO,IAAI,CAAC,UAAU;AACrB,YAAM,SAAS,aAAa,IAAI,MAAM,GAAG;AACzC,YAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,MAAM,GAAG;AAAA,UACtC,gBAAc;AAAA,UACd,eAAW;AAAA,YACT;AAAA,YACA,SACI,6FACA;AAAA,UACN;AAAA,UAEC;AAAA,kBAAM;AAAA,YACP,4CAAC,UAAK,eAAW,cAAG,gBAAgB,SAAS,kBAAkB,0BAA0B,GACtF,iBACH;AAAA;AAAA;AAAA,QAdK,MAAM;AAAA,MAeb;AAAA,IAEJ,CAAC,GACH;AAAA,KAEF;AAEJ;","names":["import_launchpad_services"]}
@@ -8,7 +8,8 @@ interface LaunchpadFilterBarProps {
8
8
  groups: ServiceGroupDefinition[];
9
9
  activeGroups: Set<ServiceGroup>;
10
10
  onToggleGroup: (key: ServiceGroup) => void;
11
- resultCount: number;
11
+ /** Accepted for API stability; the bar no longer renders a total count. */
12
+ resultCount?: number;
12
13
  }
13
14
  /**
14
15
  * Search + group-filter bar above the launchpad grid. Fully controlled — all
@@ -17,6 +18,6 @@ interface LaunchpadFilterBarProps {
17
18
  * under the current search query. Pressing "/" anywhere on the page focuses
18
19
  * the search input.
19
20
  */
20
- declare function LaunchpadFilterBar({ query, onQueryChange, groups, activeGroups, onToggleGroup, resultCount, }: LaunchpadFilterBarProps): react_jsx_runtime.JSX.Element;
21
+ declare function LaunchpadFilterBar({ query, onQueryChange, groups, activeGroups, onToggleGroup, }: LaunchpadFilterBarProps): react_jsx_runtime.JSX.Element;
21
22
 
22
23
  export { LaunchpadFilterBar, type LaunchpadFilterBarProps };
@@ -8,7 +8,8 @@ interface LaunchpadFilterBarProps {
8
8
  groups: ServiceGroupDefinition[];
9
9
  activeGroups: Set<ServiceGroup>;
10
10
  onToggleGroup: (key: ServiceGroup) => void;
11
- resultCount: number;
11
+ /** Accepted for API stability; the bar no longer renders a total count. */
12
+ resultCount?: number;
12
13
  }
13
14
  /**
14
15
  * Search + group-filter bar above the launchpad grid. Fully controlled — all
@@ -17,6 +18,6 @@ interface LaunchpadFilterBarProps {
17
18
  * under the current search query. Pressing "/" anywhere on the page focuses
18
19
  * the search input.
19
20
  */
20
- declare function LaunchpadFilterBar({ query, onQueryChange, groups, activeGroups, onToggleGroup, resultCount, }: LaunchpadFilterBarProps): react_jsx_runtime.JSX.Element;
21
+ declare function LaunchpadFilterBar({ query, onQueryChange, groups, activeGroups, onToggleGroup, }: LaunchpadFilterBarProps): react_jsx_runtime.JSX.Element;
21
22
 
22
23
  export { LaunchpadFilterBar, type LaunchpadFilterBarProps };
@@ -12,8 +12,7 @@ function LaunchpadFilterBar({
12
12
  onQueryChange,
13
13
  groups,
14
14
  activeGroups,
15
- onToggleGroup,
16
- resultCount
15
+ onToggleGroup
17
16
  }) {
18
17
  const inputRef = useRef(null);
19
18
  useEffect(() => {
@@ -75,12 +74,7 @@ function LaunchpadFilterBar({
75
74
  },
76
75
  group.key
77
76
  );
78
- }) }),
79
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground whitespace-nowrap sm:pl-2", children: [
80
- resultCount,
81
- " ",
82
- resultCount === 1 ? "service" : "services"
83
- ] })
77
+ }) })
84
78
  ] });
85
79
  }
86
80
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/launchpad-filter-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { serviceMatchesQuery } from \"./launchpad-services.js\";\nimport {\n LAUNCHPAD_SERVICE_DEFINITIONS,\n type ServiceGroup,\n type ServiceGroupDefinition,\n} from \"../data/launchpad-services.js\";\n\nexport interface LaunchpadFilterBarProps {\n query: string;\n onQueryChange: (value: string) => void;\n groups: ServiceGroupDefinition[];\n activeGroups: Set<ServiceGroup>;\n onToggleGroup: (key: ServiceGroup) => void;\n resultCount: number;\n}\n\n/**\n * Search + group-filter bar above the launchpad grid. Fully controlled — all\n * state (query, active groups) lives in the caller so the grid below reacts\n * to the same state. Each pill shows the live count of services it matches\n * under the current search query. Pressing \"/\" anywhere on the page focuses\n * the search input.\n */\nexport function LaunchpadFilterBar({\n query,\n onQueryChange,\n groups,\n activeGroups,\n onToggleGroup,\n resultCount,\n}: LaunchpadFilterBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"/\" || e.metaKey || e.ctrlKey || e.altKey) return;\n const target = e.target as HTMLElement | null;\n if (target && (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\" || target.isContentEditable)) return;\n e.preventDefault();\n inputRef.current?.focus();\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, []);\n\n const countFor = (key: ServiceGroup) =>\n LAUNCHPAD_SERVICE_DEFINITIONS.filter(\n (d) => d.group === key && d.status === \"live\" && serviceMatchesQuery(d, query),\n ).length;\n\n return (\n <div className=\"flex flex-col sm:flex-row gap-3 sm:items-center\">\n <div className=\"relative flex-1 sm:max-w-xs\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => onQueryChange(e.target.value)}\n placeholder=\"Search services\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-9 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {query ? (\n <button\n type=\"button\"\n onClick={() => onQueryChange(\"\")}\n aria-label=\"Clear search\"\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 h-5 w-5 flex items-center justify-center rounded-full text-muted-foreground hover:text-foreground transition-colors\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n ) : (\n <kbd className=\"absolute right-3 top-1/2 -translate-y-1/2 hidden sm:flex h-5 w-5 items-center justify-center rounded border border-border bg-muted/40 text-[11px] font-medium text-muted-foreground pointer-events-none\">\n /\n </kbd>\n )}\n </div>\n\n <div className=\"flex flex-wrap gap-2 flex-1\">\n {groups.map((group) => {\n const active = activeGroups.has(group.key);\n const count = countFor(group.key);\n return (\n <button\n key={group.key}\n type=\"button\"\n onClick={() => onToggleGroup(group.key)}\n aria-pressed={active}\n className={cn(\n \"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border inline-flex items-center gap-1.5\",\n active\n ? \"bg-gradient-to-r from-brand-purple to-brand-blue text-white border-transparent shadow-sm\"\n : \"bg-card text-muted-foreground border-border active:bg-muted/60 sm:hover:bg-muted/40\",\n )}\n >\n {group.title}\n <span className={cn(\"tabular-nums\", active ? \"text-white/70\" : \"text-muted-foreground/60\")}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n\n <p className=\"text-xs text-muted-foreground whitespace-nowrap sm:pl-2\">\n {resultCount} {resultCount === 1 ? \"service\" : \"services\"}\n </p>\n </div>\n );\n}\n"],"mappings":";AAyDM,SACE,KADF;AAvDN,SAAS,WAAW,cAAc;AAClC,SAAS,QAAQ,SAAS;AAC1B,SAAS,UAAU;AACnB,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,OAGK;AAkBA,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,WAAW,OAAyB,IAAI;AAE9C,YAAU,MAAM;AACd,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAQ;AACzD,YAAM,SAAS,EAAE;AACjB,UAAI,WAAW,OAAO,YAAY,WAAW,OAAO,YAAY,cAAc,OAAO,mBAAoB;AACzG,QAAE,eAAe;AACjB,eAAS,SAAS,MAAM;AAAA,IAC1B;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,CAAC,QAChB,8BAA8B;AAAA,IAC5B,CAAC,MAAM,EAAE,UAAU,OAAO,EAAE,WAAW,UAAU,oBAAoB,GAAG,KAAK;AAAA,EAC/E,EAAE;AAEJ,SACE,qBAAC,SAAI,WAAU,mDACb;AAAA,yBAAC,SAAI,WAAU,+BACb;AAAA,0BAAC,UAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,UAC7C,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,MACC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,EAAE;AAAA,UAC/B,cAAW;AAAA,UACX,WAAU;AAAA,UAEV,8BAAC,KAAE,WAAU,eAAc;AAAA;AAAA,MAC7B,IAEA,oBAAC,SAAI,WAAU,2MAA0M,eAEzN;AAAA,OAEJ;AAAA,IAEA,oBAAC,SAAI,WAAU,+BACZ,iBAAO,IAAI,CAAC,UAAU;AACrB,YAAM,SAAS,aAAa,IAAI,MAAM,GAAG;AACzC,YAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,MAAM,GAAG;AAAA,UACtC,gBAAc;AAAA,UACd,WAAW;AAAA,YACT;AAAA,YACA,SACI,6FACA;AAAA,UACN;AAAA,UAEC;AAAA,kBAAM;AAAA,YACP,oBAAC,UAAK,WAAW,GAAG,gBAAgB,SAAS,kBAAkB,0BAA0B,GACtF,iBACH;AAAA;AAAA;AAAA,QAdK,MAAM;AAAA,MAeb;AAAA,IAEJ,CAAC,GACH;AAAA,IAEA,qBAAC,OAAE,WAAU,2DACV;AAAA;AAAA,MAAY;AAAA,MAAE,gBAAgB,IAAI,YAAY;AAAA,OACjD;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/launchpad-filter-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { serviceMatchesQuery } from \"./launchpad-services.js\";\nimport {\n LAUNCHPAD_SERVICE_DEFINITIONS,\n type ServiceGroup,\n type ServiceGroupDefinition,\n} from \"../data/launchpad-services.js\";\n\nexport interface LaunchpadFilterBarProps {\n query: string;\n onQueryChange: (value: string) => void;\n groups: ServiceGroupDefinition[];\n activeGroups: Set<ServiceGroup>;\n onToggleGroup: (key: ServiceGroup) => void;\n /** Accepted for API stability; the bar no longer renders a total count. */\n resultCount?: number;\n}\n\n/**\n * Search + group-filter bar above the launchpad grid. Fully controlled — all\n * state (query, active groups) lives in the caller so the grid below reacts\n * to the same state. Each pill shows the live count of services it matches\n * under the current search query. Pressing \"/\" anywhere on the page focuses\n * the search input.\n */\nexport function LaunchpadFilterBar({\n query,\n onQueryChange,\n groups,\n activeGroups,\n onToggleGroup,\n}: LaunchpadFilterBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"/\" || e.metaKey || e.ctrlKey || e.altKey) return;\n const target = e.target as HTMLElement | null;\n if (target && (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\" || target.isContentEditable)) return;\n e.preventDefault();\n inputRef.current?.focus();\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, []);\n\n const countFor = (key: ServiceGroup) =>\n LAUNCHPAD_SERVICE_DEFINITIONS.filter(\n (d) => d.group === key && d.status === \"live\" && serviceMatchesQuery(d, query),\n ).length;\n\n return (\n <div className=\"flex flex-col sm:flex-row gap-3 sm:items-center\">\n <div className=\"relative flex-1 sm:max-w-xs\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => onQueryChange(e.target.value)}\n placeholder=\"Search services\"\n className=\"w-full h-10 rounded-full border border-border bg-card pl-9 pr-9 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-brand-purple/40 focus:border-brand-purple/40\"\n />\n {query ? (\n <button\n type=\"button\"\n onClick={() => onQueryChange(\"\")}\n aria-label=\"Clear search\"\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 h-5 w-5 flex items-center justify-center rounded-full text-muted-foreground hover:text-foreground transition-colors\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n ) : (\n <kbd className=\"absolute right-3 top-1/2 -translate-y-1/2 hidden sm:flex h-5 w-5 items-center justify-center rounded border border-border bg-muted/40 text-[11px] font-medium text-muted-foreground pointer-events-none\">\n /\n </kbd>\n )}\n </div>\n\n <div className=\"flex flex-wrap gap-2 flex-1\">\n {groups.map((group) => {\n const active = activeGroups.has(group.key);\n const count = countFor(group.key);\n return (\n <button\n key={group.key}\n type=\"button\"\n onClick={() => onToggleGroup(group.key)}\n aria-pressed={active}\n className={cn(\n \"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border inline-flex items-center gap-1.5\",\n active\n ? \"bg-gradient-to-r from-brand-purple to-brand-blue text-white border-transparent shadow-sm\"\n : \"bg-card text-muted-foreground border-border active:bg-muted/60 sm:hover:bg-muted/40\",\n )}\n >\n {group.title}\n <span className={cn(\"tabular-nums\", active ? \"text-white/70\" : \"text-muted-foreground/60\")}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n\n </div>\n );\n}\n"],"mappings":";AAyDM,SACE,KADF;AAvDN,SAAS,WAAW,cAAc;AAClC,SAAS,QAAQ,SAAS;AAC1B,SAAS,UAAU;AACnB,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,OAGK;AAmBA,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,WAAW,OAAyB,IAAI;AAE9C,YAAU,MAAM;AACd,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAQ;AACzD,YAAM,SAAS,EAAE;AACjB,UAAI,WAAW,OAAO,YAAY,WAAW,OAAO,YAAY,cAAc,OAAO,mBAAoB;AACzG,QAAE,eAAe;AACjB,eAAS,SAAS,MAAM;AAAA,IAC1B;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,CAAC,QAChB,8BAA8B;AAAA,IAC5B,CAAC,MAAM,EAAE,UAAU,OAAO,EAAE,WAAW,UAAU,oBAAoB,GAAG,KAAK;AAAA,EAC/E,EAAE;AAEJ,SACE,qBAAC,SAAI,WAAU,mDACb;AAAA,yBAAC,SAAI,WAAU,+BACb;AAAA,0BAAC,UAAO,WAAU,8FAA6F;AAAA,MAC/G;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,UAC7C,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,MACC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,EAAE;AAAA,UAC/B,cAAW;AAAA,UACX,WAAU;AAAA,UAEV,8BAAC,KAAE,WAAU,eAAc;AAAA;AAAA,MAC7B,IAEA,oBAAC,SAAI,WAAU,2MAA0M,eAEzN;AAAA,OAEJ;AAAA,IAEA,oBAAC,SAAI,WAAU,+BACZ,iBAAO,IAAI,CAAC,UAAU;AACrB,YAAM,SAAS,aAAa,IAAI,MAAM,GAAG;AACzC,YAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,cAAc,MAAM,GAAG;AAAA,UACtC,gBAAc;AAAA,UACd,WAAW;AAAA,YACT;AAAA,YACA,SACI,6FACA;AAAA,UACN;AAAA,UAEC;AAAA,kBAAM;AAAA,YACP,oBAAC,UAAK,WAAW,GAAG,gBAAgB,SAAS,kBAAkB,0BAA0B,GACtF,iBACH;AAAA;AAAA;AAAA,QAdK,MAAM;AAAA,MAeb;AAAA,IAEJ,CAAC,GACH;AAAA,KAEF;AAEJ;","names":[]}
@@ -27,7 +27,7 @@ function ServiceHeader({ icon, title, subtitle, headerAccessory, className, plai
27
27
  const body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
28
28
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2.5", children: [
29
29
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-9 w-9 rounded-xl bg-primary flex items-center justify-center shrink-0", children: icon }),
30
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-2xl sm:text-3xl font-black tracking-tight", children: title })
30
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-2xl sm:text-3xl font-semibold tracking-tight", children: title })
31
31
  ] }),
32
32
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-1.5 text-sm text-muted-foreground max-w-xl", children: subtitle }),
33
33
  headerAccessory && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-4", children: headerAccessory })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/service-header.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport interface ServiceHeaderProps {\n /** Rendered icon element, e.g. <Coins className=\"h-4 w-4 text-white\" />. */\n icon: ReactNode;\n title: string;\n subtitle: string;\n /** Optional element shown under the subtitle (e.g. a URL pill). */\n headerAccessory?: ReactNode;\n className?: string;\n /** Drop the brand gradient border for a plain neutral-bordered card.\n * Standalone headers (browse pages, coin page, /claim hub) keep the\n * gradient by default. */\n plain?: boolean;\n /** No card at all — icon chip + title + subtitle on the page background.\n * Used by ServiceFormShell so the form card is the page's only panel. */\n bare?: boolean;\n}\n\n/** Shared launchpad/claim page header: a dark card with a solid primary icon\n * chip, title + subtitle. By default it sits on a static brand gradient\n * border; pass `plain` for a neutral-bordered card. Pure presentation —\n * consuming app supplies the icon and (optionally) a header accessory. */\nexport function ServiceHeader({ icon, title, subtitle, headerAccessory, className, plain = false, bare = false }: ServiceHeaderProps) {\n const body = (\n <>\n <div className=\"flex items-center gap-2.5\">\n <div className=\"h-9 w-9 rounded-xl bg-primary flex items-center justify-center shrink-0\">\n {icon}\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-black tracking-tight\">{title}</h1>\n </div>\n <p className=\"mt-1.5 text-sm text-muted-foreground max-w-xl\">{subtitle}</p>\n {headerAccessory && <div className=\"mt-4\">{headerAccessory}</div>}\n </>\n );\n\n if (bare) {\n return <div className={className}>{body}</div>;\n }\n\n if (plain) {\n return <div className={cn(\"rounded-2xl border border-border/60 bg-card p-6 sm:p-7\", className)}>{body}</div>;\n }\n\n return (\n <div className={cn(\"rounded-2xl p-[1.5px] bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose\", className)}>\n <div className=\"rounded-[15px] bg-card p-6 sm:p-7\">{body}</div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BI;AAzBJ,gBAAmB;AAuBZ,SAAS,cAAc,EAAE,MAAM,OAAO,UAAU,iBAAiB,WAAW,QAAQ,OAAO,OAAO,MAAM,GAAuB;AACpI,QAAM,OACJ,4EACE;AAAA,iDAAC,SAAI,WAAU,6BACb;AAAA,kDAAC,SAAI,WAAU,2EACZ,gBACH;AAAA,MACA,4CAAC,QAAG,WAAU,kDAAkD,iBAAM;AAAA,OACxE;AAAA,IACA,4CAAC,OAAE,WAAU,iDAAiD,oBAAS;AAAA,IACtE,mBAAmB,4CAAC,SAAI,WAAU,QAAQ,2BAAgB;AAAA,KAC7D;AAGF,MAAI,MAAM;AACR,WAAO,4CAAC,SAAI,WAAuB,gBAAK;AAAA,EAC1C;AAEA,MAAI,OAAO;AACT,WAAO,4CAAC,SAAI,eAAW,cAAG,0DAA0D,SAAS,GAAI,gBAAK;AAAA,EACxG;AAEA,SACE,4CAAC,SAAI,eAAW,cAAG,0FAA0F,SAAS,GACpH,sDAAC,SAAI,WAAU,qCAAqC,gBAAK,GAC3D;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/service-header.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport interface ServiceHeaderProps {\n /** Rendered icon element, e.g. <Coins className=\"h-4 w-4 text-white\" />. */\n icon: ReactNode;\n title: string;\n subtitle: string;\n /** Optional element shown under the subtitle (e.g. a URL pill). */\n headerAccessory?: ReactNode;\n className?: string;\n /** Drop the brand gradient border for a plain neutral-bordered card.\n * Standalone headers (browse pages, coin page, /claim hub) keep the\n * gradient by default. */\n plain?: boolean;\n /** No card at all — icon chip + title + subtitle on the page background.\n * Used by ServiceFormShell so the form card is the page's only panel. */\n bare?: boolean;\n}\n\n/** Shared launchpad/claim page header: a dark card with a solid primary icon\n * chip, title + subtitle. By default it sits on a static brand gradient\n * border; pass `plain` for a neutral-bordered card. Pure presentation —\n * consuming app supplies the icon and (optionally) a header accessory. */\nexport function ServiceHeader({ icon, title, subtitle, headerAccessory, className, plain = false, bare = false }: ServiceHeaderProps) {\n const body = (\n <>\n <div className=\"flex items-center gap-2.5\">\n <div className=\"h-9 w-9 rounded-xl bg-primary flex items-center justify-center shrink-0\">\n {icon}\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-semibold tracking-tight\">{title}</h1>\n </div>\n <p className=\"mt-1.5 text-sm text-muted-foreground max-w-xl\">{subtitle}</p>\n {headerAccessory && <div className=\"mt-4\">{headerAccessory}</div>}\n </>\n );\n\n if (bare) {\n return <div className={className}>{body}</div>;\n }\n\n if (plain) {\n return <div className={cn(\"rounded-2xl border border-border/60 bg-card p-6 sm:p-7\", className)}>{body}</div>;\n }\n\n return (\n <div className={cn(\"rounded-2xl p-[1.5px] bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose\", className)}>\n <div className=\"rounded-[15px] bg-card p-6 sm:p-7\">{body}</div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BI;AAzBJ,gBAAmB;AAuBZ,SAAS,cAAc,EAAE,MAAM,OAAO,UAAU,iBAAiB,WAAW,QAAQ,OAAO,OAAO,MAAM,GAAuB;AACpI,QAAM,OACJ,4EACE;AAAA,iDAAC,SAAI,WAAU,6BACb;AAAA,kDAAC,SAAI,WAAU,2EACZ,gBACH;AAAA,MACA,4CAAC,QAAG,WAAU,qDAAqD,iBAAM;AAAA,OAC3E;AAAA,IACA,4CAAC,OAAE,WAAU,iDAAiD,oBAAS;AAAA,IACtE,mBAAmB,4CAAC,SAAI,WAAU,QAAQ,2BAAgB;AAAA,KAC7D;AAGF,MAAI,MAAM;AACR,WAAO,4CAAC,SAAI,WAAuB,gBAAK;AAAA,EAC1C;AAEA,MAAI,OAAO;AACT,WAAO,4CAAC,SAAI,eAAW,cAAG,0DAA0D,SAAS,GAAI,gBAAK;AAAA,EACxG;AAEA,SACE,4CAAC,SAAI,eAAW,cAAG,0FAA0F,SAAS,GACpH,sDAAC,SAAI,WAAU,qCAAqC,gBAAK,GAC3D;AAEJ;","names":[]}
@@ -4,7 +4,7 @@ function ServiceHeader({ icon, title, subtitle, headerAccessory, className, plai
4
4
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
5
5
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
6
6
  /* @__PURE__ */ jsx("div", { className: "h-9 w-9 rounded-xl bg-primary flex items-center justify-center shrink-0", children: icon }),
7
- /* @__PURE__ */ jsx("h1", { className: "text-2xl sm:text-3xl font-black tracking-tight", children: title })
7
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl sm:text-3xl font-semibold tracking-tight", children: title })
8
8
  ] }),
9
9
  /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-sm text-muted-foreground max-w-xl", children: subtitle }),
10
10
  headerAccessory && /* @__PURE__ */ jsx("div", { className: "mt-4", children: headerAccessory })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/service-header.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport interface ServiceHeaderProps {\n /** Rendered icon element, e.g. <Coins className=\"h-4 w-4 text-white\" />. */\n icon: ReactNode;\n title: string;\n subtitle: string;\n /** Optional element shown under the subtitle (e.g. a URL pill). */\n headerAccessory?: ReactNode;\n className?: string;\n /** Drop the brand gradient border for a plain neutral-bordered card.\n * Standalone headers (browse pages, coin page, /claim hub) keep the\n * gradient by default. */\n plain?: boolean;\n /** No card at all — icon chip + title + subtitle on the page background.\n * Used by ServiceFormShell so the form card is the page's only panel. */\n bare?: boolean;\n}\n\n/** Shared launchpad/claim page header: a dark card with a solid primary icon\n * chip, title + subtitle. By default it sits on a static brand gradient\n * border; pass `plain` for a neutral-bordered card. Pure presentation —\n * consuming app supplies the icon and (optionally) a header accessory. */\nexport function ServiceHeader({ icon, title, subtitle, headerAccessory, className, plain = false, bare = false }: ServiceHeaderProps) {\n const body = (\n <>\n <div className=\"flex items-center gap-2.5\">\n <div className=\"h-9 w-9 rounded-xl bg-primary flex items-center justify-center shrink-0\">\n {icon}\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-black tracking-tight\">{title}</h1>\n </div>\n <p className=\"mt-1.5 text-sm text-muted-foreground max-w-xl\">{subtitle}</p>\n {headerAccessory && <div className=\"mt-4\">{headerAccessory}</div>}\n </>\n );\n\n if (bare) {\n return <div className={className}>{body}</div>;\n }\n\n if (plain) {\n return <div className={cn(\"rounded-2xl border border-border/60 bg-card p-6 sm:p-7\", className)}>{body}</div>;\n }\n\n return (\n <div className={cn(\"rounded-2xl p-[1.5px] bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose\", className)}>\n <div className=\"rounded-[15px] bg-card p-6 sm:p-7\">{body}</div>\n </div>\n );\n}\n"],"mappings":"AA0BI,mBAEI,KADF,YADF;AAzBJ,SAAS,UAAU;AAuBZ,SAAS,cAAc,EAAE,MAAM,OAAO,UAAU,iBAAiB,WAAW,QAAQ,OAAO,OAAO,MAAM,GAAuB;AACpI,QAAM,OACJ,iCACE;AAAA,yBAAC,SAAI,WAAU,6BACb;AAAA,0BAAC,SAAI,WAAU,2EACZ,gBACH;AAAA,MACA,oBAAC,QAAG,WAAU,kDAAkD,iBAAM;AAAA,OACxE;AAAA,IACA,oBAAC,OAAE,WAAU,iDAAiD,oBAAS;AAAA,IACtE,mBAAmB,oBAAC,SAAI,WAAU,QAAQ,2BAAgB;AAAA,KAC7D;AAGF,MAAI,MAAM;AACR,WAAO,oBAAC,SAAI,WAAuB,gBAAK;AAAA,EAC1C;AAEA,MAAI,OAAO;AACT,WAAO,oBAAC,SAAI,WAAW,GAAG,0DAA0D,SAAS,GAAI,gBAAK;AAAA,EACxG;AAEA,SACE,oBAAC,SAAI,WAAW,GAAG,0FAA0F,SAAS,GACpH,8BAAC,SAAI,WAAU,qCAAqC,gBAAK,GAC3D;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/service-header.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport interface ServiceHeaderProps {\n /** Rendered icon element, e.g. <Coins className=\"h-4 w-4 text-white\" />. */\n icon: ReactNode;\n title: string;\n subtitle: string;\n /** Optional element shown under the subtitle (e.g. a URL pill). */\n headerAccessory?: ReactNode;\n className?: string;\n /** Drop the brand gradient border for a plain neutral-bordered card.\n * Standalone headers (browse pages, coin page, /claim hub) keep the\n * gradient by default. */\n plain?: boolean;\n /** No card at all — icon chip + title + subtitle on the page background.\n * Used by ServiceFormShell so the form card is the page's only panel. */\n bare?: boolean;\n}\n\n/** Shared launchpad/claim page header: a dark card with a solid primary icon\n * chip, title + subtitle. By default it sits on a static brand gradient\n * border; pass `plain` for a neutral-bordered card. Pure presentation —\n * consuming app supplies the icon and (optionally) a header accessory. */\nexport function ServiceHeader({ icon, title, subtitle, headerAccessory, className, plain = false, bare = false }: ServiceHeaderProps) {\n const body = (\n <>\n <div className=\"flex items-center gap-2.5\">\n <div className=\"h-9 w-9 rounded-xl bg-primary flex items-center justify-center shrink-0\">\n {icon}\n </div>\n <h1 className=\"text-2xl sm:text-3xl font-semibold tracking-tight\">{title}</h1>\n </div>\n <p className=\"mt-1.5 text-sm text-muted-foreground max-w-xl\">{subtitle}</p>\n {headerAccessory && <div className=\"mt-4\">{headerAccessory}</div>}\n </>\n );\n\n if (bare) {\n return <div className={className}>{body}</div>;\n }\n\n if (plain) {\n return <div className={cn(\"rounded-2xl border border-border/60 bg-card p-6 sm:p-7\", className)}>{body}</div>;\n }\n\n return (\n <div className={cn(\"rounded-2xl p-[1.5px] bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose\", className)}>\n <div className=\"rounded-[15px] bg-card p-6 sm:p-7\">{body}</div>\n </div>\n );\n}\n"],"mappings":"AA0BI,mBAEI,KADF,YADF;AAzBJ,SAAS,UAAU;AAuBZ,SAAS,cAAc,EAAE,MAAM,OAAO,UAAU,iBAAiB,WAAW,QAAQ,OAAO,OAAO,MAAM,GAAuB;AACpI,QAAM,OACJ,iCACE;AAAA,yBAAC,SAAI,WAAU,6BACb;AAAA,0BAAC,SAAI,WAAU,2EACZ,gBACH;AAAA,MACA,oBAAC,QAAG,WAAU,qDAAqD,iBAAM;AAAA,OAC3E;AAAA,IACA,oBAAC,OAAE,WAAU,iDAAiD,oBAAS;AAAA,IACtE,mBAAmB,oBAAC,SAAI,WAAU,QAAQ,2BAAgB;AAAA,KAC7D;AAGF,MAAI,MAAM;AACR,WAAO,oBAAC,SAAI,WAAuB,gBAAK;AAAA,EAC1C;AAEA,MAAI,OAAO;AACT,WAAO,oBAAC,SAAI,WAAW,GAAG,0DAA0D,SAAS,GAAI,gBAAK;AAAA,EACxG;AAEA,SACE,oBAAC,SAAI,WAAW,GAAG,0FAA0F,SAAS,GACpH,8BAAC,SAAI,WAAU,qCAAqC,gBAAK,GAC3D;AAEJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medialane/ui",
3
- "version": "0.71.0",
3
+ "version": "0.72.0",
4
4
  "description": "Shared UI components for Medialane apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",