@serve.zone/catalog 2.7.0 → 2.8.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/catalog',
6
- version: '2.7.0',
6
+ version: '2.8.0',
7
7
  description: 'UI component catalog for serve.zone'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUscUJBQXFCO0lBQzNCLE9BQU8sRUFBRSxPQUFPO0lBQ2hCLFdBQVcsRUFBRSxxQ0FBcUM7Q0FDbkQsQ0FBQSJ9
@@ -19,6 +19,7 @@ export * from './sz-registry-external-view.js';
19
19
  export * from './sz-services-list-view.js';
20
20
  export * from './sz-services-backups-view.js';
21
21
  export * from './sz-service-detail-view.js';
22
+ export * from './sz-app-store-view.js';
22
23
  export * from './sz-tokens-view.js';
23
24
  export * from './sz-settings-view.js';
24
25
  export * from './sz-login-view.js';
@@ -24,6 +24,7 @@ export * from './sz-registry-external-view.js';
24
24
  export * from './sz-services-list-view.js';
25
25
  export * from './sz-services-backups-view.js';
26
26
  export * from './sz-service-detail-view.js';
27
+ export * from './sz-app-store-view.js';
27
28
  // Tokens View
28
29
  export * from './sz-tokens-view.js';
29
30
  // Settings View
@@ -54,4 +55,4 @@ export * from './sz-demo-view-settings.js';
54
55
  export * from './sz-demo-view-mta.js';
55
56
  export * from './sz-demo-view-routes.js';
56
57
  export * from './sz-demo-view-config.js';
