@medialane/ui 0.66.0 → 0.68.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.
- package/dist/components/launchpad-filter-bar.cjs +20 -5
- package/dist/components/launchpad-filter-bar.cjs.map +1 -1
- package/dist/components/launchpad-filter-bar.d.cts +2 -1
- package/dist/components/launchpad-filter-bar.d.ts +2 -1
- package/dist/components/launchpad-filter-bar.js +18 -3
- package/dist/components/launchpad-filter-bar.js.map +1 -1
- package/dist/components/launchpad-services.cjs +93 -35
- package/dist/components/launchpad-services.cjs.map +1 -1
- package/dist/components/launchpad-services.d.cts +18 -9
- package/dist/components/launchpad-services.d.ts +18 -9
- package/dist/components/launchpad-services.js +93 -36
- package/dist/components/launchpad-services.js.map +1 -1
- package/dist/components/nav-command-menu.cjs +1 -37
- package/dist/components/nav-command-menu.cjs.map +1 -1
- package/dist/components/nav-command-menu.js +1 -37
- package/dist/components/nav-command-menu.js.map +1 -1
- package/package.json +1 -1
|
@@ -23,9 +23,11 @@ __export(launchpad_filter_bar_exports, {
|
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(launchpad_filter_bar_exports);
|
|
25
25
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
26
|
+
var import_react = require("react");
|
|
26
27
|
var import_lucide_react = require("lucide-react");
|
|
27
28
|
var import_cn = require("../utils/cn.js");
|
|
28
|
-
var import_launchpad_services = require("
|
|
29
|
+
var import_launchpad_services = require("./launchpad-services.js");
|
|
30
|
+
var import_launchpad_services2 = require("../data/launchpad-services.js");
|
|
29
31
|
function LaunchpadFilterBar({
|
|
30
32
|
query,
|
|
31
33
|
onQueryChange,
|
|
@@ -34,8 +36,20 @@ function LaunchpadFilterBar({
|
|
|
34
36
|
onToggleGroup,
|
|
35
37
|
resultCount
|
|
36
38
|
}) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
+
const inputRef = (0, import_react.useRef)(null);
|
|
40
|
+
(0, import_react.useEffect)(() => {
|
|
41
|
+
const onKeyDown = (e) => {
|
|
42
|
+
if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
43
|
+
const target = e.target;
|
|
44
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) return;
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
inputRef.current?.focus();
|
|
47
|
+
};
|
|
48
|
+
window.addEventListener("keydown", onKeyDown);
|
|
49
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
50
|
+
}, []);
|
|
51
|
+
const countFor = (key) => import_launchpad_services2.LAUNCHPAD_SERVICE_DEFINITIONS.filter(
|
|
52
|
+
(d) => d.group === key && d.status === "live" && (0, import_launchpad_services.serviceMatchesQuery)(d, query)
|
|
39
53
|
).length;
|
|
40
54
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-center", children: [
|
|
41
55
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex-1 sm:max-w-xs", children: [
|
|
@@ -43,6 +57,7 @@ function LaunchpadFilterBar({
|
|
|
43
57
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
44
58
|
"input",
|
|
45
59
|
{
|
|
60
|
+
ref: inputRef,
|
|
46
61
|
type: "text",
|
|
47
62
|
value: query,
|
|
48
63
|
onChange: (e) => onQueryChange(e.target.value),
|
|
@@ -50,7 +65,7 @@ function LaunchpadFilterBar({
|
|
|
50
65
|
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"
|
|
51
66
|
}
|
|
52
67
|
),
|
|
53
|
-
query
|
|
68
|
+
query ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
54
69
|
"button",
|
|
55
70
|
{
|
|
56
71
|
type: "button",
|
|
@@ -59,7 +74,7 @@ function LaunchpadFilterBar({
|
|
|
59
74
|
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",
|
|
60
75
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "h-3.5 w-3.5" })
|
|
61
76
|
}
|
|
62
|
-
)
|
|
77
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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", children: "/" })
|
|
63
78
|
] }),
|
|
64
79
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-2 flex-1", children: groups.map((group) => {
|
|
65
80
|
const active = activeGroups.has(group.key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/launchpad-filter-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.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.\n */\nexport function LaunchpadFilterBar({\n query,\n onQueryChange,\n groups,\n activeGroups,\n onToggleGroup,\n resultCount,\n}: LaunchpadFilterBarProps) {\n const
|
|
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"]}
|
|
@@ -14,7 +14,8 @@ interface LaunchpadFilterBarProps {
|
|
|
14
14
|
* Search + group-filter bar above the launchpad grid. Fully controlled — all
|
|
15
15
|
* state (query, active groups) lives in the caller so the grid below reacts
|
|
16
16
|
* to the same state. Each pill shows the live count of services it matches
|
|
17
|
-
* under the current search query.
|
|
17
|
+
* under the current search query. Pressing "/" anywhere on the page focuses
|
|
18
|
+
* the search input.
|
|
18
19
|
*/
|
|
19
20
|
declare function LaunchpadFilterBar({ query, onQueryChange, groups, activeGroups, onToggleGroup, resultCount, }: LaunchpadFilterBarProps): react_jsx_runtime.JSX.Element;
|
|
20
21
|
|
|
@@ -14,7 +14,8 @@ interface LaunchpadFilterBarProps {
|
|
|
14
14
|
* Search + group-filter bar above the launchpad grid. Fully controlled — all
|
|
15
15
|
* state (query, active groups) lives in the caller so the grid below reacts
|
|
16
16
|
* to the same state. Each pill shows the live count of services it matches
|
|
17
|
-
* under the current search query.
|
|
17
|
+
* under the current search query. Pressing "/" anywhere on the page focuses
|
|
18
|
+
* the search input.
|
|
18
19
|
*/
|
|
19
20
|
declare function LaunchpadFilterBar({ query, onQueryChange, groups, activeGroups, onToggleGroup, resultCount, }: LaunchpadFilterBarProps): react_jsx_runtime.JSX.Element;
|
|
20
21
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
3
4
|
import { Search, X } from "lucide-react";
|
|
4
5
|
import { cn } from "../utils/cn.js";
|
|
6
|
+
import { serviceMatchesQuery } from "./launchpad-services.js";
|
|
5
7
|
import {
|
|
6
8
|
LAUNCHPAD_SERVICE_DEFINITIONS
|
|
7
9
|
} from "../data/launchpad-services.js";
|
|
@@ -13,8 +15,20 @@ function LaunchpadFilterBar({
|
|
|
13
15
|
onToggleGroup,
|
|
14
16
|
resultCount
|
|
15
17
|
}) {
|
|
18
|
+
const inputRef = useRef(null);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const onKeyDown = (e) => {
|
|
21
|
+
if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey) return;
|
|
22
|
+
const target = e.target;
|
|
23
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) return;
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
inputRef.current?.focus();
|
|
26
|
+
};
|
|
27
|
+
window.addEventListener("keydown", onKeyDown);
|
|
28
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
29
|
+
}, []);
|
|
16
30
|
const countFor = (key) => LAUNCHPAD_SERVICE_DEFINITIONS.filter(
|
|
17
|
-
(d) => d.group === key && d.status === "live" && (
|
|
31
|
+
(d) => d.group === key && d.status === "live" && serviceMatchesQuery(d, query)
|
|
18
32
|
).length;
|
|
19
33
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-center", children: [
|
|
20
34
|
/* @__PURE__ */ jsxs("div", { className: "relative flex-1 sm:max-w-xs", children: [
|
|
@@ -22,6 +36,7 @@ function LaunchpadFilterBar({
|
|
|
22
36
|
/* @__PURE__ */ jsx(
|
|
23
37
|
"input",
|
|
24
38
|
{
|
|
39
|
+
ref: inputRef,
|
|
25
40
|
type: "text",
|
|
26
41
|
value: query,
|
|
27
42
|
onChange: (e) => onQueryChange(e.target.value),
|
|
@@ -29,7 +44,7 @@ function LaunchpadFilterBar({
|
|
|
29
44
|
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"
|
|
30
45
|
}
|
|
31
46
|
),
|
|
32
|
-
query
|
|
47
|
+
query ? /* @__PURE__ */ jsx(
|
|
33
48
|
"button",
|
|
34
49
|
{
|
|
35
50
|
type: "button",
|
|
@@ -38,7 +53,7 @@ function LaunchpadFilterBar({
|
|
|
38
53
|
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",
|
|
39
54
|
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
40
55
|
}
|
|
41
|
-
)
|
|
56
|
+
) : /* @__PURE__ */ jsx("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", children: "/" })
|
|
42
57
|
] }),
|
|
43
58
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 flex-1", children: groups.map((group) => {
|
|
44
59
|
const active = activeGroups.has(group.key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/launchpad-filter-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport { Search, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.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.\n */\nexport function LaunchpadFilterBar({\n query,\n onQueryChange,\n groups,\n activeGroups,\n onToggleGroup,\n resultCount,\n}: LaunchpadFilterBarProps) {\n const
|
|
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":[]}
|
|
@@ -29,10 +29,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
30
|
var launchpad_services_exports = {};
|
|
31
31
|
__export(launchpad_services_exports, {
|
|
32
|
-
|
|
32
|
+
GROUP_SLICES: () => GROUP_SLICES,
|
|
33
33
|
LaunchpadGroupedSections: () => LaunchpadGroupedSections,
|
|
34
34
|
LaunchpadServiceCard: () => LaunchpadServiceCard,
|
|
35
35
|
SERVICE_HUES: () => SERVICE_HUES,
|
|
36
|
+
serviceMatchesQuery: () => serviceMatchesQuery,
|
|
36
37
|
useLaunchpadFilter: () => useLaunchpadFilter
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(launchpad_services_exports);
|
|
@@ -43,18 +44,38 @@ var import_framer_motion = require("framer-motion");
|
|
|
43
44
|
var import_lucide_react = require("lucide-react");
|
|
44
45
|
var import_cn = require("../utils/cn.js");
|
|
45
46
|
var import_launchpad_services = require("../data/launchpad-services.js");
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const DEFAULT_SLICE = {
|
|
48
|
+
chip: "bg-gradient-to-br from-muted to-muted",
|
|
49
|
+
marker: "text-muted-foreground/60",
|
|
49
50
|
hoverBorder: "sm:hover:border-border"
|
|
50
51
|
};
|
|
51
|
-
const
|
|
52
|
-
"nfts": {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
52
|
+
const GROUP_SLICES = {
|
|
53
|
+
"nfts": {
|
|
54
|
+
chip: "bg-gradient-to-br from-brand-blue to-brand-indigo",
|
|
55
|
+
marker: "text-brand-blue",
|
|
56
|
+
hoverBorder: "sm:hover:border-brand-blue/50"
|
|
57
|
+
},
|
|
58
|
+
"limited-editions": {
|
|
59
|
+
chip: "bg-gradient-to-br from-brand-indigo to-brand-purple",
|
|
60
|
+
marker: "text-brand-indigo",
|
|
61
|
+
hoverBorder: "sm:hover:border-brand-indigo/50"
|
|
62
|
+
},
|
|
63
|
+
"coins": {
|
|
64
|
+
chip: "bg-gradient-to-br from-brand-purple to-brand-rose",
|
|
65
|
+
marker: "text-brand-purple",
|
|
66
|
+
hoverBorder: "sm:hover:border-brand-purple/50"
|
|
67
|
+
},
|
|
68
|
+
"community": {
|
|
69
|
+
chip: "bg-gradient-to-br from-brand-rose to-brand-orange",
|
|
70
|
+
marker: "text-brand-rose",
|
|
71
|
+
hoverBorder: "sm:hover:border-brand-rose/50"
|
|
72
|
+
},
|
|
73
|
+
"claims": {
|
|
74
|
+
chip: "bg-gradient-to-br from-brand-orange to-brand-price",
|
|
75
|
+
marker: "text-brand-orange",
|
|
76
|
+
hoverBorder: "sm:hover:border-brand-orange/50"
|
|
77
|
+
},
|
|
78
|
+
"coming-soon": DEFAULT_SLICE
|
|
58
79
|
};
|
|
59
80
|
const SERVICE_HUES = {
|
|
60
81
|
"nfts": { text: "text-blue-600 dark:text-blue-400", solid: "bg-blue-600", border: "border-blue-500/30 dark:border-blue-400/25", tint: "bg-blue-500/10" },
|
|
@@ -72,37 +93,44 @@ const SERVICE_HUES = {
|
|
|
72
93
|
const GROUP_TITLE_BY_KEY = Object.fromEntries(
|
|
73
94
|
import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.map((g) => [g.key, g.title])
|
|
74
95
|
);
|
|
96
|
+
const GROUP_KEYS = new Set(import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.map((g) => g.key));
|
|
75
97
|
function LaunchpadServiceCard({ def, override = {}, index = 0 }) {
|
|
76
|
-
const { icon: Icon, title, group } = def;
|
|
98
|
+
const { icon: Icon, title, group, features, cta } = def;
|
|
77
99
|
const status = override.status ?? def.status;
|
|
78
100
|
const blurb = override.blurb ?? def.blurb;
|
|
79
101
|
const { href } = override;
|
|
80
102
|
const reduceMotion = (0, import_framer_motion.useReducedMotion)();
|
|
81
103
|
const live = status === "live";
|
|
82
|
-
const
|
|
104
|
+
const slice = GROUP_SLICES[group] ?? DEFAULT_SLICE;
|
|
83
105
|
const body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
84
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-
|
|
85
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("flex h-
|
|
86
|
-
live
|
|
106
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [
|
|
107
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("flex h-14 w-14 items-center justify-center rounded-2xl shadow-sm", live ? slice.chip : "bg-muted/40"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: (0, import_cn.cn)("h-7 w-7", live ? "text-white" : "text-muted-foreground/50") }) }),
|
|
108
|
+
!live && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60 pt-1", children: [
|
|
87
109
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Lock, { className: "h-3 w-3" }),
|
|
88
110
|
"Coming soon"
|
|
89
111
|
] })
|
|
90
112
|
] }),
|
|
91
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-
|
|
92
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
93
|
-
title,
|
|
94
|
-
live && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { className: "h-4 w-4 shrink-0 text-muted-foreground/40 transition-transform duration-200 sm:group-hover:translate-x-0.5 motion-reduce:transform-none" })
|
|
95
|
-
] }),
|
|
113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-auto space-y-2 pt-6", children: [
|
|
114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-2xl font-bold tracking-tight leading-tight", children: title }),
|
|
96
115
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: (0, import_cn.cn)("text-sm leading-relaxed", live ? "text-muted-foreground" : "text-muted-foreground/60"), children: blurb })
|
|
97
|
-
] })
|
|
116
|
+
] }),
|
|
117
|
+
live && features.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "space-y-1.5 pt-4", children: features.slice(0, 3).map((feature) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "flex items-start gap-2 text-xs text-muted-foreground leading-relaxed", children: [
|
|
118
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { className: (0, import_cn.cn)("h-3.5 w-3.5 shrink-0 mt-px", slice.marker) }),
|
|
119
|
+
feature
|
|
120
|
+
] }, feature)) }),
|
|
121
|
+
live && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pt-5", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-center gap-2 h-10 px-5 rounded-full text-sm font-semibold text-white bg-gradient-to-r from-brand-blue via-brand-purple to-brand-rose", children: [
|
|
122
|
+
cta,
|
|
123
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { className: "h-4 w-4 transition-transform duration-200 sm:group-hover:translate-x-0.5 motion-reduce:transform-none" })
|
|
124
|
+
] }) })
|
|
98
125
|
] });
|
|
99
126
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
100
127
|
import_framer_motion.motion.div,
|
|
101
128
|
{
|
|
102
129
|
layout: !reduceMotion,
|
|
103
|
-
initial: { opacity: 0, y: reduceMotion ? 0 :
|
|
130
|
+
initial: { opacity: 0, y: reduceMotion ? 0 : 16 },
|
|
104
131
|
animate: { opacity: 1, y: 0 },
|
|
105
132
|
exit: { opacity: 0 },
|
|
133
|
+
whileTap: live ? { scale: 0.97 } : void 0,
|
|
106
134
|
transition: { duration: 0.35, delay: index * 0.04, ease: [0.25, 0.46, 0.45, 0.94] },
|
|
107
135
|
className: "flex",
|
|
108
136
|
children: live && href ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -110,13 +138,13 @@ function LaunchpadServiceCard({ def, override = {}, index = 0 }) {
|
|
|
110
138
|
{
|
|
111
139
|
href,
|
|
112
140
|
className: (0, import_cn.cn)(
|
|
113
|
-
"group flex flex-1 flex-col
|
|
114
|
-
"transition-colors duration-200
|
|
115
|
-
|
|
141
|
+
"group flex flex-1 flex-col rounded-3xl border border-border/60 bg-card p-6 sm:aspect-[4/5]",
|
|
142
|
+
"transition-colors duration-200",
|
|
143
|
+
slice.hoverBorder
|
|
116
144
|
),
|
|
117
145
|
children: body
|
|
118
146
|
}
|
|
119
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-1 flex-col
|
|
147
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-1 flex-col rounded-3xl border border-border/30 bg-card p-6 sm:aspect-[4/5] opacity-75", children: body })
|
|
120
148
|
}
|
|
121
149
|
);
|
|
122
150
|
}
|
|
@@ -130,24 +158,52 @@ function ComingSoonStrip({ group, defs }) {
|
|
|
130
158
|
] }, key)) })
|
|
131
159
|
] });
|
|
132
160
|
}
|
|
161
|
+
function serviceMatchesQuery(def, query) {
|
|
162
|
+
if (query.trim() === "") return true;
|
|
163
|
+
const haystack = `${def.title} ${def.blurb} ${def.subtitle} ${GROUP_TITLE_BY_KEY[def.group] ?? ""}`.toLowerCase();
|
|
164
|
+
return haystack.includes(query.trim().toLowerCase());
|
|
165
|
+
}
|
|
166
|
+
function syncFilterUrl(query, groups) {
|
|
167
|
+
if (typeof window === "undefined") return;
|
|
168
|
+
const params = new URLSearchParams(window.location.search);
|
|
169
|
+
if (query.trim()) params.set("q", query.trim());
|
|
170
|
+
else params.delete("q");
|
|
171
|
+
if (groups.size > 0) params.set("groups", [...groups].join(","));
|
|
172
|
+
else params.delete("groups");
|
|
173
|
+
const qs = params.toString();
|
|
174
|
+
window.history.replaceState(null, "", qs ? `${window.location.pathname}?${qs}` : window.location.pathname);
|
|
175
|
+
}
|
|
133
176
|
function useLaunchpadFilter() {
|
|
134
|
-
const [query,
|
|
177
|
+
const [query, setQueryState] = (0, import_react.useState)("");
|
|
135
178
|
const [activeGroups, setActiveGroups] = (0, import_react.useState)(/* @__PURE__ */ new Set());
|
|
179
|
+
(0, import_react.useEffect)(() => {
|
|
180
|
+
const params = new URLSearchParams(window.location.search);
|
|
181
|
+
const q = params.get("q");
|
|
182
|
+
if (q) setQueryState(q);
|
|
183
|
+
const g = params.get("groups");
|
|
184
|
+
if (g) {
|
|
185
|
+
const groups = g.split(",").filter((k) => GROUP_KEYS.has(k));
|
|
186
|
+
if (groups.length > 0) setActiveGroups(new Set(groups));
|
|
187
|
+
}
|
|
188
|
+
}, []);
|
|
136
189
|
const filterableGroups = import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.filter((g) => g.key !== "coming-soon");
|
|
190
|
+
const setQuery = (value) => {
|
|
191
|
+
setQueryState(value);
|
|
192
|
+
syncFilterUrl(value, activeGroups);
|
|
193
|
+
};
|
|
137
194
|
const toggleGroup = (key) => {
|
|
138
195
|
setActiveGroups((prev) => {
|
|
139
196
|
const next = new Set(prev);
|
|
140
197
|
if (next.has(key)) next.delete(key);
|
|
141
198
|
else next.add(key);
|
|
199
|
+
syncFilterUrl(query, next);
|
|
142
200
|
return next;
|
|
143
201
|
});
|
|
144
202
|
};
|
|
145
203
|
const matches = (def) => {
|
|
146
204
|
if (activeGroups.size > 0 && !activeGroups.has(def.group)) return false;
|
|
147
205
|
if (def.status !== "live") return false;
|
|
148
|
-
|
|
149
|
-
const haystack = `${def.title} ${def.blurb} ${def.subtitle}`.toLowerCase();
|
|
150
|
-
return haystack.includes(query.trim().toLowerCase());
|
|
206
|
+
return serviceMatchesQuery(def, query);
|
|
151
207
|
};
|
|
152
208
|
const totalMatches = (0, import_react.useMemo)(
|
|
153
209
|
() => import_launchpad_services.LAUNCHPAD_SERVICE_DEFINITIONS.filter(matches).length,
|
|
@@ -155,14 +211,15 @@ function useLaunchpadFilter() {
|
|
|
155
211
|
[query, activeGroups]
|
|
156
212
|
);
|
|
157
213
|
const clear = () => {
|
|
158
|
-
|
|
214
|
+
setQueryState("");
|
|
159
215
|
setActiveGroups(/* @__PURE__ */ new Set());
|
|
216
|
+
syncFilterUrl("", /* @__PURE__ */ new Set());
|
|
160
217
|
};
|
|
161
218
|
return { query, setQuery, activeGroups, toggleGroup, filterableGroups, totalMatches, clear };
|
|
162
219
|
}
|
|
163
220
|
function LaunchpadGroupedSections({ overrides, query, activeGroups, onClearFilters, className }) {
|
|
164
221
|
const inActiveGroup = (d) => activeGroups.size === 0 || activeGroups.has(d.group);
|
|
165
|
-
const inSearch = (d) =>
|
|
222
|
+
const inSearch = (d) => serviceMatchesQuery(d, query);
|
|
166
223
|
const groupOrder = Object.fromEntries(import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.map((g, i) => [g.key, i]));
|
|
167
224
|
const liveDefs = import_launchpad_services.LAUNCHPAD_SERVICE_DEFINITIONS.filter((d) => d.group !== "coming-soon" && d.status === "live" && inActiveGroup(d) && inSearch(d)).sort((a, b) => (groupOrder[a.group] ?? 99) - (groupOrder[b.group] ?? 99));
|
|
168
225
|
const comingSoonGroup = import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.find((g) => g.key === "coming-soon");
|
|
@@ -182,16 +239,17 @@ function LaunchpadGroupedSections({ overrides, query, activeGroups, onClearFilte
|
|
|
182
239
|
}
|
|
183
240
|
)
|
|
184
241
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
185
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_framer_motion.motion.div, { layout: true, className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-
|
|
242
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_framer_motion.motion.div, { layout: true, className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5 sm:gap-6", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_framer_motion.AnimatePresence, { mode: "popLayout", children: liveDefs.map((def, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LaunchpadServiceCard, { def, override: overrides[def.key], index: i }, def.key)) }) }),
|
|
186
243
|
comingSoonGroup && comingSoonDefs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComingSoonStrip, { group: comingSoonGroup, defs: comingSoonDefs })
|
|
187
244
|
] }) });
|
|
188
245
|
}
|
|
189
246
|
// Annotate the CommonJS export names for ESM import in node:
|
|
190
247
|
0 && (module.exports = {
|
|
191
|
-
|
|
248
|
+
GROUP_SLICES,
|
|
192
249
|
LaunchpadGroupedSections,
|
|
193
250
|
LaunchpadServiceCard,
|
|
194
251
|
SERVICE_HUES,
|
|
252
|
+
serviceMatchesQuery,
|
|
195
253
|
useLaunchpadFilter
|
|
196
254
|
});
|
|
197
255
|
//# sourceMappingURL=launchpad-services.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/launchpad-services.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Launchpad services — the single source for the /launchpad page UI in\n * medialane-io and medialane-starknet.\n *\n * One dynamic grid (2026-07-13 redesign): one card per service, no group\n * sections. Groups exist as tags — a small accent label on the card and the\n * filter pills above the grid. The card is a single link to the service's own\n * page (its complete control surface); it carries no second link, no feature\n * chips, and no per-service color identity. Five group accents are the only\n * color voice, applied to the icon tile and the tag text.\n *\n * Filter state (search + group pills) lives in `useLaunchpadFilter()` so a\n * page can render `LaunchpadFilterBar` wherever it wants while\n * `LaunchpadGroupedSections` — fully controlled — renders the grid reacting\n * to that same state.\n *\n * Apps own: hrefs + per-app rollout status flips. Everything else lives here.\n */\n\nimport { useMemo, useState } from \"react\";\nimport Link from \"next/link\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport { Lock, ArrowRight } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport {\n LAUNCHPAD_SERVICE_DEFINITIONS,\n LAUNCHPAD_SERVICE_GROUPS,\n type ServiceDefinition,\n type ServiceGroup,\n type ServiceGroupDefinition,\n type ServiceStatus,\n} from \"../data/launchpad-services.js\";\n\n// ── Per-app injection points ─────────────────────────────────────────────────\n\nexport interface ServiceOverride {\n /** Primary destination — the whole card links here (required for live services) */\n href?: string;\n /** Legacy secondary link — the redesigned card renders a single link, so this is unused. */\n browseHref?: string;\n /** Per-app rollout flips (e.g. coins live on one app first) */\n status?: ServiceStatus;\n /** Per-app one-liner override (rarely needed) */\n blurb?: string;\n}\n\nexport type ServiceOverrides = Record<string, ServiceOverride>;\n\n// ── Group accents — the grid's only color voice ─────────────────────────────\n\ninterface GroupAccent {\n /** icon glyph + tag text tint */\n text: string;\n /** soft icon-tile background */\n tint: string;\n /** card border tint on desktop hover (polish only) */\n hoverBorder: string;\n}\n\nconst DEFAULT_ACCENT: GroupAccent = {\n text: \"text-muted-foreground\",\n tint: \"bg-muted/60\",\n hoverBorder: \"sm:hover:border-border\",\n};\n\nexport const GROUP_ACCENTS: Record<ServiceGroup, GroupAccent> = {\n \"nfts\": { text: \"text-brand-blue\", tint: \"bg-brand-blue/10\", hoverBorder: \"sm:hover:border-brand-blue/40\" },\n \"limited-editions\": { text: \"text-brand-purple\", tint: \"bg-brand-purple/10\", hoverBorder: \"sm:hover:border-brand-purple/40\" },\n \"community\": { text: \"text-emerald-600 dark:text-emerald-400\", tint: \"bg-emerald-500/10\", hoverBorder: \"sm:hover:border-emerald-500/40\" },\n \"coins\": { text: \"text-brand-orange\", tint: \"bg-brand-orange/10\", hoverBorder: \"sm:hover:border-brand-orange/40\" },\n \"claims\": { text: \"text-brand-rose\", tint: \"bg-brand-rose/10\", hoverBorder: \"sm:hover:border-brand-rose/40\" },\n \"coming-soon\": DEFAULT_ACCENT,\n};\n\n/** Legacy per-service hue table — kept for LaunchpadStrip (homepage). The grid\n * no longer uses it. */\ninterface ServiceHue {\n text: string;\n solid: string;\n border: string;\n tint: string;\n}\n\nexport const SERVICE_HUES: Record<string, ServiceHue> = {\n \"nfts\": { text: \"text-blue-600 dark:text-blue-400\", solid: \"bg-blue-600\", border: \"border-blue-500/30 dark:border-blue-400/25\", tint: \"bg-blue-500/10\" },\n \"limited-editions\": { text: \"text-purple-600 dark:text-purple-400\", solid: \"bg-purple-600\", border: \"border-purple-500/30 dark:border-purple-400/25\", tint: \"bg-purple-500/10\" },\n \"creator-coins\": { text: \"text-yellow-600 dark:text-yellow-400\", solid: \"bg-yellow-600\", border: \"border-yellow-500/30 dark:border-yellow-400/25\", tint: \"bg-yellow-500/10\" },\n \"claim-memecoin\": { text: \"text-orange-600 dark:text-orange-400\", solid: \"bg-orange-600\", border: \"border-orange-500/30 dark:border-orange-400/25\", tint: \"bg-orange-500/10\" },\n \"collection-drop\": { text: \"text-orange-600 dark:text-orange-400\", solid: \"bg-orange-600\", border: \"border-orange-500/30 dark:border-orange-400/25\", tint: \"bg-orange-500/10\" },\n \"pop-protocol\": { text: \"text-emerald-600 dark:text-emerald-400\", solid: \"bg-emerald-600\", border: \"border-emerald-500/30 dark:border-emerald-400/25\", tint: \"bg-emerald-500/10\" },\n \"ip-tickets\": { text: \"text-teal-600 dark:text-teal-400\", solid: \"bg-teal-600\", border: \"border-teal-500/30 dark:border-teal-400/25\", tint: \"bg-teal-500/10\" },\n \"remix-asset\": { text: \"text-indigo-600 dark:text-indigo-400\", solid: \"bg-indigo-600\", border: \"border-indigo-500/30 dark:border-indigo-400/25\", tint: \"bg-indigo-500/10\" },\n \"claim-username\": { text: \"text-violet-600 dark:text-violet-400\", solid: \"bg-violet-600\", border: \"border-violet-500/30 dark:border-violet-400/25\", tint: \"bg-violet-500/10\" },\n \"claim-collection\": { text: \"text-cyan-600 dark:text-cyan-400\", solid: \"bg-cyan-600\", border: \"border-cyan-500/30 dark:border-cyan-400/25\", tint: \"bg-cyan-500/10\" },\n \"claim-collection-name\": { text: \"text-pink-600 dark:text-pink-400\", solid: \"bg-pink-600\", border: \"border-pink-500/30 dark:border-pink-400/25\", tint: \"bg-pink-500/10\" },\n};\n\nconst GROUP_TITLE_BY_KEY = Object.fromEntries(\n LAUNCHPAD_SERVICE_GROUPS.map((g) => [g.key, g.title]),\n) as Record<ServiceGroup, string>;\n\n// ── Service card — one link, one accent, title + one sentence ────────────────\n\nexport interface LaunchpadServiceCardProps {\n def: ServiceDefinition;\n override?: ServiceOverride;\n /** Grid position — drives the staggered entrance reveal. */\n index?: number;\n}\n\nexport function LaunchpadServiceCard({ def, override = {}, index = 0 }: LaunchpadServiceCardProps) {\n const { icon: Icon, title, group } = def;\n const status = override.status ?? def.status;\n const blurb = override.blurb ?? def.blurb;\n const { href } = override;\n const reduceMotion = useReducedMotion();\n\n const live = status === \"live\";\n const accent = GROUP_ACCENTS[group] ?? DEFAULT_ACCENT;\n\n const body = (\n <>\n <div className=\"flex items-center justify-between gap-3\">\n <span className={cn(\"flex h-10 w-10 items-center justify-center rounded-xl\", live ? accent.tint : \"bg-muted/40\")}>\n <Icon className={cn(\"h-5 w-5\", live ? accent.text : \"text-muted-foreground/50\")} />\n </span>\n {live ? (\n <span className={cn(\"text-[10px] font-semibold uppercase tracking-widest\", accent.text)}>\n {GROUP_TITLE_BY_KEY[group]}\n </span>\n ) : (\n <span className=\"flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60\">\n <Lock className=\"h-3 w-3\" />\n Coming soon\n </span>\n )}\n </div>\n <div className=\"space-y-1\">\n <h3 className=\"flex items-center gap-1.5 text-lg font-bold tracking-tight leading-snug\">\n {title}\n {live && (\n <ArrowRight className=\"h-4 w-4 shrink-0 text-muted-foreground/40 transition-transform duration-200 sm:group-hover:translate-x-0.5 motion-reduce:transform-none\" />\n )}\n </h3>\n <p className={cn(\"text-sm leading-relaxed\", live ? \"text-muted-foreground\" : \"text-muted-foreground/60\")}>\n {blurb}\n </p>\n </div>\n </>\n );\n\n return (\n <motion.div\n layout={!reduceMotion}\n initial={{ opacity: 0, y: reduceMotion ? 0 : 12 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.35, delay: index * 0.04, ease: [0.25, 0.46, 0.45, 0.94] }}\n className=\"flex\"\n >\n {live && href ? (\n <Link\n href={href}\n className={cn(\n \"group flex flex-1 flex-col gap-3 rounded-2xl border border-border/60 bg-card p-5\",\n \"transition-colors duration-200 active:scale-[0.99] motion-reduce:transform-none\",\n accent.hoverBorder,\n )}\n >\n {body}\n </Link>\n ) : (\n <div className=\"flex flex-1 flex-col gap-3 rounded-2xl border border-border/30 bg-card p-5 opacity-75\">\n {body}\n </div>\n )}\n </motion.div>\n );\n}\n\n// ── Coming-soon strip ────────────────────────────────────────────────────────\n\nfunction ComingSoonStrip({ group, defs }: { group: ServiceGroupDefinition; defs: ServiceDefinition[] }) {\n return (\n <div className=\"rounded-2xl border border-border/40 p-5\">\n <p className=\"font-semibold text-sm\">{group.title}</p>\n <p className=\"text-sm text-muted-foreground mt-0.5\">{group.tagline}</p>\n <div className=\"flex flex-wrap gap-2 mt-4\">\n {defs.map(({ key, icon: Icon, title }) => (\n <div key={key} className=\"flex items-center gap-2 px-3 py-2 rounded-full bg-muted/30 border border-border/25\">\n <Icon className=\"h-3.5 w-3.5 text-muted-foreground/60\" />\n <span className=\"text-xs font-medium text-muted-foreground\">{title}</span>\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n// ── Filter state ─────────────────────────────────────────────────────────────\n\n/**\n * Owns the search + group-filter state for the launchpad page. A page renders\n * `LaunchpadFilterBar` wherever it wants (e.g. right under the h1) wired to\n * this hook's fields, then passes `query`/`activeGroups` straight through to\n * `LaunchpadGroupedSections` so the grid reacts to the same state.\n */\nexport function useLaunchpadFilter() {\n const [query, setQuery] = useState(\"\");\n const [activeGroups, setActiveGroups] = useState<Set<ServiceGroup>>(new Set());\n\n const filterableGroups = LAUNCHPAD_SERVICE_GROUPS.filter((g) => g.key !== \"coming-soon\");\n\n const toggleGroup = (key: ServiceGroup) => {\n setActiveGroups((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n };\n\n const matches = (def: ServiceDefinition): boolean => {\n if (activeGroups.size > 0 && !activeGroups.has(def.group)) return false;\n if (def.status !== \"live\") return false;\n if (query.trim() === \"\") return true;\n const haystack = `${def.title} ${def.blurb} ${def.subtitle}`.toLowerCase();\n return haystack.includes(query.trim().toLowerCase());\n };\n\n const totalMatches = useMemo(\n () => LAUNCHPAD_SERVICE_DEFINITIONS.filter(matches).length,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [query, activeGroups],\n );\n\n const clear = () => { setQuery(\"\"); setActiveGroups(new Set()); };\n\n return { query, setQuery, activeGroups, toggleGroup, filterableGroups, totalMatches, clear };\n}\n\n// ── The grid ─────────────────────────────────────────────────────────────────\n\nexport interface LaunchpadGroupedSectionsProps {\n /** Per-app hrefs / rollout flips, keyed by service key. */\n overrides: ServiceOverrides;\n /** Current search query — from `useLaunchpadFilter()`. */\n query: string;\n /** Current active group filters — from `useLaunchpadFilter()`. */\n activeGroups: Set<ServiceGroup>;\n /** Reset both — from `useLaunchpadFilter()`. */\n onClearFilters: () => void;\n className?: string;\n}\n\n/** The full launchpad services grid — one dynamic grid of all live services,\n * ordered by group, plus the coming-soon strip. Fully controlled by\n * `useLaunchpadFilter()` state passed in from the page. */\nexport function LaunchpadGroupedSections({ overrides, query, activeGroups, onClearFilters, className }: LaunchpadGroupedSectionsProps) {\n const inActiveGroup = (d: ServiceDefinition) => activeGroups.size === 0 || activeGroups.has(d.group);\n const inSearch = (d: ServiceDefinition) =>\n query.trim() === \"\" ||\n `${d.title} ${d.blurb} ${d.subtitle}`.toLowerCase().includes(query.trim().toLowerCase());\n\n const groupOrder = Object.fromEntries(LAUNCHPAD_SERVICE_GROUPS.map((g, i) => [g.key, i]));\n\n const liveDefs = LAUNCHPAD_SERVICE_DEFINITIONS\n .filter((d) => d.group !== \"coming-soon\" && d.status === \"live\" && inActiveGroup(d) && inSearch(d))\n .sort((a, b) => (groupOrder[a.group] ?? 99) - (groupOrder[b.group] ?? 99));\n\n const comingSoonGroup = LAUNCHPAD_SERVICE_GROUPS.find((g) => g.key === \"coming-soon\");\n const comingSoonDefs = LAUNCHPAD_SERVICE_DEFINITIONS.filter(\n (d) => d.group === \"coming-soon\" && inActiveGroup(d) && inSearch(d),\n );\n\n return (\n <div className={cn(\"space-y-8\", className)}>\n {liveDefs.length === 0 && comingSoonDefs.length === 0 ? (\n <div className=\"text-center py-16 space-y-3\">\n <p className=\"text-lg font-semibold\">No services match</p>\n <p className=\"text-sm text-muted-foreground\">Try a different search or clear your filters.</p>\n <button\n type=\"button\"\n onClick={onClearFilters}\n className=\"inline-flex items-center h-9 px-4 rounded-full text-sm font-semibold bg-primary text-primary-foreground\"\n >\n Clear filters\n </button>\n </div>\n ) : (\n <>\n <motion.div layout className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n <AnimatePresence mode=\"popLayout\">\n {liveDefs.map((def, i) => (\n <LaunchpadServiceCard key={def.key} def={def} override={overrides[def.key]} index={i} />\n ))}\n </AnimatePresence>\n </motion.div>\n {comingSoonGroup && comingSoonDefs.length > 0 && (\n <ComingSoonStrip group={comingSoonGroup} defs={comingSoonDefs} />\n )}\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["Link"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/launchpad-services.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Launchpad services — the single source for the /launchpad page UI in\n * medialane-io and medialane-starknet.\n *\n * One dynamic grid (2026-07-13 redesign): one card per service, no group\n * sections. Groups exist as tags — a small accent label on the card and the\n * filter pills above the grid. The card is a single link to the service's own\n * page (its complete control surface); it carries no second link, no feature\n * chips, and no per-service color identity. Five group accents are the only\n * color voice, applied to the icon tile and the tag text.\n *\n * Filter state (search + group pills) lives in `useLaunchpadFilter()` so a\n * page can render `LaunchpadFilterBar` wherever it wants while\n * `LaunchpadGroupedSections` — fully controlled — renders the grid reacting\n * to that same state.\n *\n * Apps own: hrefs + per-app rollout status flips. Everything else lives here.\n */\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport Link from \"next/link\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport { Lock, ArrowRight, Check } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport {\n LAUNCHPAD_SERVICE_DEFINITIONS,\n LAUNCHPAD_SERVICE_GROUPS,\n type ServiceDefinition,\n type ServiceGroup,\n type ServiceGroupDefinition,\n type ServiceStatus,\n} from \"../data/launchpad-services.js\";\n\n// ── Per-app injection points ─────────────────────────────────────────────────\n\nexport interface ServiceOverride {\n /** Primary destination — the whole card links here (required for live services) */\n href?: string;\n /** Legacy secondary link — the redesigned card renders a single link, so this is unused. */\n browseHref?: string;\n /** Per-app rollout flips (e.g. coins live on one app first) */\n status?: ServiceStatus;\n /** Per-app one-liner override (rarely needed) */\n blurb?: string;\n}\n\nexport type ServiceOverrides = Record<string, ServiceOverride>;\n\n// ── Group accents — the grid's only color voice ─────────────────────────────\n\n/** The deck is the brand spectrum: each group owns one slice of the Medialane\n * gradient (blue → indigo → purple → rose → orange), so the grid read in\n * group order draws the full spectrum across the launchpad. The slice colors\n * the icon chip, the use-case markers, and the desktop hover border — never\n * text labels. */\ninterface GroupSlice {\n /** icon chip fill — a two-stop slice of the brand gradient */\n chip: string;\n /** use-case marker tint */\n marker: string;\n /** card border tint on desktop hover (polish only) */\n hoverBorder: string;\n}\n\nconst DEFAULT_SLICE: GroupSlice = {\n chip: \"bg-gradient-to-br from-muted to-muted\",\n marker: \"text-muted-foreground/60\",\n hoverBorder: \"sm:hover:border-border\",\n};\n\nexport const GROUP_SLICES: Record<ServiceGroup, GroupSlice> = {\n \"nfts\": {\n chip: \"bg-gradient-to-br from-brand-blue to-brand-indigo\",\n marker: \"text-brand-blue\",\n hoverBorder: \"sm:hover:border-brand-blue/50\",\n },\n \"limited-editions\": {\n chip: \"bg-gradient-to-br from-brand-indigo to-brand-purple\",\n marker: \"text-brand-indigo\",\n hoverBorder: \"sm:hover:border-brand-indigo/50\",\n },\n \"coins\": {\n chip: \"bg-gradient-to-br from-brand-purple to-brand-rose\",\n marker: \"text-brand-purple\",\n hoverBorder: \"sm:hover:border-brand-purple/50\",\n },\n \"community\": {\n chip: \"bg-gradient-to-br from-brand-rose to-brand-orange\",\n marker: \"text-brand-rose\",\n hoverBorder: \"sm:hover:border-brand-rose/50\",\n },\n \"claims\": {\n chip: \"bg-gradient-to-br from-brand-orange to-brand-price\",\n marker: \"text-brand-orange\",\n hoverBorder: \"sm:hover:border-brand-orange/50\",\n },\n \"coming-soon\": DEFAULT_SLICE,\n};\n\n/** Legacy per-service hue table — kept for LaunchpadStrip (homepage). The grid\n * no longer uses it. */\ninterface ServiceHue {\n text: string;\n solid: string;\n border: string;\n tint: string;\n}\n\nexport const SERVICE_HUES: Record<string, ServiceHue> = {\n \"nfts\": { text: \"text-blue-600 dark:text-blue-400\", solid: \"bg-blue-600\", border: \"border-blue-500/30 dark:border-blue-400/25\", tint: \"bg-blue-500/10\" },\n \"limited-editions\": { text: \"text-purple-600 dark:text-purple-400\", solid: \"bg-purple-600\", border: \"border-purple-500/30 dark:border-purple-400/25\", tint: \"bg-purple-500/10\" },\n \"creator-coins\": { text: \"text-yellow-600 dark:text-yellow-400\", solid: \"bg-yellow-600\", border: \"border-yellow-500/30 dark:border-yellow-400/25\", tint: \"bg-yellow-500/10\" },\n \"claim-memecoin\": { text: \"text-orange-600 dark:text-orange-400\", solid: \"bg-orange-600\", border: \"border-orange-500/30 dark:border-orange-400/25\", tint: \"bg-orange-500/10\" },\n \"collection-drop\": { text: \"text-orange-600 dark:text-orange-400\", solid: \"bg-orange-600\", border: \"border-orange-500/30 dark:border-orange-400/25\", tint: \"bg-orange-500/10\" },\n \"pop-protocol\": { text: \"text-emerald-600 dark:text-emerald-400\", solid: \"bg-emerald-600\", border: \"border-emerald-500/30 dark:border-emerald-400/25\", tint: \"bg-emerald-500/10\" },\n \"ip-tickets\": { text: \"text-teal-600 dark:text-teal-400\", solid: \"bg-teal-600\", border: \"border-teal-500/30 dark:border-teal-400/25\", tint: \"bg-teal-500/10\" },\n \"remix-asset\": { text: \"text-indigo-600 dark:text-indigo-400\", solid: \"bg-indigo-600\", border: \"border-indigo-500/30 dark:border-indigo-400/25\", tint: \"bg-indigo-500/10\" },\n \"claim-username\": { text: \"text-violet-600 dark:text-violet-400\", solid: \"bg-violet-600\", border: \"border-violet-500/30 dark:border-violet-400/25\", tint: \"bg-violet-500/10\" },\n \"claim-collection\": { text: \"text-cyan-600 dark:text-cyan-400\", solid: \"bg-cyan-600\", border: \"border-cyan-500/30 dark:border-cyan-400/25\", tint: \"bg-cyan-500/10\" },\n \"claim-collection-name\": { text: \"text-pink-600 dark:text-pink-400\", solid: \"bg-pink-600\", border: \"border-pink-500/30 dark:border-pink-400/25\", tint: \"bg-pink-500/10\" },\n};\n\nconst GROUP_TITLE_BY_KEY = Object.fromEntries(\n LAUNCHPAD_SERVICE_GROUPS.map((g) => [g.key, g.title]),\n) as Record<ServiceGroup, string>;\n\nconst GROUP_KEYS = new Set(LAUNCHPAD_SERVICE_GROUPS.map((g) => g.key));\n\n// ── Service card — one link, one accent, title + one sentence ────────────────\n\nexport interface LaunchpadServiceCardProps {\n def: ServiceDefinition;\n override?: ServiceOverride;\n /** Grid position — drives the staggered entrance reveal. */\n index?: number;\n}\n\nexport function LaunchpadServiceCard({ def, override = {}, index = 0 }: LaunchpadServiceCardProps) {\n const { icon: Icon, title, group, features, cta } = def;\n const status = override.status ?? def.status;\n const blurb = override.blurb ?? def.blurb;\n const { href } = override;\n const reduceMotion = useReducedMotion();\n\n const live = status === \"live\";\n const slice = GROUP_SLICES[group] ?? DEFAULT_SLICE;\n\n const body = (\n <>\n {/* Icon chip — the group's slice of the brand spectrum */}\n <div className=\"flex items-start justify-between gap-3\">\n <span className={cn(\"flex h-14 w-14 items-center justify-center rounded-2xl shadow-sm\", live ? slice.chip : \"bg-muted/40\")}>\n <Icon className={cn(\"h-7 w-7\", live ? \"text-white\" : \"text-muted-foreground/50\")} />\n </span>\n {!live && (\n <span className=\"flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60 pt-1\">\n <Lock className=\"h-3 w-3\" />\n Coming soon\n </span>\n )}\n </div>\n\n {/* Title + one sentence */}\n <div className=\"mt-auto space-y-2 pt-6\">\n <h3 className=\"text-2xl font-bold tracking-tight leading-tight\">{title}</h3>\n <p className={cn(\"text-sm leading-relaxed\", live ? \"text-muted-foreground\" : \"text-muted-foreground/60\")}>\n {blurb}\n </p>\n </div>\n\n {/* Use cases */}\n {live && features.length > 0 && (\n <ul className=\"space-y-1.5 pt-4\">\n {features.slice(0, 3).map((feature) => (\n <li key={feature} className=\"flex items-start gap-2 text-xs text-muted-foreground leading-relaxed\">\n <Check className={cn(\"h-3.5 w-3.5 shrink-0 mt-px\", slice.marker)} />\n {feature}\n </li>\n ))}\n </ul>\n )}\n\n {/* The action — one vivid full-spectrum pill, identical on every card */}\n {live && (\n <div className=\"pt-5\">\n <span className=\"inline-flex items-center gap-2 h-10 px-5 rounded-full text-sm font-semibold text-white bg-gradient-to-r from-brand-blue via-brand-purple to-brand-rose\">\n {cta}\n <ArrowRight className=\"h-4 w-4 transition-transform duration-200 sm:group-hover:translate-x-0.5 motion-reduce:transform-none\" />\n </span>\n </div>\n )}\n </>\n );\n\n return (\n <motion.div\n layout={!reduceMotion}\n initial={{ opacity: 0, y: reduceMotion ? 0 : 16 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0 }}\n whileTap={live ? { scale: 0.97 } : undefined}\n transition={{ duration: 0.35, delay: index * 0.04, ease: [0.25, 0.46, 0.45, 0.94] }}\n className=\"flex\"\n >\n {live && href ? (\n <Link\n href={href}\n className={cn(\n \"group flex flex-1 flex-col rounded-3xl border border-border/60 bg-card p-6 sm:aspect-[4/5]\",\n \"transition-colors duration-200\",\n slice.hoverBorder,\n )}\n >\n {body}\n </Link>\n ) : (\n <div className=\"flex flex-1 flex-col rounded-3xl border border-border/30 bg-card p-6 sm:aspect-[4/5] opacity-75\">\n {body}\n </div>\n )}\n </motion.div>\n );\n}\n\n// ── Coming-soon strip ────────────────────────────────────────────────────────\n\nfunction ComingSoonStrip({ group, defs }: { group: ServiceGroupDefinition; defs: ServiceDefinition[] }) {\n return (\n <div className=\"rounded-2xl border border-border/40 p-5\">\n <p className=\"font-semibold text-sm\">{group.title}</p>\n <p className=\"text-sm text-muted-foreground mt-0.5\">{group.tagline}</p>\n <div className=\"flex flex-wrap gap-2 mt-4\">\n {defs.map(({ key, icon: Icon, title }) => (\n <div key={key} className=\"flex items-center gap-2 px-3 py-2 rounded-full bg-muted/30 border border-border/25\">\n <Icon className=\"h-3.5 w-3.5 text-muted-foreground/60\" />\n <span className=\"text-xs font-medium text-muted-foreground\">{title}</span>\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n// ── Filter state ─────────────────────────────────────────────────────────────\n\n/** Shared search predicate — group titles count as search terms so a query\n * like \"editions\" or \"community\" finds the whole group. */\nexport function serviceMatchesQuery(def: ServiceDefinition, query: string): boolean {\n if (query.trim() === \"\") return true;\n const haystack = `${def.title} ${def.blurb} ${def.subtitle} ${GROUP_TITLE_BY_KEY[def.group] ?? \"\"}`.toLowerCase();\n return haystack.includes(query.trim().toLowerCase());\n}\n\n/** Sync filter state into the URL (?q=…&groups=…) via replaceState — the\n * filtered launchpad becomes shareable and survives reload, without touching\n * the router (this package is router-agnostic). */\nfunction syncFilterUrl(query: string, groups: Set<ServiceGroup>) {\n if (typeof window === \"undefined\") return;\n const params = new URLSearchParams(window.location.search);\n if (query.trim()) params.set(\"q\", query.trim());\n else params.delete(\"q\");\n if (groups.size > 0) params.set(\"groups\", [...groups].join(\",\"));\n else params.delete(\"groups\");\n const qs = params.toString();\n window.history.replaceState(null, \"\", qs ? `${window.location.pathname}?${qs}` : window.location.pathname);\n}\n\n/**\n * Owns the search + group-filter state for the launchpad page. A page renders\n * `LaunchpadFilterBar` wherever it wants (e.g. right under the h1) wired to\n * this hook's fields, then passes `query`/`activeGroups` straight through to\n * `LaunchpadGroupedSections` so the grid reacts to the same state.\n * State is mirrored into the URL (?q=…&groups=…) so a filtered view can be\n * shared and survives reload.\n */\nexport function useLaunchpadFilter() {\n const [query, setQueryState] = useState(\"\");\n const [activeGroups, setActiveGroups] = useState<Set<ServiceGroup>>(new Set());\n\n // Hydrate once from the URL (client only).\n useEffect(() => {\n const params = new URLSearchParams(window.location.search);\n const q = params.get(\"q\");\n if (q) setQueryState(q);\n const g = params.get(\"groups\");\n if (g) {\n const groups = g.split(\",\").filter((k): k is ServiceGroup => GROUP_KEYS.has(k as ServiceGroup));\n if (groups.length > 0) setActiveGroups(new Set(groups));\n }\n }, []);\n\n const filterableGroups = LAUNCHPAD_SERVICE_GROUPS.filter((g) => g.key !== \"coming-soon\");\n\n const setQuery = (value: string) => {\n setQueryState(value);\n syncFilterUrl(value, activeGroups);\n };\n\n const toggleGroup = (key: ServiceGroup) => {\n setActiveGroups((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n syncFilterUrl(query, next);\n return next;\n });\n };\n\n const matches = (def: ServiceDefinition): boolean => {\n if (activeGroups.size > 0 && !activeGroups.has(def.group)) return false;\n if (def.status !== \"live\") return false;\n return serviceMatchesQuery(def, query);\n };\n\n const totalMatches = useMemo(\n () => LAUNCHPAD_SERVICE_DEFINITIONS.filter(matches).length,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [query, activeGroups],\n );\n\n const clear = () => {\n setQueryState(\"\");\n setActiveGroups(new Set());\n syncFilterUrl(\"\", new Set());\n };\n\n return { query, setQuery, activeGroups, toggleGroup, filterableGroups, totalMatches, clear };\n}\n\n// ── The grid ─────────────────────────────────────────────────────────────────\n\nexport interface LaunchpadGroupedSectionsProps {\n /** Per-app hrefs / rollout flips, keyed by service key. */\n overrides: ServiceOverrides;\n /** Current search query — from `useLaunchpadFilter()`. */\n query: string;\n /** Current active group filters — from `useLaunchpadFilter()`. */\n activeGroups: Set<ServiceGroup>;\n /** Reset both — from `useLaunchpadFilter()`. */\n onClearFilters: () => void;\n className?: string;\n}\n\n/** The full launchpad services grid — one dynamic grid of all live services,\n * ordered by group, plus the coming-soon strip. Fully controlled by\n * `useLaunchpadFilter()` state passed in from the page. */\nexport function LaunchpadGroupedSections({ overrides, query, activeGroups, onClearFilters, className }: LaunchpadGroupedSectionsProps) {\n const inActiveGroup = (d: ServiceDefinition) => activeGroups.size === 0 || activeGroups.has(d.group);\n const inSearch = (d: ServiceDefinition) => serviceMatchesQuery(d, query);\n\n const groupOrder = Object.fromEntries(LAUNCHPAD_SERVICE_GROUPS.map((g, i) => [g.key, i]));\n\n const liveDefs = LAUNCHPAD_SERVICE_DEFINITIONS\n .filter((d) => d.group !== \"coming-soon\" && d.status === \"live\" && inActiveGroup(d) && inSearch(d))\n .sort((a, b) => (groupOrder[a.group] ?? 99) - (groupOrder[b.group] ?? 99));\n\n const comingSoonGroup = LAUNCHPAD_SERVICE_GROUPS.find((g) => g.key === \"coming-soon\");\n const comingSoonDefs = LAUNCHPAD_SERVICE_DEFINITIONS.filter(\n (d) => d.group === \"coming-soon\" && inActiveGroup(d) && inSearch(d),\n );\n\n return (\n <div className={cn(\"space-y-8\", className)}>\n {liveDefs.length === 0 && comingSoonDefs.length === 0 ? (\n <div className=\"text-center py-16 space-y-3\">\n <p className=\"text-lg font-semibold\">No services match</p>\n <p className=\"text-sm text-muted-foreground\">Try a different search or clear your filters.</p>\n <button\n type=\"button\"\n onClick={onClearFilters}\n className=\"inline-flex items-center h-9 px-4 rounded-full text-sm font-semibold bg-primary text-primary-foreground\"\n >\n Clear filters\n </button>\n </div>\n ) : (\n <>\n <motion.div layout className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5 sm:gap-6\">\n <AnimatePresence mode=\"popLayout\">\n {liveDefs.map((def, i) => (\n <LaunchpadServiceCard key={def.key} def={def} override={overrides[def.key]} index={i} />\n ))}\n </AnimatePresence>\n </motion.div>\n {comingSoonGroup && comingSoonDefs.length > 0 && (\n <ComingSoonStrip group={comingSoonGroup} defs={comingSoonDefs} />\n )}\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["Link"]}
|