domma-cms 0.75.0 → 0.77.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 (150) hide show
  1. package/CLAUDE.md +1 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +4 -4
  4. package/admin/js/lib/action-editor-arrange.js +1 -0
  5. package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
  6. package/admin/js/lib/api-tokens-arrange.js +3 -0
  7. package/admin/js/lib/block-editor-arrange.js +2 -0
  8. package/admin/js/lib/collection-editor-arrange.js +3 -0
  9. package/admin/js/lib/collection-entries-arrange.js +1 -0
  10. package/admin/js/lib/component-editor-arrange.js +27 -0
  11. package/admin/js/lib/context-menu-editor-arrange.js +1 -0
  12. package/admin/js/lib/dashboard-arrange.js +1 -0
  13. package/admin/js/lib/effects-arrange.js +3 -0
  14. package/admin/js/lib/form-editor-arrange.js +1 -0
  15. package/admin/js/lib/form-submissions-arrange.js +2 -0
  16. package/admin/js/lib/layouts-arrange.js +1 -0
  17. package/admin/js/lib/menu-editor-arrange.js +2 -0
  18. package/admin/js/lib/my-profile-arrange.js +1 -0
  19. package/admin/js/lib/notifications-arrange.js +1 -0
  20. package/admin/js/lib/page-editor-arrange.js +1 -0
  21. package/admin/js/lib/plugin-code-arrange.js +1 -0
  22. package/admin/js/lib/plugins-arrange.js +2 -0
  23. package/admin/js/lib/project-page-arrange.js +1 -0
  24. package/admin/js/lib/role-editor-arrange.js +3 -0
  25. package/admin/js/lib/roles-arrange.js +1 -0
  26. package/admin/js/lib/search-settings-arrange.js +1 -0
  27. package/admin/js/lib/sidebar-renderer.js +2 -2
  28. package/admin/js/lib/site-settings-arrange.js +1 -0
  29. package/admin/js/lib/user-editor-arrange.js +1 -0
  30. package/admin/js/lib/users-arrange.js +1 -0
  31. package/admin/js/lib/view-editor-arrange.js +14 -0
  32. package/admin/js/templates/action-editor.html +223 -147
  33. package/admin/js/templates/api-endpoint-editor.html +172 -117
  34. package/admin/js/templates/api-tokens.html +83 -10
  35. package/admin/js/templates/block-editor.html +194 -203
  36. package/admin/js/templates/collection-editor.html +191 -113
  37. package/admin/js/templates/collection-entries.html +99 -36
  38. package/admin/js/templates/component-editor.html +128 -126
  39. package/admin/js/templates/context-menu-editor.html +208 -148
  40. package/admin/js/templates/dashboard.html +184 -22
  41. package/admin/js/templates/effects.html +155 -72
  42. package/admin/js/templates/form-editor.html +41 -26
  43. package/admin/js/templates/form-submissions.html +78 -26
  44. package/admin/js/templates/layouts.html +126 -43
  45. package/admin/js/templates/menu-editor.html +57 -17
  46. package/admin/js/templates/menu-locations.html +54 -11
  47. package/admin/js/templates/my-profile.html +100 -12
  48. package/admin/js/templates/notifications.html +70 -10
  49. package/admin/js/templates/page-editor.html +47 -26
  50. package/admin/js/templates/plugin-code.html +91 -73
  51. package/admin/js/templates/plugins.html +100 -28
  52. package/admin/js/templates/project-detail.html +83 -145
  53. package/admin/js/templates/project-settings.html +88 -53
  54. package/admin/js/templates/role-editor.html +158 -83
  55. package/admin/js/templates/roles.html +78 -7
  56. package/admin/js/templates/search.html +193 -216
  57. package/admin/js/templates/settings.html +404 -472
  58. package/admin/js/templates/user-editor.html +150 -14
  59. package/admin/js/templates/users.html +69 -9
  60. package/admin/js/templates/view-editor.html +203 -180
  61. package/admin/js/templates/view-preview.html +45 -45
  62. package/admin/js/views/action-editor.js +7 -1
  63. package/admin/js/views/api-endpoint-editor.js +2 -3
  64. package/admin/js/views/api-tokens.js +62 -8
  65. package/admin/js/views/block-editor.js +14 -8
  66. package/admin/js/views/collection-editor.js +64 -5
  67. package/admin/js/views/collection-entries.js +50 -1
  68. package/admin/js/views/component-editor.js +2 -28
  69. package/admin/js/views/context-menu-editor.js +54 -55
  70. package/admin/js/views/dashboard.js +7 -1
  71. package/admin/js/views/effects.js +31 -1
  72. package/admin/js/views/form-editor.js +7 -7
  73. package/admin/js/views/form-submissions.js +11 -1
  74. package/admin/js/views/index.js +1 -1
  75. package/admin/js/views/layouts.js +81 -1
  76. package/admin/js/views/login.js +2 -2
  77. package/admin/js/views/menu-editor.js +21 -43
  78. package/admin/js/views/menu-locations.js +16 -1
  79. package/admin/js/views/my-profile.js +1 -1
  80. package/admin/js/views/notifications.js +18 -1
  81. package/admin/js/views/page-editor.js +26 -26
  82. package/admin/js/views/plugin-code.js +23 -4
  83. package/admin/js/views/plugins.js +97 -39
  84. package/admin/js/views/project-detail.js +9 -1
  85. package/admin/js/views/project-settings.js +1 -1
  86. package/admin/js/views/role-editor.js +11 -3
  87. package/admin/js/views/roles.js +49 -4
  88. package/admin/js/views/search.js +7 -1
  89. package/admin/js/views/settings.js +13 -3
  90. package/admin/js/views/user-editor.js +1 -1
  91. package/admin/js/views/users.js +13 -4
  92. package/admin/js/views/view-editor.js +1 -1
  93. package/admin/js/views/view-preview.js +1 -1
  94. package/package.json +1 -1
  95. package/plugins/_lib/admin/mail/compose-window.js +97 -40
  96. package/plugins/_lib/admin/mail/contacts.js +4 -1
  97. package/plugins/_lib/admin/mail/diagnostics-section.js +4 -1
  98. package/plugins/_lib/admin/mail/folder-tree.js +1 -1
  99. package/plugins/_lib/admin/mail/image-senders-section.js +69 -90
  100. package/plugins/_lib/admin/mail/mail.css +1 -1
  101. package/plugins/_lib/admin/mail/mail.html +1 -1
  102. package/plugins/_lib/admin/mail/reader-view.js +303 -44
  103. package/plugins/_lib/admin/mail/when-closed.js +26 -0
  104. package/plugins/_lib/mail/readRoutes.js +43 -1
  105. package/plugins/_lib/mail/unreadBadge.js +42 -0
  106. package/plugins/free-tier.lock.json +5 -5
  107. package/plugins/mail-reader/CLAUDE.md +1 -0
  108. package/plugins/mail-reader/plugin.js +3 -1
  109. package/plugins/mail-reader/plugin.json +5 -4
  110. package/server/routes/api/api-tokens.js +56 -0
  111. package/server/routes/api/auth.js +7 -1
  112. package/server/routes/api/collections.js +13 -1
  113. package/server/routes/api/dashboard.js +32 -4
  114. package/server/routes/api/effects.js +68 -13
  115. package/server/routes/api/forms.js +104 -13
  116. package/server/routes/api/layouts.js +93 -2
  117. package/server/routes/api/notifications.js +140 -14
  118. package/server/routes/api/plugins.js +135 -4
  119. package/server/routes/api/roles.js +111 -0
  120. package/server/routes/api/search.js +38 -4
  121. package/server/routes/api/settings.js +52 -0
  122. package/server/routes/api/users.js +65 -3
  123. package/server/routes/api/views.js +55 -1
  124. package/server/server.js +12 -0
  125. package/server/services/components.js +9 -0
  126. package/server/services/plugins.js +7 -0
  127. package/server/services/roles.js +3 -0
  128. package/server/services/search.js +58 -0
  129. package/server/services/users.js +16 -2
  130. package/server/services/views.js +14 -0
  131. package/admin/js/templates/dashboard/activity-feed.html +0 -3
  132. package/admin/js/templates/dashboard/cache.html +0 -32
  133. package/admin/js/templates/dashboard/health-detail.html +0 -2
  134. package/admin/js/templates/dashboard/journeys.html +0 -17
  135. package/admin/js/templates/dashboard/kpi-strip.html +0 -34
  136. package/admin/js/templates/dashboard/spike-feed.html +0 -3
  137. package/admin/js/templates/dashboard/top-pages.html +0 -3
  138. package/admin/js/templates/dashboard/traffic-chart.html +0 -3
  139. package/admin/js/templates/plugin-marketplace.html +0 -29
  140. package/admin/js/views/block-editor-enhance.js +0 -1
  141. package/admin/js/views/dashboard/lib/escape.js +0 -1
  142. package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
  143. package/admin/js/views/dashboard/widgets/cache.js +0 -1
  144. package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
  145. package/admin/js/views/dashboard/widgets/journeys.js +0 -1
  146. package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
  147. package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
  148. package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
  149. package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
  150. package/admin/js/views/plugin-marketplace.js +0 -1