57
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvZWxlbWVudHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsa0JBQWtCO0FBQ2xCLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsZ0NBQWdDLENBQUM7QUFDL0MsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyw0QkFBNEIsQ0FBQztBQUUzQyxrQkFBa0I7QUFDbEIsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLDhCQUE4QixDQUFDO0FBQzdDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLHdCQUF3QixDQUFDO0FBRXZDLGdCQUFnQjtBQUNoQixjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyw4QkFBOEIsQ0FBQztBQUU3QyxpQkFBaUI7QUFDakIsY0FBYyxnQ0FBZ0MsQ0FBQztBQUMvQyxjQUFjLGdDQUFnQyxDQUFDO0FBRS9DLGlCQUFpQjtBQUNqQixjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYyw2QkFBNkIsQ0FBQztBQUU1QyxjQUFjO0FBQ2QsY0FBYyxxQkFBcUIsQ0FBQztBQUVwQyxnQkFBZ0I7QUFDaEIsY0FBYyx1QkFBdUIsQ0FBQztBQUV0QyxlQUFlO0FBQ2YsY0FBYyxvQkFBb0IsQ0FBQztBQUVuQyxpQkFBaUI7QUFDakIsY0FBYyw2QkFBNkIsQ0FBQztBQUU1QyxlQUFlO0FBQ2YsY0FBYyxzQ0FBc0MsQ0FBQztBQUNyRCxjQUFjLDRCQUE0QixDQUFDO0FBRTNDLGtCQUFrQjtBQUNsQixjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMseUJBQXlCLENBQUM7QUFFeEMsNEJBQTRCO0FBQzVCLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyx5QkFBeUIsQ0FBQztBQUV4QyxlQUFlO0FBQ2YsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLHlCQUF5QixDQUFDO0FBRXhDLGFBQWE7QUFDYixjQUFjLDZCQUE2QixDQUFDO0FBQzVDLGNBQWMsNEJBQTRCLENBQUM7QUFDM0MsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLDhCQUE4QixDQUFDO0FBQzdDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYywwQkFBMEIsQ0FBQyJ9
58
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvZWxlbWVudHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsa0JBQWtCO0FBQ2xCLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsZ0NBQWdDLENBQUM7QUFDL0MsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyw0QkFBNEIsQ0FBQztBQUUzQyxrQkFBa0I7QUFDbEIsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLDhCQUE4QixDQUFDO0FBQzdDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLHdCQUF3QixDQUFDO0FBRXZDLGdCQUFnQjtBQUNoQixjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyw4QkFBOEIsQ0FBQztBQUU3QyxpQkFBaUI7QUFDakIsY0FBYyxnQ0FBZ0MsQ0FBQztBQUMvQyxjQUFjLGdDQUFnQyxDQUFDO0FBRS9DLGlCQUFpQjtBQUNqQixjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLHdCQUF3QixDQUFDO0FBRXZDLGNBQWM7QUFDZCxjQUFjLHFCQUFxQixDQUFDO0FBRXBDLGdCQUFnQjtBQUNoQixjQUFjLHVCQUF1QixDQUFDO0FBRXRDLGVBQWU7QUFDZixjQUFjLG9CQUFvQixDQUFDO0FBRW5DLGlCQUFpQjtBQUNqQixjQUFjLDZCQUE2QixDQUFDO0FBRTVDLGVBQWU7QUFDZixjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsNEJBQTRCLENBQUM7QUFFM0Msa0JBQWtCO0FBQ2xCLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyx5QkFBeUIsQ0FBQztBQUV4Qyw0QkFBNEI7QUFDNUIsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHlCQUF5QixDQUFDO0FBRXhDLGVBQWU7QUFDZixjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMseUJBQXlCLENBQUM7QUFFeEMsYUFBYTtBQUNiLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyw0QkFBNEIsQ0FBQztBQUMzQyxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsOEJBQThCLENBQUM7QUFDN0MsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDBCQUEwQixDQUFDIn0=
@@ -0,0 +1,40 @@
1
+ import { DeesElement, type TemplateResult } from '@design.estate/dees-element';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ 'sz-app-store-view': SzAppStoreView;
5
+ }
6
+ }
7
+ export interface IAppTemplate {
8
+ id: string;
9
+ name: string;
10
+ description: string;
11
+ category: string;
12
+ iconUrl?: string;
13
+ iconName?: string;
14
+ image: string;
15
+ port: number;
16
+ envVars?: Array<{
17
+ key: string;
18
+ value: string;
19
+ description: string;
20
+ required?: boolean;
21
+ }>;
22
+ volumes?: string[];
23
+ enableMongoDB?: boolean;
24
+ enableS3?: boolean;
25
+ enableClickHouse?: boolean;
26
+ }
27
+ export declare class SzAppStoreView extends DeesElement {
28
+ static demo: () => TemplateResult<1>;
29
+ static demoGroups: string[];
30
+ accessor apps: IAppTemplate[];
31
+ private accessor selectedCategory;
32
+ private accessor searchQuery;
33
+ static styles: import("@design.estate/dees-element").CSSResult[];
34
+ private get categories();
35
+ private get filteredApps();
36
+ render(): TemplateResult;
37
+ private renderAppCard;
38
+ private renderIconByName;
39
+ private handleDeploy;
40
+ }
@@ -0,0 +1,588 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ import { DeesElement, customElement, html, css, cssManager, property, state, } from '@design.estate/dees-element';
36
+ let SzAppStoreView = (() => {
37
+ let _classDecorators = [customElement('sz-app-store-view')];
38
+ let _classDescriptor;
39
+ let _classExtraInitializers = [];
40
+ let _classThis;
41
+ let _classSuper = DeesElement;
42
+ let _apps_decorators;
43
+ let _apps_initializers = [];
44
+ let _apps_extraInitializers = [];
45
+ let _selectedCategory_decorators;
46
+ let _selectedCategory_initializers = [];
47
+ let _selectedCategory_extraInitializers = [];
48
+ let _searchQuery_decorators;
49
+ let _searchQuery_initializers = [];
50
+ let _searchQuery_extraInitializers = [];
51
+ var SzAppStoreView = class extends _classSuper {
52
+ static { _classThis = this; }
53
+ static {
54
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
55
+ _apps_decorators = [property({ type: Array })];
56
+ _selectedCategory_decorators = [state()];
57
+ _searchQuery_decorators = [state()];
58
+ __esDecorate(this, null, _apps_decorators, { kind: "accessor", name: "apps", static: false, private: false, access: { has: obj => "apps" in obj, get: obj => obj.apps, set: (obj, value) => { obj.apps = value; } }, metadata: _metadata }, _apps_initializers, _apps_extraInitializers);
59
+ __esDecorate(this, null, _selectedCategory_decorators, { kind: "accessor", name: "selectedCategory", static: false, private: false, access: { has: obj => "selectedCategory" in obj, get: obj => obj.selectedCategory, set: (obj, value) => { obj.selectedCategory = value; } }, metadata: _metadata }, _selectedCategory_initializers, _selectedCategory_extraInitializers);
60
+ __esDecorate(this, null, _searchQuery_decorators, { kind: "accessor", name: "searchQuery", static: false, private: false, access: { has: obj => "searchQuery" in obj, get: obj => obj.searchQuery, set: (obj, value) => { obj.searchQuery = value; } }, metadata: _metadata }, _searchQuery_initializers, _searchQuery_extraInitializers);
61
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
62
+ SzAppStoreView = _classThis = _classDescriptor.value;
63
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
64
+ }
65
+ static demo = () => html `
66
+ <div style="padding: 24px; max-width: 1200px;">
67
+ <sz-app-store-view
68
+ .apps=${[
69
+ {
70
+ id: 'wordpress',
71
+ name: 'WordPress',
72
+ description: 'The world\'s most popular open-source content management system for building websites and blogs.',
73
+ category: 'CMS',
74
+ iconName: 'file-text',
75
+ image: 'wordpress:latest',
76
+ port: 80,
77
+ envVars: [
78
+ { key: 'WORDPRESS_DB_HOST', value: '', description: 'Database host address', required: true },
79
+ { key: 'WORDPRESS_DB_USER', value: 'wordpress', description: 'Database username', required: true },
80
+ { key: 'WORDPRESS_DB_PASSWORD', value: '', description: 'Database password', required: true },
81
+ { key: 'WORDPRESS_DB_NAME', value: 'wordpress', description: 'Database name', required: true },
82
+ ],
83
+ volumes: ['/var/www/html'],
84
+ enableMongoDB: false,
85
+ enableS3: false,
86
+ },
87
+ {
88
+ id: 'gitea',
89
+ name: 'Gitea',
90
+ description: 'A lightweight, self-hosted Git service. Painless setup for your own code hosting platform.',
91
+ category: 'Development',
92
+ iconName: 'git-branch',
93
+ image: 'gitea/gitea:latest',
94
+ port: 3000,
95
+ envVars: [
96
+ { key: 'GITEA__database__DB_TYPE', value: 'sqlite3', description: 'Database type', required: true },
97
+ { key: 'GITEA__server__ROOT_URL', value: '', description: 'Public URL of the Gitea instance', required: false },
98
+ ],
99
+ volumes: ['/data'],
100
+ },
101
+ {
102
+ id: 'ghost',
103
+ name: 'Ghost',
104
+ description: 'A powerful open-source publishing platform for professional bloggers and content creators.',
105
+ category: 'CMS',
106
+ iconName: 'book-open',
107
+ image: 'ghost:latest',
108
+ port: 2368,
109
+ envVars: [
110
+ { key: 'url', value: '', description: 'Public URL for the Ghost site', required: true },
111
+ { key: 'database__client', value: 'sqlite3', description: 'Database client type', required: false },
112
+ ],
113
+ volumes: ['/var/lib/ghost/content'],
114
+ },
115
+ {
116
+ id: 'nginx',
117
+ name: 'Nginx',
118
+ description: 'High-performance HTTP server and reverse proxy with low resource consumption.',
119
+ category: 'Web Server',
120
+ iconName: 'globe',
121
+ image: 'nginx:alpine',
122
+ port: 80,
123
+ volumes: ['/usr/share/nginx/html', '/etc/nginx/conf.d'],
124
+ },
125
+ {
126
+ id: 'redis',
127
+ name: 'Redis',
128
+ description: 'In-memory data store used as a database, cache, streaming engine, and message broker.',
129
+ category: 'Database',
130
+ iconName: 'database',
131
+ image: 'redis:alpine',
132
+ port: 6379,
133
+ },
134
+ {
135
+ id: 'postgres',
136
+ name: 'PostgreSQL',
137
+ description: 'Advanced open-source relational database with strong reliability and feature set.',
138
+ category: 'Database',
139
+ iconName: 'database',
140
+ image: 'postgres:16-alpine',
141
+ port: 5432,
142
+ envVars: [
143
+ { key: 'POSTGRES_USER', value: 'postgres', description: 'Superuser username', required: true },
144
+ { key: 'POSTGRES_PASSWORD', value: '', description: 'Superuser password', required: true },
145
+ { key: 'POSTGRES_DB', value: 'postgres', description: 'Default database name', required: false },
146
+ ],
147
+ volumes: ['/var/lib/postgresql/data'],
148
+ },
149
+ ]}
150
+ ></sz-app-store-view>
151
+ </div>
152
+ `;
153
+ static demoGroups = ['Services'];
154
+ #apps_accessor_storage = __runInitializers(this, _apps_initializers, []);
155
+ get apps() { return this.#apps_accessor_storage; }
156
+ set apps(value) { this.#apps_accessor_storage = value; }
157
+ #selectedCategory_accessor_storage = (__runInitializers(this, _apps_extraInitializers), __runInitializers(this, _selectedCategory_initializers, 'All'));
158
+ get selectedCategory() { return this.#selectedCategory_accessor_storage; }
159
+ set selectedCategory(value) { this.#selectedCategory_accessor_storage = value; }
160
+ #searchQuery_accessor_storage = (__runInitializers(this, _selectedCategory_extraInitializers), __runInitializers(this, _searchQuery_initializers, ''));
161
+ get searchQuery() { return this.#searchQuery_accessor_storage; }
162
+ set searchQuery(value) { this.#searchQuery_accessor_storage = value; }
163
+ static styles = [
164
+ cssManager.defaultStyles,
165
+ css `
166
+ :host {
167
+ display: block;
168
+ }
169
+
170
+ .header {
171
+ margin-bottom: 24px;
172
+ }
173
+
174
+ .header-title {
175
+ font-size: 20px;
176
+ font-weight: 600;
177
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
178
+ }
179
+
180
+ .header-subtitle {
181
+ font-size: 14px;
182
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
183
+ margin-top: 4px;
184
+ }
185
+
186
+ .filter-bar {
187
+ display: flex;
188
+ flex-wrap: wrap;
189
+ align-items: center;
190
+ gap: 12px;
191
+ margin-bottom: 24px;
192
+ }
193
+
194
+ .category-pills {
195
+ display: flex;
196
+ flex-wrap: wrap;
197
+ gap: 8px;
198
+ flex: 1;
199
+ min-width: 0;
200
+ }
201
+
202
+ .category-pill {
203
+ padding: 6px 14px;
204
+ border-radius: 9999px;
205
+ font-size: 13px;
206
+ font-weight: 500;
207
+ cursor: pointer;
208
+ transition: all 200ms ease;
209
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
210
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
211
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
212
+ white-space: nowrap;
213
+ }
214
+
215
+ .category-pill:hover {
216
+ border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
217
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
218
+ }
219
+
220
+ .category-pill.active {
221
+ background: ${cssManager.bdTheme('#18181b', '#fafafa')};
222
+ color: ${cssManager.bdTheme('#fafafa', '#18181b')};
223
+ border-color: ${cssManager.bdTheme('#18181b', '#fafafa')};
224
+ }
225
+
226
+ .search-input {
227
+ width: 240px;
228
+ padding: 8px 12px 8px 36px;
229
+ background: ${cssManager.bdTheme('#ffffff', '#18181b')};
230
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
231
+ border-radius: 6px;
232
+ font-size: 14px;
233
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
234
+ outline: none;
235
+ transition: border-color 200ms ease;
236
+ box-sizing: border-box;
237
+ }
238
+
239
+ .search-input:focus {
240
+ border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
241
+ }
242
+
243
+ .search-input::placeholder {
244
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
245
+ }
246
+
247
+ .search-wrapper {
248
+ position: relative;
249
+ flex-shrink: 0;
250
+ }
251
+
252
+ .search-icon {
253
+ position: absolute;
254
+ left: 10px;
255
+ top: 50%;
256
+ transform: translateY(-50%);
257
+ width: 16px;
258
+ height: 16px;
259
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
260
+ pointer-events: none;
261
+ }
262
+
263
+ .app-grid {
264
+ display: grid;
265
+ grid-template-columns: repeat(3, 1fr);
266
+ gap: 16px;
267
+ }
268
+
269
+ @media (max-width: 1024px) {
270
+ .app-grid {
271
+ grid-template-columns: repeat(2, 1fr);
272
+ }
273
+ }
274
+
275
+ @media (max-width: 640px) {
276
+ .app-grid {
277
+ grid-template-columns: 1fr;
278
+ }
279
+
280
+ .filter-bar {
281
+ flex-direction: column;
282
+ align-items: stretch;
283
+ }
284
+
285
+ .search-input {
286
+ width: 100%;
287
+ }
288
+ }
289
+
290
+ .app-card {
291
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
292
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
293
+ border-radius: 8px;
294
+ padding: 20px;
295
+ display: flex;
296
+ flex-direction: column;
297
+ gap: 12px;
298
+ transition: all 200ms ease;
299
+ cursor: default;
300
+ }
301
+
302
+ .app-card:hover {
303
+ border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
304
+ box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0,0,0,0.05)', 'rgba(0,0,0,0.2)')};
305
+ transform: translateY(-1px);
306
+ }
307
+
308
+ .card-top {
309
+ display: flex;
310
+ align-items: flex-start;
311
+ gap: 14px;
312
+ }
313
+
314
+ .app-icon {
315
+ width: 44px;
316
+ height: 44px;
317
+ border-radius: 10px;
318
+ background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ flex-shrink: 0;
323
+ overflow: hidden;
324
+ }
325
+
326
+ .app-icon svg {
327
+ width: 22px;
328
+ height: 22px;
329
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
330
+ }
331
+
332
+ .app-icon img {
333
+ width: 100%;
334
+ height: 100%;
335
+ object-fit: cover;
336
+ }
337
+
338
+ .app-icon .letter-fallback {
339
+ font-size: 20px;
340
+ font-weight: 600;
341
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
342
+ user-select: none;
343
+ }
344
+
345
+ .card-info {
346
+ flex: 1;
347
+ min-width: 0;
348
+ }
349
+
350
+ .app-name {
351
+ font-size: 15px;
352
+ font-weight: 600;
353
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
354
+ line-height: 1.3;
355
+ }
356
+
357
+ .category-badge {
358
+ display: inline-flex;
359
+ align-items: center;
360
+ padding: 2px 8px;
361
+ border-radius: 9999px;
362
+ font-size: 11px;
363
+ font-weight: 500;
364
+ margin-top: 4px;
365
+ background: ${cssManager.bdTheme('#eff6ff', 'rgba(59, 130, 246, 0.15)')};
366
+ color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
367
+ }
368
+
369
+ .app-description {
370
+ font-size: 13px;
371
+ line-height: 1.5;
372
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
373
+ display: -webkit-box;
374
+ -webkit-line-clamp: 2;
375
+ -webkit-box-orient: vertical;
376
+ overflow: hidden;
377
+ text-overflow: ellipsis;
378
+ flex: 1;
379
+ }
380
+
381
+ .card-footer {
382
+ display: flex;
383
+ align-items: center;
384
+ justify-content: space-between;
385
+ padding-top: 12px;
386
+ border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')};
387
+ margin-top: auto;
388
+ }
389
+
390
+ .image-tag {
391
+ font-family: monospace;
392
+ font-size: 12px;
393
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
394
+ overflow: hidden;
395
+ text-overflow: ellipsis;
396
+ white-space: nowrap;
397
+ max-width: 160px;
398
+ }
399
+
400
+ .deploy-button {
401
+ display: inline-flex;
402
+ align-items: center;
403
+ gap: 6px;
404
+ padding: 7px 16px;
405
+ border-radius: 6px;
406
+ font-size: 13px;
407
+ font-weight: 500;
408
+ cursor: pointer;
409
+ transition: all 200ms ease;
410
+ border: none;
411
+ background: ${cssManager.bdTheme('#18181b', '#fafafa')};
412
+ color: ${cssManager.bdTheme('#fafafa', '#18181b')};
413
+ white-space: nowrap;
414
+ flex-shrink: 0;
415
+ }
416
+
417
+ .deploy-button:hover {
418
+ opacity: 0.85;
419
+ }
420
+
421
+ .deploy-button svg {
422
+ width: 14px;
423
+ height: 14px;
424
+ }
425
+
426
+ .empty-state {
427
+ grid-column: 1 / -1;
428
+ padding: 64px 24px;
429
+ text-align: center;
430
+ }
431
+
432
+ .empty-icon {
433
+ width: 48px;
434
+ height: 48px;
435
+ color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
436
+ margin: 0 auto 16px;
437
+ }
438
+
439
+ .empty-title {
440
+ font-size: 16px;
441
+ font-weight: 600;
442
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
443
+ margin-bottom: 4px;
444
+ }
445
+
446
+ .empty-description {
447
+ font-size: 14px;
448
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
449
+ }
450
+ `,
451
+ ];
452
+ get categories() {
453
+ const cats = new Set(this.apps.map((app) => app.category));
454
+ return ['All', ...Array.from(cats).sort()];
455
+ }
456
+ get filteredApps() {
457
+ let result = this.apps;
458
+ if (this.selectedCategory !== 'All') {
459
+ result = result.filter((app) => app.category === this.selectedCategory);
460
+ }
461
+ if (this.searchQuery.trim()) {
462
+ const query = this.searchQuery.trim().toLowerCase();
463
+ result = result.filter((app) => app.name.toLowerCase().includes(query) ||
464
+ app.description.toLowerCase().includes(query) ||
465
+ app.category.toLowerCase().includes(query));
466
+ }
467
+ return result;
468
+ }
469
+ render() {
470
+ const filtered = this.filteredApps;
471
+ return html `
472
+ <div class="header">
473
+ <div class="header-title">App Store</div>
474
+ <div class="header-subtitle">Deploy popular applications with one click</div>
475
+ </div>
476
+
477
+ <div class="filter-bar">
478
+ <div class="category-pills">
479
+ ${this.categories.map((cat) => html `
480
+ <button
481
+ class="category-pill ${this.selectedCategory === cat ? 'active' : ''}"
482
+ @click=${() => { this.selectedCategory = cat; }}
483
+ >
484
+ ${cat}
485
+ </button>
486
+ `)}
487
+ </div>
488
+ <div class="search-wrapper">
489
+ <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
490
+ <circle cx="11" cy="11" r="8"></circle>
491
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
492
+ </svg>
493
+ <input
494
+ type="text"
495
+ class="search-input"
496
+ placeholder="Search apps..."
497
+ .value=${this.searchQuery}
498
+ @input=${(e) => { this.searchQuery = e.target.value; }}
499
+ >
500
+ </div>
501
+ </div>
502
+
503
+ <div class="app-grid">
504
+ ${filtered.length > 0
505
+ ? filtered.map((app) => this.renderAppCard(app))
506
+ : html `
507
+ <div class="empty-state">
508
+ <svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
509
+ <circle cx="11" cy="11" r="8"></circle>
510
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
511
+ <line x1="8" y1="11" x2="14" y2="11"></line>
512
+ </svg>
513
+ <div class="empty-title">No apps found</div>
514
+ <div class="empty-description">
515
+ Try adjusting your search or filter to find what you're looking for.
516
+ </div>
517
+ </div>
518
+ `}
519
+ </div>
520
+ `;
521
+ }
522
+ renderAppCard(app) {
523
+ return html `
524
+ <div class="app-card">
525
+ <div class="card-top">
526
+ <div class="app-icon">
527
+ ${app.iconUrl
528
+ ? html `<img src="${app.iconUrl}" alt="${app.name}">`
529
+ : app.iconName
530
+ ? this.renderIconByName(app.iconName)
531
+ : html `<span class="letter-fallback">${app.name.charAt(0).toUpperCase()}</span>`}
532
+ </div>
533
+ <div class="card-info">
534
+ <div class="app-name">${app.name}</div>
535
+ <div class="category-badge">${app.category}</div>
536
+ </div>
537
+ </div>
538
+ <div class="app-description">${app.description}</div>
539
+ <div class="card-footer">
540
+ <span class="image-tag" title="${app.image}">${app.image}</span>
541
+ <button class="deploy-button" @click=${() => this.handleDeploy(app)}>
542
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
543
+ <polyline points="20 12 20 22 4 22 4 12"></polyline>
544
+ <rect x="2" y="7" width="20" height="5"></rect>
545
+ <line x1="12" y1="22" x2="12" y2="7"></line>
546
+ <path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
547
+ <path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
548
+ </svg>
549
+ Deploy
550
+ </button>
551
+ </div>
552
+ </div>
553
+ `;
554
+ }
555
+ renderIconByName(name) {
556
+ const icons = {
557
+ 'file-text': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
558
+ 'git-branch': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg>`,
559
+ 'book-open': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>`,
560
+ 'globe': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>`,
561
+ 'database': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>`,
562
+ 'server': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>`,
563
+ 'package': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>`,
564
+ 'mail': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>`,
565
+ 'shield': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>`,
566
+ 'monitor': html `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>`,
567
+ };
568
+ return icons[name] || html `<span class="letter-fallback">?</span>`;
569
+ }
570
+ handleDeploy(app) {
571
+ this.dispatchEvent(new CustomEvent('deploy-app', {
572
+ detail: { app },
573
+ bubbles: true,
574
+ composed: true,
575
+ }));
576
+ }
577
+ constructor() {
578
+ super(...arguments);
579
+ __runInitializers(this, _searchQuery_extraInitializers);
580
+ }
581
+ static {
582
+ __runInitializers(_classThis, _classExtraInitializers);
583
+ }
584
+ };
585
+ return SzAppStoreView = _classThis;
586
+ })();
587
+ export { SzAppStoreView };
588
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3otYXBwLXN0b3JlLXZpZXcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvZWxlbWVudHMvc3otYXBwLXN0b3JlLXZpZXcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLE9BQU8sRUFDTCxXQUFXLEVBQ1gsYUFBYSxFQUNiLElBQUksRUFDSixHQUFHLEVBQ0gsVUFBVSxFQUNWLFFBQVEsRUFDUixLQUFLLEdBRU4sTUFBTSw2QkFBNkIsQ0FBQztJQXlCeEIsY0FBYzs0QkFEMUIsYUFBYSxDQUFDLG1CQUFtQixDQUFDOzs7O3NCQUNDLFdBQVc7Ozs7Ozs7Ozs7OEJBQW5CLFNBQVEsV0FBVzs7OztnQ0E0RjVDLFFBQVEsQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQzs0Q0FHekIsS0FBSyxFQUFFO3VDQUdQLEtBQUssRUFBRTtZQUxSLGlLQUFnQixJQUFJLDZCQUFKLElBQUksbUZBQXNCO1lBRzFDLHFNQUFpQixnQkFBZ0IsNkJBQWhCLGdCQUFnQiwyR0FBaUI7WUFHbEQsc0xBQWlCLFdBQVcsNkJBQVgsV0FBVyxpR0FBYztZQW5HNUMsNktBcWhCQzs7OztRQXBoQlEsTUFBTSxDQUFDLElBQUksR0FBRyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUE7OztnQkFHakI7WUFDTjtnQkFDRSxFQUFFLEVBQUUsV0FBVztnQkFDZixJQUFJLEVBQUUsV0FBVztnQkFDakIsV0FBVyxFQUFFLGtHQUFrRztnQkFDL0csUUFBUSxFQUFFLEtBQUs7Z0JBQ2YsUUFBUSxFQUFFLFdBQVc7Z0JBQ3JCLEtBQUssRUFBRSxrQkFBa0I7Z0JBQ3pCLElBQUksRUFBRSxFQUFFO2dCQUNSLE9BQU8sRUFBRTtvQkFDUCxFQUFFLEdBQUcsRUFBRSxtQkFBbUIsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLFdBQVcsRUFBRSx1QkFBdUIsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFO29CQUM3RixFQUFFLEdBQUcsRUFBRSxtQkFBbUIsRUFBRSxLQUFLLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxtQkFBbUIsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFO29CQUNsRyxFQUFFLEdBQUcsRUFBRSx1QkFBdUIsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLFdBQVcsRUFBRSxtQkFBbUIsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFO29CQUM3RixFQUFFLEdBQUcsRUFBRSxtQkFBbUIsRUFBRSxLQUFLLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRTtpQkFDL0Y7Z0JBQ0QsT0FBTyxFQUFFLENBQUMsZUFBZSxDQUFDO2dCQUMxQixhQUFhLEVBQUUsS0FBSztnQkFDcEIsUUFBUSxFQUFFLEtBQUs7YUFDaEI7WUFDRDtnQkFDRSxFQUFFLEVBQUUsT0FBTztnQkFDWCxJQUFJLEVBQUUsT0FBTztnQkFDYixXQUFXLEVBQUUsNEZBQTRGO2dCQUN6RyxRQUFRLEVBQUUsYUFBYTtnQkFDdkIsUUFBUSxFQUFFLFlBQVk7Z0JBQ3RCLEtBQUssRUFBRSxvQkFBb0I7Z0JBQzNCLElBQUksRUFBRSxJQUFJO2dCQUNWLE9BQU8sRUFBRTtvQkFDUCxFQUFFLEdBQUcsRUFBRSwwQkFBMEIsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRTtvQkFDbkcsRUFBRSxHQUFHLEVBQUUseUJBQXlCLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxXQUFXLEVBQUUsa0NBQWtDLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRTtpQkFDaEg7Z0JBQ0QsT0FBTyxFQUFFLENBQUMsT0FBTyxDQUFDO2FBQ25CO1lBQ0Q7Z0JBQ0UsRUFBRSxFQUFFLE9BQU87Z0JBQ1gsSUFBSSxFQUFFLE9BQU87Z0JBQ2IsV0FBVyxFQUFFLDRGQUE0RjtnQkFDekcsUUFBUSxFQUFFLEtBQUs7Z0JBQ2YsUUFBUSxFQUFFLFdBQVc7Z0JBQ3JCLEtBQUssRUFBRSxjQUFjO2dCQUNyQixJQUFJLEVBQUUsSUFBSTtnQkFDVixPQUFPLEVBQUU7b0JBQ1AsRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUUsV0FBVyxFQUFFLCtCQUErQixFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUU7b0JBQ3ZGLEVBQUUsR0FBRyxFQUFFLGtCQUFrQixFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsV0FBVyxFQUFFLHNCQUFzQixFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUU7aUJBQ3BHO2dCQUNELE9BQU8sRUFBRSxDQUFDLHdCQUF3QixDQUFDO2FBQ3BDO1lBQ0Q7Z0JBQ0UsRUFBRSxFQUFFLE9BQU87Z0JBQ1gsSUFBSSxFQUFFLE9BQU87Z0JBQ2IsV0FBVyxFQUFFLCtFQUErRTtnQkFDNUYsUUFBUSxFQUFFLFlBQVk7Z0JBQ3RCLFFBQVEsRUFBRSxPQUFPO2dCQUNqQixLQUFLLEVBQUUsY0FBYztnQkFDckIsSUFBSSxFQUFFLEVBQUU7Z0JBQ1IsT0FBTyxFQUFFLENBQUMsdUJBQXVCLEVBQUUsbUJBQW1CLENBQUM7YUFDeEQ7WUFDRDtnQkFDRSxFQUFFLEVBQUUsT0FBTztnQkFDWCxJQUFJLEVBQUUsT0FBTztnQkFDYixXQUFXLEVBQUUsdUZBQXVGO2dCQUNwRyxRQUFRLEVBQUUsVUFBVTtnQkFDcEIsUUFBUSxFQUFFLFVBQVU7Z0JBQ3BCLEtBQUssRUFBRSxjQUFjO2dCQUNyQixJQUFJLEVBQUUsSUFBSTthQUNYO1lBQ0Q7Z0JBQ0UsRUFBRSxFQUFFLFVBQVU7Z0JBQ2QsSUFBSSxFQUFFLFlBQVk7Z0JBQ2xCLFdBQVcsRUFBRSxtRkFBbUY7Z0JBQ2hHLFFBQVEsRUFBRSxVQUFVO2dCQUNwQixRQUFRLEVBQUUsVUFBVTtnQkFDcEIsS0FBSyxFQUFFLG9CQUFvQjtnQkFDM0IsSUFBSSxFQUFFLElBQUk7Z0JBQ1YsT0FBTyxFQUFFO29CQUNQLEVBQUUsR0FBRyxFQUFFLGVBQWUsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxvQkFBb0IsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFO29CQUM5RixFQUFFLEdBQUcsRUFBRSxtQkFBbUIsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLFdBQVcsRUFBRSxvQkFBb0IsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFO29CQUMxRixFQUFFLEdBQUcsRUFBRSxhQUFhLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsdUJBQXVCLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRTtpQkFDakc7Z0JBQ0QsT0FBTyxFQUFFLENBQUMsMEJBQTBCLENBQUM7YUFDdEM7U0FDRjs7O0dBR04sQ0FBQztRQUVLLE1BQU0sQ0FBQyxVQUFVLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUd4QyxxRUFBdUMsRUFBRSxFQUFDO1FBQTFDLElBQWdCLElBQUksMENBQXNCO1FBQTFDLElBQWdCLElBQUksZ0RBQXNCO1FBRzFDLGdKQUE0QyxLQUFLLEdBQUM7UUFBbEQsSUFBaUIsZ0JBQWdCLHNEQUFpQjtRQUFsRCxJQUFpQixnQkFBZ0IsNERBQWlCO1FBR2xELGtKQUF1QyxFQUFFLEdBQUM7UUFBMUMsSUFBaUIsV0FBVyxpREFBYztRQUExQyxJQUFpQixXQUFXLHVEQUFjO1FBRW5DLE1BQU0sQ0FBQyxNQUFNLEdBQUc7WUFDckIsVUFBVSxDQUFDLGFBQWE7WUFDeEIsR0FBRyxDQUFBOzs7Ozs7Ozs7Ozs7aUJBWVUsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7OztpQkFLeEMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7NEJBMkI3QixVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7c0JBQzlDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQztpQkFDN0MsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozt3QkFLakMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDO2lCQUMvQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7c0JBSW5DLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQztpQkFDN0MsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDO3dCQUNqQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7OztzQkFNMUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzRCQUNsQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7OztpQkFHbkQsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7O3dCQU9qQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7aUJBSS9DLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7O2lCQWV4QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O3NCQWdDbkMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzRCQUNsQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7Ozs7Ozs7O3dCQVc1QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUNBQy9CLFVBQVUsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLEVBQUUsaUJBQWlCLENBQUM7Ozs7Ozs7Ozs7Ozs7O3NCQWNwRSxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7Ozs7Ozs7O2lCQVc3QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7Ozs7Ozs7OztpQkFZeEMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7Ozs7Ozs7aUJBWXhDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7Ozs7Ozs7O3NCQVluQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSwwQkFBMEIsQ0FBQztpQkFDOUQsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7aUJBTXhDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7Z0NBY3pCLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7OztpQkFPdkQsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7c0JBa0JuQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUJBQzdDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7aUJBdUJ4QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7Ozs7aUJBT3hDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7O2lCQU14QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7O0tBRXBEO1NBQ0YsQ0FBQztRQUVGLElBQVksVUFBVTtZQUNwQixNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7WUFDM0QsT0FBTyxDQUFDLEtBQUssRUFBRSxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUM3QyxDQUFDO1FBRUQsSUFBWSxZQUFZO1lBQ3RCLElBQUksTUFBTSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUM7WUFFdkIsSUFBSSxJQUFJLENBQUMsZ0JBQWdCLEtBQUssS0FBSyxFQUFFLENBQUM7Z0JBQ3BDLE1BQU0sR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsUUFBUSxLQUFLLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1lBQzFFLENBQUM7WUFFRCxJQUFJLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQztnQkFDNUIsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDcEQsTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQ3BCLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FDTixHQUFHLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUM7b0JBQ3RDLEdBQUcsQ0FBQyxXQUFXLENBQUMsV0FBVyxFQUFFLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQztvQkFDN0MsR0FBRyxDQUFDLFFBQVEsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQzdDLENBQUM7WUFDSixDQUFDO1lBRUQsT0FBTyxNQUFNLENBQUM7UUFDaEIsQ0FBQztRQUVNLE1BQU07WUFDWCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDO1lBRW5DLE9BQU8sSUFBSSxDQUFBOzs7Ozs7OztZQVFILElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUNuQixDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFBOzt1Q0FFYyxJQUFJLENBQUMsZ0JBQWdCLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUU7eUJBQzNELEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUFDOztrQkFFN0MsR0FBRzs7YUFFUixDQUNGOzs7Ozs7Ozs7OztxQkFXVSxJQUFJLENBQUMsV0FBVztxQkFDaEIsQ0FBQyxDQUFRLEVBQUUsRUFBRSxHQUFHLElBQUksQ0FBQyxXQUFXLEdBQUksQ0FBQyxDQUFDLE1BQTJCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQzs7Ozs7O1VBTXJGLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQztnQkFDbkIsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQ2hELENBQUMsQ0FBQyxJQUFJLENBQUE7Ozs7Ozs7Ozs7OzthQVlIOztLQUVSLENBQUM7UUFDSixDQUFDO1FBRU8sYUFBYSxDQUFDLEdBQWlCO1lBQ3JDLE9BQU8sSUFBSSxDQUFBOzs7O2NBSUQsR0FBRyxDQUFDLE9BQU87Z0JBQ1gsQ0FBQyxDQUFDLElBQUksQ0FBQSxhQUFhLEdBQUcsQ0FBQyxPQUFPLFVBQVUsR0FBRyxDQUFDLElBQUksSUFBSTtnQkFDcEQsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxRQUFRO29CQUNaLENBQUMsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQztvQkFDckMsQ0FBQyxDQUFDLElBQUksQ0FBQSxpQ0FBaUMsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLFNBQVM7OztvQ0FHNUQsR0FBRyxDQUFDLElBQUk7MENBQ0YsR0FBRyxDQUFDLFFBQVE7Ozt1Q0FHZixHQUFHLENBQUMsV0FBVzs7MkNBRVgsR0FBRyxDQUFDLEtBQUssS0FBSyxHQUFHLENBQUMsS0FBSztpREFDakIsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUM7Ozs7Ozs7Ozs7OztLQVl4RSxDQUFDO1FBQ0osQ0FBQztRQUVPLGdCQUFnQixDQUFDLElBQVk7WUFDbkMsTUFBTSxLQUFLLEdBQW1DO2dCQUM1QyxXQUFXLEVBQUUsSUFBSSxDQUFBLGdWQUFnVjtnQkFDalcsWUFBWSxFQUFFLElBQUksQ0FBQSwrT0FBK087Z0JBQ2pRLFdBQVcsRUFBRSxJQUFJLENBQUEsME1BQTBNO2dCQUMzTixPQUFPLEVBQUUsSUFBSSxDQUFBLG9SQUFvUjtnQkFDalMsVUFBVSxFQUFFLElBQUksQ0FBQSw0T0FBNE87Z0JBQzVQLFFBQVEsRUFBRSxJQUFJLENBQUEseVNBQXlTO2dCQUN2VCxTQUFTLEVBQUUsSUFBSSxDQUFBLDhYQUE4WDtnQkFDN1ksTUFBTSxFQUFFLElBQUksQ0FBQSw4TkFBOE47Z0JBQzFPLFFBQVEsRUFBRSxJQUFJLENBQUEsaUpBQWlKO2dCQUMvSixTQUFTLEVBQUUsSUFBSSxDQUFBLDJPQUEyTzthQUMzUCxDQUFDO1lBRUYsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFBLHdDQUF3QyxDQUFDO1FBQ3JFLENBQUM7UUFFTyxZQUFZLENBQUMsR0FBaUI7WUFDcEMsSUFBSSxDQUFDLGFBQWEsQ0FDaEIsSUFBSSxXQUFXLENBQUMsWUFBWSxFQUFFO2dCQUM1QixNQUFNLEVBQUUsRUFBRSxHQUFHLEVBQUU7Z0JBQ2YsT0FBTyxFQUFFLElBQUk7Z0JBQ2IsUUFBUSxFQUFFLElBQUk7YUFDZixDQUFDLENBQ0gsQ0FBQztRQUNKLENBQUM7Ozs7OztZQXBoQlUsdURBQWM7Ozs7O1NBQWQsY0FBYyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/catalog",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "private": false,
5
5
  "description": "UI component catalog for serve.zone",
6
6
  "main": "dist_ts_web/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/catalog',
6
- version: '2.7.0',
6
+ version: '2.8.0',
7
7
  description: 'UI component catalog for serve.zone'
8
8
  }
@@ -28,6 +28,7 @@ export * from './sz-registry-external-view.js';
28
28
  export * from './sz-services-list-view.js';
29
29
  export * from './sz-services-backups-view.js';
30
30
  export * from './sz-service-detail-view.js';
31
+ export * from './sz-app-store-view.js';
31
32
 
32
33
  // Tokens View
33
34
  export * from './sz-tokens-view.js';
@@ -0,0 +1,568 @@
1
+ import {
2
+ DeesElement,
3
+ customElement,
4
+ html,
5
+ css,
6
+ cssManager,
7
+ property,
8
+ state,
9
+ type TemplateResult,
10
+ } from '@design.estate/dees-element';
11
+
12
+ declare global {
13
+ interface HTMLElementTagNameMap {
14
+ 'sz-app-store-view': SzAppStoreView;
15
+ }
16
+ }
17
+
18
+ export interface IAppTemplate {
19
+ id: string;
20
+ name: string;
21
+ description: string;
22
+ category: string;
23
+ iconUrl?: string;
24
+ iconName?: string;
25
+ image: string;
26
+ port: number;
27
+ envVars?: Array<{ key: string; value: string; description: string; required?: boolean }>;
28
+ volumes?: string[];
29
+ enableMongoDB?: boolean;
30
+ enableS3?: boolean;
31
+ enableClickHouse?: boolean;
32
+ }
33
+
34
+ @customElement('sz-app-store-view')
35
+ export class SzAppStoreView extends DeesElement {
36
+ public static demo = () => html`
37
+ <div style="padding: 24px; max-width: 1200px;">
38
+ <sz-app-store-view
39
+ .apps=${[
40
+ {
41
+ id: 'wordpress',
42
+ name: 'WordPress',
43
+ description: 'The world\'s most popular open-source content management system for building websites and blogs.',
44
+ category: 'CMS',
45
+ iconName: 'file-text',
46
+ image: 'wordpress:latest',
47
+ port: 80,
48
+ envVars: [
49
+ { key: 'WORDPRESS_DB_HOST', value: '', description: 'Database host address', required: true },
50
+ { key: 'WORDPRESS_DB_USER', value: 'wordpress', description: 'Database username', required: true },
51
+ { key: 'WORDPRESS_DB_PASSWORD', value: '', description: 'Database password', required: true },
52
+ { key: 'WORDPRESS_DB_NAME', value: 'wordpress', description: 'Database name', required: true },
53
+ ],
54
+ volumes: ['/var/www/html'],
55
+ enableMongoDB: false,
56
+ enableS3: false,
57
+ },
58
+ {
59
+ id: 'gitea',
60
+ name: 'Gitea',
61
+ description: 'A lightweight, self-hosted Git service. Painless setup for your own code hosting platform.',
62
+ category: 'Development',
63
+ iconName: 'git-branch',
64
+ image: 'gitea/gitea:latest',
65
+ port: 3000,
66
+ envVars: [
67
+ { key: 'GITEA__database__DB_TYPE', value: 'sqlite3', description: 'Database type', required: true },
68
+ { key: 'GITEA__server__ROOT_URL', value: '', description: 'Public URL of the Gitea instance', required: false },
69
+ ],
70
+ volumes: ['/data'],
71
+ },
72
+ {
73
+ id: 'ghost',
74
+ name: 'Ghost',
75
+ description: 'A powerful open-source publishing platform for professional bloggers and content creators.',
76
+ category: 'CMS',
77
+ iconName: 'book-open',
78
+ image: 'ghost:latest',
79
+ port: 2368,
80
+ envVars: [
81
+ { key: 'url', value: '', description: 'Public URL for the Ghost site', required: true },
82
+ { key: 'database__client', value: 'sqlite3', description: 'Database client type', required: false },
83
+ ],
84
+ volumes: ['/var/lib/ghost/content'],
85
+ },
86
+ {
87
+ id: 'nginx',
88
+ name: 'Nginx',
89
+ description: 'High-performance HTTP server and reverse proxy with low resource consumption.',
90
+ category: 'Web Server',
91
+ iconName: 'globe',
92
+ image: 'nginx:alpine',
93
+ port: 80,
94
+ volumes: ['/usr/share/nginx/html', '/etc/nginx/conf.d'],
95
+ },
96
+ {
97
+ id: 'redis',
98
+ name: 'Redis',
99
+ description: 'In-memory data store used as a database, cache, streaming engine, and message broker.',
100
+ category: 'Database',
101
+ iconName: 'database',
102
+ image: 'redis:alpine',
103
+ port: 6379,
104
+ },
105
+ {
106
+ id: 'postgres',
107
+ name: 'PostgreSQL',
108
+ description: 'Advanced open-source relational database with strong reliability and feature set.',
109
+ category: 'Database',
110
+ iconName: 'database',
111
+ image: 'postgres:16-alpine',
112
+ port: 5432,
113
+ envVars: [
114
+ { key: 'POSTGRES_USER', value: 'postgres', description: 'Superuser username', required: true },
115
+ { key: 'POSTGRES_PASSWORD', value: '', description: 'Superuser password', required: true },
116
+ { key: 'POSTGRES_DB', value: 'postgres', description: 'Default database name', required: false },
117
+ ],
118
+ volumes: ['/var/lib/postgresql/data'],
119
+ },
120
+ ]}
121
+ ></sz-app-store-view>
122
+ </div>
123
+ `;
124
+
125
+ public static demoGroups = ['Services'];
126
+
127
+ @property({ type: Array })
128
+ public accessor apps: IAppTemplate[] = [];
129
+
130
+ @state()
131
+ private accessor selectedCategory: string = 'All';
132
+
133
+ @state()
134
+ private accessor searchQuery: string = '';
135
+
136
+ public static styles = [
137
+ cssManager.defaultStyles,
138
+ css`
139
+ :host {
140
+ display: block;
141
+ }
142
+
143
+ .header {
144
+ margin-bottom: 24px;
145
+ }
146
+
147
+ .header-title {
148
+ font-size: 20px;
149
+ font-weight: 600;
150
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
151
+ }
152
+
153
+ .header-subtitle {
154
+ font-size: 14px;
155
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
156
+ margin-top: 4px;
157
+ }
158
+
159
+ .filter-bar {
160
+ display: flex;
161
+ flex-wrap: wrap;
162
+ align-items: center;
163
+ gap: 12px;
164
+ margin-bottom: 24px;
165
+ }
166
+
167
+ .category-pills {
168
+ display: flex;
169
+ flex-wrap: wrap;
170
+ gap: 8px;
171
+ flex: 1;
172
+ min-width: 0;
173
+ }
174
+
175
+ .category-pill {
176
+ padding: 6px 14px;
177
+ border-radius: 9999px;
178
+ font-size: 13px;
179
+ font-weight: 500;
180
+ cursor: pointer;
181
+ transition: all 200ms ease;
182
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
183
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
184
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
185
+ white-space: nowrap;
186
+ }
187
+
188
+ .category-pill:hover {
189
+ border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
190
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
191
+ }
192
+
193
+ .category-pill.active {
194
+ background: ${cssManager.bdTheme('#18181b', '#fafafa')};
195
+ color: ${cssManager.bdTheme('#fafafa', '#18181b')};
196
+ border-color: ${cssManager.bdTheme('#18181b', '#fafafa')};
197
+ }
198
+
199
+ .search-input {
200
+ width: 240px;
201
+ padding: 8px 12px 8px 36px;
202
+ background: ${cssManager.bdTheme('#ffffff', '#18181b')};
203
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
204
+ border-radius: 6px;
205
+ font-size: 14px;
206
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
207
+ outline: none;
208
+ transition: border-color 200ms ease;
209
+ box-sizing: border-box;
210
+ }
211
+
212
+ .search-input:focus {
213
+ border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
214
+ }
215
+
216
+ .search-input::placeholder {
217
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
218
+ }
219
+
220
+ .search-wrapper {
221
+ position: relative;
222
+ flex-shrink: 0;
223
+ }
224
+
225
+ .search-icon {
226
+ position: absolute;
227
+ left: 10px;
228
+ top: 50%;
229
+ transform: translateY(-50%);
230
+ width: 16px;
231
+ height: 16px;
232
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
233
+ pointer-events: none;
234
+ }
235
+
236
+ .app-grid {
237
+ display: grid;
238
+ grid-template-columns: repeat(3, 1fr);
239
+ gap: 16px;
240
+ }
241
+
242
+ @media (max-width: 1024px) {
243
+ .app-grid {
244
+ grid-template-columns: repeat(2, 1fr);
245
+ }
246
+ }
247
+
248
+ @media (max-width: 640px) {
249
+ .app-grid {
250
+ grid-template-columns: 1fr;
251
+ }
252
+
253
+ .filter-bar {
254
+ flex-direction: column;
255
+ align-items: stretch;
256
+ }
257
+
258
+ .search-input {
259
+ width: 100%;
260
+ }
261
+ }
262
+
263
+ .app-card {
264
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
265
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
266
+ border-radius: 8px;
267
+ padding: 20px;
268
+ display: flex;
269
+ flex-direction: column;
270
+ gap: 12px;
271
+ transition: all 200ms ease;
272
+ cursor: default;
273
+ }
274
+
275
+ .app-card:hover {
276
+ border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
277
+ box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0,0,0,0.05)', 'rgba(0,0,0,0.2)')};
278
+ transform: translateY(-1px);
279
+ }
280
+
281
+ .card-top {
282
+ display: flex;
283
+ align-items: flex-start;
284
+ gap: 14px;
285
+ }
286
+
287
+ .app-icon {
288
+ width: 44px;
289
+ height: 44px;
290
+ border-radius: 10px;
291
+ background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: center;
295
+ flex-shrink: 0;
296
+ overflow: hidden;
297
+ }
298
+
299
+ .app-icon svg {
300
+ width: 22px;
301
+ height: 22px;
302
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
303
+ }
304
+
305
+ .app-icon img {
306
+ width: 100%;
307
+ height: 100%;
308
+ object-fit: cover;
309
+ }
310
+
311
+ .app-icon .letter-fallback {
312
+ font-size: 20px;
313
+ font-weight: 600;
314
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
315
+ user-select: none;
316
+ }
317
+
318
+ .card-info {
319
+ flex: 1;
320
+ min-width: 0;
321
+ }
322
+
323
+ .app-name {
324
+ font-size: 15px;
325
+ font-weight: 600;
326
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
327
+ line-height: 1.3;
328
+ }
329
+
330
+ .category-badge {
331
+ display: inline-flex;
332
+ align-items: center;
333
+ padding: 2px 8px;
334
+ border-radius: 9999px;
335
+ font-size: 11px;
336
+ font-weight: 500;
337
+ margin-top: 4px;
338
+ background: ${cssManager.bdTheme('#eff6ff', 'rgba(59, 130, 246, 0.15)')};
339
+ color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
340
+ }
341
+
342
+ .app-description {
343
+ font-size: 13px;
344
+ line-height: 1.5;
345
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
346
+ display: -webkit-box;
347
+ -webkit-line-clamp: 2;
348
+ -webkit-box-orient: vertical;
349
+ overflow: hidden;
350
+ text-overflow: ellipsis;
351
+ flex: 1;
352
+ }
353
+
354
+ .card-footer {
355
+ display: flex;
356
+ align-items: center;
357
+ justify-content: space-between;
358
+ padding-top: 12px;
359
+ border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')};
360
+ margin-top: auto;
361
+ }
362
+
363
+ .image-tag {
364
+ font-family: monospace;
365
+ font-size: 12px;
366
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
367
+ overflow: hidden;
368
+ text-overflow: ellipsis;
369
+ white-space: nowrap;
370
+ max-width: 160px;
371
+ }
372
+
373
+ .deploy-button {
374
+ display: inline-flex;
375
+ align-items: center;
376
+ gap: 6px;
377
+ padding: 7px 16px;
378
+ border-radius: 6px;
379
+ font-size: 13px;
380
+ font-weight: 500;
381
+ cursor: pointer;
382
+ transition: all 200ms ease;
383
+ border: none;
384
+ background: ${cssManager.bdTheme('#18181b', '#fafafa')};
385
+ color: ${cssManager.bdTheme('#fafafa', '#18181b')};
386
+ white-space: nowrap;
387
+ flex-shrink: 0;
388
+ }
389
+
390
+ .deploy-button:hover {
391
+ opacity: 0.85;
392
+ }
393
+
394
+ .deploy-button svg {
395
+ width: 14px;
396
+ height: 14px;
397
+ }
398
+
399
+ .empty-state {
400
+ grid-column: 1 / -1;
401
+ padding: 64px 24px;
402
+ text-align: center;
403
+ }
404
+
405
+ .empty-icon {
406
+ width: 48px;
407
+ height: 48px;
408
+ color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
409
+ margin: 0 auto 16px;
410
+ }
411
+
412
+ .empty-title {
413
+ font-size: 16px;
414
+ font-weight: 600;
415
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
416
+ margin-bottom: 4px;
417
+ }
418
+
419
+ .empty-description {
420
+ font-size: 14px;
421
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
422
+ }
423
+ `,
424
+ ];
425
+
426
+ private get categories(): string[] {
427
+ const cats = new Set(this.apps.map((app) => app.category));
428
+ return ['All', ...Array.from(cats).sort()];
429
+ }
430
+
431
+ private get filteredApps(): IAppTemplate[] {
432
+ let result = this.apps;
433
+
434
+ if (this.selectedCategory !== 'All') {
435
+ result = result.filter((app) => app.category === this.selectedCategory);
436
+ }
437
+
438
+ if (this.searchQuery.trim()) {
439
+ const query = this.searchQuery.trim().toLowerCase();
440
+ result = result.filter(
441
+ (app) =>
442
+ app.name.toLowerCase().includes(query) ||
443
+ app.description.toLowerCase().includes(query) ||
444
+ app.category.toLowerCase().includes(query)
445
+ );
446
+ }
447
+
448
+ return result;
449
+ }
450
+
451
+ public render(): TemplateResult {
452
+ const filtered = this.filteredApps;
453
+
454
+ return html`
455
+ <div class="header">
456
+ <div class="header-title">App Store</div>
457
+ <div class="header-subtitle">Deploy popular applications with one click</div>
458
+ </div>
459
+
460
+ <div class="filter-bar">
461
+ <div class="category-pills">
462
+ ${this.categories.map(
463
+ (cat) => html`
464
+ <button
465
+ class="category-pill ${this.selectedCategory === cat ? 'active' : ''}"
466
+ @click=${() => { this.selectedCategory = cat; }}
467
+ >
468
+ ${cat}
469
+ </button>
470
+ `
471
+ )}
472
+ </div>
473
+ <div class="search-wrapper">
474
+ <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
475
+ <circle cx="11" cy="11" r="8"></circle>
476
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
477
+ </svg>
478
+ <input
479
+ type="text"
480
+ class="search-input"
481
+ placeholder="Search apps..."
482
+ .value=${this.searchQuery}
483
+ @input=${(e: Event) => { this.searchQuery = (e.target as HTMLInputElement).value; }}
484
+ >
485
+ </div>
486
+ </div>
487
+
488
+ <div class="app-grid">
489
+ ${filtered.length > 0
490
+ ? filtered.map((app) => this.renderAppCard(app))
491
+ : html`
492
+ <div class="empty-state">
493
+ <svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
494
+ <circle cx="11" cy="11" r="8"></circle>
495
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
496
+ <line x1="8" y1="11" x2="14" y2="11"></line>
497
+ </svg>
498
+ <div class="empty-title">No apps found</div>
499
+ <div class="empty-description">
500
+ Try adjusting your search or filter to find what you're looking for.
501
+ </div>
502
+ </div>
503
+ `}
504
+ </div>
505
+ `;
506
+ }
507
+
508
+ private renderAppCard(app: IAppTemplate): TemplateResult {
509
+ return html`
510
+ <div class="app-card">
511
+ <div class="card-top">
512
+ <div class="app-icon">
513
+ ${app.iconUrl
514
+ ? html`<img src="${app.iconUrl}" alt="${app.name}">`
515
+ : app.iconName
516
+ ? this.renderIconByName(app.iconName)
517
+ : html`<span class="letter-fallback">${app.name.charAt(0).toUpperCase()}</span>`}
518
+ </div>
519
+ <div class="card-info">
520
+ <div class="app-name">${app.name}</div>
521
+ <div class="category-badge">${app.category}</div>
522
+ </div>
523
+ </div>
524
+ <div class="app-description">${app.description}</div>
525
+ <div class="card-footer">
526
+ <span class="image-tag" title="${app.image}">${app.image}</span>
527
+ <button class="deploy-button" @click=${() => this.handleDeploy(app)}>
528
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
529
+ <polyline points="20 12 20 22 4 22 4 12"></polyline>
530
+ <rect x="2" y="7" width="20" height="5"></rect>
531
+ <line x1="12" y1="22" x2="12" y2="7"></line>
532
+ <path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
533
+ <path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
534
+ </svg>
535
+ Deploy
536
+ </button>
537
+ </div>
538
+ </div>
539
+ `;
540
+ }
541
+
542
+ private renderIconByName(name: string): TemplateResult {
543
+ const icons: Record<string, TemplateResult> = {
544
+ 'file-text': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
545
+ 'git-branch': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg>`,
546
+ 'book-open': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>`,
547
+ 'globe': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>`,
548
+ 'database': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>`,
549
+ 'server': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>`,
550
+ 'package': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>`,
551
+ 'mail': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>`,
552
+ 'shield': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>`,
553
+ 'monitor': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>`,
554
+ };
555
+
556
+ return icons[name] || html`<span class="letter-fallback">?</span>`;
557
+ }
558
+
559
+ private handleDeploy(app: IAppTemplate) {
560
+ this.dispatchEvent(
561
+ new CustomEvent('deploy-app', {
562
+ detail: { app },
563
+ bubbles: true,
564
+ composed: true,
565
+ })
566
+ );
567
+ }
568
+ }