@jjlmoya/utils-developer 1.2.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.
Files changed (228) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +34 -0
  3. package/src/category/i18n/es.ts +34 -0
  4. package/src/category/i18n/fr.ts +34 -0
  5. package/src/category/index.ts +31 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +78 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/css_specificity_logic.test.ts +39 -0
  17. package/src/tests/faq_count.test.ts +19 -0
  18. package/src/tests/locale_completeness.test.ts +42 -0
  19. package/src/tests/mocks/astro_mock.js +2 -0
  20. package/src/tests/no_h1_in_components.test.ts +48 -0
  21. package/src/tests/schemas_fulfillment.test.ts +23 -0
  22. package/src/tests/seo_length.test.ts +22 -0
  23. package/src/tests/title_quality.test.ts +55 -0
  24. package/src/tests/tool_validation.test.ts +17 -0
  25. package/src/tool/aspectRatio/bibliography.astro +14 -0
  26. package/src/tool/aspectRatio/component.astro +468 -0
  27. package/src/tool/aspectRatio/i18n/en.ts +177 -0
  28. package/src/tool/aspectRatio/i18n/es.ts +177 -0
  29. package/src/tool/aspectRatio/i18n/fr.ts +177 -0
  30. package/src/tool/aspectRatio/index.ts +34 -0
  31. package/src/tool/aspectRatio/seo.astro +14 -0
  32. package/src/tool/aspectRatio/ui.ts +12 -0
  33. package/src/tool/calculadoraTiempoDatos/bibliography.astro +14 -0
  34. package/src/tool/calculadoraTiempoDatos/component.astro +829 -0
  35. package/src/tool/calculadoraTiempoDatos/i18n/en.ts +150 -0
  36. package/src/tool/calculadoraTiempoDatos/i18n/es.ts +163 -0
  37. package/src/tool/calculadoraTiempoDatos/i18n/fr.ts +150 -0
  38. package/src/tool/calculadoraTiempoDatos/index.ts +33 -0
  39. package/src/tool/calculadoraTiempoDatos/seo.astro +14 -0
  40. package/src/tool/calculadoraTiempoDatos/ui.ts +24 -0
  41. package/src/tool/colorConverter/bibliography.astro +14 -0
  42. package/src/tool/colorConverter/component.astro +504 -0
  43. package/src/tool/colorConverter/i18n/en.ts +126 -0
  44. package/src/tool/colorConverter/i18n/es.ts +126 -0
  45. package/src/tool/colorConverter/i18n/fr.ts +126 -0
  46. package/src/tool/colorConverter/index.ts +33 -0
  47. package/src/tool/colorConverter/seo.astro +14 -0
  48. package/src/tool/colorConverter/ui.ts +16 -0
  49. package/src/tool/conversorExcelCsvHtml/bibliography.astro +14 -0
  50. package/src/tool/conversorExcelCsvHtml/component.astro +595 -0
  51. package/src/tool/conversorExcelCsvHtml/i18n/en.ts +129 -0
  52. package/src/tool/conversorExcelCsvHtml/i18n/es.ts +129 -0
  53. package/src/tool/conversorExcelCsvHtml/i18n/fr.ts +129 -0
  54. package/src/tool/conversorExcelCsvHtml/index.ts +33 -0
  55. package/src/tool/conversorExcelCsvHtml/seo.astro +14 -0
  56. package/src/tool/conversorExcelCsvHtml/ui.ts +14 -0
  57. package/src/tool/cronGenerator/bibliography.astro +14 -0
  58. package/src/tool/cronGenerator/component.astro +667 -0
  59. package/src/tool/cronGenerator/i18n/en.ts +191 -0
  60. package/src/tool/cronGenerator/i18n/es.ts +191 -0
  61. package/src/tool/cronGenerator/i18n/fr.ts +191 -0
  62. package/src/tool/cronGenerator/index.ts +34 -0
  63. package/src/tool/cronGenerator/seo.astro +14 -0
  64. package/src/tool/cronGenerator/ui.ts +45 -0
  65. package/src/tool/cssSpecificityCalculator/bibliography.astro +14 -0
  66. package/src/tool/cssSpecificityCalculator/component.astro +292 -0
  67. package/src/tool/cssSpecificityCalculator/i18n/en.ts +168 -0
  68. package/src/tool/cssSpecificityCalculator/i18n/es.ts +168 -0
  69. package/src/tool/cssSpecificityCalculator/i18n/fr.ts +168 -0
  70. package/src/tool/cssSpecificityCalculator/index.ts +34 -0
  71. package/src/tool/cssSpecificityCalculator/logic.ts +100 -0
  72. package/src/tool/cssSpecificityCalculator/seo.astro +14 -0
  73. package/src/tool/cssSpecificityCalculator/ui-controller.ts +57 -0
  74. package/src/tool/cssSpecificityCalculator/ui.ts +11 -0
  75. package/src/tool/cssToInlineConverter/bibliography.astro +14 -0
  76. package/src/tool/cssToInlineConverter/component.astro +308 -0
  77. package/src/tool/cssToInlineConverter/i18n/en.ts +160 -0
  78. package/src/tool/cssToInlineConverter/i18n/es.ts +160 -0
  79. package/src/tool/cssToInlineConverter/i18n/fr.ts +160 -0
  80. package/src/tool/cssToInlineConverter/index.ts +34 -0
  81. package/src/tool/cssToInlineConverter/seo.astro +14 -0
  82. package/src/tool/cssToInlineConverter/ui.ts +12 -0
  83. package/src/tool/duplicateCssRemover/bibliography.astro +14 -0
  84. package/src/tool/duplicateCssRemover/component.astro +353 -0
  85. package/src/tool/duplicateCssRemover/i18n/en.ts +167 -0
  86. package/src/tool/duplicateCssRemover/i18n/es.ts +167 -0
  87. package/src/tool/duplicateCssRemover/i18n/fr.ts +167 -0
  88. package/src/tool/duplicateCssRemover/index.ts +34 -0
  89. package/src/tool/duplicateCssRemover/seo.astro +14 -0
  90. package/src/tool/duplicateCssRemover/ui.ts +12 -0
  91. package/src/tool/generadorSecurityTxt/bibliography.astro +14 -0
  92. package/src/tool/generadorSecurityTxt/component.astro +473 -0
  93. package/src/tool/generadorSecurityTxt/i18n/en.ts +173 -0
  94. package/src/tool/generadorSecurityTxt/i18n/es.ts +173 -0
  95. package/src/tool/generadorSecurityTxt/i18n/fr.ts +173 -0
  96. package/src/tool/generadorSecurityTxt/index.ts +33 -0
  97. package/src/tool/generadorSecurityTxt/seo.astro +14 -0
  98. package/src/tool/generadorSecurityTxt/ui.ts +27 -0
  99. package/src/tool/hashGenerator/bibliography.astro +14 -0
  100. package/src/tool/hashGenerator/component.astro +367 -0
  101. package/src/tool/hashGenerator/i18n/en.ts +118 -0
  102. package/src/tool/hashGenerator/i18n/es.ts +118 -0
  103. package/src/tool/hashGenerator/i18n/fr.ts +118 -0
  104. package/src/tool/hashGenerator/index.ts +33 -0
  105. package/src/tool/hashGenerator/md5.ts +138 -0
  106. package/src/tool/hashGenerator/seo.astro +14 -0
  107. package/src/tool/hashGenerator/ui.ts +11 -0
  108. package/src/tool/inspectorCertificadosSsl/bibliography.astro +14 -0
  109. package/src/tool/inspectorCertificadosSsl/component.astro +580 -0
  110. package/src/tool/inspectorCertificadosSsl/i18n/en.ts +214 -0
  111. package/src/tool/inspectorCertificadosSsl/i18n/es.ts +214 -0
  112. package/src/tool/inspectorCertificadosSsl/i18n/fr.ts +214 -0
  113. package/src/tool/inspectorCertificadosSsl/index.ts +33 -0
  114. package/src/tool/inspectorCertificadosSsl/seo.astro +14 -0
  115. package/src/tool/inspectorCertificadosSsl/ui.ts +29 -0
  116. package/src/tool/jsonFormatter/bibliography.astro +14 -0
  117. package/src/tool/jsonFormatter/component.astro +590 -0
  118. package/src/tool/jsonFormatter/i18n/en.ts +156 -0
  119. package/src/tool/jsonFormatter/i18n/es.ts +156 -0
  120. package/src/tool/jsonFormatter/i18n/fr.ts +156 -0
  121. package/src/tool/jsonFormatter/index.ts +34 -0
  122. package/src/tool/jsonFormatter/seo.astro +14 -0
  123. package/src/tool/jsonFormatter/ui.ts +15 -0
  124. package/src/tool/keycode/bibliography.astro +14 -0
  125. package/src/tool/keycode/component.astro +540 -0
  126. package/src/tool/keycode/i18n/en.ts +160 -0
  127. package/src/tool/keycode/i18n/es.ts +160 -0
  128. package/src/tool/keycode/i18n/fr.ts +160 -0
  129. package/src/tool/keycode/index.ts +34 -0
  130. package/src/tool/keycode/seo.astro +14 -0
  131. package/src/tool/keycode/ui.ts +10 -0
  132. package/src/tool/llmCostCalculator/bibliography.astro +14 -0
  133. package/src/tool/llmCostCalculator/component.astro +488 -0
  134. package/src/tool/llmCostCalculator/data.ts +99 -0
  135. package/src/tool/llmCostCalculator/i18n/en.ts +165 -0
  136. package/src/tool/llmCostCalculator/i18n/es.ts +165 -0
  137. package/src/tool/llmCostCalculator/i18n/fr.ts +165 -0
  138. package/src/tool/llmCostCalculator/index.ts +34 -0
  139. package/src/tool/llmCostCalculator/seo.astro +14 -0
  140. package/src/tool/llmCostCalculator/ui.ts +11 -0
  141. package/src/tool/mobileMockupGenerator/app.ts +614 -0
  142. package/src/tool/mobileMockupGenerator/bibliography.astro +14 -0
  143. package/src/tool/mobileMockupGenerator/component.astro +1044 -0
  144. package/src/tool/mobileMockupGenerator/constants.ts +48 -0
  145. package/src/tool/mobileMockupGenerator/i18n/en.ts +121 -0
  146. package/src/tool/mobileMockupGenerator/i18n/es.ts +121 -0
  147. package/src/tool/mobileMockupGenerator/i18n/fr.ts +121 -0
  148. package/src/tool/mobileMockupGenerator/index.ts +33 -0
  149. package/src/tool/mobileMockupGenerator/rendering.ts +189 -0
  150. package/src/tool/mobileMockupGenerator/seo.astro +14 -0
  151. package/src/tool/mobileMockupGenerator/store.ts +145 -0
  152. package/src/tool/mobileMockupGenerator/types.ts +41 -0
  153. package/src/tool/mobileMockupGenerator/ui.ts +48 -0
  154. package/src/tool/musicalTypography/bibliography.astro +14 -0
  155. package/src/tool/musicalTypography/component.astro +480 -0
  156. package/src/tool/musicalTypography/i18n/en.ts +179 -0
  157. package/src/tool/musicalTypography/i18n/es.ts +179 -0
  158. package/src/tool/musicalTypography/i18n/fr.ts +179 -0
  159. package/src/tool/musicalTypography/index.ts +33 -0
  160. package/src/tool/musicalTypography/seo.astro +14 -0
  161. package/src/tool/musicalTypography/ui.ts +25 -0
  162. package/src/tool/placeholderGenerator/bibliography.astro +14 -0
  163. package/src/tool/placeholderGenerator/component.astro +499 -0
  164. package/src/tool/placeholderGenerator/i18n/en.ts +176 -0
  165. package/src/tool/placeholderGenerator/i18n/es.ts +176 -0
  166. package/src/tool/placeholderGenerator/i18n/fr.ts +176 -0
  167. package/src/tool/placeholderGenerator/index.ts +34 -0
  168. package/src/tool/placeholderGenerator/seo.astro +14 -0
  169. package/src/tool/placeholderGenerator/ui.ts +14 -0
  170. package/src/tool/promptLibrary/bibliography.astro +14 -0
  171. package/src/tool/promptLibrary/component.astro +1131 -0
  172. package/src/tool/promptLibrary/controller.ts +49 -0
  173. package/src/tool/promptLibrary/i18n/en.ts +131 -0
  174. package/src/tool/promptLibrary/i18n/es.ts +131 -0
  175. package/src/tool/promptLibrary/i18n/fr.ts +126 -0
  176. package/src/tool/promptLibrary/index.ts +33 -0
  177. package/src/tool/promptLibrary/seo.astro +14 -0
  178. package/src/tool/promptLibrary/ui.ts +29 -0
  179. package/src/tool/readabilityCalculator/bibliography.astro +14 -0
  180. package/src/tool/readabilityCalculator/component.astro +534 -0
  181. package/src/tool/readabilityCalculator/i18n/en.ts +133 -0
  182. package/src/tool/readabilityCalculator/i18n/es.ts +133 -0
  183. package/src/tool/readabilityCalculator/i18n/fr.ts +133 -0
  184. package/src/tool/readabilityCalculator/index.ts +33 -0
  185. package/src/tool/readabilityCalculator/seo.astro +14 -0
  186. package/src/tool/readabilityCalculator/ui.ts +27 -0
  187. package/src/tool/svgSanitizer/bibliography.astro +14 -0
  188. package/src/tool/svgSanitizer/component.astro +554 -0
  189. package/src/tool/svgSanitizer/i18n/en.ts +125 -0
  190. package/src/tool/svgSanitizer/i18n/es.ts +125 -0
  191. package/src/tool/svgSanitizer/i18n/fr.ts +125 -0
  192. package/src/tool/svgSanitizer/index.ts +33 -0
  193. package/src/tool/svgSanitizer/seo.astro +14 -0
  194. package/src/tool/svgSanitizer/ui.ts +18 -0
  195. package/src/tool/svgToCss/bibliography.astro +14 -0
  196. package/src/tool/svgToCss/component.astro +381 -0
  197. package/src/tool/svgToCss/i18n/en.ts +174 -0
  198. package/src/tool/svgToCss/i18n/es.ts +174 -0
  199. package/src/tool/svgToCss/i18n/fr.ts +174 -0
  200. package/src/tool/svgToCss/index.ts +34 -0
  201. package/src/tool/svgToCss/seo.astro +14 -0
  202. package/src/tool/svgToCss/ui.ts +13 -0
  203. package/src/tool/urlCleaner/bibliography.astro +14 -0
  204. package/src/tool/urlCleaner/component.astro +458 -0
  205. package/src/tool/urlCleaner/i18n/en.ts +116 -0
  206. package/src/tool/urlCleaner/i18n/es.ts +116 -0
  207. package/src/tool/urlCleaner/i18n/fr.ts +116 -0
  208. package/src/tool/urlCleaner/index.ts +33 -0
  209. package/src/tool/urlCleaner/seo.astro +14 -0
  210. package/src/tool/urlCleaner/ui.ts +12 -0
  211. package/src/tool/urlEncoderDecoder/bibliography.astro +14 -0
  212. package/src/tool/urlEncoderDecoder/component.astro +291 -0
  213. package/src/tool/urlEncoderDecoder/i18n/en.ts +162 -0
  214. package/src/tool/urlEncoderDecoder/i18n/es.ts +162 -0
  215. package/src/tool/urlEncoderDecoder/i18n/fr.ts +162 -0
  216. package/src/tool/urlEncoderDecoder/index.ts +34 -0
  217. package/src/tool/urlEncoderDecoder/seo.astro +14 -0
  218. package/src/tool/urlEncoderDecoder/ui.ts +11 -0
  219. package/src/tool/utmGenerator/bibliography.astro +14 -0
  220. package/src/tool/utmGenerator/component.astro +270 -0
  221. package/src/tool/utmGenerator/i18n/en.ts +117 -0
  222. package/src/tool/utmGenerator/i18n/es.ts +117 -0
  223. package/src/tool/utmGenerator/i18n/fr.ts +117 -0
  224. package/src/tool/utmGenerator/index.ts +33 -0
  225. package/src/tool/utmGenerator/seo.astro +14 -0
  226. package/src/tool/utmGenerator/ui.ts +13 -0
  227. package/src/tools.ts +27 -0
  228. package/src/types.ts +72 -0
