main
html 1,004 lines 38.5 KB
Raw
1 <html>
2 <head>
3 <title>Plugin Details</title>
4 <script type="module">
5 import { store } from "/plugins/_plugin_installer/webui/pluginInstallStore.js";
6 </script>
7 </head>
8 <body>
9 <div x-data>
10 <template x-if="$store.pluginInstallStore?.selectedPlugin">
11 <div class="pi-detail-container">
12 <div class="pi-hero">
13 <div class="pi-hero-thumb">
14 <template x-if="$store.pluginInstallStore.getDetailThumbnailUrl()">
15 <img :src="$store.pluginInstallStore.getDetailThumbnailUrl()"
16 :alt="$store.pluginInstallStore.selectedPlugin.title"
17 @error="$store.pluginInstallStore.detailThumbnailUrl = null">
18 </template>
19 <template x-if="!$store.pluginInstallStore.getDetailThumbnailUrl()">
20 <x-icon class="pi-hero-placeholder" name="extension"></x-icon>
21 </template>
22 </div>
23 <div class="pi-hero-info">
24 <div class="pi-hero-title-row">
25 <div class="pi-hero-main">
26 <h2 class="pi-hero-title" x-text="$store.pluginInstallStore.selectedPlugin.title || $store.pluginInstallStore.selectedPlugin.key"></h2>
27 <div class="pi-status-badges">
28 <template x-if="$store.pluginInstallStore.selectedPlugin.suspended">
29 <span class="pi-card-suspended-pill">
30 <x-icon name="priority_high"></x-icon>
31 <span>Suspended</span>
32 </span>
33 </template>
34 <template x-if="$store.pluginInstallStore.selectedPlugin.installed">
35 <span class="pi-card-installed-pill">Installed</span>
36 </template>
37 <template x-if="$store.pluginInstallStore.selectedPlugin.has_update">
38 <span class="pi-card-update-pill">New version</span>
39 </template>
40 </div>
41 <div class="pi-hero-meta">
42 <template x-if="$store.pluginInstallStore.selectedPlugin.author">
43 <span class="pi-hero-author">
44 <span class="pi-hero-label">Author</span>
45 <span x-text="$store.pluginInstallStore.selectedPlugin.author"></span>
46 </span>
47 </template>
48 <span class="pi-hero-stars">
49 <x-icon name="star"></x-icon>
50 <span x-text="($store.pluginInstallStore.selectedPlugin.stars || 0)"></span>
51 <template x-if="$store.pluginInstallStore.selectedPlugin.github">
52 <a :href="$store.pluginInstallStore.selectedPlugin.github"
53 target="_blank"
54 rel="noopener noreferrer"
55 class="pi-dev-link">
56 Give a star
57 </a>
58 </template>
59 </span>
60 </div>
61 <div class="pi-hero-tags" x-show="$store.pluginInstallStore.selectedPlugin.tags?.length">
62 <template x-for="tag in $store.pluginInstallStore.selectedPlugin.tags || []" :key="tag">
63 <span class="pi-tag" x-text="tag"></span>
64 </template>
65 </div>
66 </div>
67 <template x-if="$store.pluginInstallStore.selectedPlugin.installed && $store.pluginInstallStore.installedPluginInfo">
68 <div class="pi-hero-manage">
69 <template x-if="$store.pluginInstallStore.installedPluginInfo.has_main_screen">
70 <button type="button" class="button"
71 @click="$store.pluginInstallStore.handleOpenPlugin()">
72 <x-icon class="icon" name="dashboard"></x-icon> Open
73 </button>
74 </template>
75 <template x-if="$store.pluginInstallStore.installedPluginInfo.has_config_screen">
76 <button type="button" class="button"
77 @click="$store.pluginInstallStore.handleOpenConfig()">
78 <x-icon class="icon" name="settings"></x-icon> Config
79 </button>
80 </template>
81 <template x-if="$store.pluginInstallStore.installedPluginInfo.has_readme">
82 <button type="button" class="button"
83 @click="$store.pluginInstallStore.handleOpenDoc('readme')">
84 <x-icon class="icon" name="description"></x-icon> README
85 </button>
86 </template>
87 <template x-if="$store.pluginInstallStore.installedPluginInfo.has_license">
88 <button type="button" class="button"
89 @click="$store.pluginInstallStore.handleOpenDoc('license')">
90 <x-icon class="icon" name="gavel"></x-icon> License
91 </button>
92 </template>
93 <template x-if="$store.pluginInstallStore.installedPluginInfo.has_execute_script">
94 <button type="button" class="button"
95 @click="$store.pluginInstallStore.handleOpenExecute()">
96 <x-icon class="icon" name="terminal"></x-icon> Script
97 </button>
98 </template>
99 <button type="button" class="button"
100 @click="$store.pluginInstallStore.handleOpenInfo()">
101 <x-icon class="icon" name="info"></x-icon> Info
102 </button>
103 </div>
104 </template>
105 </div>
106 </div>
107 </div>
108
109 <template x-if="$store.pluginInstallStore.selectedPlugin.suspended">
110 <div class="pi-suspension-banner">
111 <x-icon name="priority_high"></x-icon>
112 <div class="pi-suspension-banner-copy">
113 <div class="pi-suspension-banner-title">Plugin has been suspended for the following reasons:</div>
114 <div class="pi-suspension-banner-explanation"
115 x-text="$store.pluginInstallStore.selectedPlugin.suspended"></div>
116 </div>
117 </div>
118 </template>
119
120 <div class="pi-description" x-text="$store.pluginInstallStore.selectedPlugin.description || 'No description available.'"></div>
121
122 <div class="pi-screenshots-section"
123 x-show="$store.pluginInstallStore.selectedPlugin.screenshots?.length">
124 <div class="pi-screenshots-header">Screenshots</div>
125 <div class="pi-screenshots-grid">
126 <template x-for="(url, i) in ($store.pluginInstallStore.selectedPlugin.screenshots || [])" :key="i">
127 <img class="pi-screenshot-thumb"
128 :src="url"
129 :alt="$store.pluginInstallStore.selectedPlugin.title + ' screenshot ' + (i + 1)"
130 @click="$store.pluginInstallStore.openScreenshot(url)">
131 </template>
132 </div>
133 </div>
134
135 <div class="pi-actions-primary">
136 <template x-if="!$store.pluginInstallStore.selectedPlugin.installed">
137 <button class="pi-btn-install"
138 @click="$store.pluginInstallStore.installFromIndex($store.pluginInstallStore.selectedPlugin)"
139 :disabled="$store.pluginInstallStore.loading">
140 <template x-if="$store.pluginInstallStore.loading">
141 <span class="pi-btn-loading">
142 <span class="spinner"></span>
143 <span x-text="$store.pluginInstallStore.loadingMessage || 'Installing...'"></span>
144 </span>
145 </template>
146 <template x-if="!$store.pluginInstallStore.loading">
147 <x-icon name="download"></x-icon>
148 </template>
149 <template x-if="!$store.pluginInstallStore.loading">
150 <span>Install</span>
151 </template>
152 </button>
153 </template>
154 <template x-if="$store.pluginInstallStore.selectedPlugin.installed && $store.pluginInstallStore.installedPluginInfo?.is_custom && $store.pluginInstallStore.selectedPlugin.has_update">
155 <button type="button" class="pi-btn-install"
156 @click="$store.pluginInstallStore.handleUpdatePlugin()"
157 :disabled="$store.pluginInstallStore.loading">
158 <template x-if="$store.pluginInstallStore.loading">
159 <span class="pi-btn-loading">
160 <span class="spinner"></span>
161 <span x-text="$store.pluginInstallStore.loadingMessage || 'Installing...'"></span>
162 </span>
163 </template>
164 <template x-if="!$store.pluginInstallStore.loading">
165 <x-icon name="system_update"></x-icon>
166 </template>
167 <template x-if="!$store.pluginInstallStore.loading">
168 <span>Update</span>
169 </template>
170 </button>
171 </template>
172 <template x-if="$store.pluginInstallStore.selectedPlugin.installed && $store.pluginInstallStore.installedPluginInfo?.is_custom">
173 <button type="button" class="pi-btn-uninstall"
174 @click="$confirmClick($event, () => $store.pluginInstallStore.handleDeletePlugin())"
175 :disabled="$store.pluginInstallStore.loading">
176 <span class="pi-btn-loading" x-show="$store.pluginInstallStore.loading">
177 <span class="spinner"></span>
178 <span x-text="$store.pluginInstallStore.loadingMessage || 'Uninstalling...'"></span>
179 </span>
180 <span x-show="!$store.pluginInstallStore.loading">
181 <x-icon name="delete"></x-icon>
182 </span>
183 <span x-show="!$store.pluginInstallStore.loading">
184 <span>Uninstall</span>
185 </span>
186 </button>
187 </template>
188 <template x-if="$store.pluginInstallStore.selectedPlugin.discussion">
189 <a :href="$store.pluginInstallStore.selectedPlugin.discussion" target="_blank" class="pi-btn-discussion">
190 <x-icon name="forum"></x-icon>
191 <span>Discussion</span>
192 </a>
193 </template>
194 </div>
195
196 <template x-if="$store.pluginInstallStore.detailError">
197 <div class="pi-detail-error">
198 <div class="pi-detail-error-header">
199 <x-icon name="error"></x-icon>
200 <span x-text="$store.pluginInstallStore.detailError.message"></span>
201 </div>
202 <template x-if="$store.pluginInstallStore.detailError.conflicting_files?.length">
203 <ul class="pi-detail-error-files">
204 <template x-for="f in $store.pluginInstallStore.detailError.conflicting_files" :key="f">
205 <li x-text="f"></li>
206 </template>
207 </ul>
208 </template>
209 </div>
210 </template>
211
212 <div class="pi-readme-section" x-show="$store.pluginInstallStore.readmeContent || $store.pluginInstallStore.readmeLoading">
213 <div class="pi-readme-header">Readme</div>
214 <div x-show="$store.pluginInstallStore.readmeLoading" class="pi-loading-text">
215 Loading readme...
216 </div>
217 <div x-show="$store.pluginInstallStore.readmeContent && !$store.pluginInstallStore.readmeLoading"
218 class="pi-readme-content" x-html="$store.pluginInstallStore.readmeContent"></div>
219 </div>
220
221 <div class="pi-version-section"
222 x-show="$store.pluginInstallStore.getCurrentInstalledVersion() || $store.pluginInstallStore.getCurrentInstalledCommit() || $store.pluginInstallStore.getLatestPluginHubVersion() || $store.pluginInstallStore.getLatestPluginHubCommit()">
223 <div class="pi-version-header">Version</div>
224 <div class="pi-version-grid">
225 <div class="pi-version-card"
226 x-show="$store.pluginInstallStore.selectedPlugin.installed && ($store.pluginInstallStore.getCurrentInstalledVersion() || $store.pluginInstallStore.getCurrentInstalledCommit())">
227 <div class="pi-version-label">Current</div>
228 <div class="pi-version-name"
229 x-text="$store.pluginInstallStore.getCurrentInstalledVersion() || 'Unknown version'"></div>
230 <template x-if="$store.pluginInstallStore.getCurrentInstalledCommit() && $store.pluginInstallStore.getRepoCommitUrl($store.pluginInstallStore.selectedPlugin, $store.pluginInstallStore.getCurrentInstalledCommit())">
231 <a class="pi-version-commit"
232 :href="$store.pluginInstallStore.getRepoCommitUrl($store.pluginInstallStore.selectedPlugin, $store.pluginInstallStore.getCurrentInstalledCommit())"
233 target="_blank"
234 rel="noopener noreferrer"
235 x-text="$store.pluginInstallStore.getCommitShortHash($store.pluginInstallStore.getCurrentInstalledCommit())"></a>
236 </template>
237 <template x-if="!($store.pluginInstallStore.getCurrentInstalledCommit() && $store.pluginInstallStore.getRepoCommitUrl($store.pluginInstallStore.selectedPlugin, $store.pluginInstallStore.getCurrentInstalledCommit()))">
238 <div class="pi-version-commit"
239 x-text="$store.pluginInstallStore.getCommitShortHash($store.pluginInstallStore.getCurrentInstalledCommit()) || 'Unknown'"></div>
240 </template>
241 <div class="pi-version-time"
242 x-text="$store.pluginInstallStore.formatUserLocaleDateTime($store.pluginInstallStore.getCurrentInstalledCommitTimestamp()) || 'Timestamp unavailable'"></div>
243 </div>
244 <div class="pi-version-card"
245 x-show="$store.pluginInstallStore.getLatestPluginHubVersion() || $store.pluginInstallStore.getLatestPluginHubCommit()">
246 <div class="pi-version-label">Latest</div>
247 <div class="pi-version-name"
248 x-text="$store.pluginInstallStore.getLatestPluginHubVersion() || 'Unknown version'"></div>
249 <template x-if="$store.pluginInstallStore.getLatestPluginHubCommit() && $store.pluginInstallStore.getRepoCommitUrl($store.pluginInstallStore.selectedPlugin, $store.pluginInstallStore.getLatestPluginHubCommit())">
250 <a class="pi-version-commit"
251 :href="$store.pluginInstallStore.getRepoCommitUrl($store.pluginInstallStore.selectedPlugin, $store.pluginInstallStore.getLatestPluginHubCommit())"
252 target="_blank"
253 rel="noopener noreferrer"
254 x-text="$store.pluginInstallStore.getCommitShortHash($store.pluginInstallStore.getLatestPluginHubCommit())"></a>
255 </template>
256 <template x-if="!($store.pluginInstallStore.getLatestPluginHubCommit() && $store.pluginInstallStore.getRepoCommitUrl($store.pluginInstallStore.selectedPlugin, $store.pluginInstallStore.getLatestPluginHubCommit()))">
257 <div class="pi-version-commit"
258 x-text="$store.pluginInstallStore.getCommitShortHash($store.pluginInstallStore.getLatestPluginHubCommit()) || 'Unknown'"></div>
259 </template>
260 <div class="pi-version-time"
261 x-text="$store.pluginInstallStore.formatUserLocaleDateTime($store.pluginInstallStore.getLatestPluginHubCommitTimestamp()) || 'Timestamp unavailable'"></div>
262 </div>
263 </div>
264 </div>
265
266 <div class="pi-developer-section">
267 <div class="pi-developer-header">Plugin Code</div>
268 <div class="pi-developer-links">
269 <template x-if="$store.pluginInstallStore.selectedPlugin.github">
270 <a :href="$store.pluginInstallStore.selectedPlugin.github" target="_blank" class="pi-dev-link">
271 <x-icon name="code"></x-icon>
272 <span>GitHub Repository</span>
273 </a>
274 </template>
275 <template x-if="$store.pluginInstallStore.selectedPlugin.key">
276 <a :href="$store.pluginInstallStore.getIndexUrl($store.pluginInstallStore.selectedPlugin.key)" target="_blank" class="pi-dev-link">
277 <x-icon name="inventory_2"></x-icon>
278 <span>Plugin Index</span>
279 </a>
280 </template>
281 <template x-if="$store.pluginInstallStore.getReportUrl($store.pluginInstallStore.selectedPlugin)">
282 <a :href="$store.pluginInstallStore.getReportUrl($store.pluginInstallStore.selectedPlugin)" target="_blank" class="pi-dev-link pi-report-link">
283 <x-icon name="report"></x-icon>
284 <span>Report malicious plugin</span>
285 </a>
286 </template>
287 </div>
288 </div>
289 </div>
290 </template>
291
292 <template x-if="$store.pluginInstallStore?.selectedPlugin">
293 <div class="modal-footer pi-footer" data-modal-footer>
294 <div class="buttons-container">
295 <div class="buttons-left"></div>
296 <div class="buttons-right">
297 <button class="btn btn-cancel" @click="window.closeModal?.()">Close</button>
298 </div>
299 </div>
300 </div>
301 </template>
302 </div>
303
304 <style>
305 .pi-detail-container {
306 padding: 1rem;
307 margin: 0 auto;
308 }
309
310 .pi-hero {
311 display: flex;
312 align-items: flex-start;
313 gap: 1.5rem;
314 margin-bottom: 1.25rem;
315 }
316
317 .pi-hero-thumb {
318 width: 120px;
319 height: 120px;
320 border-radius: 12px;
321 overflow: hidden;
322 flex-shrink: 0;
323 display: flex;
324 align-items: center;
325 justify-content: center;
326 background: radial-gradient(circle at top, #6b728089, var(--color-panel)),
327 var(--color-panel);
328 border: 1px solid var(--color-border);
329 }
330
331 .pi-hero-thumb img {
332 width: 100%;
333 height: 100%;
334 object-fit: contain;
335 padding: 0.5rem;
336 }
337
338 .pi-hero-placeholder {
339 font-size: 5rem;
340 color: var(--color-text-muted);
341 }
342
343 .pi-hero-info {
344 flex: 1;
345 min-width: 0;
346 }
347
348 .pi-hero-title-row {
349 display: grid;
350 grid-template-columns: minmax(0, 1fr) clamp(15rem, 42%, 28rem);
351 align-items: flex-start;
352 column-gap: 1rem;
353 row-gap: 0.5rem;
354 margin-bottom: 0.5rem;
355 }
356
357 .pi-hero-main {
358 min-width: 0;
359 width: 100%;
360 row-gap: var(--spacing-xxs);
361 }
362
363 .pi-status-badges {
364 display: flex;
365 flex-wrap: wrap;
366 gap: 0.85rem;
367 margin: var(--spacing-xs) 0;
368 }
369
370 .pi-hero-title {
371 margin: 0;
372 font-size: 1.75rem;
373 font-weight: 600;
374 line-height: 1.2;
375 color: var(--color-text-primary);
376 min-width: 0;
377 }
378
379 .pi-hero-manage {
380 display: flex;
381 align-items: center;
382 gap: 0.5rem;
383 flex-wrap: wrap;
384 justify-content: flex-end;
385 align-content: flex-start;
386 width: 100%;
387 }
388
389 .pi-hero-manage .button {
390 padding: 0.3rem 0.6rem;
391 font-size: 0.9rem;
392 }
393
394 .pi-hero-manage .button .icon {
395 font-size: 1.1rem;
396 }
397
398 .pi-hero-meta {
399 display: flex;
400 align-items: center;
401 gap: 1rem;
402 flex-wrap: wrap;
403 margin-bottom: 0.5rem;
404 }
405
406 .pi-hero-label {
407 font-size: 0.8rem;
408 color: var(--color-text-muted);
409 margin-right: 0.2rem;
410 }
411
412 .pi-hero-author {
413 font-size: 1rem;
414 color: var(--color-text-secondary);
415 font-weight: 500;
416 display: inline-flex;
417 align-items: baseline;
418 gap: 0.15rem;
419 }
420
421 .pi-hero-stars {
422 display: inline-flex;
423 align-items: center;
424 gap: 0.3rem;
425 font-size: 0.95rem;
426 color: var(--color-text-secondary);
427 flex-wrap: wrap;
428 }
429
430 .pi-hero-stars .material-symbols-outlined {
431 font-size: 1.1rem;
432 color: #fbbf24;
433 }
434
435 .pi-hero-tags {
436 display: flex;
437 flex-wrap: wrap;
438 gap: 0.4rem;
439 }
440
441 .pi-card-installed-pill {
442 display: inline-flex;
443 padding: 0.24rem 0.5rem;
444 border-radius: 0.5rem;
445 background: rgba(34, 197, 94, 0.15);
446 color: #4ade80;
447 font-size: 0.72rem;
448 font-weight: 700;
449 }
450
451 body.light-mode .pi-card-installed-pill {
452 background: rgba(34, 197, 94, 0.22);
453 color: #166534;
454 }
455
456 .pi-card-update-pill {
457 display: inline-flex;
458 padding: 0.24rem 0.5rem;
459 border-radius: 0.5rem;
460 background: rgba(59, 130, 246, 0.16);
461 color: #60a5fa;
462 font-size: 0.72rem;
463 font-weight: 700;
464 }
465
466 body.light-mode .pi-card-update-pill {
467 background: rgba(59, 130, 246, 0.2);
468 color: #1d4ed8;
469 }
470
471 .pi-card-suspended-pill {
472 display: inline-flex;
473 align-items: center;
474 gap: 0.3rem;
475 padding: 0.24rem 0.5rem;
476 border-radius: 0.5rem;
477 background: rgba(245, 158, 11, 0.16);
478 color: #f59e0b;
479 font-size: 0.72rem;
480 font-weight: 700;
481 }
482
483 .pi-card-suspended-pill .material-symbols-outlined {
484 font-size: 0.95rem;
485 }
486
487 body.light-mode .pi-card-suspended-pill {
488 background: rgba(245, 158, 11, 0.2);
489 color: #b45309;
490 }
491
492 .pi-status-badges .pi-card-installed-pill {
493 position: static;
494 top: auto;
495 right: auto;
496 }
497
498 .pi-status-badges .pi-card-update-pill {
499 position: static;
500 top: auto;
501 right: auto;
502 }
503
504 .pi-status-badges .pi-card-suspended-pill {
505 position: static;
506 top: auto;
507 right: auto;
508 }
509
510 .pi-tag {
511 display: inline-flex;
512 align-items: center;
513 font-size: 0.8rem;
514 padding: 0.25rem 0.6rem;
515 border-radius: 4px;
516 background: var(--color-panel);
517 color: var(--color-text-secondary);
518 border: 1px solid var(--color-border);
519 font-weight: 500;
520 }
521
522 .pi-description {
523 font-size: 1rem;
524 line-height: 1.6;
525 color: var(--color-text-primary);
526 margin-bottom: 1.5rem;
527 }
528
529 .pi-suspension-banner {
530 display: flex;
531 align-items: flex-start;
532 gap: 0.75rem;
533 margin-bottom: 1rem;
534 padding: 0.9rem 1rem;
535 border: 1px solid rgba(245, 158, 11, 0.32);
536 border-radius: 10px;
537 background: rgba(245, 158, 11, 0.12);
538 color: #f59e0b;
539 }
540
541 .pi-suspension-banner .material-symbols-outlined {
542 font-size: 1.2rem;
543 line-height: 1.2;
544 flex-shrink: 0;
545 }
546
547 .pi-suspension-banner-copy {
548 min-width: 0;
549 }
550
551 .pi-suspension-banner-title {
552 font-size: 0.92rem;
553 font-weight: 700;
554 line-height: 1.45;
555 }
556
557 .pi-suspension-banner-explanation {
558 margin-top: 0.2rem;
559 font-size: 0.92rem;
560 line-height: 1.55;
561 white-space: pre-line;
562 }
563
564 body.light-mode .pi-suspension-banner {
565 border-color: rgba(217, 119, 6, 0.28);
566 background: rgba(245, 158, 11, 0.14);
567 color: #b45309;
568 }
569
570 .pi-actions-primary {
571 display: flex;
572 gap: 0.75rem;
573 margin-bottom: 1.5rem;
574 flex-wrap: wrap;
575 }
576
577 .pi-btn-install {
578 flex: 1;
579 min-width: 200px;
580 padding: 0.85rem 1.5rem;
581 border: none;
582 border-radius: 8px;
583 background: var(--color-highlight);
584 color: #fff;
585 font-family: 'Rubik';
586 font-size: 1rem;
587 font-weight: 600;
588 cursor: pointer;
589 transition: all 0.2s;
590 display: flex;
591 align-items: center;
592 justify-content: center;
593 gap: 0.5rem;
594 }
595
596 .pi-btn-install:hover:not(:disabled) {
597 opacity: 0.9;
598 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
599 }
600
601 .pi-btn-install:disabled {
602 opacity: 0.6;
603 cursor: not-allowed;
604 }
605
606 .pi-btn-install .material-symbols-outlined {
607 font-size: 1.3rem;
608 }
609
610 .pi-btn-uninstall {
611 flex: 1;
612 min-width: 200px;
613 padding: 0.85rem 1.5rem;
614 border: 2px solid var(--color-accent);
615 border-radius: 8px;
616 background: transparent;
617 color: var(--color-accent);
618 font-family: 'Rubik';
619 font-size: 1rem;
620 font-weight: 600;
621 cursor: pointer;
622 transition: all 0.2s;
623 display: flex;
624 align-items: center;
625 justify-content: center;
626 gap: 0.5rem;
627 }
628
629 .pi-btn-uninstall:hover {
630 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
631 background: var(--color-accent);
632 color: #fff;
633 }
634
635 .pi-btn-uninstall:disabled {
636 opacity: 0.6;
637 cursor: not-allowed;
638 }
639
640 .pi-btn-uninstall .material-symbols-outlined {
641 font-size: 1.3rem;
642 }
643
644 .pi-btn-discussion {
645 flex: 1;
646 min-width: 200px;
647 padding: 0.85rem 1.5rem;
648 border: 2px solid var(--color-primary);
649 border-radius: 8px;
650 background: transparent;
651 color: var(--color-text);
652 font-size: 1rem;
653 font-weight: 600;
654 cursor: pointer;
655 transition: all 0.2s;
656 display: flex;
657 align-items: center;
658 justify-content: center;
659 gap: 0.5rem;
660 text-decoration: none;
661 }
662
663 .pi-btn-discussion:hover {
664 background: var(--color-highlight);
665 color: #fff;
666 border: 2px solid var(--color-highlight);
667 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
668 }
669
670 .pi-btn-discussion .material-symbols-outlined {
671 font-size: 1.3rem;
672 }
673
674 .pi-btn-loading {
675 display: flex;
676 align-items: center;
677 gap: 0.6rem;
678 }
679
680 .pi-loading-text {
681 font-size: 0.9rem;
682 color: var(--color-text-secondary);
683 padding: 0.75rem 0;
684 }
685
686 .pi-screenshots-section {
687 margin: var(--spacing-lg) 0;
688 padding-top: var(--spacing-lg);
689 border-top: 1px solid var(--color-border);
690 }
691
692 .pi-version-section {
693 margin-bottom: 1.5rem;
694 padding-top: 1.5rem;
695 border-top: 1px solid var(--color-border);
696 }
697
698 .pi-version-header {
699 font-size: 0.85rem;
700 font-weight: 600;
701 color: var(--color-text-muted);
702 margin-bottom: 0.75rem;
703 }
704
705 .pi-version-grid {
706 display: grid;
707 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
708 gap: 0.75rem;
709 }
710
711 .pi-version-card {
712 display: flex;
713 flex-direction: column;
714 gap: 0.3rem;
715 padding: 0.9rem 1rem;
716 border: 1px solid var(--color-border);
717 border-radius: 10px;
718 }
719
720 .pi-version-label {
721 font-size: 0.78rem;
722 font-weight: 700;
723 letter-spacing: 0.04em;
724 text-transform: uppercase;
725 color: var(--color-text-muted);
726 }
727
728 .pi-version-name {
729 font-size: 1rem;
730 font-weight: 700;
731 color: var(--color-text-primary);
732 line-height: 1.35;
733 }
734
735 .pi-version-commit {
736 font-size: 0.92rem;
737 font-weight: 600;
738 color: var(--color-text-secondary);
739 text-decoration: none;
740 }
741
742 a.pi-version-commit:hover {
743 color: var(--color-highlight);
744 }
745
746 .pi-version-time {
747 font-size: 0.85rem;
748 color: var(--color-text-secondary);
749 line-height: 1.4;
750 }
751
752 .pi-screenshots-header {
753 font-size: 0.85rem;
754 font-weight: 600;
755 color: var(--color-text-muted);
756 margin-bottom: 0.75rem;
757 }
758
759 .pi-screenshots-grid {
760 display: flex;
761 gap: 0.75rem;
762 flex-wrap: wrap;
763 }
764
765 .pi-screenshot-thumb {
766 height: 120px;
767 flex: 1;
768 min-width: 0;
769 object-fit: cover;
770 border-radius: 8px;
771 cursor: pointer;
772 border: 1px solid var(--color-border);
773 transition: opacity 0.15s;
774 }
775
776 .pi-screenshot-thumb:hover {
777 opacity: 0.8;
778 }
779
780 .pi-readme-section {
781 margin-top: 2rem;
782 padding-top: 1.5rem;
783 border-top: 1px solid var(--color-border);
784 }
785
786 .pi-readme-header {
787 font-size: 0.85rem;
788 font-weight: 600;
789 color: var(--color-text-muted);
790 margin-bottom: 0.75rem;
791 }
792
793 .pi-readme-content {
794 font-size: 0.95rem;
795 line-height: 1.6;
796 color: var(--color-text-primary);
797 overflow-y: auto;
798 padding-right: 0.5rem;
799 scrollbar-width: none;
800 -ms-overflow-style: none;
801 }
802 .pi-readme-content::-webkit-scrollbar {
803 display: none;
804 }
805
806 .pi-readme-content h1,
807 .pi-readme-content h2,
808 .pi-readme-content h3,
809 .pi-readme-content h4 {
810 margin-top: 1rem;
811 margin-bottom: 0.5rem;
812 color: var(--color-text-primary);
813 }
814
815 .pi-readme-content h1 { font-size: 1.5rem; }
816 .pi-readme-content h2 { font-size: 1.25rem; }
817 .pi-readme-content h3 { font-size: 1.1rem; }
818 .pi-readme-content h4 { font-size: 1rem; }
819
820 .pi-readme-content p {
821 margin-bottom: 0.75rem;
822 }
823
824 .pi-readme-content code {
825 background: var(--color-panel);
826 padding: 0.15rem 0.35rem;
827 border-radius: 4px;
828 font-family: var(--font-family-code);
829 font-size: 0.85em;
830 }
831
832 .pi-readme-content pre {
833 background: var(--color-panel);
834 padding: 1rem;
835 border-radius: 8px;
836 overflow-x: auto;
837 margin-bottom: 1rem;
838 }
839
840 .pi-readme-content pre code {
841 background: none;
842 padding: 0;
843 }
844
845 .pi-readme-content ul,
846 .pi-readme-content ol {
847 margin-bottom: 0.75rem;
848 padding-left: 1.5rem;
849 }
850
851 .pi-readme-content li {
852 margin-bottom: 0.25rem;
853 }
854
855 .pi-readme-content a {
856 color: var(--color-highlight);
857 text-decoration: none;
858 }
859
860 .pi-readme-content a:hover {
861 text-decoration: underline;
862 }
863
864 .pi-readme-content img {
865 max-width: 100%;
866 border-radius: 8px;
867 margin: 0.5rem 0;
868 }
869
870 .pi-readme-content blockquote {
871 border-left: 3px solid var(--color-border);
872 padding-left: 1rem;
873 margin-left: 0;
874 color: var(--color-text-secondary);
875 }
876
877 .pi-readme-content table {
878 width: 100%;
879 border-collapse: collapse;
880 margin-bottom: 1rem;
881 }
882
883 .pi-readme-content th,
884 .pi-readme-content td {
885 border: 1px solid var(--color-border);
886 padding: 0.5rem;
887 text-align: left;
888 }
889
890 .pi-readme-content th {
891 background: var(--color-panel);
892 }
893
894 .pi-developer-section {
895 margin-top: 2rem;
896 padding-top: 1.5rem;
897 border-top: 1px solid var(--color-border);
898 }
899
900 .pi-developer-header {
901 font-size: 0.85rem;
902 font-weight: 600;
903 color: var(--color-text-muted);
904 margin-bottom: 0.75rem;
905 }
906
907 .pi-developer-links {
908 display: flex;
909 flex-direction: column;
910 gap: 0.5rem;
911 }
912
913 .pi-dev-link {
914 display: inline-flex;
915 align-items: center;
916 gap: 0.5rem;
917 padding: 0.5rem 0;
918 text-decoration: none;
919 font-size: 0.8rem;
920 color: var(--color-text-secondary);
921 transition: color 0.15s;
922 }
923
924 .pi-dev-link:hover {
925 color: var(--color-highlight);
926 }
927
928 .pi-dev-link .material-symbols-outlined {
929 font-size: 1.1rem;
930 color: var(--color-text-muted);
931 }
932
933 .pi-dev-link:hover .material-symbols-outlined {
934 color: var(--color-highlight);
935 }
936
937 .pi-dev-link.pi-report-link,
938 .pi-dev-link.pi-report-link:hover,
939 .pi-dev-link.pi-report-link .material-symbols-outlined,
940 .pi-dev-link.pi-report-link:hover .material-symbols-outlined {
941 color: var(--color-error-text);
942 }
943
944 .pi-footer .buttons-container {
945 width: 100%;
946 }
947
948 @media (max-width: 768px) {
949 .pi-hero {
950 flex-direction: column;
951 align-items: center;
952 text-align: center;
953 }
954
955 .pi-hero-thumb {
956 width: 100px;
957 height: 100px;
958 }
959
960 .pi-hero-title {
961 font-size: 1.5rem;
962 }
963
964 .pi-hero-title-row {
965 grid-template-columns: 1fr;
966 justify-items: center;
967 }
968
969 .pi-hero-manage {
970 justify-content: center;
971 }
972
973 .pi-hero-meta {
974 justify-content: center;
975 }
976
977 .pi-hero-stars {
978 flex-basis: 100%;
979 justify-content: center;
980 }
981
982 .pi-hero-tags {
983 justify-content: center;
984 }
985
986 .pi-card-installed-pill {
987 position: absolute;
988 top: unset;
989 right: unset;
990 margin: 0 auto var(--spacing-sm) auto;
991 }
992
993 .pi-actions-primary {
994 flex-direction: column;
995 }
996
997 .pi-btn-install,
998 .pi-btn-discussion {
999 min-width: 100%;
1000 }
1001 }
1002 </style>
1003 </body>
1004 </html>