@@ -1,82 +1,100 @@
1
- <style>
2
- .pcode-layout { display: flex; gap: 1rem; align-items: stretch; min-height: 70vh; }
3
- .pcode-files { flex: 0 0 250px; overflow-y: auto; }
4
- .pcode-files .card-body { padding: .5rem; }
5
- .pcode-dir {
6
- padding: .5rem .5rem .2rem; font-size: .7rem; font-weight: 700;
7
- text-transform: uppercase; letter-spacing: .04em;
8
- color: var(--dm-text-muted, #6b7280);
9
- }
10
- .pcode-file {
11
- display: flex; align-items: center; gap: .4rem; width: 100%;
12
- padding: .3rem .5rem .3rem 1rem; border: 0; background: none; cursor: pointer;
13
- border-radius: var(--dm-radius-md, 6px); text-align: left;
14
- color: var(--dm-text, inherit); font-size: .82rem;
15
- font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace);
16
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
17
- }
18
- .pcode-file:hover { background: var(--dm-hover-bg, rgba(128,128,128,.12)); }
19
- .pcode-file.active { background: var(--dm-primary, #2563eb); color: #fff; }
20
- .pcode-file .pcode-dirty { margin-left: auto; font-weight: 700; }
21
- .pcode-editor-card { flex: 1; display: flex; flex-direction: column; min-width: 0; }
22
- .pcode-editor-card .card-body { flex: 1; display: flex; flex-direction: column; padding: 0; }
23
- .pcode-toolbar {
24
- display: flex; align-items: center; gap: .5rem;
25
- padding: .5rem .75rem; border-bottom: 1px solid var(--dm-border, rgba(128,128,128,.25));
26
- }
27
- .pcode-current {
28
- font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace);
29
- font-size: .85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
30
- }
31
- #pcode-editor { flex: 1; display: flex; min-height: 55vh; }
32
- #pcode-editor > .dm-edit-wrap { flex: 1; }
33
- .pcode-statusbar {
34
- display: flex; align-items: center; gap: .75rem; padding: .4rem .75rem;
35
- border-top: 1px solid var(--dm-border, rgba(128,128,128,.25));
36
- font-size: .78rem; color: var(--dm-text-muted, #6b7280);
37
- }
38
- #pcode-cursor { margin-left: auto; font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace); }
39
- </style>
40
-
41
- <div class="view-header d-flex justify-content-between align-items-center mb-4">
42
- <div>
43
- <h1 class="view-title"><span data-icon="code"></span> <span id="pcode-title">Plugin Code</span></h1>
44
- <p class="view-subtitle text-muted" style="margin:0;font-size:.875rem">
45
- <code>admin/</code> files apply on a hard refresh; everything else needs a server restart.
46
- </p>
47
- </div>
48
- <div class="d-flex gap-2">
49
- <button id="pcode-back" class="btn btn-ghost"><span data-icon="arrow-left"></span> Plugins</button>
50
- <button id="pcode-restart" class="btn btn-warning"><span data-icon="refresh-cw"></span> Restart Server</button>
51
- </div>
52
- </div>
1
+ <!-- Plugin code editor. Bound with M.applyBindings - see admin/js/views/plugin-code.js, which also inserts
2
+ the file-row template (the router renders a templateUrl through _.render, eating any {{…}}).
3
+ No page heading: the Tool banner ("Marketplace › <plugin>") is the heading; Back, New file, Save all
4
+ and Restart server are banner actions. -->
5
+ <div class="pcx">
6
+ <p class="pcx-alert is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
7
+ <p class="pcx-alert" data-bind-hidden="!restartPending.value">
8
+ <span data-icon="alert-triangle" data-icon-size="16"></span>
9
+ <span>Server files were saved - they apply when the server restarts.</span>
10
+ <button type="button" class="btn btn-sm btn-warning" data-on-click="restart">Restart server</button>
11
+ </p>
53
12
 
54
- <div class="pcode-layout">
55
- <div class="card pcode-files">
56
- <div class="card-body">
57
- <div style="display:flex;justify-content:space-between;align-items:center;padding:.25rem .5rem .5rem;">
58
- <strong style="font-size:.8rem">Files</strong>
59
- <button id="pcode-newfile" class="btn btn-sm btn-ghost" title="New file"><span data-icon="plus"></span></button>
13
+ <div class="pcx-layout">
14
+ <div class="pcx-card pcx-files">
15
+ <div class="pcx-card-title">Files
16
+ <span class="pcx-count" data-bind-text="files.value.length"></span>
17
+ <span data-help-title="Files" data-help="Every editable file in the plugin (.js, .json, .md, .html, .css, .txt, .svg). Click one to open it - unsaved edits are kept per file while you switch, and a ● marks them. Right-click a file (or ⋮, or Shift+F10) to save, revert, copy its path, rename, duplicate or delete it. plugin.json, plugin.js and config.js cannot be deleted or renamed."></span>
18
+ <span class="pcx-spacer"></span>
19
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="newFile" title="New file" aria-label="New file"><span data-icon="plus" data-icon-size="14"></span></button>
60
20
  </div>
61
- <div id="pcode-filelist"></div>
21
+ <input type="search" class="form-input pcx-search" placeholder="Filter files…" aria-label="Filter files" data-model="query.value">
22
+ <p class="pcx-note" data-bind-hidden="!loading.value">Loading…</p>
23
+ <p class="pcx-note" data-bind-hidden="loading.value || rows.value.length" data-bind-text="emptyText.value"></p>
24
+ <div class="pcx-list" data-each="rows.value key=key"></div>
62
25
  </div>
63
- </div>
64
26
 
65
- <div class="card pcode-editor-card">
66
- <div class="card-body">
67
- <div class="pcode-toolbar">
68
- <span class="pcode-current" id="pcode-current">Select a file</span>
69
- <span style="flex:1"></span>
70
- <button id="pcode-rename" class="btn btn-sm btn-ghost" title="Rename file" disabled><span data-icon="edit-3"></span></button>
71
- <button id="pcode-delete" class="btn btn-sm btn-ghost" title="Delete file" disabled><span data-icon="trash"></span></button>
72
- <button id="pcode-saveall" class="btn btn-sm btn-ghost" disabled>Save All</button>
73
- <button id="pcode-save" class="btn btn-sm btn-primary" disabled><span data-icon="save"></span> Save</button>
27
+ <div class="pcx-card pcx-editor-card">
28
+ <div class="pcx-toolbar">
29
+ <span class="pcx-current" data-bind-text="currentText.value">Select a file</span>
30
+ <span class="pcx-chip" data-bind-hidden="!openPath.value" data-bind-class="scopeCls.value" data-bind-text="scopeLabel.value" data-bind-title="scopeHint.value"></span>
31
+ <span class="pcx-chip is-warn" data-bind-hidden="!currentDirty.value">Unsaved</span>
32
+ <span data-help-title="Saving" data-help="Ctrl+S saves the open file, Ctrl+Shift+S every unsaved file. An admin/ file applies when the admin tab is refreshed; anything else (plugin.js, config.js, public snippets…) is read when the server starts, so it needs a restart - Restart server is in the banner. Leaving with unsaved files asks first and keeps them as a draft for your next visit."></span>
33
+ <span class="pcx-spacer"></span>
34
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="revert" data-bind-hidden="!currentDirty.value" title="Put the open file back as it was saved">Revert</button>
35
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="!currentDirty.value || saving.value">
36
+ <span data-icon="save" data-icon-size="14"></span> Save</button>
37
+ <button type="button" class="pcx-kebab pcx-more" data-bind-hidden="!openPath.value" aria-label="More for this file" title="More">⋮</button>
74
38
  </div>
75
- <div id="pcode-editor"></div>
76
- <div class="pcode-statusbar">
77
- <span id="pcode-status">Ctrl+S saves the open file. Unsaved edits survive switching files.</span>
78
- <span id="pcode-cursor"></span>
39
+ <div class="pcx-editor" id="pcode-editor"></div>
40
+ <p class="pcx-empty" data-bind-hidden="openPath.value">Choose a file on the left to edit it.</p>
41
+ <div class="pcx-statusbar">
42
+ <span data-bind-text="status.value"></span>
43
+ <span class="pcx-spacer"></span>
44
+ <span data-bind-hidden="!dirtyCount.value" data-bind-text="dirtyText.value"></span>
45
+ <span class="pcx-cursor" data-bind-text="cursor.value"></span>
79
46
  </div>
80
47
  </div>
81
48
  </div>
82
49
  </div>
50
+
51
+ <style>
52
+ .pcx { display: flex; flex-direction: column; gap: .75rem; }
53
+ .pcx [hidden], .pcx-form [hidden] { display: none !important; }
54
+ .pcx-alert { margin: 0; display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; padding: .55rem .8rem; border-radius: var(--dm-radius-md);
55
+ background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); }
56
+ .pcx-alert.is-bad { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); color: var(--dm-danger); }
57
+ .pcx-layout { display: flex; gap: .75rem; align-items: stretch; min-height: 70vh; }
58
+ .pcx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); display: flex; flex-direction: column; min-width: 0; }
59
+ .pcx-files { flex: 0 0 270px; padding: .6rem; gap: .5rem; overflow-y: auto; max-height: 80vh; }
60
+ .pcx-card-title { display: flex; align-items: center; gap: .3rem; font-weight: 600; }
61
+ .pcx-count { opacity: .6; font-size: var(--dm-text-xs); font-weight: 400; }
62
+ .pcx-spacer { flex: 1; }
63
+ .pcx-search { width: 100%; }
64
+ .pcx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
65
+ .pcx-list { display: flex; flex-direction: column; }
66
+ .pcx-dir { padding: .5rem .35rem .15rem; font-size: var(--dm-text-xs); font-weight: 700; letter-spacing: .03em; color: var(--dm-text-muted);
67
+ font-family: var(--dm-font-mono, monospace); }
68
+ .pcx-file { display: flex; align-items: center; gap: .35rem; padding: .28rem .35rem; border-radius: var(--dm-radius-sm); cursor: pointer;
69
+ color: var(--dm-text); font-size: var(--dm-text-sm); font-family: var(--dm-font-mono, monospace); min-width: 0; }
70
+ .pcx-file.is-nested { padding-left: 1rem; }
71
+ .pcx-file:hover, .pcx-file:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
72
+ .pcx-file.is-open { background: color-mix(in srgb, var(--dm-primary) 16%, transparent); box-shadow: inset 3px 0 0 var(--dm-primary); }
73
+ .pcx-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
74
+ .pcx-dirty { color: var(--dm-warning); font-weight: 700; }
75
+ .pcx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); line-height: 1; color: var(--dm-text-muted);
76
+ border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
77
+ .pcx-kebab:hover { color: var(--dm-primary); }
78
+ .pcx-editor-card { flex: 1; }
79
+ .pcx-toolbar { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; padding: .5rem .75rem; border-bottom: 1px solid var(--dm-border); }
80
+ .pcx-toolbar .btn { display: inline-flex; align-items: center; gap: .3rem; }
81
+ .pcx-current { font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
82
+ .pcx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
83
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
84
+ .pcx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 14%, transparent); color: var(--dm-info, var(--dm-primary)); }
85
+ .pcx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
86
+ .pcx-editor { flex: 1; display: flex; min-height: 55vh; }
87
+ .pcx-editor > .dm-edit-wrap { flex: 1; }
88
+ .pcx-empty { margin: 0; padding: 2rem; color: var(--dm-text-muted); text-align: center; }
89
+ .pcx-statusbar { display: flex; align-items: center; gap: .75rem; padding: .4rem .75rem; border-top: 1px solid var(--dm-border);
90
+ font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
91
+ .pcx-cursor { font-family: var(--dm-font-mono, monospace); }
92
+ /* The path slideover */
93
+ .pcx-form { display: flex; flex-direction: column; gap: .85rem; }
94
+ .pcx-form .form-label { display: flex; align-items: center; gap: .3rem; }
95
+ .pcx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
96
+ .pcx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
97
+ .pcx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
98
+ .pcx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
99
+ @media (max-width: 800px) { .pcx-layout { flex-direction: column; } .pcx-files { flex-basis: auto; max-height: 40vh; } }
100
+ </style>
@@ -1,35 +1,107 @@
1
- <div class="view-header d-flex justify-content-between align-items-center">
2
- <div>
3
- <h1><span data-icon="store"></span> Marketplace</h1>
4
- <p class="text-muted" style="margin:0;font-size:.875rem">Install plugins to add features. Once enabled, a plugin appears under Tools. Changes take effect after a server restart.</p>
5
- </div>
6
- <div style="display:flex;gap:.5rem;align-items:center;">
7
- <button id="install-file-btn" class="btn btn-secondary" title="Install a .dcmsplugin file">
8
- <span data-icon="upload"></span> Install from File
9
- </button>
10
- <button id="new-plugin-btn" class="btn btn-primary">
11
- <span data-icon="plus"></span> New Plugin
12
- </button>
13
- </div>
14
- <input type="file" id="install-file-input" accept=".dcmsplugin" hidden>
15
- </div>
1
+ <!-- The Marketplace (System › Plugins). Bound with M.applyBindings - see admin/js/views/plugins.js, which
2
+ also inserts the row templates (the router renders a templateUrl through _.render, eating any {{…}}).
3
+ No page heading: the Tool banner is the heading; New plugin and Install from file are banner actions. -->
4
+ <div class="plx">
5
+ <p class="plx-note">Install plugins to add features. Once switched on, a plugin is a Tool and appears in the sidebar.
6
+ <span data-help-title="The Marketplace" data-help="Installed lists what is on this site; Browse lists what this site can get from its manager (a site that is not managed installs from a .dcmsplugin file instead - Install from file in the banner). Switching a plugin on or off restarts the server, because a plugin's routes and screens are only read at start-up. Licensed plugins carry their licence with them: one that is switched on but not licensed does not run, and says so here."></span></p>
7
+ <input type="file" class="plx-file" accept=".dcmsplugin" hidden>
16
8
 
17
- <div id="plugin-tabs" class="tabs">
18
- <div class="tab-list">
19
- <button class="tab-item active" data-tab="installed">Installed</button>
20
- <button class="tab-item" data-tab="marketplace">Browse</button>
21
- </div>
9
+ <p class="plx-alert" data-bind-hidden="!restarting.value">
10
+ <span class="plx-spinner" aria-hidden="true"></span>
11
+ <span data-bind-text="restarting.value"></span>
12
+ </p>
22
13
 
23
- <div class="tab-panel active" data-panel="installed">
24
- <div id="plugins-empty" class="card" style="display:none">
25
- <div class="card-body text-muted">
26
- No plugins found. Drop a plugin folder into the <code>plugins/</code> directory to get started.
14
+ <div class="tabs plx-tabs">
15
+ <div class="tab-list">
16
+ <button type="button" class="tab-item active">Installed <span class="plx-count" data-bind-text="plugins.value.length"></span></button>
17
+ <button type="button" class="tab-item">Browse <span class="plx-count" data-bind-hidden="!updateCount.value" data-bind-text="updateCountText.value"></span></button>
18
+ </div>
19
+
20
+ <!-- Installed -->
21
+ <div class="tab-panel active">
22
+ <div class="plx-card">
23
+ <div class="plx-bar">
24
+ <div class="plx-seg" role="group" aria-label="Show">
25
+ <button type="button" data-on-click="showAll" data-bind-class="filter.value === 'all' && 'is-on'">All <b data-bind-text="counts.value.all"></b></button>
26
+ <button type="button" data-on-click="showOn" data-bind-class="filter.value === 'on' && 'is-on'">On <b data-bind-text="counts.value.on"></b></button>
27
+ <button type="button" data-on-click="showOff" data-bind-class="filter.value === 'off' && 'is-on'">Off <b data-bind-text="counts.value.off"></b></button>
28
+ <button type="button" data-on-click="showAttention" data-bind-class="filter.value === 'attention' && 'is-on'" data-bind-hidden="!counts.value.attention">Needs attention <b data-bind-text="counts.value.attention"></b></button>
29
+ </div>
30
+ <input type="search" class="form-input plx-search" placeholder="Search plugins…" aria-label="Search plugins" data-model="query.value">
31
+ <span data-help-title="Installed plugins" data-help="Click a plugin for its details and settings. Right-click it (or ⋮, or Shift+F10) to switch it on or off, configure it, update it, view its source, export it as a .dcmsplugin file or uninstall it. Built-in plugins are part of the CMS and always on. Licensed plugins cannot be viewed or exported here."></span>
32
+ </div>
33
+ <p class="plx-note" data-bind-hidden="!loading.value">Loading…</p>
34
+ <p class="plx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
35
+ <p class="plx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
36
+ <div class="plx-list plx-installed" data-each="rows.value key=name"></div>
27
37
  </div>
28
38
  </div>
29
- <div id="plugins-grid" class="plugins-grid"></div>
30
- </div>
31
39
 
32
- <div class="tab-panel" data-panel="marketplace">
33
- <div id="marketplace-mount"></div>
40
+ <!-- Browse -->
41
+ <div class="tab-panel">
42
+ <div class="plx-browse"></div>
43
+ </div>
34
44
  </div>
35
45
  </div>
46
+
47
+ <style>
48
+ .plx { display: flex; flex-direction: column; gap: .75rem; }
49
+ .plx [hidden], .plx-form [hidden] { display: none !important; }
50
+ .plx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
51
+ .plx-note.is-bad { color: var(--dm-danger); }
52
+ .plx-alert { margin: 0; display: flex; gap: .5rem; align-items: center; padding: .6rem .8rem; border-radius: var(--dm-radius-md);
53
+ background: color-mix(in srgb, var(--dm-primary) 14%, transparent); color: var(--dm-text); }
54
+ .plx-spinner { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--dm-primary) 30%, transparent);
55
+ border-top-color: var(--dm-primary); animation: plx-spin .8s linear infinite; flex-shrink: 0; }
56
+ @keyframes plx-spin { to { transform: rotate(360deg); } }
57
+ .plx-count { opacity: .65; font-size: var(--dm-text-xs); margin-left: .2rem; }
58
+ .plx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; margin-top: .75rem; }
59
+ .plx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
60
+ .plx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
61
+ .plx-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
62
+ .plx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
63
+ .plx-seg button + button { border-left: 1px solid var(--dm-border); }
64
+ .plx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
65
+ .plx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
66
+ .plx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
67
+ .plx-list { display: flex; flex-direction: column; }
68
+ .plx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
69
+ .plx-row:last-child { border-bottom: 0; }
70
+ .plx-row:hover, .plx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
71
+ .plx-row.is-attention { box-shadow: inset 3px 0 0 var(--dm-warning); }
72
+ .plx-icon { display: inline-flex; align-items: center; justify-content: center; width: 2.1rem; height: 2.1rem; flex-shrink: 0;
73
+ border-radius: var(--dm-radius-md); color: var(--plugin-accent, var(--dm-primary));
74
+ background: color-mix(in srgb, var(--plugin-accent, var(--dm-primary)) 14%, transparent); }
75
+ .plx-row.is-off .plx-icon { color: var(--dm-text-muted); background: color-mix(in srgb, var(--dm-text-muted) 12%, transparent); }
76
+ .plx-row.is-off .plx-main strong { color: var(--dm-text-muted); }
77
+ .plx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
78
+ .plx-main strong, .plx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
79
+ .plx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
80
+ .plx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
81
+ .plx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
82
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
83
+ .plx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
84
+ .plx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 16%, transparent); color: var(--dm-info, var(--dm-primary)); }
85
+ .plx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
86
+ .plx-chip.is-danger { background: color-mix(in srgb, var(--dm-danger) 16%, transparent); color: var(--dm-danger); }
87
+ .plx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
88
+ .plx-kebab:hover { color: var(--dm-primary); }
89
+ .plx-act { flex-shrink: 0; }
90
+ /* Slideovers */
91
+ .plx-form { display: flex; flex-direction: column; gap: .85rem; }
92
+ .plx-form .form-label { display: flex; align-items: center; gap: .3rem; }
93
+ .plx-facts { display: grid; grid-template-columns: max-content 1fr; gap: .3rem .9rem; margin: 0; font-size: var(--dm-text-sm); }
94
+ .plx-facts dt { color: var(--dm-text-muted); }
95
+ .plx-facts dd { margin: 0; }
96
+ .plx-desc { margin: 0; }
97
+ .plx-sub { margin: .25rem 0 0; font-size: var(--dm-text-sm); font-weight: 600; }
98
+ .plx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
99
+ .plx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
100
+ .plx-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
101
+ .plx-setting .form-label { margin: 0; }
102
+ .plx-json { font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm); }
103
+ .plx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); flex-wrap: wrap; }
104
+ .plx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
105
+ .plx-spacer { flex: 1; }
106
+ @media (max-width: 640px) { .plx-main small { white-space: normal; } .plx-chips { display: none; } }
107
+ </style>
@@ -1,152 +1,90 @@
1
- <style>
2
- .pd-wrap { display: flex; flex-direction: column; gap: 1.5rem; }
3
-
4
- /* ── Hero ─────────────────────────────────────────────────────────── */
5
- .pd-hero {
6
- display: flex;
7
- gap: 1.25rem;
8
- align-items: flex-start;
9
- padding: 1.5rem;
10
- border: 1px solid var(--dm-border, rgba(255,255,255,.08));
11
- border-radius: 14px;
12
- background:
13
- radial-gradient(120% 160% at 0% 0%, color-mix(in srgb, var(--dm-primary) 12%, transparent), transparent 55%),
14
- var(--dm-card-bg, rgba(255,255,255,.04));
15
- position: relative;
16
- overflow: hidden;
17
- }
18
- .pd-hero::before {
19
- content: '';
20
- position: absolute;
21
- top: 0; bottom: 0; left: 0;
22
- width: 4px;
23
- background: linear-gradient(180deg, var(--dm-primary), var(--dm-primary-dark));
24
- }
25
- .pd-hero-icon {
26
- width: 64px; height: 64px;
27
- flex-shrink: 0;
28
- border-radius: 16px;
29
- display: flex; align-items: center; justify-content: center;
30
- background: linear-gradient(135deg, color-mix(in srgb, var(--dm-primary) 28%, transparent), color-mix(in srgb, var(--dm-primary-dark) 28%, transparent));
31
- color: var(--dm-primary);
32
- box-shadow: 0 6px 18px color-mix(in srgb, var(--dm-primary) 18%, transparent);
33
- }
34
- .pd-hero-icon svg, .pd-hero-icon span[data-icon] { width: 32px; height: 32px; }
35
- .pd-hero-main { flex: 1; min-width: 0; }
36
- .pd-hero-top {
37
- display: flex; align-items: flex-start; justify-content: space-between;
38
- gap: 1rem; flex-wrap: wrap;
39
- }
40
- .pd-title { font-size: 1.6rem; font-weight: 700; margin: 0; line-height: 1.15; }
41
- .pd-actions { display: flex; gap: .5rem; flex-shrink: 0; }
42
- .pd-desc { color: var(--dm-text-muted, #888); margin: .5rem 0 0; max-width: 64ch; }
43
- .pd-desc:empty { display: none; }
44
-
45
- /* ── Meta badges ──────────────────────────────────────────────────── */
46
- .pd-meta { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.1rem; }
47
- .pd-meta-badge {
48
- display: inline-flex; align-items: center; gap: .45rem;
49
- padding: .4rem .7rem;
50
- border-radius: 999px;
51
- font-size: .8rem; line-height: 1;
52
- border: 1px solid transparent;
53
- }
54
- .pd-meta-badge svg, .pd-meta-badge span[data-icon] { width: 14px; height: 14px; }
55
- .pd-meta-k { color: var(--dm-text-muted, #999); font-weight: 500; }
56
- .pd-meta-v { font-weight: 600; }
57
-
58
- /* ── Section header ───────────────────────────────────────────────── */
59
- .pd-section-head {
60
- display: flex; align-items: baseline; gap: .6rem;
61
- margin: 0 0 .35rem;
62
- }
63
- .pd-section-title {
64
- font-size: 1rem; font-weight: 600; margin: 0;
65
- display: flex; align-items: center; gap: .5rem;
66
- }
67
- .pd-section-title svg, .pd-section-title span[data-icon] {
68
- width: 17px; height: 17px; color: var(--dm-text-muted, #888);
69
- }
70
- .pd-total { font-size: .8rem; color: var(--dm-text-muted, #888); }
71
- .pd-hint { color: var(--dm-text-muted, #888); margin: 0 0 .9rem; font-size: .82rem; }
72
-
73
- /* ── Artefact stat tiles (each with a + to create one) ────────────── */
74
- .pd-tiles {
75
- display: grid;
76
- grid-template-columns: repeat(auto-fill, minmax(176px, 1fr));
77
- gap: .85rem;
78
- }
79
- .pd-tile {
80
- position: relative;
81
- border: 1px solid var(--dm-border, rgba(255,255,255,.08));
82
- border-left: 3px solid var(--c, #5b8cff);
83
- border-radius: 11px;
84
- background: var(--dm-card-bg, rgba(255,255,255,.04));
85
- transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
86
- }
87
- .pd-tile--link:hover {
88
- transform: translateY(-2px);
89
- box-shadow: var(--dm-shadow-lg);
90
- border-color: var(--c, #5b8cff);
91
- }
92
- .pd-tile-link {
93
- display: flex; align-items: center; gap: .85rem;
94
- padding: .9rem 1rem; padding-right: 2.6rem; /* room for the + */
95
- text-decoration: none; color: inherit;
96
- }
97
- .pd-tile-link--empty { opacity: .55; }
98
- .pd-tile-chip {
99
- width: 40px; height: 40px; flex-shrink: 0;
100
- border-radius: 10px;
101
- display: flex; align-items: center; justify-content: center;
102
- }
103
- .pd-tile-chip svg, .pd-tile-chip span[data-icon] { width: 20px; height: 20px; }
104
- .pd-tile-body { display: flex; flex-direction: column; min-width: 0; }
105
- .pd-tile-count { font-size: 1.5rem; font-weight: 700; line-height: 1; }
106
- .pd-tile-label {
107
- font-size: .78rem; color: var(--dm-text-muted, #888);
108
- margin-top: .2rem; text-transform: uppercase; letter-spacing: .03em;
109
- }
110
- .pd-tile-add {
111
- position: absolute; top: 8px; right: 8px;
112
- width: 26px; height: 26px; border-radius: 7px;
113
- display: flex; align-items: center; justify-content: center;
114
- border: 1px solid var(--dm-border, rgba(255,255,255,.12));
115
- background: var(--dm-card-bg, rgba(255,255,255,.05));
116
- color: var(--c, #5b8cff);
117
- cursor: pointer; opacity: .7;
118
- transition: opacity .14s ease, background .14s ease, border-color .14s ease, transform .14s ease;
119
- }
120
- .pd-tile-add:hover {
121
- opacity: 1; transform: scale(1.08);
122
- border-color: var(--c, #5b8cff);
123
- background: var(--dm-hover-bg);
124
- }
125
- .pd-tile-add svg, .pd-tile-add span[data-icon] { width: 15px; height: 15px; }
126
- </style>
1
+ <!-- A project's overview. Bound with M.applyBindings - see admin/js/views/project-detail.js, which also
2
+ inserts the tile template (the router renders a templateUrl through _.render, eating any {{…}}).
3
+ No page heading: the Tool banner is the heading ("Projects › <name>"), with Settings in it. -->
4
+ <div class="pdx">
5
+ <p class="pdx-alert" data-bind-hidden="!off.value">
6
+ <span data-icon="alert-triangle" data-icon-size="16"></span>
7
+ <span>This project is <strong>disabled</strong>: its public pages 404 and its external API does not answer. Editing it here carries on.</span>
8
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="enable" data-bind-disabled="busy.value">Enable</button>
9
+ </p>
10
+ <p class="pdx-alert is-bad" data-bind-hidden="!loadError.value">
11
+ <span data-bind-text="loadError.value"></span>
12
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="retry">Try again</button>
13
+ </p>
127
14
 
128
- <div class="pd-wrap">
129
- <div class="pd-hero">
130
- <div class="pd-hero-icon"><span id="pd-icon" data-icon="folder"></span></div>
131
- <div class="pd-hero-main">
132
- <div class="pd-hero-top">
133
- <h2 class="pd-title"><span id="pd-name">Project</span></h2>
134
- <div class="pd-actions">
135
- <a class="btn btn-secondary btn-sm" href="#/projects"><span data-icon="arrow-left"></span> Projects</a>
136
- <a class="btn btn-secondary btn-sm" id="pd-settings"><span data-icon="settings"></span> Settings</a>
137
- </div>
15
+ <div class="pdx-hero">
16
+ <div class="pdx-hero-icon" aria-hidden="true"><span data-icon="folder" data-icon-size="32"></span></div>
17
+ <div class="pdx-hero-main">
18
+ <strong class="pdx-name" data-bind-text="name.value"></strong>
19
+ <p class="pdx-desc" data-bind-hidden="!description.value" data-bind-text="description.value"></p>
20
+ <div class="pdx-chips">
21
+ <span class="pdx-chip is-warn" data-bind-hidden="!off.value">Disabled</span>
22
+ <span class="pdx-chip is-ok" data-bind-hidden="off.value || loading.value">Enabled</span>
23
+ <span class="pdx-chip" data-bind-hidden="!builtIn.value" title="Built in - cannot be deleted">Built in</span>
24
+ <span class="pdx-chip" data-bind-text="rootText.value"></span>
25
+ <span data-help-title="Root URL" data-help="Pages whose address starts with the project's root URL are filed under it without being tagged one by one. Change it, or the name, icon and order, on the project's Settings (banner)."></span>
26
+ </div>
27
+ <div class="pdx-meta">
28
+ <span data-bind-hidden="!created.value"><span data-icon="calendar" data-icon-size="13"></span> Created <b data-bind-text="created.value"></b></span>
29
+ <span data-bind-hidden="!creator.value"><span data-icon="user" data-icon-size="13"></span> by <b data-bind-text="creator.value"></b></span>
30
+ <span data-bind-hidden="!updated.value" data-bind-title="updatedTitle.value"><span data-icon="clock" data-icon-size="13"></span> Changed <b data-bind-text="updated.value"></b></span>
138
31
  </div>
139
- <p id="pd-desc" class="pd-desc"></p>
140
- <div id="pd-meta" class="pd-meta"></div>
141
32
  </div>
142
33
  </div>
143
34
 
144
- <section>
145
- <div class="pd-section-head">
146
- <h3 class="pd-section-title"><span data-icon="layers"></span> Artefacts in this project</h3>
147
- <span id="pd-total" class="pd-total"></span>
35
+ <div class="pdx-card">
36
+ <div class="pdx-card-title">
37
+ <span data-icon="layers" data-icon-size="16"></span> Artefacts in this project
38
+ <small class="pdx-muted" data-bind-text="totalText.value"></small>
39
+ <span data-help-title="Artefacts" data-help="Everything filed under this project, by type. Click a type to open its list (or, when there are none, to make the first). The + makes a new one filed under this project - pages, collections, menus and blocks in a quick panel here, the rest in their own editor. Right-click a type (or ⋮, or Shift+F10) for both."></span>
148
40
  </div>
149
- <p class="pd-hint">Click the <strong>+</strong> on any card to create one, filed under this project automatically.</p>
150
- <div id="pd-counts"></div>
151
- </section>
41
+ <p class="pdx-note" data-bind-hidden="!loading.value">Loading…</p>
42
+ <div class="pdx-tiles" data-each="tiles.value key=key"></div>
43
+ </div>
152
44
  </div>
45
+
46
+ <style>
47
+ .pdx { display: flex; flex-direction: column; gap: .9rem; }
48
+ .pdx [hidden] { display: none !important; }
49
+ .pdx-alert { margin: 0; display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; padding: .6rem .8rem; border-radius: var(--dm-radius-md);
50
+ background: color-mix(in srgb, var(--dm-warning, var(--dm-primary)) 16%, transparent); color: var(--dm-text); }
51
+ .pdx-alert.is-bad { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); }
52
+ .pdx-alert > span:not([data-icon]) { flex: 1; min-width: 12rem; }
53
+ .pdx-hero { display: flex; gap: 1.1rem; align-items: flex-start; padding: 1.1rem 1.25rem; border: 1px solid var(--dm-border);
54
+ border-left: 4px solid var(--dm-primary); border-radius: var(--dm-radius-md);
55
+ background: radial-gradient(120% 160% at 0% 0%, color-mix(in srgb, var(--dm-primary) 10%, transparent), transparent 55%), var(--dm-surface); }
56
+ .pdx-hero-icon { width: 56px; height: 56px; flex-shrink: 0; border-radius: var(--dm-radius-md); display: flex; align-items: center; justify-content: center;
57
+ color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 16%, transparent); }
58
+ .pdx-hero-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .4rem; }
59
+ .pdx-name { font-size: var(--dm-text-xl, 1.4rem); line-height: 1.2; }
60
+ .pdx-desc { margin: 0; color: var(--dm-text-muted); max-width: 64ch; }
61
+ .pdx-chips { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; }
62
+ .pdx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .55rem; border-radius: 999px; white-space: nowrap;
63
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
64
+ .pdx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
65
+ .pdx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning, var(--dm-danger)) 18%, transparent); color: var(--dm-warning, var(--dm-danger)); }
66
+ .pdx-meta { display: flex; flex-wrap: wrap; gap: .9rem; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
67
+ .pdx-meta > span { display: inline-flex; align-items: center; gap: .3rem; }
68
+ .pdx-meta b { color: var(--dm-text); font-weight: 600; }
69
+ .pdx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .8rem .95rem; display: flex; flex-direction: column; gap: .7rem; }
70
+ .pdx-card-title { display: flex; align-items: center; gap: .45rem; font-weight: 600; }
71
+ .pdx-muted { color: var(--dm-text-muted); font-weight: 400; font-size: var(--dm-text-sm); }
72
+ .pdx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
73
+ .pdx-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: .7rem; }
74
+ .pdx-tile { position: relative; display: flex; align-items: center; gap: .75rem; padding: .75rem .8rem; cursor: pointer;
75
+ border: 1px solid var(--dm-border); border-left: 3px solid var(--c, var(--dm-primary)); border-radius: var(--dm-radius-md); background: var(--dm-surface);
76
+ transition: border-color .14s ease, background .14s ease; }
77
+ .pdx-tile:hover, .pdx-tile:focus-visible { border-color: var(--c, var(--dm-primary)); background: var(--dm-table-hover-bg, var(--dm-surface-raised)); outline: none; }
78
+ .pdx-tile.is-empty .pdx-tile-chip, .pdx-tile.is-empty .pdx-tile-body { opacity: .55; }
79
+ .pdx-tile-chip { width: 38px; height: 38px; flex-shrink: 0; border-radius: var(--dm-radius-md); display: flex; align-items: center; justify-content: center;
80
+ color: var(--c, var(--dm-primary)); background: color-mix(in srgb, var(--c, var(--dm-primary)) 14%, transparent); }
81
+ .pdx-tile-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
82
+ .pdx-tile-body strong { font-size: 1.4rem; line-height: 1; font-variant-numeric: tabular-nums; }
83
+ .pdx-tile-body small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); text-transform: uppercase; letter-spacing: .03em; margin-top: .2rem; }
84
+ .pdx-tile-actions { display: flex; flex-direction: column; gap: .2rem; align-self: flex-start; }
85
+ .pdx-add, .pdx-kebab { border: 1px solid transparent; background: none; cursor: pointer; color: var(--dm-text-muted); border-radius: var(--dm-radius-sm);
86
+ width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: var(--dm-text-base); line-height: 1; padding: 0; }
87
+ .pdx-add { color: var(--c, var(--dm-primary)); border-color: var(--dm-border); font-weight: 700; }
88
+ .pdx-add:hover, .pdx-kebab:hover { color: var(--dm-primary); border-color: var(--c, var(--dm-primary)); }
89
+ @media (max-width: 640px) { .pdx-hero { flex-direction: column; } }
90
+ </style>