@@ -0,0 +1,1131 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { PromptLibraryUI } from './ui';
4
+
5
+ interface Props {
6
+ locale?: KnownLocale;
7
+ ui?: Record<string, unknown>;
8
+ }
9
+
10
+ const { ui } = Astro.props;
11
+ const t = (ui ?? {}) as PromptLibraryUI;
12
+ ---
13
+
14
+ <div id="pl-root" data-i18n={JSON.stringify(t)} class="pl-root">
15
+ <header class="pl-topbar">
16
+ <div class="pl-search-wrap">
17
+ <svg class="pl-search-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
18
+ <circle cx="11" cy="11" r="8"></circle>
19
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
20
+ </svg>
21
+ <input type="text" id="pl-search" class="pl-search-input" placeholder={t.placeholderSearch} aria-label={t.placeholderSearch} />
22
+ </div>
23
+ <button id="pl-add-btn" class="pl-btn pl-btn-primary" aria-label={t.btnNew}>
24
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
25
+ <line x1="12" y1="5" x2="12" y2="19"></line>
26
+ <line x1="5" y1="12" x2="19" y2="12"></line>
27
+ </svg>
28
+ {t.btnNew}
29
+ </button>
30
+ </header>
31
+
32
+ <div id="pl-empty" class="pl-empty-state">
33
+ <div class="pl-empty-icon">
34
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
35
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
36
+ <polyline points="14 2 14 8 20 8"></polyline>
37
+ <line x1="16" y1="13" x2="8" y2="13"></line>
38
+ <line x1="16" y1="17" x2="8" y2="17"></line>
39
+ </svg>
40
+ </div>
41
+ <h3 class="pl-empty-title">{t.emptyTitle}</h3>
42
+ <p class="pl-empty-desc">{t.emptyDesc}</p>
43
+ <button id="pl-empty-add" class="pl-btn pl-btn-secondary">{t.btnAddFirst}</button>
44
+ </div>
45
+
46
+ <div id="pl-grid" class="pl-grid" style="display:none"></div>
47
+
48
+ <template id="pl-card-tpl">
49
+ <article class="pl-card">
50
+ <div class="pl-card-top">
51
+ <h4 class="pl-card-title"></h4>
52
+ <div class="pl-card-actions">
53
+ <button class="pl-btn-icon pl-star-btn" title="">
54
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" class="pl-star-icon" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
55
+ <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
56
+ </svg>
57
+ </button>
58
+ <button class="pl-btn-icon pl-edit-btn" title="">
59
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
60
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
61
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
62
+ </svg>
63
+ </button>
64
+ <button class="pl-btn-icon pl-share-btn" title="">
65
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
66
+ <circle cx="18" cy="5" r="3"></circle>
67
+ <circle cx="6" cy="12" r="3"></circle>
68
+ <circle cx="18" cy="19" r="3"></circle>
69
+ <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
70
+ <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
71
+ </svg>
72
+ </button>
73
+ <button class="pl-btn-icon pl-copy-btn" title="">
74
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
75
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
76
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
77
+ </svg>
78
+ </button>
79
+ <button class="pl-btn-icon pl-del-btn pl-danger" title="">
80
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
81
+ <polyline points="3 6 5 6 21 6"></polyline>
82
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
83
+ </svg>
84
+ </button>
85
+ </div>
86
+ </div>
87
+ <div class="pl-card-body">
88
+ <p class="pl-card-text"></p>
89
+ </div>
90
+ <div class="pl-tags-container"></div>
91
+ <div class="pl-card-vars"></div>
92
+ </article>
93
+ </template>
94
+
95
+ <dialog id="pl-modal" class="pl-modal">
96
+ <div class="pl-modal-container">
97
+ <div class="pl-modal-header">
98
+ <h3 class="pl-modal-title">{t.modalTitleCreate}</h3>
99
+ <button id="pl-close-modal" class="pl-btn-icon" aria-label={t.btnCancel}>
100
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
101
+ <line x1="18" y1="6" x2="6" y2="18"></line>
102
+ <line x1="6" y1="6" x2="18" y2="18"></line>
103
+ </svg>
104
+ </button>
105
+ </div>
106
+ <form id="pl-form" class="pl-form">
107
+ <div class="pl-form-group">
108
+ <label for="pl-title" class="pl-label">{t.labelTitle}</label>
109
+ <input type="text" id="pl-title" class="pl-input" placeholder={t.placeholderTitle} required autocomplete="off" />
110
+ </div>
111
+ <div class="pl-form-group">
112
+ <label for="pl-content" class="pl-label">{t.labelContent}</label>
113
+ <textarea id="pl-content" class="pl-textarea" placeholder={t.placeholderContent} required></textarea>
114
+ <span class="pl-hint">{t.hintContent}</span>
115
+ </div>
116
+ <div class="pl-form-group">
117
+ <label for="pl-tags-input" class="pl-label">{t.labelTags}</label>
118
+ <div class="pl-tags-input-wrapper" id="pl-tags-wrapper">
119
+ <div id="pl-tags-list" class="pl-tags-list"></div>
120
+ <input type="text" id="pl-tags-input" class="pl-tags-input-field" placeholder={t.placeholderTags} autocomplete="off" />
121
+ </div>
122
+ </div>
123
+ <div class="pl-modal-footer">
124
+ <button type="button" id="pl-cancel-form" class="pl-btn pl-btn-ghost">{t.btnCancel}</button>
125
+ <button type="submit" class="pl-btn pl-btn-primary">
126
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
127
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
128
+ <polyline points="17 21 17 13 7 13 7 21"></polyline>
129
+ <polyline points="7 3 7 8 15 8"></polyline>
130
+ </svg>
131
+ {t.btnSave}
132
+ </button>
133
+ </div>
134
+ </form>
135
+ </div>
136
+ </dialog>
137
+
138
+ <dialog id="pl-confirm-modal" class="pl-modal pl-confirm-dialog">
139
+ <div class="pl-modal-container pl-confirm-container">
140
+ <div class="pl-confirm-icon">
141
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
142
+ <path d="M3 6h18"></path>
143
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path>
144
+ <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
145
+ </svg>
146
+ </div>
147
+ <h3 class="pl-confirm-title">{t.confirmTitle}</h3>
148
+ <p class="pl-confirm-desc">{t.confirmDesc}</p>
149
+ <div class="pl-confirm-actions">
150
+ <button type="button" id="pl-cancel-delete" class="pl-btn pl-btn-ghost">{t.btnCancelDelete}</button>
151
+ <button type="button" id="pl-confirm-delete" class="pl-btn pl-btn-danger">{t.btnConfirmDelete}</button>
152
+ </div>
153
+ </div>
154
+ </dialog>
155
+ </div>
156
+
157
+ <script>
158
+ import type { PromptLibraryUI } from './ui';
159
+ import type { PromptItem } from './controller';
160
+ import { loadPrompts, addPrompt, updatePrompt, deletePrompt } from './controller';
161
+
162
+ const root = document.getElementById('pl-root')!;
163
+ const t = JSON.parse(root.dataset.i18n ?? '{}') as PromptLibraryUI;
164
+
165
+ const modal = document.getElementById('pl-modal') as HTMLDialogElement;
166
+ const confirmModal = document.getElementById('pl-confirm-modal') as HTMLDialogElement;
167
+ const searchInput = document.getElementById('pl-search') as HTMLInputElement;
168
+ const grid = document.getElementById('pl-grid') as HTMLDivElement;
169
+ const emptyState = document.getElementById('pl-empty') as HTMLDivElement;
170
+ const tpl = document.getElementById('pl-card-tpl') as HTMLTemplateElement;
171
+ const form = document.getElementById('pl-form') as HTMLFormElement;
172
+ const titleIn = document.getElementById('pl-title') as HTMLInputElement;
173
+ const contentIn = document.getElementById('pl-content') as HTMLTextAreaElement;
174
+ const tagsWrapper = document.getElementById('pl-tags-wrapper') as HTMLDivElement;
175
+ const tagsList = document.getElementById('pl-tags-list') as HTMLDivElement;
176
+ const tagsInput = document.getElementById('pl-tags-input') as HTMLInputElement;
177
+
178
+ let editId: string | null = null;
179
+ let deleteTarget: { id: string; card: HTMLElement } | null = null;
180
+ let currentTags: string[] = [];
181
+
182
+ const svgCheck = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="rgb(16,185,129)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
183
+
184
+ function extractVariables(text: string): string[] {
185
+ const regex = /\[([^\]]+)\]/g;
186
+ const matches: string[] = [];
187
+ let match;
188
+ while ((match = regex.exec(text)) !== null) {
189
+ if (!matches.includes(match[1])) matches.push(match[1]);
190
+ }
191
+ return matches;
192
+ }
193
+
194
+ function highlightVariables(text: string, vals: Record<string, string>): { html: string; raw: string } {
195
+ let html = text;
196
+ let raw = text;
197
+ extractVariables(text).forEach((v) => {
198
+ const filled = vals[v] ?? '';
199
+ const display = filled || `[${v}]`;
200
+ const cls = filled ? 'pl-var-highlight pl-var-filled' : 'pl-var-highlight';
201
+ html = html.replace(new RegExp(`\\[${v}\\]`, 'g'), `<span class="${cls}">${display}</span>`);
202
+ raw = raw.replace(new RegExp(`\\[${v}\\]`, 'g'), filled || `[${v}]`);
203
+ });
204
+ return { html, raw };
205
+ }
206
+
207
+ function copyText(text: string, btn: HTMLElement) {
208
+ navigator.clipboard.writeText(text).then(() => {
209
+ const orig = btn.innerHTML;
210
+ btn.innerHTML = svgCheck;
211
+ setTimeout(() => { btn.innerHTML = orig; }, 2000);
212
+ }).catch((e) => console.error('Copy failed', e));
213
+ }
214
+
215
+ function updateTagsUI() {
216
+ if (!tagsList) return;
217
+ tagsList.innerHTML = '';
218
+ currentTags.forEach((tag, i) => {
219
+ const span = document.createElement('span');
220
+ span.className = 'pl-card-tag';
221
+ span.style.cursor = 'pointer';
222
+ span.textContent = tag;
223
+ span.addEventListener('click', () => { currentTags.splice(i, 1); updateTagsUI(); });
224
+ tagsList.appendChild(span);
225
+ });
226
+ }
227
+
228
+ function pushTag(val: string) {
229
+ const clean = val.trim().replace(/,/g, '');
230
+ if (clean && !currentTags.includes(clean)) { currentTags.push(clean); updateTagsUI(); }
231
+ }
232
+
233
+ function openModal(item: PromptItem | null = null) {
234
+ const titleEl = modal.querySelector('.pl-modal-title');
235
+ if (item) {
236
+ editId = item.id;
237
+ titleIn.value = item.title;
238
+ contentIn.value = item.content;
239
+ currentTags = [...item.tags];
240
+ if (titleEl) titleEl.textContent = t.modalTitleEdit;
241
+ } else {
242
+ editId = null;
243
+ form.reset();
244
+ currentTags = [];
245
+ if (titleEl) titleEl.textContent = t.modalTitleCreate;
246
+ }
247
+ updateTagsUI();
248
+ modal.showModal();
249
+ modal.classList.add('pl-modal-open');
250
+ titleIn.focus();
251
+ }
252
+
253
+ function closeModal() {
254
+ modal?.classList.remove('pl-modal-open');
255
+ setTimeout(() => { modal?.close(); editId = null; form?.reset(); currentTags = []; updateTagsUI(); }, 300);
256
+ }
257
+
258
+ function closeConfirmModal() {
259
+ confirmModal?.classList.remove('pl-modal-open');
260
+ setTimeout(() => { confirmModal?.close(); deleteTarget = null; }, 300);
261
+ }
262
+
263
+ function updateVisibility(items: PromptItem[]) {
264
+ const hasItems = items.length > 0;
265
+ emptyState.style.display = hasItems ? 'none' : '';
266
+ grid.style.display = hasItems ? '' : 'none';
267
+ }
268
+
269
+ function setupCardVars(card: HTMLElement, item: PromptItem) {
270
+ const contentEl = card.querySelector('.pl-card-text') as HTMLElement;
271
+ const varsCont = card.querySelector('.pl-card-vars') as HTMLElement;
272
+ const vars = extractVariables(item.content);
273
+ const varInputs: Record<string, HTMLInputElement> = {};
274
+
275
+ const updateDisplay = () => {
276
+ const vals: Record<string, string> = {};
277
+ vars.forEach((v) => { vals[v] = varInputs[v]?.value.trim() ?? ''; });
278
+ const { html, raw } = highlightVariables(item.content, vals);
279
+ contentEl.innerHTML = html;
280
+ return raw;
281
+ };
282
+
283
+ if (vars.length > 0 && varsCont) {
284
+ varsCont.appendChild(Object.assign(document.createElement('h5'), { className: 'pl-card-vars-title', textContent: t.varsTitle }));
285
+ vars.forEach((v) => {
286
+ const inp = document.createElement('input');
287
+ inp.type = 'text';
288
+ inp.placeholder = v;
289
+ inp.className = 'pl-var-input';
290
+ inp.addEventListener('input', updateDisplay);
291
+ varsCont.appendChild(inp);
292
+ varInputs[v] = inp;
293
+ });
294
+ } else if (varsCont) {
295
+ varsCont.remove();
296
+ }
297
+ updateDisplay();
298
+ return updateDisplay;
299
+ }
300
+
301
+ function setupCardEvents(card: HTMLElement, item: PromptItem, getContent: () => string) {
302
+ const starBtn = card.querySelector('.pl-star-btn') as HTMLElement;
303
+ const starIcon = card.querySelector('.pl-star-icon') as HTMLElement;
304
+ const editBtn = card.querySelector('.pl-edit-btn') as HTMLElement;
305
+ const shareBtn = card.querySelector('.pl-share-btn') as HTMLElement;
306
+ const copyBtn = card.querySelector('.pl-copy-btn') as HTMLElement;
307
+ const delBtn = card.querySelector('.pl-del-btn') as HTMLElement;
308
+
309
+ starBtn.title = t.ariaLabelStar;
310
+ editBtn.title = t.ariaLabelEdit;
311
+ shareBtn.title = t.ariaLabelShare;
312
+ copyBtn.title = t.ariaLabelCopy;
313
+ delBtn.title = t.ariaLabelDelete;
314
+
315
+ if (item.isStarred) { starIcon.classList.add('pl-starred'); starIcon.setAttribute('fill', 'currentColor'); }
316
+
317
+ starBtn.addEventListener('click', () => { updatePrompt(item.id, { isStarred: !item.isStarred }); renderPrompts(); });
318
+ editBtn.addEventListener('click', () => openModal(item));
319
+ delBtn.addEventListener('click', () => {
320
+ deleteTarget = { id: item.id, card };
321
+ confirmModal?.showModal();
322
+ confirmModal?.classList.add('pl-modal-open');
323
+ });
324
+ shareBtn.addEventListener('click', () => {
325
+ const url = new URL(window.location.href.split('?')[0]);
326
+ url.searchParams.set('title', item.title);
327
+ url.searchParams.set('content', item.content);
328
+ if (item.tags.length > 0) url.searchParams.set('tags', item.tags.join(','));
329
+ copyText(url.toString(), shareBtn);
330
+ });
331
+ copyBtn.addEventListener('click', () => copyText(getContent(), copyBtn));
332
+ }
333
+
334
+ function renderCard(item: PromptItem): DocumentFragment {
335
+ const clone = tpl.content.cloneNode(true) as DocumentFragment;
336
+ const card = clone.querySelector('.pl-card') as HTMLElement;
337
+ const titleEl = clone.querySelector('.pl-card-title');
338
+ const tagsCont = clone.querySelector('.pl-tags-container');
339
+
340
+ if (titleEl) titleEl.textContent = item.title;
341
+
342
+ if (tagsCont && item.tags.length > 0) {
343
+ item.tags.forEach((tag) => {
344
+ const s = document.createElement('span');
345
+ s.className = 'pl-card-tag';
346
+ s.textContent = tag.trim();
347
+ if (s.textContent) tagsCont.appendChild(s);
348
+ });
349
+ } else if (tagsCont) {
350
+ tagsCont.remove();
351
+ }
352
+
353
+ const getContent = setupCardVars(card, item);
354
+ setupCardEvents(card, item, getContent);
355
+ return clone;
356
+ }
357
+
358
+ function renderPrompts() {
359
+ const items = loadPrompts();
360
+ const term = searchInput?.value.toLowerCase().trim() ?? '';
361
+ updateVisibility(items);
362
+ if (items.length === 0) { grid.innerHTML = ''; return; }
363
+
364
+ const filtered = items.filter((item) => {
365
+ const matchText = item.title.toLowerCase().includes(term) || item.content.toLowerCase().includes(term);
366
+ const matchTag = item.tags.some((tg) => tg.toLowerCase().includes(term));
367
+ return !term || matchText || matchTag;
368
+ });
369
+
370
+ if (filtered.length === 0) {
371
+ grid.innerHTML = `<div class="pl-no-results">${t.noResults}</div>`;
372
+ return;
373
+ }
374
+
375
+ const df = document.createDocumentFragment();
376
+ filtered.forEach((item) => df.appendChild(renderCard(item)));
377
+ grid.innerHTML = '';
378
+ grid.appendChild(df);
379
+ }
380
+
381
+ function handleUrlParams() {
382
+ const params = new URLSearchParams(window.location.search);
383
+ const sharedTitle = params.get('title');
384
+ const sharedContent = params.get('content');
385
+ if (!sharedTitle && !sharedContent) return;
386
+ openModal(null);
387
+ titleIn.value = sharedTitle ?? '';
388
+ contentIn.value = sharedContent ?? '';
389
+ const sharedTags = params.get('tags');
390
+ if (sharedTags) {
391
+ currentTags = sharedTags.split(',').map((tg) => tg.trim()).filter(Boolean);
392
+ updateTagsUI();
393
+ }
394
+ const cleanUrl = new URL(window.location.href);
395
+ cleanUrl.search = '';
396
+ window.history.replaceState({}, document.title, cleanUrl.toString());
397
+ }
398
+
399
+ document.getElementById('pl-add-btn')?.addEventListener('click', () => openModal(null));
400
+ document.getElementById('pl-empty-add')?.addEventListener('click', () => openModal(null));
401
+ document.getElementById('pl-close-modal')?.addEventListener('click', closeModal);
402
+ document.getElementById('pl-cancel-form')?.addEventListener('click', closeModal);
403
+ document.getElementById('pl-cancel-delete')?.addEventListener('click', closeConfirmModal);
404
+
405
+ document.getElementById('pl-confirm-delete')?.addEventListener('click', () => {
406
+ if (!deleteTarget) return;
407
+ const { id, card } = deleteTarget;
408
+ card.style.opacity = '0';
409
+ card.style.transform = 'scale(0.95)';
410
+ setTimeout(() => { deletePrompt(id); closeConfirmModal(); renderPrompts(); }, 200);
411
+ });
412
+
413
+ tagsWrapper?.addEventListener('click', () => tagsInput?.focus());
414
+
415
+ tagsInput?.addEventListener('keydown', (e) => {
416
+ if (e.key === ' ' || e.key === 'Enter' || e.key === ',') {
417
+ e.preventDefault();
418
+ pushTag(tagsInput.value);
419
+ tagsInput.value = '';
420
+ } else if (e.key === 'Backspace' && tagsInput.value === '') {
421
+ currentTags.pop();
422
+ updateTagsUI();
423
+ }
424
+ });
425
+
426
+ form?.addEventListener('submit', (e) => {
427
+ e.preventDefault();
428
+ if (tagsInput?.value) pushTag(tagsInput.value);
429
+ const data = { title: titleIn.value, content: contentIn.value, tags: [...currentTags] };
430
+ if (editId) { updatePrompt(editId, data); } else { addPrompt(data); }
431
+ renderPrompts();
432
+ closeModal();
433
+ });
434
+
435
+ modal?.addEventListener('cancel', (e) => e.preventDefault());
436
+ confirmModal?.addEventListener('cancel', (e) => e.preventDefault());
437
+
438
+ searchInput?.addEventListener('input', () => requestAnimationFrame(renderPrompts));
439
+
440
+ document.addEventListener('DOMContentLoaded', () => { renderPrompts(); handleUrlParams(); });
441
+ </script>
442
+
443
+ <style>
444
+ .pl-root {
445
+ width: 100%;
446
+ display: flex;
447
+ flex-direction: column;
448
+ gap: 2rem;
449
+ padding-bottom: 4rem;
450
+ }
451
+
452
+ .pl-topbar {
453
+ display: flex;
454
+ flex-direction: column;
455
+ gap: 1.5rem;
456
+ justify-content: space-between;
457
+ align-items: center;
458
+ background: rgba(255, 255, 255, 0.03);
459
+ backdrop-filter: blur(12px);
460
+ border: 1px solid rgba(255, 255, 255, 0.1);
461
+ border-radius: 1.5rem;
462
+ padding: 1rem 1.5rem;
463
+ box-shadow: 0 4px 20px -5px rgba(0, 0, 0, 0.05);
464
+ }
465
+
466
+ :global(.theme-dark) .pl-topbar {
467
+ background: rgba(15, 23, 42, 0.4);
468
+ border-color: rgba(255, 255, 255, 0.08);
469
+ }
470
+
471
+ .pl-search-wrap {
472
+ position: relative;
473
+ width: 100%;
474
+ max-width: 480px;
475
+ }
476
+
477
+ .pl-search-icon {
478
+ position: absolute;
479
+ left: 1.25rem;
480
+ top: 50%;
481
+ transform: translateY(-50%);
482
+ color: rgb(148, 163, 184);
483
+ pointer-events: none;
484
+ transition: color 0.3s;
485
+ }
486
+
487
+ .pl-search-input {
488
+ width: 100%;
489
+ padding: 1rem 1rem 1rem 3.5rem;
490
+ border-radius: 1rem;
491
+ border: 1px solid rgba(0, 0, 0, 0.1);
492
+ background: rgba(255, 255, 255, 0.8);
493
+ font-size: 1rem;
494
+ color: rgb(30, 41, 59);
495
+ transition: all 0.3s;
496
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.02);
497
+ box-sizing: border-box;
498
+ }
499
+
500
+ .pl-search-input:focus {
501
+ outline: none;
502
+ border-color: rgb(139, 92, 246);
503
+ background: rgb(255, 255, 255);
504
+ box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.15);
505
+ }
506
+
507
+ :global(.theme-dark) .pl-search-input {
508
+ background: rgba(15, 23, 42, 0.6);
509
+ border-color: rgba(255, 255, 255, 0.1);
510
+ color: rgb(248, 250, 252);
511
+ }
512
+
513
+ :global(.theme-dark) .pl-search-input:focus {
514
+ background: rgba(30, 41, 59, 0.8);
515
+ }
516
+
517
+ .pl-btn {
518
+ display: inline-flex;
519
+ align-items: center;
520
+ gap: 0.5rem;
521
+ padding: 0.875rem 1.5rem;
522
+ border-radius: 1rem;
523
+ font-weight: 600;
524
+ font-size: 0.95rem;
525
+ cursor: pointer;
526
+ transition: all 0.25s;
527
+ border: none;
528
+ white-space: nowrap;
529
+ }
530
+
531
+ .pl-btn-primary {
532
+ background: linear-gradient(135deg, rgb(139, 92, 246), rgb(192, 38, 211));
533
+ color: rgb(255, 255, 255);
534
+ box-shadow: 0 4px 15px -3px rgba(192, 38, 211, 0.4);
535
+ }
536
+
537
+ .pl-btn-primary:hover {
538
+ transform: translateY(-3px) scale(1.02);
539
+ box-shadow: 0 8px 25px -5px rgba(192, 38, 211, 0.5);
540
+ }
541
+
542
+ .pl-btn-primary:active {
543
+ transform: translateY(0) scale(0.98);
544
+ }
545
+
546
+ .pl-btn-secondary {
547
+ background: rgba(139, 92, 246, 0.1);
548
+ color: rgb(139, 92, 246);
549
+ border: 1px solid rgba(139, 92, 246, 0.2);
550
+ }
551
+
552
+ .pl-btn-secondary:hover {
553
+ background: rgba(139, 92, 246, 0.2);
554
+ }
555
+
556
+ .pl-btn-ghost {
557
+ background: transparent;
558
+ color: rgb(100, 116, 139);
559
+ border: none;
560
+ }
561
+
562
+ .pl-btn-ghost:hover {
563
+ background: rgba(0, 0, 0, 0.05);
564
+ color: rgb(30, 41, 59);
565
+ }
566
+
567
+ :global(.theme-dark) .pl-btn-ghost {
568
+ color: rgb(148, 163, 184);
569
+ }
570
+
571
+ :global(.theme-dark) .pl-btn-ghost:hover {
572
+ background: rgba(255, 255, 255, 0.05);
573
+ color: rgb(248, 250, 252);
574
+ }
575
+
576
+ .pl-btn-danger {
577
+ background: rgb(239, 68, 68);
578
+ color: rgb(255, 255, 255);
579
+ border: none;
580
+ box-shadow: 0 4px 15px -3px rgba(239, 68, 68, 0.4);
581
+ }
582
+
583
+ .pl-btn-danger:hover {
584
+ background: rgb(220, 38, 38);
585
+ }
586
+
587
+ .pl-btn-icon {
588
+ display: inline-flex;
589
+ align-items: center;
590
+ justify-content: center;
591
+ padding: 0.5rem;
592
+ border-radius: 0.5rem;
593
+ background: transparent;
594
+ border: none;
595
+ color: rgb(100, 116, 139);
596
+ cursor: pointer;
597
+ transition: all 0.2s;
598
+ }
599
+
600
+ .pl-btn-icon:hover {
601
+ background: rgba(0, 0, 0, 0.05);
602
+ color: rgb(30, 41, 59);
603
+ }
604
+
605
+ .pl-btn-icon.pl-danger:hover {
606
+ color: rgb(239, 68, 68);
607
+ background: rgba(239, 68, 68, 0.1);
608
+ }
609
+
610
+ :global(.theme-dark) .pl-btn-icon {
611
+ color: rgb(148, 163, 184);
612
+ }
613
+
614
+ :global(.theme-dark) .pl-btn-icon:hover {
615
+ background: rgba(255, 255, 255, 0.1);
616
+ color: rgb(248, 250, 252);
617
+ }
618
+
619
+ .pl-empty-state {
620
+ display: flex;
621
+ flex-direction: column;
622
+ align-items: center;
623
+ justify-content: center;
624
+ text-align: center;
625
+ padding: 5rem 2rem;
626
+ background: rgba(255, 255, 255, 0.02);
627
+ border: 2px dashed rgba(0, 0, 0, 0.1);
628
+ border-radius: 1.5rem;
629
+ margin-top: 2rem;
630
+ }
631
+
632
+ :global(.theme-dark) .pl-empty-state {
633
+ border-color: rgba(255, 255, 255, 0.1);
634
+ }
635
+
636
+ .pl-empty-icon {
637
+ margin-bottom: 1.5rem;
638
+ color: rgb(139, 92, 246);
639
+ filter: drop-shadow(0 0 20px rgba(139, 92, 246, 0.4));
640
+ }
641
+
642
+ .pl-empty-title {
643
+ font-size: 1.5rem;
644
+ font-weight: 700;
645
+ color: rgb(30, 41, 59);
646
+ margin-bottom: 0.5rem;
647
+ }
648
+
649
+ .pl-empty-desc {
650
+ font-size: 1.125rem;
651
+ color: rgb(100, 116, 139);
652
+ max-width: 400px;
653
+ margin-bottom: 2rem;
654
+ line-height: 1.6;
655
+ }
656
+
657
+ :global(.theme-dark) .pl-empty-title {
658
+ color: rgb(248, 250, 252);
659
+ }
660
+
661
+ :global(.theme-dark) .pl-empty-desc {
662
+ color: rgb(148, 163, 184);
663
+ }
664
+
665
+ .pl-no-results {
666
+ text-align: center;
667
+ padding: 3rem;
668
+ color: rgb(100, 116, 139);
669
+ font-size: 1.125rem;
670
+ background: rgba(255, 255, 255, 0.02);
671
+ border-radius: 1rem;
672
+ grid-column: 1 / -1;
673
+ }
674
+
675
+ .pl-grid {
676
+ display: grid;
677
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
678
+ gap: 1.5rem;
679
+ }
680
+
681
+ .pl-card {
682
+ background: rgba(255, 255, 255, 0.6);
683
+ backdrop-filter: blur(20px);
684
+ border: 1px solid rgba(255, 255, 255, 0.5);
685
+ border-radius: 1.25rem;
686
+ padding: 1.5rem;
687
+ display: flex;
688
+ flex-direction: column;
689
+ gap: 1.25rem;
690
+ transition: all 0.3s;
691
+ box-shadow: 0 4px 10px -2px rgba(0, 0, 0, 0.03);
692
+ position: relative;
693
+ overflow: hidden;
694
+ }
695
+
696
+ .pl-card::before {
697
+ content: '';
698
+ position: absolute;
699
+ top: 0;
700
+ left: 0;
701
+ width: 100%;
702
+ height: 4px;
703
+ background: linear-gradient(90deg, rgb(139, 92, 246), rgb(192, 38, 211));
704
+ opacity: 0;
705
+ transition: opacity 0.3s;
706
+ }
707
+
708
+ .pl-card:hover {
709
+ transform: translateY(-6px);
710
+ box-shadow: 0 15px 30px -5px rgba(139, 92, 246, 0.15);
711
+ border-color: rgba(139, 92, 246, 0.3);
712
+ }
713
+
714
+ .pl-card:hover::before {
715
+ opacity: 1;
716
+ }
717
+
718
+ :global(.theme-dark) .pl-card {
719
+ background: rgba(30, 41, 59, 0.5);
720
+ border-color: rgba(255, 255, 255, 0.08);
721
+ }
722
+
723
+ .pl-card-top {
724
+ display: flex;
725
+ flex-direction: column;
726
+ gap: 0.75rem;
727
+ }
728
+
729
+ .pl-card-title {
730
+ margin: 0;
731
+ font-size: 1.25rem;
732
+ font-weight: 700;
733
+ color: rgb(15, 23, 42);
734
+ line-height: 1.3;
735
+ }
736
+
737
+ :global(.theme-dark) .pl-card-title {
738
+ color: rgb(248, 250, 252);
739
+ }
740
+
741
+ .pl-card-actions {
742
+ display: flex;
743
+ gap: 0.25rem;
744
+ justify-content: flex-end;
745
+ }
746
+
747
+ .pl-card-body {
748
+ flex-grow: 1;
749
+ background: rgba(0, 0, 0, 0.02);
750
+ border-radius: 0.75rem;
751
+ padding: 1rem;
752
+ border: 1px solid rgba(0, 0, 0, 0.03);
753
+ }
754
+
755
+ :global(.theme-dark) .pl-card-body {
756
+ background: rgba(15, 23, 42, 0.4);
757
+ border-color: rgba(255, 255, 255, 0.05);
758
+ }
759
+
760
+ .pl-card-text {
761
+ margin: 0;
762
+ font-size: 0.95rem;
763
+ line-height: 1.6;
764
+ color: rgb(71, 85, 105);
765
+ white-space: pre-wrap;
766
+ overflow: hidden;
767
+ display: -webkit-box;
768
+ -webkit-line-clamp: 5;
769
+ line-clamp: 5;
770
+ -webkit-box-orient: vertical;
771
+ }
772
+
773
+ :global(.theme-dark) .pl-card-text {
774
+ color: rgb(203, 213, 225);
775
+ }
776
+
777
+ .pl-tags-container {
778
+ display: flex;
779
+ flex-wrap: wrap;
780
+ gap: 0.5rem;
781
+ }
782
+
783
+ .pl-card-tag {
784
+ background: rgba(139, 92, 246, 0.1);
785
+ color: rgb(124, 58, 237);
786
+ padding: 0.35rem 0.75rem;
787
+ border-radius: 9999px;
788
+ font-size: 0.75rem;
789
+ font-weight: 600;
790
+ letter-spacing: 0.025em;
791
+ text-transform: uppercase;
792
+ }
793
+
794
+ :global(.theme-dark) .pl-card-tag {
795
+ background: rgba(139, 92, 246, 0.15);
796
+ color: rgb(167, 139, 250);
797
+ }
798
+
799
+ .pl-star-btn {
800
+ color: rgb(203, 213, 225);
801
+ }
802
+
803
+ .pl-star-btn:hover {
804
+ color: rgb(245, 158, 11);
805
+ background: rgba(245, 158, 11, 0.1);
806
+ }
807
+
808
+ .pl-star-icon.pl-starred {
809
+ color: rgb(245, 158, 11);
810
+ fill: rgb(245, 158, 11);
811
+ }
812
+
813
+ .pl-var-highlight {
814
+ background: rgba(139, 92, 246, 0.2);
815
+ color: rgb(124, 58, 237);
816
+ padding: 0 0.25rem;
817
+ border-radius: 0.25rem;
818
+ font-weight: 600;
819
+ }
820
+
821
+ .pl-var-highlight.pl-var-filled {
822
+ background: rgba(16, 185, 129, 0.2);
823
+ color: rgb(5, 150, 105);
824
+ }
825
+
826
+ :global(.theme-dark) .pl-var-highlight {
827
+ background: rgba(139, 92, 246, 0.3);
828
+ color: rgb(167, 139, 250);
829
+ }
830
+
831
+ :global(.theme-dark) .pl-var-highlight.pl-var-filled {
832
+ background: rgba(16, 185, 129, 0.3);
833
+ color: rgb(52, 211, 153);
834
+ }
835
+
836
+ .pl-card-vars {
837
+ margin-top: 0.5rem;
838
+ display: flex;
839
+ flex-direction: column;
840
+ gap: 0.5rem;
841
+ background: rgba(139, 92, 246, 0.05);
842
+ padding: 1rem;
843
+ border-radius: 0.75rem;
844
+ border: 1px dashed rgba(139, 92, 246, 0.3);
845
+ }
846
+
847
+ .pl-card-vars-title {
848
+ font-size: 0.85rem;
849
+ font-weight: 600;
850
+ color: rgb(139, 92, 246);
851
+ text-transform: uppercase;
852
+ letter-spacing: 0.05em;
853
+ margin: 0;
854
+ }
855
+
856
+ .pl-var-input {
857
+ width: 100%;
858
+ padding: 0.5rem 0.75rem;
859
+ border-radius: 0.5rem;
860
+ border: 1px solid rgba(0, 0, 0, 0.1);
861
+ background: rgba(255, 255, 255, 0.8);
862
+ font-size: 0.9rem;
863
+ color: rgb(30, 41, 59);
864
+ box-sizing: border-box;
865
+ }
866
+
867
+ :global(.theme-dark) .pl-var-input {
868
+ background: rgba(15, 23, 42, 0.5);
869
+ border-color: rgba(255, 255, 255, 0.1);
870
+ color: rgb(248, 250, 252);
871
+ }
872
+
873
+ .pl-modal {
874
+ padding: 0;
875
+ border: none;
876
+ background: transparent;
877
+ max-width: 600px;
878
+ width: 90%;
879
+ border-radius: 1.5rem;
880
+ margin: auto;
881
+ overflow: visible;
882
+ }
883
+
884
+ .pl-modal::backdrop {
885
+ background: rgba(15, 23, 42, 0.6);
886
+ backdrop-filter: blur(8px);
887
+ opacity: 0;
888
+ transition: opacity 0.3s;
889
+ }
890
+
891
+ .pl-modal[open]::backdrop {
892
+ opacity: 1;
893
+ }
894
+
895
+ .pl-modal-container {
896
+ background: rgb(255, 255, 255);
897
+ border-radius: 1.5rem;
898
+ padding: 2rem;
899
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
900
+ transform: scale(0.95) translateY(20px);
901
+ opacity: 0;
902
+ transition: all 0.3s;
903
+ border: 1px solid rgba(0, 0, 0, 0.1);
904
+ }
905
+
906
+ :global(.theme-dark) .pl-modal-container {
907
+ background: rgb(30, 41, 59);
908
+ border-color: rgba(255, 255, 255, 0.1);
909
+ }
910
+
911
+ .pl-modal.pl-modal-open .pl-modal-container {
912
+ transform: scale(1) translateY(0);
913
+ opacity: 1;
914
+ }
915
+
916
+ .pl-modal-header {
917
+ display: flex;
918
+ justify-content: space-between;
919
+ align-items: center;
920
+ margin-bottom: 2rem;
921
+ }
922
+
923
+ .pl-modal-title {
924
+ margin: 0;
925
+ font-size: 1.5rem;
926
+ font-weight: 800;
927
+ background: linear-gradient(135deg, rgb(139, 92, 246), rgb(192, 38, 211));
928
+ -webkit-background-clip: text;
929
+ background-clip: text;
930
+ -webkit-text-fill-color: transparent;
931
+ }
932
+
933
+ .pl-form {
934
+ display: flex;
935
+ flex-direction: column;
936
+ gap: 1.5rem;
937
+ }
938
+
939
+ .pl-form-group {
940
+ display: flex;
941
+ flex-direction: column;
942
+ gap: 0.5rem;
943
+ }
944
+
945
+ .pl-label {
946
+ font-weight: 600;
947
+ font-size: 0.95rem;
948
+ color: rgb(51, 65, 85);
949
+ }
950
+
951
+ :global(.theme-dark) .pl-label {
952
+ color: rgb(226, 232, 240);
953
+ }
954
+
955
+ .pl-input {
956
+ width: 100%;
957
+ padding: 1rem;
958
+ border-radius: 1rem;
959
+ border: 1px solid rgba(0, 0, 0, 0.15);
960
+ background: rgb(248, 250, 252);
961
+ font-size: 1rem;
962
+ color: rgb(30, 41, 59);
963
+ transition: all 0.2s;
964
+ box-sizing: border-box;
965
+ }
966
+
967
+ .pl-textarea {
968
+ width: 100%;
969
+ padding: 1rem;
970
+ border-radius: 1rem;
971
+ border: 1px solid rgba(0, 0, 0, 0.15);
972
+ background: rgb(248, 250, 252);
973
+ font-size: 1rem;
974
+ color: rgb(30, 41, 59);
975
+ transition: all 0.2s;
976
+ box-sizing: border-box;
977
+ min-height: 150px;
978
+ resize: vertical;
979
+ line-height: 1.6;
980
+ }
981
+
982
+ :global(.theme-dark) .pl-input {
983
+ background: rgba(15, 23, 42, 0.6);
984
+ border-color: rgba(255, 255, 255, 0.1);
985
+ color: rgb(248, 250, 252);
986
+ }
987
+
988
+ :global(.theme-dark) .pl-textarea {
989
+ background: rgba(15, 23, 42, 0.6);
990
+ border-color: rgba(255, 255, 255, 0.1);
991
+ color: rgb(248, 250, 252);
992
+ }
993
+
994
+ .pl-input:focus {
995
+ outline: none;
996
+ border-color: rgb(139, 92, 246);
997
+ background: rgb(255, 255, 255);
998
+ box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.15);
999
+ }
1000
+
1001
+ .pl-textarea:focus {
1002
+ outline: none;
1003
+ border-color: rgb(139, 92, 246);
1004
+ background: rgb(255, 255, 255);
1005
+ box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.15);
1006
+ }
1007
+
1008
+ :global(.theme-dark) .pl-input:focus {
1009
+ background: rgba(15, 23, 42, 0.9);
1010
+ }
1011
+
1012
+ :global(.theme-dark) .pl-textarea:focus {
1013
+ background: rgba(15, 23, 42, 0.9);
1014
+ }
1015
+
1016
+ .pl-hint {
1017
+ font-size: 0.85rem;
1018
+ color: rgb(100, 116, 139);
1019
+ margin-top: 0.25rem;
1020
+ }
1021
+
1022
+ :global(.theme-dark) .pl-hint {
1023
+ color: rgb(148, 163, 184);
1024
+ }
1025
+
1026
+ .pl-tags-input-wrapper {
1027
+ display: flex;
1028
+ flex-wrap: wrap;
1029
+ gap: 0.5rem;
1030
+ padding: 0.5rem 1rem;
1031
+ border-radius: 1rem;
1032
+ border: 1px solid rgba(0, 0, 0, 0.15);
1033
+ background: rgb(248, 250, 252);
1034
+ min-height: 3.5rem;
1035
+ transition: all 0.2s;
1036
+ align-items: center;
1037
+ cursor: text;
1038
+ }
1039
+
1040
+ :global(.theme-dark) .pl-tags-input-wrapper {
1041
+ background: rgba(15, 23, 42, 0.6);
1042
+ border-color: rgba(255, 255, 255, 0.1);
1043
+ }
1044
+
1045
+ .pl-tags-input-wrapper:focus-within {
1046
+ border-color: rgb(139, 92, 246);
1047
+ background: rgb(255, 255, 255);
1048
+ box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.15);
1049
+ }
1050
+
1051
+ :global(.theme-dark) .pl-tags-input-wrapper:focus-within {
1052
+ background: rgba(15, 23, 42, 0.9);
1053
+ }
1054
+
1055
+ .pl-tags-list {
1056
+ display: flex;
1057
+ flex-wrap: wrap;
1058
+ gap: 0.5rem;
1059
+ }
1060
+
1061
+ .pl-tags-input-field {
1062
+ flex-grow: 1;
1063
+ min-width: 150px;
1064
+ border: none;
1065
+ background: transparent;
1066
+ font-size: 1rem;
1067
+ color: rgb(30, 41, 59);
1068
+ padding: 0.25rem 0;
1069
+ outline: none;
1070
+ }
1071
+
1072
+ :global(.theme-dark) .pl-tags-input-field {
1073
+ color: rgb(248, 250, 252);
1074
+ }
1075
+
1076
+ .pl-modal-footer {
1077
+ display: flex;
1078
+ justify-content: flex-end;
1079
+ gap: 1rem;
1080
+ margin-top: 1rem;
1081
+ padding-top: 1.5rem;
1082
+ border-top: 1px solid rgba(0, 0, 0, 0.05);
1083
+ }
1084
+
1085
+ :global(.theme-dark) .pl-modal-footer {
1086
+ border-top-color: rgba(255, 255, 255, 0.05);
1087
+ }
1088
+
1089
+ .pl-confirm-dialog {
1090
+ max-width: 400px;
1091
+ }
1092
+
1093
+ .pl-confirm-container {
1094
+ text-align: center;
1095
+ }
1096
+
1097
+ .pl-confirm-icon {
1098
+ margin-bottom: 1rem;
1099
+ color: rgb(239, 68, 68);
1100
+ }
1101
+
1102
+ .pl-confirm-title {
1103
+ font-size: 1.25rem;
1104
+ font-weight: 700;
1105
+ color: rgb(15, 23, 42);
1106
+ margin-bottom: 0.5rem;
1107
+ }
1108
+
1109
+ :global(.theme-dark) .pl-confirm-title {
1110
+ color: rgb(248, 250, 252);
1111
+ }
1112
+
1113
+ .pl-confirm-desc {
1114
+ font-size: 1rem;
1115
+ color: rgb(100, 116, 139);
1116
+ margin-bottom: 1.5rem;
1117
+ }
1118
+
1119
+ .pl-confirm-actions {
1120
+ display: flex;
1121
+ justify-content: center;
1122
+ gap: 1rem;
1123
+ }
1124
+
1125
+ @media (min-width: 768px) {
1126
+ .pl-topbar {
1127
+ flex-direction: row;
1128
+ padding: 1.25rem 2rem;
1129
+ }
1130
+ }
1131
+ </style>