@medialane/ui 0.65.1 → 0.67.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 +28 -5
- package/dist/components/launchpad-filter-bar.cjs.map +1 -1
- package/dist/components/launchpad-filter-bar.d.cts +5 -4
- package/dist/components/launchpad-filter-bar.d.ts +5 -4
- package/dist/components/launchpad-filter-bar.js +30 -5
- package/dist/components/launchpad-filter-bar.js.map +1 -1
- package/dist/components/launchpad-services.cjs +101 -152
- package/dist/components/launchpad-services.cjs.map +1 -1
- package/dist/components/launchpad-services.d.cts +23 -14
- package/dist/components/launchpad-services.d.ts +23 -14
- package/dist/components/launchpad-services.js +102 -155
- package/dist/components/launchpad-services.js.map +1 -1
- package/dist/components/launchpad-strip.cjs +5 -5
- package/dist/components/launchpad-strip.cjs.map +1 -1
- package/dist/components/launchpad-strip.js +5 -5
- package/dist/components/launchpad-strip.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/dist/data/launchpad-services.cjs +45 -84
- package/dist/data/launchpad-services.cjs.map +1 -1
- package/dist/data/launchpad-services.d.cts +2 -2
- package/dist/data/launchpad-services.d.ts +2 -2
- package/dist/data/launchpad-services.js +45 -85
- package/dist/data/launchpad-services.js.map +1 -1
- package/package.json +1 -1
|
@@ -23,8 +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");
|
|
29
|
+
var import_launchpad_services = require("./launchpad-services.js");
|
|
30
|
+
var import_launchpad_services2 = require("../data/launchpad-services.js");
|
|
28
31
|
function LaunchpadFilterBar({
|
|
29
32
|
query,
|
|
30
33
|
onQueryChange,
|
|
@@ -33,12 +36,28 @@ function LaunchpadFilterBar({
|
|
|
33
36
|
onToggleGroup,
|
|
34
37
|
resultCount
|
|
35
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)
|
|
53
|
+
).length;
|
|
36
54
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-center", children: [
|
|
37
55
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex-1 sm:max-w-xs", children: [
|
|
38
56
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" }),
|
|
39
57
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
40
58
|
"input",
|
|
41
59
|
{
|
|
60
|
+
ref: inputRef,
|
|
42
61
|
type: "text",
|
|
43
62
|
value: query,
|
|
44
63
|
onChange: (e) => onQueryChange(e.target.value),
|
|
@@ -46,7 +65,7 @@ function LaunchpadFilterBar({
|
|
|
46
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"
|
|
47
66
|
}
|
|
48
67
|
),
|
|
49
|
-
query
|
|
68
|
+
query ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
50
69
|
"button",
|
|
51
70
|
{
|
|
52
71
|
type: "button",
|
|
@@ -55,21 +74,25 @@ function LaunchpadFilterBar({
|
|
|
55
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",
|
|
56
75
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "h-3.5 w-3.5" })
|
|
57
76
|
}
|
|
58
|
-
)
|
|
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: "/" })
|
|
59
78
|
] }),
|
|
60
79
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-2 flex-1", children: groups.map((group) => {
|
|
61
80
|
const active = activeGroups.has(group.key);
|
|
62
|
-
|
|
81
|
+
const count = countFor(group.key);
|
|
82
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
63
83
|
"button",
|
|
64
84
|
{
|
|
65
85
|
type: "button",
|
|
66
86
|
onClick: () => onToggleGroup(group.key),
|
|
67
87
|
"aria-pressed": active,
|
|
68
88
|
className: (0, import_cn.cn)(
|
|
69
|
-
"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border",
|
|
89
|
+
"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border inline-flex items-center gap-1.5",
|
|
70
90
|
active ? "bg-gradient-to-r from-brand-purple to-brand-blue text-white border-transparent shadow-sm" : "bg-card text-muted-foreground border-border active:bg-muted/60 sm:hover:bg-muted/40"
|
|
71
91
|
),
|
|
72
|
-
children:
|
|
92
|
+
children: [
|
|
93
|
+
group.title,
|
|
94
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("tabular-nums", active ? "text-white/70" : "text-muted-foreground/60"), children: count })
|
|
95
|
+
]
|
|
73
96
|
},
|
|
74
97
|
group.key
|
|
75
98
|
);
|
|
@@ -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
|
|
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"]}
|
|
@@ -11,10 +11,11 @@ interface LaunchpadFilterBarProps {
|
|
|
11
11
|
resultCount: number;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Search + group-filter bar
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Search + group-filter bar above the launchpad grid. Fully controlled — all
|
|
15
|
+
* state (query, active groups) lives in the caller so the grid below reacts
|
|
16
|
+
* to the same state. Each pill shows the live count of services it matches
|
|
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
|
|
|
@@ -11,10 +11,11 @@ interface LaunchpadFilterBarProps {
|
|
|
11
11
|
resultCount: number;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* Search + group-filter bar
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Search + group-filter bar above the launchpad grid. Fully controlled — all
|
|
15
|
+
* state (query, active groups) lives in the caller so the grid below reacts
|
|
16
|
+
* to the same state. Each pill shows the live count of services it matches
|
|
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,12 @@
|
|
|
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";
|
|
7
|
+
import {
|
|
8
|
+
LAUNCHPAD_SERVICE_DEFINITIONS
|
|
9
|
+
} from "../data/launchpad-services.js";
|
|
5
10
|
function LaunchpadFilterBar({
|
|
6
11
|
query,
|
|
7
12
|
onQueryChange,
|
|
@@ -10,12 +15,28 @@ function LaunchpadFilterBar({
|
|
|
10
15
|
onToggleGroup,
|
|
11
16
|
resultCount
|
|
12
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
|
+
}, []);
|
|
30
|
+
const countFor = (key) => LAUNCHPAD_SERVICE_DEFINITIONS.filter(
|
|
31
|
+
(d) => d.group === key && d.status === "live" && serviceMatchesQuery(d, query)
|
|
32
|
+
).length;
|
|
13
33
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-center", children: [
|
|
14
34
|
/* @__PURE__ */ jsxs("div", { className: "relative flex-1 sm:max-w-xs", children: [
|
|
15
35
|
/* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" }),
|
|
16
36
|
/* @__PURE__ */ jsx(
|
|
17
37
|
"input",
|
|
18
38
|
{
|
|
39
|
+
ref: inputRef,
|
|
19
40
|
type: "text",
|
|
20
41
|
value: query,
|
|
21
42
|
onChange: (e) => onQueryChange(e.target.value),
|
|
@@ -23,7 +44,7 @@ function LaunchpadFilterBar({
|
|
|
23
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"
|
|
24
45
|
}
|
|
25
46
|
),
|
|
26
|
-
query
|
|
47
|
+
query ? /* @__PURE__ */ jsx(
|
|
27
48
|
"button",
|
|
28
49
|
{
|
|
29
50
|
type: "button",
|
|
@@ -32,21 +53,25 @@ function LaunchpadFilterBar({
|
|
|
32
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",
|
|
33
54
|
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
34
55
|
}
|
|
35
|
-
)
|
|
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: "/" })
|
|
36
57
|
] }),
|
|
37
58
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 flex-1", children: groups.map((group) => {
|
|
38
59
|
const active = activeGroups.has(group.key);
|
|
39
|
-
|
|
60
|
+
const count = countFor(group.key);
|
|
61
|
+
return /* @__PURE__ */ jsxs(
|
|
40
62
|
"button",
|
|
41
63
|
{
|
|
42
64
|
type: "button",
|
|
43
65
|
onClick: () => onToggleGroup(group.key),
|
|
44
66
|
"aria-pressed": active,
|
|
45
67
|
className: cn(
|
|
46
|
-
"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border",
|
|
68
|
+
"h-8 px-3.5 rounded-full text-xs font-semibold transition-colors border inline-flex items-center gap-1.5",
|
|
47
69
|
active ? "bg-gradient-to-r from-brand-purple to-brand-blue text-white border-transparent shadow-sm" : "bg-card text-muted-foreground border-border active:bg-muted/60 sm:hover:bg-muted/40"
|
|
48
70
|
),
|
|
49
|
-
children:
|
|
71
|
+
children: [
|
|
72
|
+
group.title,
|
|
73
|
+
/* @__PURE__ */ jsx("span", { className: cn("tabular-nums", active ? "text-white/70" : "text-muted-foreground/60"), children: count })
|
|
74
|
+
]
|
|
50
75
|
},
|
|
51
76
|
group.key
|
|
52
77
|
);
|
|
@@ -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
|
|
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,9 +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
|
+
GROUP_ACCENTS: () => GROUP_ACCENTS,
|
|
32
33
|
LaunchpadGroupedSections: () => LaunchpadGroupedSections,
|
|
33
34
|
LaunchpadServiceCard: () => LaunchpadServiceCard,
|
|
34
35
|
SERVICE_HUES: () => SERVICE_HUES,
|
|
36
|
+
serviceMatchesQuery: () => serviceMatchesQuery,
|
|
35
37
|
useLaunchpadFilter: () => useLaunchpadFilter
|
|
36
38
|
});
|
|
37
39
|
module.exports = __toCommonJS(launchpad_services_exports);
|
|
@@ -42,135 +44,84 @@ var import_framer_motion = require("framer-motion");
|
|
|
42
44
|
var import_lucide_react = require("lucide-react");
|
|
43
45
|
var import_cn = require("../utils/cn.js");
|
|
44
46
|
var import_launchpad_services = require("../data/launchpad-services.js");
|
|
45
|
-
const
|
|
46
|
-
text: "text-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const DEFAULT_ACCENT = {
|
|
48
|
+
text: "text-muted-foreground",
|
|
49
|
+
tint: "bg-muted/60",
|
|
50
|
+
hoverBorder: "sm:hover:border-border"
|
|
51
|
+
};
|
|
52
|
+
const GROUP_ACCENTS = {
|
|
53
|
+
"nfts": { text: "text-brand-blue", tint: "bg-brand-blue/10", hoverBorder: "sm:hover:border-brand-blue/40" },
|
|
54
|
+
"limited-editions": { text: "text-brand-purple", tint: "bg-brand-purple/10", hoverBorder: "sm:hover:border-brand-purple/40" },
|
|
55
|
+
"community": { text: "text-emerald-600 dark:text-emerald-400", tint: "bg-emerald-500/10", hoverBorder: "sm:hover:border-emerald-500/40" },
|
|
56
|
+
"coins": { text: "text-brand-orange", tint: "bg-brand-orange/10", hoverBorder: "sm:hover:border-brand-orange/40" },
|
|
57
|
+
"claims": { text: "text-brand-rose", tint: "bg-brand-rose/10", hoverBorder: "sm:hover:border-brand-rose/40" },
|
|
58
|
+
"coming-soon": DEFAULT_ACCENT
|
|
50
59
|
};
|
|
51
60
|
const SERVICE_HUES = {
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"create-collection": { text: "text-green-600 dark:text-green-400", solid: "bg-green-600", border: "border-green-500/30 dark:border-green-400/25", tint: "bg-green-500/10" },
|
|
55
|
-
// Limited Editions — purple + red
|
|
56
|
-
"ip-collection-1155": { 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" },
|
|
57
|
-
"mint-editions": { text: "text-red-600 dark:text-red-400", solid: "bg-red-600", border: "border-red-500/30 dark:border-red-400/25", tint: "bg-red-500/10" },
|
|
58
|
-
// Creator Coins & Memecoins — yellow + orange
|
|
61
|
+
"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" },
|
|
62
|
+
"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" },
|
|
59
63
|
"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" },
|
|
60
64
|
"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" },
|
|
61
65
|
"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" },
|
|
62
66
|
"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" },
|
|
67
|
+
"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" },
|
|
63
68
|
"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" },
|
|
64
69
|
"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" },
|
|
65
70
|
"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" },
|
|
66
71
|
"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" }
|
|
67
72
|
};
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
const GROUP_TITLE_BY_KEY = Object.fromEntries(
|
|
74
|
+
import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.map((g) => [g.key, g.title])
|
|
75
|
+
);
|
|
76
|
+
const GROUP_KEYS = new Set(import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.map((g) => g.key));
|
|
77
|
+
function LaunchpadServiceCard({ def, override = {}, index = 0 }) {
|
|
78
|
+
const { icon: Icon, title, group } = def;
|
|
70
79
|
const status = override.status ?? def.status;
|
|
71
80
|
const blurb = override.blurb ?? def.blurb;
|
|
72
|
-
const { href
|
|
81
|
+
const { href } = override;
|
|
73
82
|
const reduceMotion = (0, import_framer_motion.useReducedMotion)();
|
|
74
83
|
const live = status === "live";
|
|
75
|
-
const
|
|
84
|
+
const accent = GROUP_ACCENTS[group] ?? DEFAULT_ACCENT;
|
|
85
|
+
const body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
86
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [
|
|
87
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("flex h-12 w-12 items-center justify-center rounded-2xl", live ? accent.tint : "bg-muted/40"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: (0, import_cn.cn)("h-6 w-6", live ? accent.text : "text-muted-foreground/50") }) }),
|
|
88
|
+
!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: [
|
|
89
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Lock, { className: "h-3 w-3" }),
|
|
90
|
+
"Coming soon"
|
|
91
|
+
] })
|
|
92
|
+
] }),
|
|
93
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1.5 mt-auto", children: [
|
|
94
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { className: "flex items-center gap-1.5 text-xl font-bold tracking-tight leading-snug", children: [
|
|
95
|
+
title,
|
|
96
|
+
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" })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__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 })
|
|
99
|
+
] })
|
|
100
|
+
] });
|
|
76
101
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
77
102
|
import_framer_motion.motion.div,
|
|
78
103
|
{
|
|
79
104
|
layout: !reduceMotion,
|
|
80
|
-
initial: { opacity: 0, y: reduceMotion ? 0 :
|
|
81
|
-
|
|
105
|
+
initial: { opacity: 0, y: reduceMotion ? 0 : 12 },
|
|
106
|
+
animate: { opacity: 1, y: 0 },
|
|
82
107
|
exit: { opacity: 0 },
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"div",
|
|
108
|
+
transition: { duration: 0.35, delay: index * 0.04, ease: [0.25, 0.46, 0.45, 0.94] },
|
|
109
|
+
className: "flex",
|
|
110
|
+
children: live && href ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
111
|
+
import_link.default,
|
|
88
112
|
{
|
|
113
|
+
href,
|
|
89
114
|
className: (0, import_cn.cn)(
|
|
90
|
-
"group
|
|
91
|
-
|
|
115
|
+
"group flex flex-1 flex-col gap-5 rounded-2xl border border-border/60 bg-card p-6 min-h-[200px]",
|
|
116
|
+
"transition-colors duration-200 active:scale-[0.99] motion-reduce:transform-none",
|
|
117
|
+
accent.hoverBorder
|
|
92
118
|
),
|
|
93
|
-
children:
|
|
94
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [
|
|
95
|
-
live ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("flex h-12 w-12 items-center justify-center rounded-xl", hue.tint), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: (0, import_cn.cn)("h-6 w-6", hue.text) }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: "h-8 w-8 shrink-0 text-muted-foreground/50" }),
|
|
96
|
-
!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: [
|
|
97
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Lock, { className: "h-3 w-3" }),
|
|
98
|
-
"Coming soon"
|
|
99
|
-
] })
|
|
100
|
-
] }),
|
|
101
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1.5", children: [
|
|
102
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-xl sm:text-2xl font-black tracking-tight leading-snug", children: title }),
|
|
103
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: (0, import_cn.cn)("text-sm leading-relaxed", live ? "text-muted-foreground" : "text-muted-foreground/60", !featured && "max-w-[36ch]"), children: blurb })
|
|
104
|
-
] }),
|
|
105
|
-
live && features.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-1.5", children: features.slice(0, 2).map((feature) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
106
|
-
"span",
|
|
107
|
-
{
|
|
108
|
-
className: "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-muted/40 border border-border/30 text-xs font-medium text-muted-foreground",
|
|
109
|
-
children: [
|
|
110
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { className: (0, import_cn.cn)("h-3 w-3 shrink-0", hue.text) }),
|
|
111
|
-
feature
|
|
112
|
-
]
|
|
113
|
-
},
|
|
114
|
-
feature
|
|
115
|
-
)) }),
|
|
116
|
-
live && href && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href, "aria-label": `${def.cta} \u2014 ${title}`, className: "absolute inset-0 z-10" }),
|
|
117
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-auto pt-1 flex items-end justify-between gap-3", children: [
|
|
118
|
-
live && browseHref && browseLinkLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
119
|
-
import_link.default,
|
|
120
|
-
{
|
|
121
|
-
href: browseHref,
|
|
122
|
-
className: "relative z-20 inline-flex items-center gap-1 text-xs font-medium text-muted-foreground active:text-foreground sm:hover:text-foreground transition-colors",
|
|
123
|
-
children: [
|
|
124
|
-
browseLinkLabel,
|
|
125
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { className: "h-3 w-3" })
|
|
126
|
-
]
|
|
127
|
-
}
|
|
128
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
|
|
129
|
-
live && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
130
|
-
"span",
|
|
131
|
-
{
|
|
132
|
-
className: (0, import_cn.cn)(
|
|
133
|
-
"inline-flex items-center gap-2 h-10 px-5 rounded-full",
|
|
134
|
-
"text-sm font-semibold text-white",
|
|
135
|
-
hue.solid
|
|
136
|
-
),
|
|
137
|
-
children: [
|
|
138
|
-
def.cta,
|
|
139
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRight, { className: "h-4 w-4 transition-transform duration-300 sm:group-hover:translate-x-0.5 motion-reduce:transform-none" })
|
|
140
|
-
]
|
|
141
|
-
}
|
|
142
|
-
)
|
|
143
|
-
] })
|
|
144
|
-
] })
|
|
119
|
+
children: body
|
|
145
120
|
}
|
|
146
|
-
)
|
|
121
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-1 flex-col gap-5 rounded-2xl border border-border/30 bg-card p-6 min-h-[200px] opacity-75", children: body })
|
|
147
122
|
}
|
|
148
123
|
);
|
|
149
124
|
}
|
|
150
|
-
const GROUP_TITLE_ACCENTS = {
|
|
151
|
-
"coins": "bg-gradient-to-r from-yellow-500 via-amber-500 to-orange-500 bg-clip-text text-transparent",
|
|
152
|
-
"community": "bg-gradient-to-r from-emerald-500 to-green-600 bg-clip-text text-transparent"
|
|
153
|
-
};
|
|
154
|
-
function GroupHeader({ group }) {
|
|
155
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
|
|
156
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: (0, import_cn.cn)("text-3xl sm:text-4xl font-black tracking-tight", GROUP_TITLE_ACCENTS[group.key]), children: group.title }),
|
|
157
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-base sm:text-lg text-muted-foreground leading-relaxed max-w-2xl", children: group.tagline })
|
|
158
|
-
] });
|
|
159
|
-
}
|
|
160
|
-
const POP_STEPS = [
|
|
161
|
-
{ title: "Create your event badge", body: "Name it, add your artwork, and set who can claim \u2014 ready in minutes." },
|
|
162
|
-
{ title: "Share the claim link", body: "Your community claims for free \u2014 one badge per person, no faking." },
|
|
163
|
-
{ title: "It stays with them forever", body: "Badges can't be sold or transferred \u2014 lasting proof they were there." }
|
|
164
|
-
];
|
|
165
|
-
function PopHowItWorks() {
|
|
166
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col justify-center gap-5 sm:gap-6 px-2 py-4 sm:py-0 sm:pl-6", children: POP_STEPS.map((step, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-4", children: [
|
|
167
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 text-sm font-bold", children: i + 1 }),
|
|
168
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-0.5", children: [
|
|
169
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-semibold", children: step.title }),
|
|
170
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm text-muted-foreground leading-relaxed", children: step.body })
|
|
171
|
-
] })
|
|
172
|
-
] }, step.title)) });
|
|
173
|
-
}
|
|
174
125
|
function ComingSoonStrip({ group, defs }) {
|
|
175
126
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-2xl border border-border/40 p-5", children: [
|
|
176
127
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-semibold text-sm", children: group.title }),
|
|
@@ -181,24 +132,52 @@ function ComingSoonStrip({ group, defs }) {
|
|
|
181
132
|
] }, key)) })
|
|
182
133
|
] });
|
|
183
134
|
}
|
|
135
|
+
function serviceMatchesQuery(def, query) {
|
|
136
|
+
if (query.trim() === "") return true;
|
|
137
|
+
const haystack = `${def.title} ${def.blurb} ${def.subtitle} ${GROUP_TITLE_BY_KEY[def.group] ?? ""}`.toLowerCase();
|
|
138
|
+
return haystack.includes(query.trim().toLowerCase());
|
|
139
|
+
}
|
|
140
|
+
function syncFilterUrl(query, groups) {
|
|
141
|
+
if (typeof window === "undefined") return;
|
|
142
|
+
const params = new URLSearchParams(window.location.search);
|
|
143
|
+
if (query.trim()) params.set("q", query.trim());
|
|
144
|
+
else params.delete("q");
|
|
145
|
+
if (groups.size > 0) params.set("groups", [...groups].join(","));
|
|
146
|
+
else params.delete("groups");
|
|
147
|
+
const qs = params.toString();
|
|
148
|
+
window.history.replaceState(null, "", qs ? `${window.location.pathname}?${qs}` : window.location.pathname);
|
|
149
|
+
}
|
|
184
150
|
function useLaunchpadFilter() {
|
|
185
|
-
const [query,
|
|
151
|
+
const [query, setQueryState] = (0, import_react.useState)("");
|
|
186
152
|
const [activeGroups, setActiveGroups] = (0, import_react.useState)(/* @__PURE__ */ new Set());
|
|
153
|
+
(0, import_react.useEffect)(() => {
|
|
154
|
+
const params = new URLSearchParams(window.location.search);
|
|
155
|
+
const q = params.get("q");
|
|
156
|
+
if (q) setQueryState(q);
|
|
157
|
+
const g = params.get("groups");
|
|
158
|
+
if (g) {
|
|
159
|
+
const groups = g.split(",").filter((k) => GROUP_KEYS.has(k));
|
|
160
|
+
if (groups.length > 0) setActiveGroups(new Set(groups));
|
|
161
|
+
}
|
|
162
|
+
}, []);
|
|
187
163
|
const filterableGroups = import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.filter((g) => g.key !== "coming-soon");
|
|
164
|
+
const setQuery = (value) => {
|
|
165
|
+
setQueryState(value);
|
|
166
|
+
syncFilterUrl(value, activeGroups);
|
|
167
|
+
};
|
|
188
168
|
const toggleGroup = (key) => {
|
|
189
169
|
setActiveGroups((prev) => {
|
|
190
170
|
const next = new Set(prev);
|
|
191
171
|
if (next.has(key)) next.delete(key);
|
|
192
172
|
else next.add(key);
|
|
173
|
+
syncFilterUrl(query, next);
|
|
193
174
|
return next;
|
|
194
175
|
});
|
|
195
176
|
};
|
|
196
177
|
const matches = (def) => {
|
|
197
178
|
if (activeGroups.size > 0 && !activeGroups.has(def.group)) return false;
|
|
198
179
|
if (def.status !== "live") return false;
|
|
199
|
-
|
|
200
|
-
const haystack = `${def.title} ${def.blurb} ${def.subtitle}`.toLowerCase();
|
|
201
|
-
return haystack.includes(query.trim().toLowerCase());
|
|
180
|
+
return serviceMatchesQuery(def, query);
|
|
202
181
|
};
|
|
203
182
|
const totalMatches = (0, import_react.useMemo)(
|
|
204
183
|
() => import_launchpad_services.LAUNCHPAD_SERVICE_DEFINITIONS.filter(matches).length,
|
|
@@ -206,25 +185,22 @@ function useLaunchpadFilter() {
|
|
|
206
185
|
[query, activeGroups]
|
|
207
186
|
);
|
|
208
187
|
const clear = () => {
|
|
209
|
-
|
|
188
|
+
setQueryState("");
|
|
210
189
|
setActiveGroups(/* @__PURE__ */ new Set());
|
|
190
|
+
syncFilterUrl("", /* @__PURE__ */ new Set());
|
|
211
191
|
};
|
|
212
192
|
return { query, setQuery, activeGroups, toggleGroup, filterableGroups, totalMatches, clear };
|
|
213
193
|
}
|
|
214
194
|
function LaunchpadGroupedSections({ overrides, query, activeGroups, onClearFilters, className }) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const totalMatches = (0, import_react.useMemo)(
|
|
223
|
-
() => import_launchpad_services.LAUNCHPAD_SERVICE_DEFINITIONS.filter(matches).length,
|
|
224
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
225
|
-
[query, activeGroups]
|
|
195
|
+
const inActiveGroup = (d) => activeGroups.size === 0 || activeGroups.has(d.group);
|
|
196
|
+
const inSearch = (d) => serviceMatchesQuery(d, query);
|
|
197
|
+
const groupOrder = Object.fromEntries(import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.map((g, i) => [g.key, i]));
|
|
198
|
+
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));
|
|
199
|
+
const comingSoonGroup = import_launchpad_services.LAUNCHPAD_SERVICE_GROUPS.find((g) => g.key === "coming-soon");
|
|
200
|
+
const comingSoonDefs = import_launchpad_services.LAUNCHPAD_SERVICE_DEFINITIONS.filter(
|
|
201
|
+
(d) => d.group === "coming-soon" && inActiveGroup(d) && inSearch(d)
|
|
226
202
|
);
|
|
227
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_cn.cn)("space-y-8
|
|
203
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_cn.cn)("space-y-8", className), children: liveDefs.length === 0 && comingSoonDefs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-center py-16 space-y-3", children: [
|
|
228
204
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-lg font-semibold", children: "No services match" }),
|
|
229
205
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm text-muted-foreground", children: "Try a different search or clear your filters." }),
|
|
230
206
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -236,45 +212,18 @@ function LaunchpadGroupedSections({ overrides, query, activeGroups, onClearFilte
|
|
|
236
212
|
children: "Clear filters"
|
|
237
213
|
}
|
|
238
214
|
)
|
|
239
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const comingSoonDefs = import_launchpad_services.LAUNCHPAD_SERVICE_DEFINITIONS.filter(
|
|
244
|
-
(d) => d.group === group.key && inActiveGroup(d) && inSearch(d)
|
|
245
|
-
);
|
|
246
|
-
if (comingSoonDefs.length === 0) return null;
|
|
247
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComingSoonStrip, { group, defs: comingSoonDefs }, group.key);
|
|
248
|
-
}
|
|
249
|
-
const defs = import_launchpad_services.LAUNCHPAD_SERVICE_DEFINITIONS.filter((d) => d.group === group.key && matches(d));
|
|
250
|
-
if (defs.length === 0) return null;
|
|
251
|
-
const showPopHowItWorks = group.key === "community" && defs.some((d) => d.key === "pop-protocol");
|
|
252
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
253
|
-
import_framer_motion.motion.div,
|
|
254
|
-
{
|
|
255
|
-
layout: true,
|
|
256
|
-
initial: { opacity: 0, y: 8 },
|
|
257
|
-
animate: { opacity: 1, y: 0 },
|
|
258
|
-
exit: { opacity: 0 },
|
|
259
|
-
transition: { duration: 0.3, ease: [0.25, 0.46, 0.45, 0.94] },
|
|
260
|
-
className: "space-y-7 sm:space-y-10",
|
|
261
|
-
children: [
|
|
262
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(GroupHeader, { group }),
|
|
263
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-5", children: [
|
|
264
|
-
defs.map((def, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LaunchpadServiceCard, { def, override: overrides[def.key], index: i }, def.key)),
|
|
265
|
-
showPopHowItWorks && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopHowItWorks, {})
|
|
266
|
-
] })
|
|
267
|
-
]
|
|
268
|
-
},
|
|
269
|
-
group.key
|
|
270
|
-
);
|
|
271
|
-
}) }) }) });
|
|
215
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
216
|
+
/* @__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-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)) }) }),
|
|
217
|
+
comingSoonGroup && comingSoonDefs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComingSoonStrip, { group: comingSoonGroup, defs: comingSoonDefs })
|
|
218
|
+
] }) });
|
|
272
219
|
}
|
|
273
220
|
// Annotate the CommonJS export names for ESM import in node:
|
|
274
221
|
0 && (module.exports = {
|
|
222
|
+
GROUP_ACCENTS,
|
|
275
223
|
LaunchpadGroupedSections,
|
|
276
224
|
LaunchpadServiceCard,
|
|
277
225
|
SERVICE_HUES,
|
|
226
|
+
serviceMatchesQuery,
|
|
278
227
|
useLaunchpadFilter
|
|
279
228
|
});
|
|
280
229
|
//# sourceMappingURL=launchpad-services.cjs.map